Skip to content

Commit 53d9afb

Browse files
authored
Merge pull request #244 from drivecore/feature/243-mcp-tools
Implement MCP Tools Support
2 parents 12e9d42 + 26a4562 commit 53d9afb

File tree

4 files changed

+178
-10
lines changed

4 files changed

+178
-10
lines changed

packages/agent/src/core/mcp/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export interface McpConfig {
1515
servers?: McpServerConfig[];
1616
/** Default resources to load automatically */
1717
defaultResources?: string[];
18+
/** Default tools to make available */
19+
defaultTools?: string[];
1820
}
1921

2022
/**

packages/agent/src/tools/browser/filterPageContent.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Readability } from '@mozilla/readability';
22
import { JSDOM } from 'jsdom';
33
import { Page } from 'playwright';
44

5+
const OUTPUT_LIMIT = 11 * 1024; // 10KB limit
6+
57
/**
68
* Returns the raw HTML content of the page without any processing
79
*/
@@ -93,13 +95,22 @@ export async function filterPageContent(
9395
page: Page,
9496
pageFilter: 'simple' | 'none' | 'readability',
9597
): Promise<string> {
98+
let result: string = '';
9699
switch (pageFilter) {
97100
case 'none':
98-
return getNoneProcessedDOM(page);
101+
result = await getNoneProcessedDOM(page);
102+
break;
99103
case 'readability':
100-
return getReadabilityProcessedDOM(page);
104+
result = await getReadabilityProcessedDOM(page);
105+
break;
101106
case 'simple':
102107
default:
103-
return getSimpleProcessedDOM(page);
108+
result = await getSimpleProcessedDOM(page);
109+
break;
110+
}
111+
112+
if (result.length > OUTPUT_LIMIT) {
113+
return result.slice(0, OUTPUT_LIMIT) + '...(truncated)';
104114
}
115+
return result;
105116
}

packages/agent/src/tools/mcp.ts

Lines changed: 140 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,25 @@ const getResourceSchema = z.object({
2828
.describe('URI of the resource to fetch in the format "scheme://path"'),
2929
});
3030

31+
// Parameters for listTools method
32+
const listToolsSchema = z.object({
33+
server: z
34+
.string()
35+
.optional()
36+
.describe('Optional server name to filter tools by'),
37+
});
38+
39+
// Parameters for executeTool method
40+
const executeToolSchema = z.object({
41+
uri: z
42+
.string()
43+
.describe('URI of the tool to execute in the format "scheme://path"'),
44+
params: z
45+
.record(z.unknown())
46+
.optional()
47+
.describe('Parameters to pass to the tool'),
48+
});
49+
3150
// Return type for listResources
3251
const listResourcesReturnSchema = z.array(
3352
z.object({
@@ -39,6 +58,20 @@ const listResourcesReturnSchema = z.array(
3958
// Return type for getResource
4059
const getResourceReturnSchema = z.string();
4160

61+
// Return type for listTools
62+
const listToolsReturnSchema = z.array(
63+
z.object({
64+
uri: z.string(),
65+
name: z.string(),
66+
description: z.string().optional(),
67+
parameters: z.record(z.unknown()).optional(),
68+
returns: z.record(z.unknown()).optional(),
69+
}),
70+
);
71+
72+
// Return type for executeTool - can be any JSON value
73+
const executeToolReturnSchema = z.unknown();
74+
4275
// Map to store MCP clients
4376
const mcpClients = new Map<string, any>();
4477

@@ -87,7 +120,7 @@ export function createMcpTool(config: McpConfig): Tool {
87120
return {
88121
name: 'mcp',
89122
description:
90-
'Interact with Model Context Protocol (MCP) servers to retrieve resources',
123+
'Interact with Model Context Protocol (MCP) servers to retrieve resources and execute tools',
91124
parameters: z.discriminatedUnion('method', [
92125
z.object({
93126
method: z.literal('listResources'),
@@ -97,6 +130,14 @@ export function createMcpTool(config: McpConfig): Tool {
97130
method: z.literal('getResource'),
98131
params: getResourceSchema,
99132
}),
133+
z.object({
134+
method: z.literal('listTools'),
135+
params: listToolsSchema.optional(),
136+
}),
137+
z.object({
138+
method: z.literal('executeTool'),
139+
params: executeToolSchema,
140+
}),
100141
]),
101142
parametersJsonSchema: zodToJsonSchema(
102143
z.discriminatedUnion('method', [
@@ -108,15 +149,33 @@ export function createMcpTool(config: McpConfig): Tool {
108149
method: z.literal('getResource'),
109150
params: getResourceSchema,
110151
}),
152+
z.object({
153+
method: z.literal('listTools'),
154+
params: listToolsSchema.optional(),
155+
}),
156+
z.object({
157+
method: z.literal('executeTool'),
158+
params: executeToolSchema,
159+
}),
111160
]),
112161
),
113-
returns: z.union([listResourcesReturnSchema, getResourceReturnSchema]),
162+
returns: z.union([
163+
listResourcesReturnSchema,
164+
getResourceReturnSchema,
165+
listToolsReturnSchema,
166+
executeToolReturnSchema,
167+
]),
114168
returnsJsonSchema: zodToJsonSchema(
115-
z.union([listResourcesReturnSchema, getResourceReturnSchema]),
169+
z.union([
170+
listResourcesReturnSchema,
171+
getResourceReturnSchema,
172+
listToolsReturnSchema,
173+
executeToolReturnSchema,
174+
]),
116175
),
117176

118177
execute: async ({ method, params }, { logger }) => {
119-
// Extract the server name from a resource URI
178+
// Extract the server name from a URI (resource or tool)
120179
function getServerNameFromUri(uri: string): string | undefined {
121180
const match = uri.match(/^([^:]+):\/\//);
122181
return match ? match[1] : undefined;
@@ -180,6 +239,64 @@ export function createMcpTool(config: McpConfig): Tool {
180239
logger.verbose(`Fetching resource: ${uri}`);
181240
const resource = await client.resource(uri);
182241
return resource.content;
242+
} else if (method === 'listTools') {
243+
// List available tools from MCP servers
244+
const tools: any[] = [];
245+
const serverFilter = params?.server;
246+
247+
// If a specific server is requested, only check that server
248+
if (serverFilter) {
249+
const client = mcpClients.get(serverFilter);
250+
if (client) {
251+
try {
252+
logger.verbose(`Fetching tools from server: ${serverFilter}`);
253+
const serverTools = await client.tools();
254+
tools.push(...(serverTools as any[]));
255+
} catch (error) {
256+
logger.error(
257+
`Failed to fetch tools from server ${serverFilter}:`,
258+
error,
259+
);
260+
}
261+
} else {
262+
logger.warn(`Server not found: ${serverFilter}`);
263+
}
264+
} else {
265+
// Otherwise, check all servers
266+
for (const [serverName, client] of mcpClients.entries()) {
267+
try {
268+
logger.verbose(`Fetching tools from server: ${serverName}`);
269+
const serverTools = await client.tools();
270+
tools.push(...(serverTools as any[]));
271+
} catch (error) {
272+
logger.error(
273+
`Failed to fetch tools from server ${serverName}:`,
274+
error,
275+
);
276+
}
277+
}
278+
}
279+
280+
return tools;
281+
} else if (method === 'executeTool') {
282+
// Execute a tool from an MCP server
283+
const { uri, params: toolParams = {} } = params;
284+
285+
// Parse the URI to determine which server to use
286+
const serverName = getServerNameFromUri(uri);
287+
if (!serverName) {
288+
throw new Error(`Could not determine server from URI: ${uri}`);
289+
}
290+
291+
const client = mcpClients.get(serverName);
292+
if (!client) {
293+
throw new Error(`Server not found: ${serverName}`);
294+
}
295+
296+
// Use the MCP SDK to execute the tool
297+
logger.verbose(`Executing tool: ${uri} with params:`, toolParams);
298+
const result = await client.tool(uri, toolParams);
299+
return result;
183300
}
184301

185302
throw new Error(`Unknown method: ${method}`);
@@ -188,20 +305,36 @@ export function createMcpTool(config: McpConfig): Tool {
188305
logParameters: (params, { logger }) => {
189306
if (params.method === 'listResources') {
190307
logger.verbose(
191-
`Listing MCP resources${params.params?.server ? ` from server: ${params.params.server}` : ''}`,
308+
`Listing MCP resources${
309+
params.params?.server ? ` from server: ${params.params.server}` : ''
310+
}`,
192311
);
193312
} else if (params.method === 'getResource') {
194313
logger.verbose(`Fetching MCP resource: ${params.params.uri}`);
314+
} else if (params.method === 'listTools') {
315+
logger.verbose(
316+
`Listing MCP tools${
317+
params.params?.server ? ` from server: ${params.params.server}` : ''
318+
}`,
319+
);
320+
} else if (params.method === 'executeTool') {
321+
logger.verbose(`Executing MCP tool: ${params.params.uri}`);
195322
}
196323
},
197324

198325
logReturns: (result, { logger }) => {
199326
if (Array.isArray(result)) {
200-
logger.verbose(`Found ${result.length} MCP resources`);
201-
} else {
327+
if (result.length > 0 && 'description' in result[0]) {
328+
logger.verbose(`Found ${result.length} MCP tools`);
329+
} else {
330+
logger.verbose(`Found ${result.length} MCP resources`);
331+
}
332+
} else if (typeof result === 'string') {
202333
logger.verbose(
203334
`Retrieved MCP resource content (${result.length} characters)`,
204335
);
336+
} else {
337+
logger.verbose(`Executed MCP tool and received result`);
205338
}
206339
},
207340
};

packages/cli/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,9 @@ export default {
204204

205205
// Optional: Default context resources to load
206206
defaultResources: ['company-docs://api/reference'],
207+
208+
// Optional: Default tools to make available
209+
defaultTools: ['company-docs://tools/search'],
207210
},
208211
};
209212
```
@@ -212,6 +215,25 @@ When MCP is configured, the agent will have access to a new `mcp` tool that allo
212215

213216
- List available resources from configured MCP servers
214217
- Fetch resources to use as context for its work
218+
- List available tools from configured MCP servers
219+
- Execute tools provided by MCP servers
220+
221+
#### Using MCP Tools
222+
223+
MCP tools allow the agent to execute functions provided by external services through the Model Context Protocol. The agent can:
224+
225+
1. Discover available tools using `mcp.listTools()`
226+
2. Execute a tool using `mcp.executeTool({ uri: 'server-name://path/to/tool', params: { ... } })`
227+
228+
Tools can provide various capabilities like:
229+
230+
- Searching documentation
231+
- Accessing databases
232+
- Interacting with APIs
233+
- Performing specialized calculations
234+
- Accessing proprietary services
235+
236+
Each tool has a URI that identifies it, along with parameters it accepts and the type of result it returns.
215237

216238
### CLI-Only Options
217239

0 commit comments

Comments
 (0)