@ian-pascoe/pi-mcp 0.1.0 → 0.2.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/README.md +16 -3
- package/dist/pi-mcp-cli.js +137 -101
- package/package.json +8 -8
- package/src/mcp-command-completion.ts +572 -0
- package/src/mcp-command.ts +272 -131
- package/src/mcp-host.ts +56 -49
- package/src/mcp-observer-ui.ts +195 -0
- package/src/mcp-presentation.ts +660 -0
- package/src/mcp-session-files.ts +15 -6
- package/src/mcp-tool-catalog.ts +59 -44
- package/src/pi-mcp-cli.ts +7 -4
- package/src/pi-mcp-extension.ts +369 -310
- package/src/pi-mcp-settings.ts +23 -7
package/src/mcp-tool-catalog.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* oxlint-disable anti-slop/no-conditional-empty-object-spread -- Exact optional MCP result marker fields are present only when supplied by the protocol operation. */
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
import {
|
|
3
4
|
fromJsonSchema,
|
|
@@ -12,27 +13,22 @@ import type {
|
|
|
12
13
|
ToolDefinition,
|
|
13
14
|
ToolResultEvent,
|
|
14
15
|
} from "@earendil-works/pi-coding-agent";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
16
|
+
import type { TSchema } from "typebox";
|
|
17
|
+
import {
|
|
18
|
+
parseMcpResultDetails,
|
|
19
|
+
renderMcpResourceToolCall,
|
|
20
|
+
renderMcpServerToolCall,
|
|
21
|
+
renderMcpToolResult,
|
|
22
|
+
type McpPresentationRedactor,
|
|
23
|
+
type McpResultDetails,
|
|
24
|
+
type McpResultMarker,
|
|
25
|
+
} from "./mcp-presentation.js";
|
|
17
26
|
|
|
18
27
|
const RESOURCE_TOOL_NAMES = [
|
|
19
28
|
"list_mcp_resources",
|
|
20
29
|
"list_mcp_resource_templates",
|
|
21
30
|
"read_mcp_resource",
|
|
22
31
|
] 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
32
|
|
|
37
33
|
const ListResourcesSchema = {
|
|
38
34
|
type: "object",
|
|
@@ -147,21 +143,6 @@ interface CompiledServerTool {
|
|
|
147
143
|
readonly serverId: string;
|
|
148
144
|
}
|
|
149
145
|
|
|
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
146
|
/** Expected invalid-input failure raised through Pi's required throwing tool boundary. */
|
|
166
147
|
export class McpServerToolInputError extends Error {
|
|
167
148
|
readonly _tag = "McpServerToolInputError" as const;
|
|
@@ -202,13 +183,6 @@ function collisionName(
|
|
|
202
183
|
throw new Error(`Pi MCP Server Tool name hash collision for ${baseName}`);
|
|
203
184
|
}
|
|
204
185
|
|
|
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
186
|
function execution(
|
|
213
187
|
toolCallId: string,
|
|
214
188
|
signal: AbortSignal | undefined,
|
|
@@ -232,6 +206,7 @@ export class McpToolCatalog {
|
|
|
232
206
|
constructor(
|
|
233
207
|
private readonly pi: McpToolCatalogPi,
|
|
234
208
|
private readonly runtime: McpToolCatalogRuntime,
|
|
209
|
+
private readonly redact: McpPresentationRedactor = (text) => text,
|
|
235
210
|
) {
|
|
236
211
|
this.registerResourceTools();
|
|
237
212
|
this.pi.on("tool_result", (event) => {
|
|
@@ -335,6 +310,17 @@ export class McpToolCatalog {
|
|
|
335
310
|
description:
|
|
336
311
|
compiled.definition.description ?? `Call MCP Server Tool ${compiled.definition.name}.`,
|
|
337
312
|
parameters,
|
|
313
|
+
renderCall: (arguments_, theme, context) =>
|
|
314
|
+
renderMcpServerToolCall(
|
|
315
|
+
compiled.serverId,
|
|
316
|
+
compiled.definition.name,
|
|
317
|
+
arguments_,
|
|
318
|
+
theme,
|
|
319
|
+
context.expanded,
|
|
320
|
+
this.redact,
|
|
321
|
+
),
|
|
322
|
+
renderResult: (result, options, theme, context) =>
|
|
323
|
+
renderMcpToolResult(result, options, theme, context.isError, this.redact),
|
|
338
324
|
execute: async (toolCallId, arguments_, signal, onUpdate, context) => {
|
|
339
325
|
const parsed = await compiled.inputValidator["~standard"].validate(arguments_);
|
|
340
326
|
if (parsed.issues !== undefined) {
|
|
@@ -385,14 +371,13 @@ export class McpToolCatalog {
|
|
|
385
371
|
const mcp: McpResultMarker = {
|
|
386
372
|
isError: result.isError ?? false,
|
|
387
373
|
operation,
|
|
388
|
-
|
|
374
|
+
...(outputSchemaError === undefined ? {} : { outputSchemaError }),
|
|
375
|
+
...(outputSchemaValid === undefined ? {} : { outputSchemaValid }),
|
|
376
|
+
owner: "pi-mcp",
|
|
377
|
+
...(compiled === undefined
|
|
378
|
+
? {}
|
|
379
|
+
: { serverId: compiled.serverId, toolName: compiled.definition.name }),
|
|
389
380
|
};
|
|
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
381
|
return { content, details: { mcp, result: result.details } };
|
|
397
382
|
}
|
|
398
383
|
|
|
@@ -402,6 +387,16 @@ export class McpToolCatalog {
|
|
|
402
387
|
label: "List MCP Resources",
|
|
403
388
|
description: "List Resources advertised by connected MCP Servers.",
|
|
404
389
|
parameters: ListResourcesSchema,
|
|
390
|
+
renderCall: (parameters, theme, context) =>
|
|
391
|
+
renderMcpResourceToolCall(
|
|
392
|
+
"list_resources",
|
|
393
|
+
parameters,
|
|
394
|
+
theme,
|
|
395
|
+
context.expanded,
|
|
396
|
+
this.redact,
|
|
397
|
+
),
|
|
398
|
+
renderResult: (result, options, theme, context) =>
|
|
399
|
+
renderMcpToolResult(result, options, theme, context.isError, this.redact),
|
|
405
400
|
execute: async (toolCallId, parameters, signal, onUpdate, context) =>
|
|
406
401
|
this.mapOperationResult(
|
|
407
402
|
await this.runtime.listResources(
|
|
@@ -416,6 +411,16 @@ export class McpToolCatalog {
|
|
|
416
411
|
label: "List MCP Resource Templates",
|
|
417
412
|
description: "List Resource Templates advertised by connected MCP Servers.",
|
|
418
413
|
parameters: ListResourcesSchema,
|
|
414
|
+
renderCall: (parameters, theme, context) =>
|
|
415
|
+
renderMcpResourceToolCall(
|
|
416
|
+
"list_resource_templates",
|
|
417
|
+
parameters,
|
|
418
|
+
theme,
|
|
419
|
+
context.expanded,
|
|
420
|
+
this.redact,
|
|
421
|
+
),
|
|
422
|
+
renderResult: (result, options, theme, context) =>
|
|
423
|
+
renderMcpToolResult(result, options, theme, context.isError, this.redact),
|
|
419
424
|
execute: async (toolCallId, parameters, signal, onUpdate, context) =>
|
|
420
425
|
this.mapOperationResult(
|
|
421
426
|
await this.runtime.listResourceTemplates(
|
|
@@ -430,6 +435,16 @@ export class McpToolCatalog {
|
|
|
430
435
|
label: "Read MCP Resource",
|
|
431
436
|
description: "Read one Resource from a connected MCP Server.",
|
|
432
437
|
parameters: ReadResourceSchema,
|
|
438
|
+
renderCall: (parameters, theme, context) =>
|
|
439
|
+
renderMcpResourceToolCall(
|
|
440
|
+
"read_resource",
|
|
441
|
+
parameters,
|
|
442
|
+
theme,
|
|
443
|
+
context.expanded,
|
|
444
|
+
this.redact,
|
|
445
|
+
),
|
|
446
|
+
renderResult: (result, options, theme, context) =>
|
|
447
|
+
renderMcpToolResult(result, options, theme, context.isError, this.redact),
|
|
433
448
|
execute: async (toolCallId, parameters, signal, onUpdate, context) =>
|
|
434
449
|
this.mapOperationResult(
|
|
435
450
|
await this.runtime.readResource(
|
package/src/pi-mcp-cli.ts
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
// oxlint-disable anti-slop/no-conditional-empty-object-spread -- Exact optional command data requires omitting absent fields at the shared command boundary.
|
|
4
4
|
// oxlint-disable anti-slop/no-runtime-typeof, anti-slop/no-unknown-parameters -- This entrypoint owns trust-store parsing before values reach typed settings adapters.
|
|
5
|
+
import { realpathSync } from "node:fs";
|
|
5
6
|
import { readFile } from "node:fs/promises";
|
|
6
7
|
import { homedir } from "node:os";
|
|
7
8
|
import { dirname, join, resolve } from "node:path";
|
|
8
9
|
import { createInterface } from "node:readline/promises";
|
|
9
|
-
import {
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
10
11
|
import {
|
|
11
12
|
runMcpCommandTokens,
|
|
12
13
|
type McpCommandAdapterResult,
|
|
@@ -151,7 +152,7 @@ async function listStandaloneServers(state: StandaloneMcpState): Promise<McpComm
|
|
|
151
152
|
transport: definition.transport,
|
|
152
153
|
});
|
|
153
154
|
messages.push(
|
|
154
|
-
`${definition.id} (
|
|
155
|
+
`${definition.id} (provenance=${definition.provenance}, ${definition.enabled ? "enabled" : "disabled"}, transport=${definition.transport}, auth=${definition.transport === "stdio" ? "none" : (definition.auth?.type ?? "anonymous")}, stored-auth=${storedAuth ? "present" : "absent"})`,
|
|
155
156
|
);
|
|
156
157
|
}
|
|
157
158
|
for (const mask of settings.masks.values()) {
|
|
@@ -162,7 +163,9 @@ async function listStandaloneServers(state: StandaloneMcpState): Promise<McpComm
|
|
|
162
163
|
name: mask.id,
|
|
163
164
|
provenance: mask.provenance,
|
|
164
165
|
});
|
|
165
|
-
messages.push(
|
|
166
|
+
messages.push(
|
|
167
|
+
`${mask.id} (provenance=${mask.provenance}, masked, inherited=${mask.inherited ? "yes" : "no"})`,
|
|
168
|
+
);
|
|
166
169
|
}
|
|
167
170
|
const message =
|
|
168
171
|
messages.length === 0 ? "No MCP Server Definitions configured" : messages.join("\n");
|
|
@@ -494,7 +497,7 @@ export async function runPiMcpCli(
|
|
|
494
497
|
}
|
|
495
498
|
|
|
496
499
|
const entrypoint = process.argv[1];
|
|
497
|
-
if (entrypoint !== undefined && import.meta.url ===
|
|
500
|
+
if (entrypoint !== undefined && fileURLToPath(import.meta.url) === realpathSync(entrypoint)) {
|
|
498
501
|
void runPiMcpCli(process.argv.slice(2)).then(
|
|
499
502
|
(exitCode) => {
|
|
500
503
|
process.exitCode = exitCode;
|