Skip to main content

systemprompt_models/mcp/
server.rs

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