1use std::sync::{
2 atomic::{AtomicU64, Ordering},
3 Arc,
4};
5
6use soma_application::{ExecutionContext, GatewayRouteScope, SomaApplication};
7use soma_config::McpConfig;
8use soma_domain::{AuthorizationMode, Principal, RequestId, Surface, TraceContext};
9use soma_mcp_server::ResponsePageStore;
10
11#[cfg(test)]
12#[path = "state_tests.rs"]
13mod tests;
14
15#[derive(Clone)]
16pub struct McpState {
17 application: Arc<SomaApplication>,
18 config: McpConfig,
19 authorization_mode: AuthorizationMode,
20 response_pages: ResponsePageStore,
21}
22
23impl McpState {
24 pub fn new(
25 application: Arc<SomaApplication>,
26 config: McpConfig,
27 authorization_mode: AuthorizationMode,
28 response_pages: ResponsePageStore,
29 ) -> Self {
30 Self {
31 application,
32 config,
33 authorization_mode,
34 response_pages,
35 }
36 }
37
38 pub fn application(&self) -> &SomaApplication {
39 self.application.as_ref()
40 }
41
42 pub fn config(&self) -> &McpConfig {
43 &self.config
44 }
45
46 pub fn with_server_name(mut self, server_name: impl Into<String>) -> Self {
47 self.config.server_name = server_name.into();
48 self
49 }
50
51 pub fn authorization_mode(&self) -> AuthorizationMode {
52 self.authorization_mode
53 }
54
55 pub fn response_pages(&self) -> &ResponsePageStore {
56 &self.response_pages
57 }
58
59 pub fn execution_context(
60 &self,
61 principal: Option<Principal>,
62 trace: Option<TraceContext>,
63 ) -> ExecutionContext {
64 ExecutionContext {
65 principal,
66 authorization_mode: self.authorization_mode,
67 surface: Surface::Mcp,
68 trace,
69 destructive_confirmation: Default::default(),
70 response_limit: None,
71 request_id: next_request_id(),
72 }
73 }
74}
75
76fn next_request_id() -> RequestId {
77 static REQUEST_SEQUENCE: AtomicU64 = AtomicU64::new(1);
78 let sequence = REQUEST_SEQUENCE.fetch_add(1, Ordering::Relaxed);
79 RequestId::new(format!("mcp-{}-{sequence}", std::process::id()))
80 .expect("generated MCP request ids are valid")
81}
82
83pub type McpRouteScope = GatewayRouteScope;