@ian-pascoe/pi-mcp 0.1.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/LICENSE +21 -0
- package/README.md +193 -0
- package/dist/pi-mcp-cli.js +10948 -0
- package/package.json +64 -0
- package/src/index.ts +2 -0
- package/src/mcp-auth-store.ts +393 -0
- package/src/mcp-command.ts +893 -0
- package/src/mcp-content.ts +212 -0
- package/src/mcp-host.ts +971 -0
- package/src/mcp-oauth.ts +740 -0
- package/src/mcp-server-client.ts +375 -0
- package/src/mcp-session-files.ts +127 -0
- package/src/mcp-settings-store.ts +455 -0
- package/src/mcp-tool-catalog.ts +464 -0
- package/src/pi-mcp-cli.ts +507 -0
- package/src/pi-mcp-extension.ts +1013 -0
- package/src/pi-mcp-settings.ts +619 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
fromJsonSchema,
|
|
4
|
+
type JSONValue,
|
|
5
|
+
type JsonSchemaType,
|
|
6
|
+
type ToolAnnotations,
|
|
7
|
+
} from "@modelcontextprotocol/client";
|
|
8
|
+
import type {
|
|
9
|
+
AgentToolResult,
|
|
10
|
+
AgentToolUpdateCallback,
|
|
11
|
+
ExtensionContext,
|
|
12
|
+
ToolDefinition,
|
|
13
|
+
ToolResultEvent,
|
|
14
|
+
} from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import { Type, type TSchema } from "typebox";
|
|
16
|
+
import { Value } from "typebox/value";
|
|
17
|
+
|
|
18
|
+
const RESOURCE_TOOL_NAMES = [
|
|
19
|
+
"list_mcp_resources",
|
|
20
|
+
"list_mcp_resource_templates",
|
|
21
|
+
"read_mcp_resource",
|
|
22
|
+
] as const;
|
|
23
|
+
const MCP_DETAILS_OWNER = "pi-mcp";
|
|
24
|
+
const McpResultDetailsMarkerSchema = Type.Object(
|
|
25
|
+
{
|
|
26
|
+
mcp: Type.Object(
|
|
27
|
+
{
|
|
28
|
+
isError: Type.Boolean(),
|
|
29
|
+
owner: Type.Literal(MCP_DETAILS_OWNER),
|
|
30
|
+
},
|
|
31
|
+
{ additionalProperties: true },
|
|
32
|
+
),
|
|
33
|
+
},
|
|
34
|
+
{ additionalProperties: true },
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const ListResourcesSchema = {
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: { server: { type: "string", minLength: 1 } },
|
|
40
|
+
additionalProperties: false,
|
|
41
|
+
} as const;
|
|
42
|
+
const ReadResourceSchema = {
|
|
43
|
+
type: "object",
|
|
44
|
+
properties: {
|
|
45
|
+
server: { type: "string", minLength: 1 },
|
|
46
|
+
uri: { type: "string", minLength: 1 },
|
|
47
|
+
},
|
|
48
|
+
required: ["server", "uri"],
|
|
49
|
+
additionalProperties: false,
|
|
50
|
+
} as const;
|
|
51
|
+
|
|
52
|
+
/** Minimal public Pi surface required to register and activate MCP tools. */
|
|
53
|
+
export interface McpToolCatalogPi {
|
|
54
|
+
/** Register or replace one Pi tool by name. */
|
|
55
|
+
registerTool<TParameters extends TSchema, TDetails>(
|
|
56
|
+
tool: ToolDefinition<TParameters, TDetails>,
|
|
57
|
+
): void;
|
|
58
|
+
/** Return every registered tool name, including tools from other extensions. */
|
|
59
|
+
getAllTools(): readonly { readonly name: string }[];
|
|
60
|
+
/** Return the current active tool names. */
|
|
61
|
+
getActiveTools(): string[];
|
|
62
|
+
/** Replace the active tool names. */
|
|
63
|
+
setActiveTools(toolNames: string[]): void;
|
|
64
|
+
/** Observe finalized tool results so MCP errors retain their original content. */
|
|
65
|
+
on(
|
|
66
|
+
event: "tool_result",
|
|
67
|
+
handler: (
|
|
68
|
+
event: ToolResultEvent,
|
|
69
|
+
) => Promise<{ readonly isError?: boolean } | void> | { readonly isError?: boolean } | void,
|
|
70
|
+
): void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Request-scoped Pi execution state forwarded to the MCP Host. */
|
|
74
|
+
export interface McpToolExecution {
|
|
75
|
+
readonly context: ExtensionContext;
|
|
76
|
+
readonly onUpdate: AgentToolUpdateCallback<JSONValue | undefined> | undefined;
|
|
77
|
+
readonly signal: AbortSignal | undefined;
|
|
78
|
+
readonly toolCallId: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** MCP operation result already mapped to Pi-native text and image content. */
|
|
82
|
+
export interface McpToolOperationResult extends AgentToolResult<JSONValue | undefined> {
|
|
83
|
+
readonly isError?: boolean;
|
|
84
|
+
readonly structuredContent?: JSONValue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Server Tool fields retained from the validated MCP catalog boundary. */
|
|
88
|
+
export interface McpServerToolDefinition {
|
|
89
|
+
readonly annotations?: ToolAnnotations;
|
|
90
|
+
readonly description?: string;
|
|
91
|
+
readonly inputSchema: JsonSchemaType;
|
|
92
|
+
readonly name: string;
|
|
93
|
+
readonly outputSchema?: JsonSchemaType;
|
|
94
|
+
readonly title?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** JSON arguments accepted by one Server Tool after exact-schema validation. */
|
|
98
|
+
export type McpServerToolArguments = Record<string, JSONValue>;
|
|
99
|
+
|
|
100
|
+
/** Optional server selector accepted by fixed Resource list tools. */
|
|
101
|
+
export interface McpListResourcesParameters {
|
|
102
|
+
readonly server?: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Server and URI accepted by the fixed Resource read tool. */
|
|
106
|
+
export interface McpReadResourceParameters {
|
|
107
|
+
readonly server: string;
|
|
108
|
+
readonly uri: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Host operations dispatched by registered Server Tools and fixed Resource tools. */
|
|
112
|
+
export interface McpToolCatalogRuntime {
|
|
113
|
+
/** Call one Server Tool using its original server and tool names. */
|
|
114
|
+
callServerTool(
|
|
115
|
+
serverId: string,
|
|
116
|
+
toolName: string,
|
|
117
|
+
arguments_: McpServerToolArguments,
|
|
118
|
+
execution: McpToolExecution,
|
|
119
|
+
): Promise<McpToolOperationResult>;
|
|
120
|
+
/** List Resources, optionally for one server. */
|
|
121
|
+
listResources(
|
|
122
|
+
parameters: McpListResourcesParameters,
|
|
123
|
+
execution: McpToolExecution,
|
|
124
|
+
): Promise<McpToolOperationResult>;
|
|
125
|
+
/** List Resource Templates, optionally for one server. */
|
|
126
|
+
listResourceTemplates(
|
|
127
|
+
parameters: McpListResourcesParameters,
|
|
128
|
+
execution: McpToolExecution,
|
|
129
|
+
): Promise<McpToolOperationResult>;
|
|
130
|
+
/** Read one Resource from one server. */
|
|
131
|
+
readResource(
|
|
132
|
+
parameters: McpReadResourceParameters,
|
|
133
|
+
execution: McpToolExecution,
|
|
134
|
+
): Promise<McpToolOperationResult>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
interface ServerCatalog {
|
|
138
|
+
active: boolean;
|
|
139
|
+
tools: readonly McpServerToolDefinition[];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface CompiledServerTool {
|
|
143
|
+
readonly definition: McpServerToolDefinition;
|
|
144
|
+
readonly inputValidator: ReturnType<typeof fromJsonSchema<McpServerToolArguments>>;
|
|
145
|
+
readonly outputSchemaError?: string;
|
|
146
|
+
readonly outputValidator?: ReturnType<typeof fromJsonSchema<JSONValue>>;
|
|
147
|
+
readonly serverId: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
interface McpResultMarker {
|
|
151
|
+
isError: boolean;
|
|
152
|
+
operation: string;
|
|
153
|
+
outputSchemaError?: string;
|
|
154
|
+
outputSchemaValid?: boolean;
|
|
155
|
+
owner: typeof MCP_DETAILS_OWNER;
|
|
156
|
+
serverId?: string;
|
|
157
|
+
toolName?: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
interface McpResultDetails {
|
|
161
|
+
readonly mcp: McpResultMarker;
|
|
162
|
+
readonly result: JSONValue | undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Expected invalid-input failure raised through Pi's required throwing tool boundary. */
|
|
166
|
+
export class McpServerToolInputError extends Error {
|
|
167
|
+
readonly _tag = "McpServerToolInputError" as const;
|
|
168
|
+
|
|
169
|
+
/** Build an actionable error without including call values. */
|
|
170
|
+
constructor(
|
|
171
|
+
readonly serverId: string,
|
|
172
|
+
readonly toolName: string,
|
|
173
|
+
readonly issues: string,
|
|
174
|
+
) {
|
|
175
|
+
super(`Pi MCP Server Tool input invalid for ${serverId}/${toolName}: ${issues}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function schemaIssues(issues: readonly { readonly message: string }[]): string {
|
|
180
|
+
return issues.map((issue) => issue.message).join("; ");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function sanitizeToolNamePart(value: string): string {
|
|
184
|
+
const sanitized = value.replaceAll(/[^A-Za-z0-9_-]/g, "_");
|
|
185
|
+
return sanitized.length === 0 ? "_" : sanitized;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function serverToolIdentity(serverId: string, toolName: string): string {
|
|
189
|
+
return `${serverId}\0${toolName}`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function collisionName(
|
|
193
|
+
baseName: string,
|
|
194
|
+
identity: string,
|
|
195
|
+
occupiedNames: ReadonlySet<string>,
|
|
196
|
+
): string {
|
|
197
|
+
const hash = createHash("sha256").update(identity).digest("hex");
|
|
198
|
+
for (let length = 8; length <= hash.length; length += 4) {
|
|
199
|
+
const candidate = `${baseName}__${hash.slice(0, length)}`;
|
|
200
|
+
if (!occupiedNames.has(candidate)) return candidate;
|
|
201
|
+
}
|
|
202
|
+
throw new Error(`Pi MCP Server Tool name hash collision for ${baseName}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// oxlint-disable-next-line anti-slop/no-unknown-parameters -- This parser owns Pi's untyped custom tool-result details boundary.
|
|
206
|
+
function parseMcpResultDetails(input: unknown): McpResultDetails | undefined {
|
|
207
|
+
if (!Value.Check(McpResultDetailsMarkerSchema, input)) return undefined;
|
|
208
|
+
// SAFETY: The marker schema established the fields read by the result hook; this module created all matching details objects.
|
|
209
|
+
return input as McpResultDetails;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function execution(
|
|
213
|
+
toolCallId: string,
|
|
214
|
+
signal: AbortSignal | undefined,
|
|
215
|
+
onUpdate: AgentToolUpdateCallback<unknown> | undefined,
|
|
216
|
+
context: ExtensionContext,
|
|
217
|
+
): McpToolExecution {
|
|
218
|
+
const forwardUpdate =
|
|
219
|
+
onUpdate === undefined
|
|
220
|
+
? undefined
|
|
221
|
+
: (partialResult: AgentToolResult<JSONValue | undefined>) => onUpdate(partialResult);
|
|
222
|
+
return { context, onUpdate: forwardUpdate, signal, toolCallId };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Register exact-schema Server Tools and the stable fixed Resource tools for one MCP Host. */
|
|
226
|
+
export class McpToolCatalog {
|
|
227
|
+
private readonly ownedToolNames = new Set<string>();
|
|
228
|
+
private readonly serverCatalogs = new Map<string, ServerCatalog>();
|
|
229
|
+
private resourceToolsActive = false;
|
|
230
|
+
|
|
231
|
+
/** Register inert fixed tools and the MCP error-result bridge. */
|
|
232
|
+
constructor(
|
|
233
|
+
private readonly pi: McpToolCatalogPi,
|
|
234
|
+
private readonly runtime: McpToolCatalogRuntime,
|
|
235
|
+
) {
|
|
236
|
+
this.registerResourceTools();
|
|
237
|
+
this.pi.on("tool_result", (event) => {
|
|
238
|
+
if (!this.ownedToolNames.has(event.toolName)) return;
|
|
239
|
+
const details = parseMcpResultDetails(event.details);
|
|
240
|
+
if (details === undefined || !details.mcp.isError) return;
|
|
241
|
+
return { isError: true };
|
|
242
|
+
});
|
|
243
|
+
this.syncActiveTools([]);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Replace one server's complete advertised tool list and activate valid definitions. */
|
|
247
|
+
replaceServerTools(serverId: string, tools: readonly McpServerToolDefinition[]): void {
|
|
248
|
+
this.serverCatalogs.set(serverId, { active: true, tools: [...tools] });
|
|
249
|
+
this.rebuildServerTools();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Activate or deactivate one server's registered tools without touching foreign tools. */
|
|
253
|
+
setServerActive(serverId: string, active: boolean): void {
|
|
254
|
+
const catalog = this.serverCatalogs.get(serverId);
|
|
255
|
+
if (catalog === undefined || catalog.active === active) return;
|
|
256
|
+
catalog.active = active;
|
|
257
|
+
this.rebuildServerTools();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Activate or deactivate all three fixed Resource tools as one stable capability surface. */
|
|
261
|
+
setResourceToolsActive(active: boolean): void {
|
|
262
|
+
if (this.resourceToolsActive === active) return;
|
|
263
|
+
this.resourceToolsActive = active;
|
|
264
|
+
this.rebuildServerTools();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private rebuildServerTools(): void {
|
|
268
|
+
const compiledTools: CompiledServerTool[] = [];
|
|
269
|
+
for (const [serverId, catalog] of this.serverCatalogs) {
|
|
270
|
+
for (const definition of catalog.tools) {
|
|
271
|
+
try {
|
|
272
|
+
// SAFETY: fromJsonSchema is the owning boundary parser and rejects values that are not JSON Schema.
|
|
273
|
+
const inputValidator = fromJsonSchema<McpServerToolArguments>(definition.inputSchema);
|
|
274
|
+
let outputValidator: ReturnType<typeof fromJsonSchema<JSONValue>> | undefined;
|
|
275
|
+
let outputSchemaError: string | undefined;
|
|
276
|
+
if (definition.outputSchema !== undefined) {
|
|
277
|
+
try {
|
|
278
|
+
// SAFETY: fromJsonSchema is the owning boundary parser and rejects values that are not JSON Schema.
|
|
279
|
+
outputValidator = fromJsonSchema<JSONValue>(definition.outputSchema);
|
|
280
|
+
} catch (cause) {
|
|
281
|
+
outputSchemaError = cause instanceof Error ? cause.message : String(cause);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
let compiled: CompiledServerTool;
|
|
285
|
+
if (outputValidator !== undefined) {
|
|
286
|
+
compiled = { definition, inputValidator, outputValidator, serverId };
|
|
287
|
+
} else if (outputSchemaError !== undefined) {
|
|
288
|
+
compiled = { definition, inputValidator, outputSchemaError, serverId };
|
|
289
|
+
} else {
|
|
290
|
+
compiled = { definition, inputValidator, serverId };
|
|
291
|
+
}
|
|
292
|
+
compiledTools.push(compiled);
|
|
293
|
+
} catch {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
compiledTools.sort((left, right) =>
|
|
299
|
+
serverToolIdentity(left.serverId, left.definition.name).localeCompare(
|
|
300
|
+
serverToolIdentity(right.serverId, right.definition.name),
|
|
301
|
+
),
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
const foreignNames = new Set(
|
|
305
|
+
this.pi
|
|
306
|
+
.getAllTools()
|
|
307
|
+
.map(({ name }) => name)
|
|
308
|
+
.filter((name) => !this.ownedToolNames.has(name)),
|
|
309
|
+
);
|
|
310
|
+
const occupiedNames = new Set([...foreignNames, ...RESOURCE_TOOL_NAMES]);
|
|
311
|
+
const activeNames: string[] = [];
|
|
312
|
+
for (const compiled of compiledTools) {
|
|
313
|
+
const baseName = `mcp__${sanitizeToolNamePart(compiled.serverId)}__${sanitizeToolNamePart(compiled.definition.name)}`;
|
|
314
|
+
const identity = serverToolIdentity(compiled.serverId, compiled.definition.name);
|
|
315
|
+
const piToolName = occupiedNames.has(baseName)
|
|
316
|
+
? collisionName(baseName, identity, occupiedNames)
|
|
317
|
+
: baseName;
|
|
318
|
+
occupiedNames.add(piToolName);
|
|
319
|
+
this.ownedToolNames.add(piToolName);
|
|
320
|
+
this.pi.registerTool(this.serverToolDefinition(compiled, piToolName));
|
|
321
|
+
if (this.serverCatalogs.get(compiled.serverId)?.active === true) activeNames.push(piToolName);
|
|
322
|
+
}
|
|
323
|
+
this.syncActiveTools(activeNames);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private serverToolDefinition(compiled: CompiledServerTool, piToolName: string): ToolDefinition {
|
|
327
|
+
// SAFETY: MCP's parsed Tool contract requires inputSchema to be JSON Schema; fromJsonSchema compiled this exact object above. Pi accepts the same structural schema without TypeBox metadata.
|
|
328
|
+
const parameters = compiled.definition.inputSchema as TSchema;
|
|
329
|
+
return {
|
|
330
|
+
name: piToolName,
|
|
331
|
+
label:
|
|
332
|
+
compiled.definition.title ??
|
|
333
|
+
compiled.definition.annotations?.title ??
|
|
334
|
+
compiled.definition.name,
|
|
335
|
+
description:
|
|
336
|
+
compiled.definition.description ?? `Call MCP Server Tool ${compiled.definition.name}.`,
|
|
337
|
+
parameters,
|
|
338
|
+
execute: async (toolCallId, arguments_, signal, onUpdate, context) => {
|
|
339
|
+
const parsed = await compiled.inputValidator["~standard"].validate(arguments_);
|
|
340
|
+
if (parsed.issues !== undefined) {
|
|
341
|
+
throw new McpServerToolInputError(
|
|
342
|
+
compiled.serverId,
|
|
343
|
+
compiled.definition.name,
|
|
344
|
+
schemaIssues(parsed.issues),
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
const result = await this.runtime.callServerTool(
|
|
348
|
+
compiled.serverId,
|
|
349
|
+
compiled.definition.name,
|
|
350
|
+
parsed.value,
|
|
351
|
+
execution(toolCallId, signal, onUpdate, context),
|
|
352
|
+
);
|
|
353
|
+
return this.mapOperationResult(
|
|
354
|
+
result,
|
|
355
|
+
`Server Tool ${compiled.serverId}/${compiled.definition.name}`,
|
|
356
|
+
compiled,
|
|
357
|
+
);
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private async mapOperationResult(
|
|
363
|
+
result: McpToolOperationResult,
|
|
364
|
+
operation: string,
|
|
365
|
+
compiled?: CompiledServerTool,
|
|
366
|
+
): Promise<AgentToolResult<McpResultDetails>> {
|
|
367
|
+
let outputSchemaError = compiled?.outputSchemaError;
|
|
368
|
+
let outputSchemaValid: boolean | undefined;
|
|
369
|
+
if (compiled?.outputValidator !== undefined) {
|
|
370
|
+
const validation = await compiled.outputValidator["~standard"].validate(
|
|
371
|
+
result.structuredContent,
|
|
372
|
+
);
|
|
373
|
+
outputSchemaValid = validation.issues === undefined;
|
|
374
|
+
if (validation.issues !== undefined) outputSchemaError = schemaIssues(validation.issues);
|
|
375
|
+
} else if (outputSchemaError !== undefined) {
|
|
376
|
+
outputSchemaValid = false;
|
|
377
|
+
}
|
|
378
|
+
const content = [...result.content];
|
|
379
|
+
if (outputSchemaValid === false) {
|
|
380
|
+
content.push({
|
|
381
|
+
type: "text",
|
|
382
|
+
text: `[Pi MCP: MCP output schema validation failed for ${operation}; accompanying content was retained.]`,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
const mcp: McpResultMarker = {
|
|
386
|
+
isError: result.isError ?? false,
|
|
387
|
+
operation,
|
|
388
|
+
owner: MCP_DETAILS_OWNER,
|
|
389
|
+
};
|
|
390
|
+
if (outputSchemaError !== undefined) mcp.outputSchemaError = outputSchemaError;
|
|
391
|
+
if (outputSchemaValid !== undefined) mcp.outputSchemaValid = outputSchemaValid;
|
|
392
|
+
if (compiled !== undefined) {
|
|
393
|
+
mcp.serverId = compiled.serverId;
|
|
394
|
+
mcp.toolName = compiled.definition.name;
|
|
395
|
+
}
|
|
396
|
+
return { content, details: { mcp, result: result.details } };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
private registerResourceTools(): void {
|
|
400
|
+
this.registerResourceTool({
|
|
401
|
+
name: "list_mcp_resources",
|
|
402
|
+
label: "List MCP Resources",
|
|
403
|
+
description: "List Resources advertised by connected MCP Servers.",
|
|
404
|
+
parameters: ListResourcesSchema,
|
|
405
|
+
execute: async (toolCallId, parameters, signal, onUpdate, context) =>
|
|
406
|
+
this.mapOperationResult(
|
|
407
|
+
await this.runtime.listResources(
|
|
408
|
+
parameters,
|
|
409
|
+
execution(toolCallId, signal, onUpdate, context),
|
|
410
|
+
),
|
|
411
|
+
"list Resources",
|
|
412
|
+
),
|
|
413
|
+
});
|
|
414
|
+
this.registerResourceTool({
|
|
415
|
+
name: "list_mcp_resource_templates",
|
|
416
|
+
label: "List MCP Resource Templates",
|
|
417
|
+
description: "List Resource Templates advertised by connected MCP Servers.",
|
|
418
|
+
parameters: ListResourcesSchema,
|
|
419
|
+
execute: async (toolCallId, parameters, signal, onUpdate, context) =>
|
|
420
|
+
this.mapOperationResult(
|
|
421
|
+
await this.runtime.listResourceTemplates(
|
|
422
|
+
parameters,
|
|
423
|
+
execution(toolCallId, signal, onUpdate, context),
|
|
424
|
+
),
|
|
425
|
+
"list Resource Templates",
|
|
426
|
+
),
|
|
427
|
+
});
|
|
428
|
+
this.registerResourceTool({
|
|
429
|
+
name: "read_mcp_resource",
|
|
430
|
+
label: "Read MCP Resource",
|
|
431
|
+
description: "Read one Resource from a connected MCP Server.",
|
|
432
|
+
parameters: ReadResourceSchema,
|
|
433
|
+
execute: async (toolCallId, parameters, signal, onUpdate, context) =>
|
|
434
|
+
this.mapOperationResult(
|
|
435
|
+
await this.runtime.readResource(
|
|
436
|
+
parameters,
|
|
437
|
+
execution(toolCallId, signal, onUpdate, context),
|
|
438
|
+
),
|
|
439
|
+
"read Resource",
|
|
440
|
+
),
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
private registerResourceTool<TParameters extends TSchema>(
|
|
445
|
+
tool: ToolDefinition<TParameters, unknown>,
|
|
446
|
+
): void {
|
|
447
|
+
this.ownedToolNames.add(tool.name);
|
|
448
|
+
this.pi.registerTool(tool);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
private syncActiveTools(serverToolNames: readonly string[]): void {
|
|
452
|
+
const foreignActiveNames = this.pi
|
|
453
|
+
.getActiveTools()
|
|
454
|
+
.filter((name) => !this.ownedToolNames.has(name));
|
|
455
|
+
const ownActiveNames = [
|
|
456
|
+
...(this.resourceToolsActive ? RESOURCE_TOOL_NAMES : []),
|
|
457
|
+
...serverToolNames,
|
|
458
|
+
];
|
|
459
|
+
const nextActiveNames = [...foreignActiveNames, ...ownActiveNames];
|
|
460
|
+
if (JSON.stringify(nextActiveNames) !== JSON.stringify(this.pi.getActiveTools())) {
|
|
461
|
+
this.pi.setActiveTools(nextActiveNames);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|