@lovable.dev/mcp-js 0.29.0-rc.1 → 0.29.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.internal.md +22 -31
- package/README.md +23 -13
- package/dist/{authorize-BjNE3LPr.d.ts → authorize-9hTTG-qJ.d.cts} +2 -5
- package/dist/{authorize-BzZFS0zi.d.cts → authorize-DNUlPT7k.d.ts} +2 -5
- package/dist/{base-C8osWdMU.d.cts → base-7cq6HCPO.d.ts} +3 -2
- package/dist/{base-C8osWdMU.d.ts → base-CHNFFrMG.d.cts} +3 -2
- package/dist/cli/extract-manifest.cjs +25 -3
- package/dist/cli/extract-manifest.js +24 -2
- package/dist/{cors-BRv5KXKQ.cjs → cors-BDv7pyfD.cjs} +26 -30
- package/dist/{cors-BDTQQJE2.js → cors-kKiIJSyL.js} +21 -25
- package/dist/{errors-DSe96Ffd.js → errors-Bwd1L0Rf.js} +23 -8
- package/dist/{errors-CN6Da8iz.cjs → errors-Cr95rSkB.cjs} +23 -8
- package/dist/forwarded-Bnrwxbuj.js +25 -0
- package/dist/forwarded-OdBzzCMF.cjs +30 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/{io-BYUyNFdn.d.ts → io-CDV3mud0.d.cts} +1 -1
- package/dist/{io-B_YLNWDL.d.cts → io-D_5cuaEL.d.ts} +1 -1
- package/dist/mcp-BSNdmnJl.cjs +353 -0
- package/dist/mcp-GT9WU7nd.js +348 -0
- package/dist/{package-BI5r4uGM.js → package-2ZppkCKf.js} +1 -1
- package/dist/{package-ClCIqol1.cjs → package-DAQNwT8T.cjs} +1 -1
- package/dist/protocols/mcp/index.cjs +1 -1
- package/dist/protocols/mcp/index.d.cts +3 -3
- package/dist/protocols/mcp/index.d.ts +3 -3
- package/dist/protocols/mcp/index.js +1 -1
- package/dist/protocols/oauth-metadata.cjs +18 -3
- package/dist/protocols/oauth-metadata.d.cts +4 -3
- package/dist/protocols/oauth-metadata.d.ts +4 -3
- package/dist/protocols/oauth-metadata.js +18 -3
- package/dist/stacks/supabase/index.cjs +7 -36
- package/dist/stacks/supabase/index.d.cts +1 -4
- package/dist/stacks/supabase/index.d.ts +1 -4
- package/dist/stacks/supabase/index.js +7 -36
- package/dist/stacks/supabase/vite.cjs +1 -1
- package/dist/stacks/supabase/vite.d.cts +1 -1
- package/dist/stacks/supabase/vite.d.ts +1 -1
- package/dist/stacks/supabase/vite.js +1 -1
- package/dist/stacks/tanstack/index.cjs +4 -25
- package/dist/stacks/tanstack/index.d.cts +3 -20
- package/dist/stacks/tanstack/index.d.ts +3 -20
- package/dist/stacks/tanstack/index.js +5 -24
- package/dist/stacks/tanstack/vite.cjs +5 -33
- package/dist/stacks/tanstack/vite.d.cts +5 -28
- package/dist/stacks/tanstack/vite.d.ts +5 -28
- package/dist/stacks/tanstack/vite.js +5 -33
- package/dist/{types-D1cgsnPu.d.cts → types-3CZPz364.d.cts} +54 -26
- package/dist/{types-D1cgsnPu.d.ts → types-3CZPz364.d.ts} +54 -26
- package/package.json +4 -11
- package/dist/forwarded-B3EBjHnA.js +0 -43
- package/dist/forwarded-BC_RHnn5.cjs +0 -48
- package/dist/gate-CAJSRt_t.cjs +0 -26
- package/dist/gate-uCVrbDBb.js +0 -21
- package/dist/list-tools-C1OiTDR9.js +0 -37
- package/dist/list-tools-C3AoI2Zw.d.cts +0 -8
- package/dist/list-tools-DES10avA.cjs +0 -48
- package/dist/list-tools-DXqSPvNY.d.ts +0 -8
- package/dist/mcp-C9B7o75m.cjs +0 -345
- package/dist/mcp-HDkUr85V.js +0 -340
- package/dist/protocols/rest/index.cjs +0 -5
- package/dist/protocols/rest/index.d.cts +0 -9
- package/dist/protocols/rest/index.d.ts +0 -9
- package/dist/protocols/rest/index.js +0 -3
- package/dist/rest-BBdfHMGh.cjs +0 -134
- package/dist/rest-Cr7NhxL3.js +0 -129
- package/dist/tool-result-CK6hXO43.cjs +0 -89
- package/dist/tool-result-DfCk445b.js +0 -78
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as log, n as applyLogLevelFromEnv, o as parseSafeUrl, r as describeError, s as trimTrailingSlash, t as LOG_LEVEL_ENV_VAR, u as resolveMetricsConfig } from "./logger-Ctv5xUSD.js";
|
|
2
|
-
import { t as version } from "./package-
|
|
2
|
+
import { t as version } from "./package-2ZppkCKf.js";
|
|
3
3
|
import "./metadata-path-CAkNcfyY.js";
|
|
4
4
|
import { createLocalJWKSet, decodeProtectedHeader, errors, jwtVerify } from "jose";
|
|
5
5
|
//#region src/core/http.ts
|
|
@@ -28,7 +28,8 @@ function methodNotAllowed(allow) {
|
|
|
28
28
|
//#endregion
|
|
29
29
|
//#region src/metrics/otlp.ts
|
|
30
30
|
const SCOPE_NAME = "@lovable.dev/mcp-js";
|
|
31
|
-
const
|
|
31
|
+
const TOOL_EVENT_NAME = "mcp.tool.invocation";
|
|
32
|
+
const GATE_EVENT_NAME = "mcp.gate.rejection";
|
|
32
33
|
const SEVERITY_INFO = 9;
|
|
33
34
|
function strAttr(key, value) {
|
|
34
35
|
return {
|
|
@@ -43,8 +44,9 @@ function intAttr(key, value) {
|
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
46
|
function toLogRecord(rec) {
|
|
47
|
+
const eventName = rec.method === "authorize" ? GATE_EVENT_NAME : TOOL_EVENT_NAME;
|
|
46
48
|
const attributes = [
|
|
47
|
-
strAttr("event.name",
|
|
49
|
+
strAttr("event.name", eventName),
|
|
48
50
|
strAttr("mcp.method", rec.method),
|
|
49
51
|
strAttr("mcp.outcome", rec.outcome),
|
|
50
52
|
intAttr("mcp.duration_ms", rec.durationMs)
|
|
@@ -61,7 +63,7 @@ function toLogRecord(rec) {
|
|
|
61
63
|
observedTimeUnixNano: rec.timeUnixNano,
|
|
62
64
|
severityNumber: SEVERITY_INFO,
|
|
63
65
|
severityText: "INFO",
|
|
64
|
-
body: { stringValue:
|
|
66
|
+
body: { stringValue: eventName },
|
|
65
67
|
attributes
|
|
66
68
|
};
|
|
67
69
|
}
|
|
@@ -151,7 +153,7 @@ var BaseMetricRecorder = class {
|
|
|
151
153
|
}
|
|
152
154
|
async emit(ev) {
|
|
153
155
|
await this.applyLazyLogLevel();
|
|
154
|
-
if (ev.outcome !== "auth_challenge") log.info("tool.invoked", {
|
|
156
|
+
if (ev.outcome !== "auth_challenge") log.info(ev.method === "authorize" ? "gate.rejected" : "tool.invoked", {
|
|
155
157
|
tool: ev.tool,
|
|
156
158
|
method: ev.method,
|
|
157
159
|
outcome: ev.outcome,
|
|
@@ -827,17 +829,6 @@ function getOAuthRuntime(mcp, options = {}) {
|
|
|
827
829
|
options: stableOptions
|
|
828
830
|
};
|
|
829
831
|
}
|
|
830
|
-
/**
|
|
831
|
-
* REST companion routes (`/.mcp/list-tools`, `/.mcp/invoke-tool/*`) are internal
|
|
832
|
-
* RPC the main MCP server calls, so their own request path must never become the
|
|
833
|
-
* advertised resource. Require `auth.resource` or a `resourcePath` so the bound
|
|
834
|
-
* `principal.resource` is the public MCP route, not the internal `/.mcp/*` path.
|
|
835
|
-
* The MCP protocol handler is exempt: its own path is the public resource.
|
|
836
|
-
*/
|
|
837
|
-
function assertRestResourceBinding(mcp, options = {}) {
|
|
838
|
-
const auth = mcp.auth?.type === "oauth" ? mcp.auth : void 0;
|
|
839
|
-
if (auth && auth.resource === void 0 && options.resourcePath === void 0) throw new Error(`@lovable.dev/mcp-js: REST companion handlers require auth.resource or a resourcePath so principal.resource binds to the public MCP route, not the internal /.mcp/* request path`);
|
|
840
|
-
}
|
|
841
832
|
function missingRequiredScopes(auth, scopes) {
|
|
842
833
|
const requiredScopes = auth.requiredScopes ?? [];
|
|
843
834
|
if (requiredScopes.length === 0) return [];
|
|
@@ -942,7 +933,15 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
942
933
|
//#endregion
|
|
943
934
|
//#region src/core/cors.ts
|
|
944
935
|
const EXPOSE_HEADERS = "WWW-Authenticate, MCP-Protocol-Version, Mcp-Session-Id";
|
|
945
|
-
const ALLOW_HEADERS =
|
|
936
|
+
const ALLOW_HEADERS = [
|
|
937
|
+
"Authorization",
|
|
938
|
+
"Content-Type",
|
|
939
|
+
"MCP-Protocol-Version",
|
|
940
|
+
"Mcp-Method",
|
|
941
|
+
"Mcp-Name",
|
|
942
|
+
"Mcp-Session-Id",
|
|
943
|
+
"Last-Event-ID"
|
|
944
|
+
];
|
|
946
945
|
const MCP_PARAM_HEADER = /^mcp-param-[!#$%&'*+.^_`|~0-9A-Za-z-]+$/i;
|
|
947
946
|
function appendVary(headers, value) {
|
|
948
947
|
const values = new Set((headers.get("Vary") ?? "").split(",").map((entry) => entry.trim()).filter(Boolean));
|
|
@@ -958,7 +957,7 @@ function addCorsHeaders(response, allowedOrigin, variesByOrigin) {
|
|
|
958
957
|
return response;
|
|
959
958
|
}
|
|
960
959
|
function allowedRequestHeaders(request) {
|
|
961
|
-
const allowed = new Map(ALLOW_HEADERS.
|
|
960
|
+
const allowed = new Map(ALLOW_HEADERS.map((header) => [header.toLowerCase(), header]));
|
|
962
961
|
for (const header of (request.headers.get("Access-Control-Request-Headers") ?? "").split(",")) {
|
|
963
962
|
const normalized = header.trim();
|
|
964
963
|
if (MCP_PARAM_HEADER.test(normalized)) allowed.set(normalized.toLowerCase(), normalized);
|
|
@@ -968,12 +967,8 @@ function allowedRequestHeaders(request) {
|
|
|
968
967
|
function evaluateCors(request, allowedOrigins) {
|
|
969
968
|
const origin = request.headers.get("Origin");
|
|
970
969
|
const allowAny = allowedOrigins === "any";
|
|
971
|
-
|
|
972
|
-
if (allowAny
|
|
973
|
-
else if (origin === null) allowedOrigin = void 0;
|
|
974
|
-
else if (origin === new URL(request.url).origin || allowedOrigins?.includes(origin)) allowedOrigin = origin;
|
|
975
|
-
else allowedOrigin = null;
|
|
976
|
-
if (allowedOrigin === null) {
|
|
970
|
+
const originAllowed = origin === null || origin === new URL(request.url).origin || allowedOrigins?.includes(origin) === true;
|
|
971
|
+
if (!allowAny && !originAllowed) {
|
|
977
972
|
const response = Response.json({ error: "origin not allowed" }, {
|
|
978
973
|
status: 403,
|
|
979
974
|
headers: { "Cache-Control": "no-store" }
|
|
@@ -984,6 +979,7 @@ function evaluateCors(request, allowedOrigins) {
|
|
|
984
979
|
response
|
|
985
980
|
};
|
|
986
981
|
}
|
|
982
|
+
const allowedOrigin = allowAny ? "*" : origin ?? void 0;
|
|
987
983
|
return {
|
|
988
984
|
ok: true,
|
|
989
985
|
withHeaders: (response) => addCorsHeaders(response, allowedOrigin, !allowAny),
|
|
@@ -1003,4 +999,4 @@ function evaluateCors(request, allowedOrigins) {
|
|
|
1003
999
|
};
|
|
1004
1000
|
}
|
|
1005
1001
|
//#endregion
|
|
1006
|
-
export { oauthConfigurationErrorResponse as a, createRecorderForRuntime as c, JSON_HEADERS as d, finalizeWireResponse as f,
|
|
1002
|
+
export { oauthConfigurationErrorResponse as a, createRecorderForRuntime as c, JSON_HEADERS as d, finalizeWireResponse as f, normalizeHttpMethod as i, createNoopRecorder as l, createRequestAuthorizer as n, assertResourcePathShape as o, methodNotAllowed as p, getOAuthRuntime as r, resolveProtectedResource as s, evaluateCors as t, nowMs as u };
|
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
//#region src/auth/context.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* the bearer token inside it — can't be read, enumerated, spread, or
|
|
6
|
-
* `JSON.stringify`'d off the instance; the accessors below are the only way out.
|
|
7
|
-
* `getToken()` is the single intentional escape hatch for the raw bearer.
|
|
8
|
-
*/
|
|
2
|
+
const NEVER_ABORTED = new AbortController().signal;
|
|
3
|
+
/** The per-request context passed to every tool handler as its second argument. The
|
|
4
|
+
* auth context lives in a private field so the accessors are the only way out; `getToken()` is the sole bearer escape hatch. */
|
|
9
5
|
var ToolContext = class {
|
|
10
6
|
#auth;
|
|
11
|
-
|
|
7
|
+
#signal;
|
|
8
|
+
#client;
|
|
9
|
+
#sendProgress;
|
|
10
|
+
constructor(auth, request = {}) {
|
|
12
11
|
this.#auth = auth;
|
|
12
|
+
this.#signal = request.signal ?? NEVER_ABORTED;
|
|
13
|
+
this.#client = request.client ?? { protocol: "legacy" };
|
|
14
|
+
this.#sendProgress = request.sendProgress;
|
|
15
|
+
}
|
|
16
|
+
/** Aborts when the client cancels the request or disconnects. */
|
|
17
|
+
get signal() {
|
|
18
|
+
return this.#signal;
|
|
19
|
+
}
|
|
20
|
+
/** What the client declared about itself on this request. Unverified input. */
|
|
21
|
+
get client() {
|
|
22
|
+
return this.#client;
|
|
23
|
+
}
|
|
24
|
+
/** Send a progress notification for this call. A no-op when the client
|
|
25
|
+
* didn't request progress (no `progressToken` on the request). */
|
|
26
|
+
async progress(update) {
|
|
27
|
+
await this.#sendProgress?.(update);
|
|
13
28
|
}
|
|
14
29
|
/** Whether the in-flight tool call carries a verified auth context. */
|
|
15
30
|
isAuthenticated() {
|
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
//#region src/auth/context.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* the bearer token inside it — can't be read, enumerated, spread, or
|
|
6
|
-
* `JSON.stringify`'d off the instance; the accessors below are the only way out.
|
|
7
|
-
* `getToken()` is the single intentional escape hatch for the raw bearer.
|
|
8
|
-
*/
|
|
2
|
+
const NEVER_ABORTED = new AbortController().signal;
|
|
3
|
+
/** The per-request context passed to every tool handler as its second argument. The
|
|
4
|
+
* auth context lives in a private field so the accessors are the only way out; `getToken()` is the sole bearer escape hatch. */
|
|
9
5
|
var ToolContext = class {
|
|
10
6
|
#auth;
|
|
11
|
-
|
|
7
|
+
#signal;
|
|
8
|
+
#client;
|
|
9
|
+
#sendProgress;
|
|
10
|
+
constructor(auth, request = {}) {
|
|
12
11
|
this.#auth = auth;
|
|
12
|
+
this.#signal = request.signal ?? NEVER_ABORTED;
|
|
13
|
+
this.#client = request.client ?? { protocol: "legacy" };
|
|
14
|
+
this.#sendProgress = request.sendProgress;
|
|
15
|
+
}
|
|
16
|
+
/** Aborts when the client cancels the request or disconnects. */
|
|
17
|
+
get signal() {
|
|
18
|
+
return this.#signal;
|
|
19
|
+
}
|
|
20
|
+
/** What the client declared about itself on this request. Unverified input. */
|
|
21
|
+
get client() {
|
|
22
|
+
return this.#client;
|
|
23
|
+
}
|
|
24
|
+
/** Send a progress notification for this call. A no-op when the client
|
|
25
|
+
* didn't request progress (no `progressToken` on the request). */
|
|
26
|
+
async progress(update) {
|
|
27
|
+
await this.#sendProgress?.(update);
|
|
13
28
|
}
|
|
14
29
|
/** Whether the in-flight tool call carries a verified auth context. */
|
|
15
30
|
isAuthenticated() {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/core/forwarded.ts
|
|
2
|
+
/** Rewrites the request URL to the proxy-forwarded public origin so derived OAuth
|
|
3
|
+
* URLs name the client-facing host. Trust model: AGENTS.md § Forwarded-header trust model. */
|
|
4
|
+
function applyForwardedOrigin(request, options) {
|
|
5
|
+
const proto = options.trustForwardedProto ? firstForwardedValue(request, "x-forwarded-proto") : void 0;
|
|
6
|
+
const host = options.trustForwardedHost ? firstForwardedValue(request, "x-forwarded-host") : void 0;
|
|
7
|
+
if (proto === void 0 && host === void 0) return request;
|
|
8
|
+
const url = new URL(request.url);
|
|
9
|
+
let changed = false;
|
|
10
|
+
if (proto !== void 0 && `${proto}:` !== url.protocol) {
|
|
11
|
+
url.protocol = `${proto}:`;
|
|
12
|
+
changed = true;
|
|
13
|
+
}
|
|
14
|
+
if (host !== void 0 && host !== url.host) {
|
|
15
|
+
url.host = host;
|
|
16
|
+
changed = true;
|
|
17
|
+
}
|
|
18
|
+
return changed ? new Request(url.href, request) : request;
|
|
19
|
+
}
|
|
20
|
+
function firstForwardedValue(request, header) {
|
|
21
|
+
const value = request.headers.get(header)?.split(",")[0]?.trim();
|
|
22
|
+
return value ? value : void 0;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { applyForwardedOrigin as t };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/core/forwarded.ts
|
|
2
|
+
/** Rewrites the request URL to the proxy-forwarded public origin so derived OAuth
|
|
3
|
+
* URLs name the client-facing host. Trust model: AGENTS.md § Forwarded-header trust model. */
|
|
4
|
+
function applyForwardedOrigin(request, options) {
|
|
5
|
+
const proto = options.trustForwardedProto ? firstForwardedValue(request, "x-forwarded-proto") : void 0;
|
|
6
|
+
const host = options.trustForwardedHost ? firstForwardedValue(request, "x-forwarded-host") : void 0;
|
|
7
|
+
if (proto === void 0 && host === void 0) return request;
|
|
8
|
+
const url = new URL(request.url);
|
|
9
|
+
let changed = false;
|
|
10
|
+
if (proto !== void 0 && `${proto}:` !== url.protocol) {
|
|
11
|
+
url.protocol = `${proto}:`;
|
|
12
|
+
changed = true;
|
|
13
|
+
}
|
|
14
|
+
if (host !== void 0 && host !== url.host) {
|
|
15
|
+
url.host = host;
|
|
16
|
+
changed = true;
|
|
17
|
+
}
|
|
18
|
+
return changed ? new Request(url.href, request) : request;
|
|
19
|
+
}
|
|
20
|
+
function firstForwardedValue(request, header) {
|
|
21
|
+
const value = request.headers.get(header)?.split(",")[0]?.trim();
|
|
22
|
+
return value ? value : void 0;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
Object.defineProperty(exports, "applyForwardedOrigin", {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
get: function() {
|
|
28
|
+
return applyForwardedOrigin;
|
|
29
|
+
}
|
|
30
|
+
});
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_logger = require("./logger-CN1q-KNn.cjs");
|
|
3
3
|
const require_schema = require("./schema-D3vClxb7.cjs");
|
|
4
|
-
const require_errors = require("./errors-
|
|
4
|
+
const require_errors = require("./errors-Cr95rSkB.cjs");
|
|
5
5
|
//#region src/core/define.ts
|
|
6
6
|
function assertUniqueNames(mcp) {
|
|
7
7
|
const seen = /* @__PURE__ */ new Set();
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { A as McpAuthConfig, C as MetricsConfig, D as ProgressUpdate, E as McpProtocolEra, O as ToolContext, S as ZodSchema, T as McpClientInfo, _ as ToolContent, a as EmbeddedResource, b as ZodOutputSchema, c as JsonValue, d as McpDefinition, f as McpDefinitionInput, g as ToolAnnotations, h as TextContent, i as EmbeddedBlobResource, k as JwtClaims, l as JsonValueInput, m as ResourceLinkIcon, n as ContentAnnotations, o as EmbeddedTextResource, p as ResourceLink, r as ContentBlock, s as ImageContent, t as AudioContent, u as McpAllowedOrigins, v as ToolDefinition, w as MetricsOptions, x as ZodRawShape, y as ToolHandlerResult } from "./types-3CZPz364.cjs";
|
|
2
2
|
//#region src/core/define.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Declare a single MCP tool. Import the result into your MCP definition's
|
|
@@ -71,4 +71,4 @@ type LogLevel = "silent" | "error" | "warn" | "info" | "debug";
|
|
|
71
71
|
*/
|
|
72
72
|
declare function setLogLevel(level: LogLevel): LogLevel;
|
|
73
73
|
//#endregion
|
|
74
|
-
export { type AudioContent, type ContentAnnotations, type ContentBlock, type EmbeddedBlobResource, type EmbeddedResource, type EmbeddedTextResource, type ImageContent, type IssuerOAuthOptions, type JsonValue, type JwtClaims, type LogLevel, type McpAllowedOrigins, type McpDefinitionInput, type MetricsConfig, type MetricsOptions, type ResourceLink, type ResourceLinkIcon, type TextContent, type ToolAnnotations, type ToolContent, ToolContext, type ToolDefinition, ToolError, type ToolHandlerResult, type ZodOutputSchema, type ZodRawShape, type ZodSchema, auth, defineMcp, defineTool, setLogLevel };
|
|
74
|
+
export { type AudioContent, type ContentAnnotations, type ContentBlock, type EmbeddedBlobResource, type EmbeddedResource, type EmbeddedTextResource, type ImageContent, type IssuerOAuthOptions, type JsonValue, type JsonValueInput, type JwtClaims, type LogLevel, type McpAllowedOrigins, type McpClientInfo, type McpDefinitionInput, type McpProtocolEra, type MetricsConfig, type MetricsOptions, type ProgressUpdate, type ResourceLink, type ResourceLinkIcon, type TextContent, type ToolAnnotations, type ToolContent, ToolContext, type ToolDefinition, ToolError, type ToolHandlerResult, type ZodOutputSchema, type ZodRawShape, type ZodSchema, auth, defineMcp, defineTool, setLogLevel };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { A as McpAuthConfig, C as MetricsConfig, D as ProgressUpdate, E as McpProtocolEra, O as ToolContext, S as ZodSchema, T as McpClientInfo, _ as ToolContent, a as EmbeddedResource, b as ZodOutputSchema, c as JsonValue, d as McpDefinition, f as McpDefinitionInput, g as ToolAnnotations, h as TextContent, i as EmbeddedBlobResource, k as JwtClaims, l as JsonValueInput, m as ResourceLinkIcon, n as ContentAnnotations, o as EmbeddedTextResource, p as ResourceLink, r as ContentBlock, s as ImageContent, t as AudioContent, u as McpAllowedOrigins, v as ToolDefinition, w as MetricsOptions, x as ZodRawShape, y as ToolHandlerResult } from "./types-3CZPz364.js";
|
|
2
2
|
//#region src/core/define.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Declare a single MCP tool. Import the result into your MCP definition's
|
|
@@ -71,4 +71,4 @@ type LogLevel = "silent" | "error" | "warn" | "info" | "debug";
|
|
|
71
71
|
*/
|
|
72
72
|
declare function setLogLevel(level: LogLevel): LogLevel;
|
|
73
73
|
//#endregion
|
|
74
|
-
export { type AudioContent, type ContentAnnotations, type ContentBlock, type EmbeddedBlobResource, type EmbeddedResource, type EmbeddedTextResource, type ImageContent, type IssuerOAuthOptions, type JsonValue, type JwtClaims, type LogLevel, type McpAllowedOrigins, type McpDefinitionInput, type MetricsConfig, type MetricsOptions, type ResourceLink, type ResourceLinkIcon, type TextContent, type ToolAnnotations, type ToolContent, ToolContext, type ToolDefinition, ToolError, type ToolHandlerResult, type ZodOutputSchema, type ZodRawShape, type ZodSchema, auth, defineMcp, defineTool, setLogLevel };
|
|
74
|
+
export { type AudioContent, type ContentAnnotations, type ContentBlock, type EmbeddedBlobResource, type EmbeddedResource, type EmbeddedTextResource, type ImageContent, type IssuerOAuthOptions, type JsonValue, type JsonValueInput, type JwtClaims, type LogLevel, type McpAllowedOrigins, type McpClientInfo, type McpDefinitionInput, type McpProtocolEra, type MetricsConfig, type MetricsOptions, type ProgressUpdate, type ResourceLink, type ResourceLinkIcon, type TextContent, type ToolAnnotations, type ToolContent, ToolContext, type ToolDefinition, ToolError, type ToolHandlerResult, type ZodOutputSchema, type ZodRawShape, type ZodSchema, auth, defineMcp, defineTool, setLogLevel };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as setLogLevel, o as parseSafeUrl, s as trimTrailingSlash, u as resolveMetricsConfig } from "./logger-Ctv5xUSD.js";
|
|
2
2
|
import { t as jsonSchemaFromDefinition } from "./schema-CW1jKvio.js";
|
|
3
|
-
import { i as ToolContext, t as ToolError } from "./errors-
|
|
3
|
+
import { i as ToolContext, t as ToolError } from "./errors-Bwd1L0Rf.js";
|
|
4
4
|
//#region src/core/define.ts
|
|
5
5
|
function assertUniqueNames(mcp) {
|
|
6
6
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
const require_logger = require("./logger-CN1q-KNn.cjs");
|
|
2
|
+
const require_schema = require("./schema-D3vClxb7.cjs");
|
|
3
|
+
const require_errors = require("./errors-Cr95rSkB.cjs");
|
|
4
|
+
const require_cors = require("./cors-BDv7pyfD.cjs");
|
|
5
|
+
let _modelcontextprotocol_sdk_server_zod_compat_js = require("@modelcontextprotocol/sdk/server/zod-compat.js");
|
|
6
|
+
let _modelcontextprotocol_server = require("@modelcontextprotocol/server");
|
|
7
|
+
//#region src/core/content.ts
|
|
8
|
+
/** Join the text of a result's `type: "text"` blocks into one string, dropping
|
|
9
|
+
* binary/resource blocks. Used to surface a `tool_error` message in the local
|
|
10
|
+
* log; never sent over the telemetry wire. Returns undefined when there is no
|
|
11
|
+
* text to show. */
|
|
12
|
+
function extractTextContent(content) {
|
|
13
|
+
if (!content) return void 0;
|
|
14
|
+
const text = content.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
|
|
15
|
+
return text.length > 0 ? text : void 0;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/core/tool-result.ts
|
|
19
|
+
function normalizeResourceLinkIcon(icon) {
|
|
20
|
+
if (typeof icon.sizes !== "string") return icon;
|
|
21
|
+
const { sizes, ...rest } = icon;
|
|
22
|
+
const normalized = sizes.trim();
|
|
23
|
+
return normalized === "" ? rest : {
|
|
24
|
+
...rest,
|
|
25
|
+
sizes: normalized.split(/\s+/)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function normalizeToolContent(content) {
|
|
29
|
+
return (content ?? []).map((block) => {
|
|
30
|
+
if (block.type !== "resource_link" || block.icons === void 0) return block;
|
|
31
|
+
return {
|
|
32
|
+
...block,
|
|
33
|
+
icons: block.icons.map(normalizeResourceLinkIcon)
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function outputValidationError(text) {
|
|
38
|
+
return {
|
|
39
|
+
content: [{
|
|
40
|
+
type: "text",
|
|
41
|
+
text
|
|
42
|
+
}],
|
|
43
|
+
isError: true
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function isJsonValue(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
47
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") return true;
|
|
48
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
49
|
+
if (typeof value !== "object" || ancestors.has(value)) return false;
|
|
50
|
+
const prototype = Object.getPrototypeOf(value);
|
|
51
|
+
if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) return false;
|
|
52
|
+
ancestors.add(value);
|
|
53
|
+
const valid = (Array.isArray(value) ? value : Object.values(value).filter((entry) => entry !== void 0)).every((entry) => isJsonValue(entry, ancestors));
|
|
54
|
+
ancestors.delete(value);
|
|
55
|
+
return valid;
|
|
56
|
+
}
|
|
57
|
+
async function finalizeToolResult(tool, result) {
|
|
58
|
+
const content = normalizeToolContent(result.content);
|
|
59
|
+
let structuredContent = result.structuredContent;
|
|
60
|
+
if (tool.outputSchema && !result.isError) {
|
|
61
|
+
if (structuredContent === void 0) return outputValidationError(`Output validation error: Tool ${tool.name} has an output schema but no structured content was provided`);
|
|
62
|
+
try {
|
|
63
|
+
const parsed = await (0, _modelcontextprotocol_sdk_server_zod_compat_js.safeParseAsync)(require_schema.schemaFromDefinition(tool.outputSchema), structuredContent);
|
|
64
|
+
if (!parsed.success) return outputValidationError(`Output validation error: Invalid structured content for tool ${tool.name}: ${(0, _modelcontextprotocol_sdk_server_zod_compat_js.getParseErrorMessage)(parsed.error)}`);
|
|
65
|
+
structuredContent = parsed.data;
|
|
66
|
+
} catch {
|
|
67
|
+
return outputValidationError(`Output validation error: Could not validate structured content for tool ${tool.name}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (structuredContent !== void 0 && !isJsonValue(structuredContent)) {
|
|
71
|
+
if (!result.isError) return outputValidationError(`Output validation error: Tool ${tool.name} returned a non-JSON structured value`);
|
|
72
|
+
structuredContent = void 0;
|
|
73
|
+
}
|
|
74
|
+
if (structuredContent !== void 0 && (structuredContent === null || typeof structuredContent !== "object" || Array.isArray(structuredContent)) && !content.some((block) => block.type === "text")) content.push({
|
|
75
|
+
type: "text",
|
|
76
|
+
text: JSON.stringify(structuredContent)
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
content,
|
|
80
|
+
structuredContent,
|
|
81
|
+
isError: result.isError
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/protocols/gate.ts
|
|
86
|
+
function createGatedHandler(gate) {
|
|
87
|
+
const allow = [...gate.methods, "OPTIONS"].join(", ");
|
|
88
|
+
const respond = async (request, recorder) => {
|
|
89
|
+
const authResult = await gate.authorizer.authorize(request, recorder);
|
|
90
|
+
if (!authResult.ok) return authResult.response;
|
|
91
|
+
if (!gate.methods.includes(request.method)) return gate.methodNotAllowed(allow);
|
|
92
|
+
return gate.handle(request, authResult, recorder);
|
|
93
|
+
};
|
|
94
|
+
return async (request, recorder = require_cors.createNoopRecorder()) => {
|
|
95
|
+
const cors = require_cors.evaluateCors(request, gate.allowedOrigins);
|
|
96
|
+
if (!cors.ok) {
|
|
97
|
+
const httpMethod = require_cors.normalizeHttpMethod(request.method);
|
|
98
|
+
require_logger.log.info("cors.origin_rejected", {
|
|
99
|
+
origin: request.headers.get("Origin"),
|
|
100
|
+
httpMethod
|
|
101
|
+
});
|
|
102
|
+
await recorder.emit({
|
|
103
|
+
tool: null,
|
|
104
|
+
method: "authorize",
|
|
105
|
+
outcome: "origin_rejected",
|
|
106
|
+
httpMethod,
|
|
107
|
+
durationMs: 0
|
|
108
|
+
});
|
|
109
|
+
return require_cors.finalizeWireResponse(request, cors.response);
|
|
110
|
+
}
|
|
111
|
+
if (request.method === "OPTIONS") return cors.preflight(allow);
|
|
112
|
+
const final = require_cors.finalizeWireResponse(request, cors.withHeaders(await respond(request, recorder)));
|
|
113
|
+
if (!final.headers.has("Cache-Control")) final.headers.set("Cache-Control", "no-store");
|
|
114
|
+
return final;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/protocols/mcp/protocol.ts
|
|
119
|
+
const MODERN_PROTOCOL_VERSION = "2026-07-28";
|
|
120
|
+
function statelessMethodNotAllowed(allow) {
|
|
121
|
+
return Response.json({
|
|
122
|
+
jsonrpc: "2.0",
|
|
123
|
+
id: null,
|
|
124
|
+
error: {
|
|
125
|
+
code: -32e3,
|
|
126
|
+
message: "Method not allowed: stateless server offers no standalone SSE stream (GET) or session (DELETE)."
|
|
127
|
+
}
|
|
128
|
+
}, {
|
|
129
|
+
status: 405,
|
|
130
|
+
headers: { Allow: allow }
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function zodIssues(error) {
|
|
134
|
+
const issues = error?.issues;
|
|
135
|
+
if (!Array.isArray(issues)) return [{ message: String(error) }];
|
|
136
|
+
return issues.map((issue) => {
|
|
137
|
+
const value = issue;
|
|
138
|
+
return {
|
|
139
|
+
message: typeof value.message === "string" ? value.message : String(value.message),
|
|
140
|
+
...Array.isArray(value.path) ? { path: value.path } : {}
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function standardSchemaFromDefinition(definition) {
|
|
145
|
+
const schema = require_schema.schemaFromDefinition(definition);
|
|
146
|
+
return { "~standard": {
|
|
147
|
+
version: 1,
|
|
148
|
+
vendor: "zod",
|
|
149
|
+
validate: async (value) => {
|
|
150
|
+
const parsed = await (0, _modelcontextprotocol_sdk_server_zod_compat_js.safeParseAsync)(schema, value);
|
|
151
|
+
return parsed.success ? { value: parsed.data } : { issues: zodIssues(parsed.error) };
|
|
152
|
+
},
|
|
153
|
+
jsonSchema: {
|
|
154
|
+
input: () => require_schema.jsonSchemaFromDefinition(definition, "input"),
|
|
155
|
+
output: () => require_schema.jsonSchemaFromDefinition(definition, "output")
|
|
156
|
+
}
|
|
157
|
+
} };
|
|
158
|
+
}
|
|
159
|
+
function isPlainObject(value) {
|
|
160
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
161
|
+
}
|
|
162
|
+
function toolRequestContext(sdkCtx, protocol) {
|
|
163
|
+
const mcpReq = sdkCtx?.mcpReq;
|
|
164
|
+
const client = { protocol };
|
|
165
|
+
const envelope = mcpReq?.envelope;
|
|
166
|
+
const info = envelope?.[_modelcontextprotocol_server.CLIENT_INFO_META_KEY];
|
|
167
|
+
if (isPlainObject(info) && typeof info.name === "string" && typeof info.version === "string") client.info = info;
|
|
168
|
+
const capabilities = envelope?.[_modelcontextprotocol_server.CLIENT_CAPABILITIES_META_KEY];
|
|
169
|
+
if (isPlainObject(capabilities)) {
|
|
170
|
+
client.capabilities = capabilities;
|
|
171
|
+
if (isPlainObject(capabilities.extensions)) client.extensions = capabilities.extensions;
|
|
172
|
+
}
|
|
173
|
+
const progressToken = (mcpReq?._meta)?.progressToken;
|
|
174
|
+
const notify = mcpReq?.notify;
|
|
175
|
+
const sendProgress = progressToken !== void 0 && notify ? async (update) => {
|
|
176
|
+
try {
|
|
177
|
+
await notify({
|
|
178
|
+
method: "notifications/progress",
|
|
179
|
+
params: {
|
|
180
|
+
progressToken,
|
|
181
|
+
...update
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
} catch (err) {
|
|
185
|
+
require_logger.log.debug("mcp.progress_notify_failed", require_logger.describeError(err));
|
|
186
|
+
}
|
|
187
|
+
} : void 0;
|
|
188
|
+
return {
|
|
189
|
+
signal: mcpReq?.signal,
|
|
190
|
+
client,
|
|
191
|
+
sendProgress
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
async function invokeTool(tool, auth, recorder, args, protocol, sdkCtx) {
|
|
195
|
+
const endUserId = auth?.principal.sub;
|
|
196
|
+
const start = require_cors.nowMs();
|
|
197
|
+
let result;
|
|
198
|
+
try {
|
|
199
|
+
result = await tool.handler(args, new require_errors.ToolContext(auth, toolRequestContext(sdkCtx, protocol)));
|
|
200
|
+
} catch (err) {
|
|
201
|
+
const callerMessage = require_errors.resolveToolErrorMessage(err);
|
|
202
|
+
if (callerMessage === void 0) {
|
|
203
|
+
await recorder.emit({
|
|
204
|
+
tool: tool.name,
|
|
205
|
+
method: "tools/call",
|
|
206
|
+
outcome: "handler_error",
|
|
207
|
+
durationMs: require_cors.nowMs() - start,
|
|
208
|
+
errorText: require_errors.errorSummary(err),
|
|
209
|
+
endUserId,
|
|
210
|
+
protocol
|
|
211
|
+
});
|
|
212
|
+
return finalizeToolResult(tool, {
|
|
213
|
+
content: [{
|
|
214
|
+
type: "text",
|
|
215
|
+
text: "tool execution failed"
|
|
216
|
+
}],
|
|
217
|
+
isError: true
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
result = {
|
|
221
|
+
content: [{
|
|
222
|
+
type: "text",
|
|
223
|
+
text: callerMessage
|
|
224
|
+
}],
|
|
225
|
+
isError: true
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
if (result == null) {
|
|
229
|
+
await recorder.emit({
|
|
230
|
+
tool: tool.name,
|
|
231
|
+
method: "tools/call",
|
|
232
|
+
outcome: "handler_error",
|
|
233
|
+
durationMs: require_cors.nowMs() - start,
|
|
234
|
+
endUserId,
|
|
235
|
+
protocol
|
|
236
|
+
});
|
|
237
|
+
return finalizeToolResult(tool, {
|
|
238
|
+
content: [{
|
|
239
|
+
type: "text",
|
|
240
|
+
text: `tool "${tool.name}" returned no result`
|
|
241
|
+
}],
|
|
242
|
+
isError: true
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
result = await finalizeToolResult(tool, result);
|
|
246
|
+
await recorder.emit({
|
|
247
|
+
tool: tool.name,
|
|
248
|
+
method: "tools/call",
|
|
249
|
+
outcome: result.isError ? "tool_error" : "ok",
|
|
250
|
+
durationMs: require_cors.nowMs() - start,
|
|
251
|
+
errorText: result.isError ? extractTextContent(result.content) : void 0,
|
|
252
|
+
endUserId,
|
|
253
|
+
protocol
|
|
254
|
+
});
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
function adaptToolCallback(tool, auth, recorder, protocol) {
|
|
258
|
+
return async (first, second) => {
|
|
259
|
+
const sdkCtx = tool.inputSchema ? second : first;
|
|
260
|
+
const result = await invokeTool(tool, auth, recorder, tool.inputSchema ? first ?? {} : {}, protocol, sdkCtx);
|
|
261
|
+
return {
|
|
262
|
+
content: result.content ?? [],
|
|
263
|
+
structuredContent: result.structuredContent,
|
|
264
|
+
isError: result.isError
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function buildServer(mcp, auth, recorder, era) {
|
|
269
|
+
const server = new _modelcontextprotocol_server.McpServer({
|
|
270
|
+
name: mcp.name,
|
|
271
|
+
version: mcp.version,
|
|
272
|
+
title: mcp.title
|
|
273
|
+
}, {
|
|
274
|
+
instructions: mcp.instructions,
|
|
275
|
+
capabilities: { tools: { listChanged: false } }
|
|
276
|
+
});
|
|
277
|
+
for (const tool of mcp.tools) {
|
|
278
|
+
const config = {
|
|
279
|
+
title: tool.title,
|
|
280
|
+
description: tool.description,
|
|
281
|
+
annotations: tool.annotations,
|
|
282
|
+
...tool.outputSchema ? { outputSchema: standardSchemaFromDefinition(tool.outputSchema) } : {}
|
|
283
|
+
};
|
|
284
|
+
const callback = adaptToolCallback(tool, auth, recorder, era === "legacy" ? "legacy" : MODERN_PROTOCOL_VERSION);
|
|
285
|
+
if (tool.inputSchema) server.registerTool(tool.name, {
|
|
286
|
+
...config,
|
|
287
|
+
inputSchema: standardSchemaFromDefinition(tool.inputSchema)
|
|
288
|
+
}, callback);
|
|
289
|
+
else server.registerTool(tool.name, config, callback);
|
|
290
|
+
}
|
|
291
|
+
return server;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Build a Web-Standard request handler that serves the MCP protocol over
|
|
295
|
+
* streamable HTTP. A fresh `McpServer` + transport is constructed per
|
|
296
|
+
* request — the transport is stateless, so this is cheap and there is no
|
|
297
|
+
* cross-request state to leak. The per-request `recorder` is supplied by the
|
|
298
|
+
* caller (the stack handler) so telemetry reads the runtime env in-flight.
|
|
299
|
+
*/
|
|
300
|
+
function createMcpProtocolHandler(mcp, options = {}) {
|
|
301
|
+
const authorizer = require_cors.createRequestAuthorizer(mcp, options);
|
|
302
|
+
const handle = async (request, authResult, recorder) => {
|
|
303
|
+
let protocolError;
|
|
304
|
+
const reportTransportError = async (error) => {
|
|
305
|
+
await recorder.emit({
|
|
306
|
+
tool: null,
|
|
307
|
+
method: "transport",
|
|
308
|
+
outcome: "transport_error",
|
|
309
|
+
durationMs: 0,
|
|
310
|
+
endUserId: authResult.auth?.principal.sub
|
|
311
|
+
});
|
|
312
|
+
require_logger.log.error("mcp.transport_error", {
|
|
313
|
+
...error === void 0 ? {} : require_logger.describeError(error),
|
|
314
|
+
outcome: "500 internal error"
|
|
315
|
+
});
|
|
316
|
+
};
|
|
317
|
+
try {
|
|
318
|
+
const response = await (0, _modelcontextprotocol_server.createMcpHandler)(({ era }) => buildServer(mcp, authResult.auth, recorder, era), {
|
|
319
|
+
legacy: "stateless",
|
|
320
|
+
maxSubscriptions: 0,
|
|
321
|
+
onerror: (error) => {
|
|
322
|
+
protocolError = error;
|
|
323
|
+
}
|
|
324
|
+
}).fetch(request);
|
|
325
|
+
if (response.status >= 500) await reportTransportError(protocolError);
|
|
326
|
+
return response;
|
|
327
|
+
} catch (err) {
|
|
328
|
+
await reportTransportError(err);
|
|
329
|
+
return Response.json({
|
|
330
|
+
jsonrpc: "2.0",
|
|
331
|
+
id: null,
|
|
332
|
+
error: {
|
|
333
|
+
code: -32603,
|
|
334
|
+
message: "internal error"
|
|
335
|
+
}
|
|
336
|
+
}, { status: 500 });
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
return createGatedHandler({
|
|
340
|
+
authorizer,
|
|
341
|
+
allowedOrigins: mcp.allowedOrigins,
|
|
342
|
+
methods: ["POST"],
|
|
343
|
+
methodNotAllowed: statelessMethodNotAllowed,
|
|
344
|
+
handle
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
//#endregion
|
|
348
|
+
Object.defineProperty(exports, "createMcpProtocolHandler", {
|
|
349
|
+
enumerable: true,
|
|
350
|
+
get: function() {
|
|
351
|
+
return createMcpProtocolHandler;
|
|
352
|
+
}
|
|
353
|
+
});
|