Skip to main content

harn_vm/mcp_server/
server.rs

1use std::cell::RefCell;
2
3use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
4use tokio::sync::mpsc;
5
6use crate::mcp_elicit::{install_bus, ElicitationBus};
7use crate::mcp_progress::{
8    active_bus as active_progress_bus, install_active_bus as install_active_progress_bus,
9    is_valid_progress_token, scope_context, ProgressBus, ProgressContext,
10};
11use crate::mcp_protocol::{
12    self, apply_rc_result_envelope, enforce_request_protocol_version,
13    is_supported_protocol_version, parse_request_metadata, server_discover_result, McpCacheHint,
14    McpProtocolMode, McpRequestMetadata,
15};
16use crate::stdlib::json_to_vm_value;
17use crate::value::VmError;
18use crate::vm::Vm;
19
20use super::convert::{prompt_value_to_messages, vm_value_to_content, vm_value_to_json};
21use super::defs::{
22    McpCompletionSource, McpPromptDef, McpResourceDef, McpResourceTemplateDef, McpServerMetadata,
23    McpToolDef,
24};
25use super::uri::{match_uri_template, uri_template_variables};
26use super::PROTOCOL_VERSION;
27
28/// MCP server that exposes Harn tools, resources, and prompts over MCP JSON-RPC.
29pub struct McpServer {
30    server_name: String,
31    server_version: String,
32    tools: Vec<McpToolDef>,
33    resources: Vec<McpResourceDef>,
34    resource_templates: Vec<McpResourceTemplateDef>,
35    prompts: Vec<McpPromptDef>,
36    log_level: RefCell<String>,
37    /// Optional Server Card payload — advertised in the `initialize`
38    /// response's `serverInfo.card` field and exposed as a static
39    /// resource at the well-known URI `well-known://mcp-card`.
40    /// Populated by `harn serve mcp --card path/to/card.json`.
41    server_card: Option<serde_json::Value>,
42    instructions: Option<String>,
43}
44
45impl McpServer {
46    pub fn new(
47        server_name: String,
48        tools: Vec<McpToolDef>,
49        resources: Vec<McpResourceDef>,
50        resource_templates: Vec<McpResourceTemplateDef>,
51        prompts: Vec<McpPromptDef>,
52    ) -> Self {
53        Self {
54            server_name,
55            server_version: env!("CARGO_PKG_VERSION").to_string(),
56            tools,
57            resources,
58            resource_templates,
59            prompts,
60            log_level: RefCell::new("warning".to_string()),
61            server_card: None,
62            instructions: None,
63        }
64    }
65
66    /// Apply script-supplied server metadata from `mcp_server_metadata(...)`.
67    pub fn with_metadata(mut self, metadata: McpServerMetadata) -> Self {
68        if let Some(name) = metadata.name {
69            self.server_name = name;
70        }
71        if let Some(version) = metadata.version {
72            self.server_version = version;
73        }
74        self.instructions = metadata.instructions;
75        self
76    }
77
78    /// Attach a Server Card to be advertised over `initialize` and via
79    /// the `well-known://mcp-card` resource. Call on a freshly-built
80    /// `McpServer` before `run`.
81    pub fn with_server_card(mut self, card: serde_json::Value) -> Self {
82        self.server_card = Some(card);
83        self
84    }
85
86    /// Run the MCP server loop, reading JSON-RPC from stdin and writing
87    /// to stdout. The transport is split across three concurrent halves:
88    /// a stdin reader that demuxes responses to in-flight elicitation
89    /// requests away from new client requests, a stdout writer task
90    /// that drains the outbound queue, and the main dispatch loop that
91    /// owns the VM and processes new requests one at a time. This shape
92    /// is what allows a tool handler to call `mcp_elicit(...)` mid-flight
93    /// — the handler awaits an inbound response while the writer keeps
94    /// emitting the elicitation request.
95    pub async fn run(&self, vm: &mut Vm) -> Result<(), VmError> {
96        let (out_tx, mut out_rx) = mpsc::unbounded_channel::<serde_json::Value>();
97        let (in_tx, mut in_rx) = mpsc::unbounded_channel::<serde_json::Value>();
98        let bus = ElicitationBus::new(out_tx.clone());
99        let progress_bus = ProgressBus::from_mpsc(out_tx.clone());
100
101        let bus_for_reader = bus.clone();
102        let in_tx_reader = in_tx.clone();
103        let reader = tokio::spawn(async move {
104            let stdin = BufReader::new(tokio::io::stdin());
105            let mut lines = stdin.lines();
106            while let Ok(Some(line)) = lines.next_line().await {
107                let trimmed = line.trim();
108                if trimmed.is_empty() {
109                    continue;
110                }
111                let msg: serde_json::Value = match serde_json::from_str(trimmed) {
112                    Ok(v) => v,
113                    Err(_) => continue,
114                };
115                // Per JSON-RPC, responses (no `method`, has `id` + result/error)
116                // must not themselves be replied to. Route to the
117                // elicitation bus when we recognize the id; otherwise
118                // silently drop instead of letting the dispatcher
119                // bounce a "Method not found" back to the client.
120                if msg.get("method").is_none() {
121                    let _ = bus_for_reader.route_response(&msg);
122                    continue;
123                }
124                if in_tx_reader.send(msg).is_err() {
125                    break;
126                }
127            }
128        });
129        // Drop the inbound sender held by the spawn closure's parent
130        // scope so closing of stdin actually terminates the dispatcher.
131        drop(in_tx);
132
133        let writer = tokio::spawn(async move {
134            let mut stdout = tokio::io::stdout();
135            while let Some(msg) = out_rx.recv().await {
136                let mut line = match serde_json::to_string(&msg) {
137                    Ok(value) => value,
138                    Err(_) => continue,
139                };
140                line.push('\n');
141                if stdout.write_all(line.as_bytes()).await.is_err() {
142                    break;
143                }
144                if stdout.flush().await.is_err() {
145                    break;
146                }
147            }
148        });
149
150        // Make the bus visible to tool handlers running on this thread.
151        let previous_bus = install_bus(Some(bus));
152        let previous_progress = install_active_progress_bus(Some(progress_bus));
153
154        while let Some(msg) = in_rx.recv().await {
155            if let Some(response) = self.handle_json_rpc(msg, vm).await {
156                if out_tx.send(response).is_err() {
157                    break;
158                }
159            }
160        }
161
162        // Closing out_tx tells the writer task to drain and exit.
163        // Restoring the previous bus (rather than always wiping)
164        // keeps nested usages well-defined if they ever appear.
165        drop(out_tx);
166        install_bus(previous_bus);
167        install_active_progress_bus(previous_progress);
168
169        // Best-effort wait so the writer flushes any tail responses
170        // before the function returns. Reader task exits naturally
171        // when stdin closes.
172        let _ = writer.await;
173        reader.abort();
174        Ok(())
175    }
176
177    /// Handle one MCP JSON-RPC message. Notifications return `None`.
178    pub async fn handle_json_rpc(
179        &self,
180        msg: serde_json::Value,
181        vm: &mut Vm,
182    ) -> Option<serde_json::Value> {
183        let method = msg.get("method").and_then(|m| m.as_str()).unwrap_or("");
184        let id = msg.get("id").cloned()?;
185        let params = msg.get("params").cloned().unwrap_or(serde_json::json!({}));
186
187        let metadata = parse_request_metadata(&params);
188        let mode = match enforce_request_protocol_version(&id, &metadata) {
189            Ok(Some(mode)) => mode,
190            Ok(None) => McpProtocolMode::Legacy,
191            Err(response) => return Some(response),
192        };
193
194        if let Some(response) =
195            mcp_protocol::unsupported_client_bound_method_response(id.clone(), method)
196        {
197            return Some(response);
198        }
199
200        let response = match method {
201            mcp_protocol::METHOD_SERVER_DISCOVER => self.handle_server_discover(&id),
202            "initialize" => self.handle_initialize(&id, &params, &metadata),
203            "ping" => crate::jsonrpc::response(id.clone(), serde_json::json!({})),
204            "logging/setLevel" => self.handle_logging_set_level(&id, &params),
205            "harn.hitl.respond" => self.handle_hitl_respond(&id, &params).await,
206            "tools/list" => self.handle_tools_list(&id, &params),
207            "tools/call" => self.handle_tools_call(&id, &params, vm).await,
208            mcp_protocol::METHOD_TASKS_GET => self.handle_task_lookup(&id, &params),
209            mcp_protocol::METHOD_TASKS_RESULT => self.handle_task_lookup(&id, &params),
210            mcp_protocol::METHOD_TASKS_LIST => self.handle_tasks_list(&id, &params),
211            mcp_protocol::METHOD_TASKS_CANCEL => self.handle_task_lookup(&id, &params),
212            "resources/list" => self.handle_resources_list(&id, &params),
213            "resources/read" => self.handle_resources_read(&id, &params, vm).await,
214            "resources/subscribe" => self.handle_resources_subscribe(&id, &params),
215            "resources/unsubscribe" => self.handle_resources_unsubscribe(&id, &params),
216            "resources/templates/list" => self.handle_resource_templates_list(&id, &params),
217            "prompts/list" => self.handle_prompts_list(&id, &params),
218            "prompts/get" => self.handle_prompts_get(&id, &params, vm).await,
219            mcp_protocol::METHOD_COMPLETION_COMPLETE => {
220                self.handle_completion_complete(&id, &params, vm).await
221            }
222            _ => serde_json::json!({
223                "jsonrpc": "2.0",
224                "id": id,
225                "error": {
226                    "code": -32601,
227                    "message": format!("Method not found: {method}")
228                }
229            }),
230        };
231        Some(apply_envelope(
232            response,
233            mode,
234            cache_hint_for_method(method),
235        ))
236    }
237
238    fn server_capabilities(&self) -> serde_json::Map<String, serde_json::Value> {
239        let mut capabilities = serde_json::Map::new();
240        if !self.tools.is_empty() {
241            capabilities.insert("tools".into(), serde_json::json!({ "listChanged": true }));
242        }
243        if !self.resources.is_empty()
244            || !self.resource_templates.is_empty()
245            || self.server_card.is_some()
246        {
247            capabilities.insert(
248                "resources".into(),
249                serde_json::json!({ "listChanged": true, "subscribe": true }),
250            );
251        }
252        if !self.prompts.is_empty() {
253            capabilities.insert("prompts".into(), serde_json::json!({ "listChanged": true }));
254        }
255        capabilities.insert("logging".into(), serde_json::json!({}));
256        capabilities.insert("tasks".into(), mcp_protocol::tasks_capability());
257        capabilities.insert("completions".into(), mcp_protocol::completions_capability());
258        // Always advertise elicitation: any registered tool may decide
259        // at runtime whether to call `mcp_elicit(...)`, so capability
260        // negotiation can't be tied to a static check.
261        capabilities.insert("elicitation".into(), serde_json::json!({}));
262        capabilities
263    }
264
265    fn server_info_value(&self) -> serde_json::Value {
266        let mut server_info = serde_json::json!({
267            "name": self.server_name,
268            "version": self.server_version
269        });
270        if let Some(ref card) = self.server_card {
271            server_info["card"] = card.clone();
272        }
273        server_info
274    }
275
276    fn handle_server_discover(&self, id: &serde_json::Value) -> serde_json::Value {
277        let capabilities = serde_json::Value::Object(self.server_capabilities());
278        let result = server_discover_result(
279            capabilities,
280            self.server_info_value(),
281            self.instructions.as_deref(),
282        );
283        crate::jsonrpc::response(id.clone(), result)
284    }
285
286    fn handle_initialize(
287        &self,
288        id: &serde_json::Value,
289        params: &serde_json::Value,
290        metadata: &McpRequestMetadata,
291    ) -> serde_json::Value {
292        // Echo the version the client asked for when we support it,
293        // falling back to the stable default otherwise. Modern clients
294        // can also pin their target through RC `_meta.protocolVersion`,
295        // which `enforce_request_protocol_version` validated upstream.
296        let requested = metadata
297            .protocol_version
298            .as_deref()
299            .or_else(|| {
300                params
301                    .get("protocolVersion")
302                    .and_then(serde_json::Value::as_str)
303            })
304            .filter(|version| is_supported_protocol_version(version))
305            .unwrap_or(PROTOCOL_VERSION);
306        let mut result = serde_json::json!({
307            "jsonrpc": "2.0",
308            "id": id,
309            "result": {
310                "protocolVersion": requested,
311                "capabilities": self.server_capabilities(),
312                "serverInfo": self.server_info_value(),
313            }
314        });
315        if let Some(instructions) = self.instructions.as_ref() {
316            result["result"]["instructions"] = serde_json::json!(instructions);
317        }
318        result
319    }
320
321    fn handle_tools_list(
322        &self,
323        id: &serde_json::Value,
324        params: &serde_json::Value,
325    ) -> serde_json::Value {
326        let page = match mcp_protocol::mcp_list_page(params, self.tools.len(), "tools/list") {
327            Ok(page) => page,
328            Err(error) => return crate::jsonrpc::error_response(id.clone(), -32602, &error),
329        };
330        let tools: Vec<serde_json::Value> = self.tools[page.start..page.end]
331            .iter()
332            .map(|t| {
333                let mut entry = serde_json::json!({
334                    "name": t.name,
335                    "description": t.description,
336                    "inputSchema": t.input_schema,
337                });
338                if let Some(ref title) = t.title {
339                    entry["title"] = serde_json::json!(title);
340                }
341                if let Some(ref output_schema) = t.output_schema {
342                    entry["outputSchema"] = output_schema.clone();
343                }
344                if let Some(ref annotations) = t.annotations {
345                    entry["annotations"] = annotations.clone();
346                }
347                if let Some(ref icons) = t.icons {
348                    entry["icons"] = icons.clone();
349                }
350                entry["execution"] =
351                    mcp_protocol::tool_execution(mcp_protocol::McpToolTaskSupport::Forbidden);
352                entry
353            })
354            .collect();
355
356        let mut result = serde_json::json!({ "tools": tools });
357        if let Some(next_cursor) = page.next_cursor {
358            result["nextCursor"] = serde_json::json!(next_cursor);
359        }
360
361        serde_json::json!({
362            "jsonrpc": "2.0",
363            "id": id,
364            "result": result
365        })
366    }
367
368    async fn handle_tools_call(
369        &self,
370        id: &serde_json::Value,
371        params: &serde_json::Value,
372        vm: &mut Vm,
373    ) -> serde_json::Value {
374        let tool_name = params.get("name").and_then(|n| n.as_str()).unwrap_or("");
375        if mcp_protocol::requests_task_augmentation(params) {
376            return mcp_protocol::task_augmentation_error_response(
377                id.clone(),
378                "tools/call",
379                -32602,
380                "Tool does not support MCP task-augmented execution",
381                "This Harn MCP server executes registered Harn closures inline, so every tool advertises execution.taskSupport=\"forbidden\".",
382            );
383        }
384
385        let tool = match self.tools.iter().find(|t| t.name == tool_name) {
386            Some(t) => t,
387            None => {
388                return serde_json::json!({
389                    "jsonrpc": "2.0",
390                    "id": id,
391                    "error": { "code": -32602, "message": format!("Unknown tool: {tool_name}") }
392                });
393            }
394        };
395
396        let arguments = params
397            .get("arguments")
398            .cloned()
399            .unwrap_or(serde_json::json!({}));
400        // SPECULATIVE: validates draft MCP SEP-2356 file inputs.
401        // Revisit this when the MCP proposal is ratified.
402        if let Err(message) =
403            crate::mcp_file_upload::validate_file_inputs_for_call(&arguments, &tool.input_schema)
404        {
405            return serde_json::json!({
406                "jsonrpc": "2.0",
407                "id": id,
408                "result": {
409                    "content": [{ "type": "text", "text": message }],
410                    "isError": true,
411                },
412            });
413        }
414        let args_vm = json_to_vm_value(&arguments);
415
416        // Bind a per-call progress context so the handler (and any
417        // helpers it calls) can emit `notifications/progress` via
418        // `mcp_report_progress(...)`. The context is wired only when
419        // both the connection has a progress bus installed AND the
420        // client opted in via `_meta.progressToken`. Using
421        // `scope_context` (a tokio task-local) rather than a
422        // thread-local guard keeps concurrent tool calls isolated even
423        // when they share an OS thread via a `LocalSet`.
424        let progress_token = params
425            .pointer("/_meta/progressToken")
426            .cloned()
427            .filter(is_valid_progress_token);
428        let progress_ctx = progress_token
429            .and_then(|token| active_progress_bus().map(|bus| ProgressContext::new(bus, token)));
430
431        let result =
432            scope_context(progress_ctx, vm.call_closure_pub(&tool.handler, &[args_vm])).await;
433
434        match result {
435            Ok(value) => {
436                let content = vm_value_to_content(&value);
437                let mut call_result = serde_json::json!({
438                    "content": content,
439                    "isError": false
440                });
441                if tool.output_schema.is_some() {
442                    let text = value.display();
443                    let structured = match serde_json::from_str::<serde_json::Value>(&text) {
444                        Ok(v) => v,
445                        _ => serde_json::json!(text),
446                    };
447                    call_result["structuredContent"] = structured;
448                }
449                serde_json::json!({
450                    "jsonrpc": "2.0",
451                    "id": id,
452                    "result": call_result
453                })
454            }
455            Err(e) => serde_json::json!({
456                "jsonrpc": "2.0",
457                "id": id,
458                "result": {
459                    "content": [{ "type": "text", "text": tool_error_text(&e) }],
460                    "isError": true,
461                },
462            }),
463        }
464    }
465
466    fn handle_task_lookup(
467        &self,
468        id: &serde_json::Value,
469        params: &serde_json::Value,
470    ) -> serde_json::Value {
471        let task_id = params
472            .get("taskId")
473            .and_then(|value| value.as_str())
474            .unwrap_or_default();
475        serde_json::json!({
476            "jsonrpc": "2.0",
477            "id": id,
478            "error": {
479                "code": -32602,
480                "message": format!("Failed to retrieve task: task not found '{task_id}'")
481            }
482        })
483    }
484
485    fn handle_tasks_list(
486        &self,
487        id: &serde_json::Value,
488        _params: &serde_json::Value,
489    ) -> serde_json::Value {
490        serde_json::json!({
491            "jsonrpc": "2.0",
492            "id": id,
493            "result": { "tasks": [] }
494        })
495    }
496
497    async fn handle_hitl_respond(
498        &self,
499        id: &serde_json::Value,
500        params: &serde_json::Value,
501    ) -> serde_json::Value {
502        let response: crate::stdlib::hitl::HitlHostResponse =
503            match serde_json::from_value(params.clone()) {
504                Ok(response) => response,
505                Err(error) => {
506                    return serde_json::json!({
507                        "jsonrpc": "2.0",
508                        "id": id,
509                        "error": {
510                            "code": -32602,
511                            "message": format!("invalid harn.hitl.respond params: {error}"),
512                        }
513                    });
514                }
515            };
516        let cwd = std::env::current_dir().ok();
517        match crate::stdlib::hitl::append_hitl_response(cwd.as_deref(), response).await {
518            Ok(_) => serde_json::json!({
519                "jsonrpc": "2.0",
520                "id": id,
521                "result": { "ok": true }
522            }),
523            Err(error) => serde_json::json!({
524                "jsonrpc": "2.0",
525                "id": id,
526                "error": {
527                    "code": -32000,
528                    "message": error
529                }
530            }),
531        }
532    }
533
534    fn handle_resources_list(
535        &self,
536        id: &serde_json::Value,
537        params: &serde_json::Value,
538    ) -> serde_json::Value {
539        let mut all_resources = Vec::with_capacity(self.resources.len() + 1);
540        if self.server_card.is_some() {
541            all_resources.push(serde_json::json!({
542                "uri": "well-known://mcp-card",
543                "name": "Server Card",
544                "description": "MCP v2.1 Server Card advertising this server's identity and capabilities",
545                "mimeType": "application/json",
546            }));
547        }
548        all_resources.extend(self.resources.iter().map(|r| {
549            let mut entry = serde_json::json!({ "uri": r.uri, "name": r.name });
550            if let Some(ref title) = r.title {
551                entry["title"] = serde_json::json!(title);
552            }
553            if let Some(ref desc) = r.description {
554                entry["description"] = serde_json::json!(desc);
555            }
556            if let Some(ref mime) = r.mime_type {
557                entry["mimeType"] = serde_json::json!(mime);
558            }
559            entry
560        }));
561
562        let page = match mcp_protocol::mcp_list_page(params, all_resources.len(), "resources/list")
563        {
564            Ok(page) => page,
565            Err(error) => return crate::jsonrpc::error_response(id.clone(), -32602, &error),
566        };
567        let resources = all_resources[page.start..page.end].to_vec();
568
569        let mut result = serde_json::json!({ "resources": resources });
570        if let Some(next_cursor) = page.next_cursor {
571            result["nextCursor"] = serde_json::json!(next_cursor);
572        }
573
574        serde_json::json!({
575            "jsonrpc": "2.0",
576            "id": id,
577            "result": result
578        })
579    }
580
581    async fn handle_resources_read(
582        &self,
583        id: &serde_json::Value,
584        params: &serde_json::Value,
585        vm: &mut Vm,
586    ) -> serde_json::Value {
587        let uri = params.get("uri").and_then(|u| u.as_str()).unwrap_or("");
588
589        // Expose the Server Card at the well-known URI. Matches the
590        // HTTP convention (.well-known/mcp-card) but routed through
591        // the stdio resource protocol.
592        if uri == "well-known://mcp-card" {
593            if let Some(ref card) = self.server_card {
594                let content = serde_json::json!({
595                    "uri": uri,
596                    "text": serde_json::to_string(card).unwrap_or_else(|_| "{}".to_string()),
597                    "mimeType": "application/json",
598                });
599                return serde_json::json!({
600                    "jsonrpc": "2.0",
601                    "id": id,
602                    "result": { "contents": [content] }
603                });
604            }
605        }
606
607        // Static resources take precedence over templates.
608        if let Some(resource) = self.resources.iter().find(|r| r.uri == uri) {
609            let mut content = serde_json::json!({ "uri": resource.uri, "text": resource.text });
610            if let Some(ref mime) = resource.mime_type {
611                content["mimeType"] = serde_json::json!(mime);
612            }
613            return serde_json::json!({
614                "jsonrpc": "2.0",
615                "id": id,
616                "result": { "contents": [content] }
617            });
618        }
619
620        for tmpl in &self.resource_templates {
621            if let Some(args) = match_uri_template(&tmpl.uri_template, uri) {
622                let args_vm = json_to_vm_value(&serde_json::json!(args));
623                let result = vm.call_closure_pub(&tmpl.handler, &[args_vm]).await;
624                return match result {
625                    Ok(value) => {
626                        let mut content = serde_json::json!({
627                            "uri": uri,
628                            "text": value.display(),
629                        });
630                        if let Some(ref mime) = tmpl.mime_type {
631                            content["mimeType"] = serde_json::json!(mime);
632                        }
633                        serde_json::json!({
634                            "jsonrpc": "2.0",
635                            "id": id,
636                            "result": { "contents": [content] }
637                        })
638                    }
639                    Err(e) => serde_json::json!({
640                        "jsonrpc": "2.0",
641                        "id": id,
642                        "error": { "code": -32603, "message": format!("{e}") }
643                    }),
644                };
645            }
646        }
647
648        serde_json::json!({
649            "jsonrpc": "2.0",
650            "id": id,
651            "error": { "code": -32002, "message": format!("Resource not found: {uri}") }
652        })
653    }
654
655    fn handle_resources_subscribe(
656        &self,
657        id: &serde_json::Value,
658        params: &serde_json::Value,
659    ) -> serde_json::Value {
660        let uri = params.get("uri").and_then(|u| u.as_str()).unwrap_or("");
661        if !self.resource_uri_exists(uri) {
662            return serde_json::json!({
663                "jsonrpc": "2.0",
664                "id": id,
665                "error": { "code": -32002, "message": format!("Resource not found: {uri}") }
666            });
667        }
668        crate::jsonrpc::response(id.clone(), serde_json::json!({}))
669    }
670
671    fn handle_resources_unsubscribe(
672        &self,
673        id: &serde_json::Value,
674        _params: &serde_json::Value,
675    ) -> serde_json::Value {
676        crate::jsonrpc::response(id.clone(), serde_json::json!({}))
677    }
678
679    fn resource_uri_exists(&self, uri: &str) -> bool {
680        if uri == "well-known://mcp-card" {
681            return self.server_card.is_some();
682        }
683        self.resources.iter().any(|resource| resource.uri == uri)
684            || self
685                .resource_templates
686                .iter()
687                .any(|template| match_uri_template(&template.uri_template, uri).is_some())
688    }
689
690    fn handle_resource_templates_list(
691        &self,
692        id: &serde_json::Value,
693        params: &serde_json::Value,
694    ) -> serde_json::Value {
695        let page = match mcp_protocol::mcp_list_page(
696            params,
697            self.resource_templates.len(),
698            "resources/templates/list",
699        ) {
700            Ok(page) => page,
701            Err(error) => return crate::jsonrpc::error_response(id.clone(), -32602, &error),
702        };
703        let templates: Vec<serde_json::Value> = self.resource_templates[page.start..page.end]
704            .iter()
705            .map(|t| {
706                let mut entry =
707                    serde_json::json!({ "uriTemplate": t.uri_template, "name": t.name });
708                if let Some(ref title) = t.title {
709                    entry["title"] = serde_json::json!(title);
710                }
711                if let Some(ref desc) = t.description {
712                    entry["description"] = serde_json::json!(desc);
713                }
714                if let Some(ref mime) = t.mime_type {
715                    entry["mimeType"] = serde_json::json!(mime);
716                }
717                entry
718            })
719            .collect();
720
721        let mut result = serde_json::json!({ "resourceTemplates": templates });
722        if let Some(next_cursor) = page.next_cursor {
723            result["nextCursor"] = serde_json::json!(next_cursor);
724        }
725
726        serde_json::json!({
727            "jsonrpc": "2.0",
728            "id": id,
729            "result": result
730        })
731    }
732
733    fn handle_prompts_list(
734        &self,
735        id: &serde_json::Value,
736        params: &serde_json::Value,
737    ) -> serde_json::Value {
738        let page = match mcp_protocol::mcp_list_page(params, self.prompts.len(), "prompts/list") {
739            Ok(page) => page,
740            Err(error) => return crate::jsonrpc::error_response(id.clone(), -32602, &error),
741        };
742        let prompts: Vec<serde_json::Value> = self.prompts[page.start..page.end]
743            .iter()
744            .map(|p| {
745                let mut entry = serde_json::json!({ "name": p.name });
746                if let Some(ref title) = p.title {
747                    entry["title"] = serde_json::json!(title);
748                }
749                if let Some(ref desc) = p.description {
750                    entry["description"] = serde_json::json!(desc);
751                }
752                if let Some(ref args) = p.arguments {
753                    let args_json: Vec<serde_json::Value> = args
754                        .iter()
755                        .map(|a| {
756                            let mut arg =
757                                serde_json::json!({ "name": a.name, "required": a.required });
758                            if let Some(ref desc) = a.description {
759                                arg["description"] = serde_json::json!(desc);
760                            }
761                            arg
762                        })
763                        .collect();
764                    entry["arguments"] = serde_json::json!(args_json);
765                }
766                entry
767            })
768            .collect();
769
770        let mut result = serde_json::json!({ "prompts": prompts });
771        if let Some(next_cursor) = page.next_cursor {
772            result["nextCursor"] = serde_json::json!(next_cursor);
773        }
774
775        serde_json::json!({
776            "jsonrpc": "2.0",
777            "id": id,
778            "result": result
779        })
780    }
781
782    fn handle_logging_set_level(
783        &self,
784        id: &serde_json::Value,
785        params: &serde_json::Value,
786    ) -> serde_json::Value {
787        let level = params
788            .get("level")
789            .and_then(|l| l.as_str())
790            .unwrap_or("warning");
791        *self.log_level.borrow_mut() = level.to_string();
792        crate::jsonrpc::response(id.clone(), serde_json::json!({}))
793    }
794
795    async fn handle_prompts_get(
796        &self,
797        id: &serde_json::Value,
798        params: &serde_json::Value,
799        vm: &mut Vm,
800    ) -> serde_json::Value {
801        let name = params.get("name").and_then(|n| n.as_str()).unwrap_or("");
802
803        let prompt = match self.prompts.iter().find(|p| p.name == name) {
804            Some(p) => p,
805            None => {
806                return serde_json::json!({
807                    "jsonrpc": "2.0",
808                    "id": id,
809                    "error": { "code": -32602, "message": format!("Unknown prompt: {name}") }
810                });
811            }
812        };
813
814        let arguments = params
815            .get("arguments")
816            .cloned()
817            .unwrap_or(serde_json::json!({}));
818        let args_vm = json_to_vm_value(&arguments);
819
820        let result = vm.call_closure_pub(&prompt.handler, &[args_vm]).await;
821
822        match result {
823            Ok(value) => {
824                let messages = prompt_value_to_messages(&value);
825                serde_json::json!({
826                    "jsonrpc": "2.0",
827                    "id": id,
828                    "result": { "messages": messages }
829                })
830            }
831            Err(e) => serde_json::json!({
832                "jsonrpc": "2.0",
833                "id": id,
834                "error": { "code": -32603, "message": format!("{e}") }
835            }),
836        }
837    }
838
839    async fn handle_completion_complete(
840        &self,
841        id: &serde_json::Value,
842        params: &serde_json::Value,
843        vm: &mut Vm,
844    ) -> serde_json::Value {
845        let Some(ref_type) = params.pointer("/ref/type").and_then(|value| value.as_str()) else {
846            return crate::jsonrpc::error_response(
847                id.clone(),
848                -32602,
849                "completion ref.type is required",
850            );
851        };
852        match ref_type {
853            "ref/prompt" => self.complete_prompt_argument(id, params, vm).await,
854            "ref/resource" => {
855                self.complete_resource_template_argument(id, params, vm)
856                    .await
857            }
858            other => crate::jsonrpc::error_response(
859                id.clone(),
860                -32602,
861                &format!("Unsupported completion ref.type: {other}"),
862            ),
863        }
864    }
865
866    async fn complete_prompt_argument(
867        &self,
868        id: &serde_json::Value,
869        params: &serde_json::Value,
870        vm: &mut Vm,
871    ) -> serde_json::Value {
872        let name = params
873            .pointer("/ref/name")
874            .and_then(|value| value.as_str())
875            .unwrap_or("");
876        let argument_name = match completion_argument_name(params) {
877            Ok(name) => name,
878            Err(error) => return crate::jsonrpc::error_response(id.clone(), -32602, &error),
879        };
880        let value = completion_argument_value(params);
881        let prompt = match self.prompts.iter().find(|prompt| prompt.name == name) {
882            Some(prompt) => prompt,
883            None => {
884                return crate::jsonrpc::error_response(
885                    id.clone(),
886                    -32602,
887                    &format!("Unknown prompt: {name}"),
888                );
889            }
890        };
891        let Some(argument) = prompt
892            .arguments
893            .as_deref()
894            .unwrap_or_default()
895            .iter()
896            .find(|argument| argument.name == argument_name)
897        else {
898            return crate::jsonrpc::error_response(
899                id.clone(),
900                -32602,
901                &format!("Unknown prompt argument: {argument_name}"),
902            );
903        };
904        let candidates =
905            match completion_source_candidates(argument.completion.as_ref(), params, vm).await {
906                Ok(candidates) => candidates,
907                Err(error) => return crate::jsonrpc::error_response(id.clone(), -32603, &error),
908            };
909        crate::mcp_protocol::completion_result(id.clone(), candidates, value)
910    }
911
912    async fn complete_resource_template_argument(
913        &self,
914        id: &serde_json::Value,
915        params: &serde_json::Value,
916        vm: &mut Vm,
917    ) -> serde_json::Value {
918        let uri = params
919            .pointer("/ref/uri")
920            .and_then(|value| value.as_str())
921            .unwrap_or("");
922        let argument_name = match completion_argument_name(params) {
923            Ok(name) => name,
924            Err(error) => return crate::jsonrpc::error_response(id.clone(), -32602, &error),
925        };
926        let value = completion_argument_value(params);
927        let template = match self
928            .resource_templates
929            .iter()
930            .find(|template| template.uri_template == uri)
931        {
932            Some(template) => template,
933            None => {
934                return crate::jsonrpc::error_response(
935                    id.clone(),
936                    -32602,
937                    &format!("Unknown resource template: {uri}"),
938                );
939            }
940        };
941        if !uri_template_variables(&template.uri_template)
942            .iter()
943            .any(|name| name == argument_name)
944        {
945            return crate::jsonrpc::error_response(
946                id.clone(),
947                -32602,
948                &format!("Unknown resource template argument: {argument_name}"),
949            );
950        }
951        let candidates =
952            match completion_source_candidates(template.completions.get(argument_name), params, vm)
953                .await
954            {
955                Ok(candidates) => candidates,
956                Err(error) => return crate::jsonrpc::error_response(id.clone(), -32603, &error),
957            };
958        crate::mcp_protocol::completion_result(id.clone(), candidates, value)
959    }
960}
961
962/// Map a JSON-RPC method to its conservative cache hint. Read/list
963/// methods get a TTL; everything else is `None`, which still routes
964/// through [`apply_envelope`] so Modern clients see `resultType`.
965fn cache_hint_for_method(method: &str) -> Option<&'static McpCacheHint> {
966    const LIST: McpCacheHint = McpCacheHint::list_default();
967    const READ: McpCacheHint = McpCacheHint::read_default();
968    match method {
969        "tools/list"
970        | "resources/list"
971        | "resources/templates/list"
972        | "prompts/list"
973        | mcp_protocol::METHOD_TASKS_LIST => Some(&LIST),
974        "resources/read" => Some(&READ),
975        _ => None,
976    }
977}
978
979/// Stamp the RC `resultType`/cache-hint envelope onto a handler's
980/// response in one place. Error responses pass through untouched.
981fn tool_error_text(error: &VmError) -> String {
982    match error {
983        VmError::Thrown(value) => value.display(),
984        other => format!("{other}"),
985    }
986}
987
988fn apply_envelope(
989    mut response: serde_json::Value,
990    mode: McpProtocolMode,
991    hint: Option<&'static McpCacheHint>,
992) -> serde_json::Value {
993    if let Some(result) = response.get_mut("result") {
994        apply_rc_result_envelope(result, mode, hint);
995    }
996    response
997}
998
999fn completion_argument_name(params: &serde_json::Value) -> Result<&str, String> {
1000    params
1001        .pointer("/argument/name")
1002        .and_then(|value| value.as_str())
1003        .filter(|value| !value.is_empty())
1004        .ok_or_else(|| "completion argument.name is required".to_string())
1005}
1006
1007fn completion_argument_value(params: &serde_json::Value) -> &str {
1008    params
1009        .pointer("/argument/value")
1010        .and_then(|value| value.as_str())
1011        .unwrap_or_default()
1012}
1013
1014async fn completion_source_candidates(
1015    source: Option<&McpCompletionSource>,
1016    params: &serde_json::Value,
1017    vm: &mut Vm,
1018) -> Result<Vec<String>, String> {
1019    let Some(source) = source else {
1020        return Ok(Vec::new());
1021    };
1022    let mut candidates = source.values.clone();
1023    if let Some(handler) = source.handler.as_ref() {
1024        let request = json_to_vm_value(params);
1025        let value = vm
1026            .call_closure_pub(handler, &[request])
1027            .await
1028            .map_err(|error| format!("{error}"))?;
1029        candidates.extend(completion_candidates_from_json(&vm_value_to_json(&value)));
1030    }
1031    Ok(candidates)
1032}
1033
1034fn completion_candidates_from_json(value: &serde_json::Value) -> Vec<String> {
1035    match value {
1036        serde_json::Value::Array(items) => {
1037            items.iter().filter_map(json_completion_string).collect()
1038        }
1039        serde_json::Value::Object(map) => map
1040            .get("values")
1041            .or_else(|| map.get("completion").and_then(|value| value.get("values")))
1042            .map(completion_candidates_from_json)
1043            .unwrap_or_default(),
1044        _ => json_completion_string(value).into_iter().collect(),
1045    }
1046}
1047
1048fn json_completion_string(value: &serde_json::Value) -> Option<String> {
1049    match value {
1050        serde_json::Value::String(value) => Some(value.clone()),
1051        serde_json::Value::Number(_) | serde_json::Value::Bool(_) => Some(value.to_string()),
1052        _ => None,
1053    }
1054}