@genkit-ai/mcp 1.33.0 → 1.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client/client.d.mts +9 -6
- package/lib/client/client.d.ts +9 -6
- package/lib/client/client.js +19 -7
- package/lib/client/client.js.map +1 -1
- package/lib/client/client.mjs +19 -7
- package/lib/client/client.mjs.map +1 -1
- package/lib/client/host.d.mts +11 -8
- package/lib/client/host.d.ts +11 -8
- package/lib/client/host.js +4 -1
- package/lib/client/host.js.map +1 -1
- package/lib/client/host.mjs +4 -1
- package/lib/client/host.mjs.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/index.mjs.map +1 -1
- package/lib/server.d.mts +4 -4
- package/lib/server.d.ts +4 -4
- package/lib/util/tools.d.mts +5 -3
- package/lib/util/tools.d.ts +5 -3
- package/lib/util/tools.js +152 -23
- package/lib/util/tools.js.map +1 -1
- package/lib/util/tools.mjs +156 -24
- package/lib/util/tools.mjs.map +1 -1
- package/package.json +3 -3
- package/src/client/client.ts +38 -14
- package/src/client/host.ts +23 -9
- package/src/index.ts +16 -9
- package/src/util/tools.ts +204 -35
- package/tests/host_test.ts +151 -1
- package/tests/tools_test.ts +201 -0
package/src/client/host.ts
CHANGED
|
@@ -20,13 +20,14 @@ import {
|
|
|
20
20
|
type DynamicResourceAction,
|
|
21
21
|
type ExecutablePrompt,
|
|
22
22
|
type Genkit,
|
|
23
|
+
type MultipartToolAction,
|
|
23
24
|
type PromptGenerateOptions,
|
|
24
25
|
type ToolAction,
|
|
25
26
|
} from 'genkit';
|
|
26
27
|
import { logger } from 'genkit/logging';
|
|
27
28
|
import { GenkitMcpClient, McpServerConfig } from './client.js';
|
|
28
29
|
|
|
29
|
-
export interface McpHostOptions {
|
|
30
|
+
export interface McpHostOptions<M extends boolean = false> {
|
|
30
31
|
/**
|
|
31
32
|
* An optional client name for this MCP host. This name is advertised to MCP Servers
|
|
32
33
|
* as the connecting client name. Defaults to 'genkit-mcp'.
|
|
@@ -52,13 +53,19 @@ export interface McpHostOptions {
|
|
|
52
53
|
*/
|
|
53
54
|
rawToolResponses?: boolean;
|
|
54
55
|
|
|
56
|
+
/** If true, tools will be registered as multipart tool.v2 actions. */
|
|
57
|
+
multipart?: M;
|
|
58
|
+
|
|
55
59
|
/**
|
|
56
60
|
* When provided, each connected MCP server will be sent the roots specified here. Overridden by any specific roots sent in the `mcpServers` config for a given server.
|
|
57
61
|
*/
|
|
58
62
|
roots?: Root[];
|
|
59
63
|
}
|
|
60
64
|
|
|
61
|
-
export type McpHostOptionsWithCache =
|
|
65
|
+
export type McpHostOptionsWithCache<M extends boolean = false> = Omit<
|
|
66
|
+
McpHostOptions<M>,
|
|
67
|
+
'name'
|
|
68
|
+
> & {
|
|
62
69
|
/**
|
|
63
70
|
* A client name for this MCP host. This name is advertised to MCP Servers
|
|
64
71
|
* as the connecting client name.
|
|
@@ -92,9 +99,9 @@ interface ClientState {
|
|
|
92
99
|
* It allows for dynamic registration of tools from all connected and enabled MCP servers
|
|
93
100
|
* into a Genkit instance.
|
|
94
101
|
*/
|
|
95
|
-
export class GenkitMcpHost {
|
|
102
|
+
export class GenkitMcpHost<Multipart extends boolean = false> {
|
|
96
103
|
name: string;
|
|
97
|
-
private _clients: Record<string, GenkitMcpClient
|
|
104
|
+
private _clients: Record<string, GenkitMcpClient<Multipart>> = {};
|
|
98
105
|
private _clientStates: Record<string, ClientState> = {};
|
|
99
106
|
private _readyListeners: {
|
|
100
107
|
resolve: () => void;
|
|
@@ -104,10 +111,12 @@ export class GenkitMcpHost {
|
|
|
104
111
|
private _dynamicActionProvider: DynamicActionProviderAction | undefined;
|
|
105
112
|
private roots: Root[] | undefined;
|
|
106
113
|
rawToolResponses?: boolean;
|
|
114
|
+
multipart?: Multipart;
|
|
107
115
|
|
|
108
|
-
constructor(options: McpHostOptions) {
|
|
116
|
+
constructor(options: McpHostOptions<Multipart>) {
|
|
109
117
|
this.name = options.name || 'genkit-mcp';
|
|
110
118
|
this.rawToolResponses = options.rawToolResponses;
|
|
119
|
+
this.multipart = options.multipart;
|
|
111
120
|
this.roots = options.roots;
|
|
112
121
|
|
|
113
122
|
if (options.mcpServers) {
|
|
@@ -165,11 +174,12 @@ export class GenkitMcpHost {
|
|
|
165
174
|
`[MCP Host] Connecting to MCP server '${serverName}' in host '${this.name}'.`
|
|
166
175
|
);
|
|
167
176
|
try {
|
|
168
|
-
const client = new GenkitMcpClient({
|
|
177
|
+
const client = new GenkitMcpClient<Multipart>({
|
|
169
178
|
name: this.name,
|
|
170
179
|
serverName: serverName,
|
|
171
180
|
mcpServer: { ...config, roots: config.roots || this.roots },
|
|
172
181
|
rawToolResponses: this.rawToolResponses,
|
|
182
|
+
multipart: this.multipart,
|
|
173
183
|
});
|
|
174
184
|
this._clients[serverName] = client;
|
|
175
185
|
} catch (e) {
|
|
@@ -353,9 +363,13 @@ export class GenkitMcpHost {
|
|
|
353
363
|
* @returns A Promise that resolves to an array of `ToolAction` from all
|
|
354
364
|
* active MCP clients.
|
|
355
365
|
*/
|
|
356
|
-
async getActiveTools(
|
|
366
|
+
async getActiveTools(
|
|
367
|
+
ai: Genkit
|
|
368
|
+
): Promise<(Multipart extends true ? MultipartToolAction : ToolAction)[]> {
|
|
357
369
|
await this.ready();
|
|
358
|
-
let allTools:
|
|
370
|
+
let allTools: (Multipart extends true
|
|
371
|
+
? MultipartToolAction
|
|
372
|
+
: ToolAction)[] = [];
|
|
359
373
|
|
|
360
374
|
for (const serverName in this._clients) {
|
|
361
375
|
const client = this._clients[serverName];
|
|
@@ -510,7 +524,7 @@ export class GenkitMcpHost {
|
|
|
510
524
|
/**
|
|
511
525
|
* Returns an array of all active clients.
|
|
512
526
|
*/
|
|
513
|
-
get activeClients(): GenkitMcpClient[] {
|
|
527
|
+
get activeClients(): GenkitMcpClient<Multipart>[] {
|
|
514
528
|
return Object.values(this._clients).filter((c) => c.isEnabled());
|
|
515
529
|
}
|
|
516
530
|
|
package/src/index.ts
CHANGED
|
@@ -70,8 +70,10 @@ export interface McpServerOptions {
|
|
|
70
70
|
* @param options Configuration for the MCP Client Host, including the definitions of MCP servers to connect to.
|
|
71
71
|
* @returns A new instance of GenkitMcpHost.
|
|
72
72
|
*/
|
|
73
|
-
export function createMcpHost
|
|
74
|
-
|
|
73
|
+
export function createMcpHost<M extends boolean = false>(
|
|
74
|
+
options: McpHostOptions<M>
|
|
75
|
+
) {
|
|
76
|
+
return new GenkitMcpHost<M>(options);
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
/**
|
|
@@ -97,8 +99,11 @@ export function createMcpHost(options: McpHostOptions) {
|
|
|
97
99
|
* @param options Configuration for the MCP Client Host, including the definitions of MCP servers to connect to.
|
|
98
100
|
* @returns A new instance of GenkitMcpHost.
|
|
99
101
|
*/
|
|
100
|
-
export function defineMcpHost
|
|
101
|
-
|
|
102
|
+
export function defineMcpHost<M extends boolean = false>(
|
|
103
|
+
ai: Genkit,
|
|
104
|
+
options: McpHostOptionsWithCache<M>
|
|
105
|
+
) {
|
|
106
|
+
const mcpHost = new GenkitMcpHost<M>(options);
|
|
102
107
|
const dap = ai.defineDynamicActionProvider(
|
|
103
108
|
{
|
|
104
109
|
name: options.name,
|
|
@@ -135,8 +140,10 @@ export function defineMcpHost(ai: Genkit, options: McpHostOptionsWithCache) {
|
|
|
135
140
|
* to the MCP server and its behavior.
|
|
136
141
|
* @returns A new instance of GenkitMcpClient.
|
|
137
142
|
*/
|
|
138
|
-
export function createMcpClient
|
|
139
|
-
|
|
143
|
+
export function createMcpClient<M extends boolean = false>(
|
|
144
|
+
options: McpClientOptions<M>
|
|
145
|
+
) {
|
|
146
|
+
return new GenkitMcpClient<M>(options);
|
|
140
147
|
}
|
|
141
148
|
|
|
142
149
|
/**
|
|
@@ -165,11 +172,11 @@ export function createMcpClient(options: McpClientOptions) {
|
|
|
165
172
|
* to the MCP server and its behavior.
|
|
166
173
|
* @returns A new instance of GenkitMcpClient.
|
|
167
174
|
*/
|
|
168
|
-
export function defineMcpClient(
|
|
175
|
+
export function defineMcpClient<M extends boolean = false>(
|
|
169
176
|
ai: Genkit,
|
|
170
|
-
options: McpClientOptionsWithCache
|
|
177
|
+
options: McpClientOptionsWithCache<M>
|
|
171
178
|
) {
|
|
172
|
-
const mcpClient = new GenkitMcpClient(options);
|
|
179
|
+
const mcpClient = new GenkitMcpClient<M>(options);
|
|
173
180
|
const dap = ai.defineDynamicActionProvider(
|
|
174
181
|
{
|
|
175
182
|
name: options.name,
|
package/src/util/tools.ts
CHANGED
|
@@ -19,7 +19,15 @@ import type {
|
|
|
19
19
|
CallToolResult,
|
|
20
20
|
Tool,
|
|
21
21
|
} from '@modelcontextprotocol/sdk/types.js' with { 'resolution-mode': 'import' };
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
JSONSchema7,
|
|
24
|
+
tool as genkitTool,
|
|
25
|
+
z,
|
|
26
|
+
type Genkit,
|
|
27
|
+
type MultipartToolAction,
|
|
28
|
+
type Part,
|
|
29
|
+
type ToolAction,
|
|
30
|
+
} from 'genkit';
|
|
23
31
|
import { logger } from 'genkit/logging';
|
|
24
32
|
|
|
25
33
|
const toText = (c: CallToolResult['content']) =>
|
|
@@ -27,6 +35,9 @@ const toText = (c: CallToolResult['content']) =>
|
|
|
27
35
|
|
|
28
36
|
function processResult(result: CallToolResult) {
|
|
29
37
|
if (result.isError) return { error: toText(result.content) };
|
|
38
|
+
if (result.structuredContent !== undefined) {
|
|
39
|
+
return result.structuredContent;
|
|
40
|
+
}
|
|
30
41
|
if (result.content.every((c) => c.type === 'text' && !!c.text)) {
|
|
31
42
|
const text = toText(result.content);
|
|
32
43
|
if (text.trim().startsWith('{') || text.trim().startsWith('[')) {
|
|
@@ -42,6 +53,81 @@ function processResult(result: CallToolResult) {
|
|
|
42
53
|
return result;
|
|
43
54
|
}
|
|
44
55
|
|
|
56
|
+
function processMultipartResult(result: CallToolResult) {
|
|
57
|
+
if (result.isError) {
|
|
58
|
+
return {
|
|
59
|
+
output: { error: toText(result.content) },
|
|
60
|
+
metadata: result._meta,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const content: Part[] = [];
|
|
65
|
+
let textOutput = '';
|
|
66
|
+
|
|
67
|
+
for (const c of result.content) {
|
|
68
|
+
if (c.type === 'text') {
|
|
69
|
+
if (c.text) {
|
|
70
|
+
textOutput += c.text;
|
|
71
|
+
}
|
|
72
|
+
} else if (c.type === 'image' || c.type === 'audio') {
|
|
73
|
+
if (c.data) {
|
|
74
|
+
content.push({
|
|
75
|
+
media: {
|
|
76
|
+
url: `data:${c.mimeType};base64,${c.data}`,
|
|
77
|
+
contentType: c.mimeType,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
} else if (c.type === 'resource_link') {
|
|
82
|
+
if (c.uri) {
|
|
83
|
+
content.push({
|
|
84
|
+
resource: {
|
|
85
|
+
uri: c.uri,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
} else if (c.type === 'resource') {
|
|
90
|
+
if (c.resource) {
|
|
91
|
+
if ('text' in c.resource && c.resource.text) {
|
|
92
|
+
textOutput +=
|
|
93
|
+
(textOutput ? '\n\n' : '') +
|
|
94
|
+
`Resource (${c.resource.uri}):\n${c.resource.text}`;
|
|
95
|
+
} else if ('blob' in c.resource && c.resource.blob) {
|
|
96
|
+
content.push({
|
|
97
|
+
media: {
|
|
98
|
+
url: `data:${c.resource.mimeType};base64,${c.resource.blob}`,
|
|
99
|
+
contentType: c.resource.mimeType,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let output: unknown = result.structuredContent;
|
|
108
|
+
|
|
109
|
+
if (output === undefined && textOutput) {
|
|
110
|
+
if (
|
|
111
|
+
textOutput.trim().startsWith('{') ||
|
|
112
|
+
textOutput.trim().startsWith('[')
|
|
113
|
+
) {
|
|
114
|
+
try {
|
|
115
|
+
output = JSON.parse(textOutput);
|
|
116
|
+
} catch (e) {
|
|
117
|
+
output = textOutput;
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
output = textOutput;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
...(output !== undefined ? { output } : {}),
|
|
126
|
+
...(content.length > 0 ? { content } : {}),
|
|
127
|
+
metadata: result._meta,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
45
131
|
/**
|
|
46
132
|
* Registers a single MCP tool as a Genkit tool.
|
|
47
133
|
* It defines a new Genkit tool action that, when called, will
|
|
@@ -57,32 +143,69 @@ function registerTool(
|
|
|
57
143
|
ai: Genkit,
|
|
58
144
|
client: Client,
|
|
59
145
|
tool: Tool,
|
|
60
|
-
params: {
|
|
146
|
+
params: {
|
|
147
|
+
serverName: string;
|
|
148
|
+
name: string;
|
|
149
|
+
rawToolResponses?: boolean;
|
|
150
|
+
multipart?: boolean;
|
|
151
|
+
}
|
|
61
152
|
) {
|
|
62
153
|
logger.debug(
|
|
63
|
-
`[MCP] Registering tool '${params.name}/${tool.name}'
|
|
64
|
-
);
|
|
65
|
-
ai.defineTool(
|
|
66
|
-
{
|
|
67
|
-
name: `${params.serverName}/${tool.name}`,
|
|
68
|
-
description: tool.description || '',
|
|
69
|
-
inputJsonSchema: tool.inputSchema as JSONSchema7,
|
|
70
|
-
outputSchema: z.any(),
|
|
71
|
-
metadata: { mcp: { _meta: tool._meta || {} } },
|
|
72
|
-
},
|
|
73
|
-
async (args) => {
|
|
74
|
-
logger.debug(
|
|
75
|
-
`[MCP] Calling MCP tool '${params.serverName}/${tool.name}' with arguments`,
|
|
76
|
-
JSON.stringify(args)
|
|
77
|
-
);
|
|
78
|
-
const result = await client.callTool({
|
|
79
|
-
name: tool.name,
|
|
80
|
-
arguments: args,
|
|
81
|
-
});
|
|
82
|
-
if (params.rawToolResponses) return result;
|
|
83
|
-
return processResult(result as CallToolResult);
|
|
84
|
-
}
|
|
154
|
+
`[MCP] Registering tool '${params.name}/${tool.name}' from server '${params.serverName}'`
|
|
85
155
|
);
|
|
156
|
+
if (params.multipart && params.rawToolResponses) {
|
|
157
|
+
logger.warn(
|
|
158
|
+
`[MCP] Tool '${params.serverName}/${tool.name}' is configured with both multipart and rawToolResponses. Genkit will return the raw MCP CallToolResult in the output field, and media parts will not be natively parsed.`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
if (params.multipart) {
|
|
162
|
+
ai.defineTool(
|
|
163
|
+
{
|
|
164
|
+
name: `${params.serverName}/${tool.name}`,
|
|
165
|
+
description: tool.description || '',
|
|
166
|
+
inputJsonSchema: tool.inputSchema as JSONSchema7,
|
|
167
|
+
outputSchema: z.any(),
|
|
168
|
+
metadata: { mcp: { _meta: tool._meta || {} } },
|
|
169
|
+
multipart: true as const,
|
|
170
|
+
},
|
|
171
|
+
async (args, { context }) => {
|
|
172
|
+
logger.debug(
|
|
173
|
+
`[MCP] Calling MCP tool '${params.serverName}/${tool.name}' with arguments`,
|
|
174
|
+
JSON.stringify(args)
|
|
175
|
+
);
|
|
176
|
+
const result = await client.callTool({
|
|
177
|
+
name: tool.name,
|
|
178
|
+
arguments: args,
|
|
179
|
+
_meta: context?.mcp?._meta,
|
|
180
|
+
});
|
|
181
|
+
if (params.rawToolResponses) return { output: result };
|
|
182
|
+
return processMultipartResult(result as CallToolResult);
|
|
183
|
+
}
|
|
184
|
+
);
|
|
185
|
+
} else {
|
|
186
|
+
ai.defineTool(
|
|
187
|
+
{
|
|
188
|
+
name: `${params.serverName}/${tool.name}`,
|
|
189
|
+
description: tool.description || '',
|
|
190
|
+
inputJsonSchema: tool.inputSchema as JSONSchema7,
|
|
191
|
+
outputSchema: z.any(),
|
|
192
|
+
metadata: { mcp: { _meta: tool._meta || {} } },
|
|
193
|
+
},
|
|
194
|
+
async (args, { context }) => {
|
|
195
|
+
logger.debug(
|
|
196
|
+
`[MCP] Calling MCP tool '${params.serverName}/${tool.name}' with arguments`,
|
|
197
|
+
JSON.stringify(args)
|
|
198
|
+
);
|
|
199
|
+
const result = await client.callTool({
|
|
200
|
+
name: tool.name,
|
|
201
|
+
arguments: args,
|
|
202
|
+
_meta: context?.mcp?._meta,
|
|
203
|
+
});
|
|
204
|
+
if (params.rawToolResponses) return result;
|
|
205
|
+
return processResult(result as CallToolResult);
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
}
|
|
86
209
|
}
|
|
87
210
|
|
|
88
211
|
/**
|
|
@@ -96,13 +219,48 @@ function registerTool(
|
|
|
96
219
|
* @param params Configuration parameters including namespacing and raw response flag.
|
|
97
220
|
* @returns A Genkit `ToolAction` representing the MCP tool.
|
|
98
221
|
*/
|
|
99
|
-
function createDynamicTool(
|
|
222
|
+
function createDynamicTool<Multipart extends boolean = false>(
|
|
100
223
|
ai: Genkit,
|
|
101
224
|
client: Client,
|
|
102
225
|
tool: Tool,
|
|
103
|
-
params: {
|
|
104
|
-
|
|
105
|
-
|
|
226
|
+
params: {
|
|
227
|
+
serverName: string;
|
|
228
|
+
name: string;
|
|
229
|
+
rawToolResponses?: boolean;
|
|
230
|
+
multipart?: Multipart;
|
|
231
|
+
}
|
|
232
|
+
): Multipart extends true ? MultipartToolAction : ToolAction {
|
|
233
|
+
if (params.multipart && params.rawToolResponses) {
|
|
234
|
+
logger.warn(
|
|
235
|
+
`[MCP] Tool '${params.serverName}/${tool.name}' is configured with both multipart and rawToolResponses. Genkit will return the raw MCP CallToolResult in the output field, and media parts will not be natively parsed.`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
if (params.multipart) {
|
|
239
|
+
return genkitTool(
|
|
240
|
+
{
|
|
241
|
+
name: `${params.serverName}/${tool.name}`,
|
|
242
|
+
description: tool.description || '',
|
|
243
|
+
inputJsonSchema: tool.inputSchema as JSONSchema7,
|
|
244
|
+
outputSchema: z.any(),
|
|
245
|
+
metadata: { mcp: { _meta: tool._meta || {} } },
|
|
246
|
+
multipart: true as const,
|
|
247
|
+
},
|
|
248
|
+
async (args, { context }) => {
|
|
249
|
+
logger.debug(
|
|
250
|
+
`[MCP] calling tool '${params.serverName}/${tool.name}' in host '${params.name}'`
|
|
251
|
+
);
|
|
252
|
+
const result = await client.callTool({
|
|
253
|
+
name: tool.name,
|
|
254
|
+
arguments: args,
|
|
255
|
+
_meta: context?.mcp?._meta,
|
|
256
|
+
});
|
|
257
|
+
if (params.rawToolResponses) return { output: result };
|
|
258
|
+
return processMultipartResult(result as CallToolResult);
|
|
259
|
+
}
|
|
260
|
+
) as unknown as Multipart extends true ? MultipartToolAction : ToolAction;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return genkitTool(
|
|
106
264
|
{
|
|
107
265
|
name: `${params.serverName}/${tool.name}`,
|
|
108
266
|
description: tool.description || '',
|
|
@@ -119,10 +277,10 @@ function createDynamicTool(
|
|
|
119
277
|
arguments: args,
|
|
120
278
|
_meta: context?.mcp?._meta,
|
|
121
279
|
});
|
|
122
|
-
if (params.rawToolResponses) return result;
|
|
280
|
+
if (params.rawToolResponses) return result as CallToolResult;
|
|
123
281
|
return processResult(result as CallToolResult);
|
|
124
282
|
}
|
|
125
|
-
);
|
|
283
|
+
) as unknown as Multipart extends true ? MultipartToolAction : ToolAction;
|
|
126
284
|
}
|
|
127
285
|
|
|
128
286
|
/**
|
|
@@ -131,7 +289,12 @@ function createDynamicTool(
|
|
|
131
289
|
export async function registerAllTools(
|
|
132
290
|
ai: Genkit,
|
|
133
291
|
client: Client,
|
|
134
|
-
params: {
|
|
292
|
+
params: {
|
|
293
|
+
name: string;
|
|
294
|
+
serverName: string;
|
|
295
|
+
rawToolResponses?: boolean;
|
|
296
|
+
multipart?: boolean;
|
|
297
|
+
}
|
|
135
298
|
): Promise<void> {
|
|
136
299
|
let cursor: string | undefined;
|
|
137
300
|
while (true) {
|
|
@@ -145,13 +308,19 @@ export async function registerAllTools(
|
|
|
145
308
|
/**
|
|
146
309
|
* Lookup all tools available in the server and fetches as a Genkit dynamic tool.
|
|
147
310
|
*/
|
|
148
|
-
export async function fetchDynamicTools(
|
|
311
|
+
export async function fetchDynamicTools<Multipart extends boolean = false>(
|
|
149
312
|
ai: Genkit,
|
|
150
313
|
client: Client,
|
|
151
|
-
params: {
|
|
152
|
-
|
|
314
|
+
params: {
|
|
315
|
+
name: string;
|
|
316
|
+
serverName: string;
|
|
317
|
+
rawToolResponses?: boolean;
|
|
318
|
+
multipart?: Multipart;
|
|
319
|
+
}
|
|
320
|
+
): Promise<(Multipart extends true ? MultipartToolAction : ToolAction)[]> {
|
|
153
321
|
let cursor: string | undefined;
|
|
154
|
-
let allTools: ToolAction[] =
|
|
322
|
+
let allTools: (Multipart extends true ? MultipartToolAction : ToolAction)[] =
|
|
323
|
+
[];
|
|
155
324
|
while (true) {
|
|
156
325
|
const { nextCursor, tools } = await client.listTools({ cursor });
|
|
157
326
|
allTools.push(
|
package/tests/host_test.ts
CHANGED
|
@@ -251,12 +251,162 @@ describe('createMcpHost', () => {
|
|
|
251
251
|
],
|
|
252
252
|
};
|
|
253
253
|
|
|
254
|
-
const tool = (await clientHost.getActiveTools(ai))[0];
|
|
254
|
+
const tool: ToolAction = (await clientHost.getActiveTools(ai))[0];
|
|
255
255
|
const response = await tool({
|
|
256
256
|
foo: 'bar',
|
|
257
257
|
});
|
|
258
258
|
assert.deepStrictEqual(response, 'yep {"foo":"bar"}');
|
|
259
259
|
});
|
|
260
|
+
|
|
261
|
+
it('should call the multipart tool', async () => {
|
|
262
|
+
const multipartHost = createMcpHost({
|
|
263
|
+
name: 'test-multipart-host',
|
|
264
|
+
multipart: true,
|
|
265
|
+
mcpServers: {
|
|
266
|
+
'test-server': {
|
|
267
|
+
transport: fakeTransport,
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
await multipartHost.ready();
|
|
272
|
+
|
|
273
|
+
fakeTransport.callToolResult = {
|
|
274
|
+
content: [
|
|
275
|
+
{
|
|
276
|
+
type: 'text',
|
|
277
|
+
text: 'yep {"foo":"bar"}',
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
type: 'image',
|
|
281
|
+
data: 'base64data',
|
|
282
|
+
mimeType: 'image/png',
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
type: 'resource',
|
|
286
|
+
resource: {
|
|
287
|
+
uri: 'file:///foo.txt',
|
|
288
|
+
text: 'hello resource',
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
type: 'resource',
|
|
293
|
+
resource: {
|
|
294
|
+
uri: 'file:///blob.bin',
|
|
295
|
+
blob: 'base64blob',
|
|
296
|
+
mimeType: 'application/octet-stream',
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
],
|
|
300
|
+
_meta: {
|
|
301
|
+
someData: true,
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const tools = await multipartHost.getActiveTools(ai);
|
|
306
|
+
const tool = tools[0];
|
|
307
|
+
const response = await tool(
|
|
308
|
+
{ foo: 'bar' },
|
|
309
|
+
{ context: { mcp: { _meta: { soMeta: true } } } }
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
assert.deepStrictEqual(response, {
|
|
313
|
+
output:
|
|
314
|
+
'yep {"foo":"bar"}\n\nResource (file:///foo.txt):\nhello resource{"soMeta":true}',
|
|
315
|
+
content: [
|
|
316
|
+
{
|
|
317
|
+
media: {
|
|
318
|
+
url: 'data:image/png;base64,base64data',
|
|
319
|
+
contentType: 'image/png',
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
media: {
|
|
324
|
+
url: 'data:application/octet-stream;base64,base64blob',
|
|
325
|
+
contentType: 'application/octet-stream',
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
],
|
|
329
|
+
metadata: {
|
|
330
|
+
someData: true,
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('should call the multipart tool and handle errors', async () => {
|
|
336
|
+
const multipartHost = createMcpHost({
|
|
337
|
+
name: 'test-multipart-host',
|
|
338
|
+
multipart: true,
|
|
339
|
+
mcpServers: {
|
|
340
|
+
'test-server': {
|
|
341
|
+
transport: fakeTransport,
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
await multipartHost.ready();
|
|
346
|
+
|
|
347
|
+
fakeTransport.callToolResult = {
|
|
348
|
+
isError: true,
|
|
349
|
+
content: [
|
|
350
|
+
{
|
|
351
|
+
type: 'text',
|
|
352
|
+
text: 'Simulated tool failure',
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
_meta: {
|
|
356
|
+
errorCode: 500,
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const tools = await multipartHost.getActiveTools(ai);
|
|
361
|
+
const tool = tools[0];
|
|
362
|
+
const response = await tool({ foo: 'bar' });
|
|
363
|
+
|
|
364
|
+
assert.deepStrictEqual(response, {
|
|
365
|
+
output: { error: 'Simulated tool failure' },
|
|
366
|
+
metadata: {
|
|
367
|
+
errorCode: 500,
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('should return raw tool response when rawToolResponses is true alongside multipart', async () => {
|
|
373
|
+
const multipartHost = createMcpHost({
|
|
374
|
+
name: 'test-multipart-host',
|
|
375
|
+
multipart: true,
|
|
376
|
+
rawToolResponses: true,
|
|
377
|
+
mcpServers: {
|
|
378
|
+
'test-server': {
|
|
379
|
+
transport: fakeTransport,
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
await multipartHost.ready();
|
|
384
|
+
|
|
385
|
+
fakeTransport.callToolResult = {
|
|
386
|
+
content: [
|
|
387
|
+
{
|
|
388
|
+
type: 'text',
|
|
389
|
+
text: 'yep {"foo":"bar"}',
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const tools = await multipartHost.getActiveTools(ai);
|
|
395
|
+
const tool = tools[0];
|
|
396
|
+
const response = await tool({ foo: 'bar' });
|
|
397
|
+
|
|
398
|
+
// Genkit output schema for multipart expects `{ output: ... }` when returning raw responses
|
|
399
|
+
assert.deepStrictEqual(response, {
|
|
400
|
+
output: {
|
|
401
|
+
content: [
|
|
402
|
+
{
|
|
403
|
+
type: 'text',
|
|
404
|
+
text: 'yep {"foo":"bar"}',
|
|
405
|
+
},
|
|
406
|
+
],
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
});
|
|
260
410
|
});
|
|
261
411
|
|
|
262
412
|
describe('prompts', () => {
|