@forumone/throughline-core 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +29 -13
  3. package/dist/auth/authenticator.d.ts +4 -0
  4. package/dist/auth/authenticator.d.ts.map +1 -1
  5. package/dist/auth/authenticator.js +11 -0
  6. package/dist/auth/authenticator.js.map +1 -1
  7. package/dist/index.d.ts +3 -5
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +2 -3
  10. package/dist/index.js.map +1 -1
  11. package/dist/mcp/audit-context.d.ts +39 -0
  12. package/dist/mcp/audit-context.d.ts.map +1 -0
  13. package/dist/mcp/audit-context.js +36 -0
  14. package/dist/mcp/audit-context.js.map +1 -0
  15. package/dist/mcp/collector.d.ts +34 -0
  16. package/dist/mcp/collector.d.ts.map +1 -0
  17. package/dist/mcp/collector.js +34 -0
  18. package/dist/mcp/collector.js.map +1 -0
  19. package/dist/mcp/handler.d.ts.map +1 -1
  20. package/dist/mcp/handler.js +27 -1
  21. package/dist/mcp/handler.js.map +1 -1
  22. package/dist/mcp/index.d.ts +6 -0
  23. package/dist/mcp/index.d.ts.map +1 -1
  24. package/dist/mcp/index.js +3 -0
  25. package/dist/mcp/index.js.map +1 -1
  26. package/dist/mcp/payload-mcp.d.ts +66 -0
  27. package/dist/mcp/payload-mcp.d.ts.map +1 -0
  28. package/dist/mcp/payload-mcp.js +63 -0
  29. package/dist/mcp/payload-mcp.js.map +1 -0
  30. package/dist/utils/index.d.ts +0 -2
  31. package/dist/utils/index.d.ts.map +1 -1
  32. package/dist/utils/index.js +0 -2
  33. package/dist/utils/index.js.map +1 -1
  34. package/package.json +3 -7
  35. package/dist/env/index.d.ts +0 -51
  36. package/dist/env/index.d.ts.map +0 -1
  37. package/dist/env/index.js +0 -59
  38. package/dist/env/index.js.map +0 -1
  39. package/dist/utils/diff.d.ts +0 -13
  40. package/dist/utils/diff.d.ts.map +0 -1
  41. package/dist/utils/diff.js +0 -31
  42. package/dist/utils/diff.js.map +0 -1
  43. package/dist/utils/id.d.ts +0 -6
  44. package/dist/utils/id.d.ts.map +0 -1
  45. package/dist/utils/id.js +0 -13
  46. package/dist/utils/id.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,68 @@
1
1
  # @forumone/throughline-core
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1a4a441: Let every server's tools be served by Payload's own MCP plugin
8
+
9
+ `createMcpToolCollector()` in core, and an `mcpTools` option on all six servers. The host hands the collector's array to `@payloadcms/plugin-mcp` at config time and each plugin fills it at `onInit` — which works because the plugin reads `mcp.tools` inside the handler it builds per request, so an array handed over empty is read populated.
10
+
11
+ That ordering is the whole problem this solves: every tool in the suite is built at `onInit` because every one closes over `payload`, and `mcpPlugin` takes its tools as a config option.
12
+
13
+ Omit `mcpTools` and nothing changes — each server keeps its own `/mcp` endpoint, which is what lets a host move one at a time rather than all six at once.
14
+
15
+ Duplicate tool names are refused, naming both servers. Six servers each owning a `publish` was fine while each had its own endpoint; one server is one namespace, and an MCP client offered two tools under one name gets whichever registered last.
16
+
17
+ **Also fixes a defect the integration test found.** `service.loadDocument` called `findByID` without `disableErrors`, so a missing document threw `NotFound` before the pipeline ran — which made the `exist` step's `not-found` branch unreachable from every caller, and turned "publish a document that does not exist" into a thrown error instead of the diagnostic the pipeline exists to return. The step's own tests passed it an empty document and so never noticed. `unpublish` now distinguishes a missing document from one that is merely already a draft.
18
+
19
+ ## 0.4.0
20
+
21
+ ### Minor Changes
22
+
23
+ - 40839b5: Stop publishing code nothing imports
24
+
25
+ `@forumone/throughline-core` loses three things no package in the suite, and no consumer, has ever called:
26
+ - **`./env`** — `ENV_VARS`, `validateBaseEnv`, `requireEnv`, `optionalEnv`, and the subpath export that served them. The idea was that plugins would read `process.env` through shared constants instead of hard-coded strings; every plugin hard-codes the string, including the ones in this repo. A convention with no adherents is not a convention.
27
+ - **`shallowDiff`** — written for the audit writer's `diff` field, never wired to it. The writer still takes a caller-supplied diff, and Payload's own version diffing is the better answer if one is ever wanted.
28
+ - **`generateId`** — an id generator in a framework where Payload assigns the ids.
29
+
30
+ `@forumone/throughline-plugin-contract` stops shipping `examplePlugin`. It is documentation of a shape, and it now lives in the playground, which is where a shape gets demonstrated — the published package was carrying 74 lines of example for every consumer that installs it.
31
+
32
+ Removing exports from a published package, hence minor rather than patch. Nothing in this repository, and nothing in the suite's only consumer, imports any of it.
33
+
34
+ - 9f39ace: Enforce API-key scopes, which until now were only a label
35
+
36
+ The API-keys collection has always had a required `scopes` field, the README has always told you to mint keys with `--scopes publishing.execute`, and the scheduled-publish factory documents that its key "must carry `publishing.execute` scope". Nothing read the field. Every key could do whatever its linked user could, whatever it said on the label.
37
+
38
+ A tool may now declare `requiredScope`, and the handler holds callers to it: the tool is hidden from `tools/list` and refused on a direct call unless the key names that scope. Hidden as well as refused, because an agent shown a tool it will be turned away from will try it, fail, and report the tool as broken when what is narrow is the key.
39
+
40
+ The consequential tools are annotated — `publish`, `unpublish`, `schedule_publish`, `rollback` (`publishing.execute`); `request_approval` (`approvals.request`); `respond_to_approval` (`approvals.decide`); the three form writers (`forms.manage`); `trigger_sync` and `test_integration` (`integrations.trigger`). Reads are left unscoped, which is the right default for a read.
41
+
42
+ **This narrows existing keys.** A key minted with one scope could previously call every tool on every server and now cannot. That is the point, but it will change what an existing MCP client can do — check the scopes on your keys before upgrading. A key carrying no scopes at all passes nothing scoped: absent is read as none, not as everything.
43
+
44
+ - f138b3d: One audit actor shape for every tool, and stop recording agents as people
45
+
46
+ Ten tools built the audit actor by hand and four of them disagreed. Three were only untidy — a dropped `userName`, conditional spreads, an assumption that `ctx.user` is non-null. The fourth was wrong: the component tools wrote `type: 'user'` unconditionally, so a call made with an API key and no linked user was recorded as a person. An audit log that cannot tell an agent from an editor is not an audit log.
47
+
48
+ `auditContext(ctx, meta)` is now exported from core and used at all eight tool call sites. `type` follows the rule the publishing service already used — a call carrying a user is that user's, one without is the system's — and `apiKeyName` rides along either way, because a key acting for a linked user is still worth naming.
49
+
50
+ It also passes `sessionId` through for the first time. The column has been on the audit collection since it was written and nothing ever filled it; it is what lets somebody reading the log group one conversation's writes instead of reading them one at a time.
51
+
52
+ - 6fac789: Add `toPayloadMcpTool`, so Throughline's tools can be served by Payload's own MCP plugin
53
+
54
+ Payload ships `@payloadcms/plugin-mcp`, exact-pinned to the Payload version, built on the official MCP SDK: streamable HTTP, sessions, per-key per-tool capability checkboxes, and generic CRUD tools derived from the field configs. Against that, `createMcpHandler` here is a 146-line JSON-RPC subset speaking `tools/list` and `tools/call`, mounted six times over.
55
+
56
+ The transport was never the product. The tools are. This adapter is what makes moving between the two a configuration change rather than a rewrite of every tool: it translates the input schema (`withMeta`'s `z.object` to the raw shape the plugin registers), the context (a `PayloadRequest` to an `McpToolContext`), and the result (a tool's own object to MCP content blocks).
57
+
58
+ Nothing is wired to it. It is the outcome of a spike, and the servers move over one at a time.
59
+
60
+ ### Patch Changes
61
+
62
+ - Updated dependencies [40839b5]
63
+ - Updated dependencies [9f39ace]
64
+ - @forumone/throughline-plugin-contract@0.3.0
65
+
3
66
  ## 0.3.0
4
67
 
5
68
  ### Minor Changes
package/README.md CHANGED
@@ -10,9 +10,8 @@ The shared plumbing every Throughline server package depends on. Drop it into a
10
10
  | Auth | `./auth` | `createApiKeysCollection`, `createBearerTokenAuthenticator`, `generateApiKey`, `sha256Hex` |
11
11
  | Events | `./events` | `createInngestClient`, `CoreEvents`, `FrameworkEvents` (module-augmentation seam) |
12
12
  | MCP | `./mcp` | `createMcpHandler`, `McpMetaSchema`, `withMeta` |
13
- | Env | `./env` | `ENV_VARS`, `validateBaseEnv`, `requireEnv`, `optionalEnv` |
14
13
  | Logger | (main) | `defaultLogger`, `createNamedLogger` |
15
- | Utils | (main) | `shallowDiff`, `generateId`, `documentContentHash` |
14
+ | Utils | (main) | `documentContentHash` |
16
15
 
17
16
  The main entry re-exports everything; the subpath exports keep bundles smaller for consumers who only need one slice.
18
17
 
@@ -88,6 +87,34 @@ const handleMcp = createMcpHandler({
88
87
  export const POST = (req: Request) => handleMcp(req)
89
88
  ```
90
89
 
90
+ ### Scopes
91
+
92
+ A key carries `scopes`, and a tool may declare the one it needs:
93
+
94
+ ```ts
95
+ const publishTool: McpToolDefinition = {
96
+ name: 'publish',
97
+ requiredScope: 'publishing.execute',
98
+ // …
99
+ }
100
+ ```
101
+
102
+ A tool that declares no `requiredScope` is callable by any authenticated key, which is the right default for a read. A tool that declares one is **hidden from `tools/list`** and refused on a direct call unless the key names that scope — hidden as well as refused, because an agent shown a tool it will be turned away from will try it, fail, and report the tool as broken when what is narrow is the key.
103
+
104
+ A key carrying no scopes at all passes nothing scoped. Absent is read as none, not as everything.
105
+
106
+ The consequential tools in this suite and the scopes they require:
107
+
108
+ | Scope | Tools |
109
+ |---|---|
110
+ | `publishing.execute` | `publish`, `unpublish`, `schedule_publish`, `rollback` |
111
+ | `approvals.request` | `request_approval` |
112
+ | `approvals.decide` | `respond_to_approval` |
113
+ | `forms.manage` | `create_form`, `update_form_fields`, `update_form_destinations` |
114
+ | `integrations.trigger` | `trigger_sync`, `test_integration` |
115
+
116
+ Everything else — the component tools, the audit queries, the read side of publishing and approvals — needs only a valid key.
117
+
91
118
  ## Events
92
119
 
93
120
  `CoreEvents` enumerates the events the framework fires today. Server packages add their own via TypeScript module augmentation:
@@ -104,17 +131,6 @@ declare module '@forumone/throughline-core/events' {
104
131
 
105
132
  After augmentation, `inngest.send({ name: 'approval/decided', data: { ... } })` is type-checked everywhere.
106
133
 
107
- ## Env vars
108
-
109
- `ENV_VARS` is the canonical list of names the framework reads. Plugins reach into `process.env` through these constants rather than hard-coded strings:
110
-
111
- ```ts
112
- import { ENV_VARS, requireEnv, validateBaseEnv } from '@forumone/throughline-core'
113
-
114
- validateBaseEnv() // throws on missing PAYLOAD_SECRET / DATABASE_URI / NEXT_PUBLIC_SERVER_URL
115
-
116
- const apiKey = requireEnv(ENV_VARS.PUBLISHING_SERVER_API_KEY)
117
- ```
118
134
 
119
135
  ## Document content hashing
120
136
 
@@ -10,6 +10,10 @@ export interface BearerTokenAuthenticatorOptions {
10
10
  * against the API-keys collection. Returns the linked user plus key metadata
11
11
  * on success, `null` on any failure (no token, unknown token, disabled key,
12
12
  * key without linked user, expired key).
13
+ *
14
+ * The key's `scopes` come back with it. They are what the handler holds a
15
+ * scoped tool against; until they did, the field was a label on a key that
16
+ * could do everything its linked user could.
13
17
  */
14
18
  export declare function createBearerTokenAuthenticator(options: BearerTokenAuthenticatorOptions): McpAuthenticator;
15
19
  //# sourceMappingURL=authenticator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"authenticator.d.ts","sourceRoot":"","sources":["../../src/auth/authenticator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,uCAAuC,CAAA;AAG9C,MAAM,WAAW,+BAA+B;IAC9C,OAAO,EAAE,OAAO,CAAA;IAChB,mEAAmE;IACnE,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,+BAA+B,GACvC,gBAAgB,CAwClB"}
1
+ {"version":3,"file":"authenticator.d.ts","sourceRoot":"","sources":["../../src/auth/authenticator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,uCAAuC,CAAA;AAG9C,MAAM,WAAW,+BAA+B;IAC9C,OAAO,EAAE,OAAO,CAAA;IAChB,mEAAmE;IACnE,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,+BAA+B,GACvC,gBAAgB,CAyClB"}
@@ -4,6 +4,10 @@ import { DEFAULT_API_KEYS_SLUG, sha256Hex } from './api-keys.js';
4
4
  * against the API-keys collection. Returns the linked user plus key metadata
5
5
  * on success, `null` on any failure (no token, unknown token, disabled key,
6
6
  * key without linked user, expired key).
7
+ *
8
+ * The key's `scopes` come back with it. They are what the handler holds a
9
+ * scoped tool against; until they did, the field was a label on a key that
10
+ * could do everything its linked user could.
7
11
  */
8
12
  export function createBearerTokenAuthenticator(options) {
9
13
  const { payload, collectionSlug = DEFAULT_API_KEYS_SLUG } = options;
@@ -38,6 +42,7 @@ export function createBearerTokenAuthenticator(options) {
38
42
  user,
39
43
  apiKeyName: String(apiKey['name'] ?? ''),
40
44
  apiKeyId: String(apiKey['id']),
45
+ scopes: toScopes(apiKey['scopes']),
41
46
  };
42
47
  },
43
48
  };
@@ -60,4 +65,10 @@ function toAuthenticatedUser(raw) {
60
65
  groups: raw['groups'] ?? [],
61
66
  };
62
67
  }
68
+ /** The stored `scopes` as a string list, whatever shape the row came back in. */
69
+ function toScopes(raw) {
70
+ if (!Array.isArray(raw))
71
+ return [];
72
+ return raw.filter((scope) => typeof scope === 'string');
73
+ }
63
74
  //# sourceMappingURL=authenticator.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"authenticator.js","sourceRoot":"","sources":["../../src/auth/authenticator.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAQhE;;;;;GAKG;AACH,MAAM,UAAU,8BAA8B,CAC5C,OAAwC;IAExC,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,qBAAqB,EAAE,GAAG,OAAO,CAAA;IAEnE,OAAO;QACL,KAAK,CAAC,YAAY,CAAC,OAAgB;YACjC,MAAM,KAAK,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAA;YACzC,IAAI,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAA;YAEvB,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,KAAK,CAAC,CAAA;YAEnC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;gBAChC,UAAU,EAAE,cAAc;gBAC1B,KAAK,EAAE;oBACL,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC;iBACpE;gBACD,KAAK,EAAE,CAAC;gBACR,KAAK,EAAE,CAAC;aACT,CAAC,CAAA;YAEF,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YAC7B,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAA;YAExB,MAAM,SAAS,GAAG,MAAM,CAAC,WAAW,CAAC,CAAA;YACrC,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;gBACzE,OAAO,IAAI,CAAA;YACb,CAAC;YAED,MAAM,UAAU,GAAG,MAAM,CAAC,YAAY,CAAC,CAAA;YACvC,IAAI,CAAC,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAA;YAE9D,MAAM,IAAI,GAAG,mBAAmB,CAAC,UAAqC,CAAC,CAAA;YACvE,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAA;YAEtB,OAAO;gBACL,IAAI;gBACJ,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;gBACxC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;aAC/B,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAED,SAAS,kBAAkB,CAAC,OAAgB;IAC1C,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;IACnD,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IACxB,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC7C,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,CAAA;AACnC,CAAC;AAED,SAAS,mBAAmB,CAAC,GAA4B;IACvD,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,SAAS,IAAI,GAAG,CAAC,OAAO,CAAC,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IACtE,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC3B,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;QACzC,KAAK,EAAG,GAAG,CAAC,OAAO,CAA0B,IAAI,EAAE;QACnD,MAAM,EAAG,GAAG,CAAC,QAAQ,CAA0B,IAAI,EAAE;KACtD,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"authenticator.js","sourceRoot":"","sources":["../../src/auth/authenticator.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -2,14 +2,12 @@ export { AUDIT_ACTIONS, AUDIT_MCP_SERVERS, DEFAULT_AUDIT_SLUG, auditPlugin, crea
2
2
  export type { AuditAction, AuditActor, AuditCollectionOptions, AuditEventInput, AuditMcpServer, AuditPluginOptions, AuditWriter, AuditWriterOptions, } from './audit/index.js';
3
3
  export { DEFAULT_API_KEYS_SLUG, createApiKeysCollection, createBearerTokenAuthenticator, generateApiKey, sha256Hex, } from './auth/index.js';
4
4
  export type { ApiKeysCollectionOptions, BearerTokenAuthenticatorOptions } from './auth/index.js';
5
- export { ENV_VARS, validateBaseEnv, requireEnv, optionalEnv } from './env/index.js';
6
- export type { EnvVarName, BaseEnv } from './env/index.js';
7
5
  export { createInngestClient } from './events/index.js';
8
6
  export type { CoreEvents, FrameworkEvents, InngestClientOptions } from './events/index.js';
9
- export { McpMetaSchema, createMcpHandler, withMeta } from './mcp/index.js';
10
- export type { McpHandlerOptions, McpMeta } from './mcp/index.js';
7
+ export { McpMetaSchema, auditContext, createMcpHandler, createMcpToolCollector, toPayloadMcpTool, toPayloadMcpTools, withMeta, } from './mcp/index.js';
8
+ export type { AddToolsOptions, AuditContextFields, CreateMcpToolCollectorOptions, McpHandlerOptions, McpMeta, McpToolCollector, PayloadMcpRequest, PayloadMcpTool, ToPayloadMcpToolOptions, } from './mcp/index.js';
11
9
  export { defaultLogger, createNamedLogger } from './logger/index.js';
12
- export { shallowDiff, generateId, documentContentHash } from './utils/index.js';
10
+ export { documentContentHash } from './utils/index.js';
13
11
  export type { DocumentContentHashOptions } from './utils/index.js';
14
12
  export type { AuthenticatedUser, BaseCorePluginOptions, CorePlugin, Logger, McpAuthResult, McpAuthenticator, McpToolContext, McpToolDefinition, PluginRegistry, PluginRegistryEntry, } from '@forumone/throughline-plugin-contract';
15
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,GACf,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,WAAW,EACX,UAAU,EACV,sBAAsB,EACtB,eAAe,EACf,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,kBAAkB,GACnB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,8BAA8B,EAC9B,cAAc,EACd,SAAS,GACV,MAAM,iBAAiB,CAAA;AACxB,YAAY,EAAE,wBAAwB,EAAE,+BAA+B,EAAE,MAAM,iBAAiB,CAAA;AAEhG,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACnF,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAEzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACvD,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAE1F,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC1E,YAAY,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAEhE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAEpE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAC/E,YAAY,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAA;AAIlE,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,UAAU,EACV,MAAM,EACN,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,mBAAmB,GACpB,MAAM,uCAAuC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,GACf,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,WAAW,EACX,UAAU,EACV,sBAAsB,EACtB,eAAe,EACf,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,kBAAkB,GACnB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,8BAA8B,EAC9B,cAAc,EACd,SAAS,GACV,MAAM,iBAAiB,CAAA;AACxB,YAAY,EAAE,wBAAwB,EAAE,+BAA+B,EAAE,MAAM,iBAAiB,CAAA;AAEhG,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACvD,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAE1F,OAAO,EACL,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,EAChB,iBAAiB,EACjB,QAAQ,GACT,MAAM,gBAAgB,CAAA;AACvB,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,6BAA6B,EAC7B,iBAAiB,EACjB,OAAO,EACP,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,uBAAuB,GACxB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAEpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AACtD,YAAY,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAA;AAIlE,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,UAAU,EACV,MAAM,EACN,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,mBAAmB,GACpB,MAAM,uCAAuC,CAAA"}
package/dist/index.js CHANGED
@@ -2,9 +2,8 @@
2
2
  // surface here mirrors the package's `./<subpath>` exports.
3
3
  export { AUDIT_ACTIONS, AUDIT_MCP_SERVERS, DEFAULT_AUDIT_SLUG, auditPlugin, createAuditCollection, createAuditWriter, getAuditWriter, } from './audit/index.js';
4
4
  export { DEFAULT_API_KEYS_SLUG, createApiKeysCollection, createBearerTokenAuthenticator, generateApiKey, sha256Hex, } from './auth/index.js';
5
- export { ENV_VARS, validateBaseEnv, requireEnv, optionalEnv } from './env/index.js';
6
5
  export { createInngestClient } from './events/index.js';
7
- export { McpMetaSchema, createMcpHandler, withMeta } from './mcp/index.js';
6
+ export { McpMetaSchema, auditContext, createMcpHandler, createMcpToolCollector, toPayloadMcpTool, toPayloadMcpTools, withMeta, } from './mcp/index.js';
8
7
  export { defaultLogger, createNamedLogger } from './logger/index.js';
9
- export { shallowDiff, generateId, documentContentHash } from './utils/index.js';
8
+ export { documentContentHash } from './utils/index.js';
10
9
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,4DAA4D;AAE5D,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,GACf,MAAM,kBAAkB,CAAA;AAYzB,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,8BAA8B,EAC9B,cAAc,EACd,SAAS,GACV,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAGnF,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAGvD,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAG1E,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAEpE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,4DAA4D;AAE5D,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,GACf,MAAM,kBAAkB,CAAA;AAYzB,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,8BAA8B,EAC9B,cAAc,EACd,SAAS,GACV,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAGvD,OAAO,EACL,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,EAChB,iBAAiB,EACjB,QAAQ,GACT,MAAM,gBAAgB,CAAA;AAavB,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAEpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,39 @@
1
+ import type { McpToolContext } from '@forumone/throughline-plugin-contract';
2
+ import type { AuditActor } from '../audit/writer.js';
3
+ import type { McpMeta } from './meta.js';
4
+ /**
5
+ * The audit fields every tool derives from its request rather than from its
6
+ * arguments: who called, and whatever narrative context the client attached.
7
+ *
8
+ * Spread into an `AuditEventInput` alongside the fields the tool itself knows —
9
+ * `action`, `mcpServer`, `mcpTool`, the target.
10
+ */
11
+ export interface AuditContextFields {
12
+ actor: AuditActor;
13
+ prompt?: string | undefined;
14
+ reasoning?: string | undefined;
15
+ changesSummary?: string | undefined;
16
+ }
17
+ /**
18
+ * One answer to "who did this", for every tool in every server.
19
+ *
20
+ * Ten tools built this block by hand and four of them disagreed. Three variants
21
+ * were only untidy — a dropped `userName`, conditional spreads, an assumption
22
+ * that `ctx.user` is non-null. The fourth was wrong: the component tools wrote
23
+ * `type: 'user'` unconditionally, so a call made with an API key and no linked
24
+ * user was recorded as a person. An audit log that cannot tell an agent from an
25
+ * editor is not an audit log, and this is the log the MCP pilot will be judged
26
+ * on.
27
+ *
28
+ * `type` follows the rule `publishing`'s service already used: a call carrying a
29
+ * user is that user's, and one without is the system's. `apiKeyName` rides along
30
+ * either way, because a key acting on behalf of a linked user is still worth
31
+ * naming.
32
+ *
33
+ * `sessionId` is passed through for the first time. The column has existed on
34
+ * the audit collection since it was written and nothing ever filled it; it is
35
+ * what lets somebody reading the log group a whole conversation's writes rather
36
+ * than reading them one at a time.
37
+ */
38
+ export declare function auditContext(ctx: McpToolContext, meta?: McpMeta): AuditContextFields;
39
+ //# sourceMappingURL=audit-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-context.d.ts","sourceRoot":"","sources":["../../src/mcp/audit-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AAC3E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAIpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAExC;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,UAAU,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACpC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAapF"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * One answer to "who did this", for every tool in every server.
3
+ *
4
+ * Ten tools built this block by hand and four of them disagreed. Three variants
5
+ * were only untidy — a dropped `userName`, conditional spreads, an assumption
6
+ * that `ctx.user` is non-null. The fourth was wrong: the component tools wrote
7
+ * `type: 'user'` unconditionally, so a call made with an API key and no linked
8
+ * user was recorded as a person. An audit log that cannot tell an agent from an
9
+ * editor is not an audit log, and this is the log the MCP pilot will be judged
10
+ * on.
11
+ *
12
+ * `type` follows the rule `publishing`'s service already used: a call carrying a
13
+ * user is that user's, and one without is the system's. `apiKeyName` rides along
14
+ * either way, because a key acting on behalf of a linked user is still worth
15
+ * naming.
16
+ *
17
+ * `sessionId` is passed through for the first time. The column has existed on
18
+ * the audit collection since it was written and nothing ever filled it; it is
19
+ * what lets somebody reading the log group a whole conversation's writes rather
20
+ * than reading them one at a time.
21
+ */
22
+ export function auditContext(ctx, meta) {
23
+ return {
24
+ actor: {
25
+ type: ctx.user ? 'user' : 'system',
26
+ userId: ctx.user?.id,
27
+ userName: ctx.user?.name,
28
+ apiKeyName: ctx.apiKeyName,
29
+ sessionId: ctx.sessionId,
30
+ },
31
+ prompt: meta?.userPrompt,
32
+ reasoning: meta?.reasoning,
33
+ changesSummary: meta?.changesSummary,
34
+ };
35
+ }
36
+ //# sourceMappingURL=audit-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-context.js","sourceRoot":"","sources":["../../src/mcp/audit-context.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,YAAY,CAAC,GAAmB,EAAE,IAAc;IAC9D,OAAO;QACL,KAAK,EAAE;YACL,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ;YAClC,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE;YACpB,QAAQ,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI;YACxB,UAAU,EAAE,GAAG,CAAC,UAAU;YAC1B,SAAS,EAAE,GAAG,CAAC,SAAS;SACzB;QACD,MAAM,EAAE,IAAI,EAAE,UAAU;QACxB,SAAS,EAAE,IAAI,EAAE,SAAS;QAC1B,cAAc,EAAE,IAAI,EAAE,cAAc;KACrC,CAAA;AACH,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { McpToolDefinition } from '@forumone/throughline-plugin-contract';
2
+ import { type PayloadMcpTool, type ToPayloadMcpToolOptions } from './payload-mcp.js';
3
+ export interface McpToolCollector {
4
+ /**
5
+ * The array to hand `mcpPlugin`. Empty until the plugins initialise, and the
6
+ * same array afterwards — do not copy or spread it at config time, or the
7
+ * tools will be added to something nobody reads.
8
+ */
9
+ readonly tools: PayloadMcpTool[];
10
+ /** Called by a plugin at `onInit`, once it can build its tools. */
11
+ add(tools: McpToolDefinition[], options?: AddToolsOptions): void;
12
+ /** Which servers have contributed, in the order they initialised. */
13
+ readonly servers: string[];
14
+ }
15
+ export interface AddToolsOptions extends ToPayloadMcpToolOptions {
16
+ /**
17
+ * Which server these came from — `'publishing'`, `'approvals'`. Used to name
18
+ * both sides of a duplicate-name collision, which is the only thing that
19
+ * makes that error actionable.
20
+ */
21
+ serverName?: string;
22
+ }
23
+ export type CreateMcpToolCollectorOptions = ToPayloadMcpToolOptions;
24
+ /**
25
+ * Somewhere for the plugins to put their tools, that the host can pass to
26
+ * Payload's MCP plugin before they exist.
27
+ *
28
+ * Duplicate names are refused rather than silently shadowed. Six servers that
29
+ * each named their own `publish` were fine while each had its own endpoint; one
30
+ * server means one namespace, and an MCP client offered two tools with one name
31
+ * gets whichever registered last.
32
+ */
33
+ export declare function createMcpToolCollector(options?: CreateMcpToolCollectorOptions): McpToolCollector;
34
+ //# sourceMappingURL=collector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collector.d.ts","sourceRoot":"","sources":["../../src/mcp/collector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAC9E,OAAO,EAAqB,KAAK,cAAc,EAAE,KAAK,uBAAuB,EAAE,MAAM,kBAAkB,CAAA;AA6BvG,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,cAAc,EAAE,CAAA;IAChC,mEAAmE;IACnE,GAAG,CAAC,KAAK,EAAE,iBAAiB,EAAE,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,CAAA;IAChE,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAC3B;AAED,MAAM,WAAW,eAAgB,SAAQ,uBAAuB;IAC9D;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,6BAA6B,GAAG,uBAAuB,CAAA;AAEnE;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,6BAAkC,GAC1C,gBAAgB,CA2BlB"}
@@ -0,0 +1,34 @@
1
+ import { toPayloadMcpTools } from './payload-mcp.js';
2
+ /**
3
+ * Somewhere for the plugins to put their tools, that the host can pass to
4
+ * Payload's MCP plugin before they exist.
5
+ *
6
+ * Duplicate names are refused rather than silently shadowed. Six servers that
7
+ * each named their own `publish` were fine while each had its own endpoint; one
8
+ * server means one namespace, and an MCP client offered two tools with one name
9
+ * gets whichever registered last.
10
+ */
11
+ export function createMcpToolCollector(options = {}) {
12
+ const tools = [];
13
+ const servers = [];
14
+ const byName = new Map();
15
+ return {
16
+ tools,
17
+ servers,
18
+ add(incoming, addOptions = {}) {
19
+ const { serverName = 'an unnamed server', ...toolOptions } = addOptions;
20
+ for (const tool of incoming) {
21
+ const existing = byName.get(tool.name);
22
+ if (existing !== undefined) {
23
+ throw new Error(`Two MCP tools are called "${tool.name}" — one from ${existing}, one from ` +
24
+ `${serverName}. Under a single MCP server a tool name is a namespace, and a client ` +
25
+ `offered both gets whichever registered last. Rename one.`);
26
+ }
27
+ byName.set(tool.name, serverName);
28
+ }
29
+ tools.push(...toPayloadMcpTools(incoming, { ...options, ...toolOptions }));
30
+ servers.push(serverName);
31
+ },
32
+ };
33
+ }
34
+ //# sourceMappingURL=collector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collector.js","sourceRoot":"","sources":["../../src/mcp/collector.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAqD,MAAM,kBAAkB,CAAA;AAqDvG;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CACpC,UAAyC,EAAE;IAE3C,MAAM,KAAK,GAAqB,EAAE,CAAA;IAClC,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IAExC,OAAO;QACL,KAAK;QACL,OAAO;QACP,GAAG,CAAC,QAAQ,EAAE,UAAU,GAAG,EAAE;YAC3B,MAAM,EAAE,UAAU,GAAG,mBAAmB,EAAE,GAAG,WAAW,EAAE,GAAG,UAAU,CAAA;YAEvE,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;gBAC5B,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBACtC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;oBAC3B,MAAM,IAAI,KAAK,CACb,6BAA6B,IAAI,CAAC,IAAI,gBAAgB,QAAQ,aAAa;wBACzE,GAAG,UAAU,uEAAuE;wBACpF,0DAA0D,CAC7D,CAAA;gBACH,CAAC;gBACD,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;YACnC,CAAC;YAED,KAAK,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,QAAQ,EAAE,EAAE,GAAG,OAAO,EAAE,GAAG,WAAW,EAAE,CAAC,CAAC,CAAA;YAC1E,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QAC1B,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/mcp/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAGtC,OAAO,KAAK,EACV,MAAM,EACN,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,uCAAuC,CAAA;AAI9C,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAA;IAClB,kEAAkE;IAClE,KAAK,EAAE,iBAAiB,EAAE,CAAA;IAC1B,mEAAmE;IACnE,aAAa,CAAC,EAAE,gBAAgB,CAAA;IAChC,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAoFpG"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/mcp/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAGtC,OAAO,KAAK,EACV,MAAM,EACN,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,uCAAuC,CAAA;AAI9C,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAA;IAClB,kEAAkE;IAClE,KAAK,EAAE,iBAAiB,EAAE,CAAA;IAC1B,mEAAmE;IACnE,aAAa,CAAC,EAAE,gBAAgB,CAAA;IAChC,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CA0GpG"}
@@ -30,8 +30,17 @@ export function createMcpHandler(options) {
30
30
  const id = rpc.id ?? null;
31
31
  try {
32
32
  if (rpc.method === 'tools/list') {
33
+ /*
34
+ Only what this key may actually call.
35
+
36
+ An agent shown a tool it will be refused for will try it, be refused,
37
+ and try something else — and the transcript reads like the tool is
38
+ broken rather than like the key is narrow. Hiding it is the difference
39
+ between "you cannot publish" and "publishing is unavailable", and only
40
+ one of those is true.
41
+ */
33
42
  return jsonRpcResult(id, {
34
- tools: options.tools.map((tool) => ({
43
+ tools: options.tools.filter(permits(auth.scopes)).map((tool) => ({
35
44
  name: tool.name,
36
45
  description: tool.description,
37
46
  inputSchema: zodToJsonSchema(tool.inputSchema, { target: 'jsonSchema7' }),
@@ -47,6 +56,12 @@ export function createMcpHandler(options) {
47
56
  if (!tool) {
48
57
  return jsonRpcError(id, JSON_RPC_METHOD_NOT_FOUND, `Unknown tool: ${params.data.name}`);
49
58
  }
59
+ // Named rather than merely refused: a caller that cannot see why it was
60
+ // turned away has no way to ask for the right key.
61
+ if (!permits(auth.scopes)(tool)) {
62
+ logger.warn(`${tag} refused ${tool.name}: key "${auth.apiKeyName}" lacks scope ${String(tool.requiredScope)}`);
63
+ return jsonRpcError(id, JSON_RPC_INVALID_REQUEST, `This API key does not carry the "${String(tool.requiredScope)}" scope, which ${tool.name} requires.`);
64
+ }
50
65
  const inputResult = tool.inputSchema.safeParse(params.data.arguments ?? {});
51
66
  if (!inputResult.success) {
52
67
  return jsonRpcError(id, JSON_RPC_INVALID_PARAMS, `Invalid arguments: ${inputResult.error.message}`);
@@ -106,4 +121,15 @@ function jsonRpcResult(id, result) {
106
121
  function jsonRpcError(id, code, message) {
107
122
  return jsonResponse({ jsonrpc: '2.0', id, error: { code, message } });
108
123
  }
124
+ /**
125
+ * Whether a key holding `scopes` may call a given tool.
126
+ *
127
+ * A tool declaring no `requiredScope` is callable by any authenticated key —
128
+ * the right default for a read. A tool that declares one needs it named on the
129
+ * key, and a key carrying no scopes at all passes nothing.
130
+ */
131
+ function permits(scopes) {
132
+ const held = new Set(scopes ?? []);
133
+ return (tool) => tool.requiredScope === undefined || held.has(tool.requiredScope);
134
+ }
109
135
  //# sourceMappingURL=handler.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/mcp/handler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/mcp/handler.ts"],"names":[],"mappings":"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"}
@@ -2,4 +2,10 @@ export { createMcpHandler } from './handler.js';
2
2
  export type { McpHandlerOptions } from './handler.js';
3
3
  export { McpMetaSchema, withMeta } from './meta.js';
4
4
  export type { McpMeta } from './meta.js';
5
+ export { auditContext } from './audit-context.js';
6
+ export type { AuditContextFields } from './audit-context.js';
7
+ export { toPayloadMcpTool, toPayloadMcpTools } from './payload-mcp.js';
8
+ export type { PayloadMcpRequest, PayloadMcpTool, ToPayloadMcpToolOptions, } from './payload-mcp.js';
9
+ export { createMcpToolCollector } from './collector.js';
10
+ export type { AddToolsOptions, CreateMcpToolCollectorOptions, McpToolCollector, } from './collector.js';
5
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAErD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACnD,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAErD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACnD,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAExC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAE5D,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACtE,YAAY,EACV,iBAAiB,EACjB,cAAc,EACd,uBAAuB,GACxB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AACvD,YAAY,EACV,eAAe,EACf,6BAA6B,EAC7B,gBAAgB,GACjB,MAAM,gBAAgB,CAAA"}
package/dist/mcp/index.js CHANGED
@@ -1,3 +1,6 @@
1
1
  export { createMcpHandler } from './handler.js';
2
2
  export { McpMetaSchema, withMeta } from './meta.js';
3
+ export { auditContext } from './audit-context.js';
4
+ export { toPayloadMcpTool, toPayloadMcpTools } from './payload-mcp.js';
5
+ export { createMcpToolCollector } from './collector.js';
3
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAG/C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAG/C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAGnD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAGjD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAOtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,66 @@
1
+ import { z } from 'zod';
2
+ import type { Logger, McpToolDefinition } from '@forumone/throughline-plugin-contract';
3
+ /** The tool shape `plugin-mcp` accepts under its `mcp.tools` option. */
4
+ export interface PayloadMcpTool {
5
+ name: string;
6
+ description: string;
7
+ parameters: z.ZodRawShape;
8
+ handler: (args: Record<string, unknown>, req: PayloadMcpRequest, extra: unknown) => Promise<{
9
+ content: Array<{
10
+ type: 'text';
11
+ text: string;
12
+ }>;
13
+ }>;
14
+ }
15
+ /**
16
+ * The half of `PayloadRequest` this adapter reads.
17
+ *
18
+ * Declared structurally rather than imported so `core` keeps `payload` a peer
19
+ * it does not name in a type position — and because `payloadAPI` is a module
20
+ * augmentation `plugin-mcp` adds, which is only present when that package is
21
+ * installed.
22
+ */
23
+ export interface PayloadMcpRequest {
24
+ user?: {
25
+ id?: unknown;
26
+ email?: unknown;
27
+ name?: unknown;
28
+ roles?: unknown;
29
+ groups?: unknown;
30
+ } | null;
31
+ payloadAPI?: string;
32
+ payload?: {
33
+ logger?: Logger;
34
+ };
35
+ }
36
+ export interface ToPayloadMcpToolOptions {
37
+ /**
38
+ * What to record as the calling key's name.
39
+ *
40
+ * `plugin-mcp` authenticates against its own key collection and puts the user
41
+ * on the request, not the key — so unlike this package's handler, the key's
42
+ * own name is not recoverable from the request. Pass one if the audit trail
43
+ * should name something more useful than the strategy.
44
+ */
45
+ apiKeyName?: string;
46
+ logger?: Logger;
47
+ }
48
+ /**
49
+ * Wraps one Throughline tool so Payload's MCP plugin can serve it.
50
+ *
51
+ * Three things are being translated, and none of them is the tool's logic:
52
+ *
53
+ * - **The schema.** `plugin-mcp` wants the raw shape; `withMeta` produces a
54
+ * `z.object` around it. `.shape` is the whole conversion, which is why tools
55
+ * must build their input with `withMeta` or `z.object` rather than an
56
+ * arbitrary `ZodType`.
57
+ * - **The context.** Payload hands the handler a request. Throughline's tools
58
+ * take a `McpToolContext`, so one is built from it.
59
+ * - **The result.** Payload wants MCP content blocks; Throughline's tools
60
+ * return their own objects, exactly as they do through this package's own
61
+ * handler, which does the same wrapping a layer up.
62
+ */
63
+ export declare function toPayloadMcpTool(tool: McpToolDefinition, options?: ToPayloadMcpToolOptions): PayloadMcpTool;
64
+ /** Every tool in a server, in one call. */
65
+ export declare function toPayloadMcpTools(tools: McpToolDefinition[], options?: ToPayloadMcpToolOptions): PayloadMcpTool[];
66
+ //# sourceMappingURL=payload-mcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload-mcp.d.ts","sourceRoot":"","sources":["../../src/mcp/payload-mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,MAAM,EAAkB,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAsBtG,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,CAAC,CAAC,WAAW,CAAA;IACzB,OAAO,EAAE,CACP,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,OAAO,KACX,OAAO,CAAC;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAA;CACjE;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAA;IAClG,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,iBAAiB,EACvB,OAAO,GAAE,uBAA4B,GACpC,cAAc,CAYhB;AAED,2CAA2C;AAC3C,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,iBAAiB,EAAE,EAC1B,OAAO,GAAE,uBAA4B,GACpC,cAAc,EAAE,CAElB"}
@@ -0,0 +1,63 @@
1
+ import { z } from 'zod';
2
+ import { defaultLogger } from '../logger/index.js';
3
+ /**
4
+ * Wraps one Throughline tool so Payload's MCP plugin can serve it.
5
+ *
6
+ * Three things are being translated, and none of them is the tool's logic:
7
+ *
8
+ * - **The schema.** `plugin-mcp` wants the raw shape; `withMeta` produces a
9
+ * `z.object` around it. `.shape` is the whole conversion, which is why tools
10
+ * must build their input with `withMeta` or `z.object` rather than an
11
+ * arbitrary `ZodType`.
12
+ * - **The context.** Payload hands the handler a request. Throughline's tools
13
+ * take a `McpToolContext`, so one is built from it.
14
+ * - **The result.** Payload wants MCP content blocks; Throughline's tools
15
+ * return their own objects, exactly as they do through this package's own
16
+ * handler, which does the same wrapping a layer up.
17
+ */
18
+ export function toPayloadMcpTool(tool, options = {}) {
19
+ const shape = shapeOf(tool);
20
+ return {
21
+ name: tool.name,
22
+ description: tool.description,
23
+ parameters: shape,
24
+ handler: async (args, req) => {
25
+ const result = await tool.handler(args, contextFrom(req, options));
26
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
27
+ },
28
+ };
29
+ }
30
+ /** Every tool in a server, in one call. */
31
+ export function toPayloadMcpTools(tools, options = {}) {
32
+ return tools.map(tool => toPayloadMcpTool(tool, options));
33
+ }
34
+ function shapeOf(tool) {
35
+ const schema = tool.inputSchema;
36
+ if (schema instanceof z.ZodObject)
37
+ return schema.shape;
38
+ throw new Error(`Tool "${tool.name}" has an input schema that is not a z.object, so it has no shape to hand ` +
39
+ `Payload's MCP plugin. Build tool inputs with \`withMeta({ … })\` or \`z.object({ … })\`.`);
40
+ }
41
+ function contextFrom(req, options) {
42
+ const user = req.user
43
+ ? {
44
+ id: String(req.user.id ?? ''),
45
+ email: String(req.user.email ?? ''),
46
+ name: String(req.user.name ?? req.user.email ?? ''),
47
+ roles: Array.isArray(req.user.roles) ? req.user.roles : [],
48
+ groups: Array.isArray(req.user.groups) ? req.user.groups : [],
49
+ }
50
+ : null;
51
+ return {
52
+ user,
53
+ /*
54
+ The strategy name is the honest fallback. `plugin-mcp` resolves a key to its
55
+ linked user and does not carry the key document forward, so "which key" is
56
+ not a question the request can answer — and an audit row saying
57
+ `mcp-api-key` is better than one asserting a name nothing checked.
58
+ */
59
+ apiKeyName: options.apiKeyName ?? (req.payloadAPI === 'MCP' ? 'mcp-api-key' : ''),
60
+ logger: options.logger ?? req.payload?.logger ?? defaultLogger,
61
+ };
62
+ }
63
+ //# sourceMappingURL=payload-mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload-mcp.js","sourceRoot":"","sources":["../../src/mcp/payload-mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AA4DlD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAuB,EACvB,UAAmC,EAAE;IAErC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE3B,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,UAAU,EAAE,KAAK;QACjB,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;YAC3B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAA;YAClE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;QAC/E,CAAC;KACF,CAAA;AACH,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,iBAAiB,CAC/B,KAA0B,EAC1B,UAAmC,EAAE;IAErC,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;AAC3D,CAAC;AAED,SAAS,OAAO,CAAC,IAAuB;IACtC,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAA;IAC/B,IAAI,MAAM,YAAY,CAAC,CAAC,SAAS;QAAE,OAAO,MAAM,CAAC,KAAsB,CAAA;IACvE,MAAM,IAAI,KAAK,CACb,SAAS,IAAI,CAAC,IAAI,2EAA2E;QAC3F,0FAA0F,CAC7F,CAAA;AACH,CAAC;AAED,SAAS,WAAW,CAAC,GAAsB,EAAE,OAAgC;IAC3E,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI;QACnB,CAAC,CAAC;YACE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC;YAC7B,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YACnC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YACnD,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,IAAI,CAAC,KAAkB,CAAC,CAAC,CAAC,EAAE;YACxE,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,IAAI,CAAC,MAAmB,CAAC,CAAC,CAAC,EAAE;SAC5E;QACH,CAAC,CAAC,IAAI,CAAA;IAER,OAAO;QACL,IAAI;QACJ;;;;;UAKE;QACF,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,CAAC,GAAG,CAAC,UAAU,KAAK,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;QACjF,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,MAAM,IAAI,aAAa;KAC/D,CAAA;AACH,CAAC"}
@@ -1,5 +1,3 @@
1
- export { shallowDiff } from './diff.js';
2
1
  export { documentContentHash } from './content-hash.js';
3
2
  export type { DocumentContentHashOptions } from './content-hash.js';
4
- export { generateId } from './id.js';
5
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACvD,YAAY,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAA;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACvD,YAAY,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAA"}
@@ -1,4 +1,2 @@
1
- export { shallowDiff } from './diff.js';
2
1
  export { documentContentHash } from './content-hash.js';
3
- export { generateId } from './id.js';
4
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAEvD,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@forumone/throughline-core",
3
- "version": "0.3.0",
4
- "description": "Core plumbing for Throughline: audit log, MCP authentication and handler, event taxonomy + Inngest client factory, env handling, logger, shared utilities.",
3
+ "version": "0.5.0",
4
+ "description": "Core plumbing for Throughline: audit log, MCP authentication and handler, event taxonomy + Inngest client factory, logger, shared utilities.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -25,10 +25,6 @@
25
25
  "./mcp": {
26
26
  "types": "./dist/mcp/index.d.ts",
27
27
  "default": "./dist/mcp/index.js"
28
- },
29
- "./env": {
30
- "types": "./dist/env/index.d.ts",
31
- "default": "./dist/env/index.js"
32
28
  }
33
29
  },
34
30
  "files": [
@@ -62,7 +58,7 @@
62
58
  "dependencies": {
63
59
  "zod": "^3.23.0",
64
60
  "zod-to-json-schema": "^3.25.2",
65
- "@forumone/throughline-plugin-contract": "0.2.1"
61
+ "@forumone/throughline-plugin-contract": "0.3.0"
66
62
  },
67
63
  "devDependencies": {
68
64
  "@types/node": "^20.17.0",
@@ -1,51 +0,0 @@
1
- import { z } from 'zod';
2
- /**
3
- * Canonical env-var names used across Throughline packages. Plugins read
4
- * `process.env[ENV_VARS.X]` rather than hard-coding strings so renames stay
5
- * coordinated.
6
- */
7
- export declare const ENV_VARS: {
8
- readonly PAYLOAD_SECRET: "PAYLOAD_SECRET";
9
- readonly DATABASE_URI: "DATABASE_URI";
10
- readonly NEXT_PUBLIC_SERVER_URL: "NEXT_PUBLIC_SERVER_URL";
11
- readonly INNGEST_EVENT_KEY: "INNGEST_EVENT_KEY";
12
- readonly INNGEST_SIGNING_KEY: "INNGEST_SIGNING_KEY";
13
- readonly RESEND_API_KEY: "RESEND_API_KEY";
14
- readonly EMAIL_FROM_ADDRESS: "EMAIL_FROM_ADDRESS";
15
- readonly EMAIL_FROM_NAME: "EMAIL_FROM_NAME";
16
- readonly EMAIL_REPLY_TO: "EMAIL_REPLY_TO";
17
- readonly APPROVAL_TOKEN_SECRET: "APPROVAL_TOKEN_SECRET";
18
- readonly COMPONENT_SERVER_API_KEY: "COMPONENT_SERVER_API_KEY";
19
- readonly PUBLISHING_SERVER_API_KEY: "PUBLISHING_SERVER_API_KEY";
20
- readonly APPROVALS_SERVER_API_KEY: "APPROVALS_SERVER_API_KEY";
21
- readonly AUDIT_SERVER_API_KEY: "AUDIT_SERVER_API_KEY";
22
- readonly FORMS_SERVER_API_KEY: "FORMS_SERVER_API_KEY";
23
- readonly INTEGRATIONS_SERVER_API_KEY: "INTEGRATIONS_SERVER_API_KEY";
24
- };
25
- export type EnvVarName = (typeof ENV_VARS)[keyof typeof ENV_VARS];
26
- declare const BaseEnvSchema: z.ZodObject<{
27
- PAYLOAD_SECRET: z.ZodString;
28
- DATABASE_URI: z.ZodString;
29
- NEXT_PUBLIC_SERVER_URL: z.ZodString;
30
- }, "strip", z.ZodTypeAny, {
31
- PAYLOAD_SECRET: string;
32
- DATABASE_URI: string;
33
- NEXT_PUBLIC_SERVER_URL: string;
34
- }, {
35
- PAYLOAD_SECRET: string;
36
- DATABASE_URI: string;
37
- NEXT_PUBLIC_SERVER_URL: string;
38
- }>;
39
- export type BaseEnv = z.infer<typeof BaseEnvSchema>;
40
- /**
41
- * Validates the base env vars every Throughline deployment requires.
42
- * Plugins extend with their own checks. Throws on validation failure with
43
- * a multi-line, path-qualified message.
44
- */
45
- export declare function validateBaseEnv(env?: NodeJS.ProcessEnv): BaseEnv;
46
- /** Returns an env var, throwing with a clear error if missing or empty. */
47
- export declare function requireEnv(name: string, message?: string): string;
48
- /** Returns an optional env var or a fallback. */
49
- export declare function optionalEnv(name: string, fallback?: string): string | undefined;
50
- export {};
51
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/env/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;GAIG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;CAiBX,CAAA;AAEV,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,OAAO,QAAQ,CAAC,CAAA;AAEjE,QAAA,MAAM,aAAa;;;;;;;;;;;;EAMjB,CAAA;AAEF,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAA;AAEnD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAS7E;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAMjE;AAED,iDAAiD;AACjD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE/E"}
package/dist/env/index.js DELETED
@@ -1,59 +0,0 @@
1
- import { z } from 'zod';
2
- /**
3
- * Canonical env-var names used across Throughline packages. Plugins read
4
- * `process.env[ENV_VARS.X]` rather than hard-coding strings so renames stay
5
- * coordinated.
6
- */
7
- export const ENV_VARS = {
8
- PAYLOAD_SECRET: 'PAYLOAD_SECRET',
9
- DATABASE_URI: 'DATABASE_URI',
10
- NEXT_PUBLIC_SERVER_URL: 'NEXT_PUBLIC_SERVER_URL',
11
- INNGEST_EVENT_KEY: 'INNGEST_EVENT_KEY',
12
- INNGEST_SIGNING_KEY: 'INNGEST_SIGNING_KEY',
13
- RESEND_API_KEY: 'RESEND_API_KEY',
14
- EMAIL_FROM_ADDRESS: 'EMAIL_FROM_ADDRESS',
15
- EMAIL_FROM_NAME: 'EMAIL_FROM_NAME',
16
- EMAIL_REPLY_TO: 'EMAIL_REPLY_TO',
17
- APPROVAL_TOKEN_SECRET: 'APPROVAL_TOKEN_SECRET',
18
- COMPONENT_SERVER_API_KEY: 'COMPONENT_SERVER_API_KEY',
19
- PUBLISHING_SERVER_API_KEY: 'PUBLISHING_SERVER_API_KEY',
20
- APPROVALS_SERVER_API_KEY: 'APPROVALS_SERVER_API_KEY',
21
- AUDIT_SERVER_API_KEY: 'AUDIT_SERVER_API_KEY',
22
- FORMS_SERVER_API_KEY: 'FORMS_SERVER_API_KEY',
23
- INTEGRATIONS_SERVER_API_KEY: 'INTEGRATIONS_SERVER_API_KEY',
24
- };
25
- const BaseEnvSchema = z.object({
26
- [ENV_VARS.PAYLOAD_SECRET]: z
27
- .string()
28
- .min(32, `${ENV_VARS.PAYLOAD_SECRET} must be at least 32 characters`),
29
- [ENV_VARS.DATABASE_URI]: z.string().min(1, `${ENV_VARS.DATABASE_URI} must be set`),
30
- [ENV_VARS.NEXT_PUBLIC_SERVER_URL]: z.string().url(),
31
- });
32
- /**
33
- * Validates the base env vars every Throughline deployment requires.
34
- * Plugins extend with their own checks. Throws on validation failure with
35
- * a multi-line, path-qualified message.
36
- */
37
- export function validateBaseEnv(env = process.env) {
38
- const result = BaseEnvSchema.safeParse(env);
39
- if (!result.success) {
40
- const issues = result.error.issues
41
- .map((i) => ` - ${String(i.path[0] ?? '(root)')}: ${i.message}`)
42
- .join('\n');
43
- throw new Error(`Invalid environment:\n${issues}`);
44
- }
45
- return result.data;
46
- }
47
- /** Returns an env var, throwing with a clear error if missing or empty. */
48
- export function requireEnv(name, message) {
49
- const value = process.env[name];
50
- if (!value) {
51
- throw new Error(message ?? `Environment variable ${name} is required but not set`);
52
- }
53
- return value;
54
- }
55
- /** Returns an optional env var or a fallback. */
56
- export function optionalEnv(name, fallback) {
57
- return process.env[name] ?? fallback;
58
- }
59
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/env/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,cAAc,EAAE,gBAAgB;IAChC,YAAY,EAAE,cAAc;IAC5B,sBAAsB,EAAE,wBAAwB;IAChD,iBAAiB,EAAE,mBAAmB;IACtC,mBAAmB,EAAE,qBAAqB;IAC1C,cAAc,EAAE,gBAAgB;IAChC,kBAAkB,EAAE,oBAAoB;IACxC,eAAe,EAAE,iBAAiB;IAClC,cAAc,EAAE,gBAAgB;IAChC,qBAAqB,EAAE,uBAAuB;IAC9C,wBAAwB,EAAE,0BAA0B;IACpD,yBAAyB,EAAE,2BAA2B;IACtD,wBAAwB,EAAE,0BAA0B;IACpD,oBAAoB,EAAE,sBAAsB;IAC5C,oBAAoB,EAAE,sBAAsB;IAC5C,2BAA2B,EAAE,6BAA6B;CAClD,CAAA;AAIV,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7B,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;SACzB,MAAM,EAAE;SACR,GAAG,CAAC,EAAE,EAAE,GAAG,QAAQ,CAAC,cAAc,iCAAiC,CAAC;IACvE,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,QAAQ,CAAC,YAAY,cAAc,CAAC;IAClF,CAAC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;CACpD,CAAC,CAAA;AAIF;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,MAAyB,OAAO,CAAC,GAAG;IAClE,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;IAC3C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM;aAC/B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;aAChE,IAAI,CAAC,IAAI,CAAC,CAAA;QACb,MAAM,IAAI,KAAK,CAAC,yBAAyB,MAAM,EAAE,CAAC,CAAA;IACpD,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAA;AACpB,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,OAAgB;IACvD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,OAAO,IAAI,wBAAwB,IAAI,0BAA0B,CAAC,CAAA;IACpF,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,QAAiB;IACzD,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAA;AACtC,CAAC"}
@@ -1,13 +0,0 @@
1
- /**
2
- * Computes a shallow diff between two records. Returns an entry per
3
- * field whose value changed, with `before` and `after` values.
4
- *
5
- * Field equality is determined by JSON-string equality for objects and
6
- * arrays, and `===` for primitives. That is sufficient for audit-log
7
- * payloads where structures are JSON-shaped to begin with.
8
- */
9
- export declare function shallowDiff<T extends Record<string, unknown>>(before: T, after: T): Record<string, {
10
- before: unknown;
11
- after: unknown;
12
- }>;
13
- //# sourceMappingURL=diff.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/utils/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3D,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,CAAC,GACP,MAAM,CAAC,MAAM,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CASrD"}
@@ -1,31 +0,0 @@
1
- /**
2
- * Computes a shallow diff between two records. Returns an entry per
3
- * field whose value changed, with `before` and `after` values.
4
- *
5
- * Field equality is determined by JSON-string equality for objects and
6
- * arrays, and `===` for primitives. That is sufficient for audit-log
7
- * payloads where structures are JSON-shaped to begin with.
8
- */
9
- export function shallowDiff(before, after) {
10
- const diff = {};
11
- const keys = new Set([...Object.keys(before), ...Object.keys(after)]);
12
- for (const key of keys) {
13
- if (!isEqual(before[key], after[key])) {
14
- diff[key] = { before: before[key], after: after[key] };
15
- }
16
- }
17
- return diff;
18
- }
19
- function isEqual(a, b) {
20
- if (Object.is(a, b))
21
- return true;
22
- if (a === null || b === null || a === undefined || b === undefined)
23
- return false;
24
- if (typeof a !== typeof b)
25
- return false;
26
- if (typeof a === 'object') {
27
- return JSON.stringify(a) === JSON.stringify(b);
28
- }
29
- return false;
30
- }
31
- //# sourceMappingURL=diff.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/utils/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CACzB,MAAS,EACT,KAAQ;IAER,MAAM,IAAI,GAAwD,EAAE,CAAA;IACpE,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACrE,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,CAAA;QACxD,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAS,OAAO,CAAC,CAAU,EAAE,CAAU;IACrC,IAAI,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IAChC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO,KAAK,CAAA;IAChF,IAAI,OAAO,CAAC,KAAK,OAAO,CAAC;QAAE,OAAO,KAAK,CAAA;IACvC,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;IAChD,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -1,6 +0,0 @@
1
- /**
2
- * Generates a short, URL-safe, hex-encoded random ID. Optionally prefixed
3
- * with a token category (e.g. `evt_a1b2c3...`).
4
- */
5
- export declare function generateId(prefix?: string): string;
6
- //# sourceMappingURL=id.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../src/utils/id.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAOlD"}
package/dist/utils/id.js DELETED
@@ -1,13 +0,0 @@
1
- /**
2
- * Generates a short, URL-safe, hex-encoded random ID. Optionally prefixed
3
- * with a token category (e.g. `evt_a1b2c3...`).
4
- */
5
- export function generateId(prefix) {
6
- const bytes = new Uint8Array(12);
7
- crypto.getRandomValues(bytes);
8
- const id = Array.from(bytes)
9
- .map((b) => b.toString(16).padStart(2, '0'))
10
- .join('');
11
- return prefix ? `${prefix}_${id}` : id;
12
- }
13
- //# sourceMappingURL=id.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/utils/id.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,MAAe;IACxC,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAA;IAChC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAA;IAC7B,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;SACzB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SAC3C,IAAI,CAAC,EAAE,CAAC,CAAA;IACX,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;AACxC,CAAC"}