@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.
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +69 -28
- package/dist/formatting/index.d.ts +40 -0
- package/dist/formatting/index.d.ts.map +1 -0
- package/dist/formatting/index.js +73 -0
- package/dist/formatting/index.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/options.d.ts +21 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +9 -0
- package/dist/options.js.map +1 -0
- package/dist/plugin.d.ts +11 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +76 -0
- package/dist/plugin.js.map +1 -0
- package/dist/tools/access.d.ts +11 -0
- package/dist/tools/access.d.ts.map +1 -0
- package/dist/tools/access.js +15 -0
- package/dist/tools/access.js.map +1 -0
- package/dist/tools/get-change-history.d.ts +23 -0
- package/dist/tools/get-change-history.d.ts.map +1 -0
- package/dist/tools/get-change-history.js +44 -0
- package/dist/tools/get-change-history.js.map +1 -0
- package/dist/tools/get-recent-failures.d.ts +23 -0
- package/dist/tools/get-recent-failures.d.ts.map +1 -0
- package/dist/tools/get-recent-failures.js +56 -0
- package/dist/tools/get-recent-failures.js.map +1 -0
- package/dist/tools/index.d.ts +12 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +7 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/query-audit.d.ts +53 -0
- package/dist/tools/query-audit.d.ts.map +1 -0
- package/dist/tools/query-audit.js +78 -0
- package/dist/tools/query-audit.js.map +1 -0
- package/dist/tools/what-changed-in-range.d.ts +23 -0
- package/dist/tools/what-changed-in-range.d.ts.map +1 -0
- package/dist/tools/what-changed-in-range.js +86 -0
- package/dist/tools/what-changed-in-range.js.map +1 -0
- package/dist/tools/who-changed-what.d.ts +38 -0
- package/dist/tools/who-changed-what.d.ts.map +1 -0
- package/dist/tools/who-changed-what.js +66 -0
- package/dist/tools/who-changed-what.js.map +1 -0
- 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
|
-
|
|
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
|
-
|
|
5
|
+
## What this package provides
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Five MCP tools served at `/api/audit/mcp`. All are read-only and emit no audit events of their own.
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
19
|
+
## Installation
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add @forumone/throughline-audit
|
|
23
|
+
```
|
|
19
24
|
|
|
20
|
-
|
|
25
|
+
Peers: `payload@^3.0.0`. Required runtime peer: `@forumone/throughline-core` (the audit log writer).
|
|
21
26
|
|
|
22
|
-
|
|
27
|
+
## Usage
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
34
|
+
const inngest = createInngestClient({ id: 'my-site' })
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
export default buildConfig({
|
|
37
|
+
// collections, db, secret...
|
|
38
|
+
plugins: [
|
|
39
|
+
auditPlugin({ inngest }), // writes
|
|
40
|
+
auditQueryPlugin({}), // reads
|
|
41
|
+
],
|
|
42
|
+
})
|
|
43
|
+
```
|
|
36
44
|
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|
package/dist/options.js
ADDED
|
@@ -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"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -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":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAO3D,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;IAC5E,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oCAAoC,CAAC;IACxE,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,GAAG,CAAC,IAAI,CAAC;SACT,QAAQ,EAAE;SACV,QAAQ,CAAC,+DAA+D,CAAC;CAC7E,CAAC,CAAA;AAEF,MAAM,UAAU,4BAA4B,CAC1C,IAA4B;IAE5B,OAAO;QACL,IAAI,EAAE,uBAAuB;QAC7B,WAAW,EACT,4UAA4U;QAC9U,WAAW;QACX,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAC5B,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB,OAAO,cAAc,CAAC,wDAAwD,CAAC,CAAA;YACjF,CAAC;YAED,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,IAAI,IAAI,CAAA;YACzC,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,SAAS,EAAE,EAAE,kBAAkB,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE;wBACjD,EAAE,SAAS,EAAE,EAAE,eAAe,EAAE,KAAK,CAAC,EAAE,EAAE,EAAE;qBAC7C;iBACF;gBACD,IAAI,EAAE,YAAY;gBAClB,KAAK,EAAE,SAAS;aACjB,CAAC,CAAA;YAEF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAA;YAC1C,MAAM,OAAO,GAAG,IAAI,GAAG,EAA2C,CAAA;YAClE,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAA;YAC9C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAA;YAC1C,IAAI,YAAY,GAAG,CAAC,CAAA;YAEpB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAsC,EAAE,CAAC;gBAChE,MAAM,MAAM,GAAG,OAAO,GAAG,CAAC,QAAQ,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;gBAC5E,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;gBAErD,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAA4B,CAAA;gBAC7D,MAAM,OAAO,GAAG,OAAO,KAAK,CAAC,QAAQ,CAAC,KAAK,QAAQ;oBACjD,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC;oBACjB,CAAC,CAAC,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,QAAQ;wBACjC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;wBACf,CAAC,CAAC,SAAS,CAAA;gBACf,MAAM,SAAS,GAAG,OAAO,KAAK,CAAC,UAAU,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;gBACrF,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;gBACrC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;gBAE5E,MAAM,gBAAgB,GAAG,GAAG,CAAC,kBAAkB,CAAC,CAAA;gBAChD,IAAI,OAAO,gBAAgB,KAAK,QAAQ,EAAE,CAAC;oBACzC,YAAY,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;gBACnF,CAAC;gBAED,MAAM,MAAM,GAAG,OAAO,GAAG,CAAC,WAAW,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;gBAClF,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;gBAErD,IAAI,GAAG,CAAC,SAAS,CAAC,KAAK,KAAK;oBAAE,YAAY,IAAI,CAAC,CAAA;YACjD,CAAC;YAED,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAA;YAClC,MAAM,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAA;YAEhD,OAAO;gBACL,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,EAAE,EAAE,KAAK,CAAC,EAAE;gBACZ,YAAY,EAAE,SAAS;gBACvB,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM;gBAC3B,SAAS;gBACT,GAAG,CAAC,SAAS;oBACX,CAAC,CAAC;wBACE,IAAI,EAAE,kBAAkB,SAAS,oBAAoB,MAAM,CAAC,IAAI,CAAC,MAAM,gEAAgE;qBACxI;oBACH,CAAC,CAAC,EAAE,CAAC;gBACP,YAAY;gBACZ,QAAQ,EAAE,MAAM,CAAC,WAAW,CAC1B,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAC3D;gBACD,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;qBACrC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;qBACrD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;qBACjC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;gBACf,YAAY,EAAE,MAAM,CAAC,WAAW,CAC9B,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAC/D;gBACD,QAAQ,EAAE,MAAM,CAAC,WAAW,CAC1B,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAC3D;aACF,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -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":"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,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,2DAA2D,CAAC;IACxE,SAAS,EAAE,CAAC;SACT,MAAM,CAAC;QACN,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;QACtC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;KACrC,CAAC;SACD,QAAQ,EAAE;IACb,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,wBAAwB,CACtC,IAAwB;IAExB,OAAO;QACL,IAAI,EAAE,kBAAkB;QACxB,WAAW,EACT,yRAAyR;QAC3R,WAAW;QACX,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAC5B,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,EAAE,EAAE,CAAA;YAC7B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,QAAQ,CAAA;YACzC,IAAI,CAAC,OAAO;gBAAE,OAAO,cAAc,CAAC,kDAAkD,CAAC,CAAA;YAEvF,MAAM,YAAY,GAAG,CAAC,CAAC,QAAQ,IAAI,OAAO,KAAK,QAAQ,CAAA;YACvD,IAAI,CAAC,YAAY,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;gBACzC,OAAO,cAAc,CAAC,4DAA4D,CAAC,CAAA;YACrF,CAAC;YAED,MAAM,UAAU,GAAY,CAAC,EAAE,cAAc,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC,CAAA;YACrE,IAAI,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC;gBAC1B,UAAU,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,EAAE,kBAAkB,EAAE,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;YAC9E,CAAC;YACD,IAAI,KAAK,CAAC,SAAS,EAAE,EAAE,EAAE,CAAC;gBACxB,UAAU,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,EAAE,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;YACzE,CAAC;YAED,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,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAwC,CAAA;YACtE,MAAM,UAAU,GAAG,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAA4B,CAAA;YACzE,MAAM,SAAS,GACb,OAAO,UAAU,CAAC,UAAU,CAAC,KAAK,QAAQ;gBACxC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;gBACxB,CAAC,CAAC,YAAY;oBACZ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,IAAI,OAAO,CAAC;oBAC7B,CAAC,CAAC,OAAO,CAAA;YAEf,OAAO;gBACL,OAAO;gBACP,KAAK,EAAE,SAAS;gBAChB,WAAW,EAAE,MAAM,CAAC,SAAS;gBAC7B,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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forumone/throughline-audit",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "
|
|
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
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
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
|
+
}
|