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