mcp-use 1.6.3-canary.0 → 1.7.0-canary.1
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/dist/.tsbuildinfo +1 -1
- package/dist/chunk-JQKKMUCT.js +0 -0
- package/dist/chunk-PE7UMCVO.js +377 -0
- package/dist/{chunk-BWOTID2D.js → chunk-QSLJXXMG.js} +3 -346
- package/dist/chunk-R5DJJ4IV.js +942 -0
- package/dist/{chunk-SJEHVCPM.js → chunk-XN2PU4PS.js} +100 -23
- package/dist/index.cjs +132 -29
- package/dist/index.js +8 -5
- package/dist/oauth-CNGBFOZW.js +29 -0
- package/dist/src/agents/index.js +1 -1
- package/dist/src/auth/browser-provider.d.ts +2 -0
- package/dist/src/auth/browser-provider.d.ts.map +1 -1
- package/dist/src/auth/callback.d.ts.map +1 -1
- package/dist/src/auth/index.cjs +396 -0
- package/dist/src/auth/index.js +10 -0
- package/dist/src/auth/types.d.ts +3 -1
- package/dist/src/auth/types.d.ts.map +1 -1
- package/dist/src/browser.cjs +36 -8
- package/dist/src/browser.js +6 -4
- package/dist/src/connectors/http.d.ts.map +1 -1
- package/dist/src/react/index.cjs +132 -29
- package/dist/src/react/index.js +3 -2
- package/dist/src/react/types.d.ts +12 -1
- package/dist/src/react/types.d.ts.map +1 -1
- package/dist/src/react/useMcp.d.ts.map +1 -1
- package/dist/src/server/context-storage.d.ts +54 -0
- package/dist/src/server/context-storage.d.ts.map +1 -0
- package/dist/src/server/index.cjs +1409 -410
- package/dist/src/server/index.d.ts +4 -1
- package/dist/src/server/index.d.ts.map +1 -1
- package/dist/src/server/index.js +420 -412
- package/dist/src/server/mcp-server.d.ts +50 -81
- package/dist/src/server/mcp-server.d.ts.map +1 -1
- package/dist/src/server/oauth/index.d.ts +13 -0
- package/dist/src/server/oauth/index.d.ts.map +1 -0
- package/dist/src/server/oauth/middleware.d.ts +19 -0
- package/dist/src/server/oauth/middleware.d.ts.map +1 -0
- package/dist/src/server/oauth/providers/auth0.d.ts +22 -0
- package/dist/src/server/oauth/providers/auth0.d.ts.map +1 -0
- package/dist/src/server/oauth/providers/custom.d.ts +19 -0
- package/dist/src/server/oauth/providers/custom.d.ts.map +1 -0
- package/dist/src/server/oauth/providers/keycloak.d.ts +22 -0
- package/dist/src/server/oauth/providers/keycloak.d.ts.map +1 -0
- package/dist/src/server/oauth/providers/supabase.d.ts +24 -0
- package/dist/src/server/oauth/providers/supabase.d.ts.map +1 -0
- package/dist/src/server/oauth/providers/types.d.ts +138 -0
- package/dist/src/server/oauth/providers/types.d.ts.map +1 -0
- package/dist/src/server/oauth/providers/workos.d.ts +30 -0
- package/dist/src/server/oauth/providers/workos.d.ts.map +1 -0
- package/dist/src/server/oauth/providers.d.ts +208 -0
- package/dist/src/server/oauth/providers.d.ts.map +1 -0
- package/dist/src/server/oauth/routes.d.ts +33 -0
- package/dist/src/server/oauth/routes.d.ts.map +1 -0
- package/dist/src/server/oauth/utils.d.ts +155 -0
- package/dist/src/server/oauth/utils.d.ts.map +1 -0
- package/dist/src/server/types/common.d.ts +47 -0
- package/dist/src/server/types/common.d.ts.map +1 -1
- package/dist/src/server/types/context.d.ts +34 -0
- package/dist/src/server/types/context.d.ts.map +1 -0
- package/dist/src/server/types/index.d.ts +2 -1
- package/dist/src/server/types/index.d.ts.map +1 -1
- package/dist/src/server/types/tool.d.ts +82 -9
- package/dist/src/server/types/tool.d.ts.map +1 -1
- package/dist/src/server/utils/index.d.ts +6 -0
- package/dist/src/server/utils/index.d.ts.map +1 -0
- package/dist/src/server/utils/response-helpers.d.ts +151 -0
- package/dist/src/server/utils/response-helpers.d.ts.map +1 -0
- package/dist/src/server/utils/runtime.d.ts +25 -0
- package/dist/src/server/utils/runtime.d.ts.map +1 -0
- package/dist/src/task_managers/streamable_http.d.ts +1 -0
- package/dist/src/task_managers/streamable_http.d.ts.map +1 -1
- package/dist/tsup.config.d.ts.map +1 -1
- package/package.json +14 -5
- /package/dist/{chunk-MCF5P6GJ.js → chunk-GVVPUU5K.js} +0 -0
|
@@ -6,6 +6,9 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
8
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
9
12
|
var __export = (target, all) => {
|
|
10
13
|
for (var name in all)
|
|
11
14
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -16,44 +19,1082 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
19
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
20
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
21
|
}
|
|
19
|
-
return to;
|
|
20
|
-
};
|
|
21
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
-
mod
|
|
28
|
-
));
|
|
29
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
25
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
27
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
|
+
mod
|
|
31
|
+
));
|
|
32
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
33
|
+
|
|
34
|
+
// src/server/utils/runtime.ts
|
|
35
|
+
function getEnv2(key) {
|
|
36
|
+
if (isDeno2) {
|
|
37
|
+
return globalThis.Deno.env.get(key);
|
|
38
|
+
}
|
|
39
|
+
return process.env[key];
|
|
40
|
+
}
|
|
41
|
+
function getCwd() {
|
|
42
|
+
if (isDeno2) {
|
|
43
|
+
return globalThis.Deno.cwd();
|
|
44
|
+
}
|
|
45
|
+
return process.cwd();
|
|
46
|
+
}
|
|
47
|
+
function generateUUID() {
|
|
48
|
+
return globalThis.crypto.randomUUID();
|
|
49
|
+
}
|
|
50
|
+
var isDeno2, fsHelpers, pathHelpers;
|
|
51
|
+
var init_runtime = __esm({
|
|
52
|
+
"src/server/utils/runtime.ts"() {
|
|
53
|
+
"use strict";
|
|
54
|
+
isDeno2 = typeof globalThis.Deno !== "undefined";
|
|
55
|
+
__name(getEnv2, "getEnv");
|
|
56
|
+
__name(getCwd, "getCwd");
|
|
57
|
+
fsHelpers = {
|
|
58
|
+
async readFileSync(path, encoding = "utf8") {
|
|
59
|
+
if (isDeno2) {
|
|
60
|
+
return await globalThis.Deno.readTextFile(path);
|
|
61
|
+
}
|
|
62
|
+
const { readFileSync } = await import("fs");
|
|
63
|
+
const result = readFileSync(path, encoding);
|
|
64
|
+
return typeof result === "string" ? result : result.toString(encoding);
|
|
65
|
+
},
|
|
66
|
+
async readFile(path) {
|
|
67
|
+
if (isDeno2) {
|
|
68
|
+
const data = await globalThis.Deno.readFile(path);
|
|
69
|
+
return data.buffer;
|
|
70
|
+
}
|
|
71
|
+
const { readFileSync } = await import("fs");
|
|
72
|
+
const buffer = readFileSync(path);
|
|
73
|
+
return buffer.buffer.slice(
|
|
74
|
+
buffer.byteOffset,
|
|
75
|
+
buffer.byteOffset + buffer.byteLength
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
async existsSync(path) {
|
|
79
|
+
if (isDeno2) {
|
|
80
|
+
try {
|
|
81
|
+
await globalThis.Deno.stat(path);
|
|
82
|
+
return true;
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const { existsSync } = await import("fs");
|
|
88
|
+
return existsSync(path);
|
|
89
|
+
},
|
|
90
|
+
async readdirSync(path) {
|
|
91
|
+
if (isDeno2) {
|
|
92
|
+
const entries = [];
|
|
93
|
+
for await (const entry of globalThis.Deno.readDir(path)) {
|
|
94
|
+
entries.push(entry.name);
|
|
95
|
+
}
|
|
96
|
+
return entries;
|
|
97
|
+
}
|
|
98
|
+
const { readdirSync } = await import("fs");
|
|
99
|
+
return readdirSync(path);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
pathHelpers = {
|
|
103
|
+
join(...paths) {
|
|
104
|
+
if (isDeno2) {
|
|
105
|
+
return paths.join("/").replace(/\/+/g, "/");
|
|
106
|
+
}
|
|
107
|
+
return paths.join("/").replace(/\/+/g, "/");
|
|
108
|
+
},
|
|
109
|
+
relative(from, to) {
|
|
110
|
+
const fromParts = from.split("/").filter((p) => p);
|
|
111
|
+
const toParts = to.split("/").filter((p) => p);
|
|
112
|
+
let i = 0;
|
|
113
|
+
while (i < fromParts.length && i < toParts.length && fromParts[i] === toParts[i]) {
|
|
114
|
+
i++;
|
|
115
|
+
}
|
|
116
|
+
const upCount = fromParts.length - i;
|
|
117
|
+
const relativeParts = [...Array(upCount).fill(".."), ...toParts.slice(i)];
|
|
118
|
+
return relativeParts.join("/");
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
__name(generateUUID, "generateUUID");
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// src/server/oauth/providers/supabase.ts
|
|
126
|
+
var import_jose, SupabaseOAuthProvider;
|
|
127
|
+
var init_supabase = __esm({
|
|
128
|
+
"src/server/oauth/providers/supabase.ts"() {
|
|
129
|
+
"use strict";
|
|
130
|
+
import_jose = require("jose");
|
|
131
|
+
SupabaseOAuthProvider = class {
|
|
132
|
+
static {
|
|
133
|
+
__name(this, "SupabaseOAuthProvider");
|
|
134
|
+
}
|
|
135
|
+
config;
|
|
136
|
+
supabaseUrl;
|
|
137
|
+
supabaseAuthUrl;
|
|
138
|
+
issuer;
|
|
139
|
+
jwks = null;
|
|
140
|
+
constructor(config) {
|
|
141
|
+
this.config = config;
|
|
142
|
+
this.supabaseUrl = `https://${config.projectId}.supabase.co`;
|
|
143
|
+
this.supabaseAuthUrl = `${this.supabaseUrl}/auth/v1`;
|
|
144
|
+
this.issuer = `${this.supabaseUrl}/auth/v1`;
|
|
145
|
+
}
|
|
146
|
+
getJWKS() {
|
|
147
|
+
if (!this.jwks) {
|
|
148
|
+
this.jwks = (0, import_jose.createRemoteJWKSet)(
|
|
149
|
+
new URL(`${this.issuer}/.well-known/jwks.json`)
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return this.jwks;
|
|
153
|
+
}
|
|
154
|
+
async verifyToken(token) {
|
|
155
|
+
if (this.config.skipVerification) {
|
|
156
|
+
console.warn(
|
|
157
|
+
"[Supabase OAuth] \u26A0\uFE0F SKIPPING VERIFICATION (DEVELOPMENT MODE)"
|
|
158
|
+
);
|
|
159
|
+
console.warn(
|
|
160
|
+
"[Supabase OAuth] This is NOT secure! Only use for testing!"
|
|
161
|
+
);
|
|
162
|
+
const payload = (0, import_jose.decodeJwt)(token);
|
|
163
|
+
return { payload, protectedHeader: (0, import_jose.decodeProtectedHeader)(token) };
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
const header = (0, import_jose.decodeProtectedHeader)(token);
|
|
167
|
+
if (header.alg === "HS256") {
|
|
168
|
+
if (!this.config.jwtSecret) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
"JWT Secret is required for HS256 tokens. Get it from: Supabase Dashboard \u2192 Project Settings \u2192 API \u2192 JWT Settings"
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
const secret = new TextEncoder().encode(this.config.jwtSecret);
|
|
174
|
+
const result = await (0, import_jose.jwtVerify)(token, secret, {
|
|
175
|
+
issuer: this.issuer,
|
|
176
|
+
audience: "authenticated"
|
|
177
|
+
});
|
|
178
|
+
return result;
|
|
179
|
+
} else if (header.alg === "ES256") {
|
|
180
|
+
const result = await (0, import_jose.jwtVerify)(token, this.getJWKS(), {
|
|
181
|
+
issuer: this.issuer,
|
|
182
|
+
audience: "authenticated"
|
|
183
|
+
});
|
|
184
|
+
return result;
|
|
185
|
+
} else {
|
|
186
|
+
throw new Error(`Unsupported algorithm: ${header.alg}`);
|
|
187
|
+
}
|
|
188
|
+
} catch (error2) {
|
|
189
|
+
throw new Error(`Supabase JWT verification failed: ${error2}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
getUserInfo(payload) {
|
|
193
|
+
return {
|
|
194
|
+
userId: payload.sub || payload.user_id,
|
|
195
|
+
email: payload.email,
|
|
196
|
+
name: payload.user_metadata?.name || payload.user_metadata?.full_name,
|
|
197
|
+
username: payload.user_metadata?.username,
|
|
198
|
+
picture: payload.user_metadata?.avatar_url,
|
|
199
|
+
roles: payload.role ? [payload.role] : [],
|
|
200
|
+
permissions: payload.aal ? [`aal:${payload.aal}`] : [],
|
|
201
|
+
// Include Supabase-specific claims
|
|
202
|
+
aal: payload.aal,
|
|
203
|
+
// Authentication Assurance Level
|
|
204
|
+
amr: payload.amr,
|
|
205
|
+
// Authentication Methods References
|
|
206
|
+
session_id: payload.session_id
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
getIssuer() {
|
|
210
|
+
return this.issuer;
|
|
211
|
+
}
|
|
212
|
+
getAuthEndpoint() {
|
|
213
|
+
return `${this.supabaseAuthUrl}/authorize`;
|
|
214
|
+
}
|
|
215
|
+
getTokenEndpoint() {
|
|
216
|
+
return `${this.supabaseAuthUrl}/token`;
|
|
217
|
+
}
|
|
218
|
+
getScopesSupported() {
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
getGrantTypesSupported() {
|
|
222
|
+
return ["authorization_code", "refresh_token"];
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// src/server/oauth/providers/auth0.ts
|
|
229
|
+
var import_jose2, Auth0OAuthProvider;
|
|
230
|
+
var init_auth0 = __esm({
|
|
231
|
+
"src/server/oauth/providers/auth0.ts"() {
|
|
232
|
+
"use strict";
|
|
233
|
+
import_jose2 = require("jose");
|
|
234
|
+
Auth0OAuthProvider = class {
|
|
235
|
+
static {
|
|
236
|
+
__name(this, "Auth0OAuthProvider");
|
|
237
|
+
}
|
|
238
|
+
config;
|
|
239
|
+
issuer;
|
|
240
|
+
jwks = null;
|
|
241
|
+
constructor(config) {
|
|
242
|
+
this.config = config;
|
|
243
|
+
this.issuer = `https://${config.domain}`;
|
|
244
|
+
}
|
|
245
|
+
getJWKS() {
|
|
246
|
+
if (!this.jwks) {
|
|
247
|
+
this.jwks = (0, import_jose2.createRemoteJWKSet)(
|
|
248
|
+
new URL(`${this.issuer}/.well-known/jwks.json`)
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
return this.jwks;
|
|
252
|
+
}
|
|
253
|
+
async verifyToken(token) {
|
|
254
|
+
if (this.config.verifyJwt === false) {
|
|
255
|
+
console.warn("[Auth0 OAuth] \u26A0\uFE0F JWT verification is disabled");
|
|
256
|
+
console.warn("[Auth0 OAuth] Enable verifyJwt: true for production");
|
|
257
|
+
const parts = token.split(".");
|
|
258
|
+
if (parts.length !== 3) {
|
|
259
|
+
throw new Error("Invalid JWT format");
|
|
260
|
+
}
|
|
261
|
+
const payload = JSON.parse(
|
|
262
|
+
Buffer.from(parts[1], "base64url").toString("utf8")
|
|
263
|
+
);
|
|
264
|
+
return { payload };
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
const result = await (0, import_jose2.jwtVerify)(token, this.getJWKS(), {
|
|
268
|
+
issuer: this.issuer,
|
|
269
|
+
audience: this.config.audience
|
|
270
|
+
});
|
|
271
|
+
return result;
|
|
272
|
+
} catch (error2) {
|
|
273
|
+
throw new Error(`Auth0 JWT verification failed: ${error2}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
getUserInfo(payload) {
|
|
277
|
+
return {
|
|
278
|
+
userId: payload.sub,
|
|
279
|
+
email: payload.email,
|
|
280
|
+
name: payload.name,
|
|
281
|
+
username: payload.username,
|
|
282
|
+
nickname: payload.nickname,
|
|
283
|
+
picture: payload.picture,
|
|
284
|
+
// Auth0 includes permissions directly in the token
|
|
285
|
+
permissions: payload.permissions || [],
|
|
286
|
+
// Auth0 can include roles (if configured)
|
|
287
|
+
roles: payload.roles || payload["https://your-app.com/roles"] || [],
|
|
288
|
+
// Include scope as well
|
|
289
|
+
scopes: payload.scope ? payload.scope.split(" ") : [],
|
|
290
|
+
// Additional Auth0-specific claims
|
|
291
|
+
email_verified: payload.email_verified,
|
|
292
|
+
updated_at: payload.updated_at
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
getIssuer() {
|
|
296
|
+
return this.issuer;
|
|
297
|
+
}
|
|
298
|
+
getAuthEndpoint() {
|
|
299
|
+
return `${this.issuer}/authorize`;
|
|
300
|
+
}
|
|
301
|
+
getTokenEndpoint() {
|
|
302
|
+
return `${this.issuer}/oauth/token`;
|
|
303
|
+
}
|
|
304
|
+
getScopesSupported() {
|
|
305
|
+
return ["openid", "profile", "email", "offline_access"];
|
|
306
|
+
}
|
|
307
|
+
getGrantTypesSupported() {
|
|
308
|
+
return ["authorization_code", "refresh_token"];
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// src/server/oauth/providers/keycloak.ts
|
|
315
|
+
var import_jose3, KeycloakOAuthProvider;
|
|
316
|
+
var init_keycloak = __esm({
|
|
317
|
+
"src/server/oauth/providers/keycloak.ts"() {
|
|
318
|
+
"use strict";
|
|
319
|
+
import_jose3 = require("jose");
|
|
320
|
+
KeycloakOAuthProvider = class {
|
|
321
|
+
static {
|
|
322
|
+
__name(this, "KeycloakOAuthProvider");
|
|
323
|
+
}
|
|
324
|
+
config;
|
|
325
|
+
issuer;
|
|
326
|
+
jwks = null;
|
|
327
|
+
constructor(config) {
|
|
328
|
+
this.config = config;
|
|
329
|
+
const serverUrl = config.serverUrl.replace(/\/$/, "");
|
|
330
|
+
this.issuer = `${serverUrl}/realms/${config.realm}`;
|
|
331
|
+
}
|
|
332
|
+
getJWKS() {
|
|
333
|
+
if (!this.jwks) {
|
|
334
|
+
this.jwks = (0, import_jose3.createRemoteJWKSet)(
|
|
335
|
+
new URL(`${this.issuer}/protocol/openid-connect/certs`)
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
return this.jwks;
|
|
339
|
+
}
|
|
340
|
+
async verifyToken(token) {
|
|
341
|
+
if (this.config.verifyJwt === false) {
|
|
342
|
+
console.warn("[Keycloak OAuth] \u26A0\uFE0F JWT verification is disabled");
|
|
343
|
+
console.warn(
|
|
344
|
+
"[Keycloak OAuth] Enable verifyJwt: true for production"
|
|
345
|
+
);
|
|
346
|
+
const parts = token.split(".");
|
|
347
|
+
if (parts.length !== 3) {
|
|
348
|
+
throw new Error("Invalid JWT format");
|
|
349
|
+
}
|
|
350
|
+
const payload = JSON.parse(
|
|
351
|
+
Buffer.from(parts[1], "base64url").toString("utf8")
|
|
352
|
+
);
|
|
353
|
+
return { payload };
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
const result = await (0, import_jose3.jwtVerify)(token, this.getJWKS(), {
|
|
357
|
+
issuer: this.issuer,
|
|
358
|
+
// Don't verify audience if not specified
|
|
359
|
+
...this.config.clientId && { audience: this.config.clientId }
|
|
360
|
+
});
|
|
361
|
+
return result;
|
|
362
|
+
} catch (error2) {
|
|
363
|
+
throw new Error(`Keycloak JWT verification failed: ${error2}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
getUserInfo(payload) {
|
|
367
|
+
const realmRoles = payload.realm_access?.roles || [];
|
|
368
|
+
const clientRoles = this.config.clientId && payload.resource_access?.[this.config.clientId]?.roles || [];
|
|
369
|
+
const allRoles = [...realmRoles, ...clientRoles];
|
|
370
|
+
const permissions = [];
|
|
371
|
+
if (payload.resource_access) {
|
|
372
|
+
Object.entries(payload.resource_access).forEach(
|
|
373
|
+
([resource2, access]) => {
|
|
374
|
+
if (access.roles) {
|
|
375
|
+
access.roles.forEach((role) => {
|
|
376
|
+
permissions.push(`${resource2}:${role}`);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
userId: payload.sub,
|
|
384
|
+
email: payload.email,
|
|
385
|
+
name: payload.name,
|
|
386
|
+
username: payload.preferred_username,
|
|
387
|
+
nickname: payload.preferred_username,
|
|
388
|
+
picture: payload.picture,
|
|
389
|
+
roles: allRoles,
|
|
390
|
+
permissions,
|
|
391
|
+
// Include scope as well
|
|
392
|
+
scopes: payload.scope ? payload.scope.split(" ") : [],
|
|
393
|
+
// Keycloak-specific claims
|
|
394
|
+
email_verified: payload.email_verified,
|
|
395
|
+
given_name: payload.given_name,
|
|
396
|
+
family_name: payload.family_name,
|
|
397
|
+
realm_access: payload.realm_access,
|
|
398
|
+
resource_access: payload.resource_access
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
getIssuer() {
|
|
402
|
+
return this.issuer;
|
|
403
|
+
}
|
|
404
|
+
getAuthEndpoint() {
|
|
405
|
+
return `${this.issuer}/protocol/openid-connect/auth`;
|
|
406
|
+
}
|
|
407
|
+
getTokenEndpoint() {
|
|
408
|
+
return `${this.issuer}/protocol/openid-connect/token`;
|
|
409
|
+
}
|
|
410
|
+
getScopesSupported() {
|
|
411
|
+
return ["openid", "profile", "email", "offline_access", "roles"];
|
|
412
|
+
}
|
|
413
|
+
getGrantTypesSupported() {
|
|
414
|
+
return ["authorization_code", "refresh_token", "client_credentials"];
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// src/server/oauth/providers/workos.ts
|
|
421
|
+
var import_jose4, WorkOSOAuthProvider;
|
|
422
|
+
var init_workos = __esm({
|
|
423
|
+
"src/server/oauth/providers/workos.ts"() {
|
|
424
|
+
"use strict";
|
|
425
|
+
import_jose4 = require("jose");
|
|
426
|
+
WorkOSOAuthProvider = class {
|
|
427
|
+
static {
|
|
428
|
+
__name(this, "WorkOSOAuthProvider");
|
|
429
|
+
}
|
|
430
|
+
config;
|
|
431
|
+
issuer;
|
|
432
|
+
jwks = null;
|
|
433
|
+
constructor(config) {
|
|
434
|
+
this.config = config;
|
|
435
|
+
this.issuer = `https://${config.subdomain}.authkit.app`;
|
|
436
|
+
}
|
|
437
|
+
getJWKS() {
|
|
438
|
+
if (!this.jwks) {
|
|
439
|
+
this.jwks = (0, import_jose4.createRemoteJWKSet)(new URL(`${this.issuer}/oauth2/jwks`));
|
|
440
|
+
}
|
|
441
|
+
return this.jwks;
|
|
442
|
+
}
|
|
443
|
+
async verifyToken(token) {
|
|
444
|
+
if (this.config.verifyJwt === false) {
|
|
445
|
+
console.warn("[WorkOS OAuth] \u26A0\uFE0F JWT verification is disabled");
|
|
446
|
+
console.warn("[WorkOS OAuth] Enable verifyJwt: true for production");
|
|
447
|
+
const parts = token.split(".");
|
|
448
|
+
if (parts.length !== 3) {
|
|
449
|
+
throw new Error("Invalid JWT format");
|
|
450
|
+
}
|
|
451
|
+
const payload = (0, import_jose4.decodeJwt)(token);
|
|
452
|
+
return { payload };
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
const result = await (0, import_jose4.jwtVerify)(token, this.getJWKS(), {
|
|
456
|
+
issuer: this.issuer
|
|
457
|
+
});
|
|
458
|
+
return result;
|
|
459
|
+
} catch (error2) {
|
|
460
|
+
throw new Error(`WorkOS JWT verification failed: ${error2}`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
getUserInfo(payload) {
|
|
464
|
+
return {
|
|
465
|
+
userId: payload.sub,
|
|
466
|
+
email: payload.email,
|
|
467
|
+
name: payload.name,
|
|
468
|
+
username: payload.preferred_username,
|
|
469
|
+
picture: payload.picture,
|
|
470
|
+
// WorkOS includes permissions and roles in token
|
|
471
|
+
permissions: payload.permissions || [],
|
|
472
|
+
roles: payload.roles || [],
|
|
473
|
+
// Include scope as well
|
|
474
|
+
scopes: payload.scope ? payload.scope.split(" ") : [],
|
|
475
|
+
// Additional WorkOS-specific claims
|
|
476
|
+
email_verified: payload.email_verified,
|
|
477
|
+
organization_id: payload.org_id,
|
|
478
|
+
sid: payload.sid
|
|
479
|
+
// Session ID
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
getIssuer() {
|
|
483
|
+
return this.issuer;
|
|
484
|
+
}
|
|
485
|
+
getAuthEndpoint() {
|
|
486
|
+
return `${this.issuer}/oauth2/authorize`;
|
|
487
|
+
}
|
|
488
|
+
getTokenEndpoint() {
|
|
489
|
+
return `${this.issuer}/oauth2/token`;
|
|
490
|
+
}
|
|
491
|
+
getScopesSupported() {
|
|
492
|
+
return ["email", "offline_access", "openid", "profile"];
|
|
493
|
+
}
|
|
494
|
+
getGrantTypesSupported() {
|
|
495
|
+
return ["authorization_code", "refresh_token"];
|
|
496
|
+
}
|
|
497
|
+
getMode() {
|
|
498
|
+
if (this.config.clientId) {
|
|
499
|
+
console.log("[WorkOS OAuth] Using proxy mode (pre-registered client)");
|
|
500
|
+
return "proxy";
|
|
501
|
+
}
|
|
502
|
+
console.log(
|
|
503
|
+
"[WorkOS OAuth] Using direct mode (Dynamic Client Registration)"
|
|
504
|
+
);
|
|
505
|
+
return "direct";
|
|
506
|
+
}
|
|
507
|
+
getRegistrationEndpoint() {
|
|
508
|
+
if (this.config.clientId) {
|
|
509
|
+
return void 0;
|
|
510
|
+
}
|
|
511
|
+
return `${this.issuer}/oauth2/register`;
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
// src/server/oauth/providers/custom.ts
|
|
518
|
+
var CustomOAuthProvider;
|
|
519
|
+
var init_custom = __esm({
|
|
520
|
+
"src/server/oauth/providers/custom.ts"() {
|
|
521
|
+
"use strict";
|
|
522
|
+
CustomOAuthProvider = class {
|
|
523
|
+
static {
|
|
524
|
+
__name(this, "CustomOAuthProvider");
|
|
525
|
+
}
|
|
526
|
+
config;
|
|
527
|
+
constructor(config) {
|
|
528
|
+
this.config = config;
|
|
529
|
+
}
|
|
530
|
+
async verifyToken(token) {
|
|
531
|
+
try {
|
|
532
|
+
const result = await this.config.verifyToken(token);
|
|
533
|
+
return { payload: result };
|
|
534
|
+
} catch (error2) {
|
|
535
|
+
throw new Error(`Custom OAuth verification failed: ${error2}`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
getUserInfo(payload) {
|
|
539
|
+
if (this.config.getUserInfo) {
|
|
540
|
+
return this.config.getUserInfo(payload);
|
|
541
|
+
}
|
|
542
|
+
return {
|
|
543
|
+
userId: payload.sub || payload.user_id || payload.id,
|
|
544
|
+
email: payload.email,
|
|
545
|
+
name: payload.name,
|
|
546
|
+
username: payload.username || payload.preferred_username,
|
|
547
|
+
nickname: payload.nickname,
|
|
548
|
+
picture: payload.picture || payload.avatar_url,
|
|
549
|
+
roles: payload.roles || [],
|
|
550
|
+
permissions: payload.permissions || [],
|
|
551
|
+
scopes: payload.scope ? payload.scope.split(" ") : []
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
getIssuer() {
|
|
555
|
+
return this.config.issuer;
|
|
556
|
+
}
|
|
557
|
+
getAuthEndpoint() {
|
|
558
|
+
return this.config.authEndpoint;
|
|
559
|
+
}
|
|
560
|
+
getTokenEndpoint() {
|
|
561
|
+
return this.config.tokenEndpoint;
|
|
562
|
+
}
|
|
563
|
+
getScopesSupported() {
|
|
564
|
+
return this.config.scopesSupported || ["openid", "profile", "email"];
|
|
565
|
+
}
|
|
566
|
+
getGrantTypesSupported() {
|
|
567
|
+
return this.config.grantTypesSupported || ["authorization_code", "refresh_token"];
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// src/server/oauth/providers.ts
|
|
574
|
+
function oauthSupabaseProvider(config = {}) {
|
|
575
|
+
const projectId = config.projectId ?? getEnv2("MCP_USE_OAUTH_SUPABASE_PROJECT_ID");
|
|
576
|
+
const jwtSecret = config.jwtSecret ?? getEnv2("MCP_USE_OAUTH_SUPABASE_JWT_SECRET");
|
|
577
|
+
if (!projectId) {
|
|
578
|
+
throw new Error(
|
|
579
|
+
"Supabase projectId is required. Set MCP_USE_OAUTH_SUPABASE_PROJECT_ID environment variable or pass projectId in config."
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
return new SupabaseOAuthProvider({
|
|
583
|
+
provider: "supabase",
|
|
584
|
+
projectId,
|
|
585
|
+
jwtSecret,
|
|
586
|
+
skipVerification: config.skipVerification
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
function oauthAuth0Provider(config = {}) {
|
|
590
|
+
const domain = config.domain ?? getEnv2("MCP_USE_OAUTH_AUTH0_DOMAIN");
|
|
591
|
+
const audience = config.audience ?? getEnv2("MCP_USE_OAUTH_AUTH0_AUDIENCE");
|
|
592
|
+
if (!domain) {
|
|
593
|
+
throw new Error(
|
|
594
|
+
"Auth0 domain is required. Set MCP_USE_OAUTH_AUTH0_DOMAIN environment variable or pass domain in config."
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
if (!audience) {
|
|
598
|
+
throw new Error(
|
|
599
|
+
"Auth0 audience is required. Set MCP_USE_OAUTH_AUTH0_AUDIENCE environment variable or pass audience in config."
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
return new Auth0OAuthProvider({
|
|
603
|
+
provider: "auth0",
|
|
604
|
+
domain,
|
|
605
|
+
audience,
|
|
606
|
+
verifyJwt: config.verifyJwt
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
function oauthKeycloakProvider(config = {}) {
|
|
610
|
+
const serverUrl = config.serverUrl ?? getEnv2("MCP_USE_OAUTH_KEYCLOAK_SERVER_URL");
|
|
611
|
+
const realm = config.realm ?? getEnv2("MCP_USE_OAUTH_KEYCLOAK_REALM");
|
|
612
|
+
const clientId = config.clientId ?? getEnv2("MCP_USE_OAUTH_KEYCLOAK_CLIENT_ID");
|
|
613
|
+
if (!serverUrl) {
|
|
614
|
+
throw new Error(
|
|
615
|
+
"Keycloak serverUrl is required. Set MCP_USE_OAUTH_KEYCLOAK_SERVER_URL environment variable or pass serverUrl in config."
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
if (!realm) {
|
|
619
|
+
throw new Error(
|
|
620
|
+
"Keycloak realm is required. Set MCP_USE_OAUTH_KEYCLOAK_REALM environment variable or pass realm in config."
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
return new KeycloakOAuthProvider({
|
|
624
|
+
provider: "keycloak",
|
|
625
|
+
serverUrl,
|
|
626
|
+
realm,
|
|
627
|
+
clientId,
|
|
628
|
+
verifyJwt: config.verifyJwt
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
function oauthWorkOSProvider(config = {}) {
|
|
632
|
+
const subdomain = config.subdomain ?? getEnv2("MCP_USE_OAUTH_WORKOS_SUBDOMAIN");
|
|
633
|
+
const clientId = config.clientId ?? getEnv2("MCP_USE_OAUTH_WORKOS_CLIENT_ID");
|
|
634
|
+
const apiKey = config.apiKey ?? getEnv2("MCP_USE_OAUTH_WORKOS_API_KEY");
|
|
635
|
+
if (!subdomain) {
|
|
636
|
+
throw new Error(
|
|
637
|
+
"WorkOS subdomain is required. Set MCP_USE_OAUTH_WORKOS_SUBDOMAIN environment variable or pass subdomain in config."
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
if (clientId) {
|
|
641
|
+
console.log("[WorkOS OAuth] Using pre-registered OAuth client mode");
|
|
642
|
+
console.log(`[WorkOS OAuth] - Client ID: ${clientId}`);
|
|
643
|
+
console.log(
|
|
644
|
+
"[WorkOS OAuth] - Make sure this client exists in WorkOS Dashboard"
|
|
645
|
+
);
|
|
646
|
+
console.log(
|
|
647
|
+
"[WorkOS OAuth] - Configure redirect URIs to match your MCP client"
|
|
648
|
+
);
|
|
649
|
+
} else {
|
|
650
|
+
console.log("[WorkOS OAuth] Using Dynamic Client Registration (DCR) mode");
|
|
651
|
+
console.log(
|
|
652
|
+
"[WorkOS OAuth] - MCP clients will register themselves automatically"
|
|
653
|
+
);
|
|
654
|
+
console.log(
|
|
655
|
+
"[WorkOS OAuth] - Make sure DCR is enabled in WorkOS Dashboard"
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
return new WorkOSOAuthProvider({
|
|
659
|
+
provider: "workos",
|
|
660
|
+
subdomain,
|
|
661
|
+
clientId,
|
|
662
|
+
apiKey,
|
|
663
|
+
verifyJwt: config.verifyJwt
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
function oauthCustomProvider(config) {
|
|
667
|
+
return new CustomOAuthProvider({
|
|
668
|
+
provider: "custom",
|
|
669
|
+
...config
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
var init_providers = __esm({
|
|
673
|
+
"src/server/oauth/providers.ts"() {
|
|
674
|
+
"use strict";
|
|
675
|
+
init_supabase();
|
|
676
|
+
init_auth0();
|
|
677
|
+
init_keycloak();
|
|
678
|
+
init_workos();
|
|
679
|
+
init_custom();
|
|
680
|
+
init_runtime();
|
|
681
|
+
__name(oauthSupabaseProvider, "oauthSupabaseProvider");
|
|
682
|
+
__name(oauthAuth0Provider, "oauthAuth0Provider");
|
|
683
|
+
__name(oauthKeycloakProvider, "oauthKeycloakProvider");
|
|
684
|
+
__name(oauthWorkOSProvider, "oauthWorkOSProvider");
|
|
685
|
+
__name(oauthCustomProvider, "oauthCustomProvider");
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
// src/server/oauth/middleware.ts
|
|
690
|
+
function createBearerAuthMiddleware(provider, baseUrl) {
|
|
691
|
+
return async (c, next) => {
|
|
692
|
+
const authHeader = c.req.header("Authorization");
|
|
693
|
+
const getWWWAuthenticateHeader = /* @__PURE__ */ __name(() => {
|
|
694
|
+
const base = baseUrl || new URL(c.req.url).origin;
|
|
695
|
+
const parts = [
|
|
696
|
+
'Bearer error="unauthorized"',
|
|
697
|
+
'error_description="Authorization needed"'
|
|
698
|
+
];
|
|
699
|
+
parts.push(
|
|
700
|
+
`resource_metadata="${base}/.well-known/oauth-protected-resource"`
|
|
701
|
+
);
|
|
702
|
+
return parts.join(", ");
|
|
703
|
+
}, "getWWWAuthenticateHeader");
|
|
704
|
+
if (!authHeader) {
|
|
705
|
+
c.header("WWW-Authenticate", getWWWAuthenticateHeader());
|
|
706
|
+
return c.json({ error: "Missing Authorization header" }, 401);
|
|
707
|
+
}
|
|
708
|
+
const [type, token] = authHeader.split(" ");
|
|
709
|
+
if (type.toLowerCase() !== "bearer" || !token) {
|
|
710
|
+
c.header("WWW-Authenticate", getWWWAuthenticateHeader());
|
|
711
|
+
return c.json(
|
|
712
|
+
{
|
|
713
|
+
error: 'Invalid Authorization header format, expected "Bearer TOKEN"'
|
|
714
|
+
},
|
|
715
|
+
401
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
try {
|
|
719
|
+
const result = await provider.verifyToken(token);
|
|
720
|
+
const payload = result.payload;
|
|
721
|
+
const user = provider.getUserInfo(payload);
|
|
722
|
+
const authInfo = {
|
|
723
|
+
user,
|
|
724
|
+
payload,
|
|
725
|
+
accessToken: token,
|
|
726
|
+
// Extract scopes from scope claim (OAuth standard)
|
|
727
|
+
scopes: payload.scope ? payload.scope.split(" ") : [],
|
|
728
|
+
// Extract permissions (Auth0 style, or custom)
|
|
729
|
+
permissions: payload.permissions || []
|
|
730
|
+
};
|
|
731
|
+
c.set("auth", authInfo);
|
|
732
|
+
c.auth = authInfo;
|
|
733
|
+
c.set("user", user);
|
|
734
|
+
c.set("payload", payload);
|
|
735
|
+
c.set("accessToken", token);
|
|
736
|
+
await next();
|
|
737
|
+
} catch (error2) {
|
|
738
|
+
c.header("WWW-Authenticate", getWWWAuthenticateHeader());
|
|
739
|
+
return c.json({ error: `Invalid token: ${error2}` }, 401);
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
var init_middleware = __esm({
|
|
744
|
+
"src/server/oauth/middleware.ts"() {
|
|
745
|
+
"use strict";
|
|
746
|
+
__name(createBearerAuthMiddleware, "createBearerAuthMiddleware");
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
// src/server/oauth/routes.ts
|
|
751
|
+
function setupOAuthRoutes(app, provider, baseUrl) {
|
|
752
|
+
const mode = provider.getMode?.() || "proxy";
|
|
753
|
+
app.use(
|
|
754
|
+
"/.well-known/*",
|
|
755
|
+
(0, import_cors.cors)({
|
|
756
|
+
origin: "*",
|
|
757
|
+
// Allow all origins for metadata discovery
|
|
758
|
+
allowMethods: ["GET", "OPTIONS"],
|
|
759
|
+
allowHeaders: ["Content-Type", "Authorization"],
|
|
760
|
+
exposeHeaders: ["Content-Type"],
|
|
761
|
+
maxAge: 86400
|
|
762
|
+
// Cache preflight for 24 hours
|
|
763
|
+
})
|
|
764
|
+
);
|
|
765
|
+
if (mode === "proxy") {
|
|
766
|
+
app.use(
|
|
767
|
+
"/authorize",
|
|
768
|
+
(0, import_cors.cors)({
|
|
769
|
+
origin: "*",
|
|
770
|
+
allowMethods: ["GET", "POST", "OPTIONS"],
|
|
771
|
+
allowHeaders: ["Content-Type", "Authorization"],
|
|
772
|
+
maxAge: 86400
|
|
773
|
+
})
|
|
774
|
+
);
|
|
775
|
+
app.use(
|
|
776
|
+
"/token",
|
|
777
|
+
(0, import_cors.cors)({
|
|
778
|
+
origin: "*",
|
|
779
|
+
allowMethods: ["POST", "OPTIONS"],
|
|
780
|
+
allowHeaders: ["Content-Type", "Authorization"],
|
|
781
|
+
maxAge: 86400
|
|
782
|
+
})
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
if (mode === "proxy") {
|
|
786
|
+
const handleAuthorize = /* @__PURE__ */ __name(async (c) => {
|
|
787
|
+
const params = c.req.method === "POST" ? await c.req.parseBody() : c.req.query();
|
|
788
|
+
const clientId = params.client_id;
|
|
789
|
+
const redirectUri = params.redirect_uri;
|
|
790
|
+
const responseType = params.response_type;
|
|
791
|
+
const codeChallenge = params.code_challenge;
|
|
792
|
+
const codeChallengeMethod = params.code_challenge_method;
|
|
793
|
+
const state = params.state;
|
|
794
|
+
const scope = params.scope;
|
|
795
|
+
const audience = params.audience;
|
|
796
|
+
if (!clientId || !redirectUri || !responseType || !codeChallenge) {
|
|
797
|
+
return c.json(
|
|
798
|
+
{
|
|
799
|
+
error: "invalid_request",
|
|
800
|
+
error_description: "Missing required parameters"
|
|
801
|
+
},
|
|
802
|
+
400
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
const authUrl = new URL(provider.getAuthEndpoint());
|
|
806
|
+
authUrl.searchParams.set("client_id", clientId);
|
|
807
|
+
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
808
|
+
authUrl.searchParams.set("response_type", responseType);
|
|
809
|
+
authUrl.searchParams.set("code_challenge", codeChallenge);
|
|
810
|
+
authUrl.searchParams.set(
|
|
811
|
+
"code_challenge_method",
|
|
812
|
+
codeChallengeMethod || "S256"
|
|
813
|
+
);
|
|
814
|
+
if (state) authUrl.searchParams.set("state", state);
|
|
815
|
+
if (scope) authUrl.searchParams.set("scope", scope);
|
|
816
|
+
if (audience) authUrl.searchParams.set("audience", audience);
|
|
817
|
+
return c.redirect(authUrl.toString(), 302);
|
|
818
|
+
}, "handleAuthorize");
|
|
819
|
+
app.get("/authorize", handleAuthorize);
|
|
820
|
+
app.post("/authorize", handleAuthorize);
|
|
821
|
+
app.post("/token", async (c) => {
|
|
822
|
+
try {
|
|
823
|
+
const body = await c.req.parseBody();
|
|
824
|
+
const response = await fetch(provider.getTokenEndpoint(), {
|
|
825
|
+
method: "POST",
|
|
826
|
+
headers: {
|
|
827
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
828
|
+
},
|
|
829
|
+
body: new URLSearchParams(body).toString()
|
|
830
|
+
});
|
|
831
|
+
const data = await response.json();
|
|
832
|
+
if (!response.ok) {
|
|
833
|
+
return c.json(data, response.status);
|
|
834
|
+
}
|
|
835
|
+
return c.json(data);
|
|
836
|
+
} catch (error2) {
|
|
837
|
+
return c.json(
|
|
838
|
+
{
|
|
839
|
+
error: "server_error",
|
|
840
|
+
error_description: `Token exchange failed: ${error2}`
|
|
841
|
+
},
|
|
842
|
+
500
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
const handleAuthorizationServerMetadata = /* @__PURE__ */ __name(async (c) => {
|
|
848
|
+
const requestPath = new URL(c.req.url).pathname;
|
|
849
|
+
console.log(`[OAuth] Metadata request: ${requestPath} (mode: ${mode})`);
|
|
850
|
+
if (mode === "direct") {
|
|
851
|
+
try {
|
|
852
|
+
const metadataUrl = `${provider.getIssuer()}/.well-known/oauth-authorization-server`;
|
|
853
|
+
console.log(`[OAuth] Fetching metadata from provider: ${metadataUrl}`);
|
|
854
|
+
const response = await fetch(metadataUrl);
|
|
855
|
+
if (!response.ok) {
|
|
856
|
+
console.error(
|
|
857
|
+
`[OAuth] Failed to fetch provider metadata: ${response.status}`
|
|
858
|
+
);
|
|
859
|
+
return c.json(
|
|
860
|
+
{
|
|
861
|
+
error: "server_error",
|
|
862
|
+
error_description: `Failed to fetch provider metadata: ${response.status}`
|
|
863
|
+
},
|
|
864
|
+
500
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
const metadata = await response.json();
|
|
868
|
+
const hasRegisteredClient = provider.getRegistrationEndpoint && provider.config?.clientId;
|
|
869
|
+
if (hasRegisteredClient) {
|
|
870
|
+
console.log(
|
|
871
|
+
`[OAuth] Provider has pre-registered client - removing DCR endpoint`
|
|
872
|
+
);
|
|
873
|
+
delete metadata.registration_endpoint;
|
|
874
|
+
}
|
|
875
|
+
console.log(`[OAuth] Provider metadata retrieved successfully`);
|
|
876
|
+
console.log(`[OAuth] - Issuer: ${metadata.issuer}`);
|
|
877
|
+
console.log(
|
|
878
|
+
`[OAuth] - Registration endpoint: ${metadata.registration_endpoint || "not available (using pre-registered client)"}`
|
|
879
|
+
);
|
|
880
|
+
return c.json(metadata);
|
|
881
|
+
} catch (error2) {
|
|
882
|
+
console.error(`[OAuth] Error fetching provider metadata:`, error2);
|
|
883
|
+
return c.json(
|
|
884
|
+
{
|
|
885
|
+
error: "server_error",
|
|
886
|
+
error_description: `Failed to fetch provider metadata: ${error2}`
|
|
887
|
+
},
|
|
888
|
+
500
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
} else {
|
|
892
|
+
console.log(`[OAuth] Returning proxy mode metadata`);
|
|
893
|
+
return c.json({
|
|
894
|
+
issuer: provider.getIssuer(),
|
|
895
|
+
authorization_endpoint: `${baseUrl}/authorize`,
|
|
896
|
+
token_endpoint: `${baseUrl}/token`,
|
|
897
|
+
response_types_supported: ["code"],
|
|
898
|
+
grant_types_supported: provider.getGrantTypesSupported(),
|
|
899
|
+
code_challenge_methods_supported: ["S256"],
|
|
900
|
+
token_endpoint_auth_methods_supported: [
|
|
901
|
+
"client_secret_post",
|
|
902
|
+
"client_secret_basic",
|
|
903
|
+
"none"
|
|
904
|
+
],
|
|
905
|
+
scopes_supported: provider.getScopesSupported()
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
}, "handleAuthorizationServerMetadata");
|
|
909
|
+
app.get(
|
|
910
|
+
"/.well-known/oauth-authorization-server",
|
|
911
|
+
handleAuthorizationServerMetadata
|
|
912
|
+
);
|
|
913
|
+
app.get(
|
|
914
|
+
"/.well-known/openid-configuration",
|
|
915
|
+
handleAuthorizationServerMetadata
|
|
916
|
+
);
|
|
917
|
+
app.get("/.well-known/oauth-protected-resource", (c) => {
|
|
918
|
+
console.log(`[OAuth] Protected resource metadata request (mode: ${mode})`);
|
|
919
|
+
console.log(`[OAuth] - Resource: ${baseUrl}`);
|
|
920
|
+
console.log(`[OAuth] - Authorization server: ${provider.getIssuer()}`);
|
|
921
|
+
return c.json({
|
|
922
|
+
resource: baseUrl,
|
|
923
|
+
authorization_servers: [provider.getIssuer()],
|
|
924
|
+
bearer_methods_supported: ["header"],
|
|
925
|
+
resource_documentation: mode === "direct" ? "This resource uses direct OAuth flow. Clients communicate directly with the authorization server." : void 0
|
|
926
|
+
});
|
|
927
|
+
});
|
|
928
|
+
app.get("/.well-known/oauth-protected-resource/mcp", (c) => {
|
|
929
|
+
return c.json({
|
|
930
|
+
resource: `${baseUrl}/mcp`,
|
|
931
|
+
authorization_servers: [provider.getIssuer()],
|
|
932
|
+
bearer_methods_supported: ["header"]
|
|
933
|
+
});
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
var import_cors;
|
|
937
|
+
var init_routes = __esm({
|
|
938
|
+
"src/server/oauth/routes.ts"() {
|
|
939
|
+
"use strict";
|
|
940
|
+
import_cors = require("hono/cors");
|
|
941
|
+
__name(setupOAuthRoutes, "setupOAuthRoutes");
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
// src/server/oauth/utils.ts
|
|
946
|
+
function getAuth(context) {
|
|
947
|
+
return context.get("auth");
|
|
948
|
+
}
|
|
949
|
+
function hasScope(context, needed) {
|
|
950
|
+
const { scopes, permissions } = getAuth(context);
|
|
951
|
+
const requiredScopes = Array.isArray(needed) ? needed : [needed];
|
|
952
|
+
return requiredScopes.every(
|
|
953
|
+
(scope) => scopes.includes(scope) || permissions.includes(scope)
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
function hasAnyScope(context, needed) {
|
|
957
|
+
const { scopes, permissions } = getAuth(context);
|
|
958
|
+
return needed.some(
|
|
959
|
+
(scope) => scopes.includes(scope) || permissions.includes(scope)
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
function requireScope(needed) {
|
|
963
|
+
return async (c, next) => {
|
|
964
|
+
if (!hasScope(c, needed)) {
|
|
965
|
+
const { scopes, permissions } = getAuth(c);
|
|
966
|
+
const requiredScopes = Array.isArray(needed) ? needed : [needed];
|
|
967
|
+
return c.json(
|
|
968
|
+
{
|
|
969
|
+
error: "insufficient_scope",
|
|
970
|
+
required: requiredScopes,
|
|
971
|
+
granted_scopes: scopes,
|
|
972
|
+
granted_permissions: permissions,
|
|
973
|
+
message: `Missing required scope(s): ${requiredScopes.join(", ")}`
|
|
974
|
+
},
|
|
975
|
+
403
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
await next();
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
function requireAnyScope(needed) {
|
|
982
|
+
return async (c, next) => {
|
|
983
|
+
if (!hasAnyScope(c, needed)) {
|
|
984
|
+
const { scopes, permissions } = getAuth(c);
|
|
985
|
+
return c.json(
|
|
986
|
+
{
|
|
987
|
+
error: "insufficient_scope",
|
|
988
|
+
required_any: needed,
|
|
989
|
+
granted_scopes: scopes,
|
|
990
|
+
granted_permissions: permissions,
|
|
991
|
+
message: `Missing at least one required scope from: ${needed.join(", ")}`
|
|
992
|
+
},
|
|
993
|
+
403
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
await next();
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
var init_utils = __esm({
|
|
1000
|
+
"src/server/oauth/utils.ts"() {
|
|
1001
|
+
"use strict";
|
|
1002
|
+
__name(getAuth, "getAuth");
|
|
1003
|
+
__name(hasScope, "hasScope");
|
|
1004
|
+
__name(hasAnyScope, "hasAnyScope");
|
|
1005
|
+
__name(requireScope, "requireScope");
|
|
1006
|
+
__name(requireAnyScope, "requireAnyScope");
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
// src/server/oauth/index.ts
|
|
1011
|
+
var oauth_exports = {};
|
|
1012
|
+
__export(oauth_exports, {
|
|
1013
|
+
createBearerAuthMiddleware: () => createBearerAuthMiddleware,
|
|
1014
|
+
getAuth: () => getAuth,
|
|
1015
|
+
hasAnyScope: () => hasAnyScope,
|
|
1016
|
+
hasScope: () => hasScope,
|
|
1017
|
+
oauthAuth0Provider: () => oauthAuth0Provider,
|
|
1018
|
+
oauthCustomProvider: () => oauthCustomProvider,
|
|
1019
|
+
oauthKeycloakProvider: () => oauthKeycloakProvider,
|
|
1020
|
+
oauthSupabaseProvider: () => oauthSupabaseProvider,
|
|
1021
|
+
oauthWorkOSProvider: () => oauthWorkOSProvider,
|
|
1022
|
+
requireAnyScope: () => requireAnyScope,
|
|
1023
|
+
requireScope: () => requireScope,
|
|
1024
|
+
setupOAuthRoutes: () => setupOAuthRoutes
|
|
1025
|
+
});
|
|
1026
|
+
var init_oauth = __esm({
|
|
1027
|
+
"src/server/oauth/index.ts"() {
|
|
1028
|
+
"use strict";
|
|
1029
|
+
init_providers();
|
|
1030
|
+
init_middleware();
|
|
1031
|
+
init_routes();
|
|
1032
|
+
init_utils();
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
30
1035
|
|
|
31
1036
|
// src/server/index.ts
|
|
32
1037
|
var server_exports = {};
|
|
33
1038
|
__export(server_exports, {
|
|
34
1039
|
adaptConnectMiddleware: () => adaptConnectMiddleware,
|
|
35
1040
|
adaptMiddleware: () => adaptMiddleware,
|
|
1041
|
+
array: () => array,
|
|
36
1042
|
buildWidgetUrl: () => buildWidgetUrl,
|
|
37
1043
|
createExternalUrlResource: () => createExternalUrlResource,
|
|
38
1044
|
createMCPServer: () => createMCPServer,
|
|
39
1045
|
createRawHtmlResource: () => createRawHtmlResource,
|
|
40
1046
|
createRemoteDomResource: () => createRemoteDomResource,
|
|
41
1047
|
createUIResourceFromDefinition: () => createUIResourceFromDefinition,
|
|
42
|
-
|
|
1048
|
+
error: () => error,
|
|
1049
|
+
getAuth: () => getAuth,
|
|
1050
|
+
getRequestContext: () => getRequestContext,
|
|
1051
|
+
hasAnyScope: () => hasAnyScope,
|
|
1052
|
+
hasRequestContext: () => hasRequestContext,
|
|
1053
|
+
hasScope: () => hasScope,
|
|
1054
|
+
image: () => image,
|
|
1055
|
+
isExpressMiddleware: () => isExpressMiddleware,
|
|
1056
|
+
oauthAuth0Provider: () => oauthAuth0Provider,
|
|
1057
|
+
oauthCustomProvider: () => oauthCustomProvider,
|
|
1058
|
+
oauthKeycloakProvider: () => oauthKeycloakProvider,
|
|
1059
|
+
oauthSupabaseProvider: () => oauthSupabaseProvider,
|
|
1060
|
+
oauthWorkOSProvider: () => oauthWorkOSProvider,
|
|
1061
|
+
object: () => object,
|
|
1062
|
+
requireAnyScope: () => requireAnyScope,
|
|
1063
|
+
requireScope: () => requireScope,
|
|
1064
|
+
resource: () => resource,
|
|
1065
|
+
runWithContext: () => runWithContext,
|
|
1066
|
+
text: () => text,
|
|
1067
|
+
widget: () => widget
|
|
43
1068
|
});
|
|
44
1069
|
module.exports = __toCommonJS(server_exports);
|
|
45
1070
|
|
|
46
1071
|
// src/server/mcp-server.ts
|
|
47
1072
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
48
|
-
var import_hono = require("hono");
|
|
49
|
-
var import_cors = require("hono/cors");
|
|
50
1073
|
var import_zod = require("zod");
|
|
1074
|
+
var import_hono = require("hono");
|
|
1075
|
+
var import_cors2 = require("hono/cors");
|
|
1076
|
+
|
|
1077
|
+
// src/server/context-storage.ts
|
|
1078
|
+
var import_node_async_hooks = require("async_hooks");
|
|
1079
|
+
var requestContextStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
1080
|
+
async function runWithContext(context, fn) {
|
|
1081
|
+
return requestContextStorage.run(context, fn);
|
|
1082
|
+
}
|
|
1083
|
+
__name(runWithContext, "runWithContext");
|
|
1084
|
+
function getRequestContext() {
|
|
1085
|
+
return requestContextStorage.getStore();
|
|
1086
|
+
}
|
|
1087
|
+
__name(getRequestContext, "getRequestContext");
|
|
1088
|
+
function hasRequestContext() {
|
|
1089
|
+
return requestContextStorage.getStore() !== void 0;
|
|
1090
|
+
}
|
|
1091
|
+
__name(hasRequestContext, "hasRequestContext");
|
|
51
1092
|
|
|
52
1093
|
// src/server/adapters/mcp-ui-adapter.ts
|
|
53
1094
|
var import_server = require("@mcp-ui/server");
|
|
54
|
-
function buildWidgetUrl(
|
|
1095
|
+
function buildWidgetUrl(widget2, props, config) {
|
|
55
1096
|
const url = new URL(
|
|
56
|
-
`/mcp-use/widgets/${
|
|
1097
|
+
`/mcp-use/widgets/${widget2}`,
|
|
57
1098
|
`${config.baseUrl}:${config.port}`
|
|
58
1099
|
);
|
|
59
1100
|
if (props && Object.keys(props).length > 0) {
|
|
@@ -93,17 +1134,17 @@ function createRemoteDomResource(uri, script, framework = "react", encoding = "t
|
|
|
93
1134
|
}
|
|
94
1135
|
__name(createRemoteDomResource, "createRemoteDomResource");
|
|
95
1136
|
function createAppsSdkResource(uri, htmlTemplate, metadata) {
|
|
96
|
-
const
|
|
1137
|
+
const resource2 = {
|
|
97
1138
|
uri,
|
|
98
1139
|
mimeType: "text/html+skybridge",
|
|
99
1140
|
text: htmlTemplate
|
|
100
1141
|
};
|
|
101
1142
|
if (metadata && Object.keys(metadata).length > 0) {
|
|
102
|
-
|
|
1143
|
+
resource2._meta = metadata;
|
|
103
1144
|
}
|
|
104
1145
|
return {
|
|
105
1146
|
type: "resource",
|
|
106
|
-
resource
|
|
1147
|
+
resource: resource2
|
|
107
1148
|
};
|
|
108
1149
|
}
|
|
109
1150
|
__name(createAppsSdkResource, "createAppsSdkResource");
|
|
@@ -199,11 +1240,11 @@ async function adaptConnectMiddleware(connectMiddleware, middlewarePath) {
|
|
|
199
1240
|
const httpMocks = await import("node-mocks-http");
|
|
200
1241
|
createRequest = httpMocks.createRequest;
|
|
201
1242
|
createResponse = httpMocks.createResponse;
|
|
202
|
-
} catch (
|
|
1243
|
+
} catch (error2) {
|
|
203
1244
|
console.error(
|
|
204
1245
|
"[WIDGETS] node-mocks-http not available. Install connect and node-mocks-http for Vite middleware support."
|
|
205
1246
|
);
|
|
206
|
-
throw
|
|
1247
|
+
throw error2;
|
|
207
1248
|
}
|
|
208
1249
|
let normalizedPath = middlewarePath;
|
|
209
1250
|
if (normalizedPath.endsWith("*")) {
|
|
@@ -416,7 +1457,7 @@ async function requestLogger(c, next) {
|
|
|
416
1457
|
} else {
|
|
417
1458
|
console.log("\x1B[33mResponse Body:\x1B[0m (no body)");
|
|
418
1459
|
}
|
|
419
|
-
} catch (
|
|
1460
|
+
} catch (error2) {
|
|
420
1461
|
console.log("\x1B[33mResponse Body:\x1B[0m (unable to read)");
|
|
421
1462
|
}
|
|
422
1463
|
console.log("\x1B[36m" + "=".repeat(80) + "\x1B[0m\n");
|
|
@@ -425,90 +1466,8 @@ async function requestLogger(c, next) {
|
|
|
425
1466
|
__name(requestLogger, "requestLogger");
|
|
426
1467
|
|
|
427
1468
|
// src/server/mcp-server.ts
|
|
428
|
-
|
|
429
|
-
return globalThis.crypto.randomUUID();
|
|
430
|
-
}
|
|
431
|
-
__name(generateUUID, "generateUUID");
|
|
1469
|
+
init_runtime();
|
|
432
1470
|
var TMP_MCP_USE_DIR = ".mcp-use";
|
|
433
|
-
var isDeno2 = typeof globalThis.Deno !== "undefined";
|
|
434
|
-
function getEnv2(key) {
|
|
435
|
-
if (isDeno2) {
|
|
436
|
-
return globalThis.Deno.env.get(key);
|
|
437
|
-
}
|
|
438
|
-
return process.env[key];
|
|
439
|
-
}
|
|
440
|
-
__name(getEnv2, "getEnv");
|
|
441
|
-
function getCwd() {
|
|
442
|
-
if (isDeno2) {
|
|
443
|
-
return globalThis.Deno.cwd();
|
|
444
|
-
}
|
|
445
|
-
return process.cwd();
|
|
446
|
-
}
|
|
447
|
-
__name(getCwd, "getCwd");
|
|
448
|
-
var fsHelpers = {
|
|
449
|
-
async readFileSync(path, encoding = "utf8") {
|
|
450
|
-
if (isDeno2) {
|
|
451
|
-
return await globalThis.Deno.readTextFile(path);
|
|
452
|
-
}
|
|
453
|
-
const { readFileSync } = await import("fs");
|
|
454
|
-
const result = readFileSync(path, encoding);
|
|
455
|
-
return typeof result === "string" ? result : result.toString(encoding);
|
|
456
|
-
},
|
|
457
|
-
async readFile(path) {
|
|
458
|
-
if (isDeno2) {
|
|
459
|
-
const data = await globalThis.Deno.readFile(path);
|
|
460
|
-
return data.buffer;
|
|
461
|
-
}
|
|
462
|
-
const { readFileSync } = await import("fs");
|
|
463
|
-
const buffer = readFileSync(path);
|
|
464
|
-
return buffer.buffer.slice(
|
|
465
|
-
buffer.byteOffset,
|
|
466
|
-
buffer.byteOffset + buffer.byteLength
|
|
467
|
-
);
|
|
468
|
-
},
|
|
469
|
-
async existsSync(path) {
|
|
470
|
-
if (isDeno2) {
|
|
471
|
-
try {
|
|
472
|
-
await globalThis.Deno.stat(path);
|
|
473
|
-
return true;
|
|
474
|
-
} catch {
|
|
475
|
-
return false;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
const { existsSync } = await import("fs");
|
|
479
|
-
return existsSync(path);
|
|
480
|
-
},
|
|
481
|
-
async readdirSync(path) {
|
|
482
|
-
if (isDeno2) {
|
|
483
|
-
const entries = [];
|
|
484
|
-
for await (const entry of globalThis.Deno.readDir(path)) {
|
|
485
|
-
entries.push(entry.name);
|
|
486
|
-
}
|
|
487
|
-
return entries;
|
|
488
|
-
}
|
|
489
|
-
const { readdirSync } = await import("fs");
|
|
490
|
-
return readdirSync(path);
|
|
491
|
-
}
|
|
492
|
-
};
|
|
493
|
-
var pathHelpers = {
|
|
494
|
-
join(...paths) {
|
|
495
|
-
if (isDeno2) {
|
|
496
|
-
return paths.join("/").replace(/\/+/g, "/");
|
|
497
|
-
}
|
|
498
|
-
return paths.join("/").replace(/\/+/g, "/");
|
|
499
|
-
},
|
|
500
|
-
relative(from, to) {
|
|
501
|
-
const fromParts = from.split("/").filter((p) => p);
|
|
502
|
-
const toParts = to.split("/").filter((p) => p);
|
|
503
|
-
let i = 0;
|
|
504
|
-
while (i < fromParts.length && i < toParts.length && fromParts[i] === toParts[i]) {
|
|
505
|
-
i++;
|
|
506
|
-
}
|
|
507
|
-
const upCount = fromParts.length - i;
|
|
508
|
-
const relativeParts = [...Array(upCount).fill(".."), ...toParts.slice(i)];
|
|
509
|
-
return relativeParts.join("/");
|
|
510
|
-
}
|
|
511
|
-
};
|
|
512
1471
|
var McpServer = class {
|
|
513
1472
|
static {
|
|
514
1473
|
__name(this, "McpServer");
|
|
@@ -527,6 +1486,13 @@ var McpServer = class {
|
|
|
527
1486
|
buildId;
|
|
528
1487
|
sessions = /* @__PURE__ */ new Map();
|
|
529
1488
|
idleCleanupInterval;
|
|
1489
|
+
oauthProvider;
|
|
1490
|
+
// OAuthProvider from oauth/index.js
|
|
1491
|
+
oauthMiddleware;
|
|
1492
|
+
// Bearer auth middleware
|
|
1493
|
+
oauthConfig;
|
|
1494
|
+
// Store OAuth config for lazy initialization
|
|
1495
|
+
oauthSetupComplete = false;
|
|
530
1496
|
/**
|
|
531
1497
|
* Creates a new MCP server instance with Hono integration
|
|
532
1498
|
*
|
|
@@ -548,7 +1514,7 @@ var McpServer = class {
|
|
|
548
1514
|
this.app = new import_hono.Hono();
|
|
549
1515
|
this.app.use(
|
|
550
1516
|
"*",
|
|
551
|
-
(0,
|
|
1517
|
+
(0, import_cors2.cors)({
|
|
552
1518
|
origin: "*",
|
|
553
1519
|
allowMethods: ["GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
554
1520
|
allowHeaders: [
|
|
@@ -565,6 +1531,9 @@ var McpServer = class {
|
|
|
565
1531
|
})
|
|
566
1532
|
);
|
|
567
1533
|
this.app.use("*", requestLogger);
|
|
1534
|
+
if (config.oauth) {
|
|
1535
|
+
this.oauthConfig = config.oauth;
|
|
1536
|
+
}
|
|
568
1537
|
return new Proxy(this, {
|
|
569
1538
|
get(target, prop) {
|
|
570
1539
|
if (prop === "use") {
|
|
@@ -645,6 +1614,42 @@ var McpServer = class {
|
|
|
645
1614
|
console.log("[CSP] Parsed CSP URLs:", urls);
|
|
646
1615
|
return urls;
|
|
647
1616
|
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Setup OAuth authentication
|
|
1619
|
+
*
|
|
1620
|
+
* Initializes OAuth provider, creates bearer auth middleware,
|
|
1621
|
+
* sets up OAuth routes, and applies auth to /mcp endpoints.
|
|
1622
|
+
*
|
|
1623
|
+
* @private
|
|
1624
|
+
*/
|
|
1625
|
+
async setupOAuth(oauthProvider) {
|
|
1626
|
+
if (this.oauthSetupComplete) {
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
const { setupOAuthRoutes: setupOAuthRoutes2, createBearerAuthMiddleware: createBearerAuthMiddleware2 } = await Promise.resolve().then(() => (init_oauth(), oauth_exports));
|
|
1630
|
+
this.oauthProvider = oauthProvider;
|
|
1631
|
+
console.log(`[OAuth] OAuth provider initialized`);
|
|
1632
|
+
const baseUrl = this.getServerBaseUrl();
|
|
1633
|
+
this.oauthMiddleware = createBearerAuthMiddleware2(
|
|
1634
|
+
this.oauthProvider,
|
|
1635
|
+
baseUrl
|
|
1636
|
+
);
|
|
1637
|
+
setupOAuthRoutes2(this.app, this.oauthProvider, baseUrl);
|
|
1638
|
+
const mode = this.oauthProvider.getMode?.() || "proxy";
|
|
1639
|
+
if (mode === "direct") {
|
|
1640
|
+
console.log(
|
|
1641
|
+
"[OAuth] Direct mode: Clients will authenticate with provider directly"
|
|
1642
|
+
);
|
|
1643
|
+
console.log("[OAuth] Metadata endpoints: /.well-known/*");
|
|
1644
|
+
} else {
|
|
1645
|
+
console.log(
|
|
1646
|
+
"[OAuth] Proxy mode: Routes at /authorize, /token, /.well-known/*"
|
|
1647
|
+
);
|
|
1648
|
+
}
|
|
1649
|
+
this.app.use("/mcp/*", this.oauthMiddleware);
|
|
1650
|
+
console.log("[OAuth] Bearer authentication enabled on /mcp routes");
|
|
1651
|
+
this.oauthSetupComplete = true;
|
|
1652
|
+
}
|
|
648
1653
|
/**
|
|
649
1654
|
* Define a static resource that can be accessed by clients
|
|
650
1655
|
*
|
|
@@ -771,46 +1776,22 @@ var McpServer = class {
|
|
|
771
1776
|
this.registeredResources.push(resourceTemplateDefinition.name);
|
|
772
1777
|
return this;
|
|
773
1778
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
*
|
|
791
|
-
* @example
|
|
792
|
-
* ```typescript
|
|
793
|
-
* server.tool({
|
|
794
|
-
* name: 'calculate',
|
|
795
|
-
* description: 'Performs mathematical calculations',
|
|
796
|
-
* inputs: [
|
|
797
|
-
* { name: 'expression', type: 'string', required: true },
|
|
798
|
-
* { name: 'precision', type: 'number', required: false }
|
|
799
|
-
* ],
|
|
800
|
-
* cb: async ({ expression, precision = 2 }) => {
|
|
801
|
-
* const result = eval(expression)
|
|
802
|
-
* return { result: Number(result.toFixed(precision)) }
|
|
803
|
-
* },
|
|
804
|
-
* _meta: {
|
|
805
|
-
* 'openai/outputTemplate': 'ui://widgets/calculator',
|
|
806
|
-
* 'openai/toolInvocation/invoking': 'Calculating...',
|
|
807
|
-
* 'openai/toolInvocation/invoked': 'Calculation complete'
|
|
808
|
-
* }
|
|
809
|
-
* })
|
|
810
|
-
* ```
|
|
811
|
-
*/
|
|
812
|
-
tool(toolDefinition) {
|
|
813
|
-
const inputSchema = this.createParamsSchema(toolDefinition.inputs || []);
|
|
1779
|
+
// Implementation
|
|
1780
|
+
tool(toolDefinition, callback) {
|
|
1781
|
+
const actualCallback = callback || toolDefinition.cb;
|
|
1782
|
+
if (!actualCallback) {
|
|
1783
|
+
throw new Error(
|
|
1784
|
+
`Tool '${toolDefinition.name}' must have either a cb property or a callback parameter`
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1787
|
+
let inputSchema;
|
|
1788
|
+
if (toolDefinition.schema) {
|
|
1789
|
+
inputSchema = this.convertZodSchemaToParams(toolDefinition.schema);
|
|
1790
|
+
} else if (toolDefinition.inputs && toolDefinition.inputs.length > 0) {
|
|
1791
|
+
inputSchema = this.createParamsSchema(toolDefinition.inputs);
|
|
1792
|
+
} else {
|
|
1793
|
+
inputSchema = {};
|
|
1794
|
+
}
|
|
814
1795
|
this.server.registerTool(
|
|
815
1796
|
toolDefinition.name,
|
|
816
1797
|
{
|
|
@@ -821,101 +1802,95 @@ var McpServer = class {
|
|
|
821
1802
|
_meta: toolDefinition._meta
|
|
822
1803
|
},
|
|
823
1804
|
async (params, extra) => {
|
|
1805
|
+
let requestContext = getRequestContext();
|
|
1806
|
+
if (!requestContext) {
|
|
1807
|
+
for (const [, session] of this.sessions.entries()) {
|
|
1808
|
+
if (session.context) {
|
|
1809
|
+
requestContext = session.context;
|
|
1810
|
+
break;
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
824
1814
|
const progressToken = extra?._meta?.progressToken;
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
onProgress
|
|
845
|
-
} = options ?? {};
|
|
846
|
-
let progressCount = 0;
|
|
847
|
-
let completed = false;
|
|
848
|
-
let progressInterval = null;
|
|
849
|
-
if (progressToken && extra?.sendNotification) {
|
|
850
|
-
progressInterval = setInterval(async () => {
|
|
851
|
-
if (completed) return;
|
|
852
|
-
progressCount++;
|
|
853
|
-
const progressData = {
|
|
854
|
-
progress: progressCount,
|
|
855
|
-
total: void 0,
|
|
856
|
-
message: `Waiting for LLM response... (${progressCount * Math.round(progressIntervalMs / 1e3)}s elapsed)`
|
|
857
|
-
};
|
|
858
|
-
if (onProgress) {
|
|
859
|
-
try {
|
|
860
|
-
onProgress(progressData);
|
|
861
|
-
} catch {
|
|
862
|
-
}
|
|
863
|
-
}
|
|
1815
|
+
const enhancedContext = requestContext ? Object.create(requestContext) : {};
|
|
1816
|
+
enhancedContext.sample = async (sampleParams, options) => {
|
|
1817
|
+
const {
|
|
1818
|
+
timeout,
|
|
1819
|
+
progressIntervalMs = 5e3,
|
|
1820
|
+
onProgress
|
|
1821
|
+
} = options ?? {};
|
|
1822
|
+
let progressCount = 0;
|
|
1823
|
+
let completed = false;
|
|
1824
|
+
let progressInterval = null;
|
|
1825
|
+
if (progressToken && extra?.sendNotification) {
|
|
1826
|
+
progressInterval = setInterval(async () => {
|
|
1827
|
+
if (completed) return;
|
|
1828
|
+
progressCount++;
|
|
1829
|
+
const progressData = {
|
|
1830
|
+
progress: progressCount,
|
|
1831
|
+
total: void 0,
|
|
1832
|
+
message: `Waiting for LLM response... (${progressCount * Math.round(progressIntervalMs / 1e3)}s elapsed)`
|
|
1833
|
+
};
|
|
1834
|
+
if (onProgress) {
|
|
864
1835
|
try {
|
|
865
|
-
|
|
866
|
-
method: "notifications/progress",
|
|
867
|
-
params: {
|
|
868
|
-
progressToken,
|
|
869
|
-
progress: progressData.progress,
|
|
870
|
-
total: progressData.total,
|
|
871
|
-
message: progressData.message
|
|
872
|
-
}
|
|
873
|
-
});
|
|
1836
|
+
onProgress(progressData);
|
|
874
1837
|
} catch {
|
|
875
1838
|
}
|
|
876
|
-
}, progressIntervalMs);
|
|
877
|
-
}
|
|
878
|
-
try {
|
|
879
|
-
const samplePromise = this.createMessage(sampleParams);
|
|
880
|
-
if (timeout && timeout !== Infinity) {
|
|
881
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
882
|
-
setTimeout(
|
|
883
|
-
() => reject(
|
|
884
|
-
new Error(`Sampling timed out after ${timeout}ms`)
|
|
885
|
-
),
|
|
886
|
-
timeout
|
|
887
|
-
);
|
|
888
|
-
});
|
|
889
|
-
return await Promise.race([samplePromise, timeoutPromise]);
|
|
890
1839
|
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1840
|
+
try {
|
|
1841
|
+
await extra.sendNotification({
|
|
1842
|
+
method: "notifications/progress",
|
|
1843
|
+
params: {
|
|
1844
|
+
progressToken,
|
|
1845
|
+
progress: progressData.progress,
|
|
1846
|
+
total: progressData.total,
|
|
1847
|
+
message: progressData.message
|
|
1848
|
+
}
|
|
1849
|
+
});
|
|
1850
|
+
} catch {
|
|
896
1851
|
}
|
|
1852
|
+
}, progressIntervalMs);
|
|
1853
|
+
}
|
|
1854
|
+
try {
|
|
1855
|
+
const samplePromise = this.createMessage(sampleParams);
|
|
1856
|
+
if (timeout && timeout !== Infinity) {
|
|
1857
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1858
|
+
setTimeout(
|
|
1859
|
+
() => reject(new Error(`Sampling timed out after ${timeout}ms`)),
|
|
1860
|
+
timeout
|
|
1861
|
+
);
|
|
1862
|
+
});
|
|
1863
|
+
return await Promise.race([samplePromise, timeoutPromise]);
|
|
897
1864
|
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
method: "notifications/progress",
|
|
906
|
-
params: {
|
|
907
|
-
progressToken,
|
|
908
|
-
progress,
|
|
909
|
-
total,
|
|
910
|
-
message
|
|
911
|
-
}
|
|
912
|
-
});
|
|
913
|
-
} : void 0
|
|
1865
|
+
return await samplePromise;
|
|
1866
|
+
} finally {
|
|
1867
|
+
completed = true;
|
|
1868
|
+
if (progressInterval) {
|
|
1869
|
+
clearInterval(progressInterval);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
914
1872
|
};
|
|
915
|
-
|
|
916
|
-
|
|
1873
|
+
enhancedContext.reportProgress = progressToken && extra?.sendNotification ? async (progress, total, message) => {
|
|
1874
|
+
await extra.sendNotification({
|
|
1875
|
+
method: "notifications/progress",
|
|
1876
|
+
params: {
|
|
1877
|
+
progressToken,
|
|
1878
|
+
progress,
|
|
1879
|
+
total,
|
|
1880
|
+
message
|
|
1881
|
+
}
|
|
1882
|
+
});
|
|
1883
|
+
} : void 0;
|
|
1884
|
+
const executeCallback = /* @__PURE__ */ __name(async () => {
|
|
1885
|
+
if (actualCallback.length >= 2) {
|
|
1886
|
+
return await actualCallback(params, enhancedContext);
|
|
1887
|
+
}
|
|
1888
|
+
return await actualCallback(params);
|
|
1889
|
+
}, "executeCallback");
|
|
1890
|
+
if (requestContext) {
|
|
1891
|
+
return await runWithContext(requestContext, executeCallback);
|
|
917
1892
|
}
|
|
918
|
-
return await
|
|
1893
|
+
return await executeCallback();
|
|
919
1894
|
}
|
|
920
1895
|
);
|
|
921
1896
|
this.registeredTools.push(toolDefinition.name);
|
|
@@ -1266,35 +2241,6 @@ var McpServer = class {
|
|
|
1266
2241
|
}
|
|
1267
2242
|
return `ui://widget/${parts.join("-")}${extension}`;
|
|
1268
2243
|
}
|
|
1269
|
-
/**
|
|
1270
|
-
* Build a complete URL for a widget including query parameters
|
|
1271
|
-
*
|
|
1272
|
-
* Constructs the full URL to access a widget's iframe, encoding any provided
|
|
1273
|
-
* parameters as query string parameters. Complex objects are JSON-stringified
|
|
1274
|
-
* for transmission.
|
|
1275
|
-
*
|
|
1276
|
-
* @private
|
|
1277
|
-
* @param widget - Widget name/identifier
|
|
1278
|
-
* @param params - Parameters to encode in the URL
|
|
1279
|
-
* @returns Complete URL with encoded parameters
|
|
1280
|
-
*/
|
|
1281
|
-
buildWidgetUrl(widget, params) {
|
|
1282
|
-
const baseUrl = `http://${this.serverHost}:${this.serverPort}/mcp-use/widgets/${widget}`;
|
|
1283
|
-
if (Object.keys(params).length === 0) {
|
|
1284
|
-
return baseUrl;
|
|
1285
|
-
}
|
|
1286
|
-
const queryParams = new URLSearchParams();
|
|
1287
|
-
for (const [key, value] of Object.entries(params)) {
|
|
1288
|
-
if (value !== void 0 && value !== null) {
|
|
1289
|
-
if (typeof value === "object") {
|
|
1290
|
-
queryParams.append(key, JSON.stringify(value));
|
|
1291
|
-
} else {
|
|
1292
|
-
queryParams.append(key, String(value));
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
return `${baseUrl}?${queryParams.toString()}`;
|
|
1297
|
-
}
|
|
1298
2244
|
/**
|
|
1299
2245
|
* Convert widget props definition to tool input schema
|
|
1300
2246
|
*
|
|
@@ -1403,7 +2349,7 @@ var McpServer = class {
|
|
|
1403
2349
|
const srcDir = pathHelpers.join(getCwd(), resourcesDir);
|
|
1404
2350
|
try {
|
|
1405
2351
|
await fs.access(srcDir);
|
|
1406
|
-
} catch (
|
|
2352
|
+
} catch (error2) {
|
|
1407
2353
|
console.log(
|
|
1408
2354
|
`[WIDGETS] No ${resourcesDir}/ directory found - skipping widget serving`
|
|
1409
2355
|
);
|
|
@@ -1437,7 +2383,7 @@ var McpServer = class {
|
|
|
1437
2383
|
}
|
|
1438
2384
|
}
|
|
1439
2385
|
}
|
|
1440
|
-
} catch (
|
|
2386
|
+
} catch (error2) {
|
|
1441
2387
|
console.log(`[WIDGETS] No widgets found in ${resourcesDir}/ directory`);
|
|
1442
2388
|
return;
|
|
1443
2389
|
}
|
|
@@ -1462,7 +2408,7 @@ var McpServer = class {
|
|
|
1462
2408
|
'return import("@tailwindcss/vite")'
|
|
1463
2409
|
)();
|
|
1464
2410
|
tailwindcss = tailwindModule.default;
|
|
1465
|
-
} catch (
|
|
2411
|
+
} catch (error2) {
|
|
1466
2412
|
console.error(
|
|
1467
2413
|
"[WIDGETS] Dev dependencies not available. Install vite, @vitejs/plugin-react, and @tailwindcss/vite for widget development."
|
|
1468
2414
|
);
|
|
@@ -1478,8 +2424,8 @@ var McpServer = class {
|
|
|
1478
2424
|
entry: entry.path
|
|
1479
2425
|
};
|
|
1480
2426
|
});
|
|
1481
|
-
for (const
|
|
1482
|
-
const widgetTempDir = pathHelpers.join(tempDir,
|
|
2427
|
+
for (const widget2 of widgets) {
|
|
2428
|
+
const widgetTempDir = pathHelpers.join(tempDir, widget2.name);
|
|
1483
2429
|
await fs.mkdir(widgetTempDir, { recursive: true });
|
|
1484
2430
|
const resourcesPath = pathHelpers.join(getCwd(), resourcesDir);
|
|
1485
2431
|
const relativeResourcesPath = pathHelpers.relative(widgetTempDir, resourcesPath).replace(/\\/g, "/");
|
|
@@ -1496,7 +2442,7 @@ var McpServer = class {
|
|
|
1496
2442
|
const entryContent = `import React from 'react'
|
|
1497
2443
|
import { createRoot } from 'react-dom/client'
|
|
1498
2444
|
import './styles.css'
|
|
1499
|
-
import Component from '${
|
|
2445
|
+
import Component from '${widget2.entry}'
|
|
1500
2446
|
|
|
1501
2447
|
const container = document.getElementById('widget-root')
|
|
1502
2448
|
if (container && Component) {
|
|
@@ -1509,11 +2455,11 @@ if (container && Component) {
|
|
|
1509
2455
|
<head>
|
|
1510
2456
|
<meta charset="UTF-8" />
|
|
1511
2457
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
1512
|
-
<title>${
|
|
2458
|
+
<title>${widget2.name} Widget</title>
|
|
1513
2459
|
</head>
|
|
1514
2460
|
<body>
|
|
1515
2461
|
<div id="widget-root"></div>
|
|
1516
|
-
<script type="module" src="${baseRoute}/${
|
|
2462
|
+
<script type="module" src="${baseRoute}/${widget2.name}/entry.tsx"></script>
|
|
1517
2463
|
</body>
|
|
1518
2464
|
</html>`;
|
|
1519
2465
|
await fs.writeFile(
|
|
@@ -1603,8 +2549,8 @@ if (container && Component) {
|
|
|
1603
2549
|
const widgetMatch = pathname.replace(baseRoute, "").match(/^\/([^/]+)/);
|
|
1604
2550
|
if (widgetMatch) {
|
|
1605
2551
|
const widgetName = widgetMatch[1];
|
|
1606
|
-
const
|
|
1607
|
-
if (
|
|
2552
|
+
const widget2 = widgets.find((w) => w.name === widgetName);
|
|
2553
|
+
if (widget2) {
|
|
1608
2554
|
const relativePath = pathname.replace(baseRoute, "");
|
|
1609
2555
|
if (relativePath === `/${widgetName}` || relativePath === `/${widgetName}/`) {
|
|
1610
2556
|
const newUrl = new URL(c.req.url);
|
|
@@ -1655,42 +2601,42 @@ if (container && Component) {
|
|
|
1655
2601
|
const message = isAsset ? "Widget asset not found" : "Widget not found";
|
|
1656
2602
|
return c.text(message, 404);
|
|
1657
2603
|
});
|
|
1658
|
-
widgets.forEach((
|
|
2604
|
+
widgets.forEach((widget2) => {
|
|
1659
2605
|
console.log(
|
|
1660
|
-
`[WIDGET] ${
|
|
2606
|
+
`[WIDGET] ${widget2.name} mounted at ${baseRoute}/${widget2.name}`
|
|
1661
2607
|
);
|
|
1662
2608
|
});
|
|
1663
|
-
for (const
|
|
2609
|
+
for (const widget2 of widgets) {
|
|
1664
2610
|
const type = "appsSdk";
|
|
1665
2611
|
let metadata = {};
|
|
1666
2612
|
let props = {};
|
|
1667
|
-
let description =
|
|
2613
|
+
let description = widget2.description;
|
|
1668
2614
|
try {
|
|
1669
|
-
const mod = await viteServer.ssrLoadModule(
|
|
2615
|
+
const mod = await viteServer.ssrLoadModule(widget2.entry);
|
|
1670
2616
|
if (mod.widgetMetadata) {
|
|
1671
2617
|
metadata = mod.widgetMetadata;
|
|
1672
|
-
description = metadata.description ||
|
|
2618
|
+
description = metadata.description || widget2.description;
|
|
1673
2619
|
if (metadata.inputs) {
|
|
1674
2620
|
try {
|
|
1675
2621
|
props = metadata.inputs.shape || {};
|
|
1676
|
-
} catch (
|
|
2622
|
+
} catch (error2) {
|
|
1677
2623
|
console.warn(
|
|
1678
|
-
`[WIDGET] Failed to extract props schema for ${
|
|
1679
|
-
|
|
2624
|
+
`[WIDGET] Failed to extract props schema for ${widget2.name}:`,
|
|
2625
|
+
error2
|
|
1680
2626
|
);
|
|
1681
2627
|
}
|
|
1682
2628
|
}
|
|
1683
2629
|
}
|
|
1684
|
-
} catch (
|
|
2630
|
+
} catch (error2) {
|
|
1685
2631
|
console.warn(
|
|
1686
|
-
`[WIDGET] Failed to load metadata for ${
|
|
1687
|
-
|
|
2632
|
+
`[WIDGET] Failed to load metadata for ${widget2.name}:`,
|
|
2633
|
+
error2
|
|
1688
2634
|
);
|
|
1689
2635
|
}
|
|
1690
2636
|
let html = "";
|
|
1691
2637
|
try {
|
|
1692
2638
|
html = await fsHelpers.readFileSync(
|
|
1693
|
-
pathHelpers.join(tempDir,
|
|
2639
|
+
pathHelpers.join(tempDir, widget2.name, "index.html"),
|
|
1694
2640
|
"utf8"
|
|
1695
2641
|
);
|
|
1696
2642
|
const mcpUrl = this.getServerBaseUrl();
|
|
@@ -1728,25 +2674,25 @@ if (container && Component) {
|
|
|
1728
2674
|
html = html.replace(
|
|
1729
2675
|
/<head[^>]*>/i,
|
|
1730
2676
|
`<head>
|
|
1731
|
-
<script>window.__getFile = (filename) => { return "${baseUrl}/mcp-use/widgets/${
|
|
2677
|
+
<script>window.__getFile = (filename) => { return "${baseUrl}/mcp-use/widgets/${widget2.name}/"+filename }; window.__mcpPublicUrl = "${baseUrl}/mcp-use/public";</script>`
|
|
1732
2678
|
);
|
|
1733
|
-
} catch (
|
|
2679
|
+
} catch (error2) {
|
|
1734
2680
|
console.error(
|
|
1735
|
-
`Failed to read html template for widget ${
|
|
1736
|
-
|
|
2681
|
+
`Failed to read html template for widget ${widget2.name}`,
|
|
2682
|
+
error2
|
|
1737
2683
|
);
|
|
1738
2684
|
}
|
|
1739
2685
|
const mcp_connect_domain = this.getServerBaseUrl() ? new URL(this.getServerBaseUrl() || "").origin : null;
|
|
1740
2686
|
this.uiResource({
|
|
1741
|
-
name:
|
|
1742
|
-
title: metadata.title ||
|
|
2687
|
+
name: widget2.name,
|
|
2688
|
+
title: metadata.title || widget2.name,
|
|
1743
2689
|
description,
|
|
1744
2690
|
type,
|
|
1745
2691
|
props,
|
|
1746
2692
|
_meta: {
|
|
1747
2693
|
"mcp-use/widget": {
|
|
1748
|
-
name:
|
|
1749
|
-
title: metadata.title ||
|
|
2694
|
+
name: widget2.name,
|
|
2695
|
+
title: metadata.title || widget2.name,
|
|
1750
2696
|
description,
|
|
1751
2697
|
type,
|
|
1752
2698
|
props,
|
|
@@ -1758,8 +2704,8 @@ if (container && Component) {
|
|
|
1758
2704
|
htmlTemplate: html,
|
|
1759
2705
|
appsSdkMetadata: {
|
|
1760
2706
|
"openai/widgetDescription": description,
|
|
1761
|
-
"openai/toolInvocation/invoking": `Loading ${
|
|
1762
|
-
"openai/toolInvocation/invoked": `${
|
|
2707
|
+
"openai/toolInvocation/invoking": `Loading ${widget2.name}...`,
|
|
2708
|
+
"openai/toolInvocation/invoked": `${widget2.name} ready`,
|
|
1763
2709
|
"openai/widgetAccessible": true,
|
|
1764
2710
|
"openai/resultCanProduceWidget": true,
|
|
1765
2711
|
...metadata.appsSdkMetadata || {},
|
|
@@ -1832,10 +2778,10 @@ if (container && Component) {
|
|
|
1832
2778
|
} else {
|
|
1833
2779
|
console.log("[WIDGETS] No widgets found in manifest");
|
|
1834
2780
|
}
|
|
1835
|
-
} catch (
|
|
2781
|
+
} catch (error2) {
|
|
1836
2782
|
console.log(
|
|
1837
2783
|
"[WIDGETS] Could not read manifest file, falling back to directory listing:",
|
|
1838
|
-
|
|
2784
|
+
error2
|
|
1839
2785
|
);
|
|
1840
2786
|
try {
|
|
1841
2787
|
const allEntries = await fsHelpers.readdirSync(widgetsDir);
|
|
@@ -1900,10 +2846,10 @@ if (container && Component) {
|
|
|
1900
2846
|
<script>window.__getFile = (filename) => { return "${baseUrl}/mcp-use/widgets/${widgetName}/"+filename }; window.__mcpPublicUrl = "${baseUrl}/mcp-use/public";</script>`
|
|
1901
2847
|
);
|
|
1902
2848
|
}
|
|
1903
|
-
} catch (
|
|
2849
|
+
} catch (error2) {
|
|
1904
2850
|
console.error(
|
|
1905
2851
|
`[WIDGET] Failed to read ${widgetName}/index.html:`,
|
|
1906
|
-
|
|
2852
|
+
error2
|
|
1907
2853
|
);
|
|
1908
2854
|
continue;
|
|
1909
2855
|
}
|
|
@@ -2030,7 +2976,7 @@ if (container && Component) {
|
|
|
2030
2976
|
if (closeOldSessionId && this.sessions.has(closeOldSessionId)) {
|
|
2031
2977
|
try {
|
|
2032
2978
|
this.sessions.get(closeOldSessionId).transport.close();
|
|
2033
|
-
} catch (
|
|
2979
|
+
} catch (error2) {
|
|
2034
2980
|
}
|
|
2035
2981
|
this.sessions.delete(closeOldSessionId);
|
|
2036
2982
|
}
|
|
@@ -2161,7 +3107,7 @@ if (container && Component) {
|
|
|
2161
3107
|
if (now - session.lastAccessedAt > idleTimeoutMs) {
|
|
2162
3108
|
try {
|
|
2163
3109
|
session.transport.close();
|
|
2164
|
-
} catch (
|
|
3110
|
+
} catch (error2) {
|
|
2165
3111
|
}
|
|
2166
3112
|
this.sessions.delete(sessionId);
|
|
2167
3113
|
}
|
|
@@ -2317,19 +3263,23 @@ if (container && Component) {
|
|
|
2317
3263
|
}
|
|
2318
3264
|
}
|
|
2319
3265
|
if (sessionId && this.sessions.has(sessionId)) {
|
|
2320
|
-
this.sessions.get(sessionId)
|
|
3266
|
+
const session = this.sessions.get(sessionId);
|
|
3267
|
+
session.lastAccessedAt = Date.now();
|
|
3268
|
+
session.context = c;
|
|
2321
3269
|
}
|
|
2322
3270
|
if (expressRes._closeHandler) {
|
|
2323
3271
|
c.req.raw.signal?.addEventListener("abort", () => {
|
|
2324
3272
|
transport.close();
|
|
2325
3273
|
});
|
|
2326
3274
|
}
|
|
2327
|
-
await
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
3275
|
+
await runWithContext(c, async () => {
|
|
3276
|
+
await this.waitForRequestComplete(
|
|
3277
|
+
transport,
|
|
3278
|
+
expressReq,
|
|
3279
|
+
expressRes,
|
|
3280
|
+
expressReq.body
|
|
3281
|
+
);
|
|
3282
|
+
});
|
|
2333
3283
|
const response = getResponse();
|
|
2334
3284
|
if (response) {
|
|
2335
3285
|
return response;
|
|
@@ -2573,6 +3523,9 @@ if (container && Component) {
|
|
|
2573
3523
|
if (hostEnv) {
|
|
2574
3524
|
this.serverHost = hostEnv;
|
|
2575
3525
|
}
|
|
3526
|
+
if (this.oauthConfig && !this.oauthSetupComplete) {
|
|
3527
|
+
await this.setupOAuth(this.oauthConfig);
|
|
3528
|
+
}
|
|
2576
3529
|
await this.mountWidgets({
|
|
2577
3530
|
baseRoute: "/mcp-use/widgets",
|
|
2578
3531
|
resourcesDir: "resources"
|
|
@@ -2681,6 +3634,9 @@ if (container && Component) {
|
|
|
2681
3634
|
* ```
|
|
2682
3635
|
*/
|
|
2683
3636
|
async getHandler(options) {
|
|
3637
|
+
if (this.oauthConfig && !this.oauthSetupComplete) {
|
|
3638
|
+
await this.setupOAuth(this.oauthConfig);
|
|
3639
|
+
}
|
|
2684
3640
|
console.log("[MCP] Mounting widgets");
|
|
2685
3641
|
await this.mountWidgets({
|
|
2686
3642
|
baseRoute: "/mcp-use/widgets",
|
|
@@ -2784,10 +3740,10 @@ if (container && Component) {
|
|
|
2784
3740
|
for (const [sessionId, session] of this.sessions.entries()) {
|
|
2785
3741
|
try {
|
|
2786
3742
|
await session.transport.send(notification);
|
|
2787
|
-
} catch (
|
|
3743
|
+
} catch (error2) {
|
|
2788
3744
|
console.warn(
|
|
2789
3745
|
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
2790
|
-
|
|
3746
|
+
error2
|
|
2791
3747
|
);
|
|
2792
3748
|
}
|
|
2793
3749
|
}
|
|
@@ -2832,10 +3788,10 @@ if (container && Component) {
|
|
|
2832
3788
|
try {
|
|
2833
3789
|
await session.transport.send(notification);
|
|
2834
3790
|
return true;
|
|
2835
|
-
} catch (
|
|
3791
|
+
} catch (error2) {
|
|
2836
3792
|
console.warn(
|
|
2837
3793
|
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
2838
|
-
|
|
3794
|
+
error2
|
|
2839
3795
|
);
|
|
2840
3796
|
return false;
|
|
2841
3797
|
}
|
|
@@ -2903,10 +3859,10 @@ if (container && Component) {
|
|
|
2903
3859
|
return response.roots;
|
|
2904
3860
|
}
|
|
2905
3861
|
return [];
|
|
2906
|
-
} catch (
|
|
3862
|
+
} catch (error2) {
|
|
2907
3863
|
console.warn(
|
|
2908
3864
|
`[MCP] Failed to list roots from session ${sessionId}:`,
|
|
2909
|
-
|
|
3865
|
+
error2
|
|
2910
3866
|
);
|
|
2911
3867
|
return null;
|
|
2912
3868
|
}
|
|
@@ -2984,14 +3940,14 @@ if (container && Component) {
|
|
|
2984
3940
|
*/
|
|
2985
3941
|
setupWidgetRoutes() {
|
|
2986
3942
|
this.app.get("/mcp-use/widgets/:widget/assets/*", async (c) => {
|
|
2987
|
-
const
|
|
3943
|
+
const widget2 = c.req.param("widget");
|
|
2988
3944
|
const assetFile = c.req.path.split("/assets/")[1];
|
|
2989
3945
|
const assetPath = pathHelpers.join(
|
|
2990
3946
|
getCwd(),
|
|
2991
3947
|
"dist",
|
|
2992
3948
|
"resources",
|
|
2993
3949
|
"widgets",
|
|
2994
|
-
|
|
3950
|
+
widget2,
|
|
2995
3951
|
"assets",
|
|
2996
3952
|
assetFile
|
|
2997
3953
|
);
|
|
@@ -3020,10 +3976,10 @@ if (container && Component) {
|
|
|
3020
3976
|
);
|
|
3021
3977
|
try {
|
|
3022
3978
|
const widgets = await fsHelpers.readdirSync(widgetsDir);
|
|
3023
|
-
for (const
|
|
3979
|
+
for (const widget2 of widgets) {
|
|
3024
3980
|
const assetPath = pathHelpers.join(
|
|
3025
3981
|
widgetsDir,
|
|
3026
|
-
|
|
3982
|
+
widget2,
|
|
3027
3983
|
"assets",
|
|
3028
3984
|
assetFile
|
|
3029
3985
|
);
|
|
@@ -3043,13 +3999,13 @@ if (container && Component) {
|
|
|
3043
3999
|
}
|
|
3044
4000
|
});
|
|
3045
4001
|
this.app.get("/mcp-use/widgets/:widget", async (c) => {
|
|
3046
|
-
const
|
|
4002
|
+
const widget2 = c.req.param("widget");
|
|
3047
4003
|
const filePath = pathHelpers.join(
|
|
3048
4004
|
getCwd(),
|
|
3049
4005
|
"dist",
|
|
3050
4006
|
"resources",
|
|
3051
4007
|
"widgets",
|
|
3052
|
-
|
|
4008
|
+
widget2,
|
|
3053
4009
|
"index.html"
|
|
3054
4010
|
);
|
|
3055
4011
|
try {
|
|
@@ -3066,7 +4022,7 @@ if (container && Component) {
|
|
|
3066
4022
|
html = html.replace(
|
|
3067
4023
|
/<head[^>]*>/i,
|
|
3068
4024
|
`<head>
|
|
3069
|
-
<script>window.__getFile = (filename) => { return "${baseUrl}/mcp-use/widgets/${
|
|
4025
|
+
<script>window.__getFile = (filename) => { return "${baseUrl}/mcp-use/widgets/${widget2}/"+filename }</script>`
|
|
3070
4026
|
);
|
|
3071
4027
|
return c.html(html);
|
|
3072
4028
|
} catch {
|
|
@@ -3093,28 +4049,21 @@ if (container && Component) {
|
|
|
3093
4049
|
});
|
|
3094
4050
|
}
|
|
3095
4051
|
/**
|
|
3096
|
-
*
|
|
3097
|
-
*
|
|
3098
|
-
* Parses a URI template string to extract parameter names and generates a Zod
|
|
3099
|
-
* validation schema for those parameters. Used internally for validating resource
|
|
3100
|
-
* template parameters before processing requests.
|
|
4052
|
+
* Convert a Zod object schema to the internal Record<string, z.ZodSchema> format
|
|
3101
4053
|
*
|
|
3102
|
-
* @param
|
|
3103
|
-
* @returns Object mapping parameter names to Zod
|
|
3104
|
-
*
|
|
3105
|
-
* @example
|
|
3106
|
-
* ```typescript
|
|
3107
|
-
* const schema = this.createInputSchema("/users/{id}/posts/{postId}")
|
|
3108
|
-
* // Returns: { id: z.string(), postId: z.string() }
|
|
3109
|
-
* ```
|
|
4054
|
+
* @param zodSchema - Zod object schema to convert
|
|
4055
|
+
* @returns Object mapping parameter names to Zod validation schemas
|
|
3110
4056
|
*/
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
}
|
|
3117
|
-
|
|
4057
|
+
convertZodSchemaToParams(zodSchema) {
|
|
4058
|
+
if (!(zodSchema instanceof import_zod.z.ZodObject)) {
|
|
4059
|
+
throw new Error("schema must be a Zod object schema (z.object({...}))");
|
|
4060
|
+
}
|
|
4061
|
+
const shape = zodSchema.shape;
|
|
4062
|
+
const params = {};
|
|
4063
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
4064
|
+
params[key] = value;
|
|
4065
|
+
}
|
|
4066
|
+
return params;
|
|
3118
4067
|
}
|
|
3119
4068
|
/**
|
|
3120
4069
|
* Create input schema for tools
|
|
@@ -3168,75 +4117,6 @@ if (container && Component) {
|
|
|
3168
4117
|
});
|
|
3169
4118
|
return schema;
|
|
3170
4119
|
}
|
|
3171
|
-
/**
|
|
3172
|
-
* Create arguments schema for prompts
|
|
3173
|
-
*
|
|
3174
|
-
* Converts prompt argument definitions into Zod validation schemas for runtime validation.
|
|
3175
|
-
* Supports common data types (string, number, boolean, object, array) and optional
|
|
3176
|
-
* parameters. Used internally when registering prompt templates with the MCP server.
|
|
3177
|
-
*
|
|
3178
|
-
* @param inputs - Array of argument definitions with name, type, and optional flag
|
|
3179
|
-
* @returns Object mapping argument names to Zod validation schemas
|
|
3180
|
-
*
|
|
3181
|
-
* @example
|
|
3182
|
-
* ```typescript
|
|
3183
|
-
* const schema = this.createPromptArgsSchema([
|
|
3184
|
-
* { name: 'topic', type: 'string', required: true },
|
|
3185
|
-
* { name: 'style', type: 'string', required: false }
|
|
3186
|
-
* ])
|
|
3187
|
-
* // Returns: { topic: z.string(), style: z.string().optional() }
|
|
3188
|
-
* ```
|
|
3189
|
-
*/
|
|
3190
|
-
createPromptArgsSchema(inputs) {
|
|
3191
|
-
const schema = {};
|
|
3192
|
-
inputs.forEach((input) => {
|
|
3193
|
-
let zodType;
|
|
3194
|
-
switch (input.type) {
|
|
3195
|
-
case "string":
|
|
3196
|
-
zodType = import_zod.z.string();
|
|
3197
|
-
break;
|
|
3198
|
-
case "number":
|
|
3199
|
-
zodType = import_zod.z.number();
|
|
3200
|
-
break;
|
|
3201
|
-
case "boolean":
|
|
3202
|
-
zodType = import_zod.z.boolean();
|
|
3203
|
-
break;
|
|
3204
|
-
case "object":
|
|
3205
|
-
zodType = import_zod.z.object({});
|
|
3206
|
-
break;
|
|
3207
|
-
case "array":
|
|
3208
|
-
zodType = import_zod.z.array(import_zod.z.any());
|
|
3209
|
-
break;
|
|
3210
|
-
default:
|
|
3211
|
-
zodType = import_zod.z.any();
|
|
3212
|
-
}
|
|
3213
|
-
if (!input.required) {
|
|
3214
|
-
zodType = zodType.optional();
|
|
3215
|
-
}
|
|
3216
|
-
schema[input.name] = zodType;
|
|
3217
|
-
});
|
|
3218
|
-
return schema;
|
|
3219
|
-
}
|
|
3220
|
-
/**
|
|
3221
|
-
* Extract parameter names from URI template
|
|
3222
|
-
*
|
|
3223
|
-
* Parses a URI template string to extract parameter names enclosed in curly braces.
|
|
3224
|
-
* Used internally to identify dynamic parameters in resource templates and generate
|
|
3225
|
-
* appropriate validation schemas.
|
|
3226
|
-
*
|
|
3227
|
-
* @param uriTemplate - URI template string with parameter placeholders (e.g., "/users/{id}/posts/{postId}")
|
|
3228
|
-
* @returns Array of parameter names found in the template
|
|
3229
|
-
*
|
|
3230
|
-
* @example
|
|
3231
|
-
* ```typescript
|
|
3232
|
-
* const params = this.extractTemplateParams("/users/{id}/posts/{postId}")
|
|
3233
|
-
* // Returns: ["id", "postId"]
|
|
3234
|
-
* ```
|
|
3235
|
-
*/
|
|
3236
|
-
extractTemplateParams(uriTemplate) {
|
|
3237
|
-
const matches = uriTemplate.match(/\{([^}]+)\}/g);
|
|
3238
|
-
return matches ? matches.map((match) => match.slice(1, -1)) : [];
|
|
3239
|
-
}
|
|
3240
4120
|
/**
|
|
3241
4121
|
* Parse parameter values from a URI based on a template
|
|
3242
4122
|
*
|
|
@@ -3279,8 +4159,127 @@ function createMCPServer(name, config = {}) {
|
|
|
3279
4159
|
host: config.host,
|
|
3280
4160
|
baseUrl: config.baseUrl,
|
|
3281
4161
|
allowedOrigins: config.allowedOrigins,
|
|
3282
|
-
sessionIdleTimeoutMs: config.sessionIdleTimeoutMs
|
|
4162
|
+
sessionIdleTimeoutMs: config.sessionIdleTimeoutMs,
|
|
4163
|
+
autoCreateSessionOnInvalidId: config.autoCreateSessionOnInvalidId,
|
|
4164
|
+
oauth: config.oauth
|
|
3283
4165
|
});
|
|
3284
4166
|
return instance;
|
|
3285
4167
|
}
|
|
3286
4168
|
__name(createMCPServer, "createMCPServer");
|
|
4169
|
+
|
|
4170
|
+
// src/server/utils/response-helpers.ts
|
|
4171
|
+
function text(content) {
|
|
4172
|
+
return {
|
|
4173
|
+
content: [
|
|
4174
|
+
{
|
|
4175
|
+
type: "text",
|
|
4176
|
+
text: content
|
|
4177
|
+
}
|
|
4178
|
+
]
|
|
4179
|
+
};
|
|
4180
|
+
}
|
|
4181
|
+
__name(text, "text");
|
|
4182
|
+
function image(data, mimeType = "image/png") {
|
|
4183
|
+
return {
|
|
4184
|
+
content: [
|
|
4185
|
+
{
|
|
4186
|
+
type: "image",
|
|
4187
|
+
data,
|
|
4188
|
+
mimeType
|
|
4189
|
+
}
|
|
4190
|
+
]
|
|
4191
|
+
};
|
|
4192
|
+
}
|
|
4193
|
+
__name(image, "image");
|
|
4194
|
+
function resource(uri, mimeType, text2) {
|
|
4195
|
+
const resourceContent = {
|
|
4196
|
+
type: "resource",
|
|
4197
|
+
resource: {
|
|
4198
|
+
uri,
|
|
4199
|
+
...mimeType && { mimeType },
|
|
4200
|
+
...text2 && { text: text2 }
|
|
4201
|
+
}
|
|
4202
|
+
};
|
|
4203
|
+
return {
|
|
4204
|
+
content: [resourceContent]
|
|
4205
|
+
};
|
|
4206
|
+
}
|
|
4207
|
+
__name(resource, "resource");
|
|
4208
|
+
function error(message) {
|
|
4209
|
+
return {
|
|
4210
|
+
isError: true,
|
|
4211
|
+
content: [
|
|
4212
|
+
{
|
|
4213
|
+
type: "text",
|
|
4214
|
+
text: message
|
|
4215
|
+
}
|
|
4216
|
+
]
|
|
4217
|
+
};
|
|
4218
|
+
}
|
|
4219
|
+
__name(error, "error");
|
|
4220
|
+
function object(data) {
|
|
4221
|
+
return Array.isArray(data) ? array(data) : {
|
|
4222
|
+
content: [
|
|
4223
|
+
{
|
|
4224
|
+
type: "text",
|
|
4225
|
+
text: JSON.stringify(data, null, 2)
|
|
4226
|
+
}
|
|
4227
|
+
],
|
|
4228
|
+
structuredContent: data
|
|
4229
|
+
};
|
|
4230
|
+
}
|
|
4231
|
+
__name(object, "object");
|
|
4232
|
+
function array(data) {
|
|
4233
|
+
return {
|
|
4234
|
+
content: [
|
|
4235
|
+
{
|
|
4236
|
+
type: "text",
|
|
4237
|
+
text: JSON.stringify(data, null, 2)
|
|
4238
|
+
}
|
|
4239
|
+
],
|
|
4240
|
+
structuredContent: { data }
|
|
4241
|
+
};
|
|
4242
|
+
}
|
|
4243
|
+
__name(array, "array");
|
|
4244
|
+
function widget(config) {
|
|
4245
|
+
const {
|
|
4246
|
+
name,
|
|
4247
|
+
data,
|
|
4248
|
+
message,
|
|
4249
|
+
invoking,
|
|
4250
|
+
invoked,
|
|
4251
|
+
widgetAccessible = true,
|
|
4252
|
+
resultCanProduceWidget = true,
|
|
4253
|
+
buildId
|
|
4254
|
+
} = config;
|
|
4255
|
+
const randomId = Math.random().toString(36).substring(2, 15);
|
|
4256
|
+
const buildIdPart = buildId ? `-${buildId}` : "";
|
|
4257
|
+
const uniqueUri = `ui://widget/${name}${buildIdPart}-${randomId}.html`;
|
|
4258
|
+
const metadata = {
|
|
4259
|
+
"openai/outputTemplate": uniqueUri,
|
|
4260
|
+
"openai/widgetAccessible": widgetAccessible,
|
|
4261
|
+
"openai/resultCanProduceWidget": resultCanProduceWidget
|
|
4262
|
+
};
|
|
4263
|
+
if (invoking) {
|
|
4264
|
+
metadata["openai/toolInvocation/invoking"] = invoking;
|
|
4265
|
+
}
|
|
4266
|
+
if (invoked) {
|
|
4267
|
+
metadata["openai/toolInvocation/invoked"] = invoked;
|
|
4268
|
+
}
|
|
4269
|
+
const displayMessage = message || `Displaying ${name}`;
|
|
4270
|
+
return {
|
|
4271
|
+
_meta: metadata,
|
|
4272
|
+
content: [
|
|
4273
|
+
{
|
|
4274
|
+
type: "text",
|
|
4275
|
+
text: displayMessage
|
|
4276
|
+
}
|
|
4277
|
+
],
|
|
4278
|
+
// structuredContent will be injected as window.openai.toolOutput by Apps SDK
|
|
4279
|
+
structuredContent: data
|
|
4280
|
+
};
|
|
4281
|
+
}
|
|
4282
|
+
__name(widget, "widget");
|
|
4283
|
+
|
|
4284
|
+
// src/server/index.ts
|
|
4285
|
+
init_oauth();
|