@forumone/throughline-audit 0.0.1 → 0.2.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 (48) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +69 -28
  4. package/dist/formatting/index.d.ts +40 -0
  5. package/dist/formatting/index.d.ts.map +1 -0
  6. package/dist/formatting/index.js +73 -0
  7. package/dist/formatting/index.js.map +1 -0
  8. package/dist/index.d.ts +7 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +5 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/options.d.ts +21 -0
  13. package/dist/options.d.ts.map +1 -0
  14. package/dist/options.js +9 -0
  15. package/dist/options.js.map +1 -0
  16. package/dist/plugin.d.ts +11 -0
  17. package/dist/plugin.d.ts.map +1 -0
  18. package/dist/plugin.js +76 -0
  19. package/dist/plugin.js.map +1 -0
  20. package/dist/tools/access.d.ts +11 -0
  21. package/dist/tools/access.d.ts.map +1 -0
  22. package/dist/tools/access.js +15 -0
  23. package/dist/tools/access.js.map +1 -0
  24. package/dist/tools/get-change-history.d.ts +23 -0
  25. package/dist/tools/get-change-history.d.ts.map +1 -0
  26. package/dist/tools/get-change-history.js +44 -0
  27. package/dist/tools/get-change-history.js.map +1 -0
  28. package/dist/tools/get-recent-failures.d.ts +23 -0
  29. package/dist/tools/get-recent-failures.d.ts.map +1 -0
  30. package/dist/tools/get-recent-failures.js +56 -0
  31. package/dist/tools/get-recent-failures.js.map +1 -0
  32. package/dist/tools/index.d.ts +12 -0
  33. package/dist/tools/index.d.ts.map +1 -0
  34. package/dist/tools/index.js +7 -0
  35. package/dist/tools/index.js.map +1 -0
  36. package/dist/tools/query-audit.d.ts +53 -0
  37. package/dist/tools/query-audit.d.ts.map +1 -0
  38. package/dist/tools/query-audit.js +78 -0
  39. package/dist/tools/query-audit.js.map +1 -0
  40. package/dist/tools/what-changed-in-range.d.ts +23 -0
  41. package/dist/tools/what-changed-in-range.d.ts.map +1 -0
  42. package/dist/tools/what-changed-in-range.js +86 -0
  43. package/dist/tools/what-changed-in-range.js.map +1 -0
  44. package/dist/tools/who-changed-what.d.ts +38 -0
  45. package/dist/tools/who-changed-what.d.ts.map +1 -0
  46. package/dist/tools/who-changed-what.js +66 -0
  47. package/dist/tools/who-changed-what.js.map +1 -0
  48. package/package.json +60 -7
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @forumone/throughline-audit
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 68cad81: Initial release of the audit query server. Five read-only MCP tools surface the audit log written by `@forumone/throughline-core`'s `auditPlugin`: `query_audit` (general filter), `get_change_history` (single-document history), `who_changed_what` (user activity, defaults to the caller), `what_changed_in_range` (grouped time-bounded summary), `get_recent_failures` (recent `success=false` events). Output is formatted for conversational use — relative times, named actors, prose summaries. Admin / editor gate by default, with `who_changed_what` always allowing self-lookup.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Forum One Communications Corporation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,45 +1,86 @@
1
1
  # @forumone/throughline-audit
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ Read-only MCP query tools over the Throughline audit log. Pairs with the writer in `@forumone/throughline-core`: `auditPlugin` writes records, `auditQueryPlugin` exposes them as conversational query tools.
4
4
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
5
+ ## What this package provides
6
6
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
7
+ Five MCP tools served at `/api/audit/mcp`. All are read-only and emit no audit events of their own.
8
8
 
9
- ## Purpose
9
+ | Tool | Use it for | Default access |
10
+ |---|---|---|
11
+ | `query_audit` | General-purpose filter (collection, document, actor, action, server, date range, failures) | admin / editor |
12
+ | `get_change_history` | Chronological history of one document, with diffs | admin / editor |
13
+ | `who_changed_what` | A user's recent activity. Defaults to the authenticated caller, so anyone can ask about their own changes | self always; others require admin / editor |
14
+ | `what_changed_in_range` | Counts grouped by action / actor / collection / server over a date range | admin / editor |
15
+ | `get_recent_failures` | `success=false` events in the last N hours, optionally filtered by server | admin / editor |
10
16
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@forumone/throughline-audit`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
17
+ Each tool returns conversational output: relative times ("2 hours ago"), named actors (`userName` then `apiKeyName` then `system` then `unknown`), and prose summaries.
15
18
 
16
- ## What is OIDC Trusted Publishing?
19
+ ## Installation
17
20
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
21
+ ```bash
22
+ pnpm add @forumone/throughline-audit
23
+ ```
19
24
 
20
- ## Setup Instructions
25
+ Peers: `payload@^3.0.0`. Required runtime peer: `@forumone/throughline-core` (the audit log writer).
21
26
 
22
- To properly configure OIDC trusted publishing for this package:
27
+ ## Usage
23
28
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
29
+ ```ts
30
+ import { buildConfig } from 'payload'
31
+ import { auditPlugin, createInngestClient } from '@forumone/throughline-core'
32
+ import { auditQueryPlugin } from '@forumone/throughline-audit'
28
33
 
29
- ## DO NOT USE THIS PACKAGE
34
+ const inngest = createInngestClient({ id: 'my-site' })
30
35
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
36
+ export default buildConfig({
37
+ // collections, db, secret...
38
+ plugins: [
39
+ auditPlugin({ inngest }), // writes
40
+ auditQueryPlugin({}), // reads
41
+ ],
42
+ })
43
+ ```
36
44
 
37
- ## More Information
45
+ The query plugin requires the `audit-log` capability. If `auditPlugin` is not registered first, initialization fails fast with a clear message.
38
46
 
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
47
+ ## Why a separate package?
42
48
 
43
- ---
49
+ Core writes audit events; this package exposes them. Splitting reads from writes lets clients deploy the writer (which is mandatory) without exposing query tools to MCP clients that don't need them. It also keeps the read-side surface (formatting, access control, paginated tools) out of the core's hot path.
44
50
 
45
- **Maintained for OIDC setup purposes only**
51
+ ## Why purpose-built tools instead of a raw query?
52
+
53
+ Raw collection access via Payload MCP would give Claude too much: pagination semantics for conversational UX are wrong, sensitive fields are easy to leak, and raw JSON is harder to relay than formatted prose. The five tools here are bounded — every result set has a default limit and a documented purpose — and the formatted output is what Claude relays directly.
54
+
55
+ ## Access control
56
+
57
+ - The default predicate (`isAuditReader`) admits `admin` and `editor` roles.
58
+ - `who_changed_what` defaults `actorId` to the authenticated caller, so any role can ask about their own activity.
59
+ - Looking up another user's activity requires admin / editor.
60
+ - Override the predicate via the `readAccess` option if your role model differs.
61
+
62
+ ```ts
63
+ auditQueryPlugin({
64
+ readAccess: (req) => {
65
+ const roles = (req.user?.roles as string[] | undefined) ?? []
66
+ return roles.includes('observability')
67
+ },
68
+ })
69
+ ```
70
+
71
+ > Note: `readAccess` currently controls the underlying collection read (via the slug match with `auditPlugin`). The MCP tools layer their own admin/editor gate on top.
72
+
73
+ ## Options
74
+
75
+ | Option | Type | Default | Notes |
76
+ |---|---|---|---|
77
+ | `routePrefix` | `string` | `/audit` | Payload prepends `/api`, so the MCP endpoint lands at `/api/audit/mcp` |
78
+ | `collectionSlug` | `string` | `audit-events` | Must match the slug used by core's `auditPlugin` |
79
+ | `readAccess` | `(req) => boolean` | admin / editor | Custom predicate for read-side access |
80
+ | `enabled` | `boolean` | `true` | Set to `false` to no-op the plugin |
81
+ | `logger` | `Logger` | `defaultLogger` | Standard Throughline logger |
82
+
83
+ ## Related packages
84
+
85
+ - `@forumone/throughline-core` — required peer; provides the audit collection, writer, and event taxonomy
86
+ - `@forumone/throughline-publishing`, `@forumone/throughline-approvals` — write audit events that this package surfaces
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Conversational-output helpers for audit records. The query tools return
3
+ * objects shaped via `formatAuditEvent` so Claude can relay them to users
4
+ * without reformatting raw collection JSON.
5
+ */
6
+ export interface FormattedAuditEvent {
7
+ when: string;
8
+ whenIso: string;
9
+ who: string;
10
+ what: string;
11
+ why?: string;
12
+ prompt?: string;
13
+ changesSummary?: string;
14
+ diff?: Record<string, {
15
+ before: unknown;
16
+ after: unknown;
17
+ }>;
18
+ success: boolean;
19
+ errorMessage?: string;
20
+ action: string;
21
+ mcpServer: string;
22
+ mcpTool: string;
23
+ targetCollection?: string;
24
+ targetId?: string;
25
+ targetTitle?: string;
26
+ }
27
+ /**
28
+ * Formats an ISO timestamp as a relative time string. Crude but
29
+ * conversational: "just now", "5 minutes ago", "3 hours ago", "2 days ago",
30
+ * or an `YYYY-MM-DD` for anything older than a week.
31
+ */
32
+ export declare function formatRelativeTime(iso: string, now?: number): string;
33
+ /**
34
+ * Maps a raw audit-collection record to the conversational shape the query
35
+ * tools return. Internal-only fields (`consumedTokens`, `notifiedApprovers`)
36
+ * are dropped; ISO timestamps are kept alongside the relative form so the
37
+ * caller can sort or render at their preference.
38
+ */
39
+ export declare function formatAuditEvent(raw: Record<string, unknown>, now?: number): FormattedAuditEvent;
40
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/formatting/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IAC1D,OAAO,EAAE,OAAO,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAID;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,MAAM,CAcxE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,SAAa,GACf,mBAAmB,CAgCrB"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Conversational-output helpers for audit records. The query tools return
3
+ * objects shaped via `formatAuditEvent` so Claude can relay them to users
4
+ * without reformatting raw collection JSON.
5
+ */
6
+ const DAY_IN_MS = 24 * 60 * 60 * 1000;
7
+ /**
8
+ * Formats an ISO timestamp as a relative time string. Crude but
9
+ * conversational: "just now", "5 minutes ago", "3 hours ago", "2 days ago",
10
+ * or an `YYYY-MM-DD` for anything older than a week.
11
+ */
12
+ export function formatRelativeTime(iso, now = Date.now()) {
13
+ const then = new Date(iso).getTime();
14
+ if (Number.isNaN(then))
15
+ return iso;
16
+ const diffMs = Math.max(0, now - then);
17
+ const seconds = Math.floor(diffMs / 1000);
18
+ const minutes = Math.floor(seconds / 60);
19
+ const hours = Math.floor(minutes / 60);
20
+ const days = Math.floor(hours / 24);
21
+ if (seconds < 60)
22
+ return 'just now';
23
+ if (minutes < 60)
24
+ return `${minutes} minute${minutes === 1 ? '' : 's'} ago`;
25
+ if (hours < 24)
26
+ return `${hours} hour${hours === 1 ? '' : 's'} ago`;
27
+ if (diffMs < 7 * DAY_IN_MS)
28
+ return `${days} day${days === 1 ? '' : 's'} ago`;
29
+ return iso.slice(0, 10);
30
+ }
31
+ /**
32
+ * Maps a raw audit-collection record to the conversational shape the query
33
+ * tools return. Internal-only fields (`consumedTokens`, `notifiedApprovers`)
34
+ * are dropped; ISO timestamps are kept alongside the relative form so the
35
+ * caller can sort or render at their preference.
36
+ */
37
+ export function formatAuditEvent(raw, now = Date.now()) {
38
+ const actor = (raw['actor'] ?? {});
39
+ const userName = typeof actor['userName'] === 'string' ? actor['userName'] : null;
40
+ const apiKeyName = typeof actor['apiKeyName'] === 'string' ? actor['apiKeyName'] : null;
41
+ const actorType = typeof actor['type'] === 'string' ? actor['type'] : 'unknown';
42
+ const who = userName ?? apiKeyName ?? (actorType === 'system' ? 'system' : 'unknown');
43
+ const createdAt = typeof raw['createdAt'] === 'string' ? raw['createdAt'] : '';
44
+ const formatted = {
45
+ when: createdAt ? formatRelativeTime(createdAt, now) : 'unknown',
46
+ whenIso: createdAt,
47
+ who,
48
+ what: typeof raw['summary'] === 'string' ? raw['summary'] : String(raw['action'] ?? ''),
49
+ success: raw['success'] !== false,
50
+ action: typeof raw['action'] === 'string' ? raw['action'] : 'unknown',
51
+ mcpServer: typeof raw['mcpServer'] === 'string' ? raw['mcpServer'] : 'unknown',
52
+ mcpTool: typeof raw['mcpTool'] === 'string' ? raw['mcpTool'] : 'unknown',
53
+ };
54
+ if (typeof raw['reasoning'] === 'string')
55
+ formatted.why = raw['reasoning'];
56
+ if (typeof raw['prompt'] === 'string')
57
+ formatted.prompt = raw['prompt'];
58
+ if (typeof raw['changesSummary'] === 'string')
59
+ formatted.changesSummary = raw['changesSummary'];
60
+ if (raw['diff'] && typeof raw['diff'] === 'object') {
61
+ formatted.diff = raw['diff'];
62
+ }
63
+ if (typeof raw['errorMessage'] === 'string')
64
+ formatted.errorMessage = raw['errorMessage'];
65
+ if (typeof raw['targetCollection'] === 'string')
66
+ formatted.targetCollection = raw['targetCollection'];
67
+ if (typeof raw['targetId'] === 'string')
68
+ formatted.targetId = raw['targetId'];
69
+ if (typeof raw['targetTitle'] === 'string')
70
+ formatted.targetTitle = raw['targetTitle'];
71
+ return formatted;
72
+ }
73
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/formatting/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAqBH,MAAM,SAAS,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AAErC;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAW,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IAC9D,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAA;IACpC,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,GAAG,CAAA;IAClC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC,CAAA;IACtC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAA;IACxC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAA;IACtC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,CAAA;IAEnC,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,UAAU,CAAA;IACnC,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,GAAG,OAAO,UAAU,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,CAAA;IAC3E,IAAI,KAAK,GAAG,EAAE;QAAE,OAAO,GAAG,KAAK,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,CAAA;IACnE,IAAI,MAAM,GAAG,CAAC,GAAG,SAAS;QAAE,OAAO,GAAG,IAAI,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,CAAA;IAC5E,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AACzB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAC9B,GAA4B,EAC5B,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IAEhB,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAA4B,CAAA;IAC7D,MAAM,QAAQ,GAAG,OAAO,KAAK,CAAC,UAAU,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACjF,MAAM,UAAU,GAAG,OAAO,KAAK,CAAC,YAAY,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACvF,MAAM,SAAS,GAAG,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAC/E,MAAM,GAAG,GAAG,QAAQ,IAAI,UAAU,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;IAErF,MAAM,SAAS,GAAG,OAAO,GAAG,CAAC,WAAW,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAE9E,MAAM,SAAS,GAAwB;QACrC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS;QAChE,OAAO,EAAE,SAAS;QAClB,GAAG;QACH,IAAI,EAAE,OAAO,GAAG,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACvF,OAAO,EAAE,GAAG,CAAC,SAAS,CAAC,KAAK,KAAK;QACjC,MAAM,EAAE,OAAO,GAAG,CAAC,QAAQ,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS;QACrE,SAAS,EAAE,OAAO,GAAG,CAAC,WAAW,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS;QAC9E,OAAO,EAAE,OAAO,GAAG,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS;KACzE,CAAA;IAED,IAAI,OAAO,GAAG,CAAC,WAAW,CAAC,KAAK,QAAQ;QAAE,SAAS,CAAC,GAAG,GAAG,GAAG,CAAC,WAAW,CAAC,CAAA;IAC1E,IAAI,OAAO,GAAG,CAAC,QAAQ,CAAC,KAAK,QAAQ;QAAE,SAAS,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAA;IACvE,IAAI,OAAO,GAAG,CAAC,gBAAgB,CAAC,KAAK,QAAQ;QAAE,SAAS,CAAC,cAAc,GAAG,GAAG,CAAC,gBAAgB,CAAC,CAAA;IAC/F,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,QAAQ,EAAE,CAAC;QACnD,SAAS,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAwD,CAAA;IACrF,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,cAAc,CAAC,KAAK,QAAQ;QAAE,SAAS,CAAC,YAAY,GAAG,GAAG,CAAC,cAAc,CAAC,CAAA;IACzF,IAAI,OAAO,GAAG,CAAC,kBAAkB,CAAC,KAAK,QAAQ;QAAE,SAAS,CAAC,gBAAgB,GAAG,GAAG,CAAC,kBAAkB,CAAC,CAAA;IACrG,IAAI,OAAO,GAAG,CAAC,UAAU,CAAC,KAAK,QAAQ;QAAE,SAAS,CAAC,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC,CAAA;IAC7E,IAAI,OAAO,GAAG,CAAC,aAAa,CAAC,KAAK,QAAQ;QAAE,SAAS,CAAC,WAAW,GAAG,GAAG,CAAC,aAAa,CAAC,CAAA;IAEtF,OAAO,SAAS,CAAA;AAClB,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { auditQueryPlugin } from './plugin.js';
2
+ export type { AuditQueryPluginOptions } from './options.js';
3
+ export { DEFAULT_AUDIT_COLLECTION_SLUG } from './options.js';
4
+ export { formatAuditEvent, formatRelativeTime } from './formatting/index.js';
5
+ export type { FormattedAuditEvent } from './formatting/index.js';
6
+ export { createQueryAuditTool, createGetChangeHistoryTool, createWhoChangedWhatTool, createWhatChangedInRangeTool, createGetRecentFailuresTool, } from './tools/index.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,YAAY,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AAC3D,OAAO,EAAE,6BAA6B,EAAE,MAAM,cAAc,CAAA;AAE5D,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC5E,YAAY,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAEhE,OAAO,EACL,oBAAoB,EACpB,0BAA0B,EAC1B,wBAAwB,EACxB,4BAA4B,EAC5B,2BAA2B,GAC5B,MAAM,kBAAkB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { auditQueryPlugin } from './plugin.js';
2
+ export { DEFAULT_AUDIT_COLLECTION_SLUG } from './options.js';
3
+ export { formatAuditEvent, formatRelativeTime } from './formatting/index.js';
4
+ export { createQueryAuditTool, createGetChangeHistoryTool, createWhoChangedWhatTool, createWhatChangedInRangeTool, createGetRecentFailuresTool, } from './tools/index.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAE9C,OAAO,EAAE,6BAA6B,EAAE,MAAM,cAAc,CAAA;AAE5D,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAG5E,OAAO,EACL,oBAAoB,EACpB,0BAA0B,EAC1B,wBAAwB,EACxB,4BAA4B,EAC5B,2BAA2B,GAC5B,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,21 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ import type { BaseCorePluginOptions } from '@forumone/throughline-plugin-contract';
3
+ export interface AuditQueryPluginOptions extends BaseCorePluginOptions {
4
+ /**
5
+ * Override the audit collection slug. Must match the slug `auditPlugin`
6
+ * (in `@forumone/throughline-core`) writes to. Default: `'audit-events'`.
7
+ */
8
+ collectionSlug?: string;
9
+ /**
10
+ * Custom access-control function for read operations. Returns true to
11
+ * allow reads. Defaults to admin and editor roles.
12
+ */
13
+ readAccess?: (req: PayloadRequest) => boolean;
14
+ }
15
+ export declare const DEFAULT_AUDIT_COLLECTION_SLUG = "audit-events";
16
+ /**
17
+ * Validates options at load time. Currently a passthrough; reserved for
18
+ * future option-shape additions so callers always go through the same gate.
19
+ */
20
+ export declare function validateOptions(options: AuditQueryPluginOptions): AuditQueryPluginOptions;
21
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAA;AAElF,MAAM,WAAW,uBAAwB,SAAQ,qBAAqB;IACpE;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAA;CAC9C;AAED,eAAO,MAAM,6BAA6B,iBAAiB,CAAA;AAE3D;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,uBAAuB,GAAG,uBAAuB,CAEzF"}
@@ -0,0 +1,9 @@
1
+ export const DEFAULT_AUDIT_COLLECTION_SLUG = 'audit-events';
2
+ /**
3
+ * Validates options at load time. Currently a passthrough; reserved for
4
+ * future option-shape additions so callers always go through the same gate.
5
+ */
6
+ export function validateOptions(options) {
7
+ return options;
8
+ }
9
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAgBA,MAAM,CAAC,MAAM,6BAA6B,GAAG,cAAc,CAAA;AAE3D;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,OAAgC;IAC9D,OAAO,OAAO,CAAA;AAChB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { CorePlugin } from '@forumone/throughline-plugin-contract';
2
+ import { type AuditQueryPluginOptions } from './options.js';
3
+ /**
4
+ * Read-only MCP server over the audit log. Pairs with `auditPlugin` from
5
+ * `@forumone/throughline-core` (which writes records). The query plugin
6
+ * requires the `audit-log` capability and refuses to initialize without it.
7
+ *
8
+ * Exported as `auditQueryPlugin` to disambiguate from core's `auditPlugin`.
9
+ */
10
+ export declare const auditQueryPlugin: CorePlugin<AuditQueryPluginOptions>;
11
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAqB,MAAM,uCAAuC,CAAA;AAG1F,OAAO,EACL,KAAK,uBAAuB,EAG7B,MAAM,cAAc,CAAA;AAerB;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,UAAU,CAAC,uBAAuB,CAwE9D,CAAA"}
package/dist/plugin.js ADDED
@@ -0,0 +1,76 @@
1
+ import { getPluginRegistry } from '@forumone/throughline-plugin-contract';
2
+ import { createMcpHandler, createNamedLogger, defaultLogger } from '@forumone/throughline-core';
3
+ import { DEFAULT_AUDIT_COLLECTION_SLUG, validateOptions, } from './options.js';
4
+ import { createGetChangeHistoryTool, createGetRecentFailuresTool, createQueryAuditTool, createWhatChangedInRangeTool, createWhoChangedWhatTool, } from './tools/index.js';
5
+ const PLUGIN_ID = '@forumone/throughline-audit';
6
+ const PLUGIN_VERSION = '0.1.0';
7
+ const MCP_HANDLER_SYMBOL = Symbol.for('@forumone/throughline/audit-mcp-handler');
8
+ /**
9
+ * Read-only MCP server over the audit log. Pairs with `auditPlugin` from
10
+ * `@forumone/throughline-core` (which writes records). The query plugin
11
+ * requires the `audit-log` capability and refuses to initialize without it.
12
+ *
13
+ * Exported as `auditQueryPlugin` to disambiguate from core's `auditPlugin`.
14
+ */
15
+ export const auditQueryPlugin = (rawOptions) => (incomingConfig) => {
16
+ if (rawOptions.enabled === false)
17
+ return incomingConfig;
18
+ const options = validateOptions(rawOptions);
19
+ const routePrefix = options.routePrefix ?? '/audit';
20
+ const collectionSlug = options.collectionSlug ?? DEFAULT_AUDIT_COLLECTION_SLUG;
21
+ const logger = createNamedLogger('audit-query', options.logger ?? defaultLogger);
22
+ return {
23
+ ...incomingConfig,
24
+ endpoints: [
25
+ ...(incomingConfig.endpoints ?? []),
26
+ {
27
+ path: `${routePrefix}/mcp`,
28
+ method: 'post',
29
+ handler: async (req) => {
30
+ const handler = req.payload[MCP_HANDLER_SYMBOL];
31
+ if (!handler) {
32
+ return new Response(JSON.stringify({ error: 'Audit query MCP not initialized' }), { status: 503, headers: { 'content-type': 'application/json' } });
33
+ }
34
+ return handler(req);
35
+ },
36
+ },
37
+ ],
38
+ onInit: async (payload) => {
39
+ if (incomingConfig.onInit)
40
+ await incomingConfig.onInit(payload);
41
+ const registry = getPluginRegistry(payload);
42
+ registry.requireCapability('audit-log', PLUGIN_ID);
43
+ const deps = { payload, collectionSlug };
44
+ const tools = [
45
+ createQueryAuditTool(deps),
46
+ createGetChangeHistoryTool(deps),
47
+ createWhoChangedWhatTool(deps),
48
+ createWhatChangedInRangeTool(deps),
49
+ createGetRecentFailuresTool(deps),
50
+ ];
51
+ const handler = createMcpHandler({
52
+ payload,
53
+ serverName: 'audit',
54
+ tools,
55
+ logger,
56
+ });
57
+ Object.defineProperty(payload, MCP_HANDLER_SYMBOL, {
58
+ value: handler,
59
+ enumerable: false,
60
+ writable: false,
61
+ configurable: false,
62
+ });
63
+ registry.register({
64
+ id: PLUGIN_ID,
65
+ version: PLUGIN_VERSION,
66
+ capabilities: ['audit-query'],
67
+ });
68
+ logger.info('Audit query server ready', {
69
+ collectionSlug,
70
+ routePrefix,
71
+ toolCount: tools.length,
72
+ });
73
+ },
74
+ };
75
+ };
76
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { McpToolContext } from '@forumone/throughline-plugin-contract';
2
+ /**
3
+ * Default read-access predicate. Admins and editors can read everything;
4
+ * anyone else is treated as scoped to their own actions and gets a
5
+ * "permission-denied" envelope from broad-scope tools.
6
+ */
7
+ export declare function isAuditReader(ctx: McpToolContext): boolean;
8
+ export declare function deniedEnvelope(reason: string): {
9
+ error: string;
10
+ };
11
+ //# sourceMappingURL=access.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/tools/access.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AAE3E;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAI1D;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAEhE"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Default read-access predicate. Admins and editors can read everything;
3
+ * anyone else is treated as scoped to their own actions and gets a
4
+ * "permission-denied" envelope from broad-scope tools.
5
+ */
6
+ export function isAuditReader(ctx) {
7
+ if (!ctx.user)
8
+ return false;
9
+ const roles = ctx.user.roles;
10
+ return roles.includes('admin') || roles.includes('editor');
11
+ }
12
+ export function deniedEnvelope(reason) {
13
+ return { error: reason };
14
+ }
15
+ //# sourceMappingURL=access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access.js","sourceRoot":"","sources":["../../src/tools/access.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,GAAmB;IAC/C,IAAI,CAAC,GAAG,CAAC,IAAI;QAAE,OAAO,KAAK,CAAA;IAC3B,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAA;IAC5B,OAAO,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;AAC5D,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AAC1B,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { z } from 'zod';
2
+ import type { Payload } from 'payload';
3
+ import type { McpToolDefinition } from '@forumone/throughline-plugin-contract';
4
+ export interface GetChangeHistoryDeps {
5
+ payload: Payload;
6
+ collectionSlug: string;
7
+ }
8
+ declare const inputSchema: z.ZodObject<{
9
+ targetCollection: z.ZodString;
10
+ targetId: z.ZodString;
11
+ limit: z.ZodOptional<z.ZodNumber>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ targetCollection: string;
14
+ targetId: string;
15
+ limit?: number | undefined;
16
+ }, {
17
+ targetCollection: string;
18
+ targetId: string;
19
+ limit?: number | undefined;
20
+ }>;
21
+ export declare function createGetChangeHistoryTool(deps: GetChangeHistoryDeps): McpToolDefinition<typeof inputSchema>;
22
+ export {};
23
+ //# sourceMappingURL=get-change-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-change-history.d.ts","sourceRoot":"","sources":["../../src/tools/get-change-history.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAI9E,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,QAAA,MAAM,WAAW;;;;;;;;;;;;EAUf,CAAA;AAEF,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,oBAAoB,GACzB,iBAAiB,CAAC,OAAO,WAAW,CAAC,CA+BvC"}
@@ -0,0 +1,44 @@
1
+ import { z } from 'zod';
2
+ import { formatAuditEvent } from '../formatting/index.js';
3
+ import { deniedEnvelope, isAuditReader } from './access.js';
4
+ const inputSchema = z.object({
5
+ targetCollection: z.string().describe('Collection slug (e.g. "pages").'),
6
+ targetId: z.string().describe('Document ID within the collection.'),
7
+ limit: z
8
+ .number()
9
+ .int()
10
+ .positive()
11
+ .max(200)
12
+ .optional()
13
+ .describe('Max events to return. Default: 50.'),
14
+ });
15
+ export function createGetChangeHistoryTool(deps) {
16
+ return {
17
+ name: 'get_change_history',
18
+ description: 'Returns the complete chronological history of actions on a single document, including diffs. Use to answer "what has happened to this page?" or "who changed X?". Most-recent first.',
19
+ inputSchema,
20
+ handler: async (input, ctx) => {
21
+ if (!isAuditReader(ctx)) {
22
+ return deniedEnvelope('Only admins and editors can read a document\'s change history.');
23
+ }
24
+ const result = await deps.payload.find({
25
+ collection: deps.collectionSlug,
26
+ where: {
27
+ and: [
28
+ { targetCollection: { equals: input.targetCollection } },
29
+ { targetId: { equals: input.targetId } },
30
+ ],
31
+ },
32
+ sort: '-createdAt',
33
+ limit: input.limit ?? 50,
34
+ });
35
+ return {
36
+ targetCollection: input.targetCollection,
37
+ targetId: input.targetId,
38
+ eventCount: result.totalDocs,
39
+ history: result.docs.map((doc) => formatAuditEvent(doc)),
40
+ };
41
+ },
42
+ };
43
+ }
44
+ //# sourceMappingURL=get-change-history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-change-history.js","sourceRoot":"","sources":["../../src/tools/get-change-history.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAO3D,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3B,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;IACxE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,oCAAoC,CAAC;IACnE,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,GAAG,CAAC,GAAG,CAAC;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,oCAAoC,CAAC;CAClD,CAAC,CAAA;AAEF,MAAM,UAAU,0BAA0B,CACxC,IAA0B;IAE1B,OAAO;QACL,IAAI,EAAE,oBAAoB;QAC1B,WAAW,EACT,sLAAsL;QACxL,WAAW;QACX,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAC5B,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB,OAAO,cAAc,CAAC,gEAAgE,CAAC,CAAA;YACzF,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;gBACrC,UAAU,EAAE,IAAI,CAAC,cAAc;gBAC/B,KAAK,EAAE;oBACL,GAAG,EAAE;wBACH,EAAE,gBAAgB,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,gBAAgB,EAAE,EAAE;wBACxD,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,QAAQ,EAAE,EAAE;qBACzC;iBACF;gBACD,IAAI,EAAE,YAAY;gBAClB,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE;aACzB,CAAC,CAAA;YAEF,OAAO;gBACL,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;gBACxC,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,UAAU,EAAE,MAAM,CAAC,SAAS;gBAC5B,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,gBAAgB,CAAC,GAA8B,CAAC,CAAC;aACpF,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { z } from 'zod';
2
+ import type { Payload } from 'payload';
3
+ import type { McpToolDefinition } from '@forumone/throughline-plugin-contract';
4
+ export interface GetRecentFailuresDeps {
5
+ payload: Payload;
6
+ collectionSlug: string;
7
+ }
8
+ declare const inputSchema: z.ZodObject<{
9
+ hours: z.ZodOptional<z.ZodNumber>;
10
+ mcpServer: z.ZodOptional<z.ZodString>;
11
+ limit: z.ZodOptional<z.ZodNumber>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ mcpServer?: string | undefined;
14
+ limit?: number | undefined;
15
+ hours?: number | undefined;
16
+ }, {
17
+ mcpServer?: string | undefined;
18
+ limit?: number | undefined;
19
+ hours?: number | undefined;
20
+ }>;
21
+ export declare function createGetRecentFailuresTool(deps: GetRecentFailuresDeps): McpToolDefinition<typeof inputSchema>;
22
+ export {};
23
+ //# sourceMappingURL=get-recent-failures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-recent-failures.d.ts","sourceRoot":"","sources":["../../src/tools/get-recent-failures.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,OAAO,EAAS,MAAM,SAAS,CAAA;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAI9E,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,QAAA,MAAM,WAAW;;;;;;;;;;;;EAmBf,CAAA;AAEF,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,qBAAqB,GAC1B,iBAAiB,CAAC,OAAO,WAAW,CAAC,CAkCvC"}
@@ -0,0 +1,56 @@
1
+ import { z } from 'zod';
2
+ import { formatAuditEvent } from '../formatting/index.js';
3
+ import { deniedEnvelope, isAuditReader } from './access.js';
4
+ const inputSchema = z.object({
5
+ hours: z
6
+ .number()
7
+ .int()
8
+ .positive()
9
+ .max(24 * 30)
10
+ .optional()
11
+ .describe('How far back to scan, in hours. Default: 24.'),
12
+ mcpServer: z
13
+ .string()
14
+ .optional()
15
+ .describe('Filter to a specific server (e.g. "publishing", "integrations").'),
16
+ limit: z
17
+ .number()
18
+ .int()
19
+ .positive()
20
+ .max(100)
21
+ .optional()
22
+ .describe('Max results. Default: 25.'),
23
+ });
24
+ export function createGetRecentFailuresTool(deps) {
25
+ return {
26
+ name: 'get_recent_failures',
27
+ description: 'Recent failed operations across all MCP servers. Use for "what broke recently?" or when diagnosing issues. Returns actions with success=false and their error messages, most recent first.',
28
+ inputSchema,
29
+ handler: async (input, ctx) => {
30
+ if (!isAuditReader(ctx)) {
31
+ return deniedEnvelope('Only admins and editors can review recent failures.');
32
+ }
33
+ const hours = input.hours ?? 24;
34
+ const since = new Date(Date.now() - hours * 60 * 60 * 1000).toISOString();
35
+ const conditions = [
36
+ { success: { equals: false } },
37
+ { createdAt: { greater_than_equal: since } },
38
+ ];
39
+ if (input.mcpServer)
40
+ conditions.push({ mcpServer: { equals: input.mcpServer } });
41
+ const result = await deps.payload.find({
42
+ collection: deps.collectionSlug,
43
+ where: { and: conditions },
44
+ sort: '-createdAt',
45
+ limit: input.limit ?? 25,
46
+ });
47
+ return {
48
+ hoursScanned: hours,
49
+ since,
50
+ failureCount: result.totalDocs,
51
+ failures: result.docs.map((doc) => formatAuditEvent(doc)),
52
+ };
53
+ },
54
+ };
55
+ }
56
+ //# sourceMappingURL=get-recent-failures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-recent-failures.js","sourceRoot":"","sources":["../../src/tools/get-recent-failures.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAO3D,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3B,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC;SACZ,QAAQ,EAAE;SACV,QAAQ,CAAC,8CAA8C,CAAC;IAC3D,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,kEAAkE,CAAC;IAC/E,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,GAAG,CAAC,GAAG,CAAC;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,2BAA2B,CAAC;CACzC,CAAC,CAAA;AAEF,MAAM,UAAU,2BAA2B,CACzC,IAA2B;IAE3B,OAAO;QACL,IAAI,EAAE,qBAAqB;QAC3B,WAAW,EACT,4LAA4L;QAC9L,WAAW;QACX,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAC5B,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB,OAAO,cAAc,CAAC,qDAAqD,CAAC,CAAA;YAC9E,CAAC;YAED,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,EAAE,CAAA;YAC/B,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAA;YACzE,MAAM,UAAU,GAAY;gBAC1B,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE;gBAC9B,EAAE,SAAS,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,EAAE;aAC7C,CAAA;YACD,IAAI,KAAK,CAAC,SAAS;gBAAE,UAAU,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;YAEhF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;gBACrC,UAAU,EAAE,IAAI,CAAC,cAAc;gBAC/B,KAAK,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE;gBAC1B,IAAI,EAAE,YAAY;gBAClB,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE;aACzB,CAAC,CAAA;YAEF,OAAO;gBACL,YAAY,EAAE,KAAK;gBACnB,KAAK;gBACL,YAAY,EAAE,MAAM,CAAC,SAAS;gBAC9B,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,gBAAgB,CAAC,GAA8B,CAAC,CAAC;aACrF,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,12 @@
1
+ export { createQueryAuditTool } from './query-audit.js';
2
+ export type { QueryAuditDeps } from './query-audit.js';
3
+ export { createGetChangeHistoryTool } from './get-change-history.js';
4
+ export type { GetChangeHistoryDeps } from './get-change-history.js';
5
+ export { createWhoChangedWhatTool } from './who-changed-what.js';
6
+ export type { WhoChangedWhatDeps } from './who-changed-what.js';
7
+ export { createWhatChangedInRangeTool } from './what-changed-in-range.js';
8
+ export type { WhatChangedInRangeDeps } from './what-changed-in-range.js';
9
+ export { createGetRecentFailuresTool } from './get-recent-failures.js';
10
+ export type { GetRecentFailuresDeps } from './get-recent-failures.js';
11
+ export { isAuditReader } from './access.js';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACvD,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAA;AACpE,YAAY,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAA;AACnE,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAA;AAChE,YAAY,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC/D,OAAO,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAA;AACzE,YAAY,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AACxE,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAA;AACtE,YAAY,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAA;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,7 @@
1
+ export { createQueryAuditTool } from './query-audit.js';
2
+ export { createGetChangeHistoryTool } from './get-change-history.js';
3
+ export { createWhoChangedWhatTool } from './who-changed-what.js';
4
+ export { createWhatChangedInRangeTool } from './what-changed-in-range.js';
5
+ export { createGetRecentFailuresTool } from './get-recent-failures.js';
6
+ export { isAuditReader } from './access.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAEvD,OAAO,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAA;AAEpE,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAA;AAEhE,OAAO,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAA;AAEzE,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAA;AAEtE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,53 @@
1
+ import { z } from 'zod';
2
+ import type { Payload } from 'payload';
3
+ import type { McpToolDefinition } from '@forumone/throughline-plugin-contract';
4
+ export interface QueryAuditDeps {
5
+ payload: Payload;
6
+ collectionSlug: string;
7
+ }
8
+ declare const inputSchema: z.ZodObject<{
9
+ targetCollection: z.ZodOptional<z.ZodString>;
10
+ targetId: z.ZodOptional<z.ZodString>;
11
+ actorId: z.ZodOptional<z.ZodString>;
12
+ action: z.ZodOptional<z.ZodString>;
13
+ mcpServer: z.ZodOptional<z.ZodString>;
14
+ dateRange: z.ZodOptional<z.ZodObject<{
15
+ from: z.ZodOptional<z.ZodString>;
16
+ to: z.ZodOptional<z.ZodString>;
17
+ }, "strip", z.ZodTypeAny, {
18
+ from?: string | undefined;
19
+ to?: string | undefined;
20
+ }, {
21
+ from?: string | undefined;
22
+ to?: string | undefined;
23
+ }>>;
24
+ onlyFailures: z.ZodOptional<z.ZodBoolean>;
25
+ limit: z.ZodOptional<z.ZodNumber>;
26
+ }, "strip", z.ZodTypeAny, {
27
+ action?: string | undefined;
28
+ mcpServer?: string | undefined;
29
+ targetCollection?: string | undefined;
30
+ targetId?: string | undefined;
31
+ actorId?: string | undefined;
32
+ dateRange?: {
33
+ from?: string | undefined;
34
+ to?: string | undefined;
35
+ } | undefined;
36
+ onlyFailures?: boolean | undefined;
37
+ limit?: number | undefined;
38
+ }, {
39
+ action?: string | undefined;
40
+ mcpServer?: string | undefined;
41
+ targetCollection?: string | undefined;
42
+ targetId?: string | undefined;
43
+ actorId?: string | undefined;
44
+ dateRange?: {
45
+ from?: string | undefined;
46
+ to?: string | undefined;
47
+ } | undefined;
48
+ onlyFailures?: boolean | undefined;
49
+ limit?: number | undefined;
50
+ }>;
51
+ export declare function createQueryAuditTool(deps: QueryAuditDeps): McpToolDefinition<typeof inputSchema>;
52
+ export {};
53
+ //# sourceMappingURL=query-audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-audit.d.ts","sourceRoot":"","sources":["../../src/tools/query-audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,OAAO,EAAS,MAAM,SAAS,CAAA;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAI9E,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,QAAA,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiCf,CAAA;AAEF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,cAAc,GAAG,iBAAiB,CAAC,OAAO,WAAW,CAAC,CAqChG"}
@@ -0,0 +1,78 @@
1
+ import { z } from 'zod';
2
+ import { formatAuditEvent } from '../formatting/index.js';
3
+ import { deniedEnvelope, isAuditReader } from './access.js';
4
+ const inputSchema = z.object({
5
+ targetCollection: z
6
+ .string()
7
+ .optional()
8
+ .describe('Filter to a specific collection (e.g. "pages").'),
9
+ targetId: z.string().optional().describe('Filter to a specific document ID.'),
10
+ actorId: z.string().optional().describe('Filter to a specific user ID.'),
11
+ action: z
12
+ .string()
13
+ .optional()
14
+ .describe('Filter to a specific action (e.g. "publishing.publish").'),
15
+ mcpServer: z
16
+ .string()
17
+ .optional()
18
+ .describe('Filter to a specific server (e.g. "publishing", "approvals").'),
19
+ dateRange: z
20
+ .object({
21
+ from: z.string().datetime().optional(),
22
+ to: z.string().datetime().optional(),
23
+ })
24
+ .optional()
25
+ .describe('ISO-8601 timestamps. Inclusive on both ends.'),
26
+ onlyFailures: z
27
+ .boolean()
28
+ .optional()
29
+ .describe('If true, only return actions where success=false.'),
30
+ limit: z
31
+ .number()
32
+ .int()
33
+ .positive()
34
+ .max(100)
35
+ .optional()
36
+ .describe('Max results. Default: 20.'),
37
+ });
38
+ export function createQueryAuditTool(deps) {
39
+ return {
40
+ name: 'query_audit',
41
+ description: "General-purpose audit log query. Filter by collection, document, actor, action, server, date range, or failure-only. Returns chronologically ordered results, most recent first. Use when you need a custom view of system activity that doesn't fit the more specific tools.",
42
+ inputSchema,
43
+ handler: async (input, ctx) => {
44
+ if (!isAuditReader(ctx)) {
45
+ return deniedEnvelope('Only admins and editors can run general audit queries. Try `who_changed_what` for your own activity.');
46
+ }
47
+ const conditions = [];
48
+ if (input.targetCollection)
49
+ conditions.push({ targetCollection: { equals: input.targetCollection } });
50
+ if (input.targetId)
51
+ conditions.push({ targetId: { equals: input.targetId } });
52
+ if (input.actorId)
53
+ conditions.push({ 'actor.userId': { equals: input.actorId } });
54
+ if (input.action)
55
+ conditions.push({ action: { equals: input.action } });
56
+ if (input.mcpServer)
57
+ conditions.push({ mcpServer: { equals: input.mcpServer } });
58
+ if (input.onlyFailures)
59
+ conditions.push({ success: { equals: false } });
60
+ if (input.dateRange?.from)
61
+ conditions.push({ createdAt: { greater_than_equal: input.dateRange.from } });
62
+ if (input.dateRange?.to)
63
+ conditions.push({ createdAt: { less_than_equal: input.dateRange.to } });
64
+ const result = await deps.payload.find({
65
+ collection: deps.collectionSlug,
66
+ ...(conditions.length > 0 ? { where: { and: conditions } } : {}),
67
+ sort: '-createdAt',
68
+ limit: input.limit ?? 20,
69
+ });
70
+ return {
71
+ total: result.totalDocs,
72
+ returned: result.docs.length,
73
+ events: result.docs.map((doc) => formatAuditEvent(doc)),
74
+ };
75
+ },
76
+ };
77
+ }
78
+ //# sourceMappingURL=query-audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-audit.js","sourceRoot":"","sources":["../../src/tools/query-audit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { z } from 'zod';
2
+ import type { Payload } from 'payload';
3
+ import type { McpToolDefinition } from '@forumone/throughline-plugin-contract';
4
+ export interface WhatChangedInRangeDeps {
5
+ payload: Payload;
6
+ collectionSlug: string;
7
+ }
8
+ declare const inputSchema: z.ZodObject<{
9
+ from: z.ZodString;
10
+ to: z.ZodString;
11
+ scanLimit: z.ZodOptional<z.ZodNumber>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ from: string;
14
+ to: string;
15
+ scanLimit?: number | undefined;
16
+ }, {
17
+ from: string;
18
+ to: string;
19
+ scanLimit?: number | undefined;
20
+ }>;
21
+ export declare function createWhatChangedInRangeTool(deps: WhatChangedInRangeDeps): McpToolDefinition<typeof inputSchema>;
22
+ export {};
23
+ //# sourceMappingURL=what-changed-in-range.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"what-changed-in-range.d.ts","sourceRoot":"","sources":["../../src/tools/what-changed-in-range.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAG9E,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,QAAA,MAAM,WAAW;;;;;;;;;;;;EAUf,CAAA;AAEF,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,sBAAsB,GAC3B,iBAAiB,CAAC,OAAO,WAAW,CAAC,CAsFvC"}
@@ -0,0 +1,86 @@
1
+ import { z } from 'zod';
2
+ import { deniedEnvelope, isAuditReader } from './access.js';
3
+ const inputSchema = z.object({
4
+ from: z.string().datetime().describe('ISO-8601 start of range (inclusive).'),
5
+ to: z.string().datetime().describe('ISO-8601 end of range (inclusive).'),
6
+ scanLimit: z
7
+ .number()
8
+ .int()
9
+ .positive()
10
+ .max(5000)
11
+ .optional()
12
+ .describe('Max events to scan when computing the summary. Default: 1000.'),
13
+ });
14
+ export function createWhatChangedInRangeTool(deps) {
15
+ return {
16
+ name: 'what_changed_in_range',
17
+ description: 'Summarized activity over a time range, grouped by action type, actor, and target collection. Use for "what happened last week?" or weekly review questions. Returns counts and top contributors rather than individual events. Caps the scan at 1000 events by default — for wider sweeps, raise scanLimit or use query_audit with paging.',
18
+ inputSchema,
19
+ handler: async (input, ctx) => {
20
+ if (!isAuditReader(ctx)) {
21
+ return deniedEnvelope('Only admins and editors can summarize activity ranges.');
22
+ }
23
+ const scanLimit = input.scanLimit ?? 1000;
24
+ const result = await deps.payload.find({
25
+ collection: deps.collectionSlug,
26
+ where: {
27
+ and: [
28
+ { createdAt: { greater_than_equal: input.from } },
29
+ { createdAt: { less_than_equal: input.to } },
30
+ ],
31
+ },
32
+ sort: '-createdAt',
33
+ limit: scanLimit,
34
+ });
35
+ const byAction = new Map();
36
+ const byActor = new Map();
37
+ const byCollection = new Map();
38
+ const byServer = new Map();
39
+ let failureCount = 0;
40
+ for (const doc of result.docs) {
41
+ const action = typeof doc['action'] === 'string' ? doc['action'] : 'unknown';
42
+ byAction.set(action, (byAction.get(action) ?? 0) + 1);
43
+ const actor = (doc['actor'] ?? {});
44
+ const actorId = typeof actor['userId'] === 'string'
45
+ ? actor['userId']
46
+ : typeof actor['type'] === 'string'
47
+ ? actor['type']
48
+ : 'unknown';
49
+ const actorName = typeof actor['userName'] === 'string' ? actor['userName'] : actorId;
50
+ const existing = byActor.get(actorId);
51
+ byActor.set(actorId, { name: actorName, count: (existing?.count ?? 0) + 1 });
52
+ const targetCollection = doc['targetCollection'];
53
+ if (typeof targetCollection === 'string') {
54
+ byCollection.set(targetCollection, (byCollection.get(targetCollection) ?? 0) + 1);
55
+ }
56
+ const server = typeof doc['mcpServer'] === 'string' ? doc['mcpServer'] : 'unknown';
57
+ byServer.set(server, (byServer.get(server) ?? 0) + 1);
58
+ if (doc['success'] === false)
59
+ failureCount += 1;
60
+ }
61
+ const totalDocs = result.totalDocs;
62
+ const truncated = totalDocs > result.docs.length;
63
+ return {
64
+ from: input.from,
65
+ to: input.to,
66
+ totalActions: totalDocs,
67
+ scanned: result.docs.length,
68
+ truncated,
69
+ ...(truncated
70
+ ? {
71
+ note: `Range contains ${totalDocs} events but only ${result.docs.length} were scanned for summary; raise scanLimit to widen the sweep.`,
72
+ }
73
+ : {}),
74
+ failureCount,
75
+ byAction: Object.fromEntries(Array.from(byAction.entries()).sort((a, b) => b[1] - a[1])),
76
+ topActors: Array.from(byActor.entries())
77
+ .map(([id, { name, count }]) => ({ id, name, count }))
78
+ .sort((a, b) => b.count - a.count)
79
+ .slice(0, 10),
80
+ byCollection: Object.fromEntries(Array.from(byCollection.entries()).sort((a, b) => b[1] - a[1])),
81
+ byServer: Object.fromEntries(Array.from(byServer.entries()).sort((a, b) => b[1] - a[1])),
82
+ };
83
+ },
84
+ };
85
+ }
86
+ //# sourceMappingURL=what-changed-in-range.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"what-changed-in-range.js","sourceRoot":"","sources":["../../src/tools/what-changed-in-range.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ import type { Payload } from 'payload';
3
+ import type { McpToolDefinition } from '@forumone/throughline-plugin-contract';
4
+ export interface WhoChangedWhatDeps {
5
+ payload: Payload;
6
+ collectionSlug: string;
7
+ }
8
+ declare const inputSchema: z.ZodObject<{
9
+ actorId: z.ZodOptional<z.ZodString>;
10
+ dateRange: z.ZodOptional<z.ZodObject<{
11
+ from: z.ZodOptional<z.ZodString>;
12
+ to: z.ZodOptional<z.ZodString>;
13
+ }, "strip", z.ZodTypeAny, {
14
+ from?: string | undefined;
15
+ to?: string | undefined;
16
+ }, {
17
+ from?: string | undefined;
18
+ to?: string | undefined;
19
+ }>>;
20
+ limit: z.ZodOptional<z.ZodNumber>;
21
+ }, "strip", z.ZodTypeAny, {
22
+ actorId?: string | undefined;
23
+ dateRange?: {
24
+ from?: string | undefined;
25
+ to?: string | undefined;
26
+ } | undefined;
27
+ limit?: number | undefined;
28
+ }, {
29
+ actorId?: string | undefined;
30
+ dateRange?: {
31
+ from?: string | undefined;
32
+ to?: string | undefined;
33
+ } | undefined;
34
+ limit?: number | undefined;
35
+ }>;
36
+ export declare function createWhoChangedWhatTool(deps: WhoChangedWhatDeps): McpToolDefinition<typeof inputSchema>;
37
+ export {};
38
+ //# sourceMappingURL=who-changed-what.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"who-changed-what.d.ts","sourceRoot":"","sources":["../../src/tools/who-changed-what.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,OAAO,EAAS,MAAM,SAAS,CAAA;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAI9E,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,QAAA,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkBf,CAAA;AAEF,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,kBAAkB,GACvB,iBAAiB,CAAC,OAAO,WAAW,CAAC,CAgDvC"}
@@ -0,0 +1,66 @@
1
+ import { z } from 'zod';
2
+ import { formatAuditEvent } from '../formatting/index.js';
3
+ import { deniedEnvelope, isAuditReader } from './access.js';
4
+ const inputSchema = z.object({
5
+ actorId: z
6
+ .string()
7
+ .optional()
8
+ .describe('User ID to look up. Defaults to the authenticated caller.'),
9
+ dateRange: z
10
+ .object({
11
+ from: z.string().datetime().optional(),
12
+ to: z.string().datetime().optional(),
13
+ })
14
+ .optional(),
15
+ limit: z
16
+ .number()
17
+ .int()
18
+ .positive()
19
+ .max(100)
20
+ .optional()
21
+ .describe('Max results. Default: 25.'),
22
+ });
23
+ export function createWhoChangedWhatTool(deps) {
24
+ return {
25
+ name: 'who_changed_what',
26
+ description: "A user's recent activity. Use when someone asks 'what has Sarah been working on?' or 'show me my changes today'. If actorId is omitted, defaults to the authenticated caller — so users can ask about themselves without knowing their ID. Other users' activity requires admin/editor.",
27
+ inputSchema,
28
+ handler: async (input, ctx) => {
29
+ const callerId = ctx.user?.id;
30
+ const actorId = input.actorId ?? callerId;
31
+ if (!actorId)
32
+ return deniedEnvelope('No actorId provided and no authenticated caller.');
33
+ const queryingSelf = !!callerId && actorId === callerId;
34
+ if (!queryingSelf && !isAuditReader(ctx)) {
35
+ return deniedEnvelope("Only admins and editors can look up other users' activity.");
36
+ }
37
+ const conditions = [{ 'actor.userId': { equals: actorId } }];
38
+ if (input.dateRange?.from) {
39
+ conditions.push({ createdAt: { greater_than_equal: input.dateRange.from } });
40
+ }
41
+ if (input.dateRange?.to) {
42
+ conditions.push({ createdAt: { less_than_equal: input.dateRange.to } });
43
+ }
44
+ const result = await deps.payload.find({
45
+ collection: deps.collectionSlug,
46
+ where: { and: conditions },
47
+ sort: '-createdAt',
48
+ limit: input.limit ?? 25,
49
+ });
50
+ const firstDoc = result.docs[0];
51
+ const firstActor = (firstDoc?.['actor'] ?? {});
52
+ const actorName = typeof firstActor['userName'] === 'string'
53
+ ? firstActor['userName']
54
+ : queryingSelf
55
+ ? (ctx.user?.name ?? actorId)
56
+ : actorId;
57
+ return {
58
+ actorId,
59
+ actor: actorName,
60
+ actionCount: result.totalDocs,
61
+ actions: result.docs.map((doc) => formatAuditEvent(doc)),
62
+ };
63
+ },
64
+ };
65
+ }
66
+ //# sourceMappingURL=who-changed-what.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"who-changed-what.js","sourceRoot":"","sources":["../../src/tools/who-changed-what.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,10 +1,63 @@
1
1
  {
2
2
  "name": "@forumone/throughline-audit",
3
- "version": "0.0.1",
4
- "description": "OIDC trusted publishing setup package for @forumone/throughline-audit",
3
+ "version": "0.2.0",
4
+ "description": "Read-only MCP query tools over the Throughline audit log. Pairs with core's auditPlugin (which writes records).",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "CHANGELOG.md"
18
+ ],
5
19
  "keywords": [
6
- "oidc",
7
- "trusted-publishing",
8
- "setup"
9
- ]
10
- }
20
+ "throughline",
21
+ "payload",
22
+ "mcp",
23
+ "audit"
24
+ ],
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/forumone/throughline.git",
29
+ "directory": "packages/audit"
30
+ },
31
+ "homepage": "https://github.com/forumone/throughline/tree/main/packages/audit#readme",
32
+ "bugs": {
33
+ "url": "https://github.com/forumone/throughline/issues"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "peerDependencies": {
39
+ "payload": "^3.0.0"
40
+ },
41
+ "dependencies": {
42
+ "zod": "^3.23.0",
43
+ "@forumone/throughline-plugin-contract": "0.0.0",
44
+ "@forumone/throughline-core": "0.2.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^20.17.0",
48
+ "eslint": "^9.15.0",
49
+ "payload": "^3.83.0",
50
+ "typescript": "^5.6.0",
51
+ "vitest": "^2.1.0",
52
+ "@forumone/throughline-eslint-config": "0.0.0",
53
+ "@forumone/throughline-tsconfig": "0.0.0"
54
+ },
55
+ "scripts": {
56
+ "build": "tsc -b",
57
+ "dev": "tsc -b -w",
58
+ "clean": "rm -rf dist .turbo *.tsbuildinfo",
59
+ "typecheck": "tsc --noEmit",
60
+ "lint": "eslint src",
61
+ "test": "vitest run"
62
+ }
63
+ }