@warlock.js/ai-tools 5.31.0 → 5.33.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 +26 -0
- package/cjs/index.cjs +327 -60
- package/cjs/index.cjs.map +1 -1
- package/esm/contracts/http.type.d.mts +4 -5
- package/esm/contracts/http.type.d.mts.map +1 -1
- package/esm/contracts/index.d.mts +2 -1
- package/esm/contracts/mcp-serve.type.d.mts +97 -37
- package/esm/contracts/mcp-serve.type.d.mts.map +1 -1
- package/esm/contracts/mcp.type.d.mts +29 -19
- package/esm/contracts/mcp.type.d.mts.map +1 -1
- package/esm/contracts/utility.type.d.mts +7 -8
- package/esm/contracts/utility.type.d.mts.map +1 -1
- package/esm/contracts/web.type.d.mts +9 -10
- package/esm/contracts/web.type.d.mts.map +1 -1
- package/esm/errors.d.mts +31 -26
- package/esm/errors.d.mts.map +1 -1
- package/esm/http/http-request.d.mts +2 -3
- package/esm/http/http-request.d.mts.map +1 -1
- package/esm/http/http-request.mjs +7 -5
- package/esm/http/http-request.mjs.map +1 -1
- package/esm/index.d.mts +6 -2
- package/esm/index.mjs +2 -1
- package/esm/mcp/client.mjs +2 -1
- package/esm/mcp/client.mjs.map +1 -1
- package/esm/mcp/index.d.mts +5 -4
- 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/json-schema-to-standard.d.mts +1 -3
- package/esm/mcp/json-schema-to-standard.d.mts.map +1 -1
- package/esm/mcp/protocol.d.mts +1 -2
- package/esm/mcp/protocol.d.mts.map +1 -1
- package/esm/mcp/result.d.mts +17 -0
- package/esm/mcp/result.d.mts.map +1 -0
- package/esm/mcp/result.mjs +85 -0
- package/esm/mcp/result.mjs.map +1 -0
- package/esm/mcp/serve-http.mjs +64 -25
- 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 +5 -5
- package/esm/mcp/serve.d.mts.map +1 -1
- package/esm/mcp/serve.mjs +97 -18
- package/esm/mcp/serve.mjs.map +1 -1
- package/esm/mcp/transport.d.mts +4 -5
- package/esm/mcp/transport.d.mts.map +1 -1
- package/esm/mcp/transport.mjs +12 -6
- package/esm/mcp/transport.mjs.map +1 -1
- package/esm/mcp/transport.type.d.mts +2 -2
- package/esm/mcp/transport.type.d.mts.map +1 -1
- package/esm/register.d.mts +3 -4
- package/esm/register.d.mts.map +1 -1
- package/esm/utility/calculator.d.mts +1 -3
- package/esm/utility/calculator.d.mts.map +1 -1
- package/esm/utility/date-time.d.mts +3 -5
- package/esm/utility/date-time.d.mts.map +1 -1
- package/esm/utility/date-time.mjs +2 -1
- package/esm/utility/date-time.mjs.map +1 -1
- package/esm/utility/index.d.mts +2 -1
- package/esm/utility/index.mjs +1 -1
- package/esm/web/fetch-url.d.mts +2 -3
- package/esm/web/fetch-url.d.mts.map +1 -1
- package/esm/web/fetch-url.mjs +2 -1
- package/esm/web/fetch-url.mjs.map +1 -1
- package/esm/web/index.d.mts +2 -1
- package/esm/web/index.mjs +1 -1
- package/esm/web/web-search.d.mts +2 -3
- package/esm/web/web-search.d.mts.map +1 -1
- package/esm/web/web-search.mjs +3 -4
- package/esm/web/web-search.mjs.map +1 -1
- package/llms-full.txt +122 -3
- 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 +107 -3
package/esm/errors.d.mts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { AIError, AIErrorOptions } from "@warlock.js/ai";
|
|
2
|
-
|
|
3
2
|
//#region ../../packages/ai-tools/src/errors.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Why the calculator rejected an expression.
|
|
@@ -9,13 +8,14 @@ import { AIError, AIErrorOptions } from "@warlock.js/ai";
|
|
|
9
8
|
* - `"divide-by-zero"` — evaluation divided (or took a modulo) by zero.
|
|
10
9
|
* - `"overflow"` — the computed result was not a finite number.
|
|
11
10
|
*/
|
|
12
|
-
type CalculatorFailure = "syntax" | "divide-by-zero" | "overflow";
|
|
11
|
+
export type CalculatorFailure = "syntax" | "divide-by-zero" | "overflow";
|
|
13
12
|
/**
|
|
14
13
|
* Options for {@link CalculatorError} — the structured `type`
|
|
15
14
|
* discriminator so a caller can branch without parsing the message.
|
|
16
15
|
*/
|
|
17
|
-
type CalculatorErrorOptions = AIErrorOptions & {
|
|
18
|
-
/** Which class of calculator failure occurred. */
|
|
16
|
+
export type CalculatorErrorOptions = AIErrorOptions & {
|
|
17
|
+
/** Which class of calculator failure occurred. */
|
|
18
|
+
type: CalculatorFailure;
|
|
19
19
|
};
|
|
20
20
|
/**
|
|
21
21
|
* The `calculator` tool could not evaluate an expression — it was not
|
|
@@ -35,7 +35,7 @@ type CalculatorErrorOptions = AIErrorOptions & {
|
|
|
35
35
|
* // the expression divided by zero — ask the model to revise it
|
|
36
36
|
* }
|
|
37
37
|
*/
|
|
38
|
-
declare class CalculatorError extends AIError {
|
|
38
|
+
export declare class CalculatorError extends AIError {
|
|
39
39
|
/** Which class of calculator failure occurred. */
|
|
40
40
|
readonly type: CalculatorFailure;
|
|
41
41
|
constructor(message: string, options: CalculatorErrorOptions);
|
|
@@ -49,13 +49,14 @@ declare class CalculatorError extends AIError {
|
|
|
49
49
|
* - `"invalid-time-zone"` — the IANA time zone was not recognized.
|
|
50
50
|
* - `"unsupported-op"` — the `op` was not one this tool implements.
|
|
51
51
|
*/
|
|
52
|
-
type DateTimeFailure = "invalid-input" | "invalid-unit" | "invalid-time-zone" | "unsupported-op";
|
|
52
|
+
export type DateTimeFailure = "invalid-input" | "invalid-unit" | "invalid-time-zone" | "unsupported-op";
|
|
53
53
|
/**
|
|
54
54
|
* Options for {@link DateTimeError} — the structured `type`
|
|
55
55
|
* discriminator so a caller can branch without parsing the message.
|
|
56
56
|
*/
|
|
57
|
-
type DateTimeErrorOptions = AIErrorOptions & {
|
|
58
|
-
/** Which class of date-time failure occurred. */
|
|
57
|
+
export type DateTimeErrorOptions = AIErrorOptions & {
|
|
58
|
+
/** Which class of date-time failure occurred. */
|
|
59
|
+
type: DateTimeFailure;
|
|
59
60
|
};
|
|
60
61
|
/**
|
|
61
62
|
* The `date_time` tool could not complete a call — a required field was
|
|
@@ -73,7 +74,7 @@ type DateTimeErrorOptions = AIErrorOptions & {
|
|
|
73
74
|
* // the model passed an unknown unit — re-prompt with the allowed set
|
|
74
75
|
* }
|
|
75
76
|
*/
|
|
76
|
-
declare class DateTimeError extends AIError {
|
|
77
|
+
export declare class DateTimeError extends AIError {
|
|
77
78
|
/** Which class of date-time failure occurred. */
|
|
78
79
|
readonly type: DateTimeFailure;
|
|
79
80
|
constructor(message: string, options: DateTimeErrorOptions);
|
|
@@ -90,14 +91,16 @@ declare class DateTimeError extends AIError {
|
|
|
90
91
|
* - `"closed"` — the transport was used after it was closed, or the
|
|
91
92
|
* peer closed it mid-call.
|
|
92
93
|
*/
|
|
93
|
-
type McpTransportFailure = "connect" | "protocol" | "timeout" | "closed";
|
|
94
|
+
export type McpTransportFailure = "connect" | "protocol" | "timeout" | "closed";
|
|
94
95
|
/**
|
|
95
96
|
* Options for {@link McpTransportError} — the structured `type`
|
|
96
97
|
* discriminator plus an optional JSON-RPC method name for branchable
|
|
97
98
|
* diagnostics without parsing the message.
|
|
98
99
|
*/
|
|
99
|
-
type McpTransportErrorOptions = AIErrorOptions & {
|
|
100
|
-
/** Which class of transport failure occurred. */
|
|
100
|
+
export type McpTransportErrorOptions = AIErrorOptions & {
|
|
101
|
+
/** Which class of transport failure occurred. */
|
|
102
|
+
type: McpTransportFailure;
|
|
103
|
+
/** The JSON-RPC method in flight when the failure occurred, if any. */
|
|
101
104
|
method?: string;
|
|
102
105
|
};
|
|
103
106
|
/**
|
|
@@ -119,7 +122,7 @@ type McpTransportErrorOptions = AIErrorOptions & {
|
|
|
119
122
|
* // the remote call exceeded its deadline — retry or escalate
|
|
120
123
|
* }
|
|
121
124
|
*/
|
|
122
|
-
declare class McpTransportError extends AIError {
|
|
125
|
+
export declare class McpTransportError extends AIError {
|
|
123
126
|
/** Which class of transport failure occurred. */
|
|
124
127
|
readonly type: McpTransportFailure;
|
|
125
128
|
/** The JSON-RPC method in flight when the failure occurred, if any. */
|
|
@@ -137,12 +140,13 @@ declare class McpTransportError extends AIError {
|
|
|
137
140
|
* - `"listen"` — the HTTP server could not bind (port in use, permission
|
|
138
141
|
* denied, unknown host).
|
|
139
142
|
*/
|
|
140
|
-
type McpServeFailure = "config" | "collision" | "listen";
|
|
143
|
+
export type McpServeFailure = "config" | "collision" | "listen";
|
|
141
144
|
/**
|
|
142
145
|
* Options for {@link McpServeError} — the structured `type` discriminator.
|
|
143
146
|
*/
|
|
144
|
-
type McpServeErrorOptions = AIErrorOptions & {
|
|
145
|
-
/** Which class of serve failure occurred. */
|
|
147
|
+
export type McpServeErrorOptions = AIErrorOptions & {
|
|
148
|
+
/** Which class of serve failure occurred. */
|
|
149
|
+
type: McpServeFailure;
|
|
146
150
|
};
|
|
147
151
|
/**
|
|
148
152
|
* `ai.mcp.serve` could not build or start the server. Thrown by `serve()`
|
|
@@ -156,7 +160,7 @@ type McpServeErrorOptions = AIErrorOptions & {
|
|
|
156
160
|
* // two tools share a name — rename one or use a grouped source + prefixToolNames
|
|
157
161
|
* }
|
|
158
162
|
*/
|
|
159
|
-
declare class McpServeError extends AIError {
|
|
163
|
+
export declare class McpServeError extends AIError {
|
|
160
164
|
/** Which class of serve failure occurred. */
|
|
161
165
|
readonly type: McpServeFailure;
|
|
162
166
|
constructor(message: string, options: McpServeErrorOptions);
|
|
@@ -181,13 +185,14 @@ declare class McpServeError extends AIError {
|
|
|
181
185
|
* - `"request-failed"` — the network call itself failed (DNS, connection
|
|
182
186
|
* reset, timeout) or the provider returned a non-OK status.
|
|
183
187
|
*/
|
|
184
|
-
type WebToolFailure = "missing-peer" | "missing-key" | "denied-host" | "invalid-url" | "request-failed";
|
|
188
|
+
export type WebToolFailure = "missing-peer" | "missing-key" | "denied-host" | "invalid-url" | "request-failed";
|
|
185
189
|
/**
|
|
186
190
|
* Options for {@link WebToolError} — the structured `type` discriminator
|
|
187
191
|
* so a caller can branch without parsing the message.
|
|
188
192
|
*/
|
|
189
|
-
type WebToolErrorOptions = AIErrorOptions & {
|
|
190
|
-
/** Which class of web-tool failure occurred. */
|
|
193
|
+
export type WebToolErrorOptions = AIErrorOptions & {
|
|
194
|
+
/** Which class of web-tool failure occurred. */
|
|
195
|
+
type: WebToolFailure;
|
|
191
196
|
};
|
|
192
197
|
/**
|
|
193
198
|
* A web tool failed — a missing optional peer, an absent API key, a host
|
|
@@ -208,7 +213,7 @@ type WebToolErrorOptions = AIErrorOptions & {
|
|
|
208
213
|
* // the host was not in allowHosts — surfaced before any network call
|
|
209
214
|
* }
|
|
210
215
|
*/
|
|
211
|
-
declare class WebToolError extends AIError {
|
|
216
|
+
export declare class WebToolError extends AIError {
|
|
212
217
|
/** Which class of web-tool failure occurred. */
|
|
213
218
|
readonly type: WebToolFailure;
|
|
214
219
|
constructor(message: string, options: WebToolErrorOptions);
|
|
@@ -227,13 +232,14 @@ declare class WebToolError extends AIError {
|
|
|
227
232
|
* - `"invalid-url"` — the supplied URL (or its join with `baseUrl`)
|
|
228
233
|
* could not be parsed into an absolute `http(s)` URL.
|
|
229
234
|
*/
|
|
230
|
-
type HttpPolicyFailure = "method-not-allowed" | "host-not-allowed" | "invalid-url";
|
|
235
|
+
export type HttpPolicyFailure = "method-not-allowed" | "host-not-allowed" | "invalid-url";
|
|
231
236
|
/**
|
|
232
237
|
* Options for {@link HttpPolicyError} — the structured `type`
|
|
233
238
|
* discriminator so a caller can branch without parsing the message.
|
|
234
239
|
*/
|
|
235
|
-
type HttpPolicyErrorOptions = AIErrorOptions & {
|
|
236
|
-
/** Which class of policy rejection occurred. */
|
|
240
|
+
export type HttpPolicyErrorOptions = AIErrorOptions & {
|
|
241
|
+
/** Which class of policy rejection occurred. */
|
|
242
|
+
type: HttpPolicyFailure;
|
|
237
243
|
};
|
|
238
244
|
/**
|
|
239
245
|
* The `http_request` tool refused a call its construction-time policy
|
|
@@ -256,11 +262,10 @@ type HttpPolicyErrorOptions = AIErrorOptions & {
|
|
|
256
262
|
* // the model tried to reach a host outside the configured allowlist
|
|
257
263
|
* }
|
|
258
264
|
*/
|
|
259
|
-
declare class HttpPolicyError extends AIError {
|
|
265
|
+
export declare class HttpPolicyError extends AIError {
|
|
260
266
|
/** Which class of policy rejection occurred. */
|
|
261
267
|
readonly type: HttpPolicyFailure;
|
|
262
268
|
constructor(message: string, options: HttpPolicyErrorOptions);
|
|
263
269
|
}
|
|
264
270
|
//#endregion
|
|
265
|
-
export { CalculatorError, CalculatorErrorOptions, CalculatorFailure, DateTimeError, DateTimeErrorOptions, DateTimeFailure, HttpPolicyError, HttpPolicyErrorOptions, HttpPolicyFailure, McpServeError, McpServeErrorOptions, McpServeFailure, McpTransportError, McpTransportErrorOptions, McpTransportFailure, WebToolError, WebToolErrorOptions, WebToolFailure };
|
|
266
271
|
//# sourceMappingURL=errors.d.mts.map
|
package/esm/errors.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.mts","names":[],"sources":["../../../../../../../packages/ai-tools/src/errors.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"errors.d.mts","names":[],"sources":["../../../../../../../packages/ai-tools/src/errors.ts"],"mappings":";;;;;;;;;;YAUY;;;;;YAMA,yBAAyB;;EAEnC,MAAM;;;;;;;;;;;;;;;;;;;;qBAqBK,wBAAwB;;WAEnB,MAAM;EAEtB,YAAmB,iBAAiB,SAAS;;;;;;;;;;;YAiBnC;;;;;YAUA,uBAAuB;;EAEjC,MAAM;;;;;;;;;;;;;;;;;;qBAmBK,sBAAsB;;WAEjB,MAAM;EAEtB,YAAmB,iBAAiB,SAAS;;;;;;;;;;;;;;YAoBnC;;;;;;YAOA,2BAA2B;;EAErC,MAAM;;EAEN;;;;;;;;;;;;;;;;;;;;;qBAsBW,0BAA0B;;WAErB,MAAM;;WAEN;EAEhB,YAAmB,iBAAiB,SAAS;;;;;;;;;;;;;YAoBnC;;;;YAKA,uBAAuB;;EAEjC,MAAM;;;;;;;;;;;;;;qBAeK,sBAAsB;;WAEjB,MAAM;EAEtB,YAAmB,iBAAiB,SAAS;;;;;;;;;;;;;;;;;;;;;;YA4BnC;;;;;YAWA,sBAAsB;;EAEhC,MAAM;;;;;;;;;;;;;;;;;;;;;qBAsBK,qBAAqB;;WAEhB,MAAM;EAEtB,YAAmB,iBAAiB,SAAS;;;;;;;;;;;;;;;;YAsBnC;;;;;YAMA,yBAAyB;;EAEnC,MAAM;;;;;;;;;;;;;;;;;;;;;;;qBAwBK,wBAAwB;;WAEnB,MAAM;EAEtB,YAAmB,iBAAiB,SAAS"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HttpRequestInput, HttpRequestOptions, HttpRequestResult } from "../contracts/http.type.mjs";
|
|
2
|
+
import "../contracts/index.mjs";
|
|
2
3
|
import { ToolContract } from "@warlock.js/ai";
|
|
3
|
-
|
|
4
4
|
//#region ../../packages/ai-tools/src/http/http-request.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Build the agent-facing `http_request` tool — a guarded HTTP/REST client
|
|
@@ -59,7 +59,6 @@ import { ToolContract } from "@warlock.js/ai";
|
|
|
59
59
|
* });
|
|
60
60
|
* const { data } = await stripe.invoke({ method: "GET", url: "/v1/charges" });
|
|
61
61
|
*/
|
|
62
|
-
declare function httpRequestTool(options?: HttpRequestOptions): ToolContract<HttpRequestInput, HttpRequestResult>;
|
|
62
|
+
export declare function httpRequestTool(options?: HttpRequestOptions): ToolContract<HttpRequestInput, HttpRequestResult>;
|
|
63
63
|
//#endregion
|
|
64
|
-
export { httpRequestTool };
|
|
65
64
|
//# sourceMappingURL=http-request.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-request.d.mts","names":[],"sources":["../../../../../../../../packages/ai-tools/src/http/http-request.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"http-request.d.mts","names":[],"sources":["../../../../../../../../packages/ai-tools/src/http/http-request.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAgNgB,gBACd,UAAS,qBACR,aAAa,kBAAkB"}
|
|
@@ -18,7 +18,7 @@ const ALL_METHODS = [
|
|
|
18
18
|
"DELETE"
|
|
19
19
|
];
|
|
20
20
|
/** Methods that conventionally carry no request body — `body` is dropped for these. */
|
|
21
|
-
const BODYLESS_METHODS = new Set(["GET"]);
|
|
21
|
+
const BODYLESS_METHODS = /* @__PURE__ */ new Set(["GET"]);
|
|
22
22
|
/**
|
|
23
23
|
* Standard Schema for {@link HttpRequestInput}. `url` is required;
|
|
24
24
|
* `method` is constrained to the canonical HTTP verb set (further
|
|
@@ -182,10 +182,12 @@ function httpRequestTool(options = {}) {
|
|
|
182
182
|
...input.headers
|
|
183
183
|
};
|
|
184
184
|
let body;
|
|
185
|
-
if (!BODYLESS_METHODS.has(method) && input.body !== void 0)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
185
|
+
if (!BODYLESS_METHODS.has(method) && input.body !== void 0) {
|
|
186
|
+
if (typeof input.body === "string") body = input.body;
|
|
187
|
+
else {
|
|
188
|
+
body = JSON.stringify(input.body);
|
|
189
|
+
if (!Object.keys(headers).some((key) => key.toLowerCase() === "content-type")) headers["content-type"] = "application/json";
|
|
190
|
+
}
|
|
189
191
|
}
|
|
190
192
|
let response;
|
|
191
193
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-request.mjs","names":[],"sources":["../../../../../../../../packages/ai-tools/src/http/http-request.ts"],"sourcesContent":["import { guardedFetch, OutboundPolicyError, tool, type ToolContract } from \"@warlock.js/ai\";\nimport type {\n HttpMethod,\n HttpRequestInput,\n HttpRequestOptions,\n HttpRequestResult,\n} from \"../contracts\";\nimport { HttpPolicyError } from \"../errors\";\nimport {\n objectSchema,\n optionalStringEnumField,\n optionalStringRecordField,\n passthroughField,\n stringField,\n} from \"../schema\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"http_request\";\n\n/** Default per-request wall-clock timeout, in milliseconds. */\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\n/** Default hard cap on response-body bytes before truncation. */\nconst DEFAULT_MAX_BYTES = 1_000_000;\n\n/** The full set of HTTP methods, in the order they appear in {@link HttpMethod}. */\nconst ALL_METHODS: readonly HttpMethod[] = [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"];\n\n/** Methods that conventionally carry no request body — `body` is dropped for these. */\nconst BODYLESS_METHODS: ReadonlySet<HttpMethod> = new Set<HttpMethod>([\"GET\"]);\n\n/**\n * Standard Schema for {@link HttpRequestInput}. `url` is required;\n * `method` is constrained to the canonical HTTP verb set (further\n * narrowed to the tool's `allowMethods` at runtime); `headers` is an\n * optional string-to-string record; `body` is an opaque passthrough the\n * handler serializes based on its runtime type.\n */\nconst httpRequestInputSchema = objectSchema<HttpRequestInput>({\n method: optionalStringEnumField<HttpMethod>(ALL_METHODS),\n url: stringField(),\n headers: optionalStringRecordField(),\n body: passthroughField(),\n});\n\n/**\n * Resolve the request target. With a `baseUrl` configured the model\n * supplies a path joined against it; otherwise the model's `url` must be\n * an absolute `http(s)` URL. Throws a typed {@link HttpPolicyError} of\n * type `\"invalid-url\"` when the result cannot be parsed or is not an\n * `http`/`https` URL — surfaced as `{ error }` data, never a crash.\n */\nfunction resolveUrl(rawUrl: string, baseUrl: string | undefined): URL {\n let resolved: URL;\n\n try {\n // `new URL(input, base)` joins relative paths against `base` and\n // ignores `base` when `input` is already absolute, which is exactly\n // the \"path vs full URL\" behavior the design specifies.\n resolved = baseUrl !== undefined ? new URL(rawUrl, baseUrl) : new URL(rawUrl);\n } catch {\n throw new HttpPolicyError(\n `http_request could not resolve a valid URL from \"${rawUrl}\"` +\n (baseUrl !== undefined ? ` against base \"${baseUrl}\".` : \".\"),\n { type: \"invalid-url\" },\n );\n }\n\n if (resolved.protocol !== \"http:\" && resolved.protocol !== \"https:\") {\n throw new HttpPolicyError(\n `http_request only permits http(s) URLs; got \"${resolved.protocol}\".`,\n { type: \"invalid-url\" },\n );\n }\n\n return resolved;\n}\n\n/**\n * Read a `Response` body, capping at `maxBytes`. Returns the decoded text\n * and whether it was cut off. Streams chunk-by-chunk so an oversized body\n * is abandoned at the cap rather than fully buffered; falls back to\n * `response.text()` (then a post-hoc byte slice) when the body is not a\n * readable stream (e.g. a stubbed `Response` in tests).\n */\nasync function readCappedBody(\n response: Response,\n maxBytes: number,\n): Promise<{ text: string; truncated: boolean }> {\n const body = response.body;\n\n if (!body) {\n return { text: \"\", truncated: false };\n }\n\n const decoder = new TextDecoder();\n const reader = body.getReader();\n let received = 0;\n let truncated = false;\n let text = \"\";\n\n try {\n for (;;) {\n const { done, value } = await reader.read();\n\n if (done) {\n break;\n }\n\n if (!value) {\n continue;\n }\n\n const remaining = maxBytes - received;\n\n if (value.byteLength > remaining) {\n text += decoder.decode(value.subarray(0, remaining), { stream: true });\n received = maxBytes;\n truncated = true;\n break;\n }\n\n text += decoder.decode(value, { stream: true });\n received += value.byteLength;\n }\n } finally {\n // Release the lock and abandon any unread remainder.\n await reader.cancel().catch(() => undefined);\n reader.releaseLock();\n }\n\n text += decoder.decode();\n\n return { text, truncated };\n}\n\n/**\n * Decide whether a response's `content-type` indicates JSON. Matches\n * `application/json` and the `+json` structured-suffix convention\n * (e.g. `application/vnd.api+json`), case-insensitively.\n */\nfunction isJsonContentType(contentType: string | undefined): boolean {\n if (!contentType) {\n return false;\n }\n\n const value = contentType.toLowerCase();\n\n return value.includes(\"application/json\") || value.includes(\"+json\");\n}\n\n/**\n * Build the agent-facing `http_request` tool — a guarded HTTP/REST client\n * over the global `fetch`. The `options` bound what the model may do; the\n * model supplies the per-call URL / method / headers / body within those\n * rails.\n *\n * **Guardrails (all enforced before the network call).**\n * - **Method allowlist** — `allowMethods` (default `[\"GET\"]`). A method\n * outside the list is rejected with a typed\n * {@link HttpPolicyError} (`type: \"method-not-allowed\"`).\n * - **Private-network deny (default).** The request — and every redirect\n * hop — goes through the framework's `guardedFetch` outbound policy,\n * which refuses private / loopback / link-local / cloud-metadata\n * addresses (and hostnames resolving to them) unless\n * `allowPrivateNetwork: true` is set. This applies even when\n * `allowHosts` is not configured, so a bare `ai.tools.http()` is not\n * an SSRF primitive (`type: \"host-not-allowed\"`).\n * - **Host allowlist** — when `allowHosts` is set, any other host is\n * rejected (`type: \"host-not-allowed\"`), an SSRF guardrail; redirect\n * targets are held to the same allowlist.\n * - **`baseUrl` join** — when configured, the model passes a path that\n * is resolved against `baseUrl`; otherwise it must pass an absolute\n * `http(s)` URL. An unresolvable URL is rejected\n * (`type: \"invalid-url\"`).\n *\n * **Request shaping.** Static `options.headers` are merged under the\n * per-call `headers` (the per-call value wins). An object `body` is\n * JSON-serialized with a `content-type: application/json` default; a\n * string `body` is sent verbatim; `body` is dropped for bodyless methods\n * (`GET`). The call is bounded by `timeoutMs` (default `15_000`) via an\n * `AbortController`, also wired to `ctx.signal` for cooperative\n * cancellation.\n *\n * **Response shaping.** Headers are returned with lower-cased keys. The\n * body is read up to `maxBytes` (default `1_000_000`) and JSON-parsed\n * when the response `content-type` is JSON, otherwise returned as text;\n * `truncated` is `true` when the body was cut off at the cap (a truncated\n * JSON body is returned as the raw partial string, since it can no longer\n * be parsed).\n *\n * **Errors flow as data.** Every guardrail rejection and network failure\n * is thrown inside `execute`; the framework's `tool()` wrapper catches it\n * and surfaces it in the returned `{ error }` field, so the agent reads\n * the failure and self-corrects rather than crashing.\n *\n * @param options - Construction-time policy bounding the tool.\n * @returns A {@link ToolContract} the agent can call as `http_request`.\n *\n * @example\n * const stripe = httpRequestTool({\n * baseUrl: \"https://api.stripe.com\",\n * allowHosts: [\"api.stripe.com\"],\n * allowMethods: [\"GET\", \"POST\"],\n * headers: { authorization: `Bearer ${process.env.STRIPE_KEY}` },\n * });\n * const { data } = await stripe.invoke({ method: \"GET\", url: \"/v1/charges\" });\n */\nexport function httpRequestTool(\n options: HttpRequestOptions = {},\n): ToolContract<HttpRequestInput, HttpRequestResult> {\n const allowMethods = options.allowMethods ?? [\"GET\"];\n const allowedMethodSet = new Set<HttpMethod>(allowMethods);\n const allowHostSet = options.allowHosts ? new Set(options.allowHosts) : undefined;\n const allowPrivateNetwork = options.allowPrivateNetwork ?? false;\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n const staticHeaders = options.headers;\n\n return tool<HttpRequestInput, HttpRequestResult>({\n name: options.name ?? DEFAULT_NAME,\n description:\n \"Issue an HTTP request and return the status, response headers, and \" +\n \"parsed body. Allowed methods and hosts are restricted by the tool's \" +\n \"configuration; a request outside those rails is rejected before any \" +\n \"network call. Pass an object body to send JSON, or a string to send \" +\n \"it verbatim. The response body is JSON-parsed when the content-type \" +\n \"is JSON, otherwise returned as text, and is capped — `truncated` is \" +\n \"true when the body was cut off.\",\n action: (input) => `Requesting ${input.method ?? \"GET\"} ${input.url}`,\n input: httpRequestInputSchema,\n async execute(input, ctx) {\n const method: HttpMethod = input.method ?? \"GET\";\n\n // 1. Method allowlist — rejected before anything else.\n if (!allowedMethodSet.has(method)) {\n throw new HttpPolicyError(\n `http_request method \"${method}\" is not allowed. ` +\n `Permitted methods: ${[...allowedMethodSet].join(\", \")}.`,\n { type: \"method-not-allowed\" },\n );\n }\n\n // 2. URL resolution (baseUrl join when configured).\n const url = resolveUrl(input.url, options.baseUrl);\n\n // 3. Host allowlist — SSRF guardrail, before the fetch.\n if (allowHostSet && !allowHostSet.has(url.hostname)) {\n throw new HttpPolicyError(\n `http_request host \"${url.hostname}\" is not in the allowlist. ` +\n `Permitted hosts: ${[...allowHostSet].join(\", \")}.`,\n { type: \"host-not-allowed\" },\n );\n }\n\n // 4. Merge headers — static option headers under the per-call ones,\n // so a per-call header overrides a static default of the same name.\n const headers: Record<string, string> = { ...staticHeaders, ...input.headers };\n\n // 5. Shape the body. Dropped for bodyless methods; objects become\n // JSON (with a default content-type); strings are sent verbatim.\n let body: string | undefined;\n\n if (!BODYLESS_METHODS.has(method) && input.body !== undefined) {\n if (typeof input.body === \"string\") {\n body = input.body;\n } else {\n body = JSON.stringify(input.body);\n\n const hasContentType = Object.keys(headers).some(\n (key) => key.toLowerCase() === \"content-type\",\n );\n\n if (!hasContentType) {\n headers[\"content-type\"] = \"application/json\";\n }\n }\n }\n\n // 6. Issue the request through the shared hardened outbound path:\n // private-IP deny by default, per-hop redirect re-validation,\n // timeout, and the caller's signal — never a raw fetch.\n let response: Response;\n\n try {\n response = await guardedFetch(\n url.toString(),\n {\n allowedSchemes: [\"http\", \"https\"],\n hostAllowlist: options.allowHosts,\n denyPrivateIPsAfterDNS: !allowPrivateNetwork,\n timeoutMs,\n signal: ctx?.signal,\n },\n { method, headers, body },\n );\n } catch (cause) {\n if (cause instanceof OutboundPolicyError) {\n throw new HttpPolicyError(`http_request blocked: ${cause.message}`, {\n type: \"host-not-allowed\",\n cause,\n });\n }\n\n throw cause;\n }\n\n // 7. Collect response headers with lower-cased keys.\n const responseHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n responseHeaders[key.toLowerCase()] = value;\n });\n\n // 8. Read the body up to the cap, then parse-or-pass.\n const { text, truncated } = await readCappedBody(response, maxBytes);\n\n let parsedBody: unknown = text;\n\n // A truncated body can no longer be valid JSON, so only attempt a\n // parse on a complete JSON response; otherwise hand back the raw text.\n if (!truncated && isJsonContentType(responseHeaders[\"content-type\"]) && text.length > 0) {\n try {\n parsedBody = JSON.parse(text);\n } catch {\n // Content-type claimed JSON but the body was not — fall back to\n // the raw text rather than failing the whole call.\n parsedBody = text;\n }\n }\n\n return {\n status: response.status,\n headers: responseHeaders,\n body: parsedBody,\n truncated,\n };\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"http-request.mjs","names":[],"sources":["../../../../../../../../packages/ai-tools/src/http/http-request.ts"],"sourcesContent":["import { guardedFetch, OutboundPolicyError, tool, type ToolContract } from \"@warlock.js/ai\";\nimport type {\n HttpMethod,\n HttpRequestInput,\n HttpRequestOptions,\n HttpRequestResult,\n} from \"../contracts\";\nimport { HttpPolicyError } from \"../errors\";\nimport {\n objectSchema,\n optionalStringEnumField,\n optionalStringRecordField,\n passthroughField,\n stringField,\n} from \"../schema\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"http_request\";\n\n/** Default per-request wall-clock timeout, in milliseconds. */\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\n/** Default hard cap on response-body bytes before truncation. */\nconst DEFAULT_MAX_BYTES = 1_000_000;\n\n/** The full set of HTTP methods, in the order they appear in {@link HttpMethod}. */\nconst ALL_METHODS: readonly HttpMethod[] = [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"];\n\n/** Methods that conventionally carry no request body — `body` is dropped for these. */\nconst BODYLESS_METHODS: ReadonlySet<HttpMethod> = new Set<HttpMethod>([\"GET\"]);\n\n/**\n * Standard Schema for {@link HttpRequestInput}. `url` is required;\n * `method` is constrained to the canonical HTTP verb set (further\n * narrowed to the tool's `allowMethods` at runtime); `headers` is an\n * optional string-to-string record; `body` is an opaque passthrough the\n * handler serializes based on its runtime type.\n */\nconst httpRequestInputSchema = objectSchema<HttpRequestInput>({\n method: optionalStringEnumField<HttpMethod>(ALL_METHODS),\n url: stringField(),\n headers: optionalStringRecordField(),\n body: passthroughField(),\n});\n\n/**\n * Resolve the request target. With a `baseUrl` configured the model\n * supplies a path joined against it; otherwise the model's `url` must be\n * an absolute `http(s)` URL. Throws a typed {@link HttpPolicyError} of\n * type `\"invalid-url\"` when the result cannot be parsed or is not an\n * `http`/`https` URL — surfaced as `{ error }` data, never a crash.\n */\nfunction resolveUrl(rawUrl: string, baseUrl: string | undefined): URL {\n let resolved: URL;\n\n try {\n // `new URL(input, base)` joins relative paths against `base` and\n // ignores `base` when `input` is already absolute, which is exactly\n // the \"path vs full URL\" behavior the design specifies.\n resolved = baseUrl !== undefined ? new URL(rawUrl, baseUrl) : new URL(rawUrl);\n } catch {\n throw new HttpPolicyError(\n `http_request could not resolve a valid URL from \"${rawUrl}\"` +\n (baseUrl !== undefined ? ` against base \"${baseUrl}\".` : \".\"),\n { type: \"invalid-url\" },\n );\n }\n\n if (resolved.protocol !== \"http:\" && resolved.protocol !== \"https:\") {\n throw new HttpPolicyError(\n `http_request only permits http(s) URLs; got \"${resolved.protocol}\".`,\n { type: \"invalid-url\" },\n );\n }\n\n return resolved;\n}\n\n/**\n * Read a `Response` body, capping at `maxBytes`. Returns the decoded text\n * and whether it was cut off. Streams chunk-by-chunk so an oversized body\n * is abandoned at the cap rather than fully buffered; falls back to\n * `response.text()` (then a post-hoc byte slice) when the body is not a\n * readable stream (e.g. a stubbed `Response` in tests).\n */\nasync function readCappedBody(\n response: Response,\n maxBytes: number,\n): Promise<{ text: string; truncated: boolean }> {\n const body = response.body;\n\n if (!body) {\n return { text: \"\", truncated: false };\n }\n\n const decoder = new TextDecoder();\n const reader = body.getReader();\n let received = 0;\n let truncated = false;\n let text = \"\";\n\n try {\n for (;;) {\n const { done, value } = await reader.read();\n\n if (done) {\n break;\n }\n\n if (!value) {\n continue;\n }\n\n const remaining = maxBytes - received;\n\n if (value.byteLength > remaining) {\n text += decoder.decode(value.subarray(0, remaining), { stream: true });\n received = maxBytes;\n truncated = true;\n break;\n }\n\n text += decoder.decode(value, { stream: true });\n received += value.byteLength;\n }\n } finally {\n // Release the lock and abandon any unread remainder.\n await reader.cancel().catch(() => undefined);\n reader.releaseLock();\n }\n\n text += decoder.decode();\n\n return { text, truncated };\n}\n\n/**\n * Decide whether a response's `content-type` indicates JSON. Matches\n * `application/json` and the `+json` structured-suffix convention\n * (e.g. `application/vnd.api+json`), case-insensitively.\n */\nfunction isJsonContentType(contentType: string | undefined): boolean {\n if (!contentType) {\n return false;\n }\n\n const value = contentType.toLowerCase();\n\n return value.includes(\"application/json\") || value.includes(\"+json\");\n}\n\n/**\n * Build the agent-facing `http_request` tool — a guarded HTTP/REST client\n * over the global `fetch`. The `options` bound what the model may do; the\n * model supplies the per-call URL / method / headers / body within those\n * rails.\n *\n * **Guardrails (all enforced before the network call).**\n * - **Method allowlist** — `allowMethods` (default `[\"GET\"]`). A method\n * outside the list is rejected with a typed\n * {@link HttpPolicyError} (`type: \"method-not-allowed\"`).\n * - **Private-network deny (default).** The request — and every redirect\n * hop — goes through the framework's `guardedFetch` outbound policy,\n * which refuses private / loopback / link-local / cloud-metadata\n * addresses (and hostnames resolving to them) unless\n * `allowPrivateNetwork: true` is set. This applies even when\n * `allowHosts` is not configured, so a bare `ai.tools.http()` is not\n * an SSRF primitive (`type: \"host-not-allowed\"`).\n * - **Host allowlist** — when `allowHosts` is set, any other host is\n * rejected (`type: \"host-not-allowed\"`), an SSRF guardrail; redirect\n * targets are held to the same allowlist.\n * - **`baseUrl` join** — when configured, the model passes a path that\n * is resolved against `baseUrl`; otherwise it must pass an absolute\n * `http(s)` URL. An unresolvable URL is rejected\n * (`type: \"invalid-url\"`).\n *\n * **Request shaping.** Static `options.headers` are merged under the\n * per-call `headers` (the per-call value wins). An object `body` is\n * JSON-serialized with a `content-type: application/json` default; a\n * string `body` is sent verbatim; `body` is dropped for bodyless methods\n * (`GET`). The call is bounded by `timeoutMs` (default `15_000`) via an\n * `AbortController`, also wired to `ctx.signal` for cooperative\n * cancellation.\n *\n * **Response shaping.** Headers are returned with lower-cased keys. The\n * body is read up to `maxBytes` (default `1_000_000`) and JSON-parsed\n * when the response `content-type` is JSON, otherwise returned as text;\n * `truncated` is `true` when the body was cut off at the cap (a truncated\n * JSON body is returned as the raw partial string, since it can no longer\n * be parsed).\n *\n * **Errors flow as data.** Every guardrail rejection and network failure\n * is thrown inside `execute`; the framework's `tool()` wrapper catches it\n * and surfaces it in the returned `{ error }` field, so the agent reads\n * the failure and self-corrects rather than crashing.\n *\n * @param options - Construction-time policy bounding the tool.\n * @returns A {@link ToolContract} the agent can call as `http_request`.\n *\n * @example\n * const stripe = httpRequestTool({\n * baseUrl: \"https://api.stripe.com\",\n * allowHosts: [\"api.stripe.com\"],\n * allowMethods: [\"GET\", \"POST\"],\n * headers: { authorization: `Bearer ${process.env.STRIPE_KEY}` },\n * });\n * const { data } = await stripe.invoke({ method: \"GET\", url: \"/v1/charges\" });\n */\nexport function httpRequestTool(\n options: HttpRequestOptions = {},\n): ToolContract<HttpRequestInput, HttpRequestResult> {\n const allowMethods = options.allowMethods ?? [\"GET\"];\n const allowedMethodSet = new Set<HttpMethod>(allowMethods);\n const allowHostSet = options.allowHosts ? new Set(options.allowHosts) : undefined;\n const allowPrivateNetwork = options.allowPrivateNetwork ?? false;\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n const staticHeaders = options.headers;\n\n return tool<HttpRequestInput, HttpRequestResult>({\n name: options.name ?? DEFAULT_NAME,\n description:\n \"Issue an HTTP request and return the status, response headers, and \" +\n \"parsed body. Allowed methods and hosts are restricted by the tool's \" +\n \"configuration; a request outside those rails is rejected before any \" +\n \"network call. Pass an object body to send JSON, or a string to send \" +\n \"it verbatim. The response body is JSON-parsed when the content-type \" +\n \"is JSON, otherwise returned as text, and is capped — `truncated` is \" +\n \"true when the body was cut off.\",\n action: (input) => `Requesting ${input.method ?? \"GET\"} ${input.url}`,\n input: httpRequestInputSchema,\n async execute(input, ctx) {\n const method: HttpMethod = input.method ?? \"GET\";\n\n // 1. Method allowlist — rejected before anything else.\n if (!allowedMethodSet.has(method)) {\n throw new HttpPolicyError(\n `http_request method \"${method}\" is not allowed. ` +\n `Permitted methods: ${[...allowedMethodSet].join(\", \")}.`,\n { type: \"method-not-allowed\" },\n );\n }\n\n // 2. URL resolution (baseUrl join when configured).\n const url = resolveUrl(input.url, options.baseUrl);\n\n // 3. Host allowlist — SSRF guardrail, before the fetch.\n if (allowHostSet && !allowHostSet.has(url.hostname)) {\n throw new HttpPolicyError(\n `http_request host \"${url.hostname}\" is not in the allowlist. ` +\n `Permitted hosts: ${[...allowHostSet].join(\", \")}.`,\n { type: \"host-not-allowed\" },\n );\n }\n\n // 4. Merge headers — static option headers under the per-call ones,\n // so a per-call header overrides a static default of the same name.\n const headers: Record<string, string> = { ...staticHeaders, ...input.headers };\n\n // 5. Shape the body. Dropped for bodyless methods; objects become\n // JSON (with a default content-type); strings are sent verbatim.\n let body: string | undefined;\n\n if (!BODYLESS_METHODS.has(method) && input.body !== undefined) {\n if (typeof input.body === \"string\") {\n body = input.body;\n } else {\n body = JSON.stringify(input.body);\n\n const hasContentType = Object.keys(headers).some(\n (key) => key.toLowerCase() === \"content-type\",\n );\n\n if (!hasContentType) {\n headers[\"content-type\"] = \"application/json\";\n }\n }\n }\n\n // 6. Issue the request through the shared hardened outbound path:\n // private-IP deny by default, per-hop redirect re-validation,\n // timeout, and the caller's signal — never a raw fetch.\n let response: Response;\n\n try {\n response = await guardedFetch(\n url.toString(),\n {\n allowedSchemes: [\"http\", \"https\"],\n hostAllowlist: options.allowHosts,\n denyPrivateIPsAfterDNS: !allowPrivateNetwork,\n timeoutMs,\n signal: ctx?.signal,\n },\n { method, headers, body },\n );\n } catch (cause) {\n if (cause instanceof OutboundPolicyError) {\n throw new HttpPolicyError(`http_request blocked: ${cause.message}`, {\n type: \"host-not-allowed\",\n cause,\n });\n }\n\n throw cause;\n }\n\n // 7. Collect response headers with lower-cased keys.\n const responseHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n responseHeaders[key.toLowerCase()] = value;\n });\n\n // 8. Read the body up to the cap, then parse-or-pass.\n const { text, truncated } = await readCappedBody(response, maxBytes);\n\n let parsedBody: unknown = text;\n\n // A truncated body can no longer be valid JSON, so only attempt a\n // parse on a complete JSON response; otherwise hand back the raw text.\n if (!truncated && isJsonContentType(responseHeaders[\"content-type\"]) && text.length > 0) {\n try {\n parsedBody = JSON.parse(text);\n } catch {\n // Content-type claimed JSON but the body was not — fall back to\n // the raw text rather than failing the whole call.\n parsedBody = text;\n }\n }\n\n return {\n status: response.status,\n headers: responseHeaders,\n body: parsedBody,\n truncated,\n };\n },\n });\n}\n"],"mappings":"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"}
|
package/esm/index.d.mts
CHANGED
|
@@ -2,9 +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, McpHttpServeHandle, McpHttpServeTransport, McpPromptArgument, McpPromptMessage, McpServeCallContext, McpServeHandle, McpServeOptions, McpServePrompt, McpServeResource, McpServeSource, McpServeToolContext, McpServeTransport, McpServer, McpStdioServeTransport, McpToolSource } from "./contracts/mcp-serve.type.mjs";
|
|
6
|
+
import "./contracts/index.mjs";
|
|
6
7
|
import { McpServeRequestHandler, createServeHandler, serve } from "./mcp/serve.mjs";
|
|
7
8
|
import { SUPPORTED_PROTOCOL_VERSIONS } from "./mcp/protocol.mjs";
|
|
9
|
+
import { McpNativeResult, mcpResult } from "./mcp/result.mjs";
|
|
8
10
|
import { jsonSchemaToStandard } from "./mcp/json-schema-to-standard.mjs";
|
|
9
11
|
import { McpTransportClient } from "./mcp/transport.type.mjs";
|
|
10
12
|
import { JsonRpcClientHandle, createJsonRpcClient, createTransport } from "./mcp/transport.mjs";
|
|
@@ -12,10 +14,12 @@ import { McpFactory, mcp } from "./mcp/index.mjs";
|
|
|
12
14
|
import { AiToolsNamespace } from "./register.mjs";
|
|
13
15
|
import { webSearchTool } from "./web/web-search.mjs";
|
|
14
16
|
import { fetchUrlTool } from "./web/fetch-url.mjs";
|
|
17
|
+
import "./web/index.mjs";
|
|
15
18
|
import { httpRequestTool } from "./http/http-request.mjs";
|
|
16
19
|
import { calculatorTool } from "./utility/calculator.mjs";
|
|
17
20
|
import { dateTimeTool } from "./utility/date-time.mjs";
|
|
21
|
+
import "./utility/index.mjs";
|
|
18
22
|
import { CalculatorError, CalculatorErrorOptions, CalculatorFailure, DateTimeError, DateTimeErrorOptions, DateTimeFailure, HttpPolicyError, HttpPolicyErrorOptions, HttpPolicyFailure, McpServeError, McpServeErrorOptions, McpServeFailure, McpTransportError, McpTransportErrorOptions, McpTransportFailure, WebToolError, WebToolErrorOptions, WebToolFailure } from "./errors.mjs";
|
|
19
|
-
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 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, serve, webSearchTool };
|
|
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 McpBeforeCall, type McpCallRefusal, 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 McpServeToolContext, type McpServeTransport, type McpServer, type McpStdioServeTransport, 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 };
|
|
20
24
|
import "./mcp/index.mjs";
|
|
21
25
|
import "./register.mjs";
|
package/esm/index.mjs
CHANGED
|
@@ -3,6 +3,7 @@ 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
5
|
import { SUPPORTED_PROTOCOL_VERSIONS } from "./mcp/protocol.mjs";
|
|
6
|
+
import { mcpResult } from "./mcp/result.mjs";
|
|
6
7
|
import { createServeHandler, serve } from "./mcp/serve.mjs";
|
|
7
8
|
import { mcp } from "./mcp/index.mjs";
|
|
8
9
|
import { calculatorTool } from "./utility/calculator.mjs";
|
|
@@ -13,4 +14,4 @@ import "./register.mjs";
|
|
|
13
14
|
import "./web/index.mjs";
|
|
14
15
|
import "./utility/index.mjs";
|
|
15
16
|
|
|
16
|
-
export { CalculatorError, DateTimeError, HttpPolicyError, McpServeError, McpTransportError, SUPPORTED_PROTOCOL_VERSIONS, WebToolError, calculatorTool, createJsonRpcClient, createServeHandler, createTransport, dateTimeTool, fetchUrlTool, httpRequestTool, jsonSchemaToStandard, mcp, serve, webSearchTool };
|
|
17
|
+
export { CalculatorError, DateTimeError, HttpPolicyError, McpServeError, McpTransportError, SUPPORTED_PROTOCOL_VERSIONS, WebToolError, calculatorTool, createJsonRpcClient, createServeHandler, createTransport, dateTimeTool, fetchUrlTool, httpRequestTool, jsonSchemaToStandard, mcp, mcpResult, serve, webSearchTool };
|
package/esm/mcp/client.mjs
CHANGED
|
@@ -60,7 +60,8 @@ var McpClientImpl = class {
|
|
|
60
60
|
await this.rpc.notify("notifications/initialized");
|
|
61
61
|
} catch (cause) {
|
|
62
62
|
if (cause instanceof McpTransportError) throw cause;
|
|
63
|
-
|
|
63
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
64
|
+
throw new McpTransportError(`MCP initialize handshake failed: ${message}`, {
|
|
64
65
|
type: "connect",
|
|
65
66
|
method: "initialize",
|
|
66
67
|
cause
|
package/esm/mcp/client.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.mjs","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/client.ts"],"sourcesContent":["import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport type {\n McpClient,\n McpClientOptions,\n McpContentBlock,\n McpToolCallResult,\n McpToolDescriptor,\n McpTransport,\n} from \"../contracts\";\nimport { McpTransportError } from \"../errors\";\nimport { jsonSchemaToStandard } from \"./json-schema-to-standard\";\nimport { createJsonRpcClient, type JsonRpcClientHandle } from \"./transport\";\nimport type { McpTransportClient } from \"./transport.type\";\n\n/** Default per-call timeout for `tools/call`. */\nconst DEFAULT_CALL_TIMEOUT_MS = 30_000;\n\n/** The MCP protocol version this client advertises in `initialize`. */\nconst PROTOCOL_VERSION = \"2025-06-18\";\n\n/** The `tools/list` response slice we read. */\ninterface ToolsListResult {\n tools?: McpToolDescriptor[];\n}\n\n/**\n * The internal {@link McpClient} implementation. Owns one JSON-RPC client\n * over a transport, runs the `initialize` handshake on first use, lists\n * the server's tools, and adapts each into a {@link ToolContract} whose\n * `execute` issues `tools/call`. The adapted contracts are cached after\n * the first `tools()` so repeat calls don't re-handshake.\n *\n * Constructed via {@link mcp}; the class itself is internal.\n */\nclass McpClientImpl implements McpClient {\n /** The JSON-RPC client over the transport. */\n private readonly rpc: JsonRpcClientHandle;\n\n /** Construction-time options (prefix / filter / timeout). */\n private readonly options: McpClientOptions;\n\n /** Resolved + cached adapted tools, set after the first `tools()`. */\n private cached: ToolContract[] | undefined;\n\n /** In-flight `tools()` so concurrent callers share one handshake. */\n private pending: Promise<ToolContract[]> | undefined;\n\n /** Flipped once the handshake completes so we only do it once. */\n private initialized = false;\n\n /** The in-flight / settled `close()`, so closing is idempotent. */\n private closing: Promise<void> | undefined;\n\n /** Closes the client when `options.signal` aborts. */\n private readonly onAbort = (): void => {\n void this.close().catch(() => undefined);\n };\n\n public constructor(source: McpTransport | McpTransportClient, options: McpClientOptions = {}) {\n this.rpc = createJsonRpcClient(source);\n this.options = options;\n\n const signal = options.signal;\n\n if (signal?.aborted) {\n this.onAbort();\n } else {\n signal?.addEventListener(\"abort\", this.onAbort, { once: true });\n }\n }\n\n /**\n * Reject with a typed `\"closed\"` error once the client was closed or its\n * `signal` aborted, so no request is sent on a dead transport.\n */\n private assertOpen(method: string): void {\n if (this.closing || this.options.signal?.aborted) {\n throw new McpTransportError(`MCP client is closed; \"${method}\" was not sent.`, {\n type: \"closed\",\n method,\n });\n }\n }\n\n /**\n * Run the MCP `initialize` handshake exactly once, then send the\n * `notifications/initialized` notification the protocol requires before\n * any other request. Wraps a handshake failure as a typed\n * {@link McpTransportError} of type `\"connect\"`.\n */\n private async handshake(): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n this.assertOpen(\"initialize\");\n\n try {\n await this.rpc.call(\n \"initialize\",\n {\n protocolVersion: PROTOCOL_VERSION,\n capabilities: {},\n clientInfo: { name: \"@warlock.js/ai-tools\", version: \"4.4.0\" },\n },\n { signal: this.options.signal },\n );\n\n await this.rpc.notify(\"notifications/initialized\");\n } catch (cause) {\n if (cause instanceof McpTransportError) {\n throw cause;\n }\n\n const message = cause instanceof Error ? cause.message : String(cause);\n\n throw new McpTransportError(`MCP initialize handshake failed: ${message}`, {\n type: \"connect\",\n method: \"initialize\",\n cause,\n });\n }\n\n this.initialized = true;\n }\n\n public tools(): Promise<ToolContract[]> {\n if (this.cached) {\n return Promise.resolve(this.cached);\n }\n\n if (this.pending) {\n return this.pending;\n }\n\n this.pending = this.listAndAdapt()\n .then((tools) => {\n this.cached = tools;\n\n return tools;\n })\n .finally(() => {\n this.pending = undefined;\n });\n\n return this.pending;\n }\n\n /**\n * Handshake, `tools/list`, and adapt each descriptor into a\n * {@link ToolContract}, applying the `filter` and `namePrefix` options.\n */\n private async listAndAdapt(): Promise<ToolContract[]> {\n await this.handshake();\n\n this.assertOpen(\"tools/list\");\n\n const result = await this.rpc.call<ToolsListResult>(\"tools/list\", undefined, {\n signal: this.options.signal,\n });\n const descriptors = result.tools ?? [];\n\n const filter = this.options.filter;\n const selected = filter ? descriptors.filter((d) => filter(d.name)) : descriptors;\n\n return selected.map((descriptor) => this.adapt(descriptor));\n }\n\n /**\n * Adapt one remote tool descriptor into a {@link ToolContract}: build\n * the input schema from its JSON Schema via {@link jsonSchemaToStandard},\n * prefix the name, and route `execute` through a `tools/call` that\n * honors `ctx.signal`, unwraps the content blocks, and throws on an\n * `isError` result so `tool()` wraps it.\n */\n private adapt(descriptor: McpToolDescriptor): ToolContract {\n const prefixedName = `${this.options.namePrefix ?? \"\"}${descriptor.name}`;\n const remoteName = descriptor.name;\n const timeoutMs = this.options.timeoutMs ?? DEFAULT_CALL_TIMEOUT_MS;\n const input = jsonSchemaToStandard(descriptor.inputSchema);\n\n return tool<unknown, unknown>({\n name: prefixedName,\n description: descriptor.description ?? `Invoke the remote MCP tool \"${remoteName}\".`,\n input,\n execute: async (args, ctx) => {\n this.assertOpen(\"tools/call\");\n\n const result = await this.rpc.call<McpToolCallResult>(\n \"tools/call\",\n { name: remoteName, arguments: args ?? {} },\n { signal: combineSignals(ctx?.signal, this.options.signal), timeoutMs },\n );\n\n // An `isError` result is a tool-level failure — throw it so the\n // surrounding `tool()` wraps it as a `ToolExecutionError` and the\n // agent reads it as `{ error }` data and self-corrects.\n if (result.isError) {\n throw new McpTransportError(\n `MCP tool \"${remoteName}\" returned an error: ${unwrapContent(result.content)}`,\n { type: \"protocol\", method: \"tools/call\" },\n );\n }\n\n return unwrapResult(result.content);\n },\n });\n }\n\n public close(): Promise<void> {\n this.options.signal?.removeEventListener(\"abort\", this.onAbort);\n this.closing ??= this.rpc.close();\n\n return this.closing;\n }\n}\n\n/**\n * The signal a `tools/call` honors: the per-call `ctx.signal`, the\n * client-level `options.signal`, both (whichever aborts first), or none.\n */\nfunction combineSignals(\n callSignal: AbortSignal | undefined,\n clientSignal: AbortSignal | undefined,\n): AbortSignal | undefined {\n if (!callSignal || !clientSignal) {\n return callSignal ?? clientSignal;\n }\n\n return AbortSignal.any([callSignal, clientSignal]);\n}\n\n/**\n * Flatten an MCP `tools/call` result's content blocks into the value a\n * tool returns. Text blocks are concatenated; a single block whose text is\n * valid JSON is parsed so structured tool output flows back as an object\n * rather than a string. Non-text blocks are preserved as `type`-tagged\n * objects (MCP's wire `type` is kept; any inbound `kind` is normalized to\n * `type`).\n */\nfunction unwrapResult(content: McpContentBlock[] | undefined): unknown {\n const blocks = content ?? [];\n\n // The overwhelmingly common case: a single text block. Parse JSON when\n // it is one, so structured results come back typed; otherwise the string.\n if (blocks.length === 1 && blocks[0].type === \"text\") {\n const text = blocks[0].text ?? \"\";\n\n return tryParseJson(text);\n }\n\n // Multiple / mixed blocks: return a normalized array, each tagged by\n // `type` (never `kind`).\n return blocks.map((block) => normalizeBlock(block));\n}\n\n/**\n * Render content blocks to a short human string for error messages — the\n * concatenated text of every text block.\n */\nfunction unwrapContent(content: McpContentBlock[] | undefined): string {\n return (content ?? [])\n .filter((block) => block.type === \"text\" && typeof block.text === \"string\")\n .map((block) => block.text)\n .join(\" \")\n .trim();\n}\n\n/**\n * Normalize one content block onto our `type`-only shape: translate an\n * inbound `kind` discriminator to `type` (and strip `kind`) so the value a\n * tool returns never carries MCP's `kind` vocabulary.\n */\nfunction normalizeBlock(block: McpContentBlock): Record<string, unknown> {\n const { kind, ...rest } = block as McpContentBlock & { kind?: string };\n const type = block.type ?? kind ?? \"unknown\";\n\n return { ...rest, type };\n}\n\n/**\n * Parse a string as JSON, returning the parsed value on success or the\n * original string when it is not JSON — so a plain-text tool result stays\n * a string while a JSON tool result becomes an object.\n */\nfunction tryParseJson(text: string): unknown {\n const trimmed = text.trim();\n\n if (!trimmed) {\n return text;\n }\n\n const first = trimmed[0];\n\n // Only attempt a parse for plausibly-structured payloads, so a bare\n // sentence isn't mangled by a lenient parse.\n if (first !== \"{\" && first !== \"[\") {\n return text;\n }\n\n try {\n return JSON.parse(trimmed);\n } catch {\n return text;\n }\n}\n\n/**\n * Connect to an external MCP server and adapt its tools as agent tools\n * (Direction A: server → local agent tools).\n *\n * Opens the transport lazily and exposes {@link McpClient.tools}, which on\n * first call runs the `initialize` handshake, lists the server's tools via\n * `tools/list`, and maps each into a {@link ToolContract}:\n * - **input schema** — the remote tool's JSON Schema is wrapped as a\n * Standard Schema via {@link jsonSchemaToStandard} (Ajv-backed, an\n * optional peer);\n * - **execute** — issues `tools/call` honoring `ctx.signal` and the\n * configured `timeoutMs`, unwraps the result content, and throws on an\n * `isError` result so the `tool()` wrapper surfaces it as `{ error }`;\n * - **name** — prefixed with `options.namePrefix` to avoid local\n * collisions; only tools passing `options.filter` are adapted.\n *\n * The adapted contracts are cached after the first `tools()` call, so the\n * handshake + list happen exactly once. The returned contracts drop\n * straight into `ai.agent({ tools: [...] })`.\n *\n * @param server - The transport config (`{ type: \"stdio\" }` /\n * `{ type: \"http\" }`). A pre-built transport client may be injected for\n * testing.\n * @param options - Prefix / filter / per-call timeout, and a `signal` that\n * closes the client when it aborts.\n * @returns An {@link McpClient} handle.\n *\n * @example\n * const github = mcp(\n * { type: \"stdio\", command: \"npx\", args: [\"-y\", \"@modelcontextprotocol/server-github\"] },\n * { namePrefix: \"github.\" },\n * );\n * const dev = ai.agent({ model, tools: [...(await github.tools())] });\n */\nexport function mcp(\n server: McpTransport | McpTransportClient,\n options?: McpClientOptions,\n): McpClient {\n return new McpClientImpl(server, options);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.mjs","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/client.ts"],"sourcesContent":["import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport type {\n McpClient,\n McpClientOptions,\n McpContentBlock,\n McpToolCallResult,\n McpToolDescriptor,\n McpTransport,\n} from \"../contracts\";\nimport { McpTransportError } from \"../errors\";\nimport { jsonSchemaToStandard } from \"./json-schema-to-standard\";\nimport { createJsonRpcClient, type JsonRpcClientHandle } from \"./transport\";\nimport type { McpTransportClient } from \"./transport.type\";\n\n/** Default per-call timeout for `tools/call`. */\nconst DEFAULT_CALL_TIMEOUT_MS = 30_000;\n\n/** The MCP protocol version this client advertises in `initialize`. */\nconst PROTOCOL_VERSION = \"2025-06-18\";\n\n/** The `tools/list` response slice we read. */\ninterface ToolsListResult {\n tools?: McpToolDescriptor[];\n}\n\n/**\n * The internal {@link McpClient} implementation. Owns one JSON-RPC client\n * over a transport, runs the `initialize` handshake on first use, lists\n * the server's tools, and adapts each into a {@link ToolContract} whose\n * `execute` issues `tools/call`. The adapted contracts are cached after\n * the first `tools()` so repeat calls don't re-handshake.\n *\n * Constructed via {@link mcp}; the class itself is internal.\n */\nclass McpClientImpl implements McpClient {\n /** The JSON-RPC client over the transport. */\n private readonly rpc: JsonRpcClientHandle;\n\n /** Construction-time options (prefix / filter / timeout). */\n private readonly options: McpClientOptions;\n\n /** Resolved + cached adapted tools, set after the first `tools()`. */\n private cached: ToolContract[] | undefined;\n\n /** In-flight `tools()` so concurrent callers share one handshake. */\n private pending: Promise<ToolContract[]> | undefined;\n\n /** Flipped once the handshake completes so we only do it once. */\n private initialized = false;\n\n /** The in-flight / settled `close()`, so closing is idempotent. */\n private closing: Promise<void> | undefined;\n\n /** Closes the client when `options.signal` aborts. */\n private readonly onAbort = (): void => {\n void this.close().catch(() => undefined);\n };\n\n public constructor(source: McpTransport | McpTransportClient, options: McpClientOptions = {}) {\n this.rpc = createJsonRpcClient(source);\n this.options = options;\n\n const signal = options.signal;\n\n if (signal?.aborted) {\n this.onAbort();\n } else {\n signal?.addEventListener(\"abort\", this.onAbort, { once: true });\n }\n }\n\n /**\n * Reject with a typed `\"closed\"` error once the client was closed or its\n * `signal` aborted, so no request is sent on a dead transport.\n */\n private assertOpen(method: string): void {\n if (this.closing || this.options.signal?.aborted) {\n throw new McpTransportError(`MCP client is closed; \"${method}\" was not sent.`, {\n type: \"closed\",\n method,\n });\n }\n }\n\n /**\n * Run the MCP `initialize` handshake exactly once, then send the\n * `notifications/initialized` notification the protocol requires before\n * any other request. Wraps a handshake failure as a typed\n * {@link McpTransportError} of type `\"connect\"`.\n */\n private async handshake(): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n this.assertOpen(\"initialize\");\n\n try {\n await this.rpc.call(\n \"initialize\",\n {\n protocolVersion: PROTOCOL_VERSION,\n capabilities: {},\n clientInfo: { name: \"@warlock.js/ai-tools\", version: \"4.4.0\" },\n },\n { signal: this.options.signal },\n );\n\n await this.rpc.notify(\"notifications/initialized\");\n } catch (cause) {\n if (cause instanceof McpTransportError) {\n throw cause;\n }\n\n const message = cause instanceof Error ? cause.message : String(cause);\n\n throw new McpTransportError(`MCP initialize handshake failed: ${message}`, {\n type: \"connect\",\n method: \"initialize\",\n cause,\n });\n }\n\n this.initialized = true;\n }\n\n public tools(): Promise<ToolContract[]> {\n if (this.cached) {\n return Promise.resolve(this.cached);\n }\n\n if (this.pending) {\n return this.pending;\n }\n\n this.pending = this.listAndAdapt()\n .then((tools) => {\n this.cached = tools;\n\n return tools;\n })\n .finally(() => {\n this.pending = undefined;\n });\n\n return this.pending;\n }\n\n /**\n * Handshake, `tools/list`, and adapt each descriptor into a\n * {@link ToolContract}, applying the `filter` and `namePrefix` options.\n */\n private async listAndAdapt(): Promise<ToolContract[]> {\n await this.handshake();\n\n this.assertOpen(\"tools/list\");\n\n const result = await this.rpc.call<ToolsListResult>(\"tools/list\", undefined, {\n signal: this.options.signal,\n });\n const descriptors = result.tools ?? [];\n\n const filter = this.options.filter;\n const selected = filter ? descriptors.filter((d) => filter(d.name)) : descriptors;\n\n return selected.map((descriptor) => this.adapt(descriptor));\n }\n\n /**\n * Adapt one remote tool descriptor into a {@link ToolContract}: build\n * the input schema from its JSON Schema via {@link jsonSchemaToStandard},\n * prefix the name, and route `execute` through a `tools/call` that\n * honors `ctx.signal`, unwraps the content blocks, and throws on an\n * `isError` result so `tool()` wraps it.\n */\n private adapt(descriptor: McpToolDescriptor): ToolContract {\n const prefixedName = `${this.options.namePrefix ?? \"\"}${descriptor.name}`;\n const remoteName = descriptor.name;\n const timeoutMs = this.options.timeoutMs ?? DEFAULT_CALL_TIMEOUT_MS;\n const input = jsonSchemaToStandard(descriptor.inputSchema);\n\n return tool<unknown, unknown>({\n name: prefixedName,\n description: descriptor.description ?? `Invoke the remote MCP tool \"${remoteName}\".`,\n input,\n execute: async (args, ctx) => {\n this.assertOpen(\"tools/call\");\n\n const result = await this.rpc.call<McpToolCallResult>(\n \"tools/call\",\n { name: remoteName, arguments: args ?? {} },\n { signal: combineSignals(ctx?.signal, this.options.signal), timeoutMs },\n );\n\n // An `isError` result is a tool-level failure — throw it so the\n // surrounding `tool()` wraps it as a `ToolExecutionError` and the\n // agent reads it as `{ error }` data and self-corrects.\n if (result.isError) {\n throw new McpTransportError(\n `MCP tool \"${remoteName}\" returned an error: ${unwrapContent(result.content)}`,\n { type: \"protocol\", method: \"tools/call\" },\n );\n }\n\n return unwrapResult(result.content);\n },\n });\n }\n\n public close(): Promise<void> {\n this.options.signal?.removeEventListener(\"abort\", this.onAbort);\n this.closing ??= this.rpc.close();\n\n return this.closing;\n }\n}\n\n/**\n * The signal a `tools/call` honors: the per-call `ctx.signal`, the\n * client-level `options.signal`, both (whichever aborts first), or none.\n */\nfunction combineSignals(\n callSignal: AbortSignal | undefined,\n clientSignal: AbortSignal | undefined,\n): AbortSignal | undefined {\n if (!callSignal || !clientSignal) {\n return callSignal ?? clientSignal;\n }\n\n return AbortSignal.any([callSignal, clientSignal]);\n}\n\n/**\n * Flatten an MCP `tools/call` result's content blocks into the value a\n * tool returns. Text blocks are concatenated; a single block whose text is\n * valid JSON is parsed so structured tool output flows back as an object\n * rather than a string. Non-text blocks are preserved as `type`-tagged\n * objects (MCP's wire `type` is kept; any inbound `kind` is normalized to\n * `type`).\n */\nfunction unwrapResult(content: McpContentBlock[] | undefined): unknown {\n const blocks = content ?? [];\n\n // The overwhelmingly common case: a single text block. Parse JSON when\n // it is one, so structured results come back typed; otherwise the string.\n if (blocks.length === 1 && blocks[0].type === \"text\") {\n const text = blocks[0].text ?? \"\";\n\n return tryParseJson(text);\n }\n\n // Multiple / mixed blocks: return a normalized array, each tagged by\n // `type` (never `kind`).\n return blocks.map((block) => normalizeBlock(block));\n}\n\n/**\n * Render content blocks to a short human string for error messages — the\n * concatenated text of every text block.\n */\nfunction unwrapContent(content: McpContentBlock[] | undefined): string {\n return (content ?? [])\n .filter((block) => block.type === \"text\" && typeof block.text === \"string\")\n .map((block) => block.text)\n .join(\" \")\n .trim();\n}\n\n/**\n * Normalize one content block onto our `type`-only shape: translate an\n * inbound `kind` discriminator to `type` (and strip `kind`) so the value a\n * tool returns never carries MCP's `kind` vocabulary.\n */\nfunction normalizeBlock(block: McpContentBlock): Record<string, unknown> {\n const { kind, ...rest } = block as McpContentBlock & { kind?: string };\n const type = block.type ?? kind ?? \"unknown\";\n\n return { ...rest, type };\n}\n\n/**\n * Parse a string as JSON, returning the parsed value on success or the\n * original string when it is not JSON — so a plain-text tool result stays\n * a string while a JSON tool result becomes an object.\n */\nfunction tryParseJson(text: string): unknown {\n const trimmed = text.trim();\n\n if (!trimmed) {\n return text;\n }\n\n const first = trimmed[0];\n\n // Only attempt a parse for plausibly-structured payloads, so a bare\n // sentence isn't mangled by a lenient parse.\n if (first !== \"{\" && first !== \"[\") {\n return text;\n }\n\n try {\n return JSON.parse(trimmed);\n } catch {\n return text;\n }\n}\n\n/**\n * Connect to an external MCP server and adapt its tools as agent tools\n * (Direction A: server → local agent tools).\n *\n * Opens the transport lazily and exposes {@link McpClient.tools}, which on\n * first call runs the `initialize` handshake, lists the server's tools via\n * `tools/list`, and maps each into a {@link ToolContract}:\n * - **input schema** — the remote tool's JSON Schema is wrapped as a\n * Standard Schema via {@link jsonSchemaToStandard} (Ajv-backed, an\n * optional peer);\n * - **execute** — issues `tools/call` honoring `ctx.signal` and the\n * configured `timeoutMs`, unwraps the result content, and throws on an\n * `isError` result so the `tool()` wrapper surfaces it as `{ error }`;\n * - **name** — prefixed with `options.namePrefix` to avoid local\n * collisions; only tools passing `options.filter` are adapted.\n *\n * The adapted contracts are cached after the first `tools()` call, so the\n * handshake + list happen exactly once. The returned contracts drop\n * straight into `ai.agent({ tools: [...] })`.\n *\n * @param server - The transport config (`{ type: \"stdio\" }` /\n * `{ type: \"http\" }`). A pre-built transport client may be injected for\n * testing.\n * @param options - Prefix / filter / per-call timeout, and a `signal` that\n * closes the client when it aborts.\n * @returns An {@link McpClient} handle.\n *\n * @example\n * const github = mcp(\n * { type: \"stdio\", command: \"npx\", args: [\"-y\", \"@modelcontextprotocol/server-github\"] },\n * { namePrefix: \"github.\" },\n * );\n * const dev = ai.agent({ model, tools: [...(await github.tools())] });\n */\nexport function mcp(\n server: McpTransport | McpTransportClient,\n options?: McpClientOptions,\n): McpClient {\n return new McpClientImpl(server, options);\n}\n"],"mappings":"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"}
|
package/esm/mcp/index.d.mts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { McpClient, McpClientOptions, McpTransport } from "../contracts/mcp.type.mjs";
|
|
2
2
|
import { McpHttpServeHandle, McpHttpServeTransport, McpServeOptions, McpServeSource, McpServer } from "../contracts/mcp-serve.type.mjs";
|
|
3
|
+
import "../contracts/index.mjs";
|
|
3
4
|
import { McpServeRequestHandler, createServeHandler, serve } from "./serve.mjs";
|
|
4
5
|
import { SUPPORTED_PROTOCOL_VERSIONS } from "./protocol.mjs";
|
|
6
|
+
import { McpNativeResult, mcpResult } from "./result.mjs";
|
|
5
7
|
import { jsonSchemaToStandard } from "./json-schema-to-standard.mjs";
|
|
6
8
|
import { McpTransportClient } from "./transport.type.mjs";
|
|
7
9
|
import { JsonRpcClientHandle, createJsonRpcClient, createTransport } from "./transport.mjs";
|
|
8
|
-
|
|
9
10
|
//#region ../../packages/ai-tools/src/mcp/index.d.ts
|
|
10
11
|
/**
|
|
11
12
|
* The callable `ai.mcp` surface — a factory that connects to an external
|
|
@@ -14,7 +15,7 @@ import { JsonRpcClientHandle, createJsonRpcClient, createTransport } from "./tra
|
|
|
14
15
|
* attached `serve` property, mirroring how `ai.mcp(server).tools()` and
|
|
15
16
|
* `ai.mcp.serve(source, options).start()` read in the design.
|
|
16
17
|
*/
|
|
17
|
-
interface McpFactory {
|
|
18
|
+
export interface McpFactory {
|
|
18
19
|
/**
|
|
19
20
|
* Connect to an external MCP server and adapt its tools as agent tools.
|
|
20
21
|
*
|
|
@@ -44,7 +45,7 @@ interface McpFactory {
|
|
|
44
45
|
* registration (`ai.mcp = mcp`) live in `../register`, so this barrel is a
|
|
45
46
|
* pure value/type module the registrar consumes.
|
|
46
47
|
*/
|
|
47
|
-
declare const mcp: McpFactory;
|
|
48
|
+
export declare const mcp: McpFactory;
|
|
48
49
|
//#endregion
|
|
49
|
-
export {
|
|
50
|
+
export { type McpNativeResult, type McpServeRequestHandler, type McpTransportClient, SUPPORTED_PROTOCOL_VERSIONS, createServeHandler, mcpResult, serve };
|
|
50
51
|
//# 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":";;;;;;;;;;;;;;;;;iBA8BiB;;;;;;;;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
|
@@ -2,6 +2,7 @@ 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
4
|
import { SUPPORTED_PROTOCOL_VERSIONS } from "./protocol.mjs";
|
|
5
|
+
import { mcpResult } from "./result.mjs";
|
|
5
6
|
import { createServeHandler, serve } from "./serve.mjs";
|
|
6
7
|
|
|
7
8
|
//#region ../../packages/ai-tools/src/mcp/index.ts
|
|
@@ -16,5 +17,5 @@ import { createServeHandler, serve } from "./serve.mjs";
|
|
|
16
17
|
const mcp = Object.assign((server, options) => mcp$1(server, options), { serve });
|
|
17
18
|
|
|
18
19
|
//#endregion
|
|
19
|
-
export { mcp };
|
|
20
|
+
export { SUPPORTED_PROTOCOL_VERSIONS, createServeHandler, mcp, mcpResult, serve };
|
|
20
21
|
//# 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 { 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 { 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":";;;;;;;;;;;;;;;;AA8DA,MAAa,MAAkB,OAAO,QACnC,QAAsB,YAA0CA,MAAU,QAAQ,OAAO,GAC1F,EAAE,MAAM,CACV"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
-
|
|
3
2
|
//#region ../../packages/ai-tools/src/mcp/json-schema-to-standard.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Wrap a raw JSON Schema as a {@link StandardSchemaV1} whose
|
|
@@ -28,7 +27,6 @@ import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
|
28
27
|
* required: ["q"],
|
|
29
28
|
* });
|
|
30
29
|
*/
|
|
31
|
-
declare function jsonSchemaToStandard<TInput = unknown>(schema: Record<string, unknown> | undefined): StandardSchemaV1<TInput>;
|
|
30
|
+
export declare function jsonSchemaToStandard<TInput = unknown>(schema: Record<string, unknown> | undefined): StandardSchemaV1<TInput>;
|
|
32
31
|
//#endregion
|
|
33
|
-
export { jsonSchemaToStandard };
|
|
34
32
|
//# sourceMappingURL=json-schema-to-standard.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-schema-to-standard.d.mts","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/json-schema-to-standard.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"json-schema-to-standard.d.mts","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/json-schema-to-standard.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA+JgB,qBAAqB,kBACnC,QAAQ,sCACP,iBAAiB"}
|
package/esm/mcp/protocol.d.mts
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
* and https://modelcontextprotocol.io/specification/2025-11-25/basic/transports
|
|
11
11
|
* (checked 2026-10-17).
|
|
12
12
|
*/
|
|
13
|
-
declare const SUPPORTED_PROTOCOL_VERSIONS: readonly string[];
|
|
13
|
+
export declare const SUPPORTED_PROTOCOL_VERSIONS: readonly string[];
|
|
14
14
|
//#endregion
|
|
15
|
-
export { SUPPORTED_PROTOCOL_VERSIONS };
|
|
16
15
|
//# sourceMappingURL=protocol.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.mts","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/protocol.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"protocol.d.mts","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/protocol.ts"],"mappings":";;;;;;;;;;;;qBAWa"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { McpToolCallResult } from "../contracts/mcp.type.mjs";
|
|
2
|
+
import "../contracts/index.mjs";
|
|
3
|
+
//#region ../../packages/ai-tools/src/mcp/result.d.ts
|
|
4
|
+
declare const NATIVE_RESULT: unique symbol;
|
|
5
|
+
/** An explicit native MCP result, unwrapped only by the serving boundary. */
|
|
6
|
+
export type McpNativeResult<T extends McpToolCallResult = McpToolCallResult> = {
|
|
7
|
+
readonly [NATIVE_RESULT]: T;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Opt in to native MCP content, error status, structured output and metadata.
|
|
11
|
+
* Validation happens after invoke(), so malformed payloads become JSON-RPC
|
|
12
|
+
* errors rather than being caught as tool execution errors.
|
|
13
|
+
*/
|
|
14
|
+
export declare function mcpResult<T extends McpToolCallResult>(result: T): McpNativeResult<T>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=result.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"result.d.mts","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/result.ts"],"mappings":";;;cAEM;;YAGM,gBAAgB,UAAU,oBAAoB;YAC9C,gBAAgB;;;;;;;wBAQZ,UAAU,UAAU,mBAAmB,QAAQ,IAAI,gBAAgB"}
|