pi-mcp-adapter 2.6.0 → 2.7.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/CHANGELOG.md +17 -0
- package/README.md +1 -0
- package/commands.ts +22 -1
- package/index.ts +14 -2
- package/mcp-auth.ts +13 -9
- package/oauth-handler.ts +4 -7
- package/package.json +2 -1
- package/proxy-modes.ts +31 -0
- package/tool-result-renderer.ts +96 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.7.0] - 2026-05-22
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Added TUI call rendering for MCP proxy and direct tool inputs. Thanks @dmmulroy for PR #102.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Hardened OAuth credential storage paths against server-name path traversal without rejecting valid configured server names.
|
|
17
|
+
- Rejected unsafe regex-mode MCP search patterns before executing them.
|
|
18
|
+
|
|
19
|
+
## [2.6.1] - 2026-05-13
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- Added `/mcp logout <server>` to clear stored OAuth credentials and disconnect the server. Thanks @mattzcarey for PR #96.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- Cancel pending OAuth callbacks when logging out of an MCP server.
|
|
26
|
+
|
|
10
27
|
## [2.6.0] - 2026-05-10
|
|
11
28
|
|
|
12
29
|
### Added
|
package/README.md
CHANGED
|
@@ -347,6 +347,7 @@ Tool names are fuzzy-matched on hyphens and underscores — `context7_resolve_li
|
|
|
347
347
|
| `/mcp tools` | List all tools |
|
|
348
348
|
| `/mcp reconnect` | Reconnect all servers |
|
|
349
349
|
| `/mcp reconnect <server>` | Connect or reconnect a single server |
|
|
350
|
+
| `/mcp logout <server>` | Clear stored OAuth credentials for a server and disconnect it |
|
|
350
351
|
| `/mcp-auth` | Open an OAuth server picker in interactive UI sessions |
|
|
351
352
|
| `/mcp-auth <server>` | OAuth setup for a specific server |
|
|
352
353
|
|
package/commands.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import { lazyConnect, updateMetadataCache, updateStatusBar, getFailureAgeSeconds } from "./init.ts";
|
|
16
16
|
import { loadMetadataCache } from "./metadata-cache.ts";
|
|
17
17
|
import { buildToolMetadata } from "./tool-metadata.ts";
|
|
18
|
-
import { supportsOAuth, authenticate } from "./mcp-auth-flow.ts";
|
|
18
|
+
import { supportsOAuth, authenticate, removeAuth } from "./mcp-auth-flow.ts";
|
|
19
19
|
import { getAuthForUrl } from "./mcp-auth.ts";
|
|
20
20
|
import { loadOnboardingState, markSetupCompleted as persistSetupCompleted, markSharedConfigHintShown } from "./onboarding-state.ts";
|
|
21
21
|
import { openPath } from "./utils.ts";
|
|
@@ -192,6 +192,27 @@ export async function authenticateServer(
|
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
export async function logoutServer(
|
|
196
|
+
serverName: string,
|
|
197
|
+
state: McpExtensionState,
|
|
198
|
+
ctx: ExtensionContext
|
|
199
|
+
): Promise<{ ok: boolean; message: string }> {
|
|
200
|
+
const definition = state.config.mcpServers[serverName];
|
|
201
|
+
if (!definition) {
|
|
202
|
+
const message = `Server "${serverName}" not found in config`;
|
|
203
|
+
if (ctx.hasUI) ctx.ui.notify(message, "error");
|
|
204
|
+
return { ok: false, message };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
await removeAuth(serverName);
|
|
208
|
+
await state.manager.close(serverName);
|
|
209
|
+
updateStatusBar(state);
|
|
210
|
+
|
|
211
|
+
const message = `OAuth credentials cleared for "${serverName}". Run /mcp-auth ${serverName} to authenticate again.`;
|
|
212
|
+
if (ctx.hasUI) ctx.ui.notify(message, "info");
|
|
213
|
+
return { ok: true, message };
|
|
214
|
+
}
|
|
215
|
+
|
|
195
216
|
export interface PanelFlowResult {
|
|
196
217
|
configChanged: boolean;
|
|
197
218
|
}
|
package/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI, ToolInfo } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { McpExtensionState } from "./state.ts";
|
|
3
3
|
import { Type } from "typebox";
|
|
4
|
-
import { showStatus, showTools, reconnectServers, authenticateServer, openMcpAuthPanel, openMcpPanel, openMcpSetup } from "./commands.ts";
|
|
4
|
+
import { showStatus, showTools, reconnectServers, authenticateServer, logoutServer, openMcpAuthPanel, openMcpPanel, openMcpSetup } from "./commands.ts";
|
|
5
5
|
import { loadMcpConfig } from "./config.ts";
|
|
6
6
|
import { buildProxyDescription, createDirectToolExecutor, getMissingConfiguredDirectToolServers, resolveDirectTools } from "./direct-tools.ts";
|
|
7
7
|
import { flushMetadataCache, initializeMcp, updateStatusBar } from "./init.ts";
|
|
@@ -9,7 +9,7 @@ import { loadMetadataCache } from "./metadata-cache.ts";
|
|
|
9
9
|
import { executeCall, executeConnect, executeDescribe, executeList, executeSearch, executeStatus, executeUiMessages } from "./proxy-modes.ts";
|
|
10
10
|
import { getConfigPathFromArgv, truncateAtWord } from "./utils.ts";
|
|
11
11
|
import { initializeOAuth, shutdownOAuth } from "./mcp-auth-flow.ts";
|
|
12
|
-
import { renderMcpToolResult } from "./tool-result-renderer.ts";
|
|
12
|
+
import { createMcpDirectToolCallRenderer, renderMcpProxyToolCall, renderMcpToolResult } from "./tool-result-renderer.ts";
|
|
13
13
|
|
|
14
14
|
export default function mcpAdapter(pi: ExtensionAPI) {
|
|
15
15
|
let state: McpExtensionState | null = null;
|
|
@@ -74,6 +74,7 @@ export default function mcpAdapter(pi: ExtensionAPI) {
|
|
|
74
74
|
promptSnippet: truncateAtWord(spec.description, 100) || `MCP tool from ${spec.serverName}`,
|
|
75
75
|
parameters: Type.Unsafe((spec.inputSchema || { type: "object", properties: {} }) as never),
|
|
76
76
|
execute: createDirectToolExecutor(() => state, () => initPromise, spec),
|
|
77
|
+
renderCall: createMcpDirectToolCallRenderer(spec.prefixedName),
|
|
77
78
|
renderResult: renderMcpToolResult,
|
|
78
79
|
});
|
|
79
80
|
}
|
|
@@ -172,6 +173,7 @@ export default function mcpAdapter(pi: ExtensionAPI) {
|
|
|
172
173
|
const parts = args?.trim()?.split(/\s+/) ?? [];
|
|
173
174
|
const subcommand = parts[0] ?? "";
|
|
174
175
|
const targetServer = parts[1];
|
|
176
|
+
const rest = parts.slice(1).join(" ");
|
|
175
177
|
|
|
176
178
|
switch (subcommand) {
|
|
177
179
|
case "reconnect":
|
|
@@ -188,6 +190,15 @@ export default function mcpAdapter(pi: ExtensionAPI) {
|
|
|
188
190
|
}
|
|
189
191
|
break;
|
|
190
192
|
}
|
|
193
|
+
case "logout": {
|
|
194
|
+
const serverName = rest;
|
|
195
|
+
if (!serverName) {
|
|
196
|
+
if (ctx.hasUI) ctx.ui.notify("Usage: /mcp logout <server>", "error");
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
await logoutServer(serverName, state, ctx);
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
191
202
|
case "status":
|
|
192
203
|
case "":
|
|
193
204
|
default:
|
|
@@ -242,6 +253,7 @@ export default function mcpAdapter(pi: ExtensionAPI) {
|
|
|
242
253
|
label: "MCP",
|
|
243
254
|
description: buildProxyDescription(earlyConfig, earlyCache, directSpecs),
|
|
244
255
|
promptSnippet: "MCP gateway - connect to MCP servers and call their tools",
|
|
256
|
+
renderCall: renderMcpProxyToolCall,
|
|
245
257
|
parameters: Type.Object({
|
|
246
258
|
tool: Type.Optional(Type.String({ description: "Tool name to call (e.g., 'xcodebuild_list_sims')" })),
|
|
247
259
|
args: Type.Optional(Type.String({ description: "Arguments as JSON string (e.g., '{\"key\": \"value\"}')" })),
|
package/mcp-auth.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* MCP Auth Storage Module
|
|
3
3
|
*
|
|
4
4
|
* Handles secure storage of OAuth credentials, tokens, client information,
|
|
5
|
-
* and PKCE state for MCP servers.
|
|
6
|
-
* per-server directory structure.
|
|
5
|
+
* and PKCE state for MCP servers.
|
|
7
6
|
*
|
|
8
|
-
* Token storage location: $MCP_OAUTH_DIR
|
|
9
|
-
* otherwise <Pi agent dir>/mcp-oauth
|
|
7
|
+
* Token storage location: $MCP_OAUTH_DIR/sha256-<server-hash>/tokens.json when set,
|
|
8
|
+
* otherwise <Pi agent dir>/mcp-oauth/sha256-<server-hash>/tokens.json
|
|
10
9
|
*/
|
|
11
10
|
|
|
11
|
+
import { createHash } from 'crypto';
|
|
12
12
|
import { mkdirSync, readFileSync, writeFileSync, existsSync, rmSync } from 'fs';
|
|
13
13
|
import { join } from 'path';
|
|
14
14
|
import { getAgentPath } from './agent-dir.ts';
|
|
@@ -48,13 +48,17 @@ function getAuthBaseDir(): string {
|
|
|
48
48
|
* Get the server-specific directory path.
|
|
49
49
|
*/
|
|
50
50
|
function getServerDir(serverName: string): string {
|
|
51
|
-
|
|
51
|
+
if (typeof serverName !== 'string') {
|
|
52
|
+
throw new Error(`Invalid MCP server name: ${JSON.stringify(serverName)}`);
|
|
53
|
+
}
|
|
54
|
+
const storageKey = createHash('sha256').update(serverName, 'utf8').digest('hex');
|
|
55
|
+
return join(getAuthBaseDir(), `sha256-${storageKey}`);
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
/**
|
|
55
59
|
* Get the tokens file path for a server.
|
|
56
60
|
*/
|
|
57
|
-
function
|
|
61
|
+
export function getAuthEntryFilePath(serverName: string): string {
|
|
58
62
|
return join(getServerDir(serverName), 'tokens.json');
|
|
59
63
|
}
|
|
60
64
|
|
|
@@ -73,8 +77,8 @@ function ensureServerDir(serverName: string): void {
|
|
|
73
77
|
* Returns undefined if file doesn't exist.
|
|
74
78
|
*/
|
|
75
79
|
function readAuthEntry(serverName: string): AuthEntry | undefined {
|
|
80
|
+
const filePath = getAuthEntryFilePath(serverName);
|
|
76
81
|
try {
|
|
77
|
-
const filePath = getTokensFilePath(serverName);
|
|
78
82
|
if (!existsSync(filePath)) {
|
|
79
83
|
return undefined;
|
|
80
84
|
}
|
|
@@ -91,7 +95,7 @@ function readAuthEntry(serverName: string): AuthEntry | undefined {
|
|
|
91
95
|
*/
|
|
92
96
|
function writeAuthEntry(serverName: string, entry: AuthEntry): void {
|
|
93
97
|
ensureServerDir(serverName);
|
|
94
|
-
const filePath =
|
|
98
|
+
const filePath = getAuthEntryFilePath(serverName);
|
|
95
99
|
writeFileSync(filePath, JSON.stringify(entry, null, 2), { mode: 0o600 });
|
|
96
100
|
}
|
|
97
101
|
|
|
@@ -136,7 +140,7 @@ export function saveAuthEntry(serverName: string, entry: AuthEntry, serverUrl?:
|
|
|
136
140
|
*/
|
|
137
141
|
export function removeAuthEntry(serverName: string): void {
|
|
138
142
|
try {
|
|
139
|
-
const filePath =
|
|
143
|
+
const filePath = getAuthEntryFilePath(serverName);
|
|
140
144
|
if (existsSync(filePath)) {
|
|
141
145
|
writeFileSync(filePath, '{}', { mode: 0o600 });
|
|
142
146
|
}
|
package/oauth-handler.ts
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
// oauth-handler.ts - OAuth token management for MCP servers
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { getAgentPath } from "./agent-dir.ts";
|
|
5
3
|
import type { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
4
|
+
import { getAuthEntryFilePath } from "./mcp-auth.ts";
|
|
6
5
|
|
|
7
6
|
// Token storage path for a server
|
|
8
7
|
function getTokensPath(serverName: string): string {
|
|
9
|
-
|
|
10
|
-
const authDir = override ? override : getAgentPath("mcp-oauth");
|
|
11
|
-
return join(authDir, serverName, "tokens.json");
|
|
8
|
+
return getAuthEntryFilePath(serverName);
|
|
12
9
|
}
|
|
13
10
|
|
|
14
11
|
/**
|
|
15
12
|
* Get stored OAuth tokens for a server (if any).
|
|
16
13
|
* Returns undefined if no tokens or tokens are expired.
|
|
17
14
|
*
|
|
18
|
-
* Token file location: $MCP_OAUTH_DIR
|
|
19
|
-
* otherwise <Pi agent dir>/mcp-oauth
|
|
15
|
+
* Token file location: $MCP_OAUTH_DIR/sha256-<server-hash>/tokens.json when set,
|
|
16
|
+
* otherwise <Pi agent dir>/mcp-oauth/sha256-<server-hash>/tokens.json
|
|
20
17
|
*
|
|
21
18
|
* Expected format:
|
|
22
19
|
* {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mcp-adapter",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "MCP (Model Context Protocol) adapter extension for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
"@modelcontextprotocol/ext-apps": "^1.2.2",
|
|
86
86
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
87
87
|
"open": "^10.2.0",
|
|
88
|
+
"recheck": "^4.5.0",
|
|
88
89
|
"typebox": "^1.1.24",
|
|
89
90
|
"zod": "^3.25.0 || ^4.0.0"
|
|
90
91
|
},
|
package/proxy-modes.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentToolResult, ToolInfo } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { checkSync } from "recheck";
|
|
2
3
|
import type { McpExtensionState } from "./state.ts";
|
|
3
4
|
import type { ToolMetadata, McpContent } from "./types.ts";
|
|
4
5
|
import { getServerPrefix, parseUiPromptHandoff } from "./types.ts";
|
|
@@ -11,6 +12,13 @@ import { authenticate, supportsOAuth } from "./mcp-auth-flow.ts";
|
|
|
11
12
|
|
|
12
13
|
type ProxyToolResult = AgentToolResult<Record<string, unknown>>;
|
|
13
14
|
|
|
15
|
+
const MAX_REGEX_SEARCH_QUERY_LENGTH = 256;
|
|
16
|
+
const REGEX_SAFETY_CHECK_PARAMS = {
|
|
17
|
+
attackTimeout: 50,
|
|
18
|
+
incubationTimeout: 50,
|
|
19
|
+
timeout: 250,
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
14
22
|
type AutoAuthResult =
|
|
15
23
|
| { status: "skipped" }
|
|
16
24
|
| { status: "success" }
|
|
@@ -258,7 +266,30 @@ export function executeSearch(
|
|
|
258
266
|
let pattern: RegExp;
|
|
259
267
|
try {
|
|
260
268
|
if (regex) {
|
|
269
|
+
if (query.length > MAX_REGEX_SEARCH_QUERY_LENGTH) {
|
|
270
|
+
return {
|
|
271
|
+
content: [{ type: "text" as const, text: `Regex query is too long; maximum length is ${MAX_REGEX_SEARCH_QUERY_LENGTH} characters.` }],
|
|
272
|
+
details: { mode: "search", error: "query_too_long", query, maxLength: MAX_REGEX_SEARCH_QUERY_LENGTH },
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
261
276
|
pattern = new RegExp(query, "i");
|
|
277
|
+
let safety;
|
|
278
|
+
try {
|
|
279
|
+
safety = checkSync(query, "i", REGEX_SAFETY_CHECK_PARAMS);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
282
|
+
return {
|
|
283
|
+
content: [{ type: "text" as const, text: "Regex query rejected because safety analysis failed." }],
|
|
284
|
+
details: { mode: "search", error: "unsafe_pattern", query, reason: message },
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
if (safety.status !== "safe") {
|
|
288
|
+
return {
|
|
289
|
+
content: [{ type: "text" as const, text: `Regex query rejected as unsafe (${safety.status}).` }],
|
|
290
|
+
details: { mode: "search", error: "unsafe_pattern", query, safetyStatus: safety.status },
|
|
291
|
+
};
|
|
292
|
+
}
|
|
262
293
|
} else {
|
|
263
294
|
const terms = query.trim().split(/\s+/).filter(t => t.length > 0);
|
|
264
295
|
if (terms.length === 0) {
|
package/tool-result-renderer.ts
CHANGED
|
@@ -6,6 +6,19 @@ type McpToolContentBlock = AgentToolResult<McpToolResultDetails>["content"][numb
|
|
|
6
6
|
|
|
7
7
|
interface RenderTheme {
|
|
8
8
|
fg: (name: string, text: string) => string;
|
|
9
|
+
bold?: (text: string) => string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface McpProxyToolCallInput {
|
|
13
|
+
tool?: string;
|
|
14
|
+
args?: string;
|
|
15
|
+
connect?: string;
|
|
16
|
+
describe?: string;
|
|
17
|
+
search?: string;
|
|
18
|
+
regex?: boolean;
|
|
19
|
+
includeSchemas?: boolean;
|
|
20
|
+
server?: string;
|
|
21
|
+
action?: string;
|
|
9
22
|
}
|
|
10
23
|
|
|
11
24
|
interface McpToolRenderContext {
|
|
@@ -17,6 +30,89 @@ export interface McpToolResultDisplay {
|
|
|
17
30
|
truncated: boolean;
|
|
18
31
|
}
|
|
19
32
|
|
|
33
|
+
const DEFAULT_MAX_CALL_INPUT_CHARS = 1500;
|
|
34
|
+
|
|
35
|
+
function truncateText(value: string, maxChars: number): string {
|
|
36
|
+
if (value.length <= maxChars) return value;
|
|
37
|
+
return `${value.slice(0, Math.max(0, maxChars - 1))}…`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function formatJsonish(value: unknown, maxChars: number): string {
|
|
41
|
+
if (typeof value === "string") {
|
|
42
|
+
try {
|
|
43
|
+
return truncateText(JSON.stringify(JSON.parse(value), null, 2), maxChars);
|
|
44
|
+
} catch {
|
|
45
|
+
return truncateText(value, maxChars);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
return truncateText(JSON.stringify(value, null, 2), maxChars);
|
|
51
|
+
} catch {
|
|
52
|
+
return truncateText(String(value), maxChars);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function hasUsefulObjectContent(value: unknown): boolean {
|
|
57
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length > 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function formatMcpProxyToolCallLines(
|
|
61
|
+
args: McpProxyToolCallInput,
|
|
62
|
+
maxInputChars = DEFAULT_MAX_CALL_INPUT_CHARS,
|
|
63
|
+
): string[] {
|
|
64
|
+
if (args.action === "ui-messages") return [`mcp ${args.action}`];
|
|
65
|
+
|
|
66
|
+
if (args.tool) {
|
|
67
|
+
const target = args.server ? `${args.tool} @ ${args.server}` : args.tool;
|
|
68
|
+
const lines = [`mcp call ${target}`];
|
|
69
|
+
if (args.args) lines.push(formatJsonish(args.args, maxInputChars));
|
|
70
|
+
return lines;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (args.connect) return [`mcp connect ${args.connect}`];
|
|
74
|
+
if (args.describe) return [`mcp describe ${args.describe}`];
|
|
75
|
+
|
|
76
|
+
if (args.search) {
|
|
77
|
+
let line = `mcp search ${args.search}`;
|
|
78
|
+
if (args.server) line += ` @ ${args.server}`;
|
|
79
|
+
if (args.regex === true) line += " (regex)";
|
|
80
|
+
if (args.includeSchemas === false) line += " (schemas hidden)";
|
|
81
|
+
return [line];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (args.server) return [`mcp list ${args.server}`];
|
|
85
|
+
if (args.action) return [`mcp ${args.action}`];
|
|
86
|
+
|
|
87
|
+
return ["mcp status"];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function formatMcpDirectToolCallLines(
|
|
91
|
+
displayName: string,
|
|
92
|
+
args: Record<string, unknown>,
|
|
93
|
+
maxInputChars = DEFAULT_MAX_CALL_INPUT_CHARS,
|
|
94
|
+
): string[] {
|
|
95
|
+
if (!hasUsefulObjectContent(args)) return [displayName];
|
|
96
|
+
return [displayName, formatJsonish(args, maxInputChars)];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function renderToolCallLines(lines: string[], theme: RenderTheme) {
|
|
100
|
+
const [title = "mcp", ...rest] = lines;
|
|
101
|
+
const styledTitle = theme.fg("toolTitle", theme.bold ? theme.bold(title) : title);
|
|
102
|
+
const styledRest = rest.map(line => theme.fg("muted", line));
|
|
103
|
+
return new Text([styledTitle, ...styledRest].join("\n"), 0, 0);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function renderMcpProxyToolCall(args: McpProxyToolCallInput, theme: RenderTheme) {
|
|
107
|
+
return renderToolCallLines(formatMcpProxyToolCallLines(args), theme);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function createMcpDirectToolCallRenderer(displayName: string) {
|
|
111
|
+
return (args: Record<string, unknown>, theme: RenderTheme) => {
|
|
112
|
+
return renderToolCallLines(formatMcpDirectToolCallLines(displayName, args), theme);
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
20
116
|
function blockToLines(block: McpToolContentBlock): string[] {
|
|
21
117
|
if (block.type === "text") {
|
|
22
118
|
return block.text.split("\n");
|