Skip to content

Commit e3cd235

Browse files
committed
Expose system prompt through ContextRouter interface. Add basic BaseContextRouter
1 parent 4320c94 commit e3cd235

File tree

3 files changed

+113
-79
lines changed

3 files changed

+113
-79
lines changed

packages/agents/src/mcp/client-prompt.ts

Lines changed: 0 additions & 34 deletions
This file was deleted.

packages/agents/src/mcp/client.ts

Lines changed: 24 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import type { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.j
1717
import type { AgentsOAuthProvider } from "./do-oauth-client-provider";
1818
import { jsonSchema, type ToolSet } from "ai";
1919
import { nanoid } from "nanoid";
20-
import { unstable_getMcpPrompt } from "./client-prompt";
20+
import { BaseContextRouter, type ContextRouter } from "./context-router";
2121

2222
/**
2323
* Utility class that aggregates multiple MCP clients into one
@@ -33,7 +33,8 @@ export class MCPClientManager {
3333
*/
3434
constructor(
3535
private _name: string,
36-
private _version: string
36+
private _version: string,
37+
public unstable_contextRouter = new BaseContextRouter(),
3738
) {}
3839

3940
/**
@@ -177,42 +178,6 @@ export class MCPClientManager {
177178
return { serverId };
178179
}
179180

180-
/**
181-
* @returns namespaced list of tools
182-
*/
183-
listTools(): NamespacedData["tools"] {
184-
return getNamespacedData(this.mcpConnections, "tools");
185-
}
186-
187-
/**
188-
* @returns a set of tools that you can use with the AI SDK
189-
*/
190-
unstable_getAITools(): ToolSet {
191-
return Object.fromEntries(
192-
getNamespacedData(this.mcpConnections, "tools").map((tool) => {
193-
return [
194-
`${tool.serverId}_${tool.name}`,
195-
{
196-
parameters: jsonSchema(tool.inputSchema),
197-
description: tool.description,
198-
execute: async (args) => {
199-
const result = await this.callTool({
200-
name: tool.name,
201-
arguments: args,
202-
serverId: tool.serverId,
203-
});
204-
if (result.isError) {
205-
// @ts-expect-error TODO we should fix this
206-
throw new Error(result.content[0].text);
207-
}
208-
return result;
209-
},
210-
},
211-
];
212-
})
213-
);
214-
}
215-
216181
/**
217182
* Closes all connections to MCP servers
218183
*/
@@ -236,17 +201,31 @@ export class MCPClientManager {
236201
}
237202

238203
/**
239-
* @returns namespaced list of prompts
204+
* @returns namespaced list of tools
240205
*/
241-
listPrompts(): NamespacedData["prompts"] {
242-
return getNamespacedData(this.mcpConnections, "prompts");
206+
listTools(): NamespacedData["tools"] {
207+
return this.unstable_contextRouter.listTools(this)
208+
}
209+
210+
/**
211+
* @returns a set of tools that you can use with the AI SDK
212+
*/
213+
unstable_getAITools(): ToolSet {
214+
return this.unstable_contextRouter.getAITools(this)
243215
}
244216

245217
/**
246218
* @returns namespaced list of tools
247219
*/
248220
listResources(): NamespacedData["resources"] {
249-
return getNamespacedData(this.mcpConnections, "resources");
221+
return this.unstable_contextRouter.listResources(this);
222+
}
223+
224+
/**
225+
* @returns namespaced list of prompts
226+
*/
227+
listPrompts(): NamespacedData["prompts"] {
228+
return getNamespacedData(this.mcpConnections, "prompts");
250229
}
251230

252231
/**
@@ -308,12 +287,12 @@ export class MCPClientManager {
308287
*
309288
* @param includeResources Whether to include resources in the prompt. This may be useful if you include a tool to fetch resources OR if the servers you connect to interact with resources.
310289
*/
311-
unstable_getMcpPrompt(includeResources = true): string {
312-
return unstable_getMcpPrompt(this.mcpConnections, includeResources);
290+
unstable_systemPrompt(): string {
291+
return this.unstable_contextRouter.systemPrompt(this)
313292
}
314293
}
315294

316-
type NamespacedData = {
295+
export type NamespacedData = {
317296
tools: (Tool & { serverId: string })[];
318297
prompts: (Prompt & { serverId: string })[];
319298
resources: (Resource & { serverId: string })[];
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { jsonSchema, type CoreMessage, type ToolSet, type LanguageModelV1, type UserContent } from "ai";
2+
import { getNamespacedData, type MCPClientManager, type NamespacedData } from "./client";
3+
import type { MCPClientConnection } from "./client-connection";
4+
import type { Resource } from "@modelcontextprotocol/sdk/types.js"
5+
6+
/**
7+
* A context router provides an interface for:
8+
* - Filtering tools and resources
9+
* - Exposes a system prompt based on the MCP state
10+
*/
11+
export interface ContextRouter {
12+
systemPrompt(connManager: MCPClientManager): string
13+
14+
listTools(connManager: MCPClientManager): NamespacedData["tools"]
15+
getAITools(connManager: MCPClientManager): ToolSet
16+
listResources(connManager: MCPClientManager): NamespacedData["resources"]
17+
}
18+
19+
/**
20+
* The base ContextRouter:
21+
* - Does not filter tools or resources
22+
* - Provides a system prompt
23+
*/
24+
export class BaseContextRouter implements ContextRouter {
25+
constructor(public includeResources = true) {}
26+
27+
listTools(connManager: MCPClientManager) {
28+
return getNamespacedData(connManager.mcpConnections, "tools");
29+
}
30+
31+
getAITools(connManager: MCPClientManager): ToolSet {
32+
return Object.fromEntries(
33+
this.listTools(connManager).map((tool) => {
34+
return [
35+
`${tool.serverId}_${tool.name}`,
36+
{
37+
parameters: jsonSchema(tool.inputSchema),
38+
description: tool.description,
39+
execute: async (args) => {
40+
const result = await connManager.callTool({
41+
name: tool.name,
42+
arguments: args,
43+
serverId: tool.serverId,
44+
});
45+
if (result.isError) {
46+
// @ts-expect-error TODO we should fix this
47+
throw new Error(result.content[0].text);
48+
}
49+
return result;
50+
},
51+
},
52+
];
53+
})
54+
)
55+
}
56+
57+
listResources(connManager: MCPClientManager) {
58+
return getNamespacedData(connManager.mcpConnections, "resources");
59+
}
60+
61+
systemPrompt(connManager: MCPClientManager): string {
62+
return `<integrations_list>
63+
You have access to multiple integrations via Model Context Protocol (MCP). These integrations provide you with tools which you can use to execute to complete tasks or retrieive information.
64+
65+
${this.includeResources && "Each integration, provides a list of resources, which are included in the list of integrations below."}
66+
67+
Here is a list of all of the integrations you have access to, with instructions if necessary:
68+
69+
${Object.entries(connManager.mcpConnections).map(([_id, conn]) => BaseContextRouter.serverContext(conn, this.includeResources))}
70+
<integrations_list>`
71+
}
72+
73+
static serverContext(conn: MCPClientConnection, includeResources: boolean) {
74+
return `<integration>
75+
${conn.serverInfo && `<integration_name>${conn.serverInfo.name}</integration_name>`}
76+
${conn.instructions && `<integration_instructions>${conn.instructions}</integration_instructions>`}
77+
${includeResources && `<resources_list>${conn.resources.map((resource) => BaseContextRouter.resourceContext(resource))}</resources_list>`}
78+
<integration>`;
79+
}
80+
81+
static resourceContext(resource: Resource) {
82+
return `<resource>
83+
<name>${resource.name}</name>
84+
<uri>${resource.uri}</uri>
85+
<description>${resource.description}</description>
86+
<mimeType>${resource.mimeType}</mimeType>
87+
</resource>`;
88+
}
89+
}

0 commit comments

Comments
 (0)