@casys/mcp-server 0.11.0 → 0.12.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/mod.ts +3 -0
- package/package.json +1 -1
- package/src/concurrent-server.ts +197 -89
- package/src/security/csp.ts +3 -1
- package/src/types.ts +69 -0
package/mod.ts
CHANGED
|
@@ -71,6 +71,7 @@ export type {
|
|
|
71
71
|
MCPTool,
|
|
72
72
|
MCPToolMeta,
|
|
73
73
|
McpUiToolMeta,
|
|
74
|
+
ToolAnnotations,
|
|
74
75
|
QueueMetrics,
|
|
75
76
|
RateLimitContext,
|
|
76
77
|
RateLimitOptions,
|
|
@@ -79,6 +80,8 @@ export type {
|
|
|
79
80
|
SamplingClient,
|
|
80
81
|
SamplingParams,
|
|
81
82
|
SamplingResult,
|
|
83
|
+
StructuredToolResult,
|
|
84
|
+
ToolErrorMapper,
|
|
82
85
|
ToolHandler,
|
|
83
86
|
} from "./src/types.js";
|
|
84
87
|
|
package/package.json
CHANGED
package/src/concurrent-server.ts
CHANGED
|
@@ -53,6 +53,7 @@ import type {
|
|
|
53
53
|
QueueMetrics,
|
|
54
54
|
ResourceContent,
|
|
55
55
|
ResourceHandler,
|
|
56
|
+
StructuredToolResult,
|
|
56
57
|
ToolHandler,
|
|
57
58
|
} from "./types.js";
|
|
58
59
|
import { MCP_APP_MIME_TYPE, MCP_APP_URI_SCHEME } from "./types.js";
|
|
@@ -234,6 +235,7 @@ export class ConcurrentMCPServer {
|
|
|
234
235
|
capabilities: {
|
|
235
236
|
tools: {},
|
|
236
237
|
},
|
|
238
|
+
instructions: options.instructions,
|
|
237
239
|
},
|
|
238
240
|
);
|
|
239
241
|
|
|
@@ -336,14 +338,7 @@ export class ConcurrentMCPServer {
|
|
|
336
338
|
|
|
337
339
|
// tools/list handler
|
|
338
340
|
server.setRequestHandler(ListToolsRequestSchema, () => {
|
|
339
|
-
return {
|
|
340
|
-
tools: Array.from(this.tools.values()).map((t) => ({
|
|
341
|
-
name: t.name,
|
|
342
|
-
description: t.description,
|
|
343
|
-
inputSchema: t.inputSchema,
|
|
344
|
-
_meta: t._meta, // Always include, even if undefined (MCP Apps discovery)
|
|
345
|
-
})),
|
|
346
|
-
};
|
|
341
|
+
return { tools: this.buildToolListing() };
|
|
347
342
|
});
|
|
348
343
|
|
|
349
344
|
// tools/call handler (delegates to middleware pipeline)
|
|
@@ -351,43 +346,15 @@ export class ConcurrentMCPServer {
|
|
|
351
346
|
const toolName = request.params.name;
|
|
352
347
|
const args = request.params.arguments || {};
|
|
353
348
|
|
|
349
|
+
let result: unknown;
|
|
354
350
|
try {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
// If handler returns a pre-formatted MCP result (has content array),
|
|
358
|
-
// pass it through without re-wrapping. This supports proxy/gateway
|
|
359
|
-
// patterns where the handler builds the complete response.
|
|
360
|
-
if (this.isPreformattedResult(result)) {
|
|
361
|
-
return result;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Format response according to MCP protocol
|
|
365
|
-
const tool = this.tools.get(toolName);
|
|
366
|
-
const response: {
|
|
367
|
-
content: Array<{ type: "text"; text: string }>;
|
|
368
|
-
_meta?: Record<string, unknown>;
|
|
369
|
-
} = {
|
|
370
|
-
content: [
|
|
371
|
-
{
|
|
372
|
-
type: "text",
|
|
373
|
-
text: typeof result === "string"
|
|
374
|
-
? result
|
|
375
|
-
: JSON.stringify(result, null, 2),
|
|
376
|
-
},
|
|
377
|
-
],
|
|
378
|
-
};
|
|
379
|
-
if (tool?._meta) {
|
|
380
|
-
response._meta = tool._meta as Record<string, unknown>;
|
|
381
|
-
}
|
|
382
|
-
return response;
|
|
351
|
+
result = await this.executeToolCall(toolName, args);
|
|
383
352
|
} catch (error) {
|
|
384
|
-
this.
|
|
385
|
-
`Error executing tool ${request.params.name}: ${
|
|
386
|
-
error instanceof Error ? error.message : String(error)
|
|
387
|
-
}`,
|
|
388
|
-
);
|
|
389
|
-
throw error;
|
|
353
|
+
return this.handleToolError(error, toolName);
|
|
390
354
|
}
|
|
355
|
+
|
|
356
|
+
// Serialization errors are framework bugs, not tool errors — let them propagate
|
|
357
|
+
return this.buildToolCallResult(toolName, result);
|
|
391
358
|
});
|
|
392
359
|
}
|
|
393
360
|
|
|
@@ -478,6 +445,30 @@ export class ConcurrentMCPServer {
|
|
|
478
445
|
this.log(`Live-registered tool: ${tool.name} (total: ${this.tools.size})`);
|
|
479
446
|
}
|
|
480
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Register a tool that is only visible to the MCP App (UI layer),
|
|
450
|
+
* not to the model via tools/list.
|
|
451
|
+
*
|
|
452
|
+
* Equivalent to registerTool() with _meta.ui.visibility: ["app"].
|
|
453
|
+
* The tool is still callable via tools/call if the caller knows its name.
|
|
454
|
+
*
|
|
455
|
+
* @param tool - Tool definition
|
|
456
|
+
* @param handler - Tool handler function
|
|
457
|
+
*/
|
|
458
|
+
registerAppOnlyTool(tool: MCPTool, handler: ToolHandler): void {
|
|
459
|
+
const merged: MCPTool = {
|
|
460
|
+
...tool,
|
|
461
|
+
_meta: {
|
|
462
|
+
...tool._meta,
|
|
463
|
+
ui: {
|
|
464
|
+
...tool._meta?.ui,
|
|
465
|
+
visibility: ["app"],
|
|
466
|
+
},
|
|
467
|
+
} as MCPTool["_meta"],
|
|
468
|
+
};
|
|
469
|
+
this.registerTool(merged, handler);
|
|
470
|
+
}
|
|
471
|
+
|
|
481
472
|
/**
|
|
482
473
|
* Unregister a tool (removes it from tools/list and tools/call).
|
|
483
474
|
*
|
|
@@ -859,7 +850,15 @@ export class ConcurrentMCPServer {
|
|
|
859
850
|
},
|
|
860
851
|
async () => {
|
|
861
852
|
const html = await Promise.resolve(readFile(currentDistPath));
|
|
862
|
-
|
|
853
|
+
const content: import("./types.js").ResourceContent = {
|
|
854
|
+
uri: resourceUri,
|
|
855
|
+
mimeType: MCP_APP_MIME_TYPE,
|
|
856
|
+
text: html,
|
|
857
|
+
};
|
|
858
|
+
if (config.csp) {
|
|
859
|
+
(content as unknown as Record<string, unknown>)._meta = { ui: { csp: config.csp } };
|
|
860
|
+
}
|
|
861
|
+
return content;
|
|
863
862
|
},
|
|
864
863
|
);
|
|
865
864
|
|
|
@@ -1443,6 +1442,7 @@ export class ConcurrentMCPServer {
|
|
|
1443
1442
|
name: this.options.name,
|
|
1444
1443
|
version: this.options.version,
|
|
1445
1444
|
},
|
|
1445
|
+
...(this.options.instructions ? { instructions: this.options.instructions } : {}),
|
|
1446
1446
|
},
|
|
1447
1447
|
}),
|
|
1448
1448
|
{
|
|
@@ -1480,29 +1480,10 @@ export class ConcurrentMCPServer {
|
|
|
1480
1480
|
c.req.raw,
|
|
1481
1481
|
reqSessionId,
|
|
1482
1482
|
);
|
|
1483
|
-
|
|
1484
|
-
// Pre-formatted result: pass through as-is
|
|
1485
|
-
if (this.isPreformattedResult(result)) {
|
|
1486
|
-
return c.json({
|
|
1487
|
-
jsonrpc: "2.0",
|
|
1488
|
-
id,
|
|
1489
|
-
result,
|
|
1490
|
-
});
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
const tool = this.tools.get(toolName);
|
|
1494
1483
|
return c.json({
|
|
1495
1484
|
jsonrpc: "2.0",
|
|
1496
1485
|
id,
|
|
1497
|
-
result:
|
|
1498
|
-
content: [{
|
|
1499
|
-
type: "text",
|
|
1500
|
-
text: typeof result === "string"
|
|
1501
|
-
? result
|
|
1502
|
-
: JSON.stringify(result, null, 2),
|
|
1503
|
-
}],
|
|
1504
|
-
...(tool?._meta && { _meta: tool._meta }),
|
|
1505
|
-
},
|
|
1486
|
+
result: this.buildToolCallResult(toolName, result),
|
|
1506
1487
|
});
|
|
1507
1488
|
} catch (error) {
|
|
1508
1489
|
// Handle AuthError with proper HTTP status codes
|
|
@@ -1521,24 +1502,34 @@ export class ConcurrentMCPServer {
|
|
|
1521
1502
|
}
|
|
1522
1503
|
}
|
|
1523
1504
|
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1505
|
+
// Delegate to centralized error handler
|
|
1506
|
+
try {
|
|
1507
|
+
const isErrorResult = this.handleToolError(error, toolName);
|
|
1508
|
+
return c.json({
|
|
1509
|
+
jsonrpc: "2.0",
|
|
1510
|
+
id,
|
|
1511
|
+
result: isErrorResult,
|
|
1512
|
+
});
|
|
1513
|
+
} catch (rethrown) {
|
|
1514
|
+
this.log(
|
|
1515
|
+
`Error executing tool ${toolName}: ${
|
|
1516
|
+
rethrown instanceof Error ? rethrown.message : String(rethrown)
|
|
1517
|
+
}`,
|
|
1518
|
+
);
|
|
1519
|
+
const errorMessage = rethrown instanceof Error
|
|
1520
|
+
? rethrown.message
|
|
1521
|
+
: "Tool execution failed";
|
|
1522
|
+
const errorCode = errorMessage.startsWith("Unknown tool")
|
|
1523
|
+
? -32602
|
|
1524
|
+
: errorMessage.startsWith("Rate limit")
|
|
1525
|
+
? -32000
|
|
1526
|
+
: -32603;
|
|
1527
|
+
return c.json({
|
|
1528
|
+
jsonrpc: "2.0",
|
|
1529
|
+
id,
|
|
1530
|
+
error: { code: errorCode, message: errorMessage },
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1542
1533
|
}
|
|
1543
1534
|
}
|
|
1544
1535
|
|
|
@@ -1552,14 +1543,7 @@ export class ConcurrentMCPServer {
|
|
|
1552
1543
|
return c.json({
|
|
1553
1544
|
jsonrpc: "2.0",
|
|
1554
1545
|
id,
|
|
1555
|
-
result: {
|
|
1556
|
-
tools: Array.from(this.tools.values()).map((t) => ({
|
|
1557
|
-
name: t.name,
|
|
1558
|
-
description: t.description,
|
|
1559
|
-
inputSchema: t.inputSchema,
|
|
1560
|
-
_meta: t._meta,
|
|
1561
|
-
})),
|
|
1562
|
-
},
|
|
1546
|
+
result: { tools: this.buildToolListing() },
|
|
1563
1547
|
});
|
|
1564
1548
|
}
|
|
1565
1549
|
|
|
@@ -1975,6 +1959,127 @@ export class ConcurrentMCPServer {
|
|
|
1975
1959
|
"text" in obj.content[0];
|
|
1976
1960
|
}
|
|
1977
1961
|
|
|
1962
|
+
/**
|
|
1963
|
+
* Build the tools/list response, filtering out app-only tools
|
|
1964
|
+
* and passing through outputSchema/annotations when defined.
|
|
1965
|
+
*/
|
|
1966
|
+
private buildToolListing(): Array<Record<string, unknown>> {
|
|
1967
|
+
return Array.from(this.tools.values())
|
|
1968
|
+
.filter((t) => {
|
|
1969
|
+
const vis = t._meta?.ui?.visibility;
|
|
1970
|
+
if (vis !== undefined && !vis.includes("model")) return false;
|
|
1971
|
+
return true;
|
|
1972
|
+
})
|
|
1973
|
+
.map((t) => {
|
|
1974
|
+
const entry: Record<string, unknown> = {
|
|
1975
|
+
name: t.name,
|
|
1976
|
+
description: t.description,
|
|
1977
|
+
inputSchema: t.inputSchema,
|
|
1978
|
+
_meta: t._meta,
|
|
1979
|
+
};
|
|
1980
|
+
if (t.outputSchema !== undefined) entry.outputSchema = t.outputSchema;
|
|
1981
|
+
if (t.annotations !== undefined) entry.annotations = t.annotations;
|
|
1982
|
+
return entry;
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
/**
|
|
1987
|
+
* Check if a handler result is a StructuredToolResult
|
|
1988
|
+
* (has content as string + structuredContent as object).
|
|
1989
|
+
*/
|
|
1990
|
+
private isStructuredToolResult(
|
|
1991
|
+
result: unknown,
|
|
1992
|
+
): result is StructuredToolResult {
|
|
1993
|
+
if (!result || typeof result !== "object") return false;
|
|
1994
|
+
const obj = result as Record<string, unknown>;
|
|
1995
|
+
return (
|
|
1996
|
+
typeof obj.content === "string" &&
|
|
1997
|
+
obj.structuredContent !== null &&
|
|
1998
|
+
obj.structuredContent !== undefined &&
|
|
1999
|
+
typeof obj.structuredContent === "object" &&
|
|
2000
|
+
!Array.isArray(obj.structuredContent)
|
|
2001
|
+
);
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
/**
|
|
2005
|
+
* Build a CallToolResult from the handler's return value.
|
|
2006
|
+
* Priority: preformatted > structuredToolResult > plain value.
|
|
2007
|
+
*/
|
|
2008
|
+
private buildToolCallResult(
|
|
2009
|
+
toolName: string,
|
|
2010
|
+
result: unknown,
|
|
2011
|
+
): Record<string, unknown> {
|
|
2012
|
+
// Proxy/gateway pattern — pass through as-is
|
|
2013
|
+
if (this.isPreformattedResult(result)) {
|
|
2014
|
+
return result as Record<string, unknown>;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
const tool = this.tools.get(toolName);
|
|
2018
|
+
|
|
2019
|
+
// StructuredToolResult: separate content (for LLM) and structuredContent (for viewer)
|
|
2020
|
+
if (this.isStructuredToolResult(result)) {
|
|
2021
|
+
const r: Record<string, unknown> = {
|
|
2022
|
+
content: [{ type: "text", text: result.content }],
|
|
2023
|
+
structuredContent: result.structuredContent,
|
|
2024
|
+
};
|
|
2025
|
+
if (tool?._meta) r._meta = tool._meta;
|
|
2026
|
+
return r;
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
// Plain value: JSON-stringify into content[0].text
|
|
2030
|
+
const r: Record<string, unknown> = {
|
|
2031
|
+
content: [{
|
|
2032
|
+
type: "text",
|
|
2033
|
+
text: typeof result === "string"
|
|
2034
|
+
? result
|
|
2035
|
+
: JSON.stringify(result, null, 2),
|
|
2036
|
+
}],
|
|
2037
|
+
};
|
|
2038
|
+
if (tool?._meta) r._meta = tool._meta;
|
|
2039
|
+
return r;
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
/**
|
|
2043
|
+
* Handle a tool execution error using the configured toolErrorMapper.
|
|
2044
|
+
* Returns an isError result if the mapper handles it, otherwise rethrows.
|
|
2045
|
+
*/
|
|
2046
|
+
private handleToolError(
|
|
2047
|
+
error: unknown,
|
|
2048
|
+
toolName: string,
|
|
2049
|
+
): Record<string, unknown> {
|
|
2050
|
+
const mapper = this.options.toolErrorMapper;
|
|
2051
|
+
if (mapper) {
|
|
2052
|
+
let msg: string | null;
|
|
2053
|
+
try {
|
|
2054
|
+
msg = mapper(error, toolName);
|
|
2055
|
+
} catch (mapperError) {
|
|
2056
|
+
this.log(
|
|
2057
|
+
`toolErrorMapper threw for tool ${toolName}: ${
|
|
2058
|
+
mapperError instanceof Error ? mapperError.message : String(mapperError)
|
|
2059
|
+
} (original error: ${
|
|
2060
|
+
error instanceof Error ? error.message : String(error)
|
|
2061
|
+
})`,
|
|
2062
|
+
);
|
|
2063
|
+
// Fall through to rethrow original error
|
|
2064
|
+
msg = null;
|
|
2065
|
+
}
|
|
2066
|
+
if (msg !== null) {
|
|
2067
|
+
this.log(`Tool ${toolName} returned business error: ${msg}`);
|
|
2068
|
+
return {
|
|
2069
|
+
content: [{ type: "text", text: msg }],
|
|
2070
|
+
isError: true,
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
// No mapper or mapper returned null → rethrow
|
|
2075
|
+
this.log(
|
|
2076
|
+
`Error executing tool ${toolName}: ${
|
|
2077
|
+
error instanceof Error ? error.message : String(error)
|
|
2078
|
+
}`,
|
|
2079
|
+
);
|
|
2080
|
+
throw error;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
1978
2083
|
/**
|
|
1979
2084
|
* Log message using custom logger or stderr
|
|
1980
2085
|
*/
|
|
@@ -2007,6 +2112,9 @@ export interface RegisterViewersConfig {
|
|
|
2007
2112
|
} & DiscoverViewersFS;
|
|
2008
2113
|
/** Custom function to generate human-readable names. Default: kebab-to-Title. */
|
|
2009
2114
|
humanName?: (viewerName: string) => string;
|
|
2115
|
+
/** MCP Apps CSP — declares external domains the viewer needs (tiles, APIs, CDNs).
|
|
2116
|
+
* Uses McpUiCsp from @casys/mcp-compose (resourceDomains, connectDomains). */
|
|
2117
|
+
csp?: { resourceDomains?: string[]; connectDomains?: string[]; frameDomains?: string[] };
|
|
2010
2118
|
}
|
|
2011
2119
|
|
|
2012
2120
|
/** Summary returned by registerViewers() */
|
package/src/security/csp.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface CspOptions {
|
|
|
13
13
|
readonly scriptSources?: readonly string[];
|
|
14
14
|
/** Additional allowed connect sources (e.g. WebSocket endpoints). */
|
|
15
15
|
readonly connectSources?: readonly string[];
|
|
16
|
+
/** Additional allowed image sources (e.g. tile servers). */
|
|
17
|
+
readonly imgSources?: readonly string[];
|
|
16
18
|
/** Additional allowed frame ancestors. */
|
|
17
19
|
readonly frameAncestors?: readonly string[];
|
|
18
20
|
/**
|
|
@@ -49,7 +51,7 @@ export function buildCspHeader(options: CspOptions = {}): string {
|
|
|
49
51
|
`default-src 'none'`,
|
|
50
52
|
`script-src ${scriptSrc}`,
|
|
51
53
|
`style-src 'self'${inlineDirective}`,
|
|
52
|
-
`img-src 'self' data
|
|
54
|
+
`img-src 'self' data: ${(options.imgSources ?? []).join(" ")}`.trim(),
|
|
53
55
|
`font-src 'self'`,
|
|
54
56
|
`connect-src ${connectSrc}`,
|
|
55
57
|
`frame-ancestors ${frameAncestors}`,
|
package/src/types.ts
CHANGED
|
@@ -78,12 +78,29 @@ export interface ConcurrentServerOptions {
|
|
|
78
78
|
/** Enable sampling support for agentic tools (default: false) */
|
|
79
79
|
enableSampling?: boolean;
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Instructions for the LLM on how to use this server's tools.
|
|
83
|
+
* Sent in the MCP initialize response. The LLM sees this before any tool call.
|
|
84
|
+
*/
|
|
85
|
+
instructions?: string;
|
|
86
|
+
|
|
81
87
|
/** Sampling client implementation (required if enableSampling is true) */
|
|
82
88
|
samplingClient?: SamplingClient;
|
|
83
89
|
|
|
84
90
|
/** Custom logger function (default: console.error) */
|
|
85
91
|
logger?: (msg: string) => void;
|
|
86
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Custom error handler for tool execution errors.
|
|
95
|
+
*
|
|
96
|
+
* When set, errors thrown by tool handlers are passed to this function.
|
|
97
|
+
* Return a message string to produce `{ content: [{type:"text", text: msg}], isError: true }`
|
|
98
|
+
* instead of re-throwing. Return null to rethrow as a JSON-RPC error.
|
|
99
|
+
*
|
|
100
|
+
* Default: undefined (all errors are re-thrown, existing behaviour).
|
|
101
|
+
*/
|
|
102
|
+
toolErrorMapper?: ToolErrorMapper;
|
|
103
|
+
|
|
87
104
|
/**
|
|
88
105
|
* OAuth2/Bearer authentication configuration.
|
|
89
106
|
* When provided, HTTP requests require a valid Bearer token.
|
|
@@ -238,6 +255,23 @@ export const MCP_APP_URI_SCHEME = "ui:" as const;
|
|
|
238
255
|
// MCP Tool Types
|
|
239
256
|
// ============================================
|
|
240
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Behavioural hints for model clients (MCP SDK 1.27 ToolAnnotations).
|
|
260
|
+
* Passed through in tools/list so hosts can adapt their UI accordingly.
|
|
261
|
+
*/
|
|
262
|
+
export interface ToolAnnotations {
|
|
263
|
+
/** Short human-readable title, may differ from tool name */
|
|
264
|
+
title?: string;
|
|
265
|
+
/** If true, tool has no side-effects and is safe to call speculatively */
|
|
266
|
+
readOnlyHint?: boolean;
|
|
267
|
+
/** If true, executing may produce irreversible effects */
|
|
268
|
+
destructiveHint?: boolean;
|
|
269
|
+
/** If true, repeated calls with same args produce same result */
|
|
270
|
+
idempotentHint?: boolean;
|
|
271
|
+
/** If true, tool may interact with entities outside the MCP system */
|
|
272
|
+
openWorldHint?: boolean;
|
|
273
|
+
}
|
|
274
|
+
|
|
241
275
|
/**
|
|
242
276
|
* MCP Tool definition (compatible with MCP protocol)
|
|
243
277
|
*/
|
|
@@ -251,6 +285,15 @@ export interface MCPTool {
|
|
|
251
285
|
/** JSON Schema for tool input */
|
|
252
286
|
inputSchema: Record<string, unknown>;
|
|
253
287
|
|
|
288
|
+
/**
|
|
289
|
+
* JSON Schema for the tool's structured output (MCP SDK 1.27).
|
|
290
|
+
* Passed through in tools/list so hosts can validate tool results.
|
|
291
|
+
*/
|
|
292
|
+
outputSchema?: Record<string, unknown>;
|
|
293
|
+
|
|
294
|
+
/** Behavioural hints passed to model clients */
|
|
295
|
+
annotations?: ToolAnnotations;
|
|
296
|
+
|
|
254
297
|
/**
|
|
255
298
|
* Tool metadata including UI configuration for MCP Apps
|
|
256
299
|
* @see McpUiToolMeta
|
|
@@ -289,6 +332,32 @@ export type ToolHandler = (
|
|
|
289
332
|
args: Record<string, unknown>,
|
|
290
333
|
) => Promise<unknown> | unknown;
|
|
291
334
|
|
|
335
|
+
/**
|
|
336
|
+
* Structured tool result: separates the LLM text summary (content)
|
|
337
|
+
* from the machine-readable payload (structuredContent).
|
|
338
|
+
*
|
|
339
|
+
* When a ToolHandler returns this shape, the framework produces a
|
|
340
|
+
* CallToolResult with both `content` and `structuredContent` set,
|
|
341
|
+
* keeping heavy data out of the LLM context.
|
|
342
|
+
*/
|
|
343
|
+
export interface StructuredToolResult {
|
|
344
|
+
/** Human-readable summary shown in content[0].text */
|
|
345
|
+
content: string;
|
|
346
|
+
/** Structured data conforming to the tool's outputSchema */
|
|
347
|
+
structuredContent: Record<string, unknown>;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Maps a thrown error to either a business error result (isError: true)
|
|
352
|
+
* or signals that the error should be re-thrown as a JSON-RPC error.
|
|
353
|
+
*
|
|
354
|
+
* @returns A message string to produce `{ isError: true }`, or null to rethrow.
|
|
355
|
+
*/
|
|
356
|
+
export type ToolErrorMapper = (
|
|
357
|
+
error: unknown,
|
|
358
|
+
toolName: string,
|
|
359
|
+
) => string | null;
|
|
360
|
+
|
|
292
361
|
/**
|
|
293
362
|
* Sampling client interface for bidirectional LLM delegation
|
|
294
363
|
* Compatible with the agentic sampling protocol (SEP-1577)
|