@ian-pascoe/pi-mcp 0.1.1 → 0.3.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.
@@ -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 { Type, type TSchema } from "typebox";
16
- import { Value } from "typebox/value";
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
- owner: MCP_DETAILS_OWNER,
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
@@ -152,7 +152,7 @@ async function listStandaloneServers(state: StandaloneMcpState): Promise<McpComm
152
152
  transport: definition.transport,
153
153
  });
154
154
  messages.push(
155
- `${definition.id} (${definition.provenance}, ${definition.enabled ? "enabled" : "disabled"})`,
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"})`,
156
156
  );
157
157
  }
158
158
  for (const mask of settings.masks.values()) {
@@ -163,7 +163,9 @@ async function listStandaloneServers(state: StandaloneMcpState): Promise<McpComm
163
163
  name: mask.id,
164
164
  provenance: mask.provenance,
165
165
  });
166
- messages.push(`${mask.id} (${mask.provenance}, disabled mask)`);
166
+ messages.push(
167
+ `${mask.id} (provenance=${mask.provenance}, masked, inherited=${mask.inherited ? "yes" : "no"})`,
168
+ );
167
169
  }
168
170
  const message =
169
171
  messages.length === 0 ? "No MCP Server Definitions configured" : messages.join("\n");