Skip to content

Commit f16f1cc

Browse files
authored
feat(core): add tools to list and read MCP resources (#25395)
1 parent 963631a commit f16f1cc

26 files changed

Lines changed: 1126 additions & 6 deletions

docs/cli/plan-mode.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ These are the only allowed tools:
130130
[`cli_help`](../core/subagents.md#cli-help-agent)
131131
- **Interaction:** [`ask_user`](../tools/ask-user.md)
132132
- **MCP tools (Read):** Read-only [MCP tools](../tools/mcp-server.md) (for
133-
example, `github_read_issue`, `postgres_read_schema`) are allowed.
133+
example, `github_read_issue`, `postgres_read_schema`) and core
134+
[MCP resource tools](../tools/mcp-resources.md) (`list_mcp_resources`,
135+
`read_mcp_resource`) are allowed.
134136
- **Planning (Write):**
135137
[`write_file`](../tools/file-system.md#3-write_file-writefile) and
136138
[`replace`](../tools/file-system.md#6-replace-edit) only allowed for `.md`

docs/reference/tools.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ each tool.
9292
| [`ask_user`](../tools/ask-user.md) | `Communicate` | Requests clarification or missing information via an interactive dialog. |
9393
| [`write_todos`](../tools/todos.md) | `Other` | Maintains an internal list of subtasks. The model uses this to track its own progress. |
9494

95+
### MCP
96+
97+
| Tool | Kind | Description |
98+
| :------------------------------------------------ | :------- | :--------------------------------------------------------------------- |
99+
| [`list_mcp_resources`](../tools/mcp-resources.md) | `Search` | Lists all available resources exposed by connected MCP servers. |
100+
| [`read_mcp_resource`](../tools/mcp-resources.md) | `Read` | Reads the content of a specific Model Context Protocol (MCP) resource. |
101+
95102
### Memory
96103

97104
| Tool | Kind | Description |

docs/sidebar.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,14 @@
122122
}
123123
]
124124
},
125-
{ "label": "MCP servers", "slug": "docs/tools/mcp-server" },
125+
{
126+
"label": "MCP servers",
127+
"collapsed": true,
128+
"items": [
129+
{ "label": "Overview", "slug": "docs/tools/mcp-server" },
130+
{ "label": "Resource tools", "slug": "docs/tools/mcp-resources" }
131+
]
132+
},
126133
{ "label": "Model routing", "slug": "docs/cli/model-routing" },
127134
{ "label": "Model selection", "slug": "docs/cli/model" },
128135
{

docs/tools/mcp-resources.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# MCP resource tools
2+
3+
MCP resource tools let Gemini CLI discover and retrieve data from contextual
4+
resources exposed by Model Context Protocol (MCP) servers.
5+
6+
## 1. `list_mcp_resources` (ListMcpResources)
7+
8+
`list_mcp_resources` retrieves a list of all available resources from connected
9+
MCP servers. This is primarily a discovery tool that helps the model understand
10+
what external data sources are available for reference.
11+
12+
- **Tool name:** `list_mcp_resources`
13+
- **Display name:** List MCP Resources
14+
- **Kind:** `Search`
15+
- **File:** `list-mcp-resources.ts`
16+
- **Parameters:**
17+
- `serverName` (string, optional): An optional filter to list resources from a
18+
specific server.
19+
- **Behavior:**
20+
- Iterates through all connected MCP servers.
21+
- Fetches the list of resources each server exposes.
22+
- Formats the results into a plain-text list of URIs and descriptions.
23+
- **Output (`llmContent`):** A formatted list of available resources, including
24+
their URI, server name, and optional description.
25+
- **Confirmation:** No. This is a read-only discovery tool.
26+
27+
## 2. `read_mcp_resource` (ReadMcpResource)
28+
29+
`read_mcp_resource` retrieves the content of a specific resource identified by
30+
its URI.
31+
32+
- **Tool name:** `read_mcp_resource`
33+
- **Display name:** Read MCP Resource
34+
- **Kind:** `Read`
35+
- **File:** `read-mcp-resource.ts`
36+
- **Parameters:**
37+
- `uri` (string, required): The URI of the MCP resource to read.
38+
- **Behavior:**
39+
- Locates the resource and its associated server by URI.
40+
- Calls the server's `resources/read` method.
41+
- Processes the response, extracting text or binary data.
42+
- **Output (`llmContent`):** The content of the resource. For binary data, it
43+
returns a placeholder indicating the data type.
44+
- **Confirmation:** No. This is a read-only retrieval tool.

docs/tools/mcp-server.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ Gemini CLI supports three MCP transport types:
6464

6565
Some MCP servers expose contextual “resources” in addition to the tools and
6666
prompts. Gemini CLI discovers these automatically and gives you the possibility
67-
to reference them in the chat.
67+
to reference them in the chat. For more information on the tools used to
68+
interact with these resources, see [MCP resource tools](mcp-resources.md).
6869

6970
### Discovery and listing
7071

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"list_mcp_resources","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":10,"totalTokenCount":20}}]}
2+
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Here are the resources: test://resource1"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":10,"totalTokenCount":20}}]}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"read_mcp_resource","args":{"uri":"test://resource1"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":10,"totalTokenCount":20}}]}
2+
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The content is: content of resource 1"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":10,"totalTokenCount":20}}]}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"list_mcp_resources","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":10,"totalTokenCount":20}}]}
2+
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Here are the resources: test://resource1"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":10,"totalTokenCount":20}}]}
3+
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"read_mcp_resource","args":{"uri":"test://resource1"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":10,"totalTokenCount":20}}]}
4+
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The content is: content of resource 1"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":10,"totalTokenCount":20}}]}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
8+
import { TestRig } from './test-helper.js';
9+
import { join, dirname } from 'node:path';
10+
import { fileURLToPath } from 'node:url';
11+
import fs from 'node:fs';
12+
13+
const __dirname = dirname(fileURLToPath(import.meta.url));
14+
15+
describe('mcp-resources-integration', () => {
16+
let rig: TestRig;
17+
18+
beforeEach(() => {
19+
rig = new TestRig();
20+
});
21+
22+
afterEach(async () => await rig.cleanup());
23+
24+
it('should list mcp resources', async () => {
25+
await rig.setup('mcp-list-resources-test', {
26+
settings: {
27+
model: {
28+
name: 'gemini-3-flash-preview',
29+
},
30+
},
31+
fakeResponsesPath: join(__dirname, 'mcp-list-resources.responses'),
32+
});
33+
34+
// Workaround for ProjectRegistry save issue
35+
const userGeminiDir = join(rig.homeDir!, '.gemini');
36+
fs.writeFileSync(join(userGeminiDir, 'projects.json'), '{"projects":{}}');
37+
38+
// Add a dummy server to get setup done
39+
rig.addTestMcpServer('resource-server', {
40+
name: 'resource-server',
41+
tools: [],
42+
});
43+
44+
// Overwrite the script with resource support
45+
const scriptPath = join(rig.testDir!, 'test-mcp-resource-server.mjs');
46+
const scriptContent = `
47+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
48+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
49+
import {
50+
ListResourcesRequestSchema,
51+
} from '@modelcontextprotocol/sdk/types.js';
52+
53+
const server = new Server(
54+
{
55+
name: 'resource-server',
56+
version: '1.0.0',
57+
},
58+
{
59+
capabilities: {
60+
resources: {},
61+
},
62+
},
63+
);
64+
65+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
66+
return {
67+
resources: [
68+
{
69+
uri: 'test://resource1',
70+
name: 'Resource 1',
71+
mimeType: 'text/plain',
72+
description: 'A test resource',
73+
}
74+
],
75+
};
76+
});
77+
78+
const transport = new StdioServerTransport();
79+
await server.connect(transport);
80+
`;
81+
fs.writeFileSync(scriptPath, scriptContent);
82+
83+
const output = await rig.run({
84+
args: 'List all available MCP resources.',
85+
env: { GEMINI_API_KEY: 'dummy' },
86+
});
87+
88+
const foundCall = await rig.waitForToolCall('list_mcp_resources');
89+
expect(foundCall).toBeTruthy();
90+
expect(output).toContain('test://resource1');
91+
}, 60000);
92+
93+
it('should read mcp resource', async () => {
94+
await rig.setup('mcp-read-resource-test', {
95+
settings: {
96+
model: {
97+
name: 'gemini-3-flash-preview',
98+
},
99+
},
100+
fakeResponsesPath: join(__dirname, 'mcp-read-resource.responses'),
101+
});
102+
103+
// Workaround for ProjectRegistry save issue
104+
const userGeminiDir = join(rig.homeDir!, '.gemini');
105+
fs.writeFileSync(join(userGeminiDir, 'projects.json'), '{"projects":{}}');
106+
107+
// Add a dummy server to get setup done
108+
rig.addTestMcpServer('resource-server', {
109+
name: 'resource-server',
110+
tools: [],
111+
});
112+
113+
// Overwrite the script with resource support
114+
const scriptPath = join(rig.testDir!, 'test-mcp-resource-server.mjs');
115+
const scriptContent = `
116+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
117+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
118+
import {
119+
ListResourcesRequestSchema,
120+
ReadResourceRequestSchema,
121+
} from '@modelcontextprotocol/sdk/types.js';
122+
123+
const server = new Server(
124+
{
125+
name: 'resource-server',
126+
version: '1.0.0',
127+
},
128+
{
129+
capabilities: {
130+
resources: {},
131+
},
132+
},
133+
);
134+
135+
// Need to provide list resources so the tool is active!
136+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
137+
return {
138+
resources: [
139+
{
140+
uri: 'test://resource1',
141+
name: 'Resource 1',
142+
mimeType: 'text/plain',
143+
description: 'A test resource',
144+
}
145+
],
146+
};
147+
});
148+
149+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
150+
if (request.params.uri === 'test://resource1') {
151+
return {
152+
contents: [
153+
{
154+
uri: 'test://resource1',
155+
mimeType: 'text/plain',
156+
text: 'This is the content of resource 1',
157+
}
158+
],
159+
};
160+
}
161+
throw new Error('Resource not found');
162+
});
163+
164+
const transport = new StdioServerTransport();
165+
await server.connect(transport);
166+
`;
167+
fs.writeFileSync(scriptPath, scriptContent);
168+
169+
const output = await rig.run({
170+
args: 'Read the MCP resource test://resource1.',
171+
env: { GEMINI_API_KEY: 'dummy' },
172+
});
173+
174+
const foundCall = await rig.waitForToolCall('read_mcp_resource');
175+
expect(foundCall).toBeTruthy();
176+
expect(output).toContain('content of resource 1');
177+
}, 60000);
178+
});

packages/core/src/config/config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import { ResourceRegistry } from '../resources/resource-registry.js';
3030
import { ToolRegistry } from '../tools/tool-registry.js';
3131
import { LSTool } from '../tools/ls.js';
3232
import { ReadFileTool } from '../tools/read-file.js';
33+
import { ReadMcpResourceTool } from '../tools/read-mcp-resource.js';
34+
import { ListMcpResourcesTool } from '../tools/list-mcp-resources.js';
3335
import { GrepTool } from '../tools/grep.js';
3436
import { canUseRipgrep, RipGrepTool } from '../tools/ripGrep.js';
3537
import { GlobTool } from '../tools/glob.js';
@@ -3579,6 +3581,12 @@ export class Config implements McpContext, AgentLoopContext {
35793581
maybeRegister(WebFetchTool, () =>
35803582
registry.registerTool(new WebFetchTool(this, this.messageBus)),
35813583
);
3584+
maybeRegister(ReadMcpResourceTool, () =>
3585+
registry.registerTool(new ReadMcpResourceTool(this, this.messageBus)),
3586+
);
3587+
maybeRegister(ListMcpResourcesTool, () =>
3588+
registry.registerTool(new ListMcpResourcesTool(this, this.messageBus)),
3589+
);
35823590
maybeRegister(ShellTool, () =>
35833591
registry.registerTool(new ShellTool(this, this.messageBus)),
35843592
);

0 commit comments

Comments
 (0)