@warlock.js/ai-tools 5.32.0 → 5.34.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 +25 -103
- package/cjs/index.cjs +403 -131
- package/cjs/index.cjs.map +1 -1
- package/esm/contracts/index.d.mts +2 -2
- package/esm/contracts/mcp-serve.type.d.mts +85 -8
- package/esm/contracts/mcp-serve.type.d.mts.map +1 -1
- package/esm/index.d.mts +3 -2
- package/esm/index.mjs +2 -1
- package/esm/mcp/error.d.mts +9 -0
- package/esm/mcp/error.d.mts.map +1 -0
- package/esm/mcp/error.mjs +24 -0
- package/esm/mcp/error.mjs.map +1 -0
- package/esm/mcp/index.d.mts +2 -1
- package/esm/mcp/index.d.mts.map +1 -1
- package/esm/mcp/index.mjs +2 -1
- package/esm/mcp/index.mjs.map +1 -1
- package/esm/mcp/serve-http.mjs +134 -29
- package/esm/mcp/serve-http.mjs.map +1 -1
- package/esm/mcp/serve-requests.mjs +62 -0
- package/esm/mcp/serve-requests.mjs.map +1 -0
- package/esm/mcp/serve.d.mts.map +1 -1
- package/esm/mcp/serve.mjs +105 -15
- package/esm/mcp/serve.mjs.map +1 -1
- package/esm/register.d.mts +13 -52
- package/esm/register.d.mts.map +1 -1
- package/esm/register.mjs +4 -5
- package/esm/register.mjs.map +1 -1
- package/llms-full.txt +190 -2
- package/llms.txt +1 -1
- package/package.json +2 -2
- package/skills/connect-mcp-server/SKILL.md +15 -0
- package/skills/expose-as-mcp-server/SKILL.md +154 -2
- package/skills/use-web-and-http-tools/SKILL.md +21 -0
|
@@ -2,5 +2,5 @@ import { FetchUrlExtract, FetchUrlInput, FetchUrlOptions, FetchUrlResult, Search
|
|
|
2
2
|
import { HttpMethod, HttpRequestInput, HttpRequestOptions, HttpRequestResult } from "./http.type.mjs";
|
|
3
3
|
import { CalculatorInput, CalculatorOptions, CalculatorResult, DateTimeInput, DateTimeOp, DateTimeOptions, DateTimeResult } from "./utility.type.mjs";
|
|
4
4
|
import { JsonRpcError, JsonRpcId, JsonRpcMessage, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, JsonRpcVersion, McpClient, McpClientOptions, McpContentBlock, McpToolCallResult, McpToolDescriptor, McpTransport } from "./mcp.type.mjs";
|
|
5
|
-
import { McpGroupedToolSource, McpHttpAuth, McpHttpRequestInfo, McpHttpServeHandle, McpHttpServeTransport, McpPromptArgument, McpPromptMessage, McpServeCallContext, McpServeHandle, McpServeOptions, McpServePrompt, McpServeResource, McpServeSource, McpServeTransport, McpServer, McpToolSource } from "./mcp-serve.type.mjs";
|
|
6
|
-
export type { CalculatorInput, CalculatorOptions, CalculatorResult, DateTimeInput, DateTimeOp, DateTimeOptions, DateTimeResult, FetchUrlExtract, FetchUrlInput, FetchUrlOptions, FetchUrlResult, HttpMethod, HttpRequestInput, HttpRequestOptions, HttpRequestResult, JsonRpcError, JsonRpcId, JsonRpcMessage, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, JsonRpcVersion, McpClient, McpClientOptions, McpContentBlock, McpGroupedToolSource, McpHttpAuth, McpHttpRequestInfo, McpHttpServeHandle, McpHttpServeTransport, McpPromptArgument, McpPromptMessage, McpServeCallContext, McpServeHandle, McpServeOptions, McpServePrompt, McpServeResource, McpServeSource, McpServeTransport, McpServer, McpToolCallResult, McpToolDescriptor, McpToolSource, McpTransport, SearchProvider, WebSearchInput, WebSearchOptions, WebSearchResult, WebSearchResultItem };
|
|
5
|
+
import { McpBeforeCall, McpCallRefusal, McpGroupedToolSource, McpHttpAuth, McpHttpRequestInfo, McpHttpRoute, McpHttpRouteContext, McpHttpRouteResponse, McpHttpServeHandle, McpHttpServeTransport, McpListFilter, McpPromptArgument, McpPromptMessage, McpServeCallContext, McpServeHandle, McpServeOptions, McpServePrompt, McpServeResource, McpServeSource, McpServeToolContext, McpServeTransport, McpServer, McpStdioServeTransport, McpToolSource, McpValidateCallParams } from "./mcp-serve.type.mjs";
|
|
6
|
+
export type { CalculatorInput, CalculatorOptions, CalculatorResult, DateTimeInput, DateTimeOp, DateTimeOptions, DateTimeResult, FetchUrlExtract, FetchUrlInput, FetchUrlOptions, FetchUrlResult, HttpMethod, HttpRequestInput, HttpRequestOptions, HttpRequestResult, JsonRpcError, JsonRpcId, JsonRpcMessage, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, JsonRpcVersion, McpBeforeCall, McpCallRefusal, McpClient, McpClientOptions, McpContentBlock, McpGroupedToolSource, McpHttpAuth, McpHttpRequestInfo, McpHttpRoute, McpHttpRouteContext, McpHttpRouteResponse, McpHttpServeHandle, McpHttpServeTransport, McpListFilter, McpPromptArgument, McpPromptMessage, McpServeCallContext, McpServeHandle, McpServeOptions, McpServePrompt, McpServeResource, McpServeSource, McpServeToolContext, McpServeTransport, McpServer, McpStdioServeTransport, McpToolCallResult, McpToolDescriptor, McpToolSource, McpTransport, McpValidateCallParams, SearchProvider, WebSearchInput, WebSearchOptions, WebSearchResult, WebSearchResultItem };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { McpContentBlock } from "./mcp.type.mjs";
|
|
1
|
+
import { JsonRpcId, McpContentBlock, McpToolCallResult, McpToolDescriptor } from "./mcp.type.mjs";
|
|
2
2
|
import { JsonSchemaTarget, ToolContract, ToolRequestContext } from "@warlock.js/ai";
|
|
3
3
|
//#region ../../packages/ai-tools/src/contracts/mcp-serve.type.d.ts
|
|
4
4
|
/**
|
|
@@ -46,8 +46,8 @@ export type McpHttpRequestInfo = ToolRequestContext & {
|
|
|
46
46
|
};
|
|
47
47
|
/**
|
|
48
48
|
* Authentication for an HTTP-served MCP endpoint. At least one of
|
|
49
|
-
* `bearer` / `authorize` is required;
|
|
50
|
-
* pass
|
|
49
|
+
* `bearer` / `authorize` / `authenticate` is required; all configured checks
|
|
50
|
+
* must pass, in that order. A request that fails gets HTTP 401 before its body is read.
|
|
51
51
|
*/
|
|
52
52
|
export type McpHttpAuth = {
|
|
53
53
|
/**
|
|
@@ -64,6 +64,40 @@ export type McpHttpAuth = {
|
|
|
64
64
|
* request (fails closed).
|
|
65
65
|
*/
|
|
66
66
|
authorize?: (request: McpHttpRequestInfo) => boolean | Promise<boolean>;
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the caller after Host/Origin, bearer and authorize checks.
|
|
69
|
+
* Undefined or a thrown/rejected error denies with 401 and a Bearer challenge.
|
|
70
|
+
* Any other value becomes context.principal (including null or false).
|
|
71
|
+
*/
|
|
72
|
+
authenticate?: (request: McpHttpRequestInfo) => unknown | Promise<unknown>;
|
|
73
|
+
};
|
|
74
|
+
/** Authenticated context for an extra HTTP route; there is no JSON-RPC id. */
|
|
75
|
+
export type McpHttpRouteContext = McpServeCallContext & {
|
|
76
|
+
request: McpHttpRequestInfo;
|
|
77
|
+
/** Parsed request URL; use searchParams to read query parameters. */
|
|
78
|
+
url: URL;
|
|
79
|
+
/** UTF-8 request body, subject to the transport's maxBodyBytes limit. */
|
|
80
|
+
body: string;
|
|
81
|
+
signal: AbortSignal;
|
|
82
|
+
};
|
|
83
|
+
/** JSON response from an extra HTTP route. Omit body for an empty response. */
|
|
84
|
+
export type McpHttpRouteResponse = {
|
|
85
|
+
/** @default 200 */
|
|
86
|
+
status?: number;
|
|
87
|
+
headers?: Record<string, string>;
|
|
88
|
+
body?: unknown;
|
|
89
|
+
};
|
|
90
|
+
/** An exact method/path route beneath the MCP endpoint, sharing its auth. */
|
|
91
|
+
export type McpHttpRoute = {
|
|
92
|
+
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
|
|
93
|
+
/**
|
|
94
|
+
* Suffix such as /tool-policy, appended to the MCP path without its trailing
|
|
95
|
+
* slash. Nonempty segments use letters, digits, _, -, . or ~; . and .. are
|
|
96
|
+
* forbidden. No query, fragment, escapes, wildcard or trailing slash.
|
|
97
|
+
* The MCP endpoint itself is reserved for every method. Duplicates fail start().
|
|
98
|
+
*/
|
|
99
|
+
path: string;
|
|
100
|
+
handler(context: McpHttpRouteContext): McpHttpRouteResponse | Promise<McpHttpRouteResponse>;
|
|
67
101
|
};
|
|
68
102
|
/**
|
|
69
103
|
* Serve over the MCP Streamable HTTP transport.
|
|
@@ -89,13 +123,15 @@ export type McpHttpServeTransport = {
|
|
|
89
123
|
*/
|
|
90
124
|
port?: number;
|
|
91
125
|
/**
|
|
92
|
-
* The
|
|
126
|
+
* The MCP endpoint path. Other paths answer 404 unless registered in routes.
|
|
93
127
|
*
|
|
94
128
|
* @default "/mcp"
|
|
95
129
|
*/
|
|
96
130
|
path?: string;
|
|
97
|
-
/** Bearer
|
|
131
|
+
/** Bearer, authorize and/or authenticate checks. Required off loopback. */
|
|
98
132
|
auth?: McpHttpAuth;
|
|
133
|
+
/** Extra HTTP routes with the same Host/Origin and authentication checks. */
|
|
134
|
+
routes?: readonly McpHttpRoute[];
|
|
99
135
|
/**
|
|
100
136
|
* Extra browser origins allowed to call the endpoint, as full origins
|
|
101
137
|
* (`"https://app.example.com"`). Loopback origins (`http://localhost:*`,
|
|
@@ -132,9 +168,15 @@ export type McpHttpServeTransport = {
|
|
|
132
168
|
* - `"stdio"` — serve JSON-RPC over this process's stdin/stdout.
|
|
133
169
|
* - `"http"` — serve MCP Streamable HTTP (see {@link McpHttpServeTransport}).
|
|
134
170
|
*/
|
|
135
|
-
export type McpServeTransport =
|
|
171
|
+
export type McpServeTransport = McpStdioServeTransport | McpHttpServeTransport;
|
|
172
|
+
/** Local caller identity for the stdio connection. */
|
|
173
|
+
export type McpStdioServeTransport = {
|
|
136
174
|
type: "stdio";
|
|
137
|
-
|
|
175
|
+
/** Static trusted identity; never read from tool arguments. */
|
|
176
|
+
principal?: unknown;
|
|
177
|
+
/** Per-request identity provider; takes precedence over principal when set. */
|
|
178
|
+
principalProvider?: (context: McpServeCallContext) => unknown | Promise<unknown>;
|
|
179
|
+
};
|
|
138
180
|
/**
|
|
139
181
|
* Per-call context handed to resource readers and prompt builders (tools
|
|
140
182
|
* receive the same data on their `ToolContext`).
|
|
@@ -142,9 +184,30 @@ export type McpServeTransport = {
|
|
|
142
184
|
export type McpServeCallContext = {
|
|
143
185
|
/** The inbound HTTP request; absent over stdio. */
|
|
144
186
|
request?: McpHttpRequestInfo;
|
|
145
|
-
/**
|
|
187
|
+
/** JSON-RPC request id; populated by the serving boundary. */
|
|
188
|
+
requestId?: JsonRpcId;
|
|
189
|
+
/** Caller identity from HTTP authenticate or the stdio transport options. */
|
|
190
|
+
principal?: unknown;
|
|
191
|
+
/** Aborted on cancellation, client disconnect or server shutdown. */
|
|
146
192
|
signal?: AbortSignal;
|
|
147
193
|
};
|
|
194
|
+
/** Context supplied to beforeCall after successful tool input validation. */
|
|
195
|
+
export type McpServeToolContext = McpServeCallContext & {
|
|
196
|
+
requestId: JsonRpcId;
|
|
197
|
+
};
|
|
198
|
+
/** Native MCP error result returned by a beforeCall policy hook. */
|
|
199
|
+
export type McpCallRefusal = McpToolCallResult & {
|
|
200
|
+
isError: true;
|
|
201
|
+
};
|
|
202
|
+
/** Validate policy before execute; a refusal bypasses result serialization. */
|
|
203
|
+
export type McpBeforeCall = (context: McpServeToolContext, tool: ToolContract, input: unknown) => void | McpCallRefusal | Promise<void | McpCallRefusal>;
|
|
204
|
+
/** Filter fresh wire descriptors for discovery only; hidden tools stay callable. */
|
|
205
|
+
export type McpListFilter = (context: McpServeToolContext, tools: readonly McpToolDescriptor[]) => readonly McpToolDescriptor[] | Promise<readonly McpToolDescriptor[]>;
|
|
206
|
+
/**
|
|
207
|
+
* Inspect the complete tools/call params before lookup, schema validation or
|
|
208
|
+
* beforeCall. Treat params as read-only; throw McpError for an app-defined error.
|
|
209
|
+
*/
|
|
210
|
+
export type McpValidateCallParams = (context: McpServeToolContext, params: Readonly<Record<string, unknown>>) => void | Promise<void>;
|
|
148
211
|
/**
|
|
149
212
|
* A static MCP resource served by `resources/list` + `resources/read`.
|
|
150
213
|
*/
|
|
@@ -236,6 +299,20 @@ export interface McpServeOptions {
|
|
|
236
299
|
* @default "text"
|
|
237
300
|
*/
|
|
238
301
|
results?: "text" | "native";
|
|
302
|
+
/**
|
|
303
|
+
* Runs after schema validation and before invocation. Input is the validated
|
|
304
|
+
* value and should be treated as read-only. Return an isError:true native
|
|
305
|
+
* result to refuse. McpError preserves its code/message/data; other throws
|
|
306
|
+
* answer with a JSON-RPC invalid-params error.
|
|
307
|
+
*/
|
|
308
|
+
beforeCall?: McpBeforeCall;
|
|
309
|
+
/** Per-request discovery filter; never changes tools/call lookup or policy. */
|
|
310
|
+
listFilter?: McpListFilter;
|
|
311
|
+
/**
|
|
312
|
+
* Optional envelope policy. Receives all params, including unknown fields,
|
|
313
|
+
* task and _meta. By default extra fields are ignored (no task capability).
|
|
314
|
+
*/
|
|
315
|
+
validateCallParams?: McpValidateCallParams;
|
|
239
316
|
/**
|
|
240
317
|
* Prefix each tool of a grouped source (`{ groups: { … } }`) with its
|
|
241
318
|
* group name. `true` exposes `<group>_<tool>`; a function builds the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-serve.type.d.mts","names":[],"sources":["../../../../../../../../packages/ai-tools/src/contracts/mcp-serve.type.ts"],"mappings":";;;;;;;;;;;;;YAcY;EAAkB,SAAS;IAAmB;;;;;;;;;YAU9C;;EAEV,QAAQ,eAAe;;;;;;;;YASb,iBAAiB,gBAAgB;;;;;;YAOjC,qBAAqB;;EAE/B;;EAEA;;;;;;;YAQU;;;;;;;;EAQV;;;;;;EAMA,aAAa,SAAS,iCAAiC;;;;;;;;
|
|
1
|
+
{"version":3,"file":"mcp-serve.type.d.mts","names":[],"sources":["../../../../../../../../packages/ai-tools/src/contracts/mcp-serve.type.ts"],"mappings":";;;;;;;;;;;;;YAcY;EAAkB,SAAS;IAAmB;;;;;;;;;YAU9C;;EAEV,QAAQ,eAAe;;;;;;;;YASb,iBAAiB,gBAAgB;;;;;;YAOjC,qBAAqB;;EAE/B;;EAEA;;;;;;;YAQU;;;;;;;;EAQV;;;;;;EAMA,aAAa,SAAS,iCAAiC;;;;;;EAMvD,gBAAgB,SAAS,iCAAiC;;;YAIhD,sBAAsB;EAChC,SAAS;;EAET,KAAK;;EAEL;EACA,QAAQ;;;YAIE;;EAEV;EACA,UAAU;EACV;;;YAIU;EACV;;;;;;;EAOA;EACA,QAAQ,SAAS,sBAAsB,uBAAuB,QAAQ;;;;;;;;YAS5D;;EAEV;;;;;;;EAOA;;;;;;;EAOA;;;;;;EAMA;;EAEA,OAAO;;EAEP,kBAAkB;;;;;;;;;EASlB;;;;;;;EAOA;;;;;;EAMA;;;;;;;EAOA;;;;;;;;YASU,oBAAoB,yBAAyB;;YAG7C;EACV;;EAEA;;EAEA,qBAAqB,SAAS,kCAAkC;;;;;;YAOtD;;EAEV,UAAU;;EAEV,YAAY;;EAEZ;;EAEA,SAAS;;;YAIC,sBAAsB;EAAwB,WAAW;;;YAGzD,iBAAiB;EAAsB;;;YAGvC,iBACV,SAAS,qBACT,MAAM,cACN,0BACU,iBAAiB,eAAe;;YAGhC,iBACV,SAAS,qBACT,gBAAgB,iCACJ,sBAAsB,iBAAiB;;;;;YAMzC,yBACV,SAAS,qBACT,QAAQ,SAAS,oCACP;;;;YAKA;;EAEV;;EAEA;;EAEA;;EAEA;;EAEA;;;;;EAKA,KAAK,SAAS,+BAA+B,aAAa,iBAAiB;;;YAIjE;;EAEV;;EAEA;;EAEA;;;YAIU;;EAEV;;EAEA,SAAS;;;;;YAMC;;EAEV;;EAEA;;EAEA;;EAEA,YAAY;;;;;;EAMZ,IACE,MAAM,wBACN,SAAS,+BACC,qBAAqB,iBAAiB;;;;;;iBAOnC;;EAEf;;;;;;EAMA;;;;;;EAMA,YAAY;;;;;;;;EAQZ,eAAe;;;;;;;;;EASf;;;;;;;EAOA,aAAa;;EAEb,aAAa;;;;;EAKb,qBAAqB;;;;;;;;EAQrB,8BAA8B,eAAe;;EAE7C,YAAY;;EAEZ,UAAU;;;;;;iBAOK;;EAEf;;;;;;EAMA,SAAS;;;iBAIM,2BAA2B;;EAE1C;;;;;iBAMe,UAAU,gBAAgB,iBAAiB;;;;;;EAM1D,SAAS,QAAQ;;EAEjB,QAAQ"}
|
package/esm/index.d.mts
CHANGED
|
@@ -2,10 +2,11 @@ import { FetchUrlExtract, FetchUrlInput, FetchUrlOptions, FetchUrlResult, Search
|
|
|
2
2
|
import { HttpMethod, HttpRequestInput, HttpRequestOptions, HttpRequestResult } from "./contracts/http.type.mjs";
|
|
3
3
|
import { CalculatorInput, CalculatorOptions, CalculatorResult, DateTimeInput, DateTimeOp, DateTimeOptions, DateTimeResult } from "./contracts/utility.type.mjs";
|
|
4
4
|
import { JsonRpcError, JsonRpcId, JsonRpcMessage, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, JsonRpcVersion, McpClient, McpClientOptions, McpContentBlock, McpToolCallResult, McpToolDescriptor, McpTransport } from "./contracts/mcp.type.mjs";
|
|
5
|
-
import { McpGroupedToolSource, McpHttpAuth, McpHttpRequestInfo, McpHttpServeHandle, McpHttpServeTransport, McpPromptArgument, McpPromptMessage, McpServeCallContext, McpServeHandle, McpServeOptions, McpServePrompt, McpServeResource, McpServeSource, McpServeTransport, McpServer, McpToolSource } from "./contracts/mcp-serve.type.mjs";
|
|
5
|
+
import { McpBeforeCall, McpCallRefusal, McpGroupedToolSource, McpHttpAuth, McpHttpRequestInfo, McpHttpRoute, McpHttpRouteContext, McpHttpRouteResponse, McpHttpServeHandle, McpHttpServeTransport, McpListFilter, McpPromptArgument, McpPromptMessage, McpServeCallContext, McpServeHandle, McpServeOptions, McpServePrompt, McpServeResource, McpServeSource, McpServeToolContext, McpServeTransport, McpServer, McpStdioServeTransport, McpToolSource, McpValidateCallParams } from "./contracts/mcp-serve.type.mjs";
|
|
6
6
|
import "./contracts/index.mjs";
|
|
7
7
|
import { McpServeRequestHandler, createServeHandler, serve } from "./mcp/serve.mjs";
|
|
8
8
|
import { SUPPORTED_PROTOCOL_VERSIONS } from "./mcp/protocol.mjs";
|
|
9
|
+
import { McpError } from "./mcp/error.mjs";
|
|
9
10
|
import { McpNativeResult, mcpResult } from "./mcp/result.mjs";
|
|
10
11
|
import { jsonSchemaToStandard } from "./mcp/json-schema-to-standard.mjs";
|
|
11
12
|
import { McpTransportClient } from "./mcp/transport.type.mjs";
|
|
@@ -20,6 +21,6 @@ import { calculatorTool } from "./utility/calculator.mjs";
|
|
|
20
21
|
import { dateTimeTool } from "./utility/date-time.mjs";
|
|
21
22
|
import "./utility/index.mjs";
|
|
22
23
|
import { CalculatorError, CalculatorErrorOptions, CalculatorFailure, DateTimeError, DateTimeErrorOptions, DateTimeFailure, HttpPolicyError, HttpPolicyErrorOptions, HttpPolicyFailure, McpServeError, McpServeErrorOptions, McpServeFailure, McpTransportError, McpTransportErrorOptions, McpTransportFailure, WebToolError, WebToolErrorOptions, WebToolFailure } from "./errors.mjs";
|
|
23
|
-
export { type AiToolsNamespace, CalculatorError, type CalculatorErrorOptions, type CalculatorFailure, type CalculatorInput, type CalculatorOptions, type CalculatorResult, DateTimeError, type DateTimeErrorOptions, type DateTimeFailure, type DateTimeInput, type DateTimeOp, type DateTimeOptions, type DateTimeResult, type FetchUrlExtract, type FetchUrlInput, type FetchUrlOptions, type FetchUrlResult, type HttpMethod, HttpPolicyError, type HttpPolicyErrorOptions, type HttpPolicyFailure, type HttpRequestInput, type HttpRequestOptions, type HttpRequestResult, type JsonRpcClientHandle, type JsonRpcError, type JsonRpcId, type JsonRpcMessage, type JsonRpcNotification, type JsonRpcRequest, type JsonRpcResponse, type JsonRpcVersion, type McpClient, type McpClientOptions, type McpContentBlock, type McpFactory, type McpGroupedToolSource, type McpHttpAuth, type McpHttpRequestInfo, type McpHttpServeHandle, type McpHttpServeTransport, type McpNativeResult, type McpPromptArgument, type McpPromptMessage, type McpServeCallContext, McpServeError, type McpServeErrorOptions, type McpServeFailure, type McpServeHandle, type McpServeOptions, type McpServePrompt, type McpServeRequestHandler, type McpServeResource, type McpServeSource, type McpServeTransport, type McpServer, type McpToolCallResult, type McpToolDescriptor, type McpToolSource, type McpTransport, type McpTransportClient, McpTransportError, type McpTransportErrorOptions, type McpTransportFailure, SUPPORTED_PROTOCOL_VERSIONS, type SearchProvider, type WebSearchInput, type WebSearchOptions, type WebSearchResult, type WebSearchResultItem, WebToolError, type WebToolErrorOptions, type WebToolFailure, calculatorTool, createJsonRpcClient, createServeHandler, createTransport, dateTimeTool, fetchUrlTool, httpRequestTool, jsonSchemaToStandard, mcp, mcpResult, serve, webSearchTool };
|
|
24
|
+
export { type AiToolsNamespace, CalculatorError, type CalculatorErrorOptions, type CalculatorFailure, type CalculatorInput, type CalculatorOptions, type CalculatorResult, DateTimeError, type DateTimeErrorOptions, type DateTimeFailure, type DateTimeInput, type DateTimeOp, type DateTimeOptions, type DateTimeResult, type FetchUrlExtract, type FetchUrlInput, type FetchUrlOptions, type FetchUrlResult, type HttpMethod, HttpPolicyError, type HttpPolicyErrorOptions, type HttpPolicyFailure, type HttpRequestInput, type HttpRequestOptions, type HttpRequestResult, type JsonRpcClientHandle, type JsonRpcError, type JsonRpcId, type JsonRpcMessage, type JsonRpcNotification, type JsonRpcRequest, type JsonRpcResponse, type JsonRpcVersion, type McpBeforeCall, type McpCallRefusal, type McpClient, type McpClientOptions, type McpContentBlock, McpError, type McpFactory, type McpGroupedToolSource, type McpHttpAuth, type McpHttpRequestInfo, type McpHttpRoute, type McpHttpRouteContext, type McpHttpRouteResponse, type McpHttpServeHandle, type McpHttpServeTransport, type McpListFilter, type McpNativeResult, type McpPromptArgument, type McpPromptMessage, type McpServeCallContext, McpServeError, type McpServeErrorOptions, type McpServeFailure, type McpServeHandle, type McpServeOptions, type McpServePrompt, type McpServeRequestHandler, type McpServeResource, type McpServeSource, type McpServeToolContext, type McpServeTransport, type McpServer, type McpStdioServeTransport, type McpToolCallResult, type McpToolDescriptor, type McpToolSource, type McpTransport, type McpTransportClient, McpTransportError, type McpTransportErrorOptions, type McpTransportFailure, type McpValidateCallParams, SUPPORTED_PROTOCOL_VERSIONS, type SearchProvider, type WebSearchInput, type WebSearchOptions, type WebSearchResult, type WebSearchResultItem, WebToolError, type WebToolErrorOptions, type WebToolFailure, calculatorTool, createJsonRpcClient, createServeHandler, createTransport, dateTimeTool, fetchUrlTool, httpRequestTool, jsonSchemaToStandard, mcp, mcpResult, serve, webSearchTool };
|
|
24
25
|
import "./mcp/index.mjs";
|
|
25
26
|
import "./register.mjs";
|
package/esm/index.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { CalculatorError, DateTimeError, HttpPolicyError, McpServeError, McpTran
|
|
|
2
2
|
import { httpRequestTool } from "./http/http-request.mjs";
|
|
3
3
|
import { jsonSchemaToStandard } from "./mcp/json-schema-to-standard.mjs";
|
|
4
4
|
import { createJsonRpcClient, createTransport } from "./mcp/transport.mjs";
|
|
5
|
+
import { McpError } from "./mcp/error.mjs";
|
|
5
6
|
import { SUPPORTED_PROTOCOL_VERSIONS } from "./mcp/protocol.mjs";
|
|
6
7
|
import { mcpResult } from "./mcp/result.mjs";
|
|
7
8
|
import { createServeHandler, serve } from "./mcp/serve.mjs";
|
|
@@ -14,4 +15,4 @@ import "./register.mjs";
|
|
|
14
15
|
import "./web/index.mjs";
|
|
15
16
|
import "./utility/index.mjs";
|
|
16
17
|
|
|
17
|
-
export { CalculatorError, DateTimeError, HttpPolicyError, McpServeError, McpTransportError, SUPPORTED_PROTOCOL_VERSIONS, WebToolError, calculatorTool, createJsonRpcClient, createServeHandler, createTransport, dateTimeTool, fetchUrlTool, httpRequestTool, jsonSchemaToStandard, mcp, mcpResult, serve, webSearchTool };
|
|
18
|
+
export { CalculatorError, DateTimeError, HttpPolicyError, McpError, McpServeError, McpTransportError, SUPPORTED_PROTOCOL_VERSIONS, WebToolError, calculatorTool, createJsonRpcClient, createServeHandler, createTransport, dateTimeTool, fetchUrlTool, httpRequestTool, jsonSchemaToStandard, mcp, mcpResult, serve, webSearchTool };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region ../../packages/ai-tools/src/mcp/error.d.ts
|
|
2
|
+
/** An application-defined JSON-RPC error thrown by MCP serving policy hooks. */
|
|
3
|
+
export declare class McpError extends Error {
|
|
4
|
+
readonly code: number;
|
|
5
|
+
readonly data: unknown;
|
|
6
|
+
constructor(code: number, message: string, data?: unknown);
|
|
7
|
+
}
|
|
8
|
+
//#endregion
|
|
9
|
+
//# sourceMappingURL=error.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.mts","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/error.ts"],"mappings":";;qBACa,iBAAiB;WACZ;WAEA;EAEhB,YAAmB,cAAc,iBAAiB"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region ../../packages/ai-tools/src/mcp/error.ts
|
|
2
|
+
/** An application-defined JSON-RPC error thrown by MCP serving policy hooks. */
|
|
3
|
+
var McpError = class extends Error {
|
|
4
|
+
constructor(code, message, data) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "McpError";
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.data = data;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Recognize our error and the SDK's McpError without a runtime SDK dependency.
|
|
13
|
+
* SDK source (checked 2026-10-19):
|
|
14
|
+
* https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.x/src/types.ts
|
|
15
|
+
* Only explicitly named errors with an integer code opt in; ordinary throws
|
|
16
|
+
* retain the existing mapping. Preserve the error's message verbatim.
|
|
17
|
+
*/
|
|
18
|
+
function isMcpError(cause) {
|
|
19
|
+
return cause instanceof Error && cause.name === "McpError" && "code" in cause && typeof cause.code === "number" && Number.isInteger(cause.code);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
export { McpError, isMcpError };
|
|
24
|
+
//# sourceMappingURL=error.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.mjs","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/error.ts"],"sourcesContent":["/** An application-defined JSON-RPC error thrown by MCP serving policy hooks. */\nexport class McpError extends Error {\n public readonly code: number;\n\n public readonly data: unknown;\n\n public constructor(code: number, message: string, data?: unknown) {\n super(message);\n this.name = \"McpError\";\n this.code = code;\n this.data = data;\n }\n}\n\n/**\n * Recognize our error and the SDK's McpError without a runtime SDK dependency.\n * SDK source (checked 2026-10-19):\n * https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.x/src/types.ts\n * Only explicitly named errors with an integer code opt in; ordinary throws\n * retain the existing mapping. Preserve the error's message verbatim.\n */\nexport function isMcpError(cause: unknown): cause is Error & { code: number; data?: unknown } {\n return (\n cause instanceof Error &&\n cause.name === \"McpError\" &&\n \"code\" in cause &&\n typeof cause.code === \"number\" &&\n Number.isInteger(cause.code)\n );\n}\n"],"mappings":";;AACA,IAAa,WAAb,cAA8B,MAAM;CAKlC,AAAO,YAAY,MAAc,SAAiB,MAAgB;EAChE,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;;;;;;;;AASA,SAAgB,WAAW,OAAmE;CAC5F,OACE,iBAAiB,SACjB,MAAM,SAAS,cACf,UAAU,SACV,OAAO,MAAM,SAAS,YACtB,OAAO,UAAU,MAAM,IAAI;AAE/B"}
|
package/esm/mcp/index.d.mts
CHANGED
|
@@ -3,6 +3,7 @@ import { McpHttpServeHandle, McpHttpServeTransport, McpServeOptions, McpServeSou
|
|
|
3
3
|
import "../contracts/index.mjs";
|
|
4
4
|
import { McpServeRequestHandler, createServeHandler, serve } from "./serve.mjs";
|
|
5
5
|
import { SUPPORTED_PROTOCOL_VERSIONS } from "./protocol.mjs";
|
|
6
|
+
import { McpError } from "./error.mjs";
|
|
6
7
|
import { McpNativeResult, mcpResult } from "./result.mjs";
|
|
7
8
|
import { jsonSchemaToStandard } from "./json-schema-to-standard.mjs";
|
|
8
9
|
import { McpTransportClient } from "./transport.type.mjs";
|
|
@@ -47,5 +48,5 @@ export interface McpFactory {
|
|
|
47
48
|
*/
|
|
48
49
|
export declare const mcp: McpFactory;
|
|
49
50
|
//#endregion
|
|
50
|
-
export { type McpNativeResult, type McpServeRequestHandler, type McpTransportClient, SUPPORTED_PROTOCOL_VERSIONS, createServeHandler, mcpResult, serve };
|
|
51
|
+
export { McpError, type McpNativeResult, type McpServeRequestHandler, type McpTransportClient, SUPPORTED_PROTOCOL_VERSIONS, createServeHandler, mcpResult, serve };
|
|
51
52
|
//# sourceMappingURL=index.d.mts.map
|
package/esm/mcp/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;iBA+BiB;;;;;;;;GAQd,QAAQ,cAAc,UAAU,mBAAmB;;;;;;;;;EASpD,MACE,QAAQ,gBACR,SAAS;IAAoB,WAAW;MACvC,UAAU;EACb,MAAM,QAAQ,gBAAgB,SAAS,kBAAkB;;;;;;;;;;qBAW9C,KAAK"}
|
package/esm/mcp/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsonSchemaToStandard } from "./json-schema-to-standard.mjs";
|
|
2
2
|
import { createJsonRpcClient, createTransport } from "./transport.mjs";
|
|
3
3
|
import { mcp as mcp$1 } from "./client.mjs";
|
|
4
|
+
import { McpError } from "./error.mjs";
|
|
4
5
|
import { SUPPORTED_PROTOCOL_VERSIONS } from "./protocol.mjs";
|
|
5
6
|
import { mcpResult } from "./result.mjs";
|
|
6
7
|
import { createServeHandler, serve } from "./serve.mjs";
|
|
@@ -17,5 +18,5 @@ import { createServeHandler, serve } from "./serve.mjs";
|
|
|
17
18
|
const mcp = Object.assign((server, options) => mcp$1(server, options), { serve });
|
|
18
19
|
|
|
19
20
|
//#endregion
|
|
20
|
-
export { SUPPORTED_PROTOCOL_VERSIONS, createServeHandler, mcp, mcpResult, serve };
|
|
21
|
+
export { McpError, SUPPORTED_PROTOCOL_VERSIONS, createServeHandler, mcp, mcpResult, serve };
|
|
21
22
|
//# sourceMappingURL=index.mjs.map
|
package/esm/mcp/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["mcpClient"],"sources":["../../../../../../../../packages/ai-tools/src/mcp/index.ts"],"sourcesContent":["import type {\n McpClient,\n McpClientOptions,\n McpHttpServeHandle,\n McpHttpServeTransport,\n McpServeOptions,\n McpServeSource,\n McpServer,\n McpTransport,\n} from \"../contracts\";\nimport { mcp as mcpClient } from \"./client\";\nimport { serve } from \"./serve\";\n\nexport { serve, createServeHandler } from \"./serve\";\nexport type { McpServeRequestHandler } from \"./serve\";\nexport { SUPPORTED_PROTOCOL_VERSIONS } from \"./protocol\";\nexport { mcpResult } from \"./result\";\nexport type { McpNativeResult } from \"./result\";\nexport { jsonSchemaToStandard } from \"./json-schema-to-standard\";\nexport { createJsonRpcClient, createTransport } from \"./transport\";\nexport type { JsonRpcClientHandle } from \"./transport\";\nexport type { McpTransportClient } from \"./transport.type\";\n\n/**\n * The callable `ai.mcp` surface — a factory that connects to an external\n * MCP server (Direction A) and also carries `.serve` to expose a local\n * primitive AS an MCP server (Direction B). Modeled as a function with an\n * attached `serve` property, mirroring how `ai.mcp(server).tools()` and\n * `ai.mcp.serve(source, options).start()` read in the design.\n */\nexport interface McpFactory {\n /**\n * Connect to an external MCP server and adapt its tools as agent tools.\n *\n * @param server - The transport config (`{ type: \"stdio\" }` / `{ type: \"http\" }`).\n * @param options - Prefix / filter / per-call timeout, and a `signal`\n * that closes the client (and fails its calls) when it aborts.\n */\n (server: McpTransport, options?: McpClientOptions): McpClient;\n /**\n * Expose a built agent / supervisor / orchestrator (or a literal\n * `ToolContract[]`, or named `{ groups }`) AS an MCP server. Over\n * `{ type: \"http\" }`, `start()` resolves `{ url, close }`.\n *\n * @param source - The tools to expose.\n * @param options - Server name, version, transport, schema dialect, resources, prompts.\n */\n serve(\n source: McpServeSource,\n options: McpServeOptions & { transport: McpHttpServeTransport },\n ): McpServer<McpHttpServeHandle>;\n serve(source: McpServeSource, options: McpServeOptions): McpServer;\n}\n\n/**\n * The `ai.mcp` factory value: the client factory with `.serve` attached.\n * `Object.assign` keeps `mcp` callable (Direction A) while widening it with\n * the `serve` member (Direction B) — one object, both directions. The\n * `declare module \"@warlock.js/ai\"` augmentation and the runtime\n * registration (`ai.mcp = mcp`) live in `../register`, so this barrel is a\n * pure value/type module the registrar consumes.\n */\nexport const mcp: McpFactory = Object.assign(\n (server: McpTransport, options?: McpClientOptions): McpClient => mcpClient(server, options),\n { serve },\n);\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["mcpClient"],"sources":["../../../../../../../../packages/ai-tools/src/mcp/index.ts"],"sourcesContent":["import type {\n McpClient,\n McpClientOptions,\n McpHttpServeHandle,\n McpHttpServeTransport,\n McpServeOptions,\n McpServeSource,\n McpServer,\n McpTransport,\n} from \"../contracts\";\nimport { mcp as mcpClient } from \"./client\";\nimport { serve } from \"./serve\";\n\nexport { serve, createServeHandler } from \"./serve\";\nexport type { McpServeRequestHandler } from \"./serve\";\nexport { SUPPORTED_PROTOCOL_VERSIONS } from \"./protocol\";\nexport { McpError } from \"./error\";\nexport { mcpResult } from \"./result\";\nexport type { McpNativeResult } from \"./result\";\nexport { jsonSchemaToStandard } from \"./json-schema-to-standard\";\nexport { createJsonRpcClient, createTransport } from \"./transport\";\nexport type { JsonRpcClientHandle } from \"./transport\";\nexport type { McpTransportClient } from \"./transport.type\";\n\n/**\n * The callable `ai.mcp` surface — a factory that connects to an external\n * MCP server (Direction A) and also carries `.serve` to expose a local\n * primitive AS an MCP server (Direction B). Modeled as a function with an\n * attached `serve` property, mirroring how `ai.mcp(server).tools()` and\n * `ai.mcp.serve(source, options).start()` read in the design.\n */\nexport interface McpFactory {\n /**\n * Connect to an external MCP server and adapt its tools as agent tools.\n *\n * @param server - The transport config (`{ type: \"stdio\" }` / `{ type: \"http\" }`).\n * @param options - Prefix / filter / per-call timeout, and a `signal`\n * that closes the client (and fails its calls) when it aborts.\n */\n (server: McpTransport, options?: McpClientOptions): McpClient;\n /**\n * Expose a built agent / supervisor / orchestrator (or a literal\n * `ToolContract[]`, or named `{ groups }`) AS an MCP server. Over\n * `{ type: \"http\" }`, `start()` resolves `{ url, close }`.\n *\n * @param source - The tools to expose.\n * @param options - Server name, version, transport, schema dialect, resources, prompts.\n */\n serve(\n source: McpServeSource,\n options: McpServeOptions & { transport: McpHttpServeTransport },\n ): McpServer<McpHttpServeHandle>;\n serve(source: McpServeSource, options: McpServeOptions): McpServer;\n}\n\n/**\n * The `ai.mcp` factory value: the client factory with `.serve` attached.\n * `Object.assign` keeps `mcp` callable (Direction A) while widening it with\n * the `serve` member (Direction B) — one object, both directions. The\n * `declare module \"@warlock.js/ai\"` augmentation and the runtime\n * registration (`ai.mcp = mcp`) live in `../register`, so this barrel is a\n * pure value/type module the registrar consumes.\n */\nexport const mcp: McpFactory = Object.assign(\n (server: McpTransport, options?: McpClientOptions): McpClient => mcpClient(server, options),\n { serve },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;AA+DA,MAAa,MAAkB,OAAO,QACnC,QAAsB,YAA0CA,MAAU,QAAQ,OAAO,GAC1F,EAAE,MAAM,CACV"}
|
package/esm/mcp/serve-http.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { McpServeError } from "../errors.mjs";
|
|
2
2
|
import { SUPPORTED_PROTOCOL_VERSIONS } from "./protocol.mjs";
|
|
3
|
+
import { McpServeRequests } from "./serve-requests.mjs";
|
|
3
4
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
4
5
|
import { createServer } from "node:http";
|
|
5
6
|
import { isIPv4, isIPv6 } from "node:net";
|
|
@@ -12,7 +13,7 @@ import { isIPv4, isIPv6 } from "node:net";
|
|
|
12
13
|
* specification (https://modelcontextprotocol.io/specification/2025-11-25/basic/transports,
|
|
13
14
|
* checked 2026-10-17) on `node:http`, with no SDK dependency:
|
|
14
15
|
*
|
|
15
|
-
* - one endpoint path; each POST carries exactly one JSON-RPC message
|
|
16
|
+
* - one MCP endpoint path (plus optional authenticated app routes); each MCP POST carries exactly one JSON-RPC message
|
|
16
17
|
* (batches were removed from the protocol in 2025-06-18 and get 400);
|
|
17
18
|
* - a request is answered with `Content-Type: application/json` (the spec
|
|
18
19
|
* lets a server pick JSON or SSE per request); a notification or a
|
|
@@ -70,6 +71,35 @@ function hostnameOf(hostHeader) {
|
|
|
70
71
|
function digest(value) {
|
|
71
72
|
return createHash("sha256").update(value, "utf8").digest();
|
|
72
73
|
}
|
|
74
|
+
/** Resolve exact descendant routes; no route may replace the MCP endpoint. */
|
|
75
|
+
function resolveRoutes(path, routes) {
|
|
76
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
77
|
+
const methods = /* @__PURE__ */ new Set([
|
|
78
|
+
"GET",
|
|
79
|
+
"POST",
|
|
80
|
+
"PUT",
|
|
81
|
+
"PATCH",
|
|
82
|
+
"DELETE",
|
|
83
|
+
"HEAD",
|
|
84
|
+
"OPTIONS"
|
|
85
|
+
]);
|
|
86
|
+
if (routes.length > 0) {
|
|
87
|
+
let canonical = false;
|
|
88
|
+
try {
|
|
89
|
+
canonical = !path.startsWith("//") && new URL(path, "http://localhost").pathname === path;
|
|
90
|
+
} catch {}
|
|
91
|
+
if (!canonical) throw new McpServeError("MCP http routes require a canonical endpoint pathname.", { type: "config" });
|
|
92
|
+
}
|
|
93
|
+
for (const route of routes) {
|
|
94
|
+
if (!methods.has(route.method) || !/^\/[A-Za-z0-9._~-]+(?:\/[A-Za-z0-9._~-]+)*$/.test(route.path) || route.path.split("/").some((segment) => segment === "." || segment === "..") || typeof route.handler !== "function") throw new McpServeError("Invalid MCP http route method, path or handler.", { type: "config" });
|
|
95
|
+
const fullPath = `${path.replace(/\/+$/, "")}${route.path}`;
|
|
96
|
+
const byMethod = resolved.get(fullPath) ?? /* @__PURE__ */ new Map();
|
|
97
|
+
if (fullPath === path || byMethod.has(route.method)) throw new McpServeError(`MCP http route "${route.method} ${fullPath}" conflicts.`, { type: "collision" });
|
|
98
|
+
byMethod.set(route.method, { ...route });
|
|
99
|
+
resolved.set(fullPath, byMethod);
|
|
100
|
+
}
|
|
101
|
+
return resolved;
|
|
102
|
+
}
|
|
73
103
|
/** Normalise a configured origin to `scheme://host[:port]`, or throw a config error. */
|
|
74
104
|
function normalizeOrigin(origin) {
|
|
75
105
|
try {
|
|
@@ -101,9 +131,9 @@ function resolveSettings(transport) {
|
|
|
101
131
|
const tokens = auth?.bearer === void 0 ? [] : typeof auth.bearer === "string" ? [auth.bearer] : [...auth.bearer];
|
|
102
132
|
if (tokens.some((token) => token.length === 0)) throw new McpServeError("MCP http auth.bearer tokens must be non-empty strings.", { type: "config" });
|
|
103
133
|
if (auth && auth.bearer !== void 0 && tokens.length === 0) throw new McpServeError("MCP http auth.bearer needs at least one token.", { type: "config" });
|
|
104
|
-
if (auth && tokens.length === 0 && !auth.authorize) throw new McpServeError("MCP http auth needs `bearer` and/or `
|
|
105
|
-
const authRequired = tokens.length > 0 || auth?.authorize !== void 0;
|
|
106
|
-
if (!loopback && !authRequired) throw new McpServeError(`Refusing to serve MCP over http on non-loopback host "${host}" without auth. Set transport.auth ({ bearer } or {
|
|
134
|
+
if (auth && tokens.length === 0 && !auth.authorize && !auth.authenticate) throw new McpServeError("MCP http auth needs `bearer`, `authorize` and/or `authenticate`.", { type: "config" });
|
|
135
|
+
const authRequired = tokens.length > 0 || auth?.authorize !== void 0 || auth?.authenticate !== void 0;
|
|
136
|
+
if (!loopback && !authRequired) throw new McpServeError(`Refusing to serve MCP over http on non-loopback host "${host}" without auth. Set transport.auth ({ bearer }, { authorize } or { authenticate }), or bind 127.0.0.1.`, {
|
|
107
137
|
type: "config",
|
|
108
138
|
context: { host }
|
|
109
139
|
});
|
|
@@ -116,9 +146,11 @@ function resolveSettings(transport) {
|
|
|
116
146
|
drainTimeoutMs,
|
|
117
147
|
tokenDigests: tokens.map(digest),
|
|
118
148
|
authorize: auth?.authorize,
|
|
149
|
+
authenticate: auth?.authenticate,
|
|
119
150
|
authRequired,
|
|
120
151
|
allowedOrigins: new Set((transport.allowedOrigins ?? []).map(normalizeOrigin)),
|
|
121
|
-
allowedHosts: new Set((transport.allowedHosts ?? []).map((name) => hostnameOf(name)))
|
|
152
|
+
allowedHosts: new Set((transport.allowedHosts ?? []).map((name) => hostnameOf(name))),
|
|
153
|
+
routes: resolveRoutes(path, transport.routes ?? [])
|
|
122
154
|
};
|
|
123
155
|
}
|
|
124
156
|
/** Flatten Node's header bag into lower-cased `name → value` strings. */
|
|
@@ -174,6 +206,7 @@ function sendEmpty(response, status, headers = {}) {
|
|
|
174
206
|
async function startHttpServer(transport, dispatch) {
|
|
175
207
|
const settings = resolveSettings(transport);
|
|
176
208
|
const shutdown = new AbortController();
|
|
209
|
+
const requests = new McpServeRequests();
|
|
177
210
|
const inFlight = /* @__PURE__ */ new Set();
|
|
178
211
|
let closing;
|
|
179
212
|
const server = createServer((request, response) => {
|
|
@@ -181,7 +214,7 @@ async function startHttpServer(transport, dispatch) {
|
|
|
181
214
|
sendRefusal(response, 503, JSON_RPC_SERVER_ERROR, "Server is shutting down.", { connection: "close" });
|
|
182
215
|
return;
|
|
183
216
|
}
|
|
184
|
-
const work = handleRequest(request, response, settings, dispatch, shutdown.signal).catch(() => {
|
|
217
|
+
const work = handleRequest(request, response, settings, dispatch, shutdown.signal, requests).catch(() => {
|
|
185
218
|
sendRefusal(response, 500, JSON_RPC_SERVER_ERROR, "Internal server error.");
|
|
186
219
|
});
|
|
187
220
|
inFlight.add(work);
|
|
@@ -240,13 +273,15 @@ async function startHttpServer(transport, dispatch) {
|
|
|
240
273
|
};
|
|
241
274
|
}
|
|
242
275
|
/**
|
|
243
|
-
* Validate and answer one HTTP request:
|
|
244
|
-
* method →
|
|
276
|
+
* Validate and answer one HTTP request: Host / Origin → path → auth →
|
|
277
|
+
* method → route handler or MCP headers/body/JSON-RPC dispatch.
|
|
245
278
|
*/
|
|
246
|
-
async function handleRequest(request, response, settings, dispatch, signal) {
|
|
279
|
+
async function handleRequest(request, response, settings, dispatch, signal, requests) {
|
|
247
280
|
const headers = flattenHeaders(request.headers);
|
|
248
281
|
const method = (request.method ?? "GET").toUpperCase();
|
|
249
|
-
const
|
|
282
|
+
const url = new URL(request.url ?? "/", "http://localhost");
|
|
283
|
+
const path = url.pathname;
|
|
284
|
+
const routes = settings.routes.get(path);
|
|
250
285
|
if (!hostAllowed(headers.host, settings)) {
|
|
251
286
|
sendRefusal(response, 403, JSON_RPC_SERVER_ERROR, "Forbidden: Host header not allowed.");
|
|
252
287
|
return;
|
|
@@ -255,7 +290,7 @@ async function handleRequest(request, response, settings, dispatch, signal) {
|
|
|
255
290
|
sendRefusal(response, 403, JSON_RPC_SERVER_ERROR, "Forbidden: Origin not allowed.");
|
|
256
291
|
return;
|
|
257
292
|
}
|
|
258
|
-
if (path !== settings.path) {
|
|
293
|
+
if (path !== settings.path && !routes) {
|
|
259
294
|
sendRefusal(response, 404, JSON_RPC_SERVER_ERROR, "Not found.");
|
|
260
295
|
return;
|
|
261
296
|
}
|
|
@@ -265,11 +300,21 @@ async function handleRequest(request, response, settings, dispatch, signal) {
|
|
|
265
300
|
headers
|
|
266
301
|
};
|
|
267
302
|
if (request.socket.remoteAddress !== void 0) info.remoteAddress = request.socket.remoteAddress;
|
|
268
|
-
|
|
269
|
-
|
|
303
|
+
const authentication = await authorized(info, settings);
|
|
304
|
+
if (!authentication.allowed) {
|
|
305
|
+
const challenge = settings.tokenDigests.length > 0 || settings.authenticate ? { "www-authenticate": "Bearer realm=\"mcp\"" } : {};
|
|
270
306
|
sendRefusal(response, 401, JSON_RPC_SERVER_ERROR, "Unauthorized.", challenge);
|
|
271
307
|
return;
|
|
272
308
|
}
|
|
309
|
+
if (routes) {
|
|
310
|
+
const route = routes.get(method);
|
|
311
|
+
if (!route) {
|
|
312
|
+
sendEmpty(response, 405, { allow: [...routes.keys()].sort().join(", ") });
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
await handleRoute(request, response, route, info, url, authentication.principal, signal, settings);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
273
318
|
if (method !== "POST") {
|
|
274
319
|
sendEmpty(response, 405, { allow: "POST" });
|
|
275
320
|
return;
|
|
@@ -311,6 +356,7 @@ async function handleRequest(request, response, settings, dispatch, signal) {
|
|
|
311
356
|
const id = record.id;
|
|
312
357
|
if (typeof record.method === "string") {
|
|
313
358
|
if (typeof id !== "string" && typeof id !== "number") {
|
|
359
|
+
requests.cancel(record, cancellationScope(info));
|
|
314
360
|
sendEmpty(response, 202);
|
|
315
361
|
return;
|
|
316
362
|
}
|
|
@@ -320,10 +366,20 @@ async function handleRequest(request, response, settings, dispatch, signal) {
|
|
|
320
366
|
method: record.method
|
|
321
367
|
};
|
|
322
368
|
if (record.params !== void 0) rpcRequest.params = record.params;
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
369
|
+
const disconnected = new AbortController();
|
|
370
|
+
const onClose = () => disconnected.abort();
|
|
371
|
+
response.once("close", onClose);
|
|
372
|
+
try {
|
|
373
|
+
const rpcResponse = await requests.run(rpcRequest, {
|
|
374
|
+
request: info,
|
|
375
|
+
principal: authentication.principal,
|
|
376
|
+
signal: AbortSignal.any([signal, disconnected.signal])
|
|
377
|
+
}, (context) => dispatch(rpcRequest, context), cancellationScope(info));
|
|
378
|
+
if (rpcResponse === void 0) sendEmpty(response, 202);
|
|
379
|
+
else sendJson(response, 200, rpcResponse);
|
|
380
|
+
} finally {
|
|
381
|
+
response.off("close", onClose);
|
|
382
|
+
}
|
|
327
383
|
return;
|
|
328
384
|
}
|
|
329
385
|
if ((typeof id === "string" || typeof id === "number") && ("result" in record || "error" in record)) {
|
|
@@ -332,6 +388,32 @@ async function handleRequest(request, response, settings, dispatch, signal) {
|
|
|
332
388
|
}
|
|
333
389
|
sendRefusal(response, 400, JSON_RPC_INVALID_REQUEST, "Invalid request: not a JSON-RPC message.");
|
|
334
390
|
}
|
|
391
|
+
/** Run a side route after the shared guards, tracking disconnect and shutdown. */
|
|
392
|
+
async function handleRoute(request, response, route, info, url, principal, shutdown, settings) {
|
|
393
|
+
const disconnected = new AbortController();
|
|
394
|
+
const onClose = () => disconnected.abort();
|
|
395
|
+
response.once("close", onClose);
|
|
396
|
+
try {
|
|
397
|
+
const body = await readBody(request, settings.maxBodyBytes);
|
|
398
|
+
if (body.type === "too-large") {
|
|
399
|
+
sendRefusal(response, 413, JSON_RPC_SERVER_ERROR, `Request body exceeds ${settings.maxBodyBytes} bytes.`, { connection: "close" });
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
const signal = AbortSignal.any([shutdown, disconnected.signal]);
|
|
403
|
+
signal.throwIfAborted();
|
|
404
|
+
const result = await route.handler({
|
|
405
|
+
request: info,
|
|
406
|
+
url,
|
|
407
|
+
principal,
|
|
408
|
+
body: body.text,
|
|
409
|
+
signal
|
|
410
|
+
});
|
|
411
|
+
if (result.body === void 0 || info.method === "HEAD") sendEmpty(response, result.status ?? 200, result.headers);
|
|
412
|
+
else sendJson(response, result.status ?? 200, result.body, result.headers);
|
|
413
|
+
} finally {
|
|
414
|
+
response.off("close", onClose);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
335
417
|
/**
|
|
336
418
|
* Host-header check. While bound to loopback only loopback names (plus
|
|
337
419
|
* `allowedHosts`) are accepted, so a DNS-rebound name pointing at
|
|
@@ -362,27 +444,50 @@ function originAllowed(origin, settings) {
|
|
|
362
444
|
if (settings.allowedOrigins.has(parsed.origin)) return true;
|
|
363
445
|
return (parsed.protocol === "http:" || parsed.protocol === "https:") && isLoopbackHost(parsed.hostname);
|
|
364
446
|
}
|
|
365
|
-
/**
|
|
366
|
-
* Run the configured auth. Bearer tokens are compared in constant time
|
|
367
|
-
* over SHA-256 digests (equal-length buffers for `timingSafeEqual`), and
|
|
368
|
-
* every configured token is compared so the match position does not leak.
|
|
369
|
-
* An `authorize` hook that throws denies the request.
|
|
370
|
-
*/
|
|
371
447
|
async function authorized(info, settings) {
|
|
372
|
-
if (!settings.authRequired) return true;
|
|
448
|
+
if (!settings.authRequired) return { allowed: true };
|
|
373
449
|
if (settings.tokenDigests.length > 0) {
|
|
374
450
|
const match = /^Bearer[ \t]+(.+)$/i.exec(info.headers.authorization ?? "");
|
|
375
451
|
const presented = digest(match?.[1]?.trim() ?? "");
|
|
376
452
|
let matched = false;
|
|
377
453
|
for (const expected of settings.tokenDigests) matched = timingSafeEqual(presented, expected) || matched;
|
|
378
|
-
if (!match || !matched) return false;
|
|
454
|
+
if (!match || !matched) return { allowed: false };
|
|
379
455
|
}
|
|
380
|
-
|
|
381
|
-
|
|
456
|
+
try {
|
|
457
|
+
if (settings.authorize && await settings.authorize(info) !== true) return { allowed: false };
|
|
458
|
+
if (settings.authenticate) {
|
|
459
|
+
const principal = await settings.authenticate(info);
|
|
460
|
+
return principal === void 0 ? { allowed: false } : {
|
|
461
|
+
allowed: true,
|
|
462
|
+
principal
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
return { allowed: true };
|
|
382
466
|
} catch {
|
|
383
|
-
return false;
|
|
467
|
+
return { allowed: false };
|
|
384
468
|
}
|
|
385
|
-
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Stateless HTTP has no connection/session id. Repeat the caller's credential
|
|
472
|
+
* and application headers on notifications; per-message transport headers do
|
|
473
|
+
* not identify a caller. Hash the material rather than retaining secrets as keys.
|
|
474
|
+
*/
|
|
475
|
+
function cancellationScope(info) {
|
|
476
|
+
const transportHeaders = /* @__PURE__ */ new Set([
|
|
477
|
+
"host",
|
|
478
|
+
"origin",
|
|
479
|
+
"accept",
|
|
480
|
+
"accept-encoding",
|
|
481
|
+
"accept-language",
|
|
482
|
+
"connection",
|
|
483
|
+
"content-type",
|
|
484
|
+
"content-length",
|
|
485
|
+
"transfer-encoding",
|
|
486
|
+
"mcp-protocol-version",
|
|
487
|
+
"user-agent"
|
|
488
|
+
]);
|
|
489
|
+
const credentials = Object.entries(info.headers).filter(([name]) => !transportHeaders.has(name)).sort(([left], [right]) => left.localeCompare(right));
|
|
490
|
+
return digest(JSON.stringify([info.remoteAddress, credentials])).toString("hex");
|
|
386
491
|
}
|
|
387
492
|
/** True for `application/json` (any parameters, e.g. `; charset=utf-8`). */
|
|
388
493
|
function isJsonContentType(contentType) {
|