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