Skip to main content

harn_vm/mcp_server/
server.rs

1use super::convert::{prompt_value_to_messages, vm_value_to_content, vm_value_to_json};
2use super::defs::{
3    McpCompletionSource, McpPromptDef, McpResourceDef, McpResourceTemplateDef, McpServerMetadata,
4    McpToolDef,
5};
6use super::tools_schema::McpToolSet;
7use super::uri::{match_uri_template, uri_template_variables};
8use crate::mcp_progress::{
9    active_bus as active_progress_bus, install_active_bus as install_active_progress_bus,
10    is_valid_progress_token, scope_context, ProgressBus, ProgressContext,
11};
12use crate::mcp_protocol::{
13    self, apply_result_envelope, parse_request_metadata, server_discover_result, McpCacheHint,
14};
15use crate::stdlib::json_to_vm_value;
16use crate::value::VmError;
17use crate::vm::Vm;
18use std::sync::Mutex;
19use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
20use tokio::sync::mpsc;
21/// MCP server that exposes Harn tools, resources, and prompts over MCP JSON-RPC.
22pub struct McpServer {
23    server_name: String,
24    server_version: String,
25    tools: McpToolSet,
26    resources: Vec<McpResourceDef>,
27    resource_templates: Vec<McpResourceTemplateDef>,
28    prompts: Vec<McpPromptDef>,
29    /// Optional Server Card payload — advertised by `server/discover` in
30    /// `serverInfo.card` and exposed as a static
31    /// resource at the well-known URI `well-known://mcp-card`.
32    /// Populated by `harn serve mcp --card path/to/card.json`.
33    server_card: Option<serde_json::Value>,
34    instructions: Option<String>,
35    list_changes: bool,
36    connection: Mutex<mcp_protocol::McpServerSession>,
37    /// Tasks this server has handed out. Shared lifecycle with the
38    /// orchestrator server (`harn_vm::mcp_tasks`), so `tasks/get` answers the
39    /// same way on both.
40    tasks: crate::mcp_tasks::McpTaskStore,
41}
42
43/// One fully validated replacement for a live script-backed MCP server.
44///
45/// `anchor` retains adapter-owned resources, such as connector clients, for
46/// exactly as long as the VM whose handlers use them.
47pub struct McpServerReload<A> {
48    server: McpServer,
49    vm: Vm,
50    anchor: A,
51}
52
53impl<A> McpServerReload<A> {
54    pub fn new(server: McpServer, vm: Vm, anchor: A) -> Self {
55        Self { server, vm, anchor }
56    }
57}
58
59impl McpServer {
60    pub fn new(
61        server_name: String,
62        tools: McpToolSet,
63        resources: Vec<McpResourceDef>,
64        resource_templates: Vec<McpResourceTemplateDef>,
65        prompts: Vec<McpPromptDef>,
66    ) -> Self {
67        Self {
68            server_name,
69            server_version: env!("CARGO_PKG_VERSION").to_string(),
70            tools,
71            resources,
72            resource_templates,
73            prompts,
74            server_card: None,
75            instructions: None,
76            list_changes: false,
77            connection: Mutex::new(mcp_protocol::McpServerSession::default()),
78            tasks: crate::mcp_tasks::McpTaskStore::new(),
79        }
80    }
81
82    /// Apply script-supplied server metadata from `mcp_server_metadata(...)`.
83    pub fn with_metadata(mut self, metadata: McpServerMetadata) -> Self {
84        if let Some(name) = metadata.name {
85            self.server_name = name;
86        }
87        if let Some(version) = metadata.version {
88            self.server_version = version;
89        }
90        self.instructions = metadata.instructions;
91        self
92    }
93
94    /// Attach a Server Card to be advertised by `server/discover` and via
95    /// the `well-known://mcp-card` resource. Call on a freshly-built
96    /// `McpServer` before `run`.
97    pub fn with_server_card(mut self, card: serde_json::Value) -> Self {
98        self.server_card = Some(card);
99        self
100    }
101
102    /// Advertise and emit standard capability-list change notifications.
103    pub fn with_list_changes(mut self, enabled: bool) -> Self {
104        self.list_changes = enabled;
105        self
106    }
107
108    /// Run the stable MCP server loop over newline-delimited stdio.
109    /// Client input is handled by `input_required` response/retry rounds,
110    /// so the transport never has to demultiplex server-initiated requests.
111    pub async fn run(&mut self, vm: &mut Vm) -> Result<(), VmError> {
112        let (_reload_tx, mut reload_rx) = mpsc::unbounded_channel();
113        let mut anchor = ();
114        self.run_loop(vm, &mut anchor, &mut reload_rx, false).await
115    }
116
117    /// Run one stdio connection while applying complete validated runtime
118    /// replacements supplied by the owning adapter.
119    pub async fn run_reloadable<A>(
120        mut self,
121        mut vm: Vm,
122        mut anchor: A,
123        mut reload_rx: mpsc::UnboundedReceiver<Result<McpServerReload<A>, String>>,
124    ) -> Result<(), VmError> {
125        let result = self
126            .run_loop(&mut vm, &mut anchor, &mut reload_rx, true)
127            .await;
128        // Handlers may retain connector-backed values, so destroy the VM before
129        // releasing the adapter resource that keeps those connectors alive.
130        drop(vm);
131        drop(anchor);
132        result
133    }
134
135    async fn run_loop<A>(
136        &mut self,
137        vm: &mut Vm,
138        anchor: &mut A,
139        reload_rx: &mut mpsc::UnboundedReceiver<Result<McpServerReload<A>, String>>,
140        mut reload_enabled: bool,
141    ) -> Result<(), VmError> {
142        let (out_tx, mut out_rx) = mpsc::unbounded_channel::<serde_json::Value>();
143        let progress_bus = ProgressBus::from_mpsc(out_tx.clone());
144
145        let writer = tokio::spawn(async move {
146            let mut stdout = tokio::io::stdout();
147            while let Some(msg) = out_rx.recv().await {
148                let mut line = match serde_json::to_string(&msg) {
149                    Ok(value) => value,
150                    Err(_) => continue,
151                };
152                line.push('\n');
153                if stdout.write_all(line.as_bytes()).await.is_err() {
154                    break;
155                }
156                if stdout.flush().await.is_err() {
157                    break;
158                }
159            }
160        });
161
162        let previous_progress = install_active_progress_bus(Some(progress_bus));
163        let stdin = BufReader::new(tokio::io::stdin());
164        let mut lines = stdin.lines();
165        loop {
166            tokio::select! {
167                line = lines.next_line() => {
168                    let Ok(Some(line)) = line else {
169                        break;
170                    };
171                    let trimmed = line.trim();
172                    if trimmed.is_empty() {
173                        continue;
174                    }
175                    let Ok(msg) = serde_json::from_str::<serde_json::Value>(trimmed) else {
176                        continue;
177                    };
178                    // Stable MCP stdio is request/response. Stray responses have no
179                    // server-initiated request to match and are ignored.
180                    if msg.get("method").is_none() {
181                        continue;
182                    }
183                    if let Some(response) = self.handle_json_rpc(msg, vm).await {
184                        if out_tx.send(response).is_err() {
185                            break;
186                        }
187                    }
188                }
189                replacement = reload_rx.recv(), if reload_enabled => {
190                    match replacement {
191                        Some(Ok(replacement)) => {
192                            let McpServerReload {
193                                server,
194                                vm: next_vm,
195                                anchor: next_anchor,
196                            } = replacement;
197                            self.apply_reload(server);
198                            let previous_vm = std::mem::replace(vm, next_vm);
199                            let previous_anchor = std::mem::replace(anchor, next_anchor);
200                            drop(previous_vm);
201                            drop(previous_anchor);
202                            if self
203                                .connection
204                                .lock()
205                                .expect("MCP session lock poisoned")
206                                .is_ready_for_notifications()
207                            {
208                                let _ = out_tx.send(serde_json::json!({
209                                    "jsonrpc": "2.0",
210                                    "method": "notifications/tools/list_changed",
211                                    "params": {},
212                                }));
213                                let _ = out_tx.send(serde_json::json!({
214                                    "jsonrpc": "2.0",
215                                    "method": "notifications/resources/list_changed",
216                                    "params": {},
217                                }));
218                                let _ = out_tx.send(serde_json::json!({
219                                    "jsonrpc": "2.0",
220                                    "method": "notifications/prompts/list_changed",
221                                    "params": {},
222                                }));
223                            }
224                        }
225                        Some(Err(error)) => {
226                            eprintln!(
227                                "[harn] serve mcp: reload failed; keeping previous registry: {error}"
228                            );
229                        }
230                        None => {
231                            eprintln!(
232                                "[harn] serve mcp: source reload task stopped; keeping current registry"
233                            );
234                            reload_enabled = false;
235                        }
236                    }
237                }
238            }
239        }
240
241        // Closing out_tx tells the writer task to drain and exit.
242        drop(out_tx);
243        install_active_progress_bus(previous_progress);
244
245        // Best-effort wait so the writer flushes any tail responses
246        // before the function returns.
247        let _ = writer.await;
248        Ok(())
249    }
250
251    fn apply_reload(&mut self, replacement: McpServer) {
252        self.server_name = replacement.server_name;
253        self.server_version = replacement.server_version;
254        self.tools = replacement.tools;
255        self.resources = replacement.resources;
256        self.resource_templates = replacement.resource_templates;
257        self.prompts = replacement.prompts;
258        self.server_card = replacement.server_card;
259        self.instructions = replacement.instructions;
260    }
261
262    /// Handle one MCP JSON-RPC message. Notifications return `None`.
263    pub async fn handle_json_rpc(
264        &self,
265        msg: serde_json::Value,
266        vm: &mut Vm,
267    ) -> Option<serde_json::Value> {
268        let method = msg.get("method").and_then(|m| m.as_str()).unwrap_or("");
269        if method == "notifications/initialized" {
270            self.connection
271                .lock()
272                .expect("MCP session lock poisoned")
273                .accept_initialized_notification();
274            return None;
275        }
276        let id = msg.get("id").cloned()?;
277        let params = msg.get("params").cloned().unwrap_or(serde_json::json!({}));
278
279        if method == "initialize" {
280            let result = self
281                .connection
282                .lock()
283                .expect("MCP session lock poisoned")
284                .initialize(
285                    &params,
286                    serde_json::Value::Object(self.server_capabilities()),
287                    self.server_info_value(),
288                    self.instructions.as_deref(),
289                );
290            return Some(match result {
291                Ok(result) => crate::jsonrpc::response(id, result),
292                Err(error) => crate::jsonrpc::error_response(id, -32602, &error),
293            });
294        }
295
296        let request_profile = match self
297            .connection
298            .lock()
299            .expect("MCP session lock poisoned")
300            .accept_request(&id, method, &params)
301        {
302            Ok(profile) => profile,
303            Err(response) => return Some(response),
304        };
305
306        if let Some(response) =
307            mcp_protocol::explicit_unsupported_method_response(id.clone(), method)
308        {
309            return Some(response);
310        }
311        if request_profile.uses_result_envelope()
312            && mcp_protocol::is_task_method(method)
313            && !mcp_protocol::client_supports_tasks(&params)
314        {
315            return Some(mcp_protocol::missing_tasks_capability_response(id.clone()));
316        }
317
318        let response = match method {
319            mcp_protocol::METHOD_SERVER_DISCOVER => self.handle_server_discover(&id),
320            "ping" => crate::jsonrpc::response(id.clone(), serde_json::json!({})),
321            "harn.hitl.respond" => self.handle_hitl_respond(&id, &params).await,
322            "tools/list" => self.handle_tools_list(&id, &params),
323            "tools/call" => {
324                self.handle_tools_call(&id, &params, vm, request_profile.uses_result_envelope())
325                    .await
326            }
327            mcp_protocol::METHOD_TASKS_GET => self.tasks.handle_get(
328                &crate::mcp_tasks::McpTaskAccess::unscoped(),
329                id.clone(),
330                &params,
331            ),
332            mcp_protocol::METHOD_TASKS_UPDATE => self.tasks.handle_update(
333                &crate::mcp_tasks::McpTaskAccess::unscoped(),
334                id.clone(),
335                &params,
336            ),
337            mcp_protocol::METHOD_TASKS_CANCEL => self.tasks.handle_cancel(
338                &crate::mcp_tasks::McpTaskAccess::unscoped(),
339                id.clone(),
340                &params,
341            ),
342            "resources/list" => self.handle_resources_list(&id, &params),
343            "resources/read" => self.handle_resources_read(&id, &params, vm).await,
344            "resources/templates/list" => self.handle_resource_templates_list(&id, &params),
345            "prompts/list" => self.handle_prompts_list(&id, &params),
346            "prompts/get" => self.handle_prompts_get(&id, &params, vm).await,
347            mcp_protocol::METHOD_COMPLETION_COMPLETE => {
348                self.handle_completion_complete(&id, &params, vm).await
349            }
350            _ => serde_json::json!({
351                "jsonrpc": "2.0",
352                "id": id,
353                "error": {
354                    "code": -32601,
355                    "message": format!("Method not found: {method}")
356                }
357            }),
358        };
359        Some(if request_profile.uses_result_envelope() {
360            apply_envelope(response, cache_hint_for_method(method))
361        } else {
362            response
363        })
364    }
365
366    fn server_capabilities(&self) -> serde_json::Map<String, serde_json::Value> {
367        let mut capabilities = serde_json::Map::new();
368        if !self.tools.is_empty() || self.list_changes {
369            capabilities.insert(
370                "tools".into(),
371                if self.list_changes {
372                    serde_json::json!({"listChanged": true})
373                } else {
374                    serde_json::json!({})
375                },
376            );
377        }
378        if !self.resources.is_empty()
379            || !self.resource_templates.is_empty()
380            || self.server_card.is_some()
381            || self.list_changes
382        {
383            capabilities.insert(
384                "resources".into(),
385                if self.list_changes {
386                    serde_json::json!({"listChanged": true})
387                } else {
388                    serde_json::json!({})
389                },
390            );
391        }
392        if !self.prompts.is_empty() || self.list_changes {
393            capabilities.insert(
394                "prompts".into(),
395                if self.list_changes {
396                    serde_json::json!({"listChanged": true})
397                } else {
398                    serde_json::json!({})
399                },
400            );
401        }
402        let mut extensions = mcp_protocol::tasks_capability();
403        capabilities.insert("completions".into(), mcp_protocol::completions_capability());
404        if self.resources.iter().any(|resource| {
405            resource.uri.starts_with("ui://")
406                && resource.mime_type.as_deref() == Some("text/html;profile=mcp-app")
407        }) {
408            extensions["io.modelcontextprotocol/ui"] = serde_json::json!({
409                "mimeTypes": ["text/html;profile=mcp-app"]
410            });
411        }
412        capabilities.insert("extensions".into(), extensions);
413        capabilities
414    }
415
416    fn server_info_value(&self) -> serde_json::Value {
417        let mut server_info = serde_json::json!({
418            "name": self.server_name,
419            "version": self.server_version
420        });
421        if let Some(ref card) = self.server_card {
422            server_info["card"] = card.clone();
423        }
424        server_info
425    }
426
427    fn handle_server_discover(&self, id: &serde_json::Value) -> serde_json::Value {
428        let capabilities = serde_json::Value::Object(self.server_capabilities());
429        let result = server_discover_result(
430            capabilities,
431            self.server_info_value(),
432            self.instructions.as_deref(),
433        );
434        crate::jsonrpc::response(id.clone(), result)
435    }
436
437    fn handle_tools_list(
438        &self,
439        id: &serde_json::Value,
440        params: &serde_json::Value,
441    ) -> serde_json::Value {
442        let prepared_tools = self.tools.prepared().mcp_tools();
443        let page = match mcp_protocol::mcp_list_page(params, prepared_tools.len(), "tools/list") {
444            Ok(page) => page,
445            Err(error) => return crate::jsonrpc::error_response(id.clone(), -32602, &error),
446        };
447        let tools = prepared_tools[page.start..page.end].to_vec();
448
449        let mut result = serde_json::json!({ "tools": tools });
450        if let Some(next_cursor) = page.next_cursor {
451            result["nextCursor"] = serde_json::json!(next_cursor);
452        }
453
454        serde_json::json!({
455            "jsonrpc": "2.0",
456            "id": id,
457            "result": result
458        })
459    }
460
461    async fn handle_tools_call(
462        &self,
463        id: &serde_json::Value,
464        params: &serde_json::Value,
465        vm: &mut Vm,
466        uses_result_envelope: bool,
467    ) -> serde_json::Value {
468        let tool_name = params.get("name").and_then(|n| n.as_str()).unwrap_or("");
469        let tool = match self
470            .tools
471            .iter()
472            .find(|tool| tool.catalog.name == tool_name)
473        {
474            Some(t) => t,
475            None => {
476                return serde_json::json!({
477                    "jsonrpc": "2.0",
478                    "id": id,
479                    "error": { "code": -32602, "message": format!("Unknown tool: {tool_name}") }
480                });
481            }
482        };
483
484        // A tool that declares `required` refuses a plain call outright, so a
485        // client cannot get the work done by simply not asking for a task.
486        let wants_task = mcp_protocol::client_supports_tasks(params);
487        let task_support = tool
488            .catalog
489            .execution
490            .as_ref()
491            .map(|execution| execution.task_support)
492            .unwrap_or_default();
493        let as_task = task_support.allows_task() && wants_task;
494        if task_support == crate::mcp_tasks::McpTaskSupport::Required && !wants_task {
495            return mcp_protocol::missing_tasks_capability_response(id.clone());
496        }
497
498        let arguments = params
499            .get("arguments")
500            .cloned()
501            .unwrap_or(serde_json::json!({}));
502        if let Err(error) = self.tools.prepared().validate_input(tool_name, &arguments) {
503            return crate::jsonrpc::error_response(id.clone(), -32602, &error.to_string());
504        }
505        let args_vm = json_to_vm_value(&arguments);
506
507        // Bind a per-call progress context so the handler (and any
508        // helpers it calls) can emit `notifications/progress` via
509        // `mcp_report_progress(...)`. The context is wired only when
510        // both the connection has a progress bus installed AND the
511        // client opted in via `_meta.progressToken`. Using
512        // `scope_context` (a tokio task-local) rather than a
513        // thread-local guard keeps concurrent tool calls isolated even
514        // when they share an OS thread via a `LocalSet`.
515        let progress_token = params
516            .pointer("/_meta/progressToken")
517            .cloned()
518            .filter(is_valid_progress_token);
519        let progress_ctx = progress_token
520            .and_then(|token| active_progress_bus().map(|bus| ProgressContext::new(bus, token)));
521
522        let result = match crate::mcp_input::scope_input_context(
523            params,
524            client_capabilities(params),
525            scope_context(
526                progress_ctx,
527                crate::tool_handler_scope::scope(vm.call_closure_pub(&tool.handler, &[args_vm])),
528            ),
529        )
530        .await
531        {
532            Ok(result) => result,
533            Err(error) => return crate::jsonrpc::error_response(id.clone(), -32602, &error),
534        };
535
536        // A task call still ran to completion above. This server holds one VM
537        // and drives it from the request thread, so it has nowhere to put
538        // in-flight work; what the extension buys a client here is the
539        // lifecycle, not concurrency. That is worth serving honestly: the
540        // client's poll loop, reconnect-and-collect, and cancel-before-read all
541        // behave, whereas the previous stub advertised the capability and told
542        // every `tasks/get` its task did not exist.
543        if as_task {
544            let lease = match self.tasks.begin(
545                crate::mcp_tasks::McpTaskAccess::unscoped(),
546                Some(crate::mcp_tasks::DEFAULT_TASK_TTL_MS),
547            ) {
548                Ok(lease) => lease,
549                Err(error) => {
550                    return crate::jsonrpc::error_response(id.clone(), -32000, &error.to_string());
551                }
552            };
553            let task = lease.task().clone();
554            match crate::tool_registry::classify_tool_result(
555                self.tools.prepared(),
556                &tool.catalog.name,
557                result,
558            ) {
559                Ok(crate::tool_registry::ToolInvocationOutcome::Success { value, json }) => {
560                    match successful_tool_call_result(&self.tools, tool, &value, json) {
561                        Ok(result) => lease.complete_with_tool_result(result, uses_result_envelope),
562                        Err(error) => lease.complete(Err(error), uses_result_envelope),
563                    }
564                }
565                Ok(crate::tool_registry::ToolInvocationOutcome::ApplicationError(error)) => lease
566                    .complete_with_tool_result(
567                        crate::tool_registry::application_error_mcp_result(&error),
568                        uses_result_envelope,
569                    ),
570                Err(crate::tool_registry::ToolInvocationError::Runtime(
571                    VmError::McpInputRequired(_),
572                )) => lease.complete(
573                    Err("Tool requested client input, which a task cannot carry".to_string()),
574                    uses_result_envelope,
575                ),
576                Err(error) => lease.complete_with_tool_result(
577                    serde_json::json!({
578                        "content": [{"type": "text", "text": error.to_string()}],
579                        "isError": true,
580                    }),
581                    uses_result_envelope,
582                ),
583            }
584            return crate::mcp_tasks::task_created_response(
585                id.clone(),
586                &task,
587                "The requested Harn tool ran and its result is available via tasks/get.",
588            );
589        }
590
591        match crate::tool_registry::classify_tool_result(
592            self.tools.prepared(),
593            &tool.catalog.name,
594            result,
595        ) {
596            Ok(crate::tool_registry::ToolInvocationOutcome::Success { value, json }) => {
597                match successful_tool_call_result(&self.tools, tool, &value, json) {
598                    Ok(result) => serde_json::json!({
599                        "jsonrpc": "2.0",
600                        "id": id,
601                        "result": result
602                    }),
603                    Err(error) => crate::jsonrpc::response(
604                        id.clone(),
605                        serde_json::json!({
606                            "content": [{"type": "text", "text": error}],
607                            "isError": true,
608                        }),
609                    ),
610                }
611            }
612            Ok(crate::tool_registry::ToolInvocationOutcome::ApplicationError(error)) => {
613                crate::jsonrpc::response(
614                    id.clone(),
615                    crate::tool_registry::application_error_mcp_result(&error),
616                )
617            }
618            Err(crate::tool_registry::ToolInvocationError::Runtime(VmError::McpInputRequired(
619                required,
620            ))) => crate::jsonrpc::response(id.clone(), crate::mcp_input::input_result(*required)),
621            Err(error) => serde_json::json!({
622                "jsonrpc": "2.0",
623                "id": id,
624                "result": {
625                    "content": [{ "type": "text", "text": error.to_string() }],
626                    "isError": true,
627                },
628            }),
629        }
630    }
631
632    async fn handle_hitl_respond(
633        &self,
634        id: &serde_json::Value,
635        params: &serde_json::Value,
636    ) -> serde_json::Value {
637        let response: crate::stdlib::hitl::HitlHostResponse =
638            match serde_json::from_value(params.clone()) {
639                Ok(response) => response,
640                Err(error) => {
641                    return serde_json::json!({
642                        "jsonrpc": "2.0",
643                        "id": id,
644                        "error": {
645                            "code": -32602,
646                            "message": format!("invalid harn.hitl.respond params: {error}"),
647                        }
648                    });
649                }
650            };
651        let cwd = std::env::current_dir().ok();
652        match crate::stdlib::hitl::append_hitl_response(cwd.as_deref(), response).await {
653            Ok(_) => serde_json::json!({
654                "jsonrpc": "2.0",
655                "id": id,
656                "result": { "ok": true }
657            }),
658            Err(error) => serde_json::json!({
659                "jsonrpc": "2.0",
660                "id": id,
661                "error": {
662                    "code": -32000,
663                    "message": error
664                }
665            }),
666        }
667    }
668
669    fn handle_resources_list(
670        &self,
671        id: &serde_json::Value,
672        params: &serde_json::Value,
673    ) -> serde_json::Value {
674        let mut all_resources = Vec::with_capacity(self.resources.len() + 1);
675        if self.server_card.is_some() {
676            all_resources.push(serde_json::json!({
677                "uri": "well-known://mcp-card",
678                "name": "Server Card",
679                "description": "MCP v2.1 Server Card advertising this server's identity and capabilities",
680                "mimeType": "application/json",
681            }));
682        }
683        all_resources.extend(self.resources.iter().map(|r| {
684            let mut entry = serde_json::json!({ "uri": r.uri, "name": r.name });
685            if let Some(ref title) = r.title {
686                entry["title"] = serde_json::json!(title);
687            }
688            if let Some(ref desc) = r.description {
689                entry["description"] = serde_json::json!(desc);
690            }
691            if let Some(ref mime) = r.mime_type {
692                entry["mimeType"] = serde_json::json!(mime);
693            }
694            if let Some(ref meta) = r.meta {
695                entry["_meta"] = meta.clone();
696            }
697            entry
698        }));
699
700        let page = match mcp_protocol::mcp_list_page(params, all_resources.len(), "resources/list")
701        {
702            Ok(page) => page,
703            Err(error) => return crate::jsonrpc::error_response(id.clone(), -32602, &error),
704        };
705        let resources = all_resources[page.start..page.end].to_vec();
706
707        let mut result = serde_json::json!({ "resources": resources });
708        if let Some(next_cursor) = page.next_cursor {
709            result["nextCursor"] = serde_json::json!(next_cursor);
710        }
711
712        serde_json::json!({
713            "jsonrpc": "2.0",
714            "id": id,
715            "result": result
716        })
717    }
718
719    async fn handle_resources_read(
720        &self,
721        id: &serde_json::Value,
722        params: &serde_json::Value,
723        vm: &mut Vm,
724    ) -> serde_json::Value {
725        let uri = params.get("uri").and_then(|u| u.as_str()).unwrap_or("");
726
727        // Expose the Server Card at the well-known URI. Matches the
728        // HTTP convention (.well-known/mcp-card) but routed through
729        // the stdio resource protocol.
730        if uri == "well-known://mcp-card" {
731            if let Some(ref card) = self.server_card {
732                let content = serde_json::json!({
733                    "uri": uri,
734                    "text": serde_json::to_string(card).unwrap_or_else(|_| "{}".to_string()),
735                    "mimeType": "application/json",
736                });
737                return serde_json::json!({
738                    "jsonrpc": "2.0",
739                    "id": id,
740                    "result": { "contents": [content] }
741                });
742            }
743        }
744
745        // Static resources take precedence over templates.
746        if let Some(resource) = self.resources.iter().find(|r| r.uri == uri) {
747            let mut content = serde_json::json!({ "uri": resource.uri, "text": resource.text });
748            if let Some(ref mime) = resource.mime_type {
749                content["mimeType"] = serde_json::json!(mime);
750            }
751            if let Some(ref meta) = resource.meta {
752                content["_meta"] = meta.clone();
753            }
754            return serde_json::json!({
755                "jsonrpc": "2.0",
756                "id": id,
757                "result": { "contents": [content] }
758            });
759        }
760
761        for tmpl in &self.resource_templates {
762            if let Some(args) = match_uri_template(&tmpl.uri_template, uri) {
763                let args_vm = json_to_vm_value(&serde_json::json!(args));
764                let result = match crate::mcp_input::scope_input_context(
765                    params,
766                    client_capabilities(params),
767                    vm.call_closure_pub(&tmpl.handler, &[args_vm]),
768                )
769                .await
770                {
771                    Ok(result) => result,
772                    Err(error) => {
773                        return crate::jsonrpc::error_response(id.clone(), -32602, &error)
774                    }
775                };
776                return match result {
777                    Ok(value) => {
778                        let mut content = serde_json::json!({
779                            "uri": uri,
780                            "text": value.display(),
781                        });
782                        if let Some(ref mime) = tmpl.mime_type {
783                            content["mimeType"] = serde_json::json!(mime);
784                        }
785                        serde_json::json!({
786                            "jsonrpc": "2.0",
787                            "id": id,
788                            "result": { "contents": [content] }
789                        })
790                    }
791                    Err(VmError::McpInputRequired(required)) => crate::jsonrpc::response(
792                        id.clone(),
793                        crate::mcp_input::input_result(*required),
794                    ),
795                    Err(e) => serde_json::json!({
796                        "jsonrpc": "2.0",
797                        "id": id,
798                        "error": { "code": -32603, "message": format!("{e}") }
799                    }),
800                };
801            }
802        }
803
804        serde_json::json!({
805            "jsonrpc": "2.0",
806            "id": id,
807            "error": { "code": -32002, "message": format!("Resource not found: {uri}") }
808        })
809    }
810
811    fn handle_resource_templates_list(
812        &self,
813        id: &serde_json::Value,
814        params: &serde_json::Value,
815    ) -> serde_json::Value {
816        let page = match mcp_protocol::mcp_list_page(
817            params,
818            self.resource_templates.len(),
819            "resources/templates/list",
820        ) {
821            Ok(page) => page,
822            Err(error) => return crate::jsonrpc::error_response(id.clone(), -32602, &error),
823        };
824        let templates: Vec<serde_json::Value> = self.resource_templates[page.start..page.end]
825            .iter()
826            .map(|t| {
827                let mut entry =
828                    serde_json::json!({ "uriTemplate": t.uri_template, "name": t.name });
829                if let Some(ref title) = t.title {
830                    entry["title"] = serde_json::json!(title);
831                }
832                if let Some(ref desc) = t.description {
833                    entry["description"] = serde_json::json!(desc);
834                }
835                if let Some(ref mime) = t.mime_type {
836                    entry["mimeType"] = serde_json::json!(mime);
837                }
838                entry
839            })
840            .collect();
841
842        let mut result = serde_json::json!({ "resourceTemplates": templates });
843        if let Some(next_cursor) = page.next_cursor {
844            result["nextCursor"] = serde_json::json!(next_cursor);
845        }
846
847        serde_json::json!({
848            "jsonrpc": "2.0",
849            "id": id,
850            "result": result
851        })
852    }
853
854    fn handle_prompts_list(
855        &self,
856        id: &serde_json::Value,
857        params: &serde_json::Value,
858    ) -> serde_json::Value {
859        let page = match mcp_protocol::mcp_list_page(params, self.prompts.len(), "prompts/list") {
860            Ok(page) => page,
861            Err(error) => return crate::jsonrpc::error_response(id.clone(), -32602, &error),
862        };
863        let prompts: Vec<serde_json::Value> = self.prompts[page.start..page.end]
864            .iter()
865            .map(|p| {
866                let mut entry = serde_json::json!({ "name": p.name });
867                if let Some(ref title) = p.title {
868                    entry["title"] = serde_json::json!(title);
869                }
870                if let Some(ref desc) = p.description {
871                    entry["description"] = serde_json::json!(desc);
872                }
873                if let Some(ref args) = p.arguments {
874                    let args_json: Vec<serde_json::Value> = args
875                        .iter()
876                        .map(|a| {
877                            let mut arg =
878                                serde_json::json!({ "name": a.name, "required": a.required });
879                            if let Some(ref desc) = a.description {
880                                arg["description"] = serde_json::json!(desc);
881                            }
882                            arg
883                        })
884                        .collect();
885                    entry["arguments"] = serde_json::json!(args_json);
886                }
887                entry
888            })
889            .collect();
890
891        let mut result = serde_json::json!({ "prompts": prompts });
892        if let Some(next_cursor) = page.next_cursor {
893            result["nextCursor"] = serde_json::json!(next_cursor);
894        }
895
896        serde_json::json!({
897            "jsonrpc": "2.0",
898            "id": id,
899            "result": result
900        })
901    }
902
903    async fn handle_prompts_get(
904        &self,
905        id: &serde_json::Value,
906        params: &serde_json::Value,
907        vm: &mut Vm,
908    ) -> serde_json::Value {
909        let name = params.get("name").and_then(|n| n.as_str()).unwrap_or("");
910
911        let prompt = match self.prompts.iter().find(|p| p.name == name) {
912            Some(p) => p,
913            None => {
914                return serde_json::json!({
915                    "jsonrpc": "2.0",
916                    "id": id,
917                    "error": { "code": -32602, "message": format!("Unknown prompt: {name}") }
918                });
919            }
920        };
921
922        let arguments = params
923            .get("arguments")
924            .cloned()
925            .unwrap_or(serde_json::json!({}));
926        let args_vm = json_to_vm_value(&arguments);
927
928        let result = match crate::mcp_input::scope_input_context(
929            params,
930            client_capabilities(params),
931            vm.call_closure_pub(&prompt.handler, &[args_vm]),
932        )
933        .await
934        {
935            Ok(result) => result,
936            Err(error) => return crate::jsonrpc::error_response(id.clone(), -32602, &error),
937        };
938
939        match result {
940            Ok(value) => {
941                let messages = prompt_value_to_messages(&value);
942                serde_json::json!({
943                    "jsonrpc": "2.0",
944                    "id": id,
945                    "result": { "messages": messages }
946                })
947            }
948            Err(VmError::McpInputRequired(required)) => {
949                crate::jsonrpc::response(id.clone(), crate::mcp_input::input_result(*required))
950            }
951            Err(e) => serde_json::json!({
952                "jsonrpc": "2.0",
953                "id": id,
954                "error": { "code": -32603, "message": format!("{e}") }
955            }),
956        }
957    }
958
959    async fn handle_completion_complete(
960        &self,
961        id: &serde_json::Value,
962        params: &serde_json::Value,
963        vm: &mut Vm,
964    ) -> serde_json::Value {
965        let Some(ref_type) = params.pointer("/ref/type").and_then(|value| value.as_str()) else {
966            return crate::jsonrpc::error_response(
967                id.clone(),
968                -32602,
969                "completion ref.type is required",
970            );
971        };
972        match ref_type {
973            "ref/prompt" => self.complete_prompt_argument(id, params, vm).await,
974            "ref/resource" => {
975                self.complete_resource_template_argument(id, params, vm)
976                    .await
977            }
978            other => crate::jsonrpc::error_response(
979                id.clone(),
980                -32602,
981                &format!("Unsupported completion ref.type: {other}"),
982            ),
983        }
984    }
985
986    async fn complete_prompt_argument(
987        &self,
988        id: &serde_json::Value,
989        params: &serde_json::Value,
990        vm: &mut Vm,
991    ) -> serde_json::Value {
992        let name = params
993            .pointer("/ref/name")
994            .and_then(|value| value.as_str())
995            .unwrap_or("");
996        let argument_name = match completion_argument_name(params) {
997            Ok(name) => name,
998            Err(error) => return crate::jsonrpc::error_response(id.clone(), -32602, &error),
999        };
1000        let value = completion_argument_value(params);
1001        let prompt = match self.prompts.iter().find(|prompt| prompt.name == name) {
1002            Some(prompt) => prompt,
1003            None => {
1004                return crate::jsonrpc::error_response(
1005                    id.clone(),
1006                    -32602,
1007                    &format!("Unknown prompt: {name}"),
1008                );
1009            }
1010        };
1011        let Some(argument) = prompt
1012            .arguments
1013            .as_deref()
1014            .unwrap_or_default()
1015            .iter()
1016            .find(|argument| argument.name == argument_name)
1017        else {
1018            return crate::jsonrpc::error_response(
1019                id.clone(),
1020                -32602,
1021                &format!("Unknown prompt argument: {argument_name}"),
1022            );
1023        };
1024        let candidates =
1025            match completion_source_candidates(argument.completion.as_ref(), params, vm).await {
1026                Ok(candidates) => candidates,
1027                Err(error) => return crate::jsonrpc::error_response(id.clone(), -32603, &error),
1028            };
1029        crate::mcp_protocol::completion_result(id.clone(), candidates, value)
1030    }
1031
1032    async fn complete_resource_template_argument(
1033        &self,
1034        id: &serde_json::Value,
1035        params: &serde_json::Value,
1036        vm: &mut Vm,
1037    ) -> serde_json::Value {
1038        let uri = params
1039            .pointer("/ref/uri")
1040            .and_then(|value| value.as_str())
1041            .unwrap_or("");
1042        let argument_name = match completion_argument_name(params) {
1043            Ok(name) => name,
1044            Err(error) => return crate::jsonrpc::error_response(id.clone(), -32602, &error),
1045        };
1046        let value = completion_argument_value(params);
1047        let template = match self
1048            .resource_templates
1049            .iter()
1050            .find(|template| template.uri_template == uri)
1051        {
1052            Some(template) => template,
1053            None => {
1054                return crate::jsonrpc::error_response(
1055                    id.clone(),
1056                    -32602,
1057                    &format!("Unknown resource template: {uri}"),
1058                );
1059            }
1060        };
1061        if !uri_template_variables(&template.uri_template)
1062            .iter()
1063            .any(|name| name == argument_name)
1064        {
1065            return crate::jsonrpc::error_response(
1066                id.clone(),
1067                -32602,
1068                &format!("Unknown resource template argument: {argument_name}"),
1069            );
1070        }
1071        let candidates =
1072            match completion_source_candidates(template.completions.get(argument_name), params, vm)
1073                .await
1074            {
1075                Ok(candidates) => candidates,
1076                Err(error) => return crate::jsonrpc::error_response(id.clone(), -32603, &error),
1077            };
1078        crate::mcp_protocol::completion_result(id.clone(), candidates, value)
1079    }
1080}
1081
1082fn successful_tool_call_result(
1083    tools: &McpToolSet,
1084    tool: &McpToolDef,
1085    value: &crate::value::VmValue,
1086    value_json: serde_json::Value,
1087) -> Result<serde_json::Value, String> {
1088    let mut result = serde_json::json!({
1089        "content": vm_value_to_content(value),
1090        "isError": false,
1091    });
1092    let entry = tools
1093        .prepared()
1094        .entry(&tool.catalog.name)
1095        .expect("prepared MCP catalog covers every executable handler");
1096    if let Some(structured) = tools
1097        .prepared()
1098        .catalog()
1099        .mcp_structured_content(entry, value_json)
1100        .map_err(|error| error.to_string())?
1101    {
1102        result["structuredContent"] = structured;
1103    }
1104    Ok(result)
1105}
1106
1107/// Map a JSON-RPC method to its conservative cache hint. Read/list
1108/// methods get a TTL; everything else is `None`, which still routes
1109/// through [`apply_envelope`] so Stable clients see `resultType`.
1110fn cache_hint_for_method(method: &str) -> Option<&'static McpCacheHint> {
1111    const LIST: McpCacheHint = McpCacheHint::list_default();
1112    const READ: McpCacheHint = McpCacheHint::read_default();
1113    match method {
1114        "tools/list" | "resources/list" | "resources/templates/list" | "prompts/list" => {
1115            Some(&LIST)
1116        }
1117        "resources/read" => Some(&READ),
1118        _ => None,
1119    }
1120}
1121
1122fn apply_envelope(
1123    mut response: serde_json::Value,
1124    hint: Option<&'static McpCacheHint>,
1125) -> serde_json::Value {
1126    if let Some(result) = response.get_mut("result") {
1127        apply_result_envelope(result, hint);
1128    }
1129    response
1130}
1131
1132fn client_capabilities(params: &serde_json::Value) -> serde_json::Value {
1133    serde_json::to_value(parse_request_metadata(params).client_capabilities())
1134        .unwrap_or_else(|_| serde_json::json!({}))
1135}
1136
1137fn completion_argument_name(params: &serde_json::Value) -> Result<&str, String> {
1138    params
1139        .pointer("/argument/name")
1140        .and_then(|value| value.as_str())
1141        .filter(|value| !value.is_empty())
1142        .ok_or_else(|| "completion argument.name is required".to_string())
1143}
1144
1145fn completion_argument_value(params: &serde_json::Value) -> &str {
1146    params
1147        .pointer("/argument/value")
1148        .and_then(|value| value.as_str())
1149        .unwrap_or_default()
1150}
1151
1152async fn completion_source_candidates(
1153    source: Option<&McpCompletionSource>,
1154    params: &serde_json::Value,
1155    vm: &mut Vm,
1156) -> Result<Vec<String>, String> {
1157    let Some(source) = source else {
1158        return Ok(Vec::new());
1159    };
1160    let mut candidates = source.values.clone();
1161    if let Some(handler) = source.handler.as_ref() {
1162        let request = json_to_vm_value(params);
1163        let value = vm
1164            .call_closure_pub(handler, &[request])
1165            .await
1166            .map_err(|error| format!("{error}"))?;
1167        candidates.extend(completion_candidates_from_json(&vm_value_to_json(&value)));
1168    }
1169    Ok(candidates)
1170}
1171
1172fn completion_candidates_from_json(value: &serde_json::Value) -> Vec<String> {
1173    match value {
1174        serde_json::Value::Array(items) => {
1175            items.iter().filter_map(json_completion_string).collect()
1176        }
1177        serde_json::Value::Object(map) => map
1178            .get("values")
1179            .or_else(|| map.get("completion").and_then(|value| value.get("values")))
1180            .map(completion_candidates_from_json)
1181            .unwrap_or_default(),
1182        _ => json_completion_string(value).into_iter().collect(),
1183    }
1184}
1185
1186fn json_completion_string(value: &serde_json::Value) -> Option<String> {
1187    match value {
1188        serde_json::Value::String(value) => Some(value.clone()),
1189        serde_json::Value::Number(_) | serde_json::Value::Bool(_) => Some(value.to_string()),
1190        _ => None,
1191    }
1192}