@softure-ai/mcp-access 0.1.6 → 0.1.8

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.
Files changed (148) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +146 -21
  3. package/adoption/move-app-tables.sql +100 -0
  4. package/dist/contract.d.ts +13 -1
  5. package/dist/contract.d.ts.map +1 -1
  6. package/dist/index.d.ts +58 -3
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +26 -5
  9. package/dist/index.js.map +1 -1
  10. package/dist/messages/en.d.ts +28 -0
  11. package/dist/messages/en.d.ts.map +1 -1
  12. package/dist/messages/en.js +28 -0
  13. package/dist/messages/en.js.map +1 -1
  14. package/dist/messages/index.d.ts +56 -0
  15. package/dist/messages/index.d.ts.map +1 -1
  16. package/dist/messages/pl.d.ts.map +1 -1
  17. package/dist/messages/pl.js +28 -0
  18. package/dist/messages/pl.js.map +1 -1
  19. package/dist/next/actions.d.ts +3 -1
  20. package/dist/next/actions.d.ts.map +1 -1
  21. package/dist/next/actions.js +23 -2
  22. package/dist/next/actions.js.map +1 -1
  23. package/dist/next/context.d.ts +7 -1
  24. package/dist/next/context.d.ts.map +1 -1
  25. package/dist/next/context.js +12 -10
  26. package/dist/next/context.js.map +1 -1
  27. package/dist/next/index.d.ts +4 -1
  28. package/dist/next/index.d.ts.map +1 -1
  29. package/dist/next/index.js +5 -2
  30. package/dist/next/index.js.map +1 -1
  31. package/dist/next/oauth-page.d.ts +7 -0
  32. package/dist/next/oauth-page.d.ts.map +1 -0
  33. package/dist/next/oauth-page.js +36 -0
  34. package/dist/next/oauth-page.js.map +1 -0
  35. package/dist/next/oauth-routes.d.ts +19 -0
  36. package/dist/next/oauth-routes.d.ts.map +1 -0
  37. package/dist/next/oauth-routes.js +53 -0
  38. package/dist/next/oauth-routes.js.map +1 -0
  39. package/dist/next/pages.d.ts.map +1 -1
  40. package/dist/next/pages.js +13 -2
  41. package/dist/next/pages.js.map +1 -1
  42. package/dist/options.d.ts +40 -0
  43. package/dist/options.d.ts.map +1 -1
  44. package/dist/options.js +94 -1
  45. package/dist/options.js.map +1 -1
  46. package/dist/origins.d.ts +27 -0
  47. package/dist/origins.d.ts.map +1 -0
  48. package/dist/origins.js +32 -0
  49. package/dist/origins.js.map +1 -0
  50. package/dist/schema.d.ts +486 -0
  51. package/dist/schema.d.ts.map +1 -1
  52. package/dist/schema.js +45 -3
  53. package/dist/schema.js.map +1 -1
  54. package/dist/server/context.d.ts +4 -0
  55. package/dist/server/context.d.ts.map +1 -0
  56. package/dist/server/context.js +13 -0
  57. package/dist/server/context.js.map +1 -0
  58. package/dist/server/endpoint.d.ts +2 -4
  59. package/dist/server/endpoint.d.ts.map +1 -1
  60. package/dist/server/endpoint.js +14 -9
  61. package/dist/server/endpoint.js.map +1 -1
  62. package/dist/server/health.d.ts.map +1 -1
  63. package/dist/server/health.js +5 -3
  64. package/dist/server/health.js.map +1 -1
  65. package/dist/server/index.d.ts +8 -0
  66. package/dist/server/index.d.ts.map +1 -1
  67. package/dist/server/index.js +7 -0
  68. package/dist/server/index.js.map +1 -1
  69. package/dist/server/oauth-http.d.ts +127 -0
  70. package/dist/server/oauth-http.d.ts.map +1 -0
  71. package/dist/server/oauth-http.js +397 -0
  72. package/dist/server/oauth-http.js.map +1 -0
  73. package/dist/server/oauth-validation.d.ts +28 -0
  74. package/dist/server/oauth-validation.d.ts.map +1 -0
  75. package/dist/server/oauth-validation.js +88 -0
  76. package/dist/server/oauth-validation.js.map +1 -0
  77. package/dist/server/oauth.d.ts +106 -0
  78. package/dist/server/oauth.d.ts.map +1 -0
  79. package/dist/server/oauth.js +235 -0
  80. package/dist/server/oauth.js.map +1 -0
  81. package/dist/server/options.d.ts +11 -2
  82. package/dist/server/options.d.ts.map +1 -1
  83. package/dist/server/options.js +11 -4
  84. package/dist/server/options.js.map +1 -1
  85. package/dist/server/origins.d.ts +8 -0
  86. package/dist/server/origins.d.ts.map +1 -0
  87. package/dist/server/origins.js +15 -0
  88. package/dist/server/origins.js.map +1 -0
  89. package/dist/server/pkce.d.ts +7 -0
  90. package/dist/server/pkce.d.ts.map +1 -0
  91. package/dist/server/pkce.js +24 -0
  92. package/dist/server/pkce.js.map +1 -0
  93. package/dist/server/privacy.d.ts +7 -0
  94. package/dist/server/privacy.d.ts.map +1 -1
  95. package/dist/server/privacy.js +19 -9
  96. package/dist/server/privacy.js.map +1 -1
  97. package/dist/server/scopes.d.ts +5 -0
  98. package/dist/server/scopes.d.ts.map +1 -0
  99. package/dist/server/scopes.js +6 -0
  100. package/dist/server/scopes.js.map +1 -0
  101. package/dist/server/tokens.d.ts +18 -6
  102. package/dist/server/tokens.d.ts.map +1 -1
  103. package/dist/server/tokens.js +60 -25
  104. package/dist/server/tokens.js.map +1 -1
  105. package/dist/ui/consent-form.d.ts +31 -0
  106. package/dist/ui/consent-form.d.ts.map +1 -0
  107. package/dist/ui/consent-form.js +24 -0
  108. package/dist/ui/consent-form.js.map +1 -0
  109. package/dist/ui/index.d.ts +2 -1
  110. package/dist/ui/index.d.ts.map +1 -1
  111. package/dist/ui/index.js +1 -0
  112. package/dist/ui/index.js.map +1 -1
  113. package/dist/ui/token-manager.d.ts +16 -2
  114. package/dist/ui/token-manager.d.ts.map +1 -1
  115. package/dist/ui/token-manager.js +6 -2
  116. package/dist/ui/token-manager.js.map +1 -1
  117. package/migrations/0002_create_oauth_grants.sql +68 -0
  118. package/module.json +18 -4
  119. package/package.json +2 -1
  120. package/src/contract.ts +16 -1
  121. package/src/index.ts +33 -4
  122. package/src/messages/en.ts +28 -0
  123. package/src/messages/pl.ts +28 -0
  124. package/src/next/actions.ts +23 -3
  125. package/src/next/context.ts +15 -8
  126. package/src/next/index.ts +12 -2
  127. package/src/next/oauth-page.tsx +68 -0
  128. package/src/next/oauth-routes.ts +73 -0
  129. package/src/next/pages.tsx +16 -3
  130. package/src/options.ts +107 -1
  131. package/src/origins.ts +48 -0
  132. package/src/schema.ts +51 -3
  133. package/src/server/context.ts +14 -0
  134. package/src/server/endpoint.ts +14 -9
  135. package/src/server/health.ts +5 -3
  136. package/src/server/index.ts +56 -0
  137. package/src/server/oauth-http.ts +480 -0
  138. package/src/server/oauth-validation.ts +106 -0
  139. package/src/server/oauth.ts +315 -0
  140. package/src/server/options.ts +20 -4
  141. package/src/server/origins.ts +19 -0
  142. package/src/server/pkce.ts +27 -0
  143. package/src/server/privacy.ts +26 -9
  144. package/src/server/scopes.ts +6 -0
  145. package/src/server/tokens.ts +62 -25
  146. package/src/ui/consent-form.tsx +103 -0
  147. package/src/ui/index.ts +3 -0
  148. package/src/ui/token-manager.tsx +79 -3
@@ -0,0 +1,15 @@
1
+ import { isBareOrigin } from "../options.js";
2
+ import { getMcpAccessOptions } from "./options.js";
3
+ /**
4
+ * The origins for `request`, or the configured ones without a request. A resolver that returns
5
+ * something other than a bare http(s) origin is a setup bug: thrown by name, never served.
6
+ */
7
+ export function resolveMcpOrigins(config, request) {
8
+ const options = getMcpAccessOptions(config);
9
+ const resolved = request === undefined || request === null || options.resolveAppOrigin === undefined ? null : options.resolveAppOrigin(request);
10
+ if (resolved !== null && !isBareOrigin(resolved)) {
11
+ throw new Error(`@softure-ai/mcp-access: resolveAppOrigin returned "${resolved.slice(0, 100)}", which is not an http(s) origin without a path`);
12
+ }
13
+ return { appOrigin: new URL(resolved ?? config.appOrigin).origin, resourceOrigins: options.resourceOrigins };
14
+ }
15
+ //# sourceMappingURL=origins.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"origins.js","sourceRoot":"","sources":["../../src/server/origins.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAqB,EAAE,OAAiC;IACxF,MAAM,OAAO,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,QAAQ,GAAG,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAChJ,IAAI,QAAQ,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CAAC,sDAAsD,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,kDAAkD,CAAC,CAAC;IAClJ,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,IAAI,GAAG,CAAC,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,CAAC,eAAe,EAAE,CAAC;AAC/G,CAAC"}
@@ -0,0 +1,7 @@
1
+ export declare const PKCE_METHOD = "S256";
2
+ export declare function isValidCodeChallenge(challenge: string): boolean;
3
+ /** `BASE64URL(SHA256(ASCII(code_verifier)))`. */
4
+ export declare function getCodeChallenge(verifier: string): string;
5
+ /** Whether the verifier matches the challenge stored with the code; compared in constant time. */
6
+ export declare function isPkceVerifierValid(verifier: string, challenge: string): boolean;
7
+ //# sourceMappingURL=pkce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../src/server/pkce.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,SAAS,CAAC;AAOlC,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED,iDAAiD;AACjD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,kGAAkG;AAClG,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAKhF"}
@@ -0,0 +1,24 @@
1
+ // PKCE S256 (RFC 7636 §4.6), the only method accepted: OAuth 2.1 and the MCP authorization spec
2
+ // require it, and `plain` hands the verifier to anyone who saw the authorization URL.
3
+ import { createHash, timingSafeEqual } from "node:crypto";
4
+ export const PKCE_METHOD = "S256";
5
+ /** RFC 7636 §4.1: 43 to 128 unreserved characters. A shorter one carries too little entropy. */
6
+ const VERIFIER_PATTERN = /^[A-Za-z0-9._~-]{43,128}$/;
7
+ /** base64url of a sha256 without padding: always 43 characters. */
8
+ const CHALLENGE_PATTERN = /^[A-Za-z0-9_-]{43}$/;
9
+ export function isValidCodeChallenge(challenge) {
10
+ return CHALLENGE_PATTERN.test(challenge);
11
+ }
12
+ /** `BASE64URL(SHA256(ASCII(code_verifier)))`. */
13
+ export function getCodeChallenge(verifier) {
14
+ return createHash("sha256").update(verifier, "ascii").digest("base64url");
15
+ }
16
+ /** Whether the verifier matches the challenge stored with the code; compared in constant time. */
17
+ export function isPkceVerifierValid(verifier, challenge) {
18
+ if (!VERIFIER_PATTERN.test(verifier) || !isValidCodeChallenge(challenge))
19
+ return false;
20
+ const expected = Buffer.from(challenge);
21
+ const actual = Buffer.from(getCodeChallenge(verifier));
22
+ return expected.length === actual.length && timingSafeEqual(expected, actual);
23
+ }
24
+ //# sourceMappingURL=pkce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../src/server/pkce.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,sFAAsF;AACtF,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC;AAElC,gGAAgG;AAChG,MAAM,gBAAgB,GAAG,2BAA2B,CAAC;AACrD,mEAAmE;AACnE,MAAM,iBAAiB,GAAG,qBAAqB,CAAC;AAEhD,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,OAAO,iBAAiB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAC3C,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAC5E,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAE,SAAiB;IACrE,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,oBAAoB,CAAC,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IACvF,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC;IACvD,OAAO,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,IAAI,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAChF,CAAC"}
@@ -8,6 +8,13 @@ export interface McpAccessUserData {
8
8
  readonly expiresAt: Date;
9
9
  readonly lastUsedAt: Date | null;
10
10
  }[];
11
+ /** Apps connected through OAuth; their short-lived access tokens are not listed one by one. */
12
+ readonly connectedApps: readonly {
13
+ readonly clientName: string;
14
+ readonly canWrite: boolean;
15
+ readonly createdAt: Date;
16
+ readonly lastUsedAt: Date | null;
17
+ }[];
11
18
  }
12
19
  export declare function exportMcpAccessUserData(context: ModuleContext, userId: string): Promise<Ok<McpAccessUserData>>;
13
20
  export declare function deleteMcpAccessUserData(context: ModuleContext, userId: string): Promise<Ok<undefined>>;
@@ -1 +1 @@
1
- {"version":3,"file":"privacy.d.ts","sourceRoot":"","sources":["../../src/server/privacy.ts"],"names":[],"mappings":"AAGA,OAAO,EAAM,KAAK,aAAa,EAAE,KAAK,EAAE,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAK5F,2EAA2E;AAC3E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,YAAY,EAAE,SAAS;QAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;QAC3B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;QACzB,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;QACzB,QAAQ,CAAC,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;KAClC,EAAE,CAAC;CACL;AAID,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAgBpH;AAED,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAI5G;AAED,eAAO,MAAM,2BAA2B,EAAE,kBAGzC,CAAC"}
1
+ {"version":3,"file":"privacy.d.ts","sourceRoot":"","sources":["../../src/server/privacy.ts"],"names":[],"mappings":"AAGA,OAAO,EAAM,KAAK,aAAa,EAAE,KAAK,EAAE,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAK5F,2EAA2E;AAC3E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,YAAY,EAAE,SAAS;QAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;QAC3B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;QACzB,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;QACzB,QAAQ,CAAC,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;KAClC,EAAE,CAAC;IACJ,+FAA+F;IAC/F,QAAQ,CAAC,aAAa,EAAE,SAAS;QAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;QAC3B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;QACzB,QAAQ,CAAC,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;KAClC,EAAE,CAAC;CACL;AAID,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAsBpH;AAED,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAQ5G;AAED,eAAO,MAAM,2BAA2B,EAAE,kBAGzC,CAAC"}
@@ -1,16 +1,16 @@
1
1
  // The mcp-access part of a GDPR export and deletion (`@softure-ai/privacy`): the user's access
2
- // tokens, by name and dates. Never a token hash. Deletion removes the tokens by name before auth
3
- // deletes the account, so it does not rest on the foreign key's cascade alone.
2
+ // tokens and connected apps (OAuth grants), by name and dates. Never a hash. Deletion removes them
3
+ // by name before auth deletes the account, so it does not rest on the foreign keys' cascade alone.
4
4
  import { ok } from "@softure-ai/core";
5
- import { asc, eq } from "drizzle-orm";
6
- import { accessTokens } from "../schema.js";
5
+ import { and, asc, eq, isNull } from "drizzle-orm";
6
+ import { accessTokens, oauthAuthorizationCodes, oauthClients, oauthGrants } from "../schema.js";
7
7
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8
8
  export async function exportMcpAccessUserData(context, userId) {
9
9
  if (!UUID.test(userId))
10
- return ok({ accessTokens: [] });
10
+ return ok({ accessTokens: [], connectedApps: [] });
11
11
  // Core types `db` as unknown; privacy passes the @softure-ai/db handle or its transaction.
12
12
  const db = context.db;
13
- const rows = await db
13
+ const tokens = await db
14
14
  .select({
15
15
  name: accessTokens.name,
16
16
  canWrite: accessTokens.canWrite,
@@ -19,14 +19,24 @@ export async function exportMcpAccessUserData(context, userId) {
19
19
  lastUsedAt: accessTokens.lastUsedAt,
20
20
  })
21
21
  .from(accessTokens)
22
- .where(eq(accessTokens.userId, userId))
22
+ .where(and(eq(accessTokens.userId, userId), isNull(accessTokens.grantId)))
23
23
  .orderBy(asc(accessTokens.createdAt));
24
- return ok({ accessTokens: rows });
24
+ const connectedApps = await db
25
+ .select({ clientName: oauthClients.clientName, canWrite: oauthGrants.canWrite, createdAt: oauthGrants.createdAt, lastUsedAt: oauthGrants.lastUsedAt })
26
+ .from(oauthGrants)
27
+ .innerJoin(oauthClients, eq(oauthGrants.clientId, oauthClients.id))
28
+ .where(eq(oauthGrants.userId, userId))
29
+ .orderBy(asc(oauthGrants.createdAt));
30
+ return ok({ accessTokens: tokens, connectedApps });
25
31
  }
26
32
  export async function deleteMcpAccessUserData(context, userId) {
27
33
  if (!UUID.test(userId))
28
34
  return ok();
29
- await context.db.delete(accessTokens).where(eq(accessTokens.userId, userId));
35
+ const db = context.db;
36
+ await db.delete(oauthAuthorizationCodes).where(eq(oauthAuthorizationCodes.userId, userId));
37
+ // A grant's access tokens go with it by cascade; the next line takes the hand-issued ones.
38
+ await db.delete(oauthGrants).where(eq(oauthGrants.userId, userId));
39
+ await db.delete(accessTokens).where(eq(accessTokens.userId, userId));
30
40
  return ok();
31
41
  }
32
42
  export const mcpAccessPrivacyContributor = {
@@ -1 +1 @@
1
- {"version":3,"file":"privacy.js","sourceRoot":"","sources":["../../src/server/privacy.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,iGAAiG;AACjG,+EAA+E;AAC/E,OAAO,EAAE,EAAE,EAAwD,MAAM,kBAAkB,CAAC;AAE5F,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAa5C,MAAM,IAAI,GAAG,iEAAiE,CAAC;AAE/E,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,OAAsB,EAAE,MAAc;IAClF,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;IACxD,2FAA2F;IAC3F,MAAM,EAAE,GAAG,OAAO,CAAC,EAAe,CAAC;IACnC,MAAM,IAAI,GAAG,MAAM,EAAE;SAClB,MAAM,CAAC;QACN,IAAI,EAAE,YAAY,CAAC,IAAI;QACvB,QAAQ,EAAE,YAAY,CAAC,QAAQ;QAC/B,SAAS,EAAE,YAAY,CAAC,SAAS;QACjC,SAAS,EAAE,YAAY,CAAC,SAAS;QACjC,UAAU,EAAE,YAAY,CAAC,UAAU;KACpC,CAAC;SACD,IAAI,CAAC,YAAY,CAAC;SAClB,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;SACtC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC;IACxC,OAAO,EAAE,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,OAAsB,EAAE,MAAc;IAClF,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,EAAE,CAAC;IACpC,MAAO,OAAO,CAAC,EAAgB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5F,OAAO,EAAE,EAAE,CAAC;AACd,CAAC;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAuB;IAC7D,cAAc,EAAE,uBAAuB;IACvC,cAAc,EAAE,uBAAuB;CACxC,CAAC"}
1
+ {"version":3,"file":"privacy.js","sourceRoot":"","sources":["../../src/server/privacy.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,mGAAmG;AACnG,mGAAmG;AACnG,OAAO,EAAE,EAAE,EAAwD,MAAM,kBAAkB,CAAC;AAE5F,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAoBhG,MAAM,IAAI,GAAG,iEAAiE,CAAC;AAE/E,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,OAAsB,EAAE,MAAc;IAClF,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC,CAAC;IAC3E,2FAA2F;IAC3F,MAAM,EAAE,GAAG,OAAO,CAAC,EAAe,CAAC;IACnC,MAAM,MAAM,GAAG,MAAM,EAAE;SACpB,MAAM,CAAC;QACN,IAAI,EAAE,YAAY,CAAC,IAAI;QACvB,QAAQ,EAAE,YAAY,CAAC,QAAQ;QAC/B,SAAS,EAAE,YAAY,CAAC,SAAS;QACjC,SAAS,EAAE,YAAY,CAAC,SAAS;QACjC,UAAU,EAAE,YAAY,CAAC,UAAU;KACpC,CAAC;SACD,IAAI,CAAC,YAAY,CAAC;SAClB,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;SACzE,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC;IACxC,MAAM,aAAa,GAAG,MAAM,EAAE;SAC3B,MAAM,CAAC,EAAE,UAAU,EAAE,YAAY,CAAC,UAAU,EAAE,QAAQ,EAAE,WAAW,CAAC,QAAQ,EAAE,SAAS,EAAE,WAAW,CAAC,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,UAAU,EAAE,CAAC;SACrJ,IAAI,CAAC,WAAW,CAAC;SACjB,SAAS,CAAC,YAAY,EAAE,EAAE,CAAC,WAAW,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC;SAClE,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;SACrC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC;IACvC,OAAO,EAAE,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,OAAsB,EAAE,MAAc;IAClF,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,EAAE,CAAC;IACpC,MAAM,EAAE,GAAG,OAAO,CAAC,EAAe,CAAC;IACnC,MAAM,EAAE,CAAC,MAAM,CAAC,uBAAuB,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3F,2FAA2F;IAC3F,MAAM,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnE,MAAM,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACrE,OAAO,EAAE,EAAE,CAAC;AACd,CAAC;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAuB;IAC7D,cAAc,EAAE,uBAAuB;IACvC,cAAc,EAAE,uBAAuB;CACxC,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** Every valid token reads. */
2
+ export declare const MCP_READ_SCOPE = "mcp:read";
3
+ /** A token that may write: issued for writes, and the app allows them. */
4
+ export declare const MCP_WRITE_SCOPE = "mcp:write";
5
+ //# sourceMappingURL=scopes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scopes.d.ts","sourceRoot":"","sources":["../../src/server/scopes.ts"],"names":[],"mappings":"AAEA,+BAA+B;AAC/B,eAAO,MAAM,cAAc,aAAa,CAAC;AACzC,0EAA0E;AAC1E,eAAO,MAAM,eAAe,cAAc,CAAC"}
@@ -0,0 +1,6 @@
1
+ // The two scopes of the MCP endpoint, shared by the Bearer check and the OAuth server.
2
+ /** Every valid token reads. */
3
+ export const MCP_READ_SCOPE = "mcp:read";
4
+ /** A token that may write: issued for writes, and the app allows them. */
5
+ export const MCP_WRITE_SCOPE = "mcp:write";
6
+ //# sourceMappingURL=scopes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../src/server/scopes.ts"],"names":[],"mappings":"AAAA,uFAAuF;AAEvF,+BAA+B;AAC/B,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;AACzC,0EAA0E;AAC1E,MAAM,CAAC,MAAM,eAAe,GAAG,WAAW,CAAC"}
@@ -5,8 +5,13 @@ export type McpAccessContext = ModuleContext<Queryable>;
5
5
  /** Marks a token as this module's, for people and for secret scanners. */
6
6
  export declare const ACCESS_TOKEN_PREFIX = "sftmcp_";
7
7
  export declare function hashAccessToken(token: string): string;
8
- /** Whether a presented value could be a token at all; anything else is not looked up. */
8
+ /** Whether a presented value has the module's token shape. */
9
9
  export declare function isAccessTokenShape(token: string): boolean;
10
+ /** A new secret with the given prefix and its hash; the plaintext is returned once, never stored. */
11
+ export declare function createSecret(prefix: string): {
12
+ readonly secret: string;
13
+ readonly secretHash: string;
14
+ };
10
15
  export interface IssueAccessTokenInput {
11
16
  /** The auth user id of the owner, from the session. */
12
17
  readonly userId: string;
@@ -30,7 +35,10 @@ export type IssueAccessTokenResult = Ok<IssuedAccessToken> | Err<"mcp-access.nam
30
35
  * Database failures propagate.
31
36
  */
32
37
  export declare function issueAccessToken(ctx: McpAccessContext, input: IssueAccessTokenInput): Promise<IssueAccessTokenResult>;
33
- /** The owner's tokens, newest first, expired ones included (the list marks them). */
38
+ /**
39
+ * The owner's hand-issued tokens, newest first, expired ones included (the list marks them).
40
+ * OAuth access tokens are left out: the page shows their grants instead.
41
+ */
34
42
  export declare function listAccessTokens(ctx: McpAccessContext, userId: string): Promise<AccessTokenView[]>;
35
43
  export interface RevokeAccessTokenInput {
36
44
  /** The owner, from the session. */
@@ -39,8 +47,9 @@ export interface RevokeAccessTokenInput {
39
47
  readonly tokenId: string;
40
48
  }
41
49
  /**
42
- * Deletes a token at once; the next request with it gets 401. The owner is part of the `WHERE`,
43
- * so another account's id matches no row instead of relying on a comparison here.
50
+ * Deletes a hand-issued token at once; the next request with it gets 401. The owner is part of the
51
+ * `WHERE`, so another account's id matches no row instead of relying on a comparison here. An
52
+ * OAuth access token is revoked with its grant (`revokeOAuthGrant`).
44
53
  */
45
54
  export declare function revokeAccessToken(ctx: McpAccessContext, input: RevokeAccessTokenInput): Promise<Ok<undefined> | Err<"mcp-access.token_not_found">>;
46
55
  /** The identity behind a valid token. */
@@ -50,6 +59,8 @@ export interface VerifiedAccessToken {
50
59
  /** Effective write access: the token was issued for writes and the app allows them now. */
51
60
  readonly canWrite: boolean;
52
61
  readonly expiresAt: Date;
62
+ /** The OAuth grant the token was issued from; null for a hand-issued token. */
63
+ readonly grantId: string | null;
53
64
  }
54
65
  /**
55
66
  * The identity behind a token, or null for anything unknown, revoked or expired: the caller cannot
@@ -59,8 +70,9 @@ export interface VerifiedAccessToken {
59
70
  */
60
71
  export declare function verifyAccessToken(ctx: McpAccessContext, token: string): Promise<VerifiedAccessToken | null>;
61
72
  /**
62
- * Deletes every expired token, of every account. Issuing already removes the issuer's own; an app
63
- * may call this from a scheduled job so abandoned accounts do not keep dead rows.
73
+ * Deletes every expired token, of every account, hand-issued and OAuth alike. Issuing already
74
+ * removes the issuer's own; an app may call this from a scheduled job so abandoned accounts do not
75
+ * keep dead rows (with `pruneOAuthRecords` for codes, grants and clients).
64
76
  */
65
77
  export declare function pruneAccessTokens(ctx: McpAccessContext): Promise<number>;
66
78
  //# sourceMappingURL=tokens.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/server/tokens.ts"],"names":[],"mappings":"AAIA,OAAO,EAA0B,KAAK,GAAG,EAAE,KAAK,aAAa,EAAE,KAAK,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACjG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAKtD,MAAM,MAAM,gBAAgB,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;AAExD,0EAA0E;AAC1E,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAU7C,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,yFAAyF;AACzF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzD;AAOD,MAAM,WAAW,qBAAqB;IACpC,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;CAC1B;AAED,MAAM,MAAM,sBAAsB,GAC9B,EAAE,CAAC,iBAAiB,CAAC,GACrB,GAAG,CAAC,0BAA0B,GAAG,0BAA0B,GAAG,gCAAgC,CAAC,CAAC;AAEpG;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,gBAAgB,EAAE,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAwB3H;AAED,qFAAqF;AACrF,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAaxG;AAED,MAAM,WAAW,sBAAsB;IACrC,mCAAmC;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,gBAAgB,EACrB,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,EAAE,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,4BAA4B,CAAC,CAAC,CAO5D;AAED,yCAAyC;AACzC,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,2FAA2F;IAC3F,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAgBjH;AAeD;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAM9E"}
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/server/tokens.ts"],"names":[],"mappings":"AAIA,OAAO,EAA0B,KAAK,GAAG,EAAE,KAAK,aAAa,EAAE,KAAK,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACjG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAKtD,MAAM,MAAM,gBAAgB,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;AAExD,0EAA0E;AAC1E,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAU7C,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzD;AAYD,qGAAqG;AACrG,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAGrG;AAED,MAAM,WAAW,qBAAqB;IACpC,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;CAC1B;AAED,MAAM,MAAM,sBAAsB,GAC9B,EAAE,CAAC,iBAAiB,CAAC,GACrB,GAAG,CAAC,0BAA0B,GAAG,0BAA0B,GAAG,gCAAgC,CAAC,CAAC;AAEpG;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,gBAAgB,EAAE,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CA4B3H;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAaxG;AAED,MAAM,WAAW,sBAAsB;IACrC,mCAAmC;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,gBAAgB,EACrB,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,EAAE,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,4BAA4B,CAAC,CAAC,CAO5D;AAED,yCAAyC;AACzC,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,2FAA2F;IAC3F,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAwBjH;AAsBD;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAM9E"}
@@ -1,11 +1,11 @@
1
- // Access tokens (FIRE_TRACKER `src/db/access-tokens.ts`, generalised). A token is `sftmcp_` and 32
2
- // random bytes; the database holds only its sha256, so a leaked row cannot be replayed. The
3
- // plaintext leaves this file once, in the result of `issueAccessToken`.
1
+ // Access tokens (an adopting app's token code, generalised). A token is `sftmcp_` and 32 random
2
+ // bytes; the database holds only its sha256, so a leaked row cannot be replayed. The plaintext
3
+ // leaves this file once, in the result of `issueAccessToken` (or of an OAuth exchange, `oauth.ts`).
4
4
  import { createHash, randomBytes } from "node:crypto";
5
5
  import { err, errorLogLabel, ok } from "@softure-ai/core";
6
6
  import { and, count, desc, eq, gt, isNull, lt, lte, or, sql } from "drizzle-orm";
7
- import { MAX_TOKEN_NAME_LENGTH } from "../options.js";
8
- import { accessTokens } from "../schema.js";
7
+ import { MAX_PRESENTED_TOKEN_LENGTH, MAX_TOKEN_NAME_LENGTH } from "../options.js";
8
+ import { accessTokens, oauthGrants } from "../schema.js";
9
9
  import { getMcpAccessOptions } from "./options.js";
10
10
  /** Marks a token as this module's, for people and for secret scanners. */
11
11
  export const ACCESS_TOKEN_PREFIX = "sftmcp_";
@@ -19,13 +19,24 @@ const LAST_USED_RESOLUTION_MS = 60_000;
19
19
  export function hashAccessToken(token) {
20
20
  return createHash("sha256").update(token, "utf8").digest("hex");
21
21
  }
22
- /** Whether a presented value could be a token at all; anything else is not looked up. */
22
+ /** Whether a presented value has the module's token shape. */
23
23
  export function isAccessTokenShape(token) {
24
24
  return TOKEN_SHAPE.test(token);
25
25
  }
26
- function createAccessTokenSecret() {
27
- const token = `${ACCESS_TOKEN_PREFIX}${randomBytes(TOKEN_BYTES).toString("base64url")}`;
28
- return { token, tokenHash: hashAccessToken(token) };
26
+ /**
27
+ * Whether a presented value is looked up at all: the module's shape, or the app's
28
+ * `legacyTokenPattern` for tokens issued before the module. The length cap runs first, so no
29
+ * pattern ever sees a huge header.
30
+ */
31
+ function isAcceptedTokenShape(options, token) {
32
+ if (token.length > MAX_PRESENTED_TOKEN_LENGTH)
33
+ return false;
34
+ return isAccessTokenShape(token) || (options.legacyTokenPattern?.test(token) ?? false);
35
+ }
36
+ /** A new secret with the given prefix and its hash; the plaintext is returned once, never stored. */
37
+ export function createSecret(prefix) {
38
+ const secret = `${prefix}${randomBytes(TOKEN_BYTES).toString("base64url")}`;
39
+ return { secret, secretHash: hashAccessToken(secret) };
29
40
  }
30
41
  /**
31
42
  * Issues a token for its owner. The per-account limit counts unexpired tokens, in one transaction
@@ -46,10 +57,14 @@ export async function issueAccessToken(ctx, input) {
46
57
  return ctx.db.transaction(async (tx) => {
47
58
  await tx.execute(sql `select pg_advisory_xact_lock(hashtextextended(${`mcp-access:${input.userId}`}, 0))`);
48
59
  await tx.delete(accessTokens).where(and(eq(accessTokens.userId, input.userId), lte(accessTokens.expiresAt, now)));
49
- const [held] = await tx.select({ total: count() }).from(accessTokens).where(eq(accessTokens.userId, input.userId));
60
+ // OAuth access tokens belong to their grant and rotate every hour: they do not take places.
61
+ const [held] = await tx
62
+ .select({ total: count() })
63
+ .from(accessTokens)
64
+ .where(and(eq(accessTokens.userId, input.userId), isNull(accessTokens.grantId)));
50
65
  if ((held?.total ?? 0) >= options.maxTokensPerUser)
51
66
  return err("mcp-access.token_limit_reached");
52
- const { token, tokenHash } = createAccessTokenSecret();
67
+ const { secret: token, secretHash: tokenHash } = createSecret(ACCESS_TOKEN_PREFIX);
53
68
  const [row] = await tx
54
69
  .insert(accessTokens)
55
70
  .values({ userId: input.userId, name, tokenHash, canWrite, createdAt: now, expiresAt })
@@ -59,7 +74,10 @@ export async function issueAccessToken(ctx, input) {
59
74
  return ok({ id: row.id, token, name, canWrite, expiresAt });
60
75
  });
61
76
  }
62
- /** The owner's tokens, newest first, expired ones included (the list marks them). */
77
+ /**
78
+ * The owner's hand-issued tokens, newest first, expired ones included (the list marks them).
79
+ * OAuth access tokens are left out: the page shows their grants instead.
80
+ */
63
81
  export async function listAccessTokens(ctx, userId) {
64
82
  return ctx.db
65
83
  .select({
@@ -71,19 +89,20 @@ export async function listAccessTokens(ctx, userId) {
71
89
  lastUsedAt: accessTokens.lastUsedAt,
72
90
  })
73
91
  .from(accessTokens)
74
- .where(eq(accessTokens.userId, userId))
92
+ .where(and(eq(accessTokens.userId, userId), isNull(accessTokens.grantId)))
75
93
  .orderBy(desc(accessTokens.createdAt), desc(accessTokens.id));
76
94
  }
77
95
  /**
78
- * Deletes a token at once; the next request with it gets 401. The owner is part of the `WHERE`,
79
- * so another account's id matches no row instead of relying on a comparison here.
96
+ * Deletes a hand-issued token at once; the next request with it gets 401. The owner is part of the
97
+ * `WHERE`, so another account's id matches no row instead of relying on a comparison here. An
98
+ * OAuth access token is revoked with its grant (`revokeOAuthGrant`).
80
99
  */
81
100
  export async function revokeAccessToken(ctx, input) {
82
101
  if (!UUID_SHAPE.test(input.tokenId))
83
102
  return err("mcp-access.token_not_found");
84
103
  const removed = await ctx.db
85
104
  .delete(accessTokens)
86
- .where(and(eq(accessTokens.userId, input.userId), eq(accessTokens.id, input.tokenId)))
105
+ .where(and(eq(accessTokens.userId, input.userId), eq(accessTokens.id, input.tokenId), isNull(accessTokens.grantId)))
87
106
  .returning();
88
107
  return removed.length > 0 ? ok() : err("mcp-access.token_not_found");
89
108
  }
@@ -94,39 +113,55 @@ export async function revokeAccessToken(ctx, input) {
94
113
  * fail the request. Database failures of the lookup propagate.
95
114
  */
96
115
  export async function verifyAccessToken(ctx, token) {
97
- if (!isAccessTokenShape(token))
116
+ const options = getMcpAccessOptions(ctx.config);
117
+ if (!isAcceptedTokenShape(options, token))
98
118
  return null;
99
119
  const now = ctx.clock.now();
100
120
  const [row] = await ctx.db
101
- .select({ id: accessTokens.id, userId: accessTokens.userId, canWrite: accessTokens.canWrite, expiresAt: accessTokens.expiresAt })
121
+ .select({
122
+ id: accessTokens.id,
123
+ userId: accessTokens.userId,
124
+ canWrite: accessTokens.canWrite,
125
+ expiresAt: accessTokens.expiresAt,
126
+ grantId: accessTokens.grantId,
127
+ })
102
128
  .from(accessTokens)
103
129
  .where(and(eq(accessTokens.tokenHash, hashAccessToken(token)), gt(accessTokens.expiresAt, now)));
104
130
  if (row === undefined)
105
131
  return null;
106
- await recordUse(ctx, row.id, now);
132
+ await recordUse(ctx, row, now);
107
133
  return {
108
134
  tokenId: row.id,
109
135
  userId: row.userId,
110
- canWrite: row.canWrite && getMcpAccessOptions(ctx.config).allowWrites,
136
+ canWrite: row.canWrite && options.allowWrites,
111
137
  expiresAt: row.expiresAt,
138
+ grantId: row.grantId,
112
139
  };
113
140
  }
114
- async function recordUse(ctx, tokenId, now) {
141
+ /** Records the use on the token and, for an OAuth token, on its grant (the connected apps list). */
142
+ async function recordUse(ctx, token, now) {
115
143
  const staleBefore = new Date(now.getTime() - LAST_USED_RESOLUTION_MS);
116
144
  try {
117
145
  await ctx.db
118
146
  .update(accessTokens)
119
147
  .set({ lastUsedAt: now })
120
- .where(and(eq(accessTokens.id, tokenId), or(isNull(accessTokens.lastUsedAt), lt(accessTokens.lastUsedAt, staleBefore))));
148
+ .where(and(eq(accessTokens.id, token.id), or(isNull(accessTokens.lastUsedAt), lt(accessTokens.lastUsedAt, staleBefore))));
149
+ if (token.grantId !== null) {
150
+ await ctx.db
151
+ .update(oauthGrants)
152
+ .set({ lastUsedAt: now })
153
+ .where(and(eq(oauthGrants.id, token.grantId), or(isNull(oauthGrants.lastUsedAt), lt(oauthGrants.lastUsedAt, staleBefore))));
154
+ }
121
155
  }
122
156
  catch (error) {
123
157
  // The token id is not a secret; the token is never logged.
124
- console.error(`@softure-ai/mcp-access: recording the use of token ${tokenId} failed: ${errorLogLabel(error)}`);
158
+ console.error(`@softure-ai/mcp-access: recording the use of token ${token.id} failed: ${errorLogLabel(error)}`);
125
159
  }
126
160
  }
127
161
  /**
128
- * Deletes every expired token, of every account. Issuing already removes the issuer's own; an app
129
- * may call this from a scheduled job so abandoned accounts do not keep dead rows.
162
+ * Deletes every expired token, of every account, hand-issued and OAuth alike. Issuing already
163
+ * removes the issuer's own; an app may call this from a scheduled job so abandoned accounts do not
164
+ * keep dead rows (with `pruneOAuthRecords` for codes, grants and clients).
130
165
  */
131
166
  export async function pruneAccessTokens(ctx) {
132
167
  const removed = await ctx.db
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/server/tokens.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/server/tokens.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { type ClassNames } from "@softure-ai/ui";
2
+ import type { McpAccessMessages } from "../messages/index.js";
3
+ export type ConsentFormSlot = "root" | "details" | "term" | "value" | "note" | "actions" | "text" | "link";
4
+ export interface ConsentFormProps {
5
+ /** The decision route the form posts to. */
6
+ readonly action: string;
7
+ /** The authorization request's parameters, carried in hidden fields. */
8
+ readonly params: readonly (readonly [string, string])[];
9
+ readonly clientName: string;
10
+ /** Where the code goes: the host for http(s), the whole URI for a native scheme. */
11
+ readonly redirectTarget: string;
12
+ readonly accountEmail: string;
13
+ /** Offer the change checkbox: the client asked for `mcp:write` and the app allows writes. */
14
+ readonly isWriteOffered: boolean;
15
+ readonly messages: McpAccessMessages;
16
+ readonly classNames?: ClassNames<ConsentFormSlot>;
17
+ readonly unstyled?: boolean;
18
+ }
19
+ export declare function ConsentForm({ action, params, clientName, redirectTarget, accountEmail, isWriteOffered, messages, classNames, unstyled }: ConsentFormProps): import("react").JSX.Element;
20
+ export interface ConsentErrorProps {
21
+ readonly message: string;
22
+ /** The client's redirect URI with the error, when the client was confirmed; a link, never automatic. */
23
+ readonly backLocation?: string;
24
+ readonly backTarget?: string;
25
+ readonly messages: McpAccessMessages;
26
+ readonly classNames?: ClassNames<ConsentFormSlot>;
27
+ readonly unstyled?: boolean;
28
+ }
29
+ /** Why the request cannot go on. Nothing was granted; going back to the client is the person's click. */
30
+ export declare function ConsentError({ message, backLocation, backTarget, messages, classNames, unstyled }: ConsentErrorProps): import("react").JSX.Element;
31
+ //# sourceMappingURL=consent-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consent-form.d.ts","sourceRoot":"","sources":["../../src/ui/consent-form.tsx"],"names":[],"mappings":"AACA,OAAO,EAAoB,KAAK,UAAU,EAAyB,MAAM,gBAAgB,CAAC;AAC1F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAQ9D,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAE3G,MAAM,WAAW,gBAAgB;IAC/B,4CAA4C;IAC5C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;IACxD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,oFAAoF;IACpF,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,6FAA6F;IAC7F,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC,eAAe,CAAC,CAAC;IAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAaD,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+BAmCzJ;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wGAAwG;IACxG,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC,eAAe,CAAC,CAAC;IAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,yGAAyG;AACzG,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,iBAAiB,+BAepH"}
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { formatMessage } from "@softure-ai/core";
3
+ import { Button, Checkbox, createSlotClassGetter } from "@softure-ai/ui";
4
+ const DEFAULT_CLASSES = {
5
+ root: "sft:flex sft:flex-col sft:gap-4 sft:font-sans",
6
+ details: "sft:m-0 sft:flex sft:flex-col sft:gap-3 sft:text-sm",
7
+ term: "sft:text-muted",
8
+ value: "sft:m-0 sft:font-medium sft:text-foreground",
9
+ note: "sft:font-normal sft:text-muted",
10
+ actions: "sft:flex sft:flex-col sft:gap-3",
11
+ text: "sft:m-0 sft:text-sm sft:text-muted",
12
+ link: "sft:font-medium sft:text-accent",
13
+ };
14
+ export function ConsentForm({ action, params, clientName, redirectTarget, accountEmail, isWriteOffered, messages, classNames, unstyled }) {
15
+ const slot = createSlotClassGetter({ defaults: DEFAULT_CLASSES, classNames, unstyled });
16
+ const copy = messages.consent;
17
+ return (_jsxs("form", { method: "post", action: action, className: slot("root"), children: [params.map(([name, value]) => (_jsx("input", { type: "hidden", name: name, value: value }, name))), _jsxs("dl", { className: slot("details"), children: [_jsxs("div", { children: [_jsx("dt", { className: slot("term"), children: copy.app }), _jsxs("dd", { className: slot("value"), children: [clientName, " ", _jsxs("span", { className: slot("note"), children: ["(", formatMessage(copy.returnsTo, { target: redirectTarget }), ")"] })] })] }), _jsxs("div", { children: [_jsx("dt", { className: slot("term"), children: copy.account }), _jsx("dd", { className: slot("value"), children: accountEmail })] }), _jsxs("div", { children: [_jsx("dt", { className: slot("term"), children: copy.access }), _jsx("dd", { className: slot("value"), children: copy.readAccess })] })] }), isWriteOffered ? _jsx(Checkbox, { name: "canWrite", label: copy.allowWrite, description: copy.allowWriteHint, unstyled: unstyled }) : null, _jsxs("div", { className: slot("actions"), children: [_jsx(Button, { type: "submit", name: "decision", value: "allow", variant: "primary", size: "lg", fullWidth: true, unstyled: unstyled, children: copy.allow }), _jsx(Button, { type: "submit", name: "decision", value: "deny", variant: "secondary", size: "lg", fullWidth: true, unstyled: unstyled, children: copy.deny })] })] }));
18
+ }
19
+ /** Why the request cannot go on. Nothing was granted; going back to the client is the person's click. */
20
+ export function ConsentError({ message, backLocation, backTarget, messages, classNames, unstyled }) {
21
+ const slot = createSlotClassGetter({ defaults: DEFAULT_CLASSES, classNames, unstyled });
22
+ return (_jsxs("div", { className: slot("root"), children: [_jsx("p", { className: slot("text"), children: message }), _jsx("p", { className: slot("text"), children: messages.consent.nothingGranted }), backLocation === undefined || backTarget === undefined ? null : (_jsx("p", { className: slot("text"), children: _jsx("a", { href: backLocation, className: slot("link"), children: formatMessage(messages.consent.backToApp, { target: backTarget }) }) }))] }));
23
+ }
24
+ //# sourceMappingURL=consent-form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consent-form.js","sourceRoot":"","sources":["../../src/ui/consent-form.tsx"],"names":[],"mappings":"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"}
@@ -1,2 +1,3 @@
1
- export { TokenManager, type IssueTokenAction, type RevokeTokenAction, type TokenManagerProps, type TokenManagerRow, type TokenManagerSlot, type TokenManagerTool, } from "./token-manager.js";
1
+ export { ConsentError, ConsentForm, type ConsentErrorProps, type ConsentFormProps, type ConsentFormSlot } from "./consent-form.js";
2
+ export { TokenManager, type RevokeGrantAction, type TokenManagerGrant, type IssueTokenAction, type RevokeTokenAction, type TokenManagerProps, type TokenManagerRow, type TokenManagerSlot, type TokenManagerTool, } from "./token-manager.js";
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACnI,OAAO,EACL,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC"}
package/dist/ui/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // The token manager, to compose into the app's own page. It takes the server actions from
2
2
  // `@softure-ai/mcp-access/next` as props: no `next/*` import here (docs/02-module-standard.md §5).
3
+ export { ConsentError, ConsentForm } from "./consent-form.js";
3
4
  export { TokenManager, } from "./token-manager.js";
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,mGAAmG;AACnG,OAAO,EACL,YAAY,GAOb,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,mGAAmG;AACnG,OAAO,EAAE,YAAY,EAAE,WAAW,EAAuE,MAAM,mBAAmB,CAAC;AACnI,OAAO,EACL,YAAY,GASb,MAAM,oBAAoB,CAAC"}
@@ -1,9 +1,10 @@
1
1
  import { type Locale } from "@softure-ai/core";
2
2
  import { type ClassNames } from "@softure-ai/ui";
3
- import type { IssueTokenFormState, RevokeTokenFormState } from "../contract.js";
3
+ import type { IssueTokenFormState, RevokeGrantFormState, RevokeTokenFormState } from "../contract.js";
4
4
  import { type McpAccessMessages } from "../messages/index.js";
5
5
  export type IssueTokenAction = (previous: IssueTokenFormState, formData: FormData) => Promise<IssueTokenFormState>;
6
6
  export type RevokeTokenAction = (previous: RevokeTokenFormState, formData: FormData) => Promise<RevokeTokenFormState>;
7
+ export type RevokeGrantAction = (previous: RevokeGrantFormState, formData: FormData) => Promise<RevokeGrantFormState>;
7
8
  export type TokenManagerSlot = "root" | "section" | "heading" | "text" | "list" | "item" | "itemHeader" | "badge" | "details" | "snippet" | "code";
8
9
  /** One tool of the app's catalog, prepared on the server. */
9
10
  export interface TokenManagerTool {
@@ -24,6 +25,15 @@ export interface TokenManagerRow {
24
25
  /** Created, valid until and last used, already formatted. */
25
26
  readonly details: readonly string[];
26
27
  }
28
+ /** One app connected through OAuth, prepared on the server. */
29
+ export interface TokenManagerGrant {
30
+ readonly id: string;
31
+ readonly clientName: string;
32
+ /** Read only, or read and change, already in the app's copy. */
33
+ readonly scopeText: string;
34
+ /** Connected on and last used, already formatted. */
35
+ readonly details: readonly string[];
36
+ }
27
37
  export interface TokenManagerProps {
28
38
  readonly tools: readonly TokenManagerTool[];
29
39
  readonly tokens: readonly TokenManagerRow[];
@@ -33,11 +43,15 @@ export interface TokenManagerProps {
33
43
  readonly maxTokens: number;
34
44
  readonly issueAction: IssueTokenAction;
35
45
  readonly revokeAction: RevokeTokenAction;
46
+ /** The connected apps; leave out while OAuth is off, and the section is not shown. */
47
+ readonly grants?: readonly TokenManagerGrant[];
48
+ /** Disconnects one app; needed with `grants`. */
49
+ readonly revokeGrantAction?: RevokeGrantAction;
36
50
  readonly messages: McpAccessMessages;
37
51
  /** Locale of the built-in copy of the ui primitives. */
38
52
  readonly locale?: Locale;
39
53
  readonly classNames?: ClassNames<TokenManagerSlot>;
40
54
  readonly unstyled?: boolean;
41
55
  }
42
- export declare function TokenManager({ tools, tokens, allowWrites, maxTokens, issueAction, revokeAction, messages, locale, classNames, unstyled }: TokenManagerProps): import("react").JSX.Element;
56
+ export declare function TokenManager({ tools, tokens, allowWrites, maxTokens, issueAction, revokeAction, grants, revokeGrantAction, messages, locale, classNames, unstyled, }: TokenManagerProps): import("react").JSX.Element;
43
57
  //# sourceMappingURL=token-manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"token-manager.d.ts","sourceRoot":"","sources":["../../src/ui/token-manager.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAgC,KAAK,UAAU,EAA+C,MAAM,gBAAgB,CAAC;AAE5H,OAAO,KAAK,EAAe,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC7F,OAAO,EAAwB,KAAK,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AASpF,MAAM,MAAM,gBAAgB,GAAG,CAAC,QAAQ,EAAE,mBAAmB,EAAE,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;AACnH,MAAM,MAAM,iBAAiB,GAAG,CAAC,QAAQ,EAAE,oBAAoB,EAAE,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;AAEtH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,YAAY,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;AAEnJ,6DAA6D;AAC7D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,yDAAyD;AACzD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,8CAA8C;IAC9C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;IAC5C,wDAAwD;IACxD,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,QAAQ,CAAC,YAAY,EAAE,iBAAiB,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,wDAAwD;IACxD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC,gBAAgB,CAAC,CAAC;IACnD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAiKD,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,iBAAiB,+BAqC3J"}
1
+ {"version":3,"file":"token-manager.d.ts","sourceRoot":"","sources":["../../src/ui/token-manager.tsx"],"names":[],"mappings":"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"}