@fitchmultz/pi-mcp-adapter 6.0.0 → 7.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.
Files changed (111) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/OAUTH.md +1 -1
  3. package/README.md +27 -18
  4. package/dist/app-bridge.bundle.js +14 -14
  5. package/dist/config.js +41 -16
  6. package/dist/direct-tools.js +1 -1
  7. package/dist/gateway-arguments.d.ts +1 -1
  8. package/dist/index.js +38 -59
  9. package/dist/lifecycle.d.ts +2 -2
  10. package/dist/lifecycle.js +2 -2
  11. package/dist/mcp-auth-flow.js +2 -2
  12. package/dist/mcp-auth.d.ts +1 -1
  13. package/dist/mcp-auth.js +3 -3
  14. package/dist/mcp-code.d.ts +6 -6
  15. package/dist/mcp-code.js +11 -10
  16. package/dist/mcp-oauth-provider.d.ts +5 -5
  17. package/dist/mcp-oauth-provider.js +5 -5
  18. package/dist/mcp-output-guard.d.ts +1 -1
  19. package/dist/mcp-output-guard.js +1 -1
  20. package/dist/mcp-panel.d.ts +2 -2
  21. package/dist/mcp-panel.js +2 -2
  22. package/dist/mcp-setup-panel.d.ts +5 -4
  23. package/dist/mcp-setup-panel.js +15 -7
  24. package/dist/mcp-trace.d.ts +1 -1
  25. package/dist/mcp-trace.js +1 -1
  26. package/dist/npx-resolver.js +8 -4
  27. package/dist/proxy-modes.d.ts +2 -2
  28. package/dist/proxy-modes.js +20 -19
  29. package/dist/sampling-handler.js +1 -0
  30. package/dist/search-ranking.js +6 -2
  31. package/dist/server-manager.d.ts +2 -2
  32. package/dist/server-manager.js +44 -27
  33. package/dist/session-recovery.d.ts +7 -4
  34. package/dist/session-recovery.js +76 -10
  35. package/dist/tool-loader.d.ts +7 -2
  36. package/dist/tool-loader.js +56 -5
  37. package/dist/tool-metadata.d.ts +43 -43
  38. package/dist/tool-result-renderer.d.ts +3 -3
  39. package/dist/tool-result-renderer.js +3 -3
  40. package/dist/ui-resource-handler.d.ts +2 -2
  41. package/dist/ui-resource-handler.js +2 -2
  42. package/dist/ui-server.js +5 -4
  43. package/dist/ui-stream-types.d.ts +8 -8
  44. package/dist/unix-socket-transport.d.ts +1 -1
  45. package/dist/unix-socket-transport.js +1 -1
  46. package/dist/utils.d.ts +1 -0
  47. package/dist/utils.js +14 -0
  48. package/package.json +34 -98
  49. package/skills/mcp-scripting/SKILL.md +2 -0
  50. package/abort.ts +0 -34
  51. package/agent-dir.ts +0 -24
  52. package/app-bridge.bundle.js +0 -109
  53. package/banner.png +0 -0
  54. package/checkpoint.ts +0 -55
  55. package/commands.ts +0 -622
  56. package/config.ts +0 -1204
  57. package/consent-manager.ts +0 -25
  58. package/direct-tools.ts +0 -75
  59. package/elicitation-handler.ts +0 -349
  60. package/error-signal.ts +0 -21
  61. package/gateway-arguments.ts +0 -73
  62. package/glimpse-ui.ts +0 -80
  63. package/host-html-template.ts +0 -456
  64. package/index.ts +0 -767
  65. package/init.ts +0 -613
  66. package/legacy-migration.ts +0 -47
  67. package/lifecycle.ts +0 -165
  68. package/logger.ts +0 -127
  69. package/mcp-auth-flow.ts +0 -975
  70. package/mcp-auth.ts +0 -793
  71. package/mcp-callback-server.ts +0 -450
  72. package/mcp-code.ts +0 -412
  73. package/mcp-keyring-helper.cjs +0 -89
  74. package/mcp-oauth-provider.ts +0 -774
  75. package/mcp-output-guard.ts +0 -530
  76. package/mcp-panel.ts +0 -907
  77. package/mcp-script-worker.mjs +0 -131
  78. package/mcp-setup-panel.ts +0 -666
  79. package/mcp-status.ts +0 -110
  80. package/mcp-trace.ts +0 -295
  81. package/metadata-cache.ts +0 -271
  82. package/npx-resolver.ts +0 -488
  83. package/onboarding-state.ts +0 -58
  84. package/panel-keys.ts +0 -37
  85. package/prompts.ts +0 -337
  86. package/proxy-modes.ts +0 -1109
  87. package/resource-tools.ts +0 -52
  88. package/runtime-owner.ts +0 -123
  89. package/sampling-handler.ts +0 -276
  90. package/scripts/build-app-bridge.mjs +0 -15
  91. package/scripts/build.mjs +0 -132
  92. package/scripts/prepare.mjs +0 -103
  93. package/search-ranking.ts +0 -132
  94. package/server-manager.ts +0 -1320
  95. package/session-recovery.ts +0 -316
  96. package/state.ts +0 -64
  97. package/tool-approval.ts +0 -78
  98. package/tool-loader.ts +0 -153
  99. package/tool-metadata.ts +0 -240
  100. package/tool-registrar.ts +0 -74
  101. package/tool-result-renderer.ts +0 -294
  102. package/tsconfig.build.json +0 -13
  103. package/tsconfig.json +0 -16
  104. package/types.ts +0 -785
  105. package/ui-resource-handler.ts +0 -249
  106. package/ui-server.ts +0 -808
  107. package/ui-session.ts +0 -568
  108. package/ui-stream-types.ts +0 -89
  109. package/ui-tool-visibility.ts +0 -25
  110. package/unix-socket-transport.ts +0 -85
  111. package/utils.ts +0 -307
package/tool-metadata.ts DELETED
@@ -1,240 +0,0 @@
1
- import { getToolUiResourceUri } from "@modelcontextprotocol/ext-apps/app-bridge";
2
- import type { McpExtensionState } from "./state.ts";
3
- import type { ToolMetadata, McpTool, McpResource, ServerEntry, ToolPrefix } from "./types.ts";
4
- import { formatToolName, isServerDisabled, isToolAllowed, resolveToolPrefix } from "./types.ts";
5
- import { resourceNameToToolName } from "./resource-tools.ts";
6
- import { extractToolUiStreamMode } from "./utils.ts";
7
- import { extractUiToolVisibility, isUiToolVisibleToModel } from "./ui-tool-visibility.ts";
8
-
9
- export function buildToolMetadata(
10
- tools: McpTool[],
11
- resources: McpResource[],
12
- definition: ServerEntry,
13
- serverName: string,
14
- prefix: ToolPrefix
15
- ): { metadata: ToolMetadata[]; failedTools: string[] } {
16
- const metadata: ToolMetadata[] = [];
17
- const failedTools: string[] = [];
18
- if (isServerDisabled(definition)) return { metadata, failedTools };
19
- const effectivePrefix = resolveToolPrefix(definition, prefix);
20
-
21
- for (const tool of tools) {
22
- if (!tool?.name) {
23
- failedTools.push("(unnamed)");
24
- continue;
25
- }
26
- if (!isToolAllowed(tool.name, serverName, effectivePrefix, definition.includeTools, definition.excludeTools)) {
27
- continue;
28
- }
29
-
30
- const name = formatToolName(tool.name, serverName, effectivePrefix);
31
-
32
- const uiVisibility = extractUiToolVisibility(tool._meta);
33
- if (!isUiToolVisibleToModel(uiVisibility)) {
34
- continue;
35
- }
36
- let uiResourceUri: string | undefined;
37
- try {
38
- uiResourceUri = getToolUiResourceUri({ _meta: tool._meta });
39
- } catch {
40
- failedTools.push(tool.name);
41
- }
42
- const uiStreamMode = extractToolUiStreamMode(tool._meta);
43
- metadata.push({
44
- ...tool,
45
- name,
46
- originalName: tool.name,
47
- description: tool.description ?? "",
48
- ...(uiResourceUri !== undefined ? { uiResourceUri } : {}),
49
- ...(uiVisibility !== undefined ? { uiVisibility } : {}),
50
- ...(uiStreamMode !== undefined ? { uiStreamMode } : {}),
51
- });
52
- }
53
-
54
- if (definition.exposeResources !== false) {
55
- for (const resource of resources) {
56
- if (!resource?.name || !resource?.uri) continue;
57
- const baseName = `read_${resourceNameToToolName(resource.name)}`;
58
- if (!isToolAllowed(baseName, serverName, effectivePrefix, definition.includeTools, definition.excludeTools)) {
59
- continue;
60
- }
61
-
62
- const name = formatToolName(baseName, serverName, effectivePrefix);
63
-
64
- metadata.push({
65
- name,
66
- originalName: baseName,
67
- description: resource.description ?? `Read resource: ${resource.uri}`,
68
- resourceUri: resource.uri,
69
- resourceDescriptor: resource,
70
- });
71
- }
72
- }
73
-
74
- return { metadata, failedTools };
75
- }
76
-
77
- export function totalToolCount(state: McpExtensionState): number {
78
- let count = 0;
79
- for (const metadata of state.toolMetadata.values()) {
80
- count += metadata.filter(tool => tool.resourceUri === undefined).length;
81
- }
82
- return count;
83
- }
84
-
85
- export function toToolDescriptor(server: string, tool: ToolMetadata) {
86
- const { name: path, originalName: name, resourceUri: _resourceUri, resourceDescriptor: _resourceDescriptor, uiResourceUri: _uiResourceUri, uiVisibility: _uiVisibility, uiStreamMode: _uiStreamMode, ...descriptor } = tool;
87
- return { ...descriptor, server, path, name };
88
- }
89
-
90
- export function catalogCoverage(state: McpExtensionState, server?: string) {
91
- const servers = Object.keys(state.config.mcpServers).filter(name => (!server || name === server) && !isServerDisabled(state.config.mcpServers[name]));
92
- const knownServers = servers.filter(name => state.toolMetadata.has(name));
93
- const unknownServers = servers.filter(name => !state.toolMetadata.has(name));
94
- return { complete: unknownServers.length === 0, knownServers, unknownServers };
95
- }
96
-
97
- export function findToolByName(metadata: ToolMetadata[] | undefined, toolName: string): ToolMetadata | undefined {
98
- if (!metadata) return undefined;
99
- const exact = metadata.filter(m => m.name === toolName);
100
- if (exact.length > 0) return exact.length === 1 ? exact[0] : undefined;
101
- const normalized = toolName.replace(/-/g, "_");
102
- const matches = metadata.filter(m => m.name.replace(/-/g, "_") === normalized);
103
- return matches.length === 1 ? matches[0] : undefined;
104
- }
105
-
106
- export function formatSchema(schema: unknown, indent = " "): string {
107
- if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
108
- return `${indent}(no schema)`;
109
- }
110
-
111
- const s = schema as Record<string, unknown>;
112
-
113
- if (s.type === "object" && s.properties && typeof s.properties === "object" && !Array.isArray(s.properties)) {
114
- const props = s.properties as Record<string, unknown>;
115
- const required = Array.isArray(s.required) ? s.required.filter((name): name is string => typeof name === "string") : [];
116
-
117
- if (Object.keys(props).length === 0) {
118
- return `${indent}(no parameters)`;
119
- }
120
-
121
- const lines: string[] = [];
122
- for (const [name, propSchema] of Object.entries(props)) {
123
- lines.push(...formatProperty(name, propSchema, required.includes(name), indent));
124
- }
125
- return lines.join("\n");
126
- }
127
-
128
- const lines = formatNestedSchema(s, indent);
129
- if (lines.length > 0) {
130
- return lines.join("\n");
131
- }
132
-
133
- const typeStr = formatType(s);
134
- if (typeStr) {
135
- return `${indent}(${typeStr})`;
136
- }
137
-
138
- return `${indent}(complex schema)`;
139
- }
140
-
141
- function formatProperty(name: string, schema: unknown, required: boolean, indent: string): string[] {
142
- if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
143
- return [`${indent}${name}${required ? " *required*" : ""}`];
144
- }
145
-
146
- const s = schema as Record<string, unknown>;
147
- const parts = [`${indent}${name}`];
148
- const typeStr = formatType(s);
149
- if (typeStr) parts.push(`(${typeStr})`);
150
- if (required) parts.push("*required*");
151
- appendSchemaAnnotations(parts, s);
152
-
153
- return [parts.join(" "), ...formatNestedSchema(s, `${indent} `)];
154
- }
155
-
156
- function formatNestedSchema(schema: Record<string, unknown>, indent: string): string[] {
157
- const lines: string[] = [];
158
-
159
- if (Array.isArray(schema.anyOf)) {
160
- lines.push(...formatVariants("anyOf", schema.anyOf, indent));
161
- }
162
- if (Array.isArray(schema.oneOf)) {
163
- lines.push(...formatVariants("oneOf", schema.oneOf, indent));
164
- }
165
- if (schema.items !== undefined) {
166
- lines.push(...formatProperty("items", schema.items, false, indent));
167
- }
168
- if (schema.properties && typeof schema.properties === "object" && !Array.isArray(schema.properties)) {
169
- const required = Array.isArray(schema.required) ? schema.required.filter((name): name is string => typeof name === "string") : [];
170
- for (const [name, propSchema] of Object.entries(schema.properties as Record<string, unknown>)) {
171
- lines.push(...formatProperty(name, propSchema, required.includes(name), indent));
172
- }
173
- }
174
-
175
- return lines;
176
- }
177
-
178
- function formatVariants(keyword: "anyOf" | "oneOf", variants: unknown[], indent: string): string[] {
179
- const lines = [`${indent}${keyword}:`];
180
-
181
- for (const variant of variants) {
182
- if (!variant || typeof variant !== "object" || Array.isArray(variant)) {
183
- lines.push(`${indent} - ${JSON.stringify(variant)}`);
184
- continue;
185
- }
186
-
187
- const s = variant as Record<string, unknown>;
188
- const typeStr = formatType(s) || "schema";
189
- const parts = [`${indent} - ${typeStr}`];
190
- appendSchemaAnnotations(parts, s);
191
- lines.push(parts.join(" "));
192
- lines.push(...formatNestedSchema(s, `${indent} `));
193
- }
194
-
195
- return lines;
196
- }
197
-
198
- function formatType(schema: Record<string, unknown>): string {
199
- if (Object.hasOwn(schema, "const")) {
200
- return `const ${JSON.stringify(schema.const)}`;
201
- }
202
-
203
- if (Array.isArray(schema.enum)) {
204
- return `enum: ${schema.enum.map(v => JSON.stringify(v)).join(", ")}`;
205
- }
206
-
207
- if (Array.isArray(schema.type)) {
208
- return schema.type.map(type => String(type)).join(" | ");
209
- }
210
-
211
- if (schema.type) {
212
- return String(schema.type);
213
- }
214
-
215
- if (schema.properties && typeof schema.properties === "object" && !Array.isArray(schema.properties)) {
216
- return "object";
217
- }
218
-
219
- if (schema.items !== undefined) {
220
- return "array";
221
- }
222
-
223
- return "";
224
- }
225
-
226
- function appendSchemaAnnotations(parts: string[], schema: Record<string, unknown>): void {
227
- if (schema.description && typeof schema.description === "string") {
228
- parts.push(`- ${schema.description}`);
229
- }
230
-
231
- for (const key of ["minLength", "maxLength", "minimum", "maximum", "minItems", "maxItems", "format", "pattern"] as const) {
232
- if (schema[key] !== undefined) {
233
- parts.push(`[${key}: ${JSON.stringify(schema[key])}]`);
234
- }
235
- }
236
-
237
- if (schema.default !== undefined) {
238
- parts.push(`[default: ${JSON.stringify(schema.default)}]`);
239
- }
240
- }
package/tool-registrar.ts DELETED
@@ -1,74 +0,0 @@
1
- // MCP protocol content to model-visible content.
2
-
3
- import type { McpContent, ContentBlock } from "./types.ts";
4
- import { formatMcpPayloadFile, type McpPayloadFile } from "./mcp-output-guard.ts";
5
-
6
- /**
7
- * Transform MCP content types to Pi content blocks.
8
- */
9
- export function transformMcpContent(content: McpContent[]): ContentBlock[] {
10
- return content.map(c => {
11
- if (c.type === "text") {
12
- return { type: "text" as const, text: c.text ?? "" };
13
- }
14
- if (c.type === "image") {
15
- return {
16
- type: "image" as const,
17
- data: c.data ?? "",
18
- mimeType: c.mimeType ?? "image/png",
19
- };
20
- }
21
- if (c.type === "resource") {
22
- const resourceUri = c.resource?.uri ?? "(no URI)";
23
- const resourceContent = c.resource?.text ?? (c.resource?.blob !== undefined ? "[Binary content; not rendered]" : "(no content)");
24
- return {
25
- type: "text" as const,
26
- text: `[Resource: ${resourceUri}]\n${resourceContent}`,
27
- };
28
- }
29
- if (c.type === "resource_link") {
30
- const linkName = c.name ?? c.uri ?? "unknown";
31
- const linkUri = c.uri ?? "(no URI)";
32
- return {
33
- type: "text" as const,
34
- text: `[Resource Link: ${linkName}]\nURI: ${linkUri}`,
35
- };
36
- }
37
- if (c.type === "audio") {
38
- return {
39
- type: "text" as const,
40
- text: `[Audio content: ${c.mimeType ?? "audio/*"}; not rendered]`,
41
- };
42
- }
43
- return { type: "text" as const, text: JSON.stringify(c) };
44
- });
45
- }
46
-
47
- /** Human content and structured content are complementary protocol fields. */
48
- export function resolveMcpResultContent(result: Record<string, unknown>): ContentBlock[] {
49
- const blocks = transformMcpContent((Array.isArray(result.content) ? result.content : []) as McpContent[]);
50
- if (result.structuredContent !== undefined) {
51
- blocks.push({ type: "text", text: stringifyStructuredContent(result.structuredContent) });
52
- }
53
- return blocks;
54
- }
55
-
56
- /** Persist content the model cannot consume; never imply an audio/blob payload was rendered. */
57
- export function renderMcpResultContent(result: Record<string, unknown>, payloadFiles: McpPayloadFile[] = []): ContentBlock[] {
58
- const content: McpContent[] = Array.isArray(result.contents)
59
- ? result.contents.map(resource => typeof resource.text === "string" ? { type: "text", text: resource.text } : { type: "resource", resource })
60
- : Array.isArray(result.content) ? result.content : [];
61
- const rendered = content.map((block, index) => {
62
- const file = payloadFiles.find(file => file.index === index);
63
- return file ? { type: "text" as const, text: formatMcpPayloadFile(file) } : block;
64
- });
65
- return resolveMcpResultContent({ ...result, content: rendered });
66
- }
67
-
68
- function stringifyStructuredContent(value: unknown): string {
69
- try {
70
- return JSON.stringify(value, null, 2) ?? String(value);
71
- } catch {
72
- return String(value);
73
- }
74
- }
@@ -1,294 +0,0 @@
1
- import { keyHint, type AgentToolResult, type Theme, type ToolRenderResultOptions } from "@earendil-works/pi-coding-agent";
2
- import { type Component, Text } from "@earendil-works/pi-tui";
3
-
4
- type McpToolResultDetails = Record<string, unknown> & { error?: unknown };
5
- type McpToolContentBlock = AgentToolResult<McpToolResultDetails>["content"][number];
6
-
7
- type RenderTheme = Pick<Theme, "fg" | "bold">;
8
-
9
- const plainTheme: RenderTheme = {
10
- fg: (_name, text) => text,
11
- bold: (text) => text,
12
- };
13
-
14
- export interface McpProxyToolCallInput {
15
- tool?: string;
16
- args?: string | object;
17
- connect?: string;
18
- describe?: string;
19
- search?: string;
20
- query?: string;
21
- uri?: string;
22
- ref?: string;
23
- includeSchemas?: boolean;
24
- server?: string;
25
- action?: string;
26
- }
27
-
28
- interface McpToolRenderContext {
29
- isError: boolean;
30
- }
31
-
32
- export interface McpToolResultDisplay {
33
- lines: string[];
34
- truncated: boolean;
35
- }
36
-
37
- const DEFAULT_MAX_CALL_INPUT_CHARS = 1500;
38
- const DEFAULT_MAX_COLLAPSED_LINES = 3;
39
- const DEFAULT_MAX_COLLAPSED_CHARS = 8000;
40
- const COLLAPSED_RENDER_CHAR_SLACK = 8;
41
-
42
- class CollapsibleText implements Component {
43
- private readonly fullText: Text;
44
- private readonly footerText: Text;
45
- private collapsedText: { charBudget: number; fullyIncluded: boolean; text: Text } | null = null;
46
-
47
- constructor(
48
- private readonly text: string,
49
- private readonly expanded: boolean,
50
- private readonly maxCollapsedLines: number,
51
- private readonly ellipsis: string,
52
- private readonly expandHint: string,
53
- private readonly preTruncated = false,
54
- ) {
55
- this.fullText = new Text(text, 0, 0);
56
- this.footerText = new Text(`${ellipsis}\n${expandHint}`, 0, 0);
57
- }
58
-
59
- render(width: number): string[] {
60
- if (this.expanded) {
61
- return this.fullText.render(width);
62
- }
63
-
64
- const safeWidth = Math.max(1, Math.floor(width));
65
- const charBudget = safeWidth * (this.maxCollapsedLines + 1) * COLLAPSED_RENDER_CHAR_SLACK;
66
- if (!this.collapsedText || this.collapsedText.charBudget !== charBudget) {
67
- const prefix = this.text.length > charBudget
68
- ? this.text.slice(0, charBudget)
69
- : this.text;
70
- this.collapsedText = {
71
- charBudget,
72
- fullyIncluded: prefix === this.text,
73
- text: new Text(prefix, 0, 0),
74
- };
75
- }
76
-
77
- const lines = this.collapsedText.text.render(width);
78
- if (!this.preTruncated && this.collapsedText.fullyIncluded && lines.length <= this.maxCollapsedLines) return lines;
79
-
80
- return [
81
- ...lines.slice(0, this.maxCollapsedLines),
82
- ...this.footerText.render(width),
83
- ];
84
- }
85
-
86
- invalidate(): void {}
87
- }
88
-
89
- function truncateText(value: string, maxChars: number): string {
90
- if (value.length <= maxChars) return value;
91
- return `${value.slice(0, Math.max(0, maxChars - 1))}…`;
92
- }
93
-
94
- function formatJsonish(value: unknown, maxChars: number): string {
95
- if (typeof value === "string") {
96
- try {
97
- return truncateText(JSON.stringify(JSON.parse(value), null, 2), maxChars);
98
- } catch {
99
- return truncateText(value, maxChars);
100
- }
101
- }
102
-
103
- try {
104
- return truncateText(JSON.stringify(value, null, 2), maxChars);
105
- } catch {
106
- return truncateText(String(value), maxChars);
107
- }
108
- }
109
-
110
- function hasUsefulObjectContent(value: unknown): boolean {
111
- return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length > 0;
112
- }
113
-
114
- export function formatMcpProxyToolCallLines(
115
- args: McpProxyToolCallInput,
116
- maxInputChars = DEFAULT_MAX_CALL_INPUT_CHARS,
117
- ): string[] {
118
- if (args.action === "ui-messages" || args.action === "status") return [`mcp ${args.action}`];
119
- if (args.action) {
120
- const target = args.tool ?? args.query ?? args.uri ?? args.ref;
121
- let line = `mcp ${args.action}${target ? ` ${target}` : ""}${args.server ? `${target ? " @" : ""} ${args.server}` : ""}`;
122
- if (args.action === "search" && args.includeSchemas === false) line += " (schemas hidden)";
123
- return args.action === "call" && args.args ? [line, formatJsonish(args.args, maxInputChars)] : [line];
124
- }
125
-
126
- if (args.tool) {
127
- const target = args.server ? `${args.tool} @ ${args.server}` : args.tool;
128
- const lines = [`mcp call ${target}`];
129
- if (args.args) lines.push(formatJsonish(args.args, maxInputChars));
130
- return lines;
131
- }
132
-
133
- if (args.connect) return [`mcp connect ${args.connect}`];
134
- if (args.describe) return [`mcp describe ${args.describe}`];
135
-
136
- if (args.search) {
137
- let line = `mcp search ${args.search}`;
138
- if (args.server) line += ` @ ${args.server}`;
139
- if (args.includeSchemas === false) line += " (schemas hidden)";
140
- return [line];
141
- }
142
-
143
- if (args.server) return [`mcp list ${args.server}`];
144
-
145
- return ["mcp status"];
146
- }
147
-
148
- export function formatMcpDirectToolCallLines(
149
- displayName: string,
150
- args: Record<string, unknown>,
151
- maxInputChars = DEFAULT_MAX_CALL_INPUT_CHARS,
152
- ): string[] {
153
- if (!hasUsefulObjectContent(args)) return [displayName];
154
- return [displayName, formatJsonish(args, maxInputChars)];
155
- }
156
-
157
- function renderToolCallLines(lines: string[], theme?: RenderTheme) {
158
- const activeTheme = theme ?? plainTheme;
159
- const [title = "mcp", ...rest] = lines;
160
- const styledTitle = activeTheme.fg("toolTitle", activeTheme.bold(title));
161
- const styledRest = rest.map(line => activeTheme.fg("muted", line));
162
- return new Text([styledTitle, ...styledRest].join("\n"), 0, 0);
163
- }
164
-
165
- export function renderMcpProxyToolCall(args: McpProxyToolCallInput, theme?: RenderTheme) {
166
- return renderToolCallLines(formatMcpProxyToolCallLines(args), theme);
167
- }
168
-
169
- export function createMcpDirectToolCallRenderer(displayName: string) {
170
- return (args: Record<string, unknown>, theme?: RenderTheme) => {
171
- return renderToolCallLines(formatMcpDirectToolCallLines(displayName, args), theme);
172
- };
173
- }
174
-
175
- function blockToLines(block: McpToolContentBlock): string[] {
176
- if (block.type === "text") {
177
- return block.text.split("\n");
178
- }
179
- return [`[image: ${block.mimeType}]`];
180
- }
181
-
182
- function collectCollapsedResultLines(
183
- content: AgentToolResult<McpToolResultDetails>["content"],
184
- maxLines: number,
185
- maxChars: number,
186
- ): McpToolResultDisplay {
187
- if (content.length === 0) return { lines: ["(empty result)"], truncated: false };
188
-
189
- const lines: string[] = [];
190
- let remainingChars = maxChars;
191
- let truncated = false;
192
-
193
- const appendLine = (line: string) => {
194
- if (lines.length >= maxLines || remainingChars <= 0) {
195
- truncated = true;
196
- return false;
197
- }
198
-
199
- if (line.length > remainingChars) {
200
- lines.push(line.slice(0, remainingChars));
201
- truncated = true;
202
- remainingChars = 0;
203
- return false;
204
- }
205
-
206
- lines.push(line);
207
- remainingChars -= line.length + 1;
208
- return true;
209
- };
210
-
211
- for (const block of content) {
212
- if (block.type !== "text") {
213
- if (!appendLine(`[image: ${block.mimeType}]`)) break;
214
- continue;
215
- }
216
-
217
- let start = 0;
218
- while (start <= block.text.length) {
219
- const newline = block.text.indexOf("\n", start);
220
- const line = newline === -1 ? block.text.slice(start) : block.text.slice(start, newline);
221
- if (!appendLine(line)) break;
222
- if (newline === -1) break;
223
- start = newline + 1;
224
- }
225
-
226
- if (truncated) break;
227
- }
228
-
229
- if (lines.length === 0) lines.push("");
230
- if (truncated && lines.length >= maxLines) lines.push("…");
231
- return { lines, truncated };
232
- }
233
-
234
- export function formatMcpToolResultIdentity(details: McpToolResultDetails | undefined): string | null {
235
- if (details?.mode !== "call") return null;
236
- const server = typeof details.server === "string"
237
- ? details.server
238
- : typeof details.hintServer === "string"
239
- ? details.hintServer
240
- : null;
241
- if (!server) return null;
242
- if (typeof details.tool === "string") return `MCP ${server}/${details.tool}`;
243
- if (typeof details.resourceUri === "string") return `MCP ${server} resource ${details.resourceUri}`;
244
- if (typeof details.requestedTool === "string") return `MCP ${server}/${details.requestedTool}`;
245
- return null;
246
- }
247
-
248
- export function formatMcpToolResultLines(
249
- result: Pick<AgentToolResult<McpToolResultDetails>, "content">,
250
- expanded: boolean,
251
- maxCollapsedLines = 3,
252
- maxCollapsedChars = DEFAULT_MAX_COLLAPSED_CHARS,
253
- ): McpToolResultDisplay {
254
- if (!expanded) {
255
- return collectCollapsedResultLines(result.content, maxCollapsedLines, maxCollapsedChars);
256
- }
257
-
258
- const allLines = result.content.flatMap(blockToLines);
259
- const lines = allLines.length > 0 ? allLines : ["(empty result)"];
260
- return { lines, truncated: false };
261
- }
262
-
263
- export function renderMcpToolResult(
264
- result: AgentToolResult<unknown>,
265
- options: ToolRenderResultOptions,
266
- theme?: RenderTheme,
267
- context?: McpToolRenderContext,
268
- ) {
269
- const activeTheme = theme ?? plainTheme;
270
- if (options.isPartial) {
271
- return new Text(activeTheme.fg("warning", "Running MCP tool..."), 0, 0);
272
- }
273
-
274
- const details = typeof result.details === "object" && result.details !== null
275
- ? result.details as McpToolResultDetails
276
- : {};
277
- const hasErrorDetails = Boolean(details.error);
278
- const expanded = options.expanded || context?.isError === true || hasErrorDetails;
279
- const display = formatMcpToolResultLines(result, expanded);
280
- const identity = formatMcpToolResultIdentity(details);
281
- const output = [
282
- ...(identity ? [activeTheme.fg("muted", identity)] : []),
283
- ...display.lines.map((line) => activeTheme.fg("toolOutput", line)),
284
- ].join("\n");
285
-
286
- return new CollapsibleText(
287
- output,
288
- expanded,
289
- DEFAULT_MAX_COLLAPSED_LINES + (identity ? 1 : 0),
290
- activeTheme.fg("muted", "…"),
291
- activeTheme.fg("muted", `(${keyHint("app.tools.expand", "to expand")})`),
292
- display.truncated,
293
- );
294
- }
@@ -1,13 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "noEmit": false,
5
- "declaration": true,
6
- "rewriteRelativeImportExtensions": true,
7
- "outDir": "dist",
8
- "rootDir": ".",
9
- "sourceMap": false
10
- },
11
- "include": ["*.ts"],
12
- "exclude": ["*.test.ts", "vitest.config.ts", "dist", "node_modules"]
13
- }
package/tsconfig.json DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "allowImportingTsExtensions": true,
7
- "esModuleInterop": true,
8
- "strict": true,
9
- "exactOptionalPropertyTypes": true,
10
- "noUncheckedIndexedAccess": true,
11
- "skipLibCheck": true,
12
- "noEmit": true,
13
- "resolveJsonModule": true
14
- },
15
- "include": ["*.ts"]
16
- }