@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.
@@ -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 ? { ...pendingRequest, scopes: remainingRequestScopes } : void 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
- scopeAccessRequest: record2.scopeAccessRequest,
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({ scopes: scopeEntries });
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: finalApprovalRequired && requestRecorded,
123377
- nextAction: finalApprovalRequired ? requestRecorded ? "The request is waiting for owner approval in Vana. Access remains unchanged until the owner approves it." : "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." : "No new grant is needed for the requested scopes."
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.29.2",
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.29.2",
55
- "@opendatalabs/personal-server-ts-lite": "1.29.2",
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",