Skip to main content

systemprompt_models/mcp/
server.rs

1//! MCP server configuration, its authentication state, and path serde.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::path::{Path, PathBuf};
9use systemprompt_identifiers::{McpServerId, ServiceName, UserId};
10
11use crate::ai::ToolModelConfig;
12use crate::auth::{AuthenticatedUser, Permission};
13use crate::mcp::deployment::McpServerType;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct McpServerConfig {
17    pub name: String,
18    pub owner: UserId,
19    pub server_type: McpServerType,
20    pub binary: Option<String>,
21    pub enabled: bool,
22    pub display_in_web: bool,
23    pub port: Option<u16>,
24    #[serde(
25        serialize_with = "serialize_path",
26        deserialize_with = "deserialize_path"
27    )]
28    pub crate_path: PathBuf,
29    pub display_name: String,
30    pub description: String,
31    pub capabilities: Vec<String>,
32    #[serde(default)]
33    pub schemas: Vec<super::deployment::SchemaDefinition>,
34    pub oauth: super::deployment::OAuthRequirement,
35    #[serde(default)]
36    pub tools: HashMap<String, super::deployment::ToolMetadata>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub model_config: Option<ToolModelConfig>,
39    #[serde(default)]
40    pub env_vars: Vec<String>,
41    pub version: String,
42    pub host: String,
43    pub module_name: String,
44    pub protocol: String,
45    #[serde(default)]
46    pub remote_endpoint: String,
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    pub external_auth: Option<super::deployment::ExternalAuth>,
49    #[serde(default)]
50    pub headers: HashMap<String, String>,
51}
52
53fn serialize_path<S>(path: &Path, serializer: S) -> Result<S::Ok, S::Error>
54where
55    S: serde::Serializer,
56{
57    path.to_string_lossy().serialize(serializer)
58}
59
60fn deserialize_path<'de, D>(deserializer: D) -> Result<PathBuf, D::Error>
61where
62    D: serde::Deserializer<'de>,
63{
64    let s = String::deserialize(deserializer)?;
65    Ok(PathBuf::from(s))
66}
67
68impl McpServerConfig {
69    #[must_use]
70    pub fn server_id(&self) -> McpServerId {
71        McpServerId::new(self.name.as_str())
72    }
73
74    #[must_use]
75    pub fn service_name(&self) -> ServiceName {
76        ServiceName::new(self.name.as_str())
77    }
78
79    pub fn endpoint(&self, api_server_url: &str) -> String {
80        format!("{}/api/v1/mcp/{}/mcp", api_server_url, self.name)
81    }
82
83    pub fn call_url(&self, api_server_url: &str) -> String {
84        if self.is_external() {
85            self.remote_endpoint.clone()
86        } else {
87            self.endpoint(api_server_url)
88        }
89    }
90
91    pub const fn is_internal(&self) -> bool {
92        matches!(self.server_type, McpServerType::Internal)
93    }
94
95    pub const fn is_external(&self) -> bool {
96        matches!(self.server_type, McpServerType::External)
97    }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub enum McpAuthState {
102    Authenticated(AuthenticatedUser),
103    Anonymous,
104}
105
106impl McpAuthState {
107    pub const fn is_authenticated(&self) -> bool {
108        matches!(self, Self::Authenticated(_))
109    }
110
111    pub const fn is_anonymous(&self) -> bool {
112        matches!(self, Self::Anonymous)
113    }
114
115    pub const fn user(&self) -> Option<&AuthenticatedUser> {
116        match self {
117            Self::Authenticated(user) => Some(user),
118            Self::Anonymous => None,
119        }
120    }
121
122    pub fn has_permission(&self, permission: Permission) -> bool {
123        match self {
124            Self::Authenticated(user) => user.has_permission(permission),
125            Self::Anonymous => permission == Permission::Anonymous,
126        }
127    }
128
129    pub fn username(&self) -> String {
130        match self {
131            Self::Authenticated(user) => user.username.clone(),
132            Self::Anonymous => "anonymous".to_owned(),
133        }
134    }
135}