@opendatalabs/personal-server-ts-server 1.29.2 → 1.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts +3 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +1 -0
- package/dist/app.js.map +1 -1
- package/dist/bootstrap.d.ts +11 -0
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +1 -0
- package/dist/bootstrap.js.map +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1 -0
- package/dist/client.js.map +1 -1
- package/dist/middleware/cors.d.ts.map +1 -1
- package/dist/middleware/cors.js +3 -0
- package/dist/middleware/cors.js.map +1 -1
- package/dist/routes/mcp.d.ts +7 -1
- package/dist/routes/mcp.d.ts.map +1 -1
- package/dist/routes/mcp.js +140 -1
- package/dist/routes/mcp.js.map +1 -1
- package/dist/ui/ps-lite-debug.js +410 -11
- package/package.json +3 -3
package/dist/ui/ps-lite-debug.js
CHANGED
|
@@ -114138,6 +114138,10 @@ function isExpired(expiresAt, nowMs2) {
|
|
|
114138
114138
|
// ../core/dist/mcp/connection-api.js
|
|
114139
114139
|
var TOKEN_BYTES = 32;
|
|
114140
114140
|
var OAUTH_AUTHORIZATION_TTL_MS = 10 * 60 * 1e3;
|
|
114141
|
+
var MCP_SCOPE_REQUEST_TOKEN_HEADER = "X-Vana-Scope-Request-Token";
|
|
114142
|
+
var MCP_SCOPE_REQUEST_NOT_FOUND = "MCP_SCOPE_REQUEST_NOT_FOUND";
|
|
114143
|
+
var MCP_SCOPE_REQUEST_READ_TOKEN_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
114144
|
+
var MAX_READ_TOKEN_CHARS = 128;
|
|
114141
114145
|
var CLEARED_REFRESH_FAMILY = {
|
|
114142
114146
|
refreshTokenHash: void 0,
|
|
114143
114147
|
previousRefreshTokenHash: void 0,
|
|
@@ -114214,6 +114218,7 @@ function buildMcpProtectedResourceMetadataUrl(publicOrigin) {
|
|
|
114214
114218
|
const base = publicOrigin.endsWith("/") ? publicOrigin.slice(0, -1) : publicOrigin;
|
|
114215
114219
|
return `${base}/.well-known/oauth-protected-resource/mcp`;
|
|
114216
114220
|
}
|
|
114221
|
+
var MCP_CONNECTION_NOT_FOUND = "MCP_CONNECTION_NOT_FOUND";
|
|
114217
114222
|
var McpConnectionNotFoundError = class extends Error {
|
|
114218
114223
|
connectionId;
|
|
114219
114224
|
constructor(connectionId) {
|
|
@@ -114248,7 +114253,10 @@ async function approveMcpConnection(input, options) {
|
|
|
114248
114253
|
status: "approved",
|
|
114249
114254
|
grants: input.grants,
|
|
114250
114255
|
approvedAt,
|
|
114251
|
-
scopeAccessRequest: pendingRequest && remainingRequestScopes && remainingRequestScopes.length > 0 ? {
|
|
114256
|
+
scopeAccessRequest: pendingRequest && remainingRequestScopes && remainingRequestScopes.length > 0 ? {
|
|
114257
|
+
...toMcpScopeAccessRequestView(pendingRequest),
|
|
114258
|
+
scopes: remainingRequestScopes
|
|
114259
|
+
} : void 0
|
|
114252
114260
|
};
|
|
114253
114261
|
});
|
|
114254
114262
|
if (!updated)
|
|
@@ -114260,6 +114268,9 @@ function grantCoversScope(grant, scope) {
|
|
|
114260
114268
|
}
|
|
114261
114269
|
async function requestMcpScopeAccess(input, options) {
|
|
114262
114270
|
const requestedAt = nowIso(options.now);
|
|
114271
|
+
const readToken = randomToken();
|
|
114272
|
+
const readTokenHash = await hashConnectionToken(readToken);
|
|
114273
|
+
const readTokenExpiresAt = new Date(nowMs(options.now) + MCP_SCOPE_REQUEST_READ_TOKEN_TTL_MS).toISOString();
|
|
114263
114274
|
const inputScopes = Array.from(new Set(input.scopes.map((scope) => scope.trim()).filter(Boolean))).sort();
|
|
114264
114275
|
const updated = await options.store.mutate(input.connectionId, (existing) => {
|
|
114265
114276
|
if (existing.status !== "approved") {
|
|
@@ -114274,7 +114285,9 @@ async function requestMcpScopeAccess(input, options) {
|
|
|
114274
114285
|
const request2 = {
|
|
114275
114286
|
scopes,
|
|
114276
114287
|
...reason ? { reason } : {},
|
|
114277
|
-
requestedAt
|
|
114288
|
+
requestedAt,
|
|
114289
|
+
readTokenHash,
|
|
114290
|
+
readTokenExpiresAt
|
|
114278
114291
|
};
|
|
114279
114292
|
return { ...existing, scopeAccessRequest: request2 };
|
|
114280
114293
|
});
|
|
@@ -114283,9 +114296,227 @@ async function requestMcpScopeAccess(input, options) {
|
|
|
114283
114296
|
const missingInputScopes = inputScopes.filter((scope) => !updated.grants.some((grant) => grantCoversScope(grant, scope)));
|
|
114284
114297
|
return {
|
|
114285
114298
|
connection: updated,
|
|
114286
|
-
requestRecorded: missingInputScopes.length > 0 && missingInputScopes.every((scope) => updated.scopeAccessRequest?.scopes.includes(scope))
|
|
114299
|
+
requestRecorded: missingInputScopes.length > 0 && missingInputScopes.every((scope) => updated.scopeAccessRequest?.scopes.includes(scope)),
|
|
114300
|
+
...updated.scopeAccessRequest?.readTokenHash === readTokenHash ? { readToken } : {}
|
|
114287
114301
|
};
|
|
114288
114302
|
}
|
|
114303
|
+
function digestsEqual(a10, b10) {
|
|
114304
|
+
let diff = a10.length ^ b10.length;
|
|
114305
|
+
const length = Math.max(a10.length, b10.length);
|
|
114306
|
+
for (let i10 = 0; i10 < length; i10 += 1) {
|
|
114307
|
+
diff |= (a10.charCodeAt(i10) | 0) ^ (b10.charCodeAt(i10) | 0);
|
|
114308
|
+
}
|
|
114309
|
+
return diff === 0;
|
|
114310
|
+
}
|
|
114311
|
+
async function readMcpScopeRequestWithToken(input, options) {
|
|
114312
|
+
const token = input.token?.trim() ?? "";
|
|
114313
|
+
if (!token || token.length > MAX_READ_TOKEN_CHARS || !input.connectionId) {
|
|
114314
|
+
return null;
|
|
114315
|
+
}
|
|
114316
|
+
const presentedHash = await hashConnectionToken(token);
|
|
114317
|
+
const record2 = await options.store.getById(input.connectionId);
|
|
114318
|
+
const request2 = record2?.scopeAccessRequest;
|
|
114319
|
+
if (!record2 || record2.status !== "approved" || !request2)
|
|
114320
|
+
return null;
|
|
114321
|
+
if (!request2.readTokenHash || !request2.readTokenExpiresAt)
|
|
114322
|
+
return null;
|
|
114323
|
+
if (!digestsEqual(presentedHash, request2.readTokenHash))
|
|
114324
|
+
return null;
|
|
114325
|
+
const expiresAt = Date.parse(request2.readTokenExpiresAt);
|
|
114326
|
+
if (!Number.isFinite(expiresAt) || expiresAt <= nowMs(options.now)) {
|
|
114327
|
+
return null;
|
|
114328
|
+
}
|
|
114329
|
+
if (request2.scopes.length === 0)
|
|
114330
|
+
return null;
|
|
114331
|
+
return {
|
|
114332
|
+
id: record2.id,
|
|
114333
|
+
displayName: record2.displayName,
|
|
114334
|
+
scopeAccessRequest: toMcpScopeAccessRequestView(request2),
|
|
114335
|
+
grantedScopes: sortedUnique(record2.grants.flatMap((grant) => grant.scopes))
|
|
114336
|
+
};
|
|
114337
|
+
}
|
|
114338
|
+
var McpScopeRequestError = class extends Error {
|
|
114339
|
+
code;
|
|
114340
|
+
status;
|
|
114341
|
+
body;
|
|
114342
|
+
constructor(code, message, status2 = 400, body) {
|
|
114343
|
+
super(message);
|
|
114344
|
+
this.code = code;
|
|
114345
|
+
this.status = status2;
|
|
114346
|
+
this.body = body;
|
|
114347
|
+
this.name = "McpScopeRequestError";
|
|
114348
|
+
}
|
|
114349
|
+
};
|
|
114350
|
+
async function approveMcpScopeAccessRequest(input, options) {
|
|
114351
|
+
const connection = await options.store.getById(input.connectionId);
|
|
114352
|
+
if (!connection)
|
|
114353
|
+
throw new McpConnectionNotFoundError(input.connectionId);
|
|
114354
|
+
const pending = requirePendingScopeRequest(connection);
|
|
114355
|
+
const approvedScopes = parseApprovedScopes(input.scopes, pending);
|
|
114356
|
+
const live = await readLiveGrantState(connection, options.gateway);
|
|
114357
|
+
const grantedScopes = sortedUnique([...live.scopes, ...approvedScopes]);
|
|
114358
|
+
let grantResult;
|
|
114359
|
+
try {
|
|
114360
|
+
grantResult = await createGrantContract({
|
|
114361
|
+
gateway: options.gateway,
|
|
114362
|
+
serverOwner: options.serverOwner,
|
|
114363
|
+
serverSigner: options.serverSigner,
|
|
114364
|
+
body: {
|
|
114365
|
+
granteeAddress: connection.granteeAddress,
|
|
114366
|
+
scopes: grantedScopes,
|
|
114367
|
+
grantVersion: live.nextGrantVersion,
|
|
114368
|
+
...live.expiresAt !== void 0 ? { expiresAt: live.expiresAt } : {}
|
|
114369
|
+
}
|
|
114370
|
+
});
|
|
114371
|
+
} catch (err2) {
|
|
114372
|
+
throw new McpScopeRequestError("GRANT_CREATION_FAILED", err2 instanceof Error ? err2.message : String(err2), 502);
|
|
114373
|
+
}
|
|
114374
|
+
if (!grantResult.ok) {
|
|
114375
|
+
throw new McpScopeRequestError("GRANT_CREATION_FAILED", extractGrantErrorMessage(grantResult.body), grantResult.status >= 500 ? 502 : grantResult.status === 409 ? 409 : 400, grantResult.body);
|
|
114376
|
+
}
|
|
114377
|
+
const grantId = grantResult.body.grantId;
|
|
114378
|
+
if (!grantId) {
|
|
114379
|
+
throw new McpScopeRequestError("GRANT_CREATION_FAILED", "Gateway did not return a grant id.", 502, grantResult.body);
|
|
114380
|
+
}
|
|
114381
|
+
const decidedAt = nowIso(options.now);
|
|
114382
|
+
const deniedScopes = pending.scopes.filter((scope) => !approvedScopes.includes(scope));
|
|
114383
|
+
const updated = await options.store.mutate(input.connectionId, (latest) => {
|
|
114384
|
+
requirePendingScopeRequest(latest);
|
|
114385
|
+
if (JSON.stringify(latest.grants) !== JSON.stringify(connection.grants)) {
|
|
114386
|
+
throw new McpScopeRequestError("CONCURRENT_UPDATE", "The connection's grants changed during approval; retry.", 409);
|
|
114387
|
+
}
|
|
114388
|
+
const decision = {
|
|
114389
|
+
decision: "approved",
|
|
114390
|
+
approvedScopes,
|
|
114391
|
+
deniedScopes,
|
|
114392
|
+
requestedAt: pending.requestedAt,
|
|
114393
|
+
decidedAt
|
|
114394
|
+
};
|
|
114395
|
+
return {
|
|
114396
|
+
...latest,
|
|
114397
|
+
grants: [{ grantId, scopes: grantedScopes }],
|
|
114398
|
+
scopeAccessRequest: void 0,
|
|
114399
|
+
scopeAccessDecision: decision
|
|
114400
|
+
};
|
|
114401
|
+
});
|
|
114402
|
+
if (!updated)
|
|
114403
|
+
throw new McpConnectionNotFoundError(input.connectionId);
|
|
114404
|
+
return {
|
|
114405
|
+
connection: updated,
|
|
114406
|
+
grantId,
|
|
114407
|
+
grantedScopes,
|
|
114408
|
+
approvedScopes,
|
|
114409
|
+
deniedScopes
|
|
114410
|
+
};
|
|
114411
|
+
}
|
|
114412
|
+
async function denyMcpScopeAccessRequest(input, options) {
|
|
114413
|
+
const decidedAt = nowIso(options.now);
|
|
114414
|
+
const updated = await options.store.mutate(input.connectionId, (latest) => {
|
|
114415
|
+
const pending = requirePendingScopeRequest(latest);
|
|
114416
|
+
const decision = {
|
|
114417
|
+
decision: "denied",
|
|
114418
|
+
approvedScopes: [],
|
|
114419
|
+
deniedScopes: pending.scopes,
|
|
114420
|
+
requestedAt: pending.requestedAt,
|
|
114421
|
+
decidedAt
|
|
114422
|
+
};
|
|
114423
|
+
return {
|
|
114424
|
+
...latest,
|
|
114425
|
+
scopeAccessRequest: void 0,
|
|
114426
|
+
scopeAccessDecision: decision
|
|
114427
|
+
};
|
|
114428
|
+
});
|
|
114429
|
+
if (!updated)
|
|
114430
|
+
throw new McpConnectionNotFoundError(input.connectionId);
|
|
114431
|
+
return updated;
|
|
114432
|
+
}
|
|
114433
|
+
function requirePendingScopeRequest(connection) {
|
|
114434
|
+
if (connection.status !== "approved") {
|
|
114435
|
+
throw new McpConnectionStateError(connection.id, connection.status, "approved");
|
|
114436
|
+
}
|
|
114437
|
+
const pending = connection.scopeAccessRequest;
|
|
114438
|
+
if (!pending || pending.scopes.length === 0) {
|
|
114439
|
+
throw new McpScopeRequestError("NO_PENDING_REQUEST", `mcp connection ${connection.id} has no pending scope request`, 409);
|
|
114440
|
+
}
|
|
114441
|
+
return pending;
|
|
114442
|
+
}
|
|
114443
|
+
function parseApprovedScopes(value, pending) {
|
|
114444
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
114445
|
+
throw new McpScopeRequestError("SCOPES_REQUIRED", "Approve requires a non-empty scopes array; use deny to decline all.");
|
|
114446
|
+
}
|
|
114447
|
+
const scopes = [];
|
|
114448
|
+
for (const raw of value) {
|
|
114449
|
+
const scope = typeof raw === "string" ? raw.trim() : "";
|
|
114450
|
+
if (!ScopeSchema.safeParse(scope).success) {
|
|
114451
|
+
throw new McpScopeRequestError("INVALID_SCOPE", `Invalid scope: ${JSON.stringify(raw)}`);
|
|
114452
|
+
}
|
|
114453
|
+
if (!pending.scopes.includes(scope)) {
|
|
114454
|
+
throw new McpScopeRequestError("SCOPE_NOT_REQUESTED", `Scope ${scope} is not part of the pending request`);
|
|
114455
|
+
}
|
|
114456
|
+
if (!scopes.includes(scope))
|
|
114457
|
+
scopes.push(scope);
|
|
114458
|
+
}
|
|
114459
|
+
return scopes.sort();
|
|
114460
|
+
}
|
|
114461
|
+
async function readLiveGrantState(connection, gateway) {
|
|
114462
|
+
const grantIds = sortedUnique(connection.grants.map((grant) => grant.grantId));
|
|
114463
|
+
const revoked = /* @__PURE__ */ new Set();
|
|
114464
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
114465
|
+
let maxVersion = 0n;
|
|
114466
|
+
let expiresAt;
|
|
114467
|
+
for (const grantId of grantIds) {
|
|
114468
|
+
let live;
|
|
114469
|
+
try {
|
|
114470
|
+
live = await gateway.getGrant(grantId);
|
|
114471
|
+
} catch (err2) {
|
|
114472
|
+
throw new McpScopeRequestError("GATEWAY_UNAVAILABLE", `Could not read grant ${grantId}: ${err2 instanceof Error ? err2.message : String(err2)}`, 502);
|
|
114473
|
+
}
|
|
114474
|
+
if (!live)
|
|
114475
|
+
continue;
|
|
114476
|
+
const version4 = parseUint(live.grantVersion);
|
|
114477
|
+
if (version4 !== null && version4 > maxVersion)
|
|
114478
|
+
maxVersion = version4;
|
|
114479
|
+
if (live.revokedAt) {
|
|
114480
|
+
revoked.add(grantId);
|
|
114481
|
+
continue;
|
|
114482
|
+
}
|
|
114483
|
+
for (const scope of live.scopes)
|
|
114484
|
+
scopes.add(scope);
|
|
114485
|
+
const liveExpiry = parseExpirySeconds(live.expiresAt);
|
|
114486
|
+
if (liveExpiry !== void 0) {
|
|
114487
|
+
expiresAt = expiresAt === void 0 ? liveExpiry : Math.max(expiresAt, liveExpiry);
|
|
114488
|
+
}
|
|
114489
|
+
}
|
|
114490
|
+
for (const grant of connection.grants) {
|
|
114491
|
+
if (revoked.has(grant.grantId))
|
|
114492
|
+
continue;
|
|
114493
|
+
for (const scope of grant.scopes)
|
|
114494
|
+
scopes.add(scope);
|
|
114495
|
+
}
|
|
114496
|
+
return {
|
|
114497
|
+
scopes: sortedUnique(scopes),
|
|
114498
|
+
nextGrantVersion: (maxVersion + 1n).toString(),
|
|
114499
|
+
...expiresAt !== void 0 ? { expiresAt } : {}
|
|
114500
|
+
};
|
|
114501
|
+
}
|
|
114502
|
+
function parseUint(value) {
|
|
114503
|
+
if (typeof value !== "string" || !/^\d+$/u.test(value))
|
|
114504
|
+
return null;
|
|
114505
|
+
return BigInt(value);
|
|
114506
|
+
}
|
|
114507
|
+
function parseExpirySeconds(value) {
|
|
114508
|
+
if (!value)
|
|
114509
|
+
return void 0;
|
|
114510
|
+
if (/^\d+$/u.test(value)) {
|
|
114511
|
+
const seconds = Number(value);
|
|
114512
|
+
return seconds > 0 ? seconds : void 0;
|
|
114513
|
+
}
|
|
114514
|
+
const ms3 = Date.parse(value);
|
|
114515
|
+
return Number.isFinite(ms3) ? Math.floor(ms3 / 1e3) : void 0;
|
|
114516
|
+
}
|
|
114517
|
+
function sortedUnique(values) {
|
|
114518
|
+
return Array.from(new Set(values)).sort();
|
|
114519
|
+
}
|
|
114289
114520
|
async function revokeMcpConnection(connectionId, options) {
|
|
114290
114521
|
const existing = await options.store.getById(connectionId);
|
|
114291
114522
|
if (!existing)
|
|
@@ -114302,6 +114533,13 @@ async function revokeMcpConnection(connectionId, options) {
|
|
|
114302
114533
|
throw new McpConnectionNotFoundError(connectionId);
|
|
114303
114534
|
return updated;
|
|
114304
114535
|
}
|
|
114536
|
+
function toMcpScopeAccessRequestView(request2) {
|
|
114537
|
+
return {
|
|
114538
|
+
scopes: request2.scopes,
|
|
114539
|
+
...request2.reason !== void 0 ? { reason: request2.reason } : {},
|
|
114540
|
+
requestedAt: request2.requestedAt
|
|
114541
|
+
};
|
|
114542
|
+
}
|
|
114305
114543
|
function toMcpConnectionView(record2) {
|
|
114306
114544
|
return {
|
|
114307
114545
|
id: record2.id,
|
|
@@ -114309,13 +114547,21 @@ function toMcpConnectionView(record2) {
|
|
|
114309
114547
|
granteeAddress: record2.granteeAddress,
|
|
114310
114548
|
status: record2.status,
|
|
114311
114549
|
grants: record2.grants,
|
|
114312
|
-
|
|
114550
|
+
grantedScopes: sortedUnique(record2.grants.flatMap((grant) => grant.scopes)),
|
|
114551
|
+
scopeAccessRequest: record2.scopeAccessRequest ? toMcpScopeAccessRequestView(record2.scopeAccessRequest) : void 0,
|
|
114552
|
+
scopeAccessDecision: record2.scopeAccessDecision,
|
|
114313
114553
|
createdAt: record2.createdAt,
|
|
114314
114554
|
approvedAt: record2.approvedAt,
|
|
114315
114555
|
revokedAt: record2.revokedAt,
|
|
114316
114556
|
lastUsedAt: record2.lastUsedAt
|
|
114317
114557
|
};
|
|
114318
114558
|
}
|
|
114559
|
+
async function getMcpConnectionView(connectionId, store) {
|
|
114560
|
+
const record2 = await store.getById(connectionId);
|
|
114561
|
+
if (!record2)
|
|
114562
|
+
throw new McpConnectionNotFoundError(connectionId);
|
|
114563
|
+
return toMcpConnectionView(record2);
|
|
114564
|
+
}
|
|
114319
114565
|
async function listMcpConnectionViews(store) {
|
|
114320
114566
|
const records = await store.list();
|
|
114321
114567
|
return records.map(toMcpConnectionView);
|
|
@@ -123333,7 +123579,13 @@ var listGrantedScopes = {
|
|
|
123333
123579
|
reason
|
|
123334
123580
|
};
|
|
123335
123581
|
}));
|
|
123336
|
-
return textResult({
|
|
123582
|
+
return textResult({
|
|
123583
|
+
scopes: scopeEntries,
|
|
123584
|
+
...connection.scopeAccessRequest ? {
|
|
123585
|
+
pendingScopeRequest: toMcpScopeAccessRequestView(connection.scopeAccessRequest)
|
|
123586
|
+
} : {},
|
|
123587
|
+
...connection.scopeAccessDecision ? { lastScopeDecision: connection.scopeAccessDecision } : {}
|
|
123588
|
+
});
|
|
123337
123589
|
}
|
|
123338
123590
|
};
|
|
123339
123591
|
var requestScopeAccess = {
|
|
@@ -123341,8 +123593,9 @@ var requestScopeAccess = {
|
|
|
123341
123593
|
title: "Request scope access",
|
|
123342
123594
|
description: "Record missing scopes on this MCP connection for explicit owner approval in Vana.",
|
|
123343
123595
|
inputSchema: scopeAccessRequestInputShape,
|
|
123344
|
-
async handler(args, { connection, requestScopeAccess: requestScopeAccess2 }) {
|
|
123596
|
+
async handler(args, { connection, requestScopeAccess: requestScopeAccess2, scopeRequestApprovalUrl, serverOrigin }) {
|
|
123345
123597
|
const requestedScopes = normalizeScopeListInput(args.scopes);
|
|
123598
|
+
const previousDecision = connection.scopeAccessDecision;
|
|
123346
123599
|
let effectiveConnection = connection;
|
|
123347
123600
|
let grantedRequestedScopes = requestedScopes.filter((scope) => Boolean(resolveGrantForScope(effectiveConnection, scope)));
|
|
123348
123601
|
let missingScopes = requestedScopes.filter((scope) => !resolveGrantForScope(connection, scope));
|
|
@@ -123350,6 +123603,7 @@ var requestScopeAccess = {
|
|
|
123350
123603
|
const approvalRequired = missingScopes.length > 0;
|
|
123351
123604
|
const alreadyRecorded = approvalRequired && missingScopes.every((scope) => connection.scopeAccessRequest?.scopes.includes(scope));
|
|
123352
123605
|
let requestRecorded = alreadyRecorded;
|
|
123606
|
+
let readToken;
|
|
123353
123607
|
if (approvalRequired && requestScopeAccess2) {
|
|
123354
123608
|
const outcome = await requestScopeAccess2({
|
|
123355
123609
|
scopes: missingScopes,
|
|
@@ -123357,10 +123611,17 @@ var requestScopeAccess = {
|
|
|
123357
123611
|
});
|
|
123358
123612
|
effectiveConnection = outcome.connection;
|
|
123359
123613
|
requestRecorded = outcome.requestRecorded;
|
|
123614
|
+
readToken = outcome.readToken;
|
|
123360
123615
|
grantedRequestedScopes = requestedScopes.filter((scope) => Boolean(resolveGrantForScope(effectiveConnection, scope)));
|
|
123361
123616
|
missingScopes = requestedScopes.filter((scope) => !resolveGrantForScope(effectiveConnection, scope));
|
|
123362
123617
|
}
|
|
123363
123618
|
const finalApprovalRequired = missingScopes.length > 0;
|
|
123619
|
+
const recorded = finalApprovalRequired && requestRecorded;
|
|
123620
|
+
const approvalUrl = recorded ? resolveScopeRequestApprovalUrl(scopeRequestApprovalUrl, connection.id, {
|
|
123621
|
+
serverOrigin,
|
|
123622
|
+
...readToken ? { readToken } : {}
|
|
123623
|
+
}) : void 0;
|
|
123624
|
+
const declinedScopes = previousDecision?.deniedScopes.filter((scope) => missingScopes.includes(scope)) ?? [];
|
|
123364
123625
|
return textResult({
|
|
123365
123626
|
approvalRequired: finalApprovalRequired,
|
|
123366
123627
|
client: {
|
|
@@ -123373,11 +123634,41 @@ var requestScopeAccess = {
|
|
|
123373
123634
|
missingScopes,
|
|
123374
123635
|
grantedScopes: uniqueScopes(effectiveConnection),
|
|
123375
123636
|
reason: reason || void 0,
|
|
123376
|
-
requestRecorded:
|
|
123377
|
-
|
|
123637
|
+
requestRecorded: recorded,
|
|
123638
|
+
approvalUrl,
|
|
123639
|
+
previousDecision,
|
|
123640
|
+
nextAction: scopeRequestNextAction({
|
|
123641
|
+
finalApprovalRequired,
|
|
123642
|
+
recorded,
|
|
123643
|
+
approvalUrl,
|
|
123644
|
+
declinedScopes
|
|
123645
|
+
})
|
|
123378
123646
|
});
|
|
123379
123647
|
}
|
|
123380
123648
|
};
|
|
123649
|
+
function resolveScopeRequestApprovalUrl(hook, connectionId, context) {
|
|
123650
|
+
if (!hook)
|
|
123651
|
+
return void 0;
|
|
123652
|
+
try {
|
|
123653
|
+
const url2 = hook(connectionId, context);
|
|
123654
|
+
return typeof url2 === "string" && url2.trim() ? url2.trim() : void 0;
|
|
123655
|
+
} catch {
|
|
123656
|
+
return void 0;
|
|
123657
|
+
}
|
|
123658
|
+
}
|
|
123659
|
+
function scopeRequestNextAction(input) {
|
|
123660
|
+
if (!input.finalApprovalRequired) {
|
|
123661
|
+
return "No new grant is needed for the requested scopes.";
|
|
123662
|
+
}
|
|
123663
|
+
const declined = input.declinedScopes.length > 0 ? `The owner previously declined ${input.declinedScopes.join(", ")}; only ask again if the user wants to. ` : "";
|
|
123664
|
+
if (!input.recorded) {
|
|
123665
|
+
return `${declined}Ask the user to open Vana's Personal Server page and add the missing scopes to this MCP client. This tool cannot grant access by itself.`;
|
|
123666
|
+
}
|
|
123667
|
+
if (input.approvalUrl) {
|
|
123668
|
+
return `${declined}Show the user this link to review the request: ${input.approvalUrl} . Ask them to approve or decline it there and to tell you when they are done, then call list_granted_scopes to see what was granted. Access remains unchanged until the owner approves.`;
|
|
123669
|
+
}
|
|
123670
|
+
return `${declined}The request is waiting for owner approval in Vana. Access remains unchanged until the owner approves it.`;
|
|
123671
|
+
}
|
|
123381
123672
|
var readScope = {
|
|
123382
123673
|
name: READ_SCOPE_TOOL,
|
|
123383
123674
|
title: "Read scope",
|
|
@@ -134557,7 +134848,9 @@ function createMcpServerForConnection(options) {
|
|
|
134557
134848
|
connection: options.connection,
|
|
134558
134849
|
readClient: options.readClient,
|
|
134559
134850
|
activityRecorder: options.activityRecorder,
|
|
134560
|
-
requestScopeAccess: options.requestScopeAccess
|
|
134851
|
+
requestScopeAccess: options.requestScopeAccess,
|
|
134852
|
+
scopeRequestApprovalUrl: options.scopeRequestApprovalUrl,
|
|
134853
|
+
serverOrigin: options.serverOrigin
|
|
134561
134854
|
};
|
|
134562
134855
|
for (const tool of MCP_TOOLS) {
|
|
134563
134856
|
server.registerTool(tool.name, toolConfig(tool), async (args) => {
|
|
@@ -135641,6 +135934,7 @@ function createPsLiteRuntime(options) {
|
|
|
135641
135934
|
store: options.mcpConnectionStore,
|
|
135642
135935
|
authorizationStore: mcpOAuthAuthorizationStore,
|
|
135643
135936
|
approvalUrl: options.mcpOAuthApprovalUrl,
|
|
135937
|
+
scopeRequestApprovalUrl: options.mcpScopeRequestApprovalUrl,
|
|
135644
135938
|
auth,
|
|
135645
135939
|
dataStorage,
|
|
135646
135940
|
accessLogWriter,
|
|
@@ -135865,6 +136159,25 @@ async function handleMcpRoute(input) {
|
|
|
135865
136159
|
}
|
|
135866
136160
|
return errorResponse3(404, "NOT_FOUND", "Not found");
|
|
135867
136161
|
}
|
|
136162
|
+
const scopeRequestRead = /^\/v1\/mcp\/connections\/([^/]+)\/scope-request$/u.exec(pathname);
|
|
136163
|
+
if (scopeRequestRead && input.request.method === "GET") {
|
|
136164
|
+
let connectionId = "";
|
|
136165
|
+
try {
|
|
136166
|
+
connectionId = decodeURIComponent(scopeRequestRead[1] ?? "");
|
|
136167
|
+
} catch {
|
|
136168
|
+
connectionId = "";
|
|
136169
|
+
}
|
|
136170
|
+
const view = await readMcpScopeRequestWithToken({
|
|
136171
|
+
connectionId,
|
|
136172
|
+
token: input.request.headers.get(MCP_SCOPE_REQUEST_TOKEN_HEADER)
|
|
136173
|
+
}, { store: input.store, now: input.now });
|
|
136174
|
+
if (!view) {
|
|
136175
|
+
const notFound2 = errorResponse3(404, MCP_SCOPE_REQUEST_NOT_FOUND, "Scope request not found");
|
|
136176
|
+
notFound2.headers.set("Cache-Control", "no-store");
|
|
136177
|
+
return notFound2;
|
|
136178
|
+
}
|
|
136179
|
+
return jsonResponse4(view, { headers: { "Cache-Control": "no-store" } });
|
|
136180
|
+
}
|
|
135868
136181
|
if (pathname === ownerPrefix || pathname.startsWith(`${ownerPrefix}/`)) {
|
|
135869
136182
|
try {
|
|
135870
136183
|
await input.auth.authorizeOwner(input.request);
|
|
@@ -135896,10 +136209,19 @@ async function handleMcpRoute(input) {
|
|
|
135896
136209
|
return errorResponse3(405, "METHOD_NOT_ALLOWED", "Method not allowed");
|
|
135897
136210
|
}
|
|
135898
136211
|
const tail = pathname.slice(ownerPrefix.length + 1);
|
|
135899
|
-
const [id2, action] = tail.split("/");
|
|
136212
|
+
const [id2, action, subAction, ...rest] = tail.split("/");
|
|
135900
136213
|
if (!id2) {
|
|
135901
136214
|
return errorResponse3(404, "NOT_FOUND", "Not found");
|
|
135902
136215
|
}
|
|
136216
|
+
if (action === "scope-request") {
|
|
136217
|
+
if (rest.length > 0 || subAction !== "approve" && subAction !== "deny") {
|
|
136218
|
+
return errorResponse3(404, "NOT_FOUND", "Not found");
|
|
136219
|
+
}
|
|
136220
|
+
if (input.request.method !== "POST") {
|
|
136221
|
+
return errorResponse3(405, "METHOD_NOT_ALLOWED", "Method not allowed");
|
|
136222
|
+
}
|
|
136223
|
+
return handleScopeRequestAnswer(id2, subAction);
|
|
136224
|
+
}
|
|
135903
136225
|
if (action === "approve") {
|
|
135904
136226
|
if (input.request.method !== "POST") {
|
|
135905
136227
|
return errorResponse3(405, "METHOD_NOT_ALLOWED", "Method not allowed");
|
|
@@ -135927,6 +136249,16 @@ async function handleMcpRoute(input) {
|
|
|
135927
136249
|
}
|
|
135928
136250
|
}
|
|
135929
136251
|
if (!action) {
|
|
136252
|
+
if (input.request.method === "GET") {
|
|
136253
|
+
try {
|
|
136254
|
+
return jsonResponse4(await getMcpConnectionView(id2, input.store));
|
|
136255
|
+
} catch (err2) {
|
|
136256
|
+
if (err2 instanceof McpConnectionNotFoundError) {
|
|
136257
|
+
return errorResponse3(404, MCP_CONNECTION_NOT_FOUND, err2.message);
|
|
136258
|
+
}
|
|
136259
|
+
throw err2;
|
|
136260
|
+
}
|
|
136261
|
+
}
|
|
135930
136262
|
if (input.request.method === "DELETE") {
|
|
135931
136263
|
try {
|
|
135932
136264
|
const updated = await revokeMcpConnection(id2, {
|
|
@@ -135945,6 +136277,71 @@ async function handleMcpRoute(input) {
|
|
|
135945
136277
|
}
|
|
135946
136278
|
return errorResponse3(404, "NOT_FOUND", "Not found");
|
|
135947
136279
|
}
|
|
136280
|
+
async function handleScopeRequestAnswer(id2, answer) {
|
|
136281
|
+
let scopes;
|
|
136282
|
+
if (answer === "approve") {
|
|
136283
|
+
try {
|
|
136284
|
+
scopes = (await input.request.json()).scopes;
|
|
136285
|
+
} catch {
|
|
136286
|
+
return errorResponse3(400, "INVALID_BODY", "Body must be JSON");
|
|
136287
|
+
}
|
|
136288
|
+
}
|
|
136289
|
+
const gateway = input.gateway;
|
|
136290
|
+
if (answer === "approve" && !gateway) {
|
|
136291
|
+
return errorResponse3(500, "SERVER_NOT_CONFIGURED", "Gateway is not configured");
|
|
136292
|
+
}
|
|
136293
|
+
const activityId = input.activityRecorder?.start({
|
|
136294
|
+
tool: `owner.scope_request.${answer}`,
|
|
136295
|
+
...Array.isArray(scopes) ? {
|
|
136296
|
+
scopes: scopes.filter((scope) => typeof scope === "string")
|
|
136297
|
+
} : {}
|
|
136298
|
+
});
|
|
136299
|
+
const finish = (status2, errorCode) => {
|
|
136300
|
+
if (activityId) {
|
|
136301
|
+
input.activityRecorder?.finish(activityId, {
|
|
136302
|
+
status: status2,
|
|
136303
|
+
...errorCode ? { errorCode } : {}
|
|
136304
|
+
});
|
|
136305
|
+
}
|
|
136306
|
+
};
|
|
136307
|
+
try {
|
|
136308
|
+
if (answer === "deny" || !gateway) {
|
|
136309
|
+
const denied = await denyMcpScopeAccessRequest({ connectionId: id2 }, { store: input.store, now: input.now });
|
|
136310
|
+
finish("succeeded");
|
|
136311
|
+
return jsonResponse4({ connection: toMcpConnectionView(denied) });
|
|
136312
|
+
}
|
|
136313
|
+
const approved = await approveMcpScopeAccessRequest({ connectionId: id2, scopes }, {
|
|
136314
|
+
store: input.store,
|
|
136315
|
+
gateway,
|
|
136316
|
+
serverOwner: input.serverOwner,
|
|
136317
|
+
serverSigner: input.serverSigner,
|
|
136318
|
+
now: input.now
|
|
136319
|
+
});
|
|
136320
|
+
finish("succeeded");
|
|
136321
|
+
return jsonResponse4({
|
|
136322
|
+
connection: toMcpConnectionView(approved.connection),
|
|
136323
|
+
grantId: approved.grantId,
|
|
136324
|
+
grantedScopes: approved.grantedScopes,
|
|
136325
|
+
approvedScopes: approved.approvedScopes,
|
|
136326
|
+
deniedScopes: approved.deniedScopes
|
|
136327
|
+
});
|
|
136328
|
+
} catch (err2) {
|
|
136329
|
+
if (err2 instanceof McpConnectionNotFoundError) {
|
|
136330
|
+
finish("failed", MCP_CONNECTION_NOT_FOUND);
|
|
136331
|
+
return errorResponse3(404, MCP_CONNECTION_NOT_FOUND, err2.message);
|
|
136332
|
+
}
|
|
136333
|
+
if (err2 instanceof McpConnectionStateError) {
|
|
136334
|
+
finish("failed", "INVALID_STATE");
|
|
136335
|
+
return errorResponse3(409, "INVALID_STATE", err2.message);
|
|
136336
|
+
}
|
|
136337
|
+
if (err2 instanceof McpScopeRequestError) {
|
|
136338
|
+
finish("failed", err2.code);
|
|
136339
|
+
return errorResponse3(err2.status, err2.code, err2.message);
|
|
136340
|
+
}
|
|
136341
|
+
finish("failed", "INTERNAL");
|
|
136342
|
+
throw err2;
|
|
136343
|
+
}
|
|
136344
|
+
}
|
|
135948
136345
|
async function handleMcpToken(rawToken, options) {
|
|
135949
136346
|
if (!rawToken) {
|
|
135950
136347
|
if (!options.oauthChallenge) {
|
|
@@ -135984,7 +136381,9 @@ async function handleMcpRoute(input) {
|
|
|
135984
136381
|
activityRecorder: input.activityRecorder,
|
|
135985
136382
|
requestScopeAccess: async (request2) => {
|
|
135986
136383
|
return requestMcpScopeAccess({ connectionId: record2.id, ...request2 }, { store: input.store, now: input.now });
|
|
135987
|
-
}
|
|
136384
|
+
},
|
|
136385
|
+
scopeRequestApprovalUrl: input.scopeRequestApprovalUrl,
|
|
136386
|
+
serverOrigin: input.serverOrigin
|
|
135988
136387
|
});
|
|
135989
136388
|
void input.store.update(record2.id, {
|
|
135990
136389
|
lastUsedAt: input.now().toISOString()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opendatalabs/personal-server-ts-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.31.0",
|
|
4
4
|
"description": "Hono HTTP server for the Vana Personal Server — routes, middleware, composition root",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@hono/node-server": "^1.19.17",
|
|
54
|
-
"@opendatalabs/personal-server-ts-core": "1.
|
|
55
|
-
"@opendatalabs/personal-server-ts-lite": "1.
|
|
54
|
+
"@opendatalabs/personal-server-ts-core": "1.31.0",
|
|
55
|
+
"@opendatalabs/personal-server-ts-lite": "1.31.0",
|
|
56
56
|
"@opendatalabs/vana-sdk": "3.23.0-pr.211.36da1d8",
|
|
57
57
|
"better-sqlite3": "^12.11.1",
|
|
58
58
|
"hono": "^4.13.12",
|