Skip to main content

systemprompt_api/services/server/
discovery.rs

1use axum::routing::get;
2use axum::{Json, Router};
3use serde_json::json;
4use systemprompt_models::api::SingleResponse;
5use systemprompt_models::modules::ApiPaths;
6use systemprompt_runtime::AppContext;
7
8use super::health::handle_health;
9use super::health_detail::handle_health_detail;
10
11#[allow(clippy::unused_async)]
12pub async fn handle_root_discovery(
13    axum::extract::State(ctx): axum::extract::State<AppContext>,
14) -> impl axum::response::IntoResponse {
15    let base = &ctx.config().api_external_url;
16    let data = json!({
17        "name": format!("{} API", ctx.config().sitename),
18        "version": "1.0.0",
19        "description": "systemprompt.io OS API Gateway",
20        "endpoints": {
21            "health": format!("{}{}", base, ApiPaths::HEALTH),
22            "oauth": {
23                "href": format!("{}{}", base, ApiPaths::OAUTH_BASE),
24                "description": "OAuth2/OIDC authentication and WebAuthn",
25                "endpoints": {
26                    "authorize": format!("{}{}", base, ApiPaths::OAUTH_AUTHORIZE),
27                    "token": format!("{}{}", base, ApiPaths::OAUTH_TOKEN),
28                    "userinfo": format!("{}{}/userinfo", base, ApiPaths::OAUTH_BASE),
29                    "introspect": format!("{}{}/introspect", base, ApiPaths::OAUTH_BASE),
30                    "revoke": format!("{}{}/revoke", base, ApiPaths::OAUTH_BASE),
31                    "webauthn": format!("{}{}/webauthn", base, ApiPaths::OAUTH_BASE)
32                }
33            },
34            "core": {
35                "href": format!("{}{}", base, ApiPaths::CORE_BASE),
36                "description": "Core conversation, task, and artifact management",
37                "endpoints": {
38                    "contexts": format!("{}{}", base, ApiPaths::CORE_CONTEXTS),
39                    "tasks": format!("{}{}", base, ApiPaths::CORE_TASKS),
40                    "artifacts": format!("{}{}", base, ApiPaths::CORE_ARTIFACTS)
41                }
42            },
43            "agents": {
44                "href": format!("{}{}", base, ApiPaths::AGENTS_REGISTRY),
45                "description": "A2A protocol agent registry and proxy",
46                "endpoints": {
47                    "registry": format!("{}{}", base, ApiPaths::AGENTS_REGISTRY),
48                    "proxy": format!("{}{}{{agent_id}}", base, ApiPaths::AGENTS_BASE)
49                }
50            },
51            "mcp": {
52                "href": format!("{}{}", base, ApiPaths::MCP_REGISTRY),
53                "description": "MCP server registry and lifecycle management",
54                "endpoints": {
55                    "registry": format!("{}{}", base, ApiPaths::MCP_REGISTRY),
56                    "proxy": format!("{}{}{{server_name}}", base, ApiPaths::MCP_BASE)
57                }
58            },
59            "stream": {
60                "href": format!("{}{}", base, ApiPaths::STREAM_BASE),
61                "description": "Server-Sent Events (SSE) for real-time updates",
62                "endpoints": {
63                    "contexts": format!("{}{}", base, ApiPaths::STREAM_CONTEXTS)
64                }
65            }
66        },
67        "wellknown": {
68            "oauth": format!("{}{}", base, ApiPaths::WELLKNOWN_OAUTH_SERVER),
69            "agent": format!("{}{}", base, ApiPaths::WELLKNOWN_AGENT_CARD)
70        }
71    });
72
73    Json(SingleResponse::new(data))
74}
75
76#[allow(clippy::unused_async)]
77pub async fn handle_core_discovery(
78    axum::extract::State(ctx): axum::extract::State<AppContext>,
79) -> impl axum::response::IntoResponse {
80    let base = &ctx.config().api_external_url;
81    let data = json!({
82        "name": "Core Services",
83        "description": "Core conversation, task, and artifact management APIs",
84        "endpoints": {
85            "contexts": {
86                "href": format!("{}{}", base, ApiPaths::CORE_CONTEXTS),
87                "description": "Conversation context management",
88                "methods": ["GET", "POST", "DELETE"]
89            },
90            "tasks": {
91                "href": format!("{}{}", base, ApiPaths::CORE_TASKS),
92                "description": "Task management for agent operations",
93                "methods": ["GET", "POST", "PUT", "DELETE"]
94            },
95            "artifacts": {
96                "href": format!("{}{}", base, ApiPaths::CORE_ARTIFACTS),
97                "description": "Artifact storage and retrieval",
98                "methods": ["GET", "POST", "DELETE"]
99            },
100            "oauth": {
101                "href": format!("{}{}", base, ApiPaths::OAUTH_BASE),
102                "description": "OAuth2/OIDC authentication endpoints"
103            }
104        }
105    });
106    Json(SingleResponse::new(data))
107}
108
109#[allow(clippy::unused_async)]
110pub async fn handle_agents_discovery(
111    axum::extract::State(ctx): axum::extract::State<AppContext>,
112) -> impl axum::response::IntoResponse {
113    let base = &ctx.config().api_external_url;
114    let data = json!({
115        "name": "Agent Services",
116        "description": "A2A protocol agent registry and proxy",
117        "endpoints": {
118            "registry": {
119                "href": format!("{}{}", base, ApiPaths::AGENTS_REGISTRY),
120                "description": "List and discover available agents",
121                "methods": ["GET"]
122            },
123            "proxy": {
124                "href": format!("{}{}/<agent_id>/", base, ApiPaths::AGENTS_BASE),
125                "description": "Proxy requests to specific agents",
126                "methods": ["GET", "POST"]
127            }
128        }
129    });
130    Json(SingleResponse::new(data))
131}
132
133#[allow(clippy::unused_async)]
134pub async fn handle_mcp_discovery(
135    axum::extract::State(ctx): axum::extract::State<AppContext>,
136) -> impl axum::response::IntoResponse {
137    let base = &ctx.config().api_external_url;
138    let data = json!({
139        "name": "MCP Services",
140        "description": "Model Context Protocol server registry and proxy",
141        "endpoints": {
142            "registry": {
143                "href": format!("{}{}", base, ApiPaths::MCP_REGISTRY),
144                "description": "List and discover available MCP servers",
145                "methods": ["GET"]
146            },
147            "proxy": {
148                "href": format!("{}{}/<server_name>/mcp", base, ApiPaths::MCP_BASE),
149                "description": "Proxy requests to specific MCP servers",
150                "methods": ["GET", "POST"]
151            }
152        }
153    });
154    Json(SingleResponse::new(data))
155}
156
157pub fn discovery_router(ctx: &AppContext) -> Router {
158    Router::new()
159        .route(ApiPaths::DISCOVERY, get(handle_root_discovery))
160        .route(ApiPaths::HEALTH, get(handle_health))
161        .route("/health", get(handle_health))
162        .route(ApiPaths::CORE_BASE, get(handle_core_discovery))
163        .route(ApiPaths::AGENTS_BASE, get(handle_agents_discovery))
164        .route(ApiPaths::MCP_BASE, get(handle_mcp_discovery))
165        .with_state(ctx.clone())
166}
167
168pub fn authenticated_discovery_router(ctx: &AppContext) -> Router {
169    Router::new()
170        .route("/api/v1/health/detail", get(handle_health_detail))
171        .with_state(ctx.clone())
172}