@softure-ai/mcp-access 0.0.0-stage → 0.1.6

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 (122) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +177 -2
  4. package/dist/client-setup.d.ts +32 -0
  5. package/dist/client-setup.d.ts.map +1 -0
  6. package/dist/client-setup.js +57 -0
  7. package/dist/client-setup.js.map +1 -0
  8. package/dist/contract.d.ts +80 -0
  9. package/dist/contract.d.ts.map +1 -0
  10. package/dist/contract.js +2 -0
  11. package/dist/contract.js.map +1 -0
  12. package/dist/index.d.ts +119 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +50 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/messages/en.d.ts +77 -0
  17. package/dist/messages/en.d.ts.map +1 -0
  18. package/dist/messages/en.js +77 -0
  19. package/dist/messages/en.js.map +1 -0
  20. package/dist/messages/index.d.ts +161 -0
  21. package/dist/messages/index.d.ts.map +1 -0
  22. package/dist/messages/index.js +14 -0
  23. package/dist/messages/index.js.map +1 -0
  24. package/dist/messages/pl.d.ts +3 -0
  25. package/dist/messages/pl.d.ts.map +1 -0
  26. package/dist/messages/pl.js +77 -0
  27. package/dist/messages/pl.js.map +1 -0
  28. package/dist/next/actions.d.ts +6 -0
  29. package/dist/next/actions.d.ts.map +1 -0
  30. package/dist/next/actions.js +88 -0
  31. package/dist/next/actions.js.map +1 -0
  32. package/dist/next/context.d.ts +4 -0
  33. package/dist/next/context.d.ts.map +1 -0
  34. package/dist/next/context.js +13 -0
  35. package/dist/next/context.js.map +1 -0
  36. package/dist/next/format.d.ts +4 -0
  37. package/dist/next/format.d.ts.map +1 -0
  38. package/dist/next/format.js +7 -0
  39. package/dist/next/format.js.map +1 -0
  40. package/dist/next/index.d.ts +4 -0
  41. package/dist/next/index.d.ts.map +1 -0
  42. package/dist/next/index.js +6 -0
  43. package/dist/next/index.js.map +1 -0
  44. package/dist/next/pages.d.ts +2 -0
  45. package/dist/next/pages.d.ts.map +1 -0
  46. package/dist/next/pages.js +61 -0
  47. package/dist/next/pages.js.map +1 -0
  48. package/dist/next/route.d.ts +3 -0
  49. package/dist/next/route.d.ts.map +1 -0
  50. package/dist/next/route.js +24 -0
  51. package/dist/next/route.js.map +1 -0
  52. package/dist/options.d.ts +43 -0
  53. package/dist/options.d.ts.map +1 -0
  54. package/dist/options.js +47 -0
  55. package/dist/options.js.map +1 -0
  56. package/dist/schema.d.ts +145 -0
  57. package/dist/schema.d.ts.map +1 -0
  58. package/dist/schema.js +18 -0
  59. package/dist/schema.js.map +1 -0
  60. package/dist/server/endpoint.d.ts +26 -0
  61. package/dist/server/endpoint.d.ts.map +1 -0
  62. package/dist/server/endpoint.js +107 -0
  63. package/dist/server/endpoint.js.map +1 -0
  64. package/dist/server/health.d.ts +3 -0
  65. package/dist/server/health.d.ts.map +1 -0
  66. package/dist/server/health.js +11 -0
  67. package/dist/server/health.js.map +1 -0
  68. package/dist/server/index.d.ts +6 -0
  69. package/dist/server/index.d.ts.map +1 -0
  70. package/dist/server/index.js +8 -0
  71. package/dist/server/index.js.map +1 -0
  72. package/dist/server/options.d.ts +20 -0
  73. package/dist/server/options.d.ts.map +1 -0
  74. package/dist/server/options.js +40 -0
  75. package/dist/server/options.js.map +1 -0
  76. package/dist/server/privacy.d.ts +15 -0
  77. package/dist/server/privacy.d.ts.map +1 -0
  78. package/dist/server/privacy.js +36 -0
  79. package/dist/server/privacy.js.map +1 -0
  80. package/dist/server/tokens.d.ts +66 -0
  81. package/dist/server/tokens.d.ts.map +1 -0
  82. package/dist/server/tokens.js +138 -0
  83. package/dist/server/tokens.js.map +1 -0
  84. package/dist/token-status.d.ts +9 -0
  85. package/dist/token-status.d.ts.map +1 -0
  86. package/dist/token-status.js +14 -0
  87. package/dist/token-status.js.map +1 -0
  88. package/dist/ui/index.d.ts +2 -0
  89. package/dist/ui/index.d.ts.map +1 -0
  90. package/dist/ui/index.js +4 -0
  91. package/dist/ui/index.js.map +1 -0
  92. package/dist/ui/token-manager.d.ts +43 -0
  93. package/dist/ui/token-manager.d.ts.map +1 -0
  94. package/dist/ui/token-manager.js +50 -0
  95. package/dist/ui/token-manager.js.map +1 -0
  96. package/migrations/0001_create_access_tokens.sql +24 -0
  97. package/module.json +15 -0
  98. package/package.json +69 -4
  99. package/src/client-setup.ts +71 -0
  100. package/src/contract.ts +80 -0
  101. package/src/index.ts +81 -0
  102. package/src/messages/en.ts +76 -0
  103. package/src/messages/index.ts +18 -0
  104. package/src/messages/pl.ts +78 -0
  105. package/src/next/actions.ts +91 -0
  106. package/src/next/context.ts +14 -0
  107. package/src/next/format.ts +10 -0
  108. package/src/next/index.ts +5 -0
  109. package/src/next/next-modules.d.ts +16 -0
  110. package/src/next/pages.tsx +80 -0
  111. package/src/next/route.ts +24 -0
  112. package/src/options.ts +58 -0
  113. package/src/schema.ts +19 -0
  114. package/src/server/endpoint.ts +141 -0
  115. package/src/server/health.ts +12 -0
  116. package/src/server/index.ts +36 -0
  117. package/src/server/options.ts +54 -0
  118. package/src/server/privacy.ts +49 -0
  119. package/src/server/tokens.ts +188 -0
  120. package/src/token-status.ts +27 -0
  121. package/src/ui/index.ts +11 -0
  122. package/src/ui/token-manager.tsx +254 -0
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // The token page, ready to mount with one line:
3
+ // `export { McpAccessPage as default } from "@softure-ai/mcp-access/next"`.
4
+ // A server component for the signed-in user; without a session it redirects to the login page and
5
+ // back. Everything the client part shows is prepared here, so dates are formatted once, in the
6
+ // app's locale and time zone, and never differ between the server render and hydration.
7
+ import { requireUser } from "@softure-ai/auth/next";
8
+ import { formatMessage } from "@softure-ai/core";
9
+ import { getSoftureConfig } from "@softure-ai/core/next";
10
+ import { Card } from "@softure-ai/ui";
11
+ import { getLocalizedText, getMcpAccessMessages, getMcpAccessOptions, getMcpAccessRoutes } from "../server/options.js";
12
+ import { listAccessTokens } from "../server/tokens.js";
13
+ import { getAccessTokenStatus } from "../token-status.js";
14
+ import { TokenManager } from "../ui/token-manager.js";
15
+ import { issueTokenAction, revokeTokenAction } from "./actions.js";
16
+ import { getMcpAccessContext } from "./context.js";
17
+ import { formatDate, formatDateTime } from "./format.js";
18
+ const LAYOUT_CLASS = "sft:mx-auto sft:box-border sft:w-full sft:sm:max-w-3xl sft:px-4 sft:py-4";
19
+ function describeStatus(status, messages) {
20
+ switch (status.kind) {
21
+ case "active":
22
+ return messages.list.active;
23
+ case "expired":
24
+ return messages.list.expired;
25
+ case "expiring":
26
+ if (status.daysLeft === 0)
27
+ return messages.list.expiresToday;
28
+ if (status.daysLeft === 1)
29
+ return messages.list.expiresTomorrow;
30
+ return formatMessage(messages.list.expiresInDays, { days: status.daysLeft });
31
+ }
32
+ }
33
+ function toRow(token, now, config, options, messages) {
34
+ const status = getAccessTokenStatus(token.expiresAt, now, { timezone: config.timezone, warningDays: options.expiryWarningDays });
35
+ const isExpired = status.kind === "expired";
36
+ const expiry = formatMessage(isExpired ? messages.list.expiredOn : messages.list.expiresOn, { date: formatDate(config, token.expiresAt) });
37
+ const lastUse = token.lastUsedAt === null ? messages.list.neverUsed : formatMessage(messages.list.lastUsed, { date: formatDateTime(config, token.lastUsedAt) });
38
+ return {
39
+ id: token.id,
40
+ name: token.name,
41
+ scopeText: token.canWrite && options.allowWrites ? messages.list.readWrite : messages.list.readOnly,
42
+ statusText: describeStatus(status, messages),
43
+ isExpired,
44
+ details: [formatMessage(messages.list.createdOn, { date: formatDate(config, token.createdAt) }), expiry, lastUse],
45
+ };
46
+ }
47
+ function toTool(tool, config, options, messages) {
48
+ const accessText = tool.access === "read" ? messages.tools.read : options.allowWrites ? messages.tools.write : messages.tools.writeUnavailable;
49
+ return { name: tool.name, description: getLocalizedText(tool.description, config.locale), accessText };
50
+ }
51
+ export async function McpAccessPage() {
52
+ const config = getSoftureConfig();
53
+ const user = await requireUser({ next: getMcpAccessRoutes(config).page });
54
+ const options = getMcpAccessOptions(config);
55
+ const messages = getMcpAccessMessages(config);
56
+ const ctx = await getMcpAccessContext(config);
57
+ const now = ctx.clock.now();
58
+ const tokens = await listAccessTokens(ctx, user.id);
59
+ return (_jsx("main", { className: LAYOUT_CLASS, children: _jsx(Card, { title: messages.page.title, subtitle: messages.page.lead, children: _jsx(TokenManager, { tools: options.tools.map((tool) => toTool(tool, config, options, messages)), tokens: tokens.map((token) => toRow(token, now, config, options, messages)), allowWrites: options.allowWrites, maxTokens: options.maxTokensPerUser, issueAction: issueTokenAction, revokeAction: revokeTokenAction, messages: messages, locale: config.locale }) }) }));
60
+ }
61
+ //# sourceMappingURL=pages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pages.js","sourceRoot":"","sources":["../../src/next/pages.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { type McpEndpointOptions } from "../server/endpoint.js";
2
+ export declare function createMcpRoute(options: McpEndpointOptions): (request: Request) => Promise<Response>;
3
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/next/route.ts"],"names":[],"mappings":"AAMA,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAKnF,wBAAgB,cAAc,CAAC,OAAO,EAAE,kBAAkB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAYnG"}
@@ -0,0 +1,24 @@
1
+ // `POST /api/mcp`, built around the app's MCP server factory and mounted with one line:
2
+ // `app/api/mcp/route.ts → export const POST = createMcpRoute({ createServer });`
3
+ // The factory lives in the app, next to its tools; the module brings the rate limit, the token
4
+ // check and the identity the factory receives. GET and DELETE are not exported, so Next answers
5
+ // them with 405: the endpoint is stateless and keeps no sessions.
6
+ import { errorLogLabel } from "@softure-ai/core";
7
+ import { createMcpEndpoint } from "../server/endpoint.js";
8
+ import { getMcpAccessContext } from "./context.js";
9
+ const NO_STORE = { "cache-control": "no-store" };
10
+ export function createMcpRoute(options) {
11
+ const endpoint = createMcpEndpoint(options);
12
+ return async (request) => {
13
+ let ctx;
14
+ try {
15
+ ctx = await getMcpAccessContext();
16
+ }
17
+ catch (error) {
18
+ console.error(`@softure-ai/mcp-access: opening the database failed: ${errorLogLabel(error)}`);
19
+ return Response.json({ error: "temporarily_unavailable" }, { status: 503, headers: NO_STORE });
20
+ }
21
+ return endpoint(ctx, request);
22
+ };
23
+ }
24
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.js","sourceRoot":"","sources":["../../src/next/route.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,iFAAiF;AACjF,+FAA+F;AAC/F,gGAAgG;AAChG,kEAAkE;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAA2B,MAAM,uBAAuB,CAAC;AACnF,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,MAAM,QAAQ,GAAG,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC;AAEjD,MAAM,UAAU,cAAc,CAAC,OAA2B;IACxD,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE;QACvB,IAAI,GAAG,CAAC;QACR,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,mBAAmB,EAAE,CAAC;QACpC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,wDAAwD,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC9F,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;QACjG,CAAC;QACD,OAAO,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAChC,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,43 @@
1
+ import { z } from "zod";
2
+ /** The name an MCP client lists the server under: no spaces, so a shell command needs no quotes. */
3
+ export declare const SERVER_NAME_PATTERN: RegExp;
4
+ /** The MCP rule for tool names: 1 to 128 of A-Z, a-z, 0-9, `_`, `-` and `.`. */
5
+ export declare const TOOL_NAME_PATTERN: RegExp;
6
+ /** The longest token name the table takes (`access_tokens.name`). */
7
+ export declare const MAX_TOKEN_NAME_LENGTH = 60;
8
+ declare const toolSchema: z.ZodObject<{
9
+ name: z.ZodString;
10
+ access: z.ZodEnum<{
11
+ read: "read";
12
+ write: "write";
13
+ }>;
14
+ description: z.ZodRecord<z.ZodEnum<{
15
+ en: "en";
16
+ pl: "pl";
17
+ }> & z.core.$partial, z.ZodString>;
18
+ }, z.core.$strict>;
19
+ export declare const mcpAccessOptionsSchema: z.ZodObject<{
20
+ serverName: z.ZodString;
21
+ tools: z.ZodDefault<z.ZodArray<z.ZodObject<{
22
+ name: z.ZodString;
23
+ access: z.ZodEnum<{
24
+ read: "read";
25
+ write: "write";
26
+ }>;
27
+ description: z.ZodRecord<z.ZodEnum<{
28
+ en: "en";
29
+ pl: "pl";
30
+ }> & z.core.$partial, z.ZodString>;
31
+ }, z.core.$strict>>>;
32
+ allowWrites: z.ZodDefault<z.ZodBoolean>;
33
+ tokenLifetimeDays: z.ZodDefault<z.ZodInt>;
34
+ maxTokensPerUser: z.ZodDefault<z.ZodInt>;
35
+ expiryWarningDays: z.ZodDefault<z.ZodInt>;
36
+ }, z.core.$strict>;
37
+ export type McpAccessOptionsInput = z.input<typeof mcpAccessOptionsSchema>;
38
+ export type McpAccessOptions = z.output<typeof mcpAccessOptionsSchema>;
39
+ export type McpToolDefinitionInput = z.input<typeof toolSchema>;
40
+ export type McpToolDefinition = z.output<typeof toolSchema>;
41
+ export type McpToolAccess = McpToolDefinition["access"];
42
+ export {};
43
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,oGAAoG;AACpG,eAAO,MAAM,mBAAmB,QAA2C,CAAC;AAE5E,gFAAgF;AAChF,eAAO,MAAM,iBAAiB,QAA4B,CAAC;AAE3D,qEAAqE;AACrE,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAKxC,QAAA,MAAM,UAAU;;;;;;;;;;kBAUd,CAAC;AAEH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;kBAuB/B,CAAC;AAEL,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAC3E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,sBAAsB,CAAC,CAAC;AACvE,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAChE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAC5D,MAAM,MAAM,aAAa,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC"}
@@ -0,0 +1,47 @@
1
+ // The options an app passes to `mcpAccess({ ... })` in softure.config.ts, parsed at startup.
2
+ import { LOCALES } from "@softure-ai/core";
3
+ import { z } from "zod";
4
+ /** The name an MCP client lists the server under: no spaces, so a shell command needs no quotes. */
5
+ export const SERVER_NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
6
+ /** The MCP rule for tool names: 1 to 128 of A-Z, a-z, 0-9, `_`, `-` and `.`. */
7
+ export const TOOL_NAME_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
8
+ /** The longest token name the table takes (`access_tokens.name`). */
9
+ export const MAX_TOKEN_NAME_LENGTH = 60;
10
+ /** Copy per locale; a locale without its own text falls back to `en`. */
11
+ const localizedTextSchema = z.partialRecord(z.enum(LOCALES), z.string().trim().min(1).max(500));
12
+ const toolSchema = z.strictObject({
13
+ /** The tool's name as the app's MCP server registers it. */
14
+ name: z.string().regex(TOOL_NAME_PATTERN, "must be 1 to 128 of A-Z, a-z, 0-9, _, - and ."),
15
+ /**
16
+ * `write` tools change data: the app registers them only for a token that can write. They stay
17
+ * listed while `allowWrites` is off, marked as unavailable.
18
+ */
19
+ access: z.enum(["read", "write"]),
20
+ /** What the tool does, for the owner deciding which token to issue. */
21
+ description: localizedTextSchema.refine((text) => text.en !== undefined, "needs at least an en text"),
22
+ });
23
+ export const mcpAccessOptionsSchema = z
24
+ .strictObject({
25
+ /** The name MCP clients list the server under, e.g. "acme". Used in the setup instructions. */
26
+ serverName: z.string().regex(SERVER_NAME_PATTERN, "must be lowercase letters, digits and inner dashes, at most 64 characters"),
27
+ /** The app's tool catalog, shown on the token page. The MCP server itself comes from the route. */
28
+ tools: z.array(toolSchema).default([]),
29
+ /** Lets write tokens be issued and used. Off, every token reads only, write tokens included. */
30
+ allowWrites: z.boolean().default(false),
31
+ /** How long a token works. A choice: longer than a session, never forever. */
32
+ tokenLifetimeDays: z.int().min(1).max(365).default(90),
33
+ /** How many unexpired tokens one account may hold. */
34
+ maxTokensPerUser: z.int().min(1).max(100).default(20),
35
+ /** From how many calendar days before its expiry the page warns about a token. */
36
+ expiryWarningDays: z.int().min(0).max(60).default(14),
37
+ })
38
+ .superRefine((options, context) => {
39
+ const names = new Set();
40
+ options.tools.forEach((tool, index) => {
41
+ if (names.has(tool.name)) {
42
+ context.addIssue({ code: "custom", path: ["tools", index, "name"], message: `"${tool.name}" is listed twice` });
43
+ }
44
+ names.add(tool.name);
45
+ });
46
+ });
47
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,oGAAoG;AACpG,MAAM,CAAC,MAAM,mBAAmB,GAAG,wCAAwC,CAAC;AAE5E,gFAAgF;AAChF,MAAM,CAAC,MAAM,iBAAiB,GAAG,yBAAyB,CAAC;AAE3D,qEAAqE;AACrE,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAExC,yEAAyE;AACzE,MAAM,mBAAmB,GAAG,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AAEhG,MAAM,UAAU,GAAG,CAAC,CAAC,YAAY,CAAC;IAChC,4DAA4D;IAC5D,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,iBAAiB,EAAE,+CAA+C,CAAC;IAC1F;;;OAGG;IACH,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,uEAAuE;IACvE,WAAW,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,SAAS,EAAE,2BAA2B,CAAC;CACtG,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC;KACpC,YAAY,CAAC;IACZ,+FAA+F;IAC/F,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,mBAAmB,EAAE,2EAA2E,CAAC;IAC9H,mGAAmG;IACnG,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACtC,gGAAgG;IAChG,WAAW,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACvC,8EAA8E;IAC9E,iBAAiB,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACtD,sDAAsD;IACtD,gBAAgB,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACrD,kFAAkF;IAClF,iBAAiB,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CACtD,CAAC;KACD,WAAW,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE;IAChC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QACpC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,mBAAmB,EAAE,CAAC,CAAC;QAClH,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,145 @@
1
+ export declare const mcpSchema: import("drizzle-orm/pg-core").PgSchema<"mcp">;
2
+ export declare const accessTokens: import("drizzle-orm/pg-core").PgTableWithColumns<{
3
+ name: "access_tokens";
4
+ schema: "mcp";
5
+ columns: {
6
+ id: import("drizzle-orm/pg-core").PgColumn<{
7
+ name: "id";
8
+ tableName: "access_tokens";
9
+ dataType: "string";
10
+ columnType: "PgUUID";
11
+ data: string;
12
+ driverParam: string;
13
+ notNull: true;
14
+ hasDefault: true;
15
+ isPrimaryKey: true;
16
+ isAutoincrement: false;
17
+ hasRuntimeDefault: false;
18
+ enumValues: undefined;
19
+ baseColumn: never;
20
+ identity: undefined;
21
+ generated: undefined;
22
+ }, {}, {}>;
23
+ userId: import("drizzle-orm/pg-core").PgColumn<{
24
+ name: "user_id";
25
+ tableName: "access_tokens";
26
+ dataType: "string";
27
+ columnType: "PgUUID";
28
+ data: string;
29
+ driverParam: string;
30
+ notNull: true;
31
+ hasDefault: false;
32
+ isPrimaryKey: false;
33
+ isAutoincrement: false;
34
+ hasRuntimeDefault: false;
35
+ enumValues: undefined;
36
+ baseColumn: never;
37
+ identity: undefined;
38
+ generated: undefined;
39
+ }, {}, {}>;
40
+ name: import("drizzle-orm/pg-core").PgColumn<{
41
+ name: "name";
42
+ tableName: "access_tokens";
43
+ dataType: "string";
44
+ columnType: "PgText";
45
+ data: string;
46
+ driverParam: string;
47
+ notNull: true;
48
+ hasDefault: false;
49
+ isPrimaryKey: false;
50
+ isAutoincrement: false;
51
+ hasRuntimeDefault: false;
52
+ enumValues: [string, ...string[]];
53
+ baseColumn: never;
54
+ identity: undefined;
55
+ generated: undefined;
56
+ }, {}, {}>;
57
+ tokenHash: import("drizzle-orm/pg-core").PgColumn<{
58
+ name: "token_hash";
59
+ tableName: "access_tokens";
60
+ dataType: "string";
61
+ columnType: "PgText";
62
+ data: string;
63
+ driverParam: string;
64
+ notNull: true;
65
+ hasDefault: false;
66
+ isPrimaryKey: false;
67
+ isAutoincrement: false;
68
+ hasRuntimeDefault: false;
69
+ enumValues: [string, ...string[]];
70
+ baseColumn: never;
71
+ identity: undefined;
72
+ generated: undefined;
73
+ }, {}, {}>;
74
+ canWrite: import("drizzle-orm/pg-core").PgColumn<{
75
+ name: "can_write";
76
+ tableName: "access_tokens";
77
+ dataType: "boolean";
78
+ columnType: "PgBoolean";
79
+ data: boolean;
80
+ driverParam: boolean;
81
+ notNull: true;
82
+ hasDefault: false;
83
+ isPrimaryKey: false;
84
+ isAutoincrement: false;
85
+ hasRuntimeDefault: false;
86
+ enumValues: undefined;
87
+ baseColumn: never;
88
+ identity: undefined;
89
+ generated: undefined;
90
+ }, {}, {}>;
91
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
92
+ name: "created_at";
93
+ tableName: "access_tokens";
94
+ dataType: "date";
95
+ columnType: "PgTimestamp";
96
+ data: Date;
97
+ driverParam: string;
98
+ notNull: true;
99
+ hasDefault: false;
100
+ isPrimaryKey: false;
101
+ isAutoincrement: false;
102
+ hasRuntimeDefault: false;
103
+ enumValues: undefined;
104
+ baseColumn: never;
105
+ identity: undefined;
106
+ generated: undefined;
107
+ }, {}, {}>;
108
+ expiresAt: import("drizzle-orm/pg-core").PgColumn<{
109
+ name: "expires_at";
110
+ tableName: "access_tokens";
111
+ dataType: "date";
112
+ columnType: "PgTimestamp";
113
+ data: Date;
114
+ driverParam: string;
115
+ notNull: true;
116
+ hasDefault: false;
117
+ isPrimaryKey: false;
118
+ isAutoincrement: false;
119
+ hasRuntimeDefault: false;
120
+ enumValues: undefined;
121
+ baseColumn: never;
122
+ identity: undefined;
123
+ generated: undefined;
124
+ }, {}, {}>;
125
+ lastUsedAt: import("drizzle-orm/pg-core").PgColumn<{
126
+ name: "last_used_at";
127
+ tableName: "access_tokens";
128
+ dataType: "date";
129
+ columnType: "PgTimestamp";
130
+ data: Date;
131
+ driverParam: string;
132
+ notNull: false;
133
+ hasDefault: false;
134
+ isPrimaryKey: false;
135
+ isAutoincrement: false;
136
+ hasRuntimeDefault: false;
137
+ enumValues: undefined;
138
+ baseColumn: never;
139
+ identity: undefined;
140
+ generated: undefined;
141
+ }, {}, {}>;
142
+ };
143
+ dialect: "pg";
144
+ }>;
145
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,SAAS,+CAAkB,CAAC;AAEzC,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWvB,CAAC"}
package/dist/schema.js ADDED
@@ -0,0 +1,18 @@
1
+ // Drizzle view of the module's table (migrations/0001_create_access_tokens.sql). The migration is
2
+ // the source of truth; this file only types the queries.
3
+ import { users } from "@softure-ai/auth";
4
+ import { boolean, pgSchema, text, timestamp, uuid } from "drizzle-orm/pg-core";
5
+ export const mcpSchema = pgSchema("mcp");
6
+ export const accessTokens = mcpSchema.table("access_tokens", {
7
+ id: uuid("id").primaryKey().defaultRandom(),
8
+ userId: uuid("user_id")
9
+ .notNull()
10
+ .references(() => users.id, { onDelete: "cascade" }),
11
+ name: text("name").notNull(),
12
+ tokenHash: text("token_hash").notNull().unique("access_tokens_token_hash_key"),
13
+ canWrite: boolean("can_write").notNull(),
14
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull(),
15
+ expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
16
+ lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
17
+ });
18
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,yDAAyD;AACzD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAE/E,MAAM,CAAC,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;AAEzC,MAAM,CAAC,MAAM,YAAY,GAAG,SAAS,CAAC,KAAK,CAAC,eAAe,EAAE;IAC3D,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC,aAAa,EAAE;IAC3C,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC;SACpB,OAAO,EAAE;SACT,UAAU,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtD,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE;IAC5B,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,8BAA8B,CAAC;IAC9E,QAAQ,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,OAAO,EAAE;IACxC,SAAS,EAAE,SAAS,CAAC,YAAY,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE;IACpE,SAAS,EAAE,SAAS,CAAC,YAAY,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE;IACpE,UAAU,EAAE,SAAS,CAAC,cAAc,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;CAC9D,CAAC,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { type McpServer, type Server } from "@modelcontextprotocol/server";
2
+ import type { McpServerIdentity } from "../contract.js";
3
+ import { type McpAccessContext } from "./tokens.js";
4
+ /** Every valid token reads. */
5
+ export declare const MCP_READ_SCOPE = "mcp:read";
6
+ /** A token that may write: issued for writes, and the app allows them. */
7
+ export declare const MCP_WRITE_SCOPE = "mcp:write";
8
+ /** The security bucket the endpoint counts in; `MCP_RATE_LIMIT_BUCKETS` holds its default. */
9
+ export declare const MCP_RATE_LIMIT_BUCKET = "mcp";
10
+ /** Larger bodies get 413 from the SDK before anything is parsed. No tool call needs more. */
11
+ export declare const MAX_MCP_REQUEST_BYTES: number;
12
+ /** The app's MCP server for one request. It registers write tools only when `canWrite` is true. */
13
+ export type McpServerFactory = (identity: McpServerIdentity) => McpServer | Server | Promise<McpServer | Server>;
14
+ export interface McpEndpointOptions {
15
+ readonly createServer: McpServerFactory;
16
+ }
17
+ /** Serves one request to the MCP endpoint. */
18
+ export type McpEndpoint = (ctx: McpAccessContext, request: Request) => Promise<Response>;
19
+ /**
20
+ * The endpoint around the app's factory. The factory runs once per request with that request's
21
+ * identity, so one account's server never answers another's request. Answers are plain JSON
22
+ * (`responseMode: "json"`): a stream would add proxy buffering and timeouts for no tool that needs
23
+ * it, and mid-call notifications are dropped.
24
+ */
25
+ export declare function createMcpEndpoint({ createServer }: McpEndpointOptions): McpEndpoint;
26
+ //# sourceMappingURL=endpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["../../src/server/endpoint.ts"],"names":[],"mappings":"AAKA,OAAO,EAML,KAAK,SAAS,EACd,KAAK,MAAM,EACZ,MAAM,8BAA8B,CAAC;AAGtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEvE,+BAA+B;AAC/B,eAAO,MAAM,cAAc,aAAa,CAAC;AACzC,0EAA0E;AAC1E,eAAO,MAAM,eAAe,cAAc,CAAC;AAE3C,8FAA8F;AAC9F,eAAO,MAAM,qBAAqB,QAAQ,CAAC;AAE3C,6FAA6F;AAC7F,eAAO,MAAM,qBAAqB,QAAc,CAAC;AAOjD,mGAAmG;AACnG,MAAM,MAAM,gBAAgB,GAAG,CAAC,QAAQ,EAAE,iBAAiB,KAAK,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,MAAM,CAAC,CAAC;AAEjH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC;CACzC;AAED,8CAA8C;AAC9C,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEzF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,YAAY,EAAE,EAAE,kBAAkB,GAAG,WAAW,CAmCnF"}
@@ -0,0 +1,107 @@
1
+ // The MCP endpoint (FIRE_TRACKER `src/app/api/mcp/route.ts`, generalised). It takes a Web
2
+ // `Request` and returns a `Response`, so it runs under any fetch-shaped host; the Next adapter only
3
+ // supplies the context. Order: identify the client, count the request, verify the Bearer token,
4
+ // then hand the request to a fresh server from the app's factory. Each refusal happens before the
5
+ // next step costs anything.
6
+ import { bearerAuthChallengeResponse, createMcpHandler, OAuthError, OAuthErrorCode, } from "@modelcontextprotocol/server";
7
+ import { errorLogLabel, getModule } from "@softure-ai/core";
8
+ import { consumeRateLimit, identifyClient } from "@softure-ai/security/server";
9
+ import { verifyAccessToken } from "./tokens.js";
10
+ /** Every valid token reads. */
11
+ export const MCP_READ_SCOPE = "mcp:read";
12
+ /** A token that may write: issued for writes, and the app allows them. */
13
+ export const MCP_WRITE_SCOPE = "mcp:write";
14
+ /** The security bucket the endpoint counts in; `MCP_RATE_LIMIT_BUCKETS` holds its default. */
15
+ export const MCP_RATE_LIMIT_BUCKET = "mcp";
16
+ /** Larger bodies get 413 from the SDK before anything is parsed. No tool call needs more. */
17
+ export const MAX_MCP_REQUEST_BYTES = 1024 * 1024;
18
+ /** `AuthInfo.extra` key that carries the owner's user id to the factory. */
19
+ const USER_ID_CLAIM = "userId";
20
+ const BEARER_PREFIX = /^Bearer[ \t]+/i;
21
+ const NO_STORE = { "cache-control": "no-store" };
22
+ /**
23
+ * The endpoint around the app's factory. The factory runs once per request with that request's
24
+ * identity, so one account's server never answers another's request. Answers are plain JSON
25
+ * (`responseMode: "json"`): a stream would add proxy buffering and timeouts for no tool that needs
26
+ * it, and mid-call notifications are dropped.
27
+ */
28
+ export function createMcpEndpoint({ createServer }) {
29
+ const handler = createMcpHandler((requestContext) => createServer(readIdentity(requestContext.authInfo)), {
30
+ responseMode: "json",
31
+ maxRequestBodySize: MAX_MCP_REQUEST_BYTES,
32
+ onerror: (error) => console.error(`@softure-ai/mcp-access: the MCP handler reported: ${errorLogLabel(error)}`),
33
+ });
34
+ return async (ctx, request) => {
35
+ assertRateLimitBucket(ctx);
36
+ const client = identifyClient(ctx, request.headers);
37
+ if (!client.ok)
38
+ return Response.json({ error: "client_unidentified" }, { status: 400, headers: NO_STORE });
39
+ let limit;
40
+ try {
41
+ // Counted before verification, so a flood of guesses is stopped before any lookup.
42
+ limit = await consumeRateLimit(ctx, { bucket: MCP_RATE_LIMIT_BUCKET, key: client.value });
43
+ }
44
+ catch (error) {
45
+ // Fails closed and says nothing: the route is public and this runs before authentication.
46
+ console.error(`@softure-ai/mcp-access: counting a request failed: ${errorLogLabel(error)}`);
47
+ return Response.json({ error: "temporarily_unavailable" }, { status: 503, headers: NO_STORE });
48
+ }
49
+ if (!limit.ok) {
50
+ return Response.json({ error: "too_many_requests" }, { status: 429, headers: { ...NO_STORE, "retry-after": String(limit.retryAfterSeconds) } });
51
+ }
52
+ const authInfo = await authenticate(ctx, request.headers.get("authorization"));
53
+ if (authInfo instanceof Response)
54
+ return authInfo;
55
+ return handler.fetch(request, { authInfo });
56
+ };
57
+ }
58
+ /**
59
+ * The verified identity as the SDK's `AuthInfo`, or the challenge response. Unknown, revoked,
60
+ * expired and malformed tokens get the same `401 invalid_token`. Expiry is decided by the query on
61
+ * the module's clock, not by the SDK's own check on the wall clock.
62
+ */
63
+ async function authenticate(ctx, header) {
64
+ const challenge = { requiredScopes: [MCP_READ_SCOPE] };
65
+ if (header === null || !BEARER_PREFIX.test(header)) {
66
+ return bearerAuthChallengeResponse(new OAuthError(OAuthErrorCode.InvalidToken, "Missing Bearer token"), challenge);
67
+ }
68
+ const token = header.replace(BEARER_PREFIX, "").trim();
69
+ let verified;
70
+ try {
71
+ verified = await verifyAccessToken(ctx, token);
72
+ }
73
+ catch (error) {
74
+ console.error(`@softure-ai/mcp-access: verifying a token failed: ${errorLogLabel(error)}`);
75
+ return bearerAuthChallengeResponse(new OAuthError(OAuthErrorCode.ServerError, "Internal Server Error"), challenge);
76
+ }
77
+ if (verified === null) {
78
+ return bearerAuthChallengeResponse(new OAuthError(OAuthErrorCode.InvalidToken, "Invalid access token"), challenge);
79
+ }
80
+ return {
81
+ token,
82
+ // The client is the token: it is what gets revoked, and its id may be logged.
83
+ clientId: verified.tokenId,
84
+ scopes: verified.canWrite ? [MCP_READ_SCOPE, MCP_WRITE_SCOPE] : [MCP_READ_SCOPE],
85
+ // AuthInfo counts seconds; Date counts milliseconds.
86
+ expiresAt: Math.floor(verified.expiresAt.getTime() / 1000),
87
+ extra: { [USER_ID_CLAIM]: verified.userId },
88
+ };
89
+ }
90
+ /** The factory's input from the request's `AuthInfo`; throws when the two sides disagree. */
91
+ function readIdentity(authInfo) {
92
+ const userId = authInfo?.extra?.[USER_ID_CLAIM];
93
+ if (authInfo === undefined || typeof userId !== "string" || userId === "") {
94
+ // Unreachable through the endpoint, which always passes a verified identity. A server built
95
+ // without one would run queries with no account scope, so this refuses instead.
96
+ throw new Error("@softure-ai/mcp-access: an MCP request reached the factory without a verified identity");
97
+ }
98
+ return { userId, canWrite: authInfo.scopes.includes(MCP_WRITE_SCOPE), tokenId: authInfo.clientId };
99
+ }
100
+ /** A missing bucket is a setup bug: thrown, so it reaches the log by name instead of a quiet 503. */
101
+ function assertRateLimitBucket(ctx) {
102
+ const options = getModule(ctx.config, "security")?.options;
103
+ if (options?.buckets?.[MCP_RATE_LIMIT_BUCKET] === undefined) {
104
+ throw new Error(`@softure-ai/mcp-access: the security module has no "${MCP_RATE_LIMIT_BUCKET}" bucket; spread MCP_RATE_LIMIT_BUCKETS into security({ buckets })`);
105
+ }
106
+ }
107
+ //# sourceMappingURL=endpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint.js","sourceRoot":"","sources":["../../src/server/endpoint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { type HealthCheck } from "@softure-ai/core";
2
+ export declare const checkAccessTokensTable: HealthCheck;
3
+ //# sourceMappingURL=health.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/server/health.ts"],"names":[],"mappings":"AAEA,OAAO,EAAM,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIxD,eAAO,MAAM,sBAAsB,EAAE,WAKpC,CAAC"}
@@ -0,0 +1,11 @@
1
+ // The module's readiness probe for `GET /api/health` of `@softure-ai/ops`: the tokens table exists
2
+ // and answers, i.e. `softure migrate` ran. It reads no rows.
3
+ import { ok } from "@softure-ai/core";
4
+ import { sql } from "drizzle-orm";
5
+ export const checkAccessTokensTable = async (context) => {
6
+ // Core types `db` as unknown; the health route passes the @softure-ai/db handle.
7
+ const db = context.db;
8
+ await db.execute(sql `select 1 from mcp.access_tokens limit 0`);
9
+ return ok();
10
+ };
11
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/server/health.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,6DAA6D;AAC7D,OAAO,EAAE,EAAE,EAAoB,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAElC,MAAM,CAAC,MAAM,sBAAsB,GAAgB,KAAK,EAAE,OAAO,EAAE,EAAE;IACnE,iFAAiF;IACjF,MAAM,EAAE,GAAG,OAAO,CAAC,EAAe,CAAC;IACnC,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAA,yCAAyC,CAAC,CAAC;IAC/D,OAAO,EAAE,EAAE,CAAC;AACd,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { createMcpEndpoint, MAX_MCP_REQUEST_BYTES, MCP_RATE_LIMIT_BUCKET, MCP_READ_SCOPE, MCP_WRITE_SCOPE, type McpEndpoint, type McpEndpointOptions, type McpServerFactory, } from "./endpoint.js";
2
+ export { checkAccessTokensTable } from "./health.js";
3
+ export { getMcpAccessMessages, getMcpAccessOptions, getMcpAccessRoutes, getMcpEndpointUrl, type McpAccessRoutes } from "./options.js";
4
+ export { deleteMcpAccessUserData, exportMcpAccessUserData, mcpAccessPrivacyContributor, type McpAccessUserData, } from "./privacy.js";
5
+ export { ACCESS_TOKEN_PREFIX, hashAccessToken, isAccessTokenShape, issueAccessToken, listAccessTokens, pruneAccessTokens, revokeAccessToken, verifyAccessToken, type IssueAccessTokenInput, type IssueAccessTokenResult, type IssuedAccessToken, type McpAccessContext, type RevokeAccessTokenInput, type VerifiedAccessToken, } from "./tokens.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,cAAc,EACd,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AACtI,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,2BAA2B,EAC3B,KAAK,iBAAiB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,GACzB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,8 @@
1
+ // Server-only API of @softure-ai/mcp-access. Every function receives the module context
2
+ // (`{ db, clock, config }`) and never reads request scope; the endpoint takes a Web `Request`.
3
+ export { createMcpEndpoint, MAX_MCP_REQUEST_BYTES, MCP_RATE_LIMIT_BUCKET, MCP_READ_SCOPE, MCP_WRITE_SCOPE, } from "./endpoint.js";
4
+ export { checkAccessTokensTable } from "./health.js";
5
+ export { getMcpAccessMessages, getMcpAccessOptions, getMcpAccessRoutes, getMcpEndpointUrl } from "./options.js";
6
+ export { deleteMcpAccessUserData, exportMcpAccessUserData, mcpAccessPrivacyContributor, } from "./privacy.js";
7
+ export { ACCESS_TOKEN_PREFIX, hashAccessToken, isAccessTokenShape, issueAccessToken, listAccessTokens, pruneAccessTokens, revokeAccessToken, verifyAccessToken, } from "./tokens.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,+FAA+F;AAC/F,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,cAAc,EACd,eAAe,GAIhB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,iBAAiB,EAAwB,MAAM,cAAc,CAAC;AACtI,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,2BAA2B,GAE5B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,GAOlB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { type Locale, type SoftureConfig } from "@softure-ai/core";
2
+ import type { McpAccessMessages } from "../messages/index.js";
3
+ import type { McpAccessOptions } from "../options.js";
4
+ /** The enabled module. Throws when the app did not enable it: calling its functions then is a bug. */
5
+ export declare function getMcpAccessModule(config: SoftureConfig): import("@softure-ai/core").AnySoftureModule;
6
+ export declare function getMcpAccessOptions(config: SoftureConfig): McpAccessOptions;
7
+ export interface McpAccessRoutes {
8
+ /** The token page. */
9
+ readonly page: string;
10
+ /** `POST` MCP endpoint. */
11
+ readonly endpoint: string;
12
+ }
13
+ export declare function getMcpAccessRoutes(config: SoftureConfig): McpAccessRoutes;
14
+ /** The absolute URL MCP clients call: the app's origin and the endpoint route. */
15
+ export declare function getMcpEndpointUrl(config: SoftureConfig): string;
16
+ /** The module's copy in the app's locale, with the app's overrides applied. */
17
+ export declare function getMcpAccessMessages(config: SoftureConfig): McpAccessMessages;
18
+ /** The text in `locale`, else in `en`. */
19
+ export declare function getLocalizedText(text: Partial<Record<Locale, string>>, locale: Locale): string;
20
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/server/options.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,KAAK,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAItD,sGAAsG;AACtG,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,+CAMvD;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,gBAAgB,CAG3E;AAED,MAAM,WAAW,eAAe;IAC9B,sBAAsB;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2BAA2B;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,eAAe,CASzE;AAED,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAE/D;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,aAAa,GAAG,iBAAiB,CAG7E;AAED,0CAA0C;AAC1C,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE9F"}