@ttoss/http-server-mcp 0.34.5 → 0.36.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/README.md +27 -0
- package/dist/index.cjs +147 -0
- package/dist/index.d.cts +137 -1
- package/dist/index.d.mts +137 -1
- package/dist/index.mjs +143 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1130,6 +1130,33 @@ Either way the authorization flow works. Driving the official MCP client SDK aga
|
|
|
1130
1130
|
|
|
1131
1131
|
**Whether the handshake should be public at all is a separate question.** `publicMethods: []` closes both eras, and the authorization flow still works — RFC 9728 discovery is driven by the challenge itself, so a client authenticates from its very first request instead of one step later.
|
|
1132
1132
|
|
|
1133
|
+
### Holding the surface to a budget
|
|
1134
|
+
|
|
1135
|
+
Every tool's name, description and input schema, and the server's instructions, are loaded into every session before the first call. `checkMcpSurface` compares them with a budget and answers the limits they exceed, so a test fails naming the tool that grew:
|
|
1136
|
+
|
|
1137
|
+
```typescript
|
|
1138
|
+
import {
|
|
1139
|
+
checkMcpSurface,
|
|
1140
|
+
findUnknownToolMentions,
|
|
1141
|
+
} from '@ttoss/http-server-mcp';
|
|
1142
|
+
|
|
1143
|
+
test('the surface stays inside its budget', () => {
|
|
1144
|
+
expect(
|
|
1145
|
+
checkMcpSurface({
|
|
1146
|
+
tools, // { name, description?, inputSchema? }[], e.g. from openApiToToolDefinitions
|
|
1147
|
+
instructions,
|
|
1148
|
+
budget: { total: 70_000, perTool: 10_000 },
|
|
1149
|
+
})
|
|
1150
|
+
).toEqual([]);
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
test('every tool the instructions name exists', () => {
|
|
1154
|
+
expect(findUnknownToolMentions({ instructions, tools })).toEqual([]);
|
|
1155
|
+
});
|
|
1156
|
+
```
|
|
1157
|
+
|
|
1158
|
+
Sizes are characters of `JSON.stringify({ name, description, inputSchema })`; `measureMcpSurface` answers them without judging. `total` and `perTool` have no default — they are the product's choice. `description` (each tool's description alone) and `instructions` default to `2048`, where [Claude Code truncates both](https://code.claude.com/docs/en/mcp) without saying so; a schema is never cut, which is why `perTool` cannot stand in for `description`. `findUnknownToolMentions` reads backticked names with a `-` or `_` (`` `get-next` ``); pass `pattern` when the instructions name tools another way.
|
|
1159
|
+
|
|
1133
1160
|
## AWS Lambda Deployment
|
|
1134
1161
|
|
|
1135
1162
|
The default **stateless** mode (see [Stateless vs stateful mode](#stateless-vs-stateful-mode)) is built for serverless: a fresh transport is created per request, nothing is kept in memory between invocations, and responses are plain JSON (no SSE) — exactly the request/response shape API Gateway and Lambda Function URLs expect.
|
package/dist/index.cjs
CHANGED
|
@@ -212,6 +212,148 @@ var createMcpRequestServer = ({
|
|
|
212
212
|
};
|
|
213
213
|
};
|
|
214
214
|
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region src/mcpSurface.ts
|
|
217
|
+
/**
|
|
218
|
+
* Claude Code truncates a server's instructions at this many characters
|
|
219
|
+
* without telling anyone, so a rule past it never reaches an agent
|
|
220
|
+
* (https://code.claude.com/docs/en/mcp; a user can change it with
|
|
221
|
+
* `CLAUDE_CODE_MAX_MCP_DESCRIPTION_LENGTH`).
|
|
222
|
+
*/
|
|
223
|
+
var DEFAULT_INSTRUCTIONS_LIMIT = 2048;
|
|
224
|
+
/**
|
|
225
|
+
* Claude Code truncates each tool description at the same length as the
|
|
226
|
+
* instructions, from the same documentation, and just as silently.
|
|
227
|
+
*/
|
|
228
|
+
var DEFAULT_DESCRIPTION_LIMIT = 2048;
|
|
229
|
+
var sizeOf = tool => {
|
|
230
|
+
return JSON.stringify({
|
|
231
|
+
name: tool.name,
|
|
232
|
+
description: tool.description,
|
|
233
|
+
inputSchema: tool.inputSchema
|
|
234
|
+
}).length;
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* Measures what an MCP server costs every session that connects to it: each
|
|
238
|
+
* tool's name, description and input schema, and the instructions.
|
|
239
|
+
*
|
|
240
|
+
* It does not judge; {@link checkMcpSurface} compares the same numbers with a
|
|
241
|
+
* budget.
|
|
242
|
+
*/
|
|
243
|
+
var measureMcpSurface = ({
|
|
244
|
+
tools,
|
|
245
|
+
instructions
|
|
246
|
+
}) => {
|
|
247
|
+
const sizes = tools.map(tool => {
|
|
248
|
+
return {
|
|
249
|
+
name: tool.name,
|
|
250
|
+
size: sizeOf(tool)
|
|
251
|
+
};
|
|
252
|
+
}).sort((a, b) => {
|
|
253
|
+
return b.size - a.size;
|
|
254
|
+
});
|
|
255
|
+
return {
|
|
256
|
+
total: sizes.reduce((sum, entry) => {
|
|
257
|
+
return sum + entry.size;
|
|
258
|
+
}, 0),
|
|
259
|
+
tools: sizes,
|
|
260
|
+
instructions: instructions?.length ?? 0
|
|
261
|
+
};
|
|
262
|
+
};
|
|
263
|
+
var checkDescriptions = ({
|
|
264
|
+
tools,
|
|
265
|
+
limit
|
|
266
|
+
}) => {
|
|
267
|
+
return tools.flatMap(tool => {
|
|
268
|
+
const size = tool.description?.length ?? 0;
|
|
269
|
+
return size > limit ? [{
|
|
270
|
+
kind: "description",
|
|
271
|
+
name: tool.name,
|
|
272
|
+
size,
|
|
273
|
+
limit
|
|
274
|
+
}] : [];
|
|
275
|
+
});
|
|
276
|
+
};
|
|
277
|
+
/**
|
|
278
|
+
* Every limit an MCP surface exceeds, or `[]` when it fits.
|
|
279
|
+
*
|
|
280
|
+
* Returns rather than throws, so a test's `expect(...).toEqual([])` prints
|
|
281
|
+
* which tool went over and by how much. Meant for a test, not for boot: a
|
|
282
|
+
* server refusing to start over a few hundred bytes is worse than a red test.
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* ```typescript
|
|
286
|
+
* expect(
|
|
287
|
+
* checkMcpSurface({
|
|
288
|
+
* tools,
|
|
289
|
+
* instructions,
|
|
290
|
+
* budget: { total: 70_000, perTool: 10_000 },
|
|
291
|
+
* })
|
|
292
|
+
* ).toEqual([]);
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
var checkMcpSurface = ({
|
|
296
|
+
tools,
|
|
297
|
+
instructions,
|
|
298
|
+
budget = {}
|
|
299
|
+
}) => {
|
|
300
|
+
const report = measureMcpSurface({
|
|
301
|
+
tools,
|
|
302
|
+
instructions
|
|
303
|
+
});
|
|
304
|
+
const violations = [];
|
|
305
|
+
if (budget.total !== void 0 && report.total > budget.total) violations.push({
|
|
306
|
+
kind: "total",
|
|
307
|
+
size: report.total,
|
|
308
|
+
limit: budget.total
|
|
309
|
+
});
|
|
310
|
+
if (budget.perTool !== void 0) {
|
|
311
|
+
for (const {
|
|
312
|
+
name,
|
|
313
|
+
size
|
|
314
|
+
} of report.tools) if (size > budget.perTool) violations.push({
|
|
315
|
+
kind: "per_tool",
|
|
316
|
+
name,
|
|
317
|
+
size,
|
|
318
|
+
limit: budget.perTool
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
violations.push(...checkDescriptions({
|
|
322
|
+
tools,
|
|
323
|
+
limit: budget.description ?? 2048
|
|
324
|
+
}));
|
|
325
|
+
const instructionsLimit = budget.instructions ?? 2048;
|
|
326
|
+
if (report.instructions > instructionsLimit) violations.push({
|
|
327
|
+
kind: "instructions",
|
|
328
|
+
size: report.instructions,
|
|
329
|
+
limit: instructionsLimit
|
|
330
|
+
});
|
|
331
|
+
return violations;
|
|
332
|
+
};
|
|
333
|
+
var TOOL_MENTION = /`([a-z][a-z0-9]*(?:[-_][a-z0-9]+)+)`/g;
|
|
334
|
+
/**
|
|
335
|
+
* The tool names the instructions mention that the surface does not have.
|
|
336
|
+
*
|
|
337
|
+
* Instructions are read once, at connect: a renamed tool they still name is
|
|
338
|
+
* one every connected agent is taught to call and then told is "not found".
|
|
339
|
+
* Pass `pattern` (a global RegExp whose first group is the name) when the
|
|
340
|
+
* instructions name tools some other way.
|
|
341
|
+
*/
|
|
342
|
+
var findUnknownToolMentions = ({
|
|
343
|
+
instructions,
|
|
344
|
+
tools,
|
|
345
|
+
pattern = TOOL_MENTION
|
|
346
|
+
}) => {
|
|
347
|
+
const known = new Set(tools.map(tool => {
|
|
348
|
+
return tool.name;
|
|
349
|
+
}));
|
|
350
|
+
return [...new Set([...instructions.matchAll(pattern)].map(([, name]) => {
|
|
351
|
+
return name;
|
|
352
|
+
}))].filter(name => {
|
|
353
|
+
return !known.has(name);
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
|
|
215
357
|
//#endregion
|
|
216
358
|
//#region src/registerAppResource.ts
|
|
217
359
|
/**
|
|
@@ -1571,6 +1713,8 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
1571
1713
|
};
|
|
1572
1714
|
|
|
1573
1715
|
//#endregion
|
|
1716
|
+
exports.DEFAULT_DESCRIPTION_LIMIT = DEFAULT_DESCRIPTION_LIMIT;
|
|
1717
|
+
exports.DEFAULT_INSTRUCTIONS_LIMIT = DEFAULT_INSTRUCTIONS_LIMIT;
|
|
1574
1718
|
Object.defineProperty(exports, 'InMemoryServerEventBus', {
|
|
1575
1719
|
enumerable: true,
|
|
1576
1720
|
get: function () {
|
|
@@ -1586,14 +1730,17 @@ Object.defineProperty(exports, 'McpServer', {
|
|
|
1586
1730
|
exports.UI_EXTENSION_ID = UI_EXTENSION_ID;
|
|
1587
1731
|
exports.UI_RESOURCE_MIME_TYPE = UI_RESOURCE_MIME_TYPE;
|
|
1588
1732
|
exports.apiCall = apiCall;
|
|
1733
|
+
exports.checkMcpSurface = checkMcpSurface;
|
|
1589
1734
|
exports.checkScopes = checkScopes;
|
|
1590
1735
|
exports.createMcpRouter = createMcpRouter;
|
|
1591
1736
|
exports.createToolGate = createToolGate;
|
|
1592
1737
|
exports.defineTool = defineTool;
|
|
1593
1738
|
exports.errorBodyMessage = errorBodyMessage;
|
|
1739
|
+
exports.findUnknownToolMentions = findUnknownToolMentions;
|
|
1594
1740
|
exports.getApiHeaders = getApiHeaders;
|
|
1595
1741
|
exports.getIdentity = getIdentity;
|
|
1596
1742
|
exports.getRequestLocale = getRequestLocale;
|
|
1743
|
+
exports.measureMcpSurface = measureMcpSurface;
|
|
1597
1744
|
exports.rankTools = rankTools;
|
|
1598
1745
|
exports.registerAppResource = registerAppResource;
|
|
1599
1746
|
exports.registerTools = registerTools;
|
package/dist/index.d.cts
CHANGED
|
@@ -886,6 +886,142 @@ interface DeferToolsOptions {
|
|
|
886
886
|
}) => boolean;
|
|
887
887
|
}
|
|
888
888
|
//#endregion
|
|
889
|
+
//#region src/mcpSurface.d.ts
|
|
890
|
+
/**
|
|
891
|
+
* What a client loads for one tool before the first call: the three fields a
|
|
892
|
+
* `tools/list` answers. `openApiToToolDefinitions` returns this shape, and so
|
|
893
|
+
* does a `tools/list` result.
|
|
894
|
+
*/
|
|
895
|
+
interface McpSurfaceTool {
|
|
896
|
+
name: string;
|
|
897
|
+
description?: string;
|
|
898
|
+
inputSchema?: unknown;
|
|
899
|
+
}
|
|
900
|
+
/** The size of each part of an MCP surface, in characters of JSON. */
|
|
901
|
+
interface McpSurfaceReport {
|
|
902
|
+
/** Sum of every tool's size. Instructions are not included. */
|
|
903
|
+
total: number;
|
|
904
|
+
/** One entry per tool, largest first. */
|
|
905
|
+
tools: Array<{
|
|
906
|
+
name: string;
|
|
907
|
+
size: number;
|
|
908
|
+
}>;
|
|
909
|
+
/** Length of the server instructions, or `0` when there are none. */
|
|
910
|
+
instructions: number;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* The limits a surface is checked against. `description` and `instructions`
|
|
914
|
+
* have defaults, because a client cuts them; `total` and `perTool` are a
|
|
915
|
+
* product's own choice, not a property of any client.
|
|
916
|
+
*/
|
|
917
|
+
interface McpSurfaceBudget {
|
|
918
|
+
/** Ceiling on the sum of every tool's size. */
|
|
919
|
+
total?: number;
|
|
920
|
+
/** Ceiling on any one tool's size. */
|
|
921
|
+
perTool?: number;
|
|
922
|
+
/**
|
|
923
|
+
* Ceiling on any one tool's description length. Unlike `perTool`, it leaves
|
|
924
|
+
* out the name and input schema, which no client cuts.
|
|
925
|
+
* @default 2048
|
|
926
|
+
*/
|
|
927
|
+
description?: number;
|
|
928
|
+
/**
|
|
929
|
+
* Ceiling on the instructions' length.
|
|
930
|
+
* @default 2048
|
|
931
|
+
*/
|
|
932
|
+
instructions?: number;
|
|
933
|
+
}
|
|
934
|
+
/** One limit a surface exceeds. */
|
|
935
|
+
type McpSurfaceViolation = {
|
|
936
|
+
kind: 'total';
|
|
937
|
+
size: number;
|
|
938
|
+
limit: number;
|
|
939
|
+
} | {
|
|
940
|
+
kind: 'per_tool';
|
|
941
|
+
name: string;
|
|
942
|
+
size: number;
|
|
943
|
+
limit: number;
|
|
944
|
+
} | {
|
|
945
|
+
kind: 'description';
|
|
946
|
+
name: string;
|
|
947
|
+
size: number;
|
|
948
|
+
limit: number;
|
|
949
|
+
} | {
|
|
950
|
+
kind: 'instructions';
|
|
951
|
+
size: number;
|
|
952
|
+
limit: number;
|
|
953
|
+
};
|
|
954
|
+
/**
|
|
955
|
+
* Claude Code truncates a server's instructions at this many characters
|
|
956
|
+
* without telling anyone, so a rule past it never reaches an agent
|
|
957
|
+
* (https://code.claude.com/docs/en/mcp; a user can change it with
|
|
958
|
+
* `CLAUDE_CODE_MAX_MCP_DESCRIPTION_LENGTH`).
|
|
959
|
+
*/
|
|
960
|
+
declare const DEFAULT_INSTRUCTIONS_LIMIT = 2048;
|
|
961
|
+
/**
|
|
962
|
+
* Claude Code truncates each tool description at the same length as the
|
|
963
|
+
* instructions, from the same documentation, and just as silently.
|
|
964
|
+
*/
|
|
965
|
+
declare const DEFAULT_DESCRIPTION_LIMIT = 2048;
|
|
966
|
+
/**
|
|
967
|
+
* Measures what an MCP server costs every session that connects to it: each
|
|
968
|
+
* tool's name, description and input schema, and the instructions.
|
|
969
|
+
*
|
|
970
|
+
* It does not judge; {@link checkMcpSurface} compares the same numbers with a
|
|
971
|
+
* budget.
|
|
972
|
+
*/
|
|
973
|
+
declare const measureMcpSurface: ({
|
|
974
|
+
tools,
|
|
975
|
+
instructions
|
|
976
|
+
}: {
|
|
977
|
+
tools: McpSurfaceTool[];
|
|
978
|
+
instructions?: string;
|
|
979
|
+
}) => McpSurfaceReport;
|
|
980
|
+
/**
|
|
981
|
+
* Every limit an MCP surface exceeds, or `[]` when it fits.
|
|
982
|
+
*
|
|
983
|
+
* Returns rather than throws, so a test's `expect(...).toEqual([])` prints
|
|
984
|
+
* which tool went over and by how much. Meant for a test, not for boot: a
|
|
985
|
+
* server refusing to start over a few hundred bytes is worse than a red test.
|
|
986
|
+
*
|
|
987
|
+
* @example
|
|
988
|
+
* ```typescript
|
|
989
|
+
* expect(
|
|
990
|
+
* checkMcpSurface({
|
|
991
|
+
* tools,
|
|
992
|
+
* instructions,
|
|
993
|
+
* budget: { total: 70_000, perTool: 10_000 },
|
|
994
|
+
* })
|
|
995
|
+
* ).toEqual([]);
|
|
996
|
+
* ```
|
|
997
|
+
*/
|
|
998
|
+
declare const checkMcpSurface: ({
|
|
999
|
+
tools,
|
|
1000
|
+
instructions,
|
|
1001
|
+
budget
|
|
1002
|
+
}: {
|
|
1003
|
+
tools: McpSurfaceTool[];
|
|
1004
|
+
instructions?: string;
|
|
1005
|
+
budget?: McpSurfaceBudget;
|
|
1006
|
+
}) => McpSurfaceViolation[];
|
|
1007
|
+
/**
|
|
1008
|
+
* The tool names the instructions mention that the surface does not have.
|
|
1009
|
+
*
|
|
1010
|
+
* Instructions are read once, at connect: a renamed tool they still name is
|
|
1011
|
+
* one every connected agent is taught to call and then told is "not found".
|
|
1012
|
+
* Pass `pattern` (a global RegExp whose first group is the name) when the
|
|
1013
|
+
* instructions name tools some other way.
|
|
1014
|
+
*/
|
|
1015
|
+
declare const findUnknownToolMentions: ({
|
|
1016
|
+
instructions,
|
|
1017
|
+
tools,
|
|
1018
|
+
pattern
|
|
1019
|
+
}: {
|
|
1020
|
+
instructions: string;
|
|
1021
|
+
tools: Array<Pick<McpSurfaceTool, "name">>;
|
|
1022
|
+
pattern?: RegExp;
|
|
1023
|
+
}) => string[];
|
|
1024
|
+
//#endregion
|
|
889
1025
|
//#region src/registerAppResource.d.ts
|
|
890
1026
|
/**
|
|
891
1027
|
* Identifier of the MCP Apps extension (`io.modelcontextprotocol/ui`), used as
|
|
@@ -1711,4 +1847,4 @@ interface McpRouterOptions {
|
|
|
1711
1847
|
*/
|
|
1712
1848
|
declare const createMcpRouter: (server: McpServer$1, options?: McpRouterOptions) => McpRouter;
|
|
1713
1849
|
//#endregion
|
|
1714
|
-
export { ApiCallOptions, CognitoUserPoolConfig, type CreateToolGateOptions, type DeferToolsOptions, type DefineToolParams, type GateToolParams, InMemoryServerEventBus, type JsonObjectSchema, McpAuthOptions, McpRouter, McpRouterOptions, McpServer, type RegisterAppResourceParams, type RegisterToolsParams, type RegisteredAppResource, type ServerEvent, type ServerEventBus, type ServerNotifier, type Tool, type ToolCallContext, type ToolCallGate, type ToolGate, type ToolIdentity, type ToolMetaParams, type ToolSchema, type ToolSearchArgs, UI_EXTENSION_ID, UI_RESOURCE_MIME_TYPE, type UiResourceCsp, type UiResourceMeta, type UiResourcePermissions, type UiToolVisibility, apiCall, checkScopes, createMcpRouter, createToolGate, defineTool, errorBodyMessage, getApiHeaders, getIdentity, getRequestLocale, rankTools, registerAppResource, registerTools, z };
|
|
1850
|
+
export { ApiCallOptions, CognitoUserPoolConfig, type CreateToolGateOptions, DEFAULT_DESCRIPTION_LIMIT, DEFAULT_INSTRUCTIONS_LIMIT, type DeferToolsOptions, type DefineToolParams, type GateToolParams, InMemoryServerEventBus, type JsonObjectSchema, McpAuthOptions, McpRouter, McpRouterOptions, McpServer, type McpSurfaceBudget, type McpSurfaceReport, type McpSurfaceTool, type McpSurfaceViolation, type RegisterAppResourceParams, type RegisterToolsParams, type RegisteredAppResource, type ServerEvent, type ServerEventBus, type ServerNotifier, type Tool, type ToolCallContext, type ToolCallGate, type ToolGate, type ToolIdentity, type ToolMetaParams, type ToolSchema, type ToolSearchArgs, UI_EXTENSION_ID, UI_RESOURCE_MIME_TYPE, type UiResourceCsp, type UiResourceMeta, type UiResourcePermissions, type UiToolVisibility, apiCall, checkMcpSurface, checkScopes, createMcpRouter, createToolGate, defineTool, errorBodyMessage, findUnknownToolMentions, getApiHeaders, getIdentity, getRequestLocale, measureMcpSurface, rankTools, registerAppResource, registerTools, z };
|
package/dist/index.d.mts
CHANGED
|
@@ -887,6 +887,142 @@ interface DeferToolsOptions {
|
|
|
887
887
|
}) => boolean;
|
|
888
888
|
}
|
|
889
889
|
//#endregion
|
|
890
|
+
//#region src/mcpSurface.d.ts
|
|
891
|
+
/**
|
|
892
|
+
* What a client loads for one tool before the first call: the three fields a
|
|
893
|
+
* `tools/list` answers. `openApiToToolDefinitions` returns this shape, and so
|
|
894
|
+
* does a `tools/list` result.
|
|
895
|
+
*/
|
|
896
|
+
interface McpSurfaceTool {
|
|
897
|
+
name: string;
|
|
898
|
+
description?: string;
|
|
899
|
+
inputSchema?: unknown;
|
|
900
|
+
}
|
|
901
|
+
/** The size of each part of an MCP surface, in characters of JSON. */
|
|
902
|
+
interface McpSurfaceReport {
|
|
903
|
+
/** Sum of every tool's size. Instructions are not included. */
|
|
904
|
+
total: number;
|
|
905
|
+
/** One entry per tool, largest first. */
|
|
906
|
+
tools: Array<{
|
|
907
|
+
name: string;
|
|
908
|
+
size: number;
|
|
909
|
+
}>;
|
|
910
|
+
/** Length of the server instructions, or `0` when there are none. */
|
|
911
|
+
instructions: number;
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* The limits a surface is checked against. `description` and `instructions`
|
|
915
|
+
* have defaults, because a client cuts them; `total` and `perTool` are a
|
|
916
|
+
* product's own choice, not a property of any client.
|
|
917
|
+
*/
|
|
918
|
+
interface McpSurfaceBudget {
|
|
919
|
+
/** Ceiling on the sum of every tool's size. */
|
|
920
|
+
total?: number;
|
|
921
|
+
/** Ceiling on any one tool's size. */
|
|
922
|
+
perTool?: number;
|
|
923
|
+
/**
|
|
924
|
+
* Ceiling on any one tool's description length. Unlike `perTool`, it leaves
|
|
925
|
+
* out the name and input schema, which no client cuts.
|
|
926
|
+
* @default 2048
|
|
927
|
+
*/
|
|
928
|
+
description?: number;
|
|
929
|
+
/**
|
|
930
|
+
* Ceiling on the instructions' length.
|
|
931
|
+
* @default 2048
|
|
932
|
+
*/
|
|
933
|
+
instructions?: number;
|
|
934
|
+
}
|
|
935
|
+
/** One limit a surface exceeds. */
|
|
936
|
+
type McpSurfaceViolation = {
|
|
937
|
+
kind: 'total';
|
|
938
|
+
size: number;
|
|
939
|
+
limit: number;
|
|
940
|
+
} | {
|
|
941
|
+
kind: 'per_tool';
|
|
942
|
+
name: string;
|
|
943
|
+
size: number;
|
|
944
|
+
limit: number;
|
|
945
|
+
} | {
|
|
946
|
+
kind: 'description';
|
|
947
|
+
name: string;
|
|
948
|
+
size: number;
|
|
949
|
+
limit: number;
|
|
950
|
+
} | {
|
|
951
|
+
kind: 'instructions';
|
|
952
|
+
size: number;
|
|
953
|
+
limit: number;
|
|
954
|
+
};
|
|
955
|
+
/**
|
|
956
|
+
* Claude Code truncates a server's instructions at this many characters
|
|
957
|
+
* without telling anyone, so a rule past it never reaches an agent
|
|
958
|
+
* (https://code.claude.com/docs/en/mcp; a user can change it with
|
|
959
|
+
* `CLAUDE_CODE_MAX_MCP_DESCRIPTION_LENGTH`).
|
|
960
|
+
*/
|
|
961
|
+
declare const DEFAULT_INSTRUCTIONS_LIMIT = 2048;
|
|
962
|
+
/**
|
|
963
|
+
* Claude Code truncates each tool description at the same length as the
|
|
964
|
+
* instructions, from the same documentation, and just as silently.
|
|
965
|
+
*/
|
|
966
|
+
declare const DEFAULT_DESCRIPTION_LIMIT = 2048;
|
|
967
|
+
/**
|
|
968
|
+
* Measures what an MCP server costs every session that connects to it: each
|
|
969
|
+
* tool's name, description and input schema, and the instructions.
|
|
970
|
+
*
|
|
971
|
+
* It does not judge; {@link checkMcpSurface} compares the same numbers with a
|
|
972
|
+
* budget.
|
|
973
|
+
*/
|
|
974
|
+
declare const measureMcpSurface: ({
|
|
975
|
+
tools,
|
|
976
|
+
instructions
|
|
977
|
+
}: {
|
|
978
|
+
tools: McpSurfaceTool[];
|
|
979
|
+
instructions?: string;
|
|
980
|
+
}) => McpSurfaceReport;
|
|
981
|
+
/**
|
|
982
|
+
* Every limit an MCP surface exceeds, or `[]` when it fits.
|
|
983
|
+
*
|
|
984
|
+
* Returns rather than throws, so a test's `expect(...).toEqual([])` prints
|
|
985
|
+
* which tool went over and by how much. Meant for a test, not for boot: a
|
|
986
|
+
* server refusing to start over a few hundred bytes is worse than a red test.
|
|
987
|
+
*
|
|
988
|
+
* @example
|
|
989
|
+
* ```typescript
|
|
990
|
+
* expect(
|
|
991
|
+
* checkMcpSurface({
|
|
992
|
+
* tools,
|
|
993
|
+
* instructions,
|
|
994
|
+
* budget: { total: 70_000, perTool: 10_000 },
|
|
995
|
+
* })
|
|
996
|
+
* ).toEqual([]);
|
|
997
|
+
* ```
|
|
998
|
+
*/
|
|
999
|
+
declare const checkMcpSurface: ({
|
|
1000
|
+
tools,
|
|
1001
|
+
instructions,
|
|
1002
|
+
budget
|
|
1003
|
+
}: {
|
|
1004
|
+
tools: McpSurfaceTool[];
|
|
1005
|
+
instructions?: string;
|
|
1006
|
+
budget?: McpSurfaceBudget;
|
|
1007
|
+
}) => McpSurfaceViolation[];
|
|
1008
|
+
/**
|
|
1009
|
+
* The tool names the instructions mention that the surface does not have.
|
|
1010
|
+
*
|
|
1011
|
+
* Instructions are read once, at connect: a renamed tool they still name is
|
|
1012
|
+
* one every connected agent is taught to call and then told is "not found".
|
|
1013
|
+
* Pass `pattern` (a global RegExp whose first group is the name) when the
|
|
1014
|
+
* instructions name tools some other way.
|
|
1015
|
+
*/
|
|
1016
|
+
declare const findUnknownToolMentions: ({
|
|
1017
|
+
instructions,
|
|
1018
|
+
tools,
|
|
1019
|
+
pattern
|
|
1020
|
+
}: {
|
|
1021
|
+
instructions: string;
|
|
1022
|
+
tools: Array<Pick<McpSurfaceTool, "name">>;
|
|
1023
|
+
pattern?: RegExp;
|
|
1024
|
+
}) => string[];
|
|
1025
|
+
//#endregion
|
|
890
1026
|
//#region src/registerAppResource.d.ts
|
|
891
1027
|
/**
|
|
892
1028
|
* Identifier of the MCP Apps extension (`io.modelcontextprotocol/ui`), used as
|
|
@@ -1712,4 +1848,4 @@ interface McpRouterOptions {
|
|
|
1712
1848
|
*/
|
|
1713
1849
|
declare const createMcpRouter: (server: McpServer$1, options?: McpRouterOptions) => McpRouter;
|
|
1714
1850
|
//#endregion
|
|
1715
|
-
export { ApiCallOptions, CognitoUserPoolConfig, type CreateToolGateOptions, type DeferToolsOptions, type DefineToolParams, type GateToolParams, InMemoryServerEventBus, type JsonObjectSchema, McpAuthOptions, McpRouter, McpRouterOptions, McpServer, type RegisterAppResourceParams, type RegisterToolsParams, type RegisteredAppResource, type ServerEvent, type ServerEventBus, type ServerNotifier, type Tool, type ToolCallContext, type ToolCallGate, type ToolGate, type ToolIdentity, type ToolMetaParams, type ToolSchema, type ToolSearchArgs, UI_EXTENSION_ID, UI_RESOURCE_MIME_TYPE, type UiResourceCsp, type UiResourceMeta, type UiResourcePermissions, type UiToolVisibility, apiCall, checkScopes, createMcpRouter, createToolGate, defineTool, errorBodyMessage, getApiHeaders, getIdentity, getRequestLocale, rankTools, registerAppResource, registerTools, z };
|
|
1851
|
+
export { ApiCallOptions, CognitoUserPoolConfig, type CreateToolGateOptions, DEFAULT_DESCRIPTION_LIMIT, DEFAULT_INSTRUCTIONS_LIMIT, type DeferToolsOptions, type DefineToolParams, type GateToolParams, InMemoryServerEventBus, type JsonObjectSchema, McpAuthOptions, McpRouter, McpRouterOptions, McpServer, type McpSurfaceBudget, type McpSurfaceReport, type McpSurfaceTool, type McpSurfaceViolation, type RegisterAppResourceParams, type RegisterToolsParams, type RegisteredAppResource, type ServerEvent, type ServerEventBus, type ServerNotifier, type Tool, type ToolCallContext, type ToolCallGate, type ToolGate, type ToolIdentity, type ToolMetaParams, type ToolSchema, type ToolSearchArgs, UI_EXTENSION_ID, UI_RESOURCE_MIME_TYPE, type UiResourceCsp, type UiResourceMeta, type UiResourcePermissions, type UiToolVisibility, apiCall, checkMcpSurface, checkScopes, createMcpRouter, createToolGate, defineTool, errorBodyMessage, findUnknownToolMentions, getApiHeaders, getIdentity, getRequestLocale, measureMcpSurface, rankTools, registerAppResource, registerTools, z };
|
package/dist/index.mjs
CHANGED
|
@@ -209,6 +209,148 @@ var createMcpRequestServer = ({
|
|
|
209
209
|
};
|
|
210
210
|
};
|
|
211
211
|
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/mcpSurface.ts
|
|
214
|
+
/**
|
|
215
|
+
* Claude Code truncates a server's instructions at this many characters
|
|
216
|
+
* without telling anyone, so a rule past it never reaches an agent
|
|
217
|
+
* (https://code.claude.com/docs/en/mcp; a user can change it with
|
|
218
|
+
* `CLAUDE_CODE_MAX_MCP_DESCRIPTION_LENGTH`).
|
|
219
|
+
*/
|
|
220
|
+
var DEFAULT_INSTRUCTIONS_LIMIT = 2048;
|
|
221
|
+
/**
|
|
222
|
+
* Claude Code truncates each tool description at the same length as the
|
|
223
|
+
* instructions, from the same documentation, and just as silently.
|
|
224
|
+
*/
|
|
225
|
+
var DEFAULT_DESCRIPTION_LIMIT = 2048;
|
|
226
|
+
var sizeOf = tool => {
|
|
227
|
+
return JSON.stringify({
|
|
228
|
+
name: tool.name,
|
|
229
|
+
description: tool.description,
|
|
230
|
+
inputSchema: tool.inputSchema
|
|
231
|
+
}).length;
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* Measures what an MCP server costs every session that connects to it: each
|
|
235
|
+
* tool's name, description and input schema, and the instructions.
|
|
236
|
+
*
|
|
237
|
+
* It does not judge; {@link checkMcpSurface} compares the same numbers with a
|
|
238
|
+
* budget.
|
|
239
|
+
*/
|
|
240
|
+
var measureMcpSurface = ({
|
|
241
|
+
tools,
|
|
242
|
+
instructions
|
|
243
|
+
}) => {
|
|
244
|
+
const sizes = tools.map(tool => {
|
|
245
|
+
return {
|
|
246
|
+
name: tool.name,
|
|
247
|
+
size: sizeOf(tool)
|
|
248
|
+
};
|
|
249
|
+
}).sort((a, b) => {
|
|
250
|
+
return b.size - a.size;
|
|
251
|
+
});
|
|
252
|
+
return {
|
|
253
|
+
total: sizes.reduce((sum, entry) => {
|
|
254
|
+
return sum + entry.size;
|
|
255
|
+
}, 0),
|
|
256
|
+
tools: sizes,
|
|
257
|
+
instructions: instructions?.length ?? 0
|
|
258
|
+
};
|
|
259
|
+
};
|
|
260
|
+
var checkDescriptions = ({
|
|
261
|
+
tools,
|
|
262
|
+
limit
|
|
263
|
+
}) => {
|
|
264
|
+
return tools.flatMap(tool => {
|
|
265
|
+
const size = tool.description?.length ?? 0;
|
|
266
|
+
return size > limit ? [{
|
|
267
|
+
kind: "description",
|
|
268
|
+
name: tool.name,
|
|
269
|
+
size,
|
|
270
|
+
limit
|
|
271
|
+
}] : [];
|
|
272
|
+
});
|
|
273
|
+
};
|
|
274
|
+
/**
|
|
275
|
+
* Every limit an MCP surface exceeds, or `[]` when it fits.
|
|
276
|
+
*
|
|
277
|
+
* Returns rather than throws, so a test's `expect(...).toEqual([])` prints
|
|
278
|
+
* which tool went over and by how much. Meant for a test, not for boot: a
|
|
279
|
+
* server refusing to start over a few hundred bytes is worse than a red test.
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* ```typescript
|
|
283
|
+
* expect(
|
|
284
|
+
* checkMcpSurface({
|
|
285
|
+
* tools,
|
|
286
|
+
* instructions,
|
|
287
|
+
* budget: { total: 70_000, perTool: 10_000 },
|
|
288
|
+
* })
|
|
289
|
+
* ).toEqual([]);
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
var checkMcpSurface = ({
|
|
293
|
+
tools,
|
|
294
|
+
instructions,
|
|
295
|
+
budget = {}
|
|
296
|
+
}) => {
|
|
297
|
+
const report = measureMcpSurface({
|
|
298
|
+
tools,
|
|
299
|
+
instructions
|
|
300
|
+
});
|
|
301
|
+
const violations = [];
|
|
302
|
+
if (budget.total !== void 0 && report.total > budget.total) violations.push({
|
|
303
|
+
kind: "total",
|
|
304
|
+
size: report.total,
|
|
305
|
+
limit: budget.total
|
|
306
|
+
});
|
|
307
|
+
if (budget.perTool !== void 0) {
|
|
308
|
+
for (const {
|
|
309
|
+
name,
|
|
310
|
+
size
|
|
311
|
+
} of report.tools) if (size > budget.perTool) violations.push({
|
|
312
|
+
kind: "per_tool",
|
|
313
|
+
name,
|
|
314
|
+
size,
|
|
315
|
+
limit: budget.perTool
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
violations.push(...checkDescriptions({
|
|
319
|
+
tools,
|
|
320
|
+
limit: budget.description ?? 2048
|
|
321
|
+
}));
|
|
322
|
+
const instructionsLimit = budget.instructions ?? 2048;
|
|
323
|
+
if (report.instructions > instructionsLimit) violations.push({
|
|
324
|
+
kind: "instructions",
|
|
325
|
+
size: report.instructions,
|
|
326
|
+
limit: instructionsLimit
|
|
327
|
+
});
|
|
328
|
+
return violations;
|
|
329
|
+
};
|
|
330
|
+
var TOOL_MENTION = /`([a-z][a-z0-9]*(?:[-_][a-z0-9]+)+)`/g;
|
|
331
|
+
/**
|
|
332
|
+
* The tool names the instructions mention that the surface does not have.
|
|
333
|
+
*
|
|
334
|
+
* Instructions are read once, at connect: a renamed tool they still name is
|
|
335
|
+
* one every connected agent is taught to call and then told is "not found".
|
|
336
|
+
* Pass `pattern` (a global RegExp whose first group is the name) when the
|
|
337
|
+
* instructions name tools some other way.
|
|
338
|
+
*/
|
|
339
|
+
var findUnknownToolMentions = ({
|
|
340
|
+
instructions,
|
|
341
|
+
tools,
|
|
342
|
+
pattern = TOOL_MENTION
|
|
343
|
+
}) => {
|
|
344
|
+
const known = new Set(tools.map(tool => {
|
|
345
|
+
return tool.name;
|
|
346
|
+
}));
|
|
347
|
+
return [...new Set([...instructions.matchAll(pattern)].map(([, name]) => {
|
|
348
|
+
return name;
|
|
349
|
+
}))].filter(name => {
|
|
350
|
+
return !known.has(name);
|
|
351
|
+
});
|
|
352
|
+
};
|
|
353
|
+
|
|
212
354
|
//#endregion
|
|
213
355
|
//#region src/registerAppResource.ts
|
|
214
356
|
/**
|
|
@@ -1568,4 +1710,4 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
1568
1710
|
};
|
|
1569
1711
|
|
|
1570
1712
|
//#endregion
|
|
1571
|
-
export { InMemoryServerEventBus, McpServer, UI_EXTENSION_ID, UI_RESOURCE_MIME_TYPE, apiCall, checkScopes, createMcpRouter, createToolGate, defineTool, errorBodyMessage, getApiHeaders, getIdentity, getRequestLocale, rankTools, registerAppResource, registerTools, z };
|
|
1713
|
+
export { DEFAULT_DESCRIPTION_LIMIT, DEFAULT_INSTRUCTIONS_LIMIT, InMemoryServerEventBus, McpServer, UI_EXTENSION_ID, UI_RESOURCE_MIME_TYPE, apiCall, checkMcpSurface, checkScopes, createMcpRouter, createToolGate, defineTool, errorBodyMessage, findUnknownToolMentions, getApiHeaders, getIdentity, getRequestLocale, measureMcpSurface, rankTools, registerAppResource, registerTools, z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttoss/http-server-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server integration for @ttoss/http-server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"zod": "^4.4.3",
|
|
40
40
|
"@ttoss/auth-core": "^0.17.2",
|
|
41
41
|
"@ttoss/i18n-core": "^0.8.1",
|
|
42
|
-
"@ttoss/http-server": "^0.
|
|
43
|
-
"@ttoss/http-server-auth": "^0.8.
|
|
42
|
+
"@ttoss/http-server": "^0.13.0",
|
|
43
|
+
"@ttoss/http-server-auth": "^0.8.5"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@modelcontextprotocol/client": "^2.0.0",
|