systemprompt_models/mcp/
server.rs1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::path::{Path, PathBuf};
4use systemprompt_identifiers::UserId;
5
6use crate::ai::ToolModelConfig;
7use crate::auth::{AuthenticatedUser, Permission};
8use crate::mcp::deployment::McpServerType;
9
10pub const RUNNING: &str = "running";
11pub const ERROR: &str = "error";
12pub const STOPPED: &str = "stopped";
13pub const STARTING: &str = "starting";
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: String,
21 pub enabled: bool,
22 pub display_in_web: bool,
23 pub port: 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 pub fn endpoint(&self, api_server_url: &str) -> String {
70 format!("{}/api/v1/mcp/{}/mcp", api_server_url, self.name)
71 }
72
73 pub fn call_url(&self, api_server_url: &str) -> String {
74 if self.is_external() {
75 self.remote_endpoint.clone()
76 } else {
77 self.endpoint(api_server_url)
78 }
79 }
80
81 pub const fn is_internal(&self) -> bool {
82 matches!(self.server_type, McpServerType::Internal)
83 }
84
85 pub const fn is_external(&self) -> bool {
86 matches!(self.server_type, McpServerType::External)
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub enum McpAuthState {
92 Authenticated(AuthenticatedUser),
93 Anonymous,
94}
95
96impl McpAuthState {
97 pub const fn is_authenticated(&self) -> bool {
98 matches!(self, Self::Authenticated(_))
99 }
100
101 pub const fn is_anonymous(&self) -> bool {
102 matches!(self, Self::Anonymous)
103 }
104
105 pub const fn user(&self) -> Option<&AuthenticatedUser> {
106 match self {
107 Self::Authenticated(user) => Some(user),
108 Self::Anonymous => None,
109 }
110 }
111
112 pub fn has_permission(&self, permission: Permission) -> bool {
113 match self {
114 Self::Authenticated(user) => user.has_permission(permission),
115 Self::Anonymous => permission == Permission::Anonymous,
116 }
117 }
118
119 pub fn username(&self) -> String {
120 match self {
121 Self::Authenticated(user) => user.username.clone(),
122 Self::Anonymous => "anonymous".to_owned(),
123 }
124 }
125}