Skip to content
/ loom Public

Commit adcbabf

Browse files
ghuntleyclaude
andcommitted
Extend MCP endpoint with tools, resources, and prompts
Add comprehensive MCP capabilities: Tools: - list_weavers: List weavers owned by user - get_weaver: Get weaver details - delete_weaver: Delete a weaver - attach_weaver: Get connection info for weaver Resources (loom:// URIs): - threads, threads/{id}, threads/{id}/messages - repos, repos/{id} - weavers, weavers/{id} Prompts: - create-weaver: Recommended settings by language - code-review: Code review assistance - debug-session: Debugging help - explain-code: Code explanation - write-tests: Test generation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ddb6c10 commit adcbabf

File tree

7 files changed

+2028
-98
lines changed

7 files changed

+2028
-98
lines changed

crates/loom-server/src/routes/mcp/handler.rs

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ use crate::{api::AppState, auth_middleware::RequireAuth};
1515

1616
use super::{
1717
error::{McpError, McpErrorResponse},
18-
tools,
18+
prompts, resources, tools,
1919
types::{
20-
InitializeParams, InitializeResult, JsonRpcRequest, JsonRpcResponse, ToolsCallParams,
21-
ToolsListResult, JSONRPC_VERSION,
20+
InitializeParams, InitializeResult, JsonRpcRequest, JsonRpcResponse, PromptsGetParams,
21+
ResourcesReadParams, ToolsCallParams, ToolsListResult, JSONRPC_VERSION,
2222
},
2323
};
2424

@@ -94,6 +94,11 @@ async fn route_method(
9494
"notifications/initialized" => handle_initialized_notification(request),
9595
"tools/list" => handle_tools_list(request),
9696
"tools/call" => handle_tools_call(state, current_user, request).await,
97+
"resources/list" => handle_resources_list(state, current_user, request).await,
98+
"resources/read" => handle_resources_read(state, current_user, request).await,
99+
"resources/templates/list" => handle_resource_templates_list(request),
100+
"prompts/list" => handle_prompts_list(request),
101+
"prompts/get" => handle_prompts_get(request),
97102
"ping" => handle_ping(request),
98103
_ => Err(McpError::MethodNotFound(request.method.clone())),
99104
}
@@ -221,6 +226,87 @@ fn handle_ping(request: &JsonRpcRequest) -> Result<(JsonRpcResponse, Option<Stri
221226
Ok((response, None))
222227
}
223228

229+
/// Handle the resources/list method.
230+
async fn handle_resources_list(
231+
state: &AppState,
232+
current_user: &loom_server_auth::CurrentUser,
233+
request: &JsonRpcRequest,
234+
) -> Result<(JsonRpcResponse, Option<String>), McpError> {
235+
tracing::info!(
236+
user_id = %current_user.user.id,
237+
"MCP resources/list request"
238+
);
239+
240+
let result = resources::list_resources(state, current_user).await?;
241+
let response = JsonRpcResponse::success(request.id.clone(), serde_json::to_value(result)?);
242+
Ok((response, None))
243+
}
244+
245+
/// Handle the resources/read method.
246+
async fn handle_resources_read(
247+
state: &AppState,
248+
current_user: &loom_server_auth::CurrentUser,
249+
request: &JsonRpcRequest,
250+
) -> Result<(JsonRpcResponse, Option<String>), McpError> {
251+
let params: ResourcesReadParams = request
252+
.params
253+
.as_ref()
254+
.map(|v| serde_json::from_value(v.clone()))
255+
.transpose()
256+
.map_err(|e| McpError::InvalidParams(format!("Invalid resources/read params: {e}")))?
257+
.ok_or_else(|| McpError::InvalidParams("resources/read requires params".to_string()))?;
258+
259+
tracing::info!(
260+
uri = %params.uri,
261+
user_id = %current_user.user.id,
262+
"MCP resources/read request"
263+
);
264+
265+
let result = resources::read_resource(state, current_user, &params.uri).await?;
266+
let response = JsonRpcResponse::success(request.id.clone(), serde_json::to_value(result)?);
267+
Ok((response, None))
268+
}
269+
270+
/// Handle the resources/templates/list method.
271+
fn handle_resource_templates_list(
272+
request: &JsonRpcRequest,
273+
) -> Result<(JsonRpcResponse, Option<String>), McpError> {
274+
let result = resources::list_resource_templates();
275+
let response = JsonRpcResponse::success(request.id.clone(), serde_json::to_value(result)?);
276+
Ok((response, None))
277+
}
278+
279+
/// Handle the prompts/list method.
280+
fn handle_prompts_list(
281+
request: &JsonRpcRequest,
282+
) -> Result<(JsonRpcResponse, Option<String>), McpError> {
283+
let result = prompts::list_prompts();
284+
let response = JsonRpcResponse::success(request.id.clone(), serde_json::to_value(result)?);
285+
Ok((response, None))
286+
}
287+
288+
/// Handle the prompts/get method.
289+
fn handle_prompts_get(
290+
request: &JsonRpcRequest,
291+
) -> Result<(JsonRpcResponse, Option<String>), McpError> {
292+
let params: PromptsGetParams = request
293+
.params
294+
.as_ref()
295+
.map(|v| serde_json::from_value(v.clone()))
296+
.transpose()
297+
.map_err(|e| McpError::InvalidParams(format!("Invalid prompts/get params: {e}")))?
298+
.ok_or_else(|| McpError::InvalidParams("prompts/get requires params".to_string()))?;
299+
300+
tracing::info!(
301+
prompt_name = %params.name,
302+
"MCP prompts/get request"
303+
);
304+
305+
let result = prompts::get_prompt(&params.name, &params.arguments)?;
306+
let response = JsonRpcResponse::success(request.id.clone(), serde_json::to_value(result)?);
307+
Ok((response, None))
308+
}
309+
224310
impl From<serde_json::Error> for McpError {
225311
fn from(err: serde_json::Error) -> Self {
226312
McpError::Internal(format!("JSON serialization error: {err}"))

crates/loom-server/src/routes/mcp/mod.rs

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,32 @@
1212
//! - Format: JSON-RPC 2.0
1313
//! - Version: MCP 2025-11-25
1414
//!
15-
//! ## Available Tools
15+
//! ## Capabilities
1616
//!
17-
//! - `create_weaver`: Create an ephemeral Kubernetes pod for code execution
17+
//! ### Tools
18+
//!
19+
//! - `create_weaver`: Create an ephemeral Kubernetes pod
20+
//! - `list_weavers`: List weavers owned by the user
21+
//! - `get_weaver`: Get details of a specific weaver
22+
//! - `delete_weaver`: Delete a weaver
23+
//! - `attach_weaver`: Get connection info for a weaver
24+
//!
25+
//! ### Resources
26+
//!
27+
//! - `loom://threads` - List conversation threads
28+
//! - `loom://threads/{id}` - Thread details with messages
29+
//! - `loom://repos` - List connected repositories
30+
//! - `loom://repos/{id}` - Repository details
31+
//! - `loom://weavers` - List active weavers
32+
//! - `loom://weavers/{id}` - Weaver details
33+
//!
34+
//! ### Prompts
35+
//!
36+
//! - `create-weaver`: Create weaver with recommended settings
37+
//! - `code-review`: Code review assistance
38+
//! - `debug-session`: Debugging session
39+
//! - `explain-code`: Code explanation
40+
//! - `write-tests`: Test generation
1841
//!
1942
//! ## Example Usage
2043
//!
@@ -36,24 +59,19 @@
3659
//! POST /mcp
3760
//! { "jsonrpc": "2.0", "method": "tools/list", "id": 2 }
3861
//!
39-
//! // Call create_weaver
62+
//! // List resources
4063
//! POST /mcp
41-
//! {
42-
//! "jsonrpc": "2.0",
43-
//! "method": "tools/call",
44-
//! "params": {
45-
//! "name": "create_weaver",
46-
//! "arguments": {
47-
//! "image": "python:3.12",
48-
//! "org_id": "550e8400-e29b-41d4-a716-446655440000"
49-
//! }
50-
//! },
51-
//! "id": 3
52-
//! }
64+
//! { "jsonrpc": "2.0", "method": "resources/list", "id": 3 }
65+
//!
66+
//! // List prompts
67+
//! POST /mcp
68+
//! { "jsonrpc": "2.0", "method": "prompts/list", "id": 4 }
5369
//! ```
5470
5571
mod error;
5672
mod handler;
73+
mod prompts;
74+
mod resources;
5775
pub mod session;
5876
mod tools;
5977
mod types;

0 commit comments

Comments
 (0)