claude-code-rust 0.12.0 → 0.12.2
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 +10 -6
- package/agent-sdk/README.md +1 -1
- package/agent-sdk/dist/bridge/account_metadata.js +44 -0
- package/agent-sdk/dist/bridge/available_commands.js +129 -0
- package/agent-sdk/dist/bridge/commands.js +58 -36
- package/agent-sdk/dist/bridge/error_classification.js +20 -7
- package/agent-sdk/dist/bridge/events.js +18 -0
- package/agent-sdk/dist/bridge/history.js +183 -11
- package/agent-sdk/dist/bridge/logger.js +3 -0
- package/agent-sdk/dist/bridge/mcp.js +49 -79
- package/agent-sdk/dist/bridge/mcp_metadata.js +369 -0
- package/agent-sdk/dist/bridge/message_handlers.js +401 -57
- package/agent-sdk/dist/bridge/model_metadata.js +228 -0
- package/agent-sdk/dist/bridge/session_lifecycle.js +197 -326
- package/agent-sdk/dist/bridge/state_parsing.js +7 -1
- package/agent-sdk/dist/bridge/task_links.js +34 -0
- package/agent-sdk/dist/bridge/tasks.js +862 -0
- package/agent-sdk/dist/bridge/tool_calls.js +88 -31
- package/agent-sdk/dist/bridge/tooling.js +1262 -32
- package/agent-sdk/dist/bridge.js +95 -43
- package/agent-sdk/dist/bridge.test.js +3680 -269
- package/package.json +2 -2
|
@@ -130,6 +130,7 @@ function eventToolCallId(event) {
|
|
|
130
130
|
case "elicitation_complete":
|
|
131
131
|
case "mcp_auth_redirect":
|
|
132
132
|
case "mcp_operation_error":
|
|
133
|
+
case "mcp_set_servers_result":
|
|
133
134
|
case "turn_complete":
|
|
134
135
|
case "turn_error":
|
|
135
136
|
case "slash_error":
|
|
@@ -173,6 +174,7 @@ function protocolEventLevel(event) {
|
|
|
173
174
|
case "session_update":
|
|
174
175
|
case "permission_request":
|
|
175
176
|
case "question_request":
|
|
177
|
+
case "user_dialog_request":
|
|
176
178
|
case "elicitation_request":
|
|
177
179
|
case "elicitation_complete":
|
|
178
180
|
case "mcp_auth_redirect":
|
|
@@ -182,6 +184,7 @@ function protocolEventLevel(event) {
|
|
|
182
184
|
case "status_snapshot":
|
|
183
185
|
case "context_usage":
|
|
184
186
|
case "runtime_reload_completed":
|
|
187
|
+
case "mcp_set_servers_result":
|
|
185
188
|
case "mcp_snapshot":
|
|
186
189
|
return "debug";
|
|
187
190
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { emitMcpOperationError, slashError, writeEvent } from "./events.js";
|
|
2
2
|
import { bridgeLogger, LOG_TARGETS } from "./logger.js";
|
|
3
|
+
import { bridgeMcpServersToSdk, mapMcpServerStatus, summarizeMcpServersForDiagnostics, } from "./mcp_metadata.js";
|
|
3
4
|
export const MCP_STALE_STATUS_REVALIDATION_COOLDOWN_MS = 30_000;
|
|
4
5
|
const knownConnectedMcpServers = new Set();
|
|
5
6
|
function logMcpSuccess(eventName, message, sessionId, requestId, fields) {
|
|
@@ -30,6 +31,22 @@ function logMcpFailure(eventName, message, sessionId, errorMessage, requestId, f
|
|
|
30
31
|
function queryWithMcpAuth(session) {
|
|
31
32
|
return session.query;
|
|
32
33
|
}
|
|
34
|
+
function mapMcpSetServersResult(result) {
|
|
35
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
36
|
+
return { added: [], removed: [], errors: {} };
|
|
37
|
+
}
|
|
38
|
+
const record = result;
|
|
39
|
+
const added = Array.isArray(record.added)
|
|
40
|
+
? record.added.filter((value) => typeof value === "string")
|
|
41
|
+
: [];
|
|
42
|
+
const removed = Array.isArray(record.removed)
|
|
43
|
+
? record.removed.filter((value) => typeof value === "string")
|
|
44
|
+
: [];
|
|
45
|
+
const errors = record.errors && typeof record.errors === "object" && !Array.isArray(record.errors)
|
|
46
|
+
? Object.fromEntries(Object.entries(record.errors).filter((entry) => typeof entry[1] === "string"))
|
|
47
|
+
: {};
|
|
48
|
+
return { added, removed, errors };
|
|
49
|
+
}
|
|
33
50
|
async function callMcpAuthMethod(session, methodName, args) {
|
|
34
51
|
const query = queryWithMcpAuth(session);
|
|
35
52
|
switch (methodName) {
|
|
@@ -72,17 +89,31 @@ function emitMcpCommandError(sessionId, operation, message, requestId, serverNam
|
|
|
72
89
|
message,
|
|
73
90
|
}, requestId);
|
|
74
91
|
}
|
|
75
|
-
export async function emitMcpSnapshotEvent(session, requestId) {
|
|
92
|
+
export async function emitMcpSnapshotEvent(session, requestId, source = "mcp_status") {
|
|
76
93
|
const servers = await session.query.mcpServerStatus();
|
|
77
94
|
let mapped = servers.map(mapMcpServerStatus);
|
|
78
95
|
mapped = await reconcileSuspiciousMcpStatuses(session, mapped);
|
|
96
|
+
return emitMcpSnapshotFromMappedStatuses(session, mapped, source, requestId);
|
|
97
|
+
}
|
|
98
|
+
export function emitMcpSnapshotFromStatuses(session, servers, source, requestId) {
|
|
99
|
+
return emitMcpSnapshotFromMappedStatuses(session, servers.map(mapMcpServerStatus), source, requestId);
|
|
100
|
+
}
|
|
101
|
+
export async function emitReconciledMcpSnapshotFromStatuses(session, servers, source, requestId) {
|
|
102
|
+
let mapped = servers.map(mapMcpServerStatus);
|
|
103
|
+
mapped = await reconcileSuspiciousMcpStatuses(session, mapped);
|
|
104
|
+
return emitMcpSnapshotFromMappedStatuses(session, mapped, source, requestId);
|
|
105
|
+
}
|
|
106
|
+
function emitMcpSnapshotFromMappedStatuses(session, mapped, source, requestId) {
|
|
79
107
|
rememberKnownConnectedMcpServers(mapped);
|
|
80
108
|
logMcpSuccess("mcp_snapshot_emitted", "MCP snapshot emitted", session.sessionId, requestId, {
|
|
109
|
+
source,
|
|
81
110
|
server_count: mapped.length,
|
|
111
|
+
servers: summarizeMcpServersForDiagnostics(mapped),
|
|
82
112
|
});
|
|
83
113
|
writeEvent({
|
|
84
114
|
event: "mcp_snapshot",
|
|
85
115
|
session_id: session.sessionId,
|
|
116
|
+
source,
|
|
86
117
|
servers: mapped,
|
|
87
118
|
}, requestId);
|
|
88
119
|
return mapped;
|
|
@@ -181,9 +212,9 @@ async function monitorMcpAuthSnapshot(session, serverName, attempt, maxAttempts,
|
|
|
181
212
|
}
|
|
182
213
|
}
|
|
183
214
|
}
|
|
184
|
-
export async function handleMcpStatusCommand(session, requestId) {
|
|
215
|
+
export async function handleMcpStatusCommand(session, requestId, source = "mcp_status") {
|
|
185
216
|
try {
|
|
186
|
-
await emitMcpSnapshotEvent(session, requestId);
|
|
217
|
+
await emitMcpSnapshotEvent(session, requestId, source);
|
|
187
218
|
}
|
|
188
219
|
catch (error) {
|
|
189
220
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -191,6 +222,7 @@ export async function handleMcpStatusCommand(session, requestId) {
|
|
|
191
222
|
writeEvent({
|
|
192
223
|
event: "mcp_snapshot",
|
|
193
224
|
session_id: session.sessionId,
|
|
225
|
+
source,
|
|
194
226
|
servers: [],
|
|
195
227
|
error: message,
|
|
196
228
|
}, requestId);
|
|
@@ -222,12 +254,24 @@ export async function handleMcpToggleCommand(session, command, requestId) {
|
|
|
222
254
|
}
|
|
223
255
|
export async function handleMcpSetServersCommand(session, command, requestId) {
|
|
224
256
|
try {
|
|
225
|
-
await session.query.setMcpServers(command.servers);
|
|
226
|
-
logMcpSuccess("mcp_servers_set_completed", "MCP server configuration updated", command.session_id, requestId, {
|
|
257
|
+
const result = mapMcpSetServersResult(await session.query.setMcpServers(bridgeMcpServersToSdk(command.servers)));
|
|
258
|
+
logMcpSuccess("mcp_servers_set_completed", "MCP server configuration updated", command.session_id, requestId, {
|
|
259
|
+
server_count: Object.keys(command.servers).length,
|
|
260
|
+
added_count: result.added.length,
|
|
261
|
+
removed_count: result.removed.length,
|
|
262
|
+
error_count: Object.keys(result.errors).length,
|
|
263
|
+
});
|
|
264
|
+
writeEvent({
|
|
265
|
+
event: "mcp_set_servers_result",
|
|
266
|
+
session_id: command.session_id,
|
|
267
|
+
result,
|
|
268
|
+
}, requestId);
|
|
269
|
+
await handleMcpStatusCommand(session, requestId, "mcp_set_servers");
|
|
227
270
|
}
|
|
228
271
|
catch (error) {
|
|
229
272
|
const message = error instanceof Error ? error.message : String(error);
|
|
230
273
|
logMcpFailure("mcp_servers_set_failed", "failed to update MCP server configuration", command.session_id, message, requestId, { server_count: Object.keys(command.servers).length });
|
|
274
|
+
emitMcpOperationError(command.session_id, { operation: "set-servers", message }, requestId);
|
|
231
275
|
slashError(command.session_id, `failed to set MCP servers: ${message}`, requestId);
|
|
232
276
|
}
|
|
233
277
|
}
|
|
@@ -289,77 +333,3 @@ export async function handleMcpOauthCallbackUrlCommand(session, command, request
|
|
|
289
333
|
emitMcpCommandError(command.session_id, "submit-callback-url", message, requestId, command.server_name);
|
|
290
334
|
}
|
|
291
335
|
}
|
|
292
|
-
function mapMcpServerStatus(status) {
|
|
293
|
-
return {
|
|
294
|
-
name: status.name,
|
|
295
|
-
status: status.status,
|
|
296
|
-
...(status.serverInfo
|
|
297
|
-
? {
|
|
298
|
-
server_info: {
|
|
299
|
-
name: status.serverInfo.name,
|
|
300
|
-
version: status.serverInfo.version,
|
|
301
|
-
},
|
|
302
|
-
}
|
|
303
|
-
: {}),
|
|
304
|
-
...(status.error ? { error: status.error } : {}),
|
|
305
|
-
...(status.config ? { config: mapMcpServerStatusConfig(status.config) } : {}),
|
|
306
|
-
...(status.scope ? { scope: status.scope } : {}),
|
|
307
|
-
tools: Array.isArray(status.tools)
|
|
308
|
-
? status.tools.map((tool) => ({
|
|
309
|
-
name: tool.name,
|
|
310
|
-
...(tool.description ? { description: tool.description } : {}),
|
|
311
|
-
...(tool.annotations
|
|
312
|
-
? {
|
|
313
|
-
annotations: {
|
|
314
|
-
...(typeof tool.annotations.readOnly === "boolean"
|
|
315
|
-
? { read_only: tool.annotations.readOnly }
|
|
316
|
-
: {}),
|
|
317
|
-
...(typeof tool.annotations.destructive === "boolean"
|
|
318
|
-
? { destructive: tool.annotations.destructive }
|
|
319
|
-
: {}),
|
|
320
|
-
...(typeof tool.annotations.openWorld === "boolean"
|
|
321
|
-
? { open_world: tool.annotations.openWorld }
|
|
322
|
-
: {}),
|
|
323
|
-
},
|
|
324
|
-
}
|
|
325
|
-
: {}),
|
|
326
|
-
}))
|
|
327
|
-
: [],
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
function mapMcpServerStatusConfig(config) {
|
|
331
|
-
switch (config.type) {
|
|
332
|
-
case "stdio":
|
|
333
|
-
return {
|
|
334
|
-
type: "stdio",
|
|
335
|
-
command: config.command,
|
|
336
|
-
...(Array.isArray(config.args) && config.args.length > 0 ? { args: config.args } : {}),
|
|
337
|
-
...(config.env ? { env: config.env } : {}),
|
|
338
|
-
};
|
|
339
|
-
case "sse":
|
|
340
|
-
return {
|
|
341
|
-
type: "sse",
|
|
342
|
-
url: config.url,
|
|
343
|
-
...(config.headers ? { headers: config.headers } : {}),
|
|
344
|
-
};
|
|
345
|
-
case "http":
|
|
346
|
-
return {
|
|
347
|
-
type: "http",
|
|
348
|
-
url: config.url,
|
|
349
|
-
...(config.headers ? { headers: config.headers } : {}),
|
|
350
|
-
};
|
|
351
|
-
case "sdk":
|
|
352
|
-
return {
|
|
353
|
-
type: "sdk",
|
|
354
|
-
name: config.name,
|
|
355
|
-
};
|
|
356
|
-
case "claudeai-proxy":
|
|
357
|
-
return {
|
|
358
|
-
type: "claudeai-proxy",
|
|
359
|
-
url: config.url,
|
|
360
|
-
id: config.id,
|
|
361
|
-
};
|
|
362
|
-
default:
|
|
363
|
-
throw new Error(`unsupported MCP status config: ${JSON.stringify(config)}`);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { bridgeLogger, LOG_TARGETS } from "./logger.js";
|
|
2
|
+
const TOOL_PERMISSION_POLICIES = new Set([
|
|
3
|
+
"always_allow",
|
|
4
|
+
"always_ask",
|
|
5
|
+
"always_deny",
|
|
6
|
+
]);
|
|
7
|
+
const ORG_MAX_PERMISSIONS = new Set([
|
|
8
|
+
"allow",
|
|
9
|
+
"ask",
|
|
10
|
+
"blocked",
|
|
11
|
+
]);
|
|
12
|
+
function asRecord(value, context) {
|
|
13
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
14
|
+
throw new Error(`${context} must be an object`);
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
function optionalStringArray(record, key, context) {
|
|
19
|
+
const value = record[key];
|
|
20
|
+
if (value === undefined) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) {
|
|
24
|
+
throw new Error(`${context}.${key} must be an array of strings`);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
function optionalStringMap(record, key, context) {
|
|
29
|
+
const value = record[key];
|
|
30
|
+
if (value === undefined) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const map = asRecord(value, `${context}.${key}`);
|
|
34
|
+
for (const [entryKey, entryValue] of Object.entries(map)) {
|
|
35
|
+
if (typeof entryValue !== "string") {
|
|
36
|
+
throw new Error(`${context}.${key}.${entryKey} must be a string`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return map;
|
|
40
|
+
}
|
|
41
|
+
function optionalTimeout(record, context) {
|
|
42
|
+
const value = record.timeout;
|
|
43
|
+
if (value === undefined) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value < 1000) {
|
|
47
|
+
throw new Error(`${context}.timeout must be an integer >= 1000`);
|
|
48
|
+
}
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
function optionalAlwaysLoad(record, context) {
|
|
52
|
+
const value = record.always_load;
|
|
53
|
+
if (value === undefined) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
if (typeof value !== "boolean") {
|
|
57
|
+
throw new Error(`${context}.always_load must be a boolean`);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
function optionalToolPolicies(record, context) {
|
|
62
|
+
const value = record.tools;
|
|
63
|
+
if (value === undefined) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
if (!Array.isArray(value)) {
|
|
67
|
+
throw new Error(`${context}.tools must be an array`);
|
|
68
|
+
}
|
|
69
|
+
return value.map((entry, index) => {
|
|
70
|
+
const item = asRecord(entry, `${context}.tools[${index}]`);
|
|
71
|
+
const name = item.name;
|
|
72
|
+
if (typeof name !== "string") {
|
|
73
|
+
throw new Error(`${context}.tools[${index}].name must be a string`);
|
|
74
|
+
}
|
|
75
|
+
const policy = { name };
|
|
76
|
+
const permissionPolicy = item.permission_policy;
|
|
77
|
+
if (permissionPolicy !== undefined) {
|
|
78
|
+
if (typeof permissionPolicy !== "string" || !TOOL_PERMISSION_POLICIES.has(permissionPolicy)) {
|
|
79
|
+
throw new Error(`${context}.tools[${index}].permission_policy must be one of always_allow, always_ask, always_deny`);
|
|
80
|
+
}
|
|
81
|
+
policy.permission_policy = permissionPolicy;
|
|
82
|
+
}
|
|
83
|
+
const orgMaxPermission = item.org_max_permission;
|
|
84
|
+
if (orgMaxPermission !== undefined) {
|
|
85
|
+
if (typeof orgMaxPermission !== "string" || !ORG_MAX_PERMISSIONS.has(orgMaxPermission)) {
|
|
86
|
+
throw new Error(`${context}.tools[${index}].org_max_permission must be one of allow, ask, blocked`);
|
|
87
|
+
}
|
|
88
|
+
policy.org_max_permission = orgMaxPermission;
|
|
89
|
+
}
|
|
90
|
+
return policy;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export function parseMcpServerConfig(value, context) {
|
|
94
|
+
const record = asRecord(value, context);
|
|
95
|
+
const rawType = record.type;
|
|
96
|
+
const type = rawType === undefined ? "stdio" : rawType;
|
|
97
|
+
if (typeof type !== "string") {
|
|
98
|
+
throw new Error(`${context}.type must be a string`);
|
|
99
|
+
}
|
|
100
|
+
const timeout = optionalTimeout(record, context);
|
|
101
|
+
const alwaysLoad = optionalAlwaysLoad(record, context);
|
|
102
|
+
switch (type) {
|
|
103
|
+
case "stdio": {
|
|
104
|
+
if (record.tools !== undefined) {
|
|
105
|
+
throw new Error(`${context}.tools is only supported for http and sse MCP servers`);
|
|
106
|
+
}
|
|
107
|
+
const command = record.command;
|
|
108
|
+
if (typeof command !== "string") {
|
|
109
|
+
throw new Error(`${context}.command must be a string`);
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
type,
|
|
113
|
+
command,
|
|
114
|
+
...(optionalStringArray(record, "args", context) ? { args: optionalStringArray(record, "args", context) } : {}),
|
|
115
|
+
...(optionalStringMap(record, "env", context) ? { env: optionalStringMap(record, "env", context) } : {}),
|
|
116
|
+
...(timeout === undefined ? {} : { timeout }),
|
|
117
|
+
...(alwaysLoad === undefined ? {} : { always_load: alwaysLoad }),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
case "sse":
|
|
121
|
+
case "http": {
|
|
122
|
+
const url = record.url;
|
|
123
|
+
if (typeof url !== "string") {
|
|
124
|
+
throw new Error(`${context}.url must be a string`);
|
|
125
|
+
}
|
|
126
|
+
const tools = optionalToolPolicies(record, context);
|
|
127
|
+
return {
|
|
128
|
+
type,
|
|
129
|
+
url,
|
|
130
|
+
...(optionalStringMap(record, "headers", context) ? { headers: optionalStringMap(record, "headers", context) } : {}),
|
|
131
|
+
...(tools === undefined ? {} : { tools }),
|
|
132
|
+
...(timeout === undefined ? {} : { timeout }),
|
|
133
|
+
...(alwaysLoad === undefined ? {} : { always_load: alwaysLoad }),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
default:
|
|
137
|
+
throw new Error(`${context}.type must be one of stdio, sse, http`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export function parseMcpServersRecord(value, context) {
|
|
141
|
+
const record = asRecord(value, context);
|
|
142
|
+
return Object.fromEntries(Object.entries(record).map(([key, entry]) => [key, parseMcpServerConfig(entry, `${context}.${key}`)]));
|
|
143
|
+
}
|
|
144
|
+
function toSdkToolPolicies(tools) {
|
|
145
|
+
return tools?.map((tool) => ({
|
|
146
|
+
name: tool.name,
|
|
147
|
+
...(tool.permission_policy === undefined ? {} : { permission_policy: tool.permission_policy }),
|
|
148
|
+
...(tool.org_max_permission === undefined ? {} : { org_max_permission: tool.org_max_permission }),
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
151
|
+
export function bridgeMcpConfigToSdk(config) {
|
|
152
|
+
switch (config.type) {
|
|
153
|
+
case "stdio":
|
|
154
|
+
return {
|
|
155
|
+
type: "stdio",
|
|
156
|
+
command: config.command,
|
|
157
|
+
...(config.args ? { args: config.args } : {}),
|
|
158
|
+
...(config.env ? { env: config.env } : {}),
|
|
159
|
+
...(config.timeout === undefined ? {} : { timeout: config.timeout }),
|
|
160
|
+
...(config.always_load === undefined ? {} : { alwaysLoad: config.always_load }),
|
|
161
|
+
};
|
|
162
|
+
case "sse":
|
|
163
|
+
return {
|
|
164
|
+
type: "sse",
|
|
165
|
+
url: config.url,
|
|
166
|
+
...(config.headers ? { headers: config.headers } : {}),
|
|
167
|
+
...(config.tools ? { tools: toSdkToolPolicies(config.tools) } : {}),
|
|
168
|
+
...(config.timeout === undefined ? {} : { timeout: config.timeout }),
|
|
169
|
+
...(config.always_load === undefined ? {} : { alwaysLoad: config.always_load }),
|
|
170
|
+
};
|
|
171
|
+
case "http":
|
|
172
|
+
return {
|
|
173
|
+
type: "http",
|
|
174
|
+
url: config.url,
|
|
175
|
+
...(config.headers ? { headers: config.headers } : {}),
|
|
176
|
+
...(config.tools ? { tools: toSdkToolPolicies(config.tools) } : {}),
|
|
177
|
+
...(config.timeout === undefined ? {} : { timeout: config.timeout }),
|
|
178
|
+
...(config.always_load === undefined ? {} : { alwaysLoad: config.always_load }),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export function bridgeMcpServersToSdk(servers) {
|
|
183
|
+
return Object.fromEntries(Object.entries(servers).map(([name, config]) => [name, bridgeMcpConfigToSdk(config)]));
|
|
184
|
+
}
|
|
185
|
+
function mapSdkToolPolicies(tools) {
|
|
186
|
+
if (!Array.isArray(tools)) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
return tools
|
|
190
|
+
.map((tool) => {
|
|
191
|
+
if (!tool || typeof tool !== "object" || Array.isArray(tool)) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
const raw = tool;
|
|
195
|
+
if (typeof raw.name !== "string") {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
const policy = { name: raw.name };
|
|
199
|
+
if (raw.permission_policy !== undefined) {
|
|
200
|
+
if (typeof raw.permission_policy !== "string" || !TOOL_PERMISSION_POLICIES.has(raw.permission_policy)) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
policy.permission_policy = raw.permission_policy;
|
|
204
|
+
}
|
|
205
|
+
if (raw.org_max_permission !== undefined) {
|
|
206
|
+
if (typeof raw.org_max_permission !== "string" || !ORG_MAX_PERMISSIONS.has(raw.org_max_permission)) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
policy.org_max_permission = raw.org_max_permission;
|
|
210
|
+
}
|
|
211
|
+
return policy;
|
|
212
|
+
})
|
|
213
|
+
.filter((tool) => tool !== null);
|
|
214
|
+
}
|
|
215
|
+
export function mapMcpServerStatus(status) {
|
|
216
|
+
return {
|
|
217
|
+
name: status.name,
|
|
218
|
+
status: status.status,
|
|
219
|
+
...(status.serverInfo
|
|
220
|
+
? {
|
|
221
|
+
server_info: {
|
|
222
|
+
name: status.serverInfo.name,
|
|
223
|
+
version: status.serverInfo.version,
|
|
224
|
+
},
|
|
225
|
+
}
|
|
226
|
+
: {}),
|
|
227
|
+
...(status.error ? { error: status.error } : {}),
|
|
228
|
+
...(status.config ? { config: mapMcpServerStatusConfig(status.config) } : {}),
|
|
229
|
+
...(status.scope ? { scope: status.scope } : {}),
|
|
230
|
+
tools: Array.isArray(status.tools)
|
|
231
|
+
? status.tools.map((tool) => ({
|
|
232
|
+
name: tool.name,
|
|
233
|
+
...(tool.description ? { description: tool.description } : {}),
|
|
234
|
+
...(tool.annotations
|
|
235
|
+
? {
|
|
236
|
+
annotations: {
|
|
237
|
+
...(typeof tool.annotations.readOnly === "boolean"
|
|
238
|
+
? { read_only: tool.annotations.readOnly }
|
|
239
|
+
: {}),
|
|
240
|
+
...(typeof tool.annotations.destructive === "boolean"
|
|
241
|
+
? { destructive: tool.annotations.destructive }
|
|
242
|
+
: {}),
|
|
243
|
+
...(typeof tool.annotations.openWorld === "boolean"
|
|
244
|
+
? { open_world: tool.annotations.openWorld }
|
|
245
|
+
: {}),
|
|
246
|
+
},
|
|
247
|
+
}
|
|
248
|
+
: {}),
|
|
249
|
+
}))
|
|
250
|
+
: [],
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
export function mapMcpServerStatusConfig(config) {
|
|
254
|
+
switch (config.type) {
|
|
255
|
+
case "stdio":
|
|
256
|
+
return {
|
|
257
|
+
type: "stdio",
|
|
258
|
+
command: config.command,
|
|
259
|
+
...(Array.isArray(config.args) && config.args.length > 0 ? { args: config.args } : {}),
|
|
260
|
+
...(config.env ? { env: config.env } : {}),
|
|
261
|
+
...(config.timeout === undefined ? {} : { timeout: config.timeout }),
|
|
262
|
+
...(config.alwaysLoad === undefined ? {} : { always_load: config.alwaysLoad }),
|
|
263
|
+
};
|
|
264
|
+
case "sse": {
|
|
265
|
+
const tools = mapSdkToolPolicies(config.tools);
|
|
266
|
+
return {
|
|
267
|
+
type: "sse",
|
|
268
|
+
url: config.url,
|
|
269
|
+
...(config.headers ? { headers: config.headers } : {}),
|
|
270
|
+
...(tools === undefined ? {} : { tools }),
|
|
271
|
+
...(config.timeout === undefined ? {} : { timeout: config.timeout }),
|
|
272
|
+
...(config.alwaysLoad === undefined ? {} : { always_load: config.alwaysLoad }),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
case "http": {
|
|
276
|
+
const tools = mapSdkToolPolicies(config.tools);
|
|
277
|
+
return {
|
|
278
|
+
type: "http",
|
|
279
|
+
url: config.url,
|
|
280
|
+
...(config.headers ? { headers: config.headers } : {}),
|
|
281
|
+
...(tools === undefined ? {} : { tools }),
|
|
282
|
+
...(config.timeout === undefined ? {} : { timeout: config.timeout }),
|
|
283
|
+
...(config.alwaysLoad === undefined ? {} : { always_load: config.alwaysLoad }),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
case "sdk":
|
|
287
|
+
return {
|
|
288
|
+
type: "sdk",
|
|
289
|
+
name: config.name,
|
|
290
|
+
};
|
|
291
|
+
case "claudeai-proxy":
|
|
292
|
+
return {
|
|
293
|
+
type: "claudeai-proxy",
|
|
294
|
+
url: config.url,
|
|
295
|
+
id: config.id,
|
|
296
|
+
...(config.timeout === undefined ? {} : { timeout: config.timeout }),
|
|
297
|
+
};
|
|
298
|
+
default: {
|
|
299
|
+
const raw = config;
|
|
300
|
+
const rawType = typeof raw.type === "string" ? raw.type : "unknown";
|
|
301
|
+
bridgeLogger.warn({
|
|
302
|
+
target: LOG_TARGETS.BRIDGE_MCP,
|
|
303
|
+
eventName: "mcp_status_config_unknown",
|
|
304
|
+
message: "unknown MCP status config type",
|
|
305
|
+
outcome: "ignored",
|
|
306
|
+
fields: { config_type: rawType },
|
|
307
|
+
});
|
|
308
|
+
return { type: "unknown", raw_type: rawType };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function mcpStatusConfigDiagnostics(config) {
|
|
313
|
+
if (!config) {
|
|
314
|
+
return {
|
|
315
|
+
config_type: "missing",
|
|
316
|
+
configured_tool_policy_count: 0,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
switch (config.type) {
|
|
320
|
+
case "stdio":
|
|
321
|
+
return {
|
|
322
|
+
config_type: "stdio",
|
|
323
|
+
...(config.timeout === undefined ? {} : { timeout_ms: config.timeout }),
|
|
324
|
+
...(config.always_load === undefined ? {} : { always_load: config.always_load }),
|
|
325
|
+
configured_tool_policy_count: 0,
|
|
326
|
+
};
|
|
327
|
+
case "sse":
|
|
328
|
+
case "http":
|
|
329
|
+
return {
|
|
330
|
+
config_type: config.type,
|
|
331
|
+
...(config.timeout === undefined ? {} : { timeout_ms: config.timeout }),
|
|
332
|
+
...(config.always_load === undefined ? {} : { always_load: config.always_load }),
|
|
333
|
+
configured_tool_policy_count: config.tools?.length ?? 0,
|
|
334
|
+
};
|
|
335
|
+
case "sdk":
|
|
336
|
+
return {
|
|
337
|
+
config_type: "sdk",
|
|
338
|
+
configured_tool_policy_count: 0,
|
|
339
|
+
};
|
|
340
|
+
case "claudeai-proxy":
|
|
341
|
+
return {
|
|
342
|
+
config_type: "claudeai-proxy",
|
|
343
|
+
...(config.timeout === undefined ? {} : { timeout_ms: config.timeout }),
|
|
344
|
+
configured_tool_policy_count: 0,
|
|
345
|
+
};
|
|
346
|
+
case "unknown":
|
|
347
|
+
return {
|
|
348
|
+
config_type: `unknown:${config.raw_type}`,
|
|
349
|
+
configured_tool_policy_count: 0,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
export function summarizeMcpServersForDiagnostics(servers) {
|
|
354
|
+
return servers.map((server) => {
|
|
355
|
+
const config = mcpStatusConfigDiagnostics(server.config);
|
|
356
|
+
return {
|
|
357
|
+
name: server.name,
|
|
358
|
+
status: server.status,
|
|
359
|
+
config_type: config.config_type,
|
|
360
|
+
...(server.scope ? { scope: server.scope } : {}),
|
|
361
|
+
...(config.timeout_ms === undefined ? {} : { timeout_ms: config.timeout_ms }),
|
|
362
|
+
...(config.always_load === undefined ? {} : { always_load: config.always_load }),
|
|
363
|
+
tool_count: server.tools.length,
|
|
364
|
+
configured_tool_policy_count: config.configured_tool_policy_count,
|
|
365
|
+
has_error: typeof server.error === "string" && server.error.length > 0,
|
|
366
|
+
has_server_info: server.server_info !== undefined,
|
|
367
|
+
};
|
|
368
|
+
});
|
|
369
|
+
}
|