[MCP] Add support for resources (#5239)

This PR adds support for [MCP
resources](https://modelcontextprotocol.io/specification/2025-06-18/server/resources)
by adding three new tools for the model:
1. `list_resources`
2. `list_resource_templates`
3. `read_resource`

These 3 tools correspond to the [three primary MCP resource protocol
messages](https://modelcontextprotocol.io/specification/2025-06-18/server/resources#protocol-messages).

Example of listing and reading a GitHub resource tempalte
<img width="2984" height="804" alt="CleanShot 2025-10-15 at 17 31 10"
src="https://github.com/user-attachments/assets/89b7f215-2e2a-41c5-90dd-b932ac84a585"
/>

`/mcp` with Figma configured
<img width="2984" height="442" alt="CleanShot 2025-10-15 at 18 29 35"
src="https://github.com/user-attachments/assets/a7578080-2ed2-4c59-b9b4-d8461f90d8ee"
/>

Fixes #4956
This commit is contained in:
Gabriel Peal
2025-10-16 22:05:15 -07:00
committed by GitHub
parent bdda762deb
commit 40fba1bb4c
18 changed files with 1705 additions and 24 deletions

View File

@@ -2071,6 +2071,8 @@ impl ChatWidget {
self.add_to_history(history_cell::new_mcp_tools_output(
&self.config,
ev.tools,
ev.resources,
ev.resource_templates,
&ev.auth_statuses,
));
}

View File

@@ -35,7 +35,9 @@ use codex_protocol::plan_tool::UpdatePlanArgs;
use image::DynamicImage;
use image::ImageReader;
use mcp_types::EmbeddedResourceResource;
use mcp_types::Resource;
use mcp_types::ResourceLink;
use mcp_types::ResourceTemplate;
use ratatui::prelude::*;
use ratatui::style::Modifier;
use ratatui::style::Style;
@@ -976,6 +978,8 @@ pub(crate) fn empty_mcp_output() -> PlainHistoryCell {
pub(crate) fn new_mcp_tools_output(
config: &Config,
tools: HashMap<String, mcp_types::Tool>,
resources: HashMap<String, Vec<Resource>>,
resource_templates: HashMap<String, Vec<ResourceTemplate>>,
auth_statuses: &HashMap<String, McpAuthStatus>,
) -> PlainHistoryCell {
let mut lines: Vec<Line<'static>> = vec![
@@ -1073,12 +1077,64 @@ pub(crate) fn new_mcp_tools_output(
}
if !cfg.enabled {
lines.push(vec![" • Tools: ".into(), "(disabled)".red()].into());
} else if names.is_empty() {
let disabled = "(disabled)".red();
lines.push(vec![" • Tools: ".into(), disabled.clone()].into());
lines.push(vec![" • Resources: ".into(), disabled.clone()].into());
lines.push(vec![" • Resource templates: ".into(), disabled].into());
lines.push(Line::from(""));
continue;
}
if names.is_empty() {
lines.push(" • Tools: (none)".into());
} else {
lines.push(vec![" • Tools: ".into(), names.join(", ").into()].into());
}
let server_resources: Vec<Resource> =
resources.get(server.as_str()).cloned().unwrap_or_default();
if server_resources.is_empty() {
lines.push(" • Resources: (none)".into());
} else {
let mut spans: Vec<Span<'static>> = vec![" • Resources: ".into()];
for (idx, resource) in server_resources.iter().enumerate() {
if idx > 0 {
spans.push(", ".into());
}
let label = resource.title.as_ref().unwrap_or(&resource.name);
spans.push(label.clone().into());
spans.push(" ".into());
spans.push(format!("({})", resource.uri).dim());
}
lines.push(spans.into());
}
let server_templates: Vec<ResourceTemplate> = resource_templates
.get(server.as_str())
.cloned()
.unwrap_or_default();
if server_templates.is_empty() {
lines.push(" • Resource templates: (none)".into());
} else {
let mut spans: Vec<Span<'static>> = vec![" • Resource templates: ".into()];
for (idx, template) in server_templates.iter().enumerate() {
if idx > 0 {
spans.push(", ".into());
}
let label = template.title.as_ref().unwrap_or(&template.name);
spans.push(label.clone().into());
spans.push(" ".into());
spans.push(format!("({})", template.uri_template).dim());
}
lines.push(spans.into());
}
lines.push(Line::from(""));
}