systemprompt_api/services/server/
discovery.rs1use 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}