@withpica/mcp-server 2.52.0 → 2.53.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 +66 -0
- package/dist/prompts/creator-question-atlas.d.ts +48 -0
- package/dist/prompts/creator-question-atlas.d.ts.map +1 -0
- package/dist/prompts/creator-question-atlas.js +618 -0
- package/dist/prompts/creator-question-atlas.js.map +1 -0
- package/dist/prompts/index.d.ts +32 -0
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +235 -0
- package/dist/prompts/index.js.map +1 -1
- package/dist/resources/index.d.ts +10 -0
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/index.js +134 -1
- package/dist/resources/index.js.map +1 -1
- package/dist/server-instructions.d.ts +4 -3
- package/dist/server-instructions.d.ts.map +1 -1
- package/dist/server-instructions.js +4 -1
- package/dist/server-instructions.js.map +1 -1
- package/dist/server.d.ts +26 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +108 -10
- package/dist/server.js.map +1 -1
- package/dist/skills/index.d.ts +42 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +59 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/skills.generated.d.ts +25 -0
- package/dist/skills/skills.generated.d.ts.map +1 -0
- package/dist/skills/skills.generated.js +86 -0
- package/dist/skills/skills.generated.js.map +1 -0
- package/dist/tools/access-simulate.d.ts +23 -0
- package/dist/tools/access-simulate.d.ts.map +1 -0
- package/dist/tools/access-simulate.js +165 -0
- package/dist/tools/access-simulate.js.map +1 -0
- package/dist/tools/agent-identity.d.ts.map +1 -1
- package/dist/tools/agent-identity.js +15 -0
- package/dist/tools/agent-identity.js.map +1 -1
- package/dist/tools/agreement-types.d.ts.map +1 -1
- package/dist/tools/agreement-types.js +24 -0
- package/dist/tools/agreement-types.js.map +1 -1
- package/dist/tools/agreements.d.ts.map +1 -1
- package/dist/tools/agreements.js +21 -3
- package/dist/tools/agreements.js.map +1 -1
- package/dist/tools/analytics.d.ts.map +1 -1
- package/dist/tools/analytics.js +19 -1
- package/dist/tools/analytics.js.map +1 -1
- package/dist/tools/app-tools.d.ts.map +1 -1
- package/dist/tools/app-tools.js +11 -2
- package/dist/tools/app-tools.js.map +1 -1
- package/dist/tools/assets.d.ts.map +1 -1
- package/dist/tools/assets.js +33 -0
- package/dist/tools/assets.js.map +1 -1
- package/dist/tools/audio-files.d.ts +5 -0
- package/dist/tools/audio-files.d.ts.map +1 -1
- package/dist/tools/audio-files.js +91 -0
- package/dist/tools/audio-files.js.map +1 -1
- package/dist/tools/audit.d.ts.map +1 -1
- package/dist/tools/audit.js +11 -2
- package/dist/tools/audit.js.map +1 -1
- package/dist/tools/auth.d.ts.map +1 -1
- package/dist/tools/auth.js +6 -0
- package/dist/tools/auth.js.map +1 -1
- package/dist/tools/bulk.d.ts +4 -0
- package/dist/tools/bulk.d.ts.map +1 -1
- package/dist/tools/bulk.js +304 -0
- package/dist/tools/bulk.js.map +1 -1
- package/dist/tools/calendar.d.ts.map +1 -1
- package/dist/tools/calendar.js +3 -0
- package/dist/tools/calendar.js.map +1 -1
- package/dist/tools/collaborators.d.ts.map +1 -1
- package/dist/tools/collaborators.js +24 -3
- package/dist/tools/collaborators.js.map +1 -1
- package/dist/tools/comparisons.d.ts.map +1 -1
- package/dist/tools/comparisons.js +6 -0
- package/dist/tools/comparisons.js.map +1 -1
- package/dist/tools/credits.d.ts +18 -0
- package/dist/tools/credits.d.ts.map +1 -1
- package/dist/tools/credits.js +344 -4
- package/dist/tools/credits.js.map +1 -1
- package/dist/tools/custody.d.ts.map +1 -1
- package/dist/tools/custody.js +23 -2
- package/dist/tools/custody.js.map +1 -1
- package/dist/tools/dashboard.d.ts.map +1 -1
- package/dist/tools/dashboard.js +43 -7
- package/dist/tools/dashboard.js.map +1 -1
- package/dist/tools/directory.d.ts.map +1 -1
- package/dist/tools/directory.js +3 -0
- package/dist/tools/directory.js.map +1 -1
- package/dist/tools/discovery.d.ts.map +1 -1
- package/dist/tools/discovery.js +99 -2
- package/dist/tools/discovery.js.map +1 -1
- package/dist/tools/disputes.d.ts.map +1 -1
- package/dist/tools/disputes.js +4 -1
- package/dist/tools/disputes.js.map +1 -1
- package/dist/tools/documents.d.ts.map +1 -1
- package/dist/tools/documents.js +3 -0
- package/dist/tools/documents.js.map +1 -1
- package/dist/tools/duplicates.d.ts.map +1 -1
- package/dist/tools/duplicates.js +6 -0
- package/dist/tools/duplicates.js.map +1 -1
- package/dist/tools/enrichment.d.ts.map +1 -1
- package/dist/tools/enrichment.js +33 -0
- package/dist/tools/enrichment.js.map +1 -1
- package/dist/tools/explainability.d.ts +24 -0
- package/dist/tools/explainability.d.ts.map +1 -0
- package/dist/tools/explainability.js +137 -0
- package/dist/tools/explainability.js.map +1 -0
- package/dist/tools/exports.d.ts.map +1 -1
- package/dist/tools/exports.js +18 -3
- package/dist/tools/exports.js.map +1 -1
- package/dist/tools/feedback.d.ts.map +1 -1
- package/dist/tools/feedback.js +3 -0
- package/dist/tools/feedback.js.map +1 -1
- package/dist/tools/files.d.ts.map +1 -1
- package/dist/tools/files.js +22 -0
- package/dist/tools/files.js.map +1 -1
- package/dist/tools/groups.d.ts.map +1 -1
- package/dist/tools/groups.js +12 -0
- package/dist/tools/groups.js.map +1 -1
- package/dist/tools/import-documents.d.ts.map +1 -1
- package/dist/tools/import-documents.js +10 -1
- package/dist/tools/import-documents.js.map +1 -1
- package/dist/tools/import.d.ts.map +1 -1
- package/dist/tools/import.js +36 -3
- package/dist/tools/import.js.map +1 -1
- package/dist/tools/index.d.ts +142 -6
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +289 -108
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/integrations.d.ts.map +1 -1
- package/dist/tools/integrations.js +28 -8
- package/dist/tools/integrations.js.map +1 -1
- package/dist/tools/labels.d.ts.map +1 -1
- package/dist/tools/labels.js +3 -0
- package/dist/tools/labels.js.map +1 -1
- package/dist/tools/licensing.d.ts.map +1 -1
- package/dist/tools/licensing.js +15 -0
- package/dist/tools/licensing.js.map +1 -1
- package/dist/tools/memory.d.ts.map +1 -1
- package/dist/tools/memory.js +15 -3
- package/dist/tools/memory.js.map +1 -1
- package/dist/tools/metadata.d.ts.map +1 -1
- package/dist/tools/metadata.js +112 -0
- package/dist/tools/metadata.js.map +1 -1
- package/dist/tools/multimedia.d.ts.map +1 -1
- package/dist/tools/multimedia.js +15 -0
- package/dist/tools/multimedia.js.map +1 -1
- package/dist/tools/my-recent-questions.d.ts +25 -0
- package/dist/tools/my-recent-questions.d.ts.map +1 -0
- package/dist/tools/my-recent-questions.js +186 -0
- package/dist/tools/my-recent-questions.js.map +1 -0
- package/dist/tools/my-reported-issues.d.ts.map +1 -1
- package/dist/tools/my-reported-issues.js +3 -0
- package/dist/tools/my-reported-issues.js.map +1 -1
- package/dist/tools/notes.d.ts.map +1 -1
- package/dist/tools/notes.js +12 -0
- package/dist/tools/notes.js.map +1 -1
- package/dist/tools/notifications.d.ts.map +1 -1
- package/dist/tools/notifications.js +25 -1
- package/dist/tools/notifications.js.map +1 -1
- package/dist/tools/onboarding.d.ts.map +1 -1
- package/dist/tools/onboarding.js +3 -0
- package/dist/tools/onboarding.js.map +1 -1
- package/dist/tools/people.d.ts +4 -0
- package/dist/tools/people.d.ts.map +1 -1
- package/dist/tools/people.js +58 -1
- package/dist/tools/people.js.map +1 -1
- package/dist/tools/projects.d.ts.map +1 -1
- package/dist/tools/projects.js +18 -0
- package/dist/tools/projects.js.map +1 -1
- package/dist/tools/public-filter.d.ts.map +1 -1
- package/dist/tools/public-filter.js +6 -0
- package/dist/tools/public-filter.js.map +1 -1
- package/dist/tools/publishers.d.ts.map +1 -1
- package/dist/tools/publishers.js +6 -0
- package/dist/tools/publishers.js.map +1 -1
- package/dist/tools/recordings.d.ts.map +1 -1
- package/dist/tools/recordings.js +15 -0
- package/dist/tools/recordings.js.map +1 -1
- package/dist/tools/recovery-hints.d.ts.map +1 -1
- package/dist/tools/recovery-hints.js +105 -0
- package/dist/tools/recovery-hints.js.map +1 -1
- package/dist/tools/release-rich.d.ts.map +1 -1
- package/dist/tools/release-rich.js +4 -2
- package/dist/tools/release-rich.js.map +1 -1
- package/dist/tools/releases.d.ts.map +1 -1
- package/dist/tools/releases.js +55 -0
- package/dist/tools/releases.js.map +1 -1
- package/dist/tools/report-issue.d.ts.map +1 -1
- package/dist/tools/report-issue.js +3 -0
- package/dist/tools/report-issue.js.map +1 -1
- package/dist/tools/royalties.d.ts.map +1 -1
- package/dist/tools/royalties.js +18 -3
- package/dist/tools/royalties.js.map +1 -1
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +10 -1
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/send.d.ts.map +1 -1
- package/dist/tools/send.js +9 -0
- package/dist/tools/send.js.map +1 -1
- package/dist/tools/sessions.d.ts.map +1 -1
- package/dist/tools/sessions.js +12 -0
- package/dist/tools/sessions.js.map +1 -1
- package/dist/tools/settings.d.ts.map +1 -1
- package/dist/tools/settings.js +30 -3
- package/dist/tools/settings.js.map +1 -1
- package/dist/tools/share-links.d.ts.map +1 -1
- package/dist/tools/share-links.js +15 -0
- package/dist/tools/share-links.js.map +1 -1
- package/dist/tools/share-send.d.ts +28 -0
- package/dist/tools/share-send.d.ts.map +1 -0
- package/dist/tools/share-send.js +131 -0
- package/dist/tools/share-send.js.map +1 -0
- package/dist/tools/sharing.d.ts +29 -0
- package/dist/tools/sharing.d.ts.map +1 -0
- package/dist/tools/sharing.js +131 -0
- package/dist/tools/sharing.js.map +1 -0
- package/dist/tools/signup.d.ts.map +1 -1
- package/dist/tools/signup.js +3 -0
- package/dist/tools/signup.js.map +1 -1
- package/dist/tools/skills.d.ts +25 -0
- package/dist/tools/skills.d.ts.map +1 -0
- package/dist/tools/skills.js +144 -0
- package/dist/tools/skills.js.map +1 -0
- package/dist/tools/split-sheets.d.ts.map +1 -1
- package/dist/tools/split-sheets.js +22 -1
- package/dist/tools/split-sheets.js.map +1 -1
- package/dist/tools/storage-config.d.ts.map +1 -1
- package/dist/tools/storage-config.js +6 -0
- package/dist/tools/storage-config.js.map +1 -1
- package/dist/tools/subscription.d.ts.map +1 -1
- package/dist/tools/subscription.js +9 -10
- package/dist/tools/subscription.js.map +1 -1
- package/dist/tools/sync-placements.d.ts.map +1 -1
- package/dist/tools/sync-placements.js +20 -2
- package/dist/tools/sync-placements.js.map +1 -1
- package/dist/tools/team.d.ts.map +1 -1
- package/dist/tools/team.js +15 -0
- package/dist/tools/team.js.map +1 -1
- package/dist/tools/telegram.d.ts.map +1 -1
- package/dist/tools/telegram.js +9 -0
- package/dist/tools/telegram.js.map +1 -1
- package/dist/tools/uploads.d.ts.map +1 -1
- package/dist/tools/uploads.js +6 -0
- package/dist/tools/uploads.js.map +1 -1
- package/dist/tools/works.d.ts +4 -0
- package/dist/tools/works.d.ts.map +1 -1
- package/dist/tools/works.js +83 -3
- package/dist/tools/works.js.map +1 -1
- package/package.json +7 -6
- package/scripts/build-skills.ts +229 -0
- package/server.json +2 -2
package/dist/tools/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { WorksTools } from "./works.js";
|
|
3
3
|
import { PeopleTools } from "./people.js";
|
|
4
4
|
import { GroupsTools } from "./groups.js";
|
|
5
|
+
import { ShareSendTools } from "./share-send.js";
|
|
5
6
|
import { RecordingsTools } from "./recordings.js";
|
|
6
7
|
import { SearchTools } from "./search.js";
|
|
7
8
|
import { LicensingTools } from "./licensing.js";
|
|
@@ -10,6 +11,7 @@ import { AudioFilesTools } from "./audio-files.js";
|
|
|
10
11
|
import { MultimediaTools } from "./multimedia.js";
|
|
11
12
|
import { AgreementsTools } from "./agreements.js";
|
|
12
13
|
import { MemoryTools } from "./memory.js";
|
|
14
|
+
import { SkillsTools } from "./skills.js";
|
|
13
15
|
import { EnrichmentTools } from "./enrichment.js";
|
|
14
16
|
import { BulkTools } from "./bulk.js";
|
|
15
17
|
import { ExportTools } from "./exports.js";
|
|
@@ -44,9 +46,13 @@ import { SubscriptionTools } from "./subscription.js";
|
|
|
44
46
|
import { OnboardingTools } from "./onboarding.js";
|
|
45
47
|
import { ReportIssueTools } from "./report-issue.js";
|
|
46
48
|
import { MyReportedIssuesTools } from "./my-reported-issues.js";
|
|
49
|
+
import { MyRecentQuestionsTools } from "./my-recent-questions.js";
|
|
47
50
|
import { AgentIdentityTools } from "./agent-identity.js";
|
|
48
51
|
import { RoyaltiesTools } from "./royalties.js";
|
|
49
52
|
import { ShareLinksTools } from "./share-links.js";
|
|
53
|
+
import { SharingTools } from "./sharing.js";
|
|
54
|
+
import { ExplainabilityTools } from "./explainability.js";
|
|
55
|
+
import { AccessSimulateTools } from "./access-simulate.js";
|
|
50
56
|
import { DisputesTools } from "./disputes.js";
|
|
51
57
|
import { CustodyTools } from "./custody.js";
|
|
52
58
|
import { PurchasesTools } from "./purchases.js";
|
|
@@ -66,6 +72,46 @@ import { getToolMetadata } from "./metadata.js";
|
|
|
66
72
|
import { getRecoveryHint } from "./recovery-hints.js";
|
|
67
73
|
import { generateConfirmationToken, validateAndConsumeToken, } from "@withpica/mcp-utils";
|
|
68
74
|
import { buildSessionState, incrementSessionMutations, resetSinceLastBriefing, } from "@withpica/mcp-utils";
|
|
75
|
+
/**
|
|
76
|
+
* ADR-230 — derive the MCP-standard 3-value risk classification from the
|
|
77
|
+
* declared tier. Sourced once at registration and at audit-write time so
|
|
78
|
+
* the contract that hints, audit row, and confirmation requirement all
|
|
79
|
+
* agree is mechanical.
|
|
80
|
+
*/
|
|
81
|
+
export function tierToRiskLevel(tier) {
|
|
82
|
+
switch (tier) {
|
|
83
|
+
case "read":
|
|
84
|
+
return "safe";
|
|
85
|
+
case "draft":
|
|
86
|
+
case "write":
|
|
87
|
+
return "mutating";
|
|
88
|
+
case "destructive":
|
|
89
|
+
return "destructive";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* ADR-230 — derive the required API-key scope from the declared tier.
|
|
94
|
+
*
|
|
95
|
+
* - `read` → `read:<resource>` (write:* satisfies via `hasScope`).
|
|
96
|
+
* - `draft|write` → `write:<resource>`.
|
|
97
|
+
* - `destructive` → `destructive:*` (admin satisfies via `hasScope`); the
|
|
98
|
+
* write:<resource> scope is also required for the
|
|
99
|
+
* underlying write surface.
|
|
100
|
+
*
|
|
101
|
+
* Returns the *additional* scope tier beyond per-resource. Resource scope
|
|
102
|
+
* is composed at call-site by `lib/services/mcp-scopes.ts`.
|
|
103
|
+
*/
|
|
104
|
+
export function tierToScopeKind(tier) {
|
|
105
|
+
switch (tier) {
|
|
106
|
+
case "read":
|
|
107
|
+
return "read";
|
|
108
|
+
case "draft":
|
|
109
|
+
case "write":
|
|
110
|
+
return "write";
|
|
111
|
+
case "destructive":
|
|
112
|
+
return "destructive";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
69
115
|
/**
|
|
70
116
|
* Build a tool description with metadata injected.
|
|
71
117
|
* Format: "[category] original description"
|
|
@@ -85,6 +131,64 @@ export const CATEGORY_DISPLAY = {
|
|
|
85
131
|
comms: "send and share",
|
|
86
132
|
settings: "manage your account and team",
|
|
87
133
|
};
|
|
134
|
+
/**
|
|
135
|
+
* Sanitize tool parameters for `mcp_audit_log.parameters`. Pure function
|
|
136
|
+
* so it's directly testable; the registry's private method delegates here.
|
|
137
|
+
*
|
|
138
|
+
* Security audit 2026-05-11 P2. The audit log is queryable by anyone with
|
|
139
|
+
* team-portal access. Tools like `pica_sign_in`, `pica_share_links_*`,
|
|
140
|
+
* and `team_comms_send` previously landed bare emails / share tokens /
|
|
141
|
+
* OAuth refresh tokens in the log because the only redaction was a
|
|
142
|
+
* `delete sanitized.confirmation_token` + length truncation.
|
|
143
|
+
*
|
|
144
|
+
* Deny-list is keyed off the param name (case-insensitive substring
|
|
145
|
+
* match) — covers `password`, `api_key`, `token`, `secret`,
|
|
146
|
+
* `authorization`, plus `confirmation_token` via the `token` substring.
|
|
147
|
+
* Sister keys like `refresh_token` / `id_token` / `access_token` /
|
|
148
|
+
* `share_token` / `client_secret` also match.
|
|
149
|
+
*
|
|
150
|
+
* Email values (regardless of param key) get the local-part masked to
|
|
151
|
+
* the first two characters: `jane@example.com` → `ja***@example.com`.
|
|
152
|
+
* Locals ≤ 2 chars become all-asterisk.
|
|
153
|
+
*
|
|
154
|
+
* Strings > 500 chars are truncated to 500 + `...[truncated]`.
|
|
155
|
+
*
|
|
156
|
+
* Exported under `_internal` so test files can exercise the function
|
|
157
|
+
* without instantiating a `ToolRegistry`.
|
|
158
|
+
*/
|
|
159
|
+
function sanitizeAuditParams(args) {
|
|
160
|
+
const sanitized = { ...args };
|
|
161
|
+
const denyKeySubstrings = [
|
|
162
|
+
"password",
|
|
163
|
+
"api_key",
|
|
164
|
+
"token",
|
|
165
|
+
"secret",
|
|
166
|
+
"authorization",
|
|
167
|
+
];
|
|
168
|
+
for (const [key, value] of Object.entries(sanitized)) {
|
|
169
|
+
const lowerKey = key.toLowerCase();
|
|
170
|
+
if (denyKeySubstrings.some((needle) => lowerKey.includes(needle))) {
|
|
171
|
+
sanitized[key] = "[redacted]";
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (typeof value === "string" &&
|
|
175
|
+
/^[^\s@]{1,64}@[^\s@]{1,255}\.[^\s@]+$/.test(value)) {
|
|
176
|
+
const atIdx = value.indexOf("@");
|
|
177
|
+
const local = value.slice(0, atIdx);
|
|
178
|
+
const domain = value.slice(atIdx);
|
|
179
|
+
const maskedLocal = local.length <= 2
|
|
180
|
+
? "*".repeat(local.length)
|
|
181
|
+
: `${local.slice(0, 2)}***`;
|
|
182
|
+
sanitized[key] = `${maskedLocal}${domain}`;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (typeof value === "string" && value.length > 500) {
|
|
186
|
+
sanitized[key] = value.substring(0, 500) + "...[truncated]";
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return sanitized;
|
|
190
|
+
}
|
|
191
|
+
export const _internal = { sanitizeAuditParams };
|
|
88
192
|
export class ToolRegistry {
|
|
89
193
|
tools;
|
|
90
194
|
pica;
|
|
@@ -111,6 +215,25 @@ export class ToolRegistry {
|
|
|
111
215
|
setCallerContext(context) {
|
|
112
216
|
this.callerContext = context;
|
|
113
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Read clientInfo from the MCP `initialize` handshake. SDK populates this
|
|
220
|
+
* on the per-request `Server` for HTTP and on the long-lived `Server` for
|
|
221
|
+
* stdio; either way getClientVersion() returns the same shape after the
|
|
222
|
+
* handshake completes. Returns empty when ctx is absent or the handshake
|
|
223
|
+
* hasn't run (lobby-mode dispatches that bypass the per-request transport).
|
|
224
|
+
*
|
|
225
|
+
* Stamped onto audit rows as provenance only — never used as a permission
|
|
226
|
+
* boundary, since clientInfo is self-declared by the client.
|
|
227
|
+
*/
|
|
228
|
+
extractClientInfo(ctx) {
|
|
229
|
+
const info = ctx?.server?.getClientVersion?.();
|
|
230
|
+
if (!info)
|
|
231
|
+
return {};
|
|
232
|
+
return {
|
|
233
|
+
client_name: typeof info.name === "string" ? info.name : undefined,
|
|
234
|
+
client_version: typeof info.version === "string" ? info.version : undefined,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
114
237
|
/**
|
|
115
238
|
* Register all available tools
|
|
116
239
|
*/
|
|
@@ -159,6 +282,11 @@ export class ToolRegistry {
|
|
|
159
282
|
groupsTools.getTools().forEach((tool) => {
|
|
160
283
|
this.tools.set(tool.definition.name, tool);
|
|
161
284
|
});
|
|
285
|
+
// Share-send tool (ADR-231)
|
|
286
|
+
const shareSendTools = new ShareSendTools(pica);
|
|
287
|
+
shareSendTools.getTools().forEach((tool) => {
|
|
288
|
+
this.tools.set(tool.definition.name, tool);
|
|
289
|
+
});
|
|
162
290
|
// Recordings tools
|
|
163
291
|
const recordingsTools = new RecordingsTools(pica);
|
|
164
292
|
recordingsTools.getTools().forEach((tool) => {
|
|
@@ -199,6 +327,11 @@ export class ToolRegistry {
|
|
|
199
327
|
memoryTools.getTools().forEach((tool) => {
|
|
200
328
|
this.tools.set(tool.definition.name, tool);
|
|
201
329
|
});
|
|
330
|
+
// Skills tools (ADR-140 Phase 2b — SEP-2640 bridge tool lane)
|
|
331
|
+
const skillsTools = new SkillsTools();
|
|
332
|
+
skillsTools.getTools().forEach((tool) => {
|
|
333
|
+
this.tools.set(tool.definition.name, tool);
|
|
334
|
+
});
|
|
202
335
|
// Enrichment tools
|
|
203
336
|
const enrichmentTools = new EnrichmentTools(pica);
|
|
204
337
|
enrichmentTools.getTools().forEach((tool) => {
|
|
@@ -377,6 +510,21 @@ export class ToolRegistry {
|
|
|
377
510
|
myReportedIssuesTools.getTools().forEach((tool) => {
|
|
378
511
|
this.tools.set(tool.definition.name, tool);
|
|
379
512
|
});
|
|
513
|
+
// My-recent-questions tool (ADR-226 Phase 5 — pica_my_recent_questions)
|
|
514
|
+
// — user-scoped read of the Phase 4 substrate (assistant_interactions
|
|
515
|
+
// joined with mcp_sessions for client identity). Atlas-resolved entry
|
|
516
|
+
// for "what did I ask the AI?".
|
|
517
|
+
//
|
|
518
|
+
// ADR-230 post-ship cleanup — the tier resolver is the registry's
|
|
519
|
+
// own `getToolTier` shape (declared `tier` on each tool definition).
|
|
520
|
+
// Lint Rule 13 makes tier required for every customer MCP tool, so
|
|
521
|
+
// this resolver returns a populated value for all known tools and
|
|
522
|
+
// `undefined` only for unknown / meta tools — `dominantTier` falls
|
|
523
|
+
// back to "read" in that case.
|
|
524
|
+
const myRecentQuestionsTools = new MyRecentQuestionsTools(pica, (name) => this.tools.get(name)?.definition.tier);
|
|
525
|
+
myRecentQuestionsTools.getTools().forEach((tool) => {
|
|
526
|
+
this.tools.set(tool.definition.name, tool);
|
|
527
|
+
});
|
|
380
528
|
// Agent identity tools (ADR-185 Part 1 — session-auth-only MCP surface).
|
|
381
529
|
// The three tools refuse grant-auth callers at the HTTP API layer;
|
|
382
530
|
// the stdio path carries an API key so behaviour is identical either
|
|
@@ -395,6 +543,21 @@ export class ToolRegistry {
|
|
|
395
543
|
shareLinksTools.getTools().forEach((tool) => {
|
|
396
544
|
this.tools.set(tool.definition.name, tool);
|
|
397
545
|
});
|
|
546
|
+
// Sharing trace tool (ADR-232 § Decision 2)
|
|
547
|
+
const sharingTools = new SharingTools(pica);
|
|
548
|
+
sharingTools.getTools().forEach((tool) => {
|
|
549
|
+
this.tools.set(tool.definition.name, tool);
|
|
550
|
+
});
|
|
551
|
+
// Explainability tools (ADR-232 § Decision 3)
|
|
552
|
+
const explainabilityTools = new ExplainabilityTools(pica);
|
|
553
|
+
explainabilityTools.getTools().forEach((tool) => {
|
|
554
|
+
this.tools.set(tool.definition.name, tool);
|
|
555
|
+
});
|
|
556
|
+
// Access simulator — read-only "who would see what?" preview.
|
|
557
|
+
const accessSimulateTools = new AccessSimulateTools(pica);
|
|
558
|
+
accessSimulateTools.getTools().forEach((tool) => {
|
|
559
|
+
this.tools.set(tool.definition.name, tool);
|
|
560
|
+
});
|
|
398
561
|
// Disputes tools (ADR-139 Step 3)
|
|
399
562
|
const disputesTools = new DisputesTools(pica);
|
|
400
563
|
disputesTools.getTools().forEach((tool) => {
|
|
@@ -456,72 +619,15 @@ export class ToolRegistry {
|
|
|
456
619
|
});
|
|
457
620
|
}
|
|
458
621
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
"_disconnect",
|
|
469
|
-
];
|
|
470
|
-
static MUTATING_PATTERNS = [
|
|
471
|
-
"_create",
|
|
472
|
-
"_update",
|
|
473
|
-
"_bulk_update",
|
|
474
|
-
"_invite",
|
|
475
|
-
"_link",
|
|
476
|
-
"_send_",
|
|
477
|
-
"_import_",
|
|
478
|
-
"_execute",
|
|
479
|
-
"_ingest",
|
|
480
|
-
"_enrich",
|
|
481
|
-
"_verify",
|
|
482
|
-
"_mark_",
|
|
483
|
-
"_review",
|
|
484
|
-
"_purchase",
|
|
485
|
-
"_submit",
|
|
486
|
-
"_generate",
|
|
487
|
-
"_set_default",
|
|
488
|
-
"_duplicate",
|
|
489
|
-
"_toggle",
|
|
490
|
-
"_save",
|
|
491
|
-
"_upload",
|
|
492
|
-
"_complete",
|
|
493
|
-
"_analyze",
|
|
494
|
-
"_analyse",
|
|
495
|
-
"_identify",
|
|
496
|
-
"_resend",
|
|
497
|
-
"_notify",
|
|
498
|
-
];
|
|
499
|
-
// Read-only tools that match mutating patterns by name but are actually safe
|
|
500
|
-
static SAFE_OVERRIDES = new Set([
|
|
501
|
-
"pica_collaborators_invites_list",
|
|
502
|
-
"pica_enrichment_candidates",
|
|
503
|
-
"pica_enrichment_compare",
|
|
504
|
-
"pica_find_duplicates",
|
|
505
|
-
"pica_import_analyze",
|
|
506
|
-
"pica_import_validate",
|
|
507
|
-
"pica_import_fields",
|
|
508
|
-
"pica_import_template",
|
|
509
|
-
"pica_import_documents_query",
|
|
510
|
-
"pica_import_documents_inspect",
|
|
511
|
-
"pica_send_query",
|
|
512
|
-
"pica_share_links_list",
|
|
513
|
-
]);
|
|
514
|
-
classifyTool(name) {
|
|
515
|
-
if (ToolRegistry.SAFE_OVERRIDES.has(name)) {
|
|
516
|
-
return "safe";
|
|
517
|
-
}
|
|
518
|
-
if (ToolRegistry.DESTRUCTIVE_PATTERNS.some((p) => name.includes(p))) {
|
|
519
|
-
return "destructive";
|
|
520
|
-
}
|
|
521
|
-
if (ToolRegistry.MUTATING_PATTERNS.some((p) => name.includes(p))) {
|
|
522
|
-
return "mutating";
|
|
523
|
-
}
|
|
524
|
-
return "safe";
|
|
622
|
+
/**
|
|
623
|
+
* ADR-230 — declared tier lookup by tool name. Used by the HTTP MCP
|
|
624
|
+
* dispatcher (`app/api/mcp/route.ts`) to enforce the elevated
|
|
625
|
+
* `destructive:*` scope on top of the resource-level scope from
|
|
626
|
+
* `lib/services/mcp-scopes`. Returns undefined for unknown tools or
|
|
627
|
+
* tools that haven't declared tier yet (legacy tolerance).
|
|
628
|
+
*/
|
|
629
|
+
getToolTier(name) {
|
|
630
|
+
return this.tools.get(name)?.definition.tier;
|
|
525
631
|
}
|
|
526
632
|
/**
|
|
527
633
|
* List all available tools with write-safety prefixes injected.
|
|
@@ -543,9 +649,14 @@ export class ToolRegistry {
|
|
|
543
649
|
return toolsToList.map((tool) => {
|
|
544
650
|
let definition = tool.definition;
|
|
545
651
|
const metadata = getToolMetadata(definition.name);
|
|
546
|
-
//
|
|
547
|
-
|
|
548
|
-
|
|
652
|
+
// ADR-230 — declared tier is the source of truth for hints + risk.
|
|
653
|
+
// Lint Rule 13 enforces tier presence on every customer MCP tool;
|
|
654
|
+
// metadata.risk is a quiet fallback for the unlikely case of a tool
|
|
655
|
+
// that bypasses lint.
|
|
656
|
+
const declaredTier = definition.tier;
|
|
657
|
+
const isDestructive = declaredTier
|
|
658
|
+
? declaredTier === "destructive"
|
|
659
|
+
: metadata?.risk === "destructive";
|
|
549
660
|
if (isDestructive) {
|
|
550
661
|
definition = {
|
|
551
662
|
...definition,
|
|
@@ -573,6 +684,26 @@ export class ToolRegistry {
|
|
|
573
684
|
},
|
|
574
685
|
};
|
|
575
686
|
}
|
|
687
|
+
// ADR-230 — derive hints from declared tier when present (every
|
|
688
|
+
// tool registered in mcp-server/src/tools/*.ts has tier as of
|
|
689
|
+
// PR-1; the metadata branch stays as a defensive code path).
|
|
690
|
+
if (declaredTier && metadata) {
|
|
691
|
+
return {
|
|
692
|
+
...definition,
|
|
693
|
+
description: injectMetadataIntoDescription(tool.definition.description, metadata),
|
|
694
|
+
annotations: {
|
|
695
|
+
title: metadata.display_name,
|
|
696
|
+
readOnlyHint: declaredTier === "read",
|
|
697
|
+
destructiveHint: declaredTier === "destructive",
|
|
698
|
+
idempotentHint: declaredTier !== "destructive" && metadata.retry_safe,
|
|
699
|
+
openWorldHint: false,
|
|
700
|
+
// ADR-199 extended annotation classes — read by ChatGPT/Claude.ai
|
|
701
|
+
// connectors directly. Derived from declared tier so the four
|
|
702
|
+
// surfaces (hint, riskLevel, audit row, scope) all agree.
|
|
703
|
+
...this.deriveExtendedAnnotations(definition, metadata, undefined, declaredTier),
|
|
704
|
+
},
|
|
705
|
+
};
|
|
706
|
+
}
|
|
576
707
|
if (metadata) {
|
|
577
708
|
return {
|
|
578
709
|
...definition,
|
|
@@ -583,25 +714,25 @@ export class ToolRegistry {
|
|
|
583
714
|
destructiveHint: metadata.risk === "destructive",
|
|
584
715
|
idempotentHint: metadata.risk !== "destructive" && metadata.retry_safe,
|
|
585
716
|
openWorldHint: false,
|
|
586
|
-
// ADR-199 extended annotation classes — read by ChatGPT/Claude.ai
|
|
587
|
-
// connectors directly. Derived from existing metadata so existing
|
|
588
|
-
// tools get this for free without per-tool edits.
|
|
589
717
|
...this.deriveExtendedAnnotations(definition, metadata),
|
|
590
718
|
},
|
|
591
719
|
};
|
|
592
720
|
}
|
|
593
|
-
//
|
|
594
|
-
|
|
721
|
+
// ADR-230 — tools without a TOOL_METADATA entry (the 5 lobby
|
|
722
|
+
// meta-tools — sign_in/out/discover/tool_details/execute) still
|
|
723
|
+
// declare tier. Source hints from declared tier; default to read
|
|
724
|
+
// when tier is somehow missing (lint blocks; defensive only).
|
|
725
|
+
const tierForHints = declaredTier ?? "read";
|
|
595
726
|
const annotations = {
|
|
596
727
|
title: definition.name,
|
|
597
|
-
readOnlyHint:
|
|
598
|
-
destructiveHint:
|
|
728
|
+
readOnlyHint: tierForHints === "read",
|
|
729
|
+
destructiveHint: tierForHints === "destructive",
|
|
599
730
|
idempotentHint: false,
|
|
600
731
|
openWorldHint: false,
|
|
601
|
-
// ADR-199: derive the extended classes
|
|
602
|
-
//
|
|
603
|
-
//
|
|
604
|
-
...this.deriveExtendedAnnotations(definition, null,
|
|
732
|
+
// ADR-199: derive the extended classes from declared tier so the
|
|
733
|
+
// category/risk surface stays aligned with the rest of the
|
|
734
|
+
// hint/audit/scope chain.
|
|
735
|
+
...this.deriveExtendedAnnotations(definition, null, undefined, tierForHints),
|
|
605
736
|
};
|
|
606
737
|
// No risk prefix needed — annotations handle this structurally
|
|
607
738
|
return { ...definition, annotations };
|
|
@@ -616,9 +747,16 @@ export class ToolRegistry {
|
|
|
616
747
|
* edits required for the retrofit. Future tool authors can override by
|
|
617
748
|
* setting fields directly on definition.annotations.
|
|
618
749
|
*/
|
|
619
|
-
deriveExtendedAnnotations(definition, metadata,
|
|
750
|
+
deriveExtendedAnnotations(definition, metadata, _classification, declaredTier) {
|
|
620
751
|
const name = definition.name;
|
|
621
|
-
|
|
752
|
+
// ADR-230 — declared tier is the source of truth. metadata.risk
|
|
753
|
+
// remains a quiet fallback for any tool that bypasses lint Rule 13.
|
|
754
|
+
// (The `_classification` parameter is retained as an unused
|
|
755
|
+
// positional slot so legacy call signatures continue to compile;
|
|
756
|
+
// its value is now ignored — classifyTool was removed in PR-3.)
|
|
757
|
+
const risk = declaredTier
|
|
758
|
+
? tierToRiskLevel(declaredTier)
|
|
759
|
+
: (metadata?.risk ?? "safe");
|
|
622
760
|
// Side-effecting external systems: telegram, send-hub, comms, broadcasts.
|
|
623
761
|
// Pattern-derived so new tools inherit without edits.
|
|
624
762
|
const externalSideEffectPatterns = [
|
|
@@ -638,10 +776,16 @@ export class ToolRegistry {
|
|
|
638
776
|
// the agent wants to confirm — the gap report flagged this as a useful
|
|
639
777
|
// class for ChatGPT to surface in the UI.
|
|
640
778
|
const requiresFollowUpInspection = risk === "mutating" || risk === "destructive";
|
|
779
|
+
// ADR-230 — destructive tier always requires confirmation. Other tiers
|
|
780
|
+
// never do via this annotation (per-tool opt-in for `write` tier is
|
|
781
|
+
// expressed through `previewMode: "two_step_token"`, not here).
|
|
782
|
+
const requiresConfirmation = declaredTier
|
|
783
|
+
? declaredTier === "destructive"
|
|
784
|
+
: risk === "destructive";
|
|
641
785
|
return {
|
|
642
786
|
riskLevel: risk,
|
|
643
787
|
category: metadata?.category,
|
|
644
|
-
requiresConfirmation
|
|
788
|
+
requiresConfirmation,
|
|
645
789
|
createsExternalSideEffects,
|
|
646
790
|
returnsPaginated,
|
|
647
791
|
requiresFollowUpInspection,
|
|
@@ -677,6 +821,15 @@ export class ToolRegistry {
|
|
|
677
821
|
display_name: displayName,
|
|
678
822
|
};
|
|
679
823
|
}
|
|
824
|
+
case "pica_works_bulk_move_to_performances": {
|
|
825
|
+
const count = args.ids?.length || 0;
|
|
826
|
+
return {
|
|
827
|
+
action: "bulk reclassify works → performances",
|
|
828
|
+
target: `${count} ${count === 1 ? "work" : "works"}`,
|
|
829
|
+
warning: `this creates ${count} new live_performance row(s) and removes the source work(s) from the catalog. for works with verified identifiers (ISRC/ISWC/MLC) the source is soft-deleted — credits and agreement links survive but are orphaned to the removed work; for unverified works the deletion is permanent. performance metadata is sparse by default and will need filling in.`,
|
|
830
|
+
display_name: displayName,
|
|
831
|
+
};
|
|
832
|
+
}
|
|
680
833
|
case "pica_people_delete": {
|
|
681
834
|
const person = await pica.people.get(args.id).catch(() => null);
|
|
682
835
|
const personName = person?.first_name
|
|
@@ -689,6 +842,15 @@ export class ToolRegistry {
|
|
|
689
842
|
display_name: displayName,
|
|
690
843
|
};
|
|
691
844
|
}
|
|
845
|
+
case "pica_people_bulk_delete": {
|
|
846
|
+
const count = args.ids?.length || 0;
|
|
847
|
+
return {
|
|
848
|
+
action: "bulk delete people",
|
|
849
|
+
target: `${count} ${count === 1 ? "person" : "people"}`,
|
|
850
|
+
warning: `this will soft-delete ${count} person record(s). credits and agreements remain linked to the removed records.`,
|
|
851
|
+
display_name: displayName,
|
|
852
|
+
};
|
|
853
|
+
}
|
|
692
854
|
case "pica_agreements_delete": {
|
|
693
855
|
const agreementResult = await pica.agreements
|
|
694
856
|
.get(args.id)
|
|
@@ -834,18 +996,11 @@ export class ToolRegistry {
|
|
|
834
996
|
}
|
|
835
997
|
}
|
|
836
998
|
/**
|
|
837
|
-
* Sanitize tool parameters for audit logging
|
|
838
|
-
*
|
|
999
|
+
* Sanitize tool parameters for audit logging — delegates to the
|
|
1000
|
+
* pure-function impl below so it can be exercised directly in tests.
|
|
839
1001
|
*/
|
|
840
1002
|
sanitizeParams(args) {
|
|
841
|
-
|
|
842
|
-
delete sanitized.confirmation_token;
|
|
843
|
-
for (const [key, value] of Object.entries(sanitized)) {
|
|
844
|
-
if (typeof value === "string" && value.length > 500) {
|
|
845
|
-
sanitized[key] = value.substring(0, 500) + "...[truncated]";
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
return sanitized;
|
|
1003
|
+
return sanitizeAuditParams(args);
|
|
849
1004
|
}
|
|
850
1005
|
/**
|
|
851
1006
|
* ADR-208 Primitive B — opaque per-org cache key.
|
|
@@ -1018,10 +1173,22 @@ export class ToolRegistry {
|
|
|
1018
1173
|
throw new ToolExecutionError(`Tool not found: ${name}`);
|
|
1019
1174
|
}
|
|
1020
1175
|
const metadata = getToolMetadata(name);
|
|
1176
|
+
// ADR-230 — declared tier is the source of truth for the freshness +
|
|
1177
|
+
// confirmation gates and the audit-log surfaces. Fall back to
|
|
1178
|
+
// metadata.risk only if the tool somehow lacks tier (lint blocks
|
|
1179
|
+
// missing tier on creator + team + directory MCPs as of PR-2).
|
|
1180
|
+
const declaredTier = tool.definition.tier;
|
|
1181
|
+
const fallbackRisk = metadata?.risk ?? "safe";
|
|
1182
|
+
const auditTier = declaredTier ??
|
|
1183
|
+
(fallbackRisk === "safe"
|
|
1184
|
+
? "read"
|
|
1185
|
+
: fallbackRisk === "destructive"
|
|
1186
|
+
? "destructive"
|
|
1187
|
+
: "write");
|
|
1188
|
+
const auditRiskLevel = tierToRiskLevel(auditTier);
|
|
1189
|
+
const isDestructive = auditTier === "destructive";
|
|
1021
1190
|
// ── Session freshness gate (ADR-151) ──
|
|
1022
|
-
if (
|
|
1023
|
-
this.config &&
|
|
1024
|
-
!this.config.lobbyMode) {
|
|
1191
|
+
if (isDestructive && this.config && !this.config.lobbyMode) {
|
|
1025
1192
|
const creds = readCredentials(this.config.credentialsPath);
|
|
1026
1193
|
if (creds?.authenticated_at) {
|
|
1027
1194
|
const authAge = Date.now() - new Date(creds.authenticated_at).getTime();
|
|
@@ -1039,12 +1206,15 @@ export class ToolRegistry {
|
|
|
1039
1206
|
}
|
|
1040
1207
|
}
|
|
1041
1208
|
}
|
|
1042
|
-
// ── Destructive confirmation gate ──
|
|
1043
|
-
if (
|
|
1209
|
+
// ── Destructive confirmation gate (ADR-230 — keyed on declared tier) ──
|
|
1210
|
+
if (isDestructive) {
|
|
1044
1211
|
const confirmationToken = args.confirmation_token;
|
|
1045
1212
|
if (!confirmationToken) {
|
|
1046
|
-
// First call — generate preview and return confirmation challenge
|
|
1047
|
-
|
|
1213
|
+
// First call — generate preview and return confirmation challenge.
|
|
1214
|
+
// ADR-230 PR-4 — token state lives in Postgres for HTTP MCP under
|
|
1215
|
+
// Fluid Compute (or in-memory for stdio); both paths via the
|
|
1216
|
+
// active ConfirmationStore singleton, so callers stay unchanged.
|
|
1217
|
+
const token = await generateConfirmationToken(name, args);
|
|
1048
1218
|
const preview = await this.buildDestructivePreview(name, args);
|
|
1049
1219
|
const response = {
|
|
1050
1220
|
content: [
|
|
@@ -1070,6 +1240,7 @@ export class ToolRegistry {
|
|
|
1070
1240
|
?.logToolExecution({
|
|
1071
1241
|
tool_name: name,
|
|
1072
1242
|
tool_category: metadata?.category || "unknown",
|
|
1243
|
+
tier: "destructive",
|
|
1073
1244
|
risk_level: "destructive",
|
|
1074
1245
|
parameters: this.sanitizeParams(args),
|
|
1075
1246
|
result_status: "confirmation_required",
|
|
@@ -1077,12 +1248,14 @@ export class ToolRegistry {
|
|
|
1077
1248
|
execution_time_ms: Date.now() - startTime,
|
|
1078
1249
|
caller_identity: this.callerContext.callerIdentity,
|
|
1079
1250
|
transport: this.callerContext.transport,
|
|
1251
|
+
...this.extractClientInfo(ctx),
|
|
1080
1252
|
})
|
|
1081
1253
|
.catch(() => { }); // Never block on audit
|
|
1082
1254
|
return response;
|
|
1083
1255
|
}
|
|
1084
|
-
// Second call — validate token
|
|
1085
|
-
|
|
1256
|
+
// Second call — validate token (Postgres-backed under Fluid Compute
|
|
1257
|
+
// since ADR-230 PR-4; the active store handles cross-instance state).
|
|
1258
|
+
const validation = await validateAndConsumeToken(confirmationToken, name, args);
|
|
1086
1259
|
if (!validation.valid) {
|
|
1087
1260
|
return {
|
|
1088
1261
|
content: [
|
|
@@ -1115,10 +1288,10 @@ export class ToolRegistry {
|
|
|
1115
1288
|
}
|
|
1116
1289
|
}
|
|
1117
1290
|
// ADR-208 Primitive B — ambient session-state on write-tool results.
|
|
1118
|
-
// Read tools (`
|
|
1119
|
-
//
|
|
1120
|
-
//
|
|
1121
|
-
const isWriteTool =
|
|
1291
|
+
// Read tools (`tier: "read"`) skip this entirely so the high-volume
|
|
1292
|
+
// read path stays cheap. `result.isError === true` also skips because
|
|
1293
|
+
// a failed mutation didn't change catalog state.
|
|
1294
|
+
const isWriteTool = auditRiskLevel !== "safe";
|
|
1122
1295
|
if (isWriteTool && result && !result.isError) {
|
|
1123
1296
|
await this.attachSessionState(result, name);
|
|
1124
1297
|
}
|
|
@@ -1144,12 +1317,14 @@ export class ToolRegistry {
|
|
|
1144
1317
|
?.logToolExecution({
|
|
1145
1318
|
tool_name: name,
|
|
1146
1319
|
tool_category: metadata?.category || "unknown",
|
|
1147
|
-
|
|
1320
|
+
tier: auditTier,
|
|
1321
|
+
risk_level: auditRiskLevel,
|
|
1148
1322
|
parameters: this.sanitizeParams(args),
|
|
1149
1323
|
result_status: "success",
|
|
1150
1324
|
execution_time_ms: Date.now() - startTime,
|
|
1151
1325
|
caller_identity: this.callerContext.callerIdentity,
|
|
1152
1326
|
transport: this.callerContext.transport,
|
|
1327
|
+
...this.extractClientInfo(ctx),
|
|
1153
1328
|
})
|
|
1154
1329
|
.catch(() => { }); // Never block on audit
|
|
1155
1330
|
return result;
|
|
@@ -1170,13 +1345,19 @@ export class ToolRegistry {
|
|
|
1170
1345
|
?.logToolExecution({
|
|
1171
1346
|
tool_name: name,
|
|
1172
1347
|
tool_category: metadata?.category || "unknown",
|
|
1173
|
-
|
|
1348
|
+
tier: auditTier,
|
|
1349
|
+
risk_level: auditRiskLevel,
|
|
1174
1350
|
parameters: this.sanitizeParams(args),
|
|
1175
1351
|
result_status: "error",
|
|
1176
1352
|
error_code: parsed.error,
|
|
1353
|
+
// Stage A — forward the parsed body's message so mcp_audit_log
|
|
1354
|
+
// captures the underlying cause. Without this every error row had
|
|
1355
|
+
// error_message=null, leaving UNKNOWN_ERROR rows opaque.
|
|
1356
|
+
error_message: typeof parsed.message === "string" ? parsed.message : undefined,
|
|
1177
1357
|
execution_time_ms: Date.now() - startTime,
|
|
1178
1358
|
caller_identity: this.callerContext.callerIdentity,
|
|
1179
1359
|
transport: this.callerContext.transport,
|
|
1360
|
+
...this.extractClientInfo(ctx),
|
|
1180
1361
|
})
|
|
1181
1362
|
.catch(() => { }); // Never block on audit
|
|
1182
1363
|
return {
|