@withpica/mcp-server 2.51.0 → 2.52.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/.claude/settings.local.json +5 -0
- package/CHANGELOG.md +0 -41
- package/dist/prompts/index.d.ts +0 -32
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +0 -235
- package/dist/prompts/index.js.map +1 -1
- package/dist/resources/index.d.ts +0 -10
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/index.js +1 -134
- package/dist/resources/index.js.map +1 -1
- package/dist/server-instructions.d.ts +3 -4
- package/dist/server-instructions.d.ts.map +1 -1
- package/dist/server-instructions.js +1 -4
- package/dist/server-instructions.js.map +1 -1
- package/dist/server.d.ts +0 -26
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +10 -108
- package/dist/server.js.map +1 -1
- package/dist/tools/agent-identity.d.ts.map +1 -1
- package/dist/tools/agent-identity.js +0 -15
- 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 +0 -24
- package/dist/tools/agreement-types.js.map +1 -1
- package/dist/tools/agreements.d.ts.map +1 -1
- package/dist/tools/agreements.js +3 -21
- package/dist/tools/agreements.js.map +1 -1
- package/dist/tools/analytics.d.ts.map +1 -1
- package/dist/tools/analytics.js +1 -19
- package/dist/tools/analytics.js.map +1 -1
- package/dist/tools/app-tools.d.ts.map +1 -1
- package/dist/tools/app-tools.js +2 -11
- package/dist/tools/app-tools.js.map +1 -1
- package/dist/tools/assets.d.ts.map +1 -1
- package/dist/tools/assets.js +0 -33
- package/dist/tools/assets.js.map +1 -1
- package/dist/tools/audio-files.d.ts +0 -5
- package/dist/tools/audio-files.d.ts.map +1 -1
- package/dist/tools/audio-files.js +0 -91
- package/dist/tools/audio-files.js.map +1 -1
- package/dist/tools/audit.d.ts.map +1 -1
- package/dist/tools/audit.js +2 -11
- package/dist/tools/audit.js.map +1 -1
- package/dist/tools/auth.d.ts.map +1 -1
- package/dist/tools/auth.js +0 -6
- package/dist/tools/auth.js.map +1 -1
- package/dist/tools/bulk.d.ts.map +1 -1
- package/dist/tools/bulk.js +0 -6
- package/dist/tools/bulk.js.map +1 -1
- package/dist/tools/calendar.d.ts.map +1 -1
- package/dist/tools/calendar.js +0 -3
- package/dist/tools/calendar.js.map +1 -1
- package/dist/tools/collaborators.d.ts.map +1 -1
- package/dist/tools/collaborators.js +3 -24
- package/dist/tools/collaborators.js.map +1 -1
- package/dist/tools/comparisons.d.ts.map +1 -1
- package/dist/tools/comparisons.js +0 -6
- package/dist/tools/comparisons.js.map +1 -1
- package/dist/tools/credits.d.ts +0 -18
- package/dist/tools/credits.d.ts.map +1 -1
- package/dist/tools/credits.js +4 -344
- package/dist/tools/credits.js.map +1 -1
- package/dist/tools/custody.d.ts.map +1 -1
- package/dist/tools/custody.js +2 -23
- package/dist/tools/custody.js.map +1 -1
- package/dist/tools/dashboard.d.ts.map +1 -1
- package/dist/tools/dashboard.js +7 -43
- package/dist/tools/dashboard.js.map +1 -1
- package/dist/tools/directory.d.ts.map +1 -1
- package/dist/tools/directory.js +0 -3
- package/dist/tools/directory.js.map +1 -1
- package/dist/tools/discovery.d.ts.map +1 -1
- package/dist/tools/discovery.js +7 -94
- package/dist/tools/discovery.js.map +1 -1
- package/dist/tools/disputes.d.ts.map +1 -1
- package/dist/tools/disputes.js +1 -4
- package/dist/tools/disputes.js.map +1 -1
- package/dist/tools/documents.d.ts.map +1 -1
- package/dist/tools/documents.js +0 -3
- package/dist/tools/documents.js.map +1 -1
- package/dist/tools/duplicates.d.ts.map +1 -1
- package/dist/tools/duplicates.js +0 -6
- package/dist/tools/duplicates.js.map +1 -1
- package/dist/tools/enrichment.d.ts.map +1 -1
- package/dist/tools/enrichment.js +0 -33
- package/dist/tools/enrichment.js.map +1 -1
- package/dist/tools/exports.d.ts.map +1 -1
- package/dist/tools/exports.js +3 -18
- package/dist/tools/exports.js.map +1 -1
- package/dist/tools/feedback.d.ts.map +1 -1
- package/dist/tools/feedback.js +0 -3
- package/dist/tools/feedback.js.map +1 -1
- package/dist/tools/files.d.ts +30 -0
- package/dist/tools/files.d.ts.map +1 -0
- package/dist/tools/files.js +98 -0
- package/dist/tools/files.js.map +1 -0
- package/dist/tools/groups.d.ts.map +1 -1
- package/dist/tools/groups.js +0 -12
- package/dist/tools/groups.js.map +1 -1
- package/dist/tools/import-documents.d.ts.map +1 -1
- package/dist/tools/import-documents.js +1 -10
- package/dist/tools/import-documents.js.map +1 -1
- package/dist/tools/import.d.ts.map +1 -1
- package/dist/tools/import.js +3 -36
- package/dist/tools/import.js.map +1 -1
- package/dist/tools/index.d.ts +6 -142
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +115 -269
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/integrations.d.ts.map +1 -1
- package/dist/tools/integrations.js +8 -28
- package/dist/tools/integrations.js.map +1 -1
- package/dist/tools/labels.d.ts.map +1 -1
- package/dist/tools/labels.js +0 -3
- package/dist/tools/labels.js.map +1 -1
- package/dist/tools/licensing.d.ts.map +1 -1
- package/dist/tools/licensing.js +0 -15
- package/dist/tools/licensing.js.map +1 -1
- package/dist/tools/memory.d.ts.map +1 -1
- package/dist/tools/memory.js +3 -15
- package/dist/tools/memory.js.map +1 -1
- package/dist/tools/metadata.d.ts.map +1 -1
- package/dist/tools/metadata.js +15 -77
- package/dist/tools/metadata.js.map +1 -1
- package/dist/tools/multimedia.d.ts.map +1 -1
- package/dist/tools/multimedia.js +0 -15
- package/dist/tools/multimedia.js.map +1 -1
- package/dist/tools/my-reported-issues.d.ts.map +1 -1
- package/dist/tools/my-reported-issues.js +0 -3
- package/dist/tools/my-reported-issues.js.map +1 -1
- package/dist/tools/notes.d.ts.map +1 -1
- package/dist/tools/notes.js +0 -12
- package/dist/tools/notes.js.map +1 -1
- package/dist/tools/notifications.d.ts.map +1 -1
- package/dist/tools/notifications.js +1 -25
- package/dist/tools/notifications.js.map +1 -1
- package/dist/tools/onboarding.d.ts.map +1 -1
- package/dist/tools/onboarding.js +0 -3
- package/dist/tools/onboarding.js.map +1 -1
- package/dist/tools/people.d.ts.map +1 -1
- package/dist/tools/people.js +1 -16
- package/dist/tools/people.js.map +1 -1
- package/dist/tools/projects.d.ts.map +1 -1
- package/dist/tools/projects.js +0 -18
- package/dist/tools/projects.js.map +1 -1
- package/dist/tools/publishers.d.ts.map +1 -1
- package/dist/tools/publishers.js +0 -6
- package/dist/tools/publishers.js.map +1 -1
- package/dist/tools/recordings.d.ts.map +1 -1
- package/dist/tools/recordings.js +0 -15
- package/dist/tools/recordings.js.map +1 -1
- package/dist/tools/recovery-hints.d.ts.map +1 -1
- package/dist/tools/recovery-hints.js +2 -28
- 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 +2 -4
- package/dist/tools/release-rich.js.map +1 -1
- package/dist/tools/releases.d.ts.map +1 -1
- package/dist/tools/releases.js +0 -55
- package/dist/tools/releases.js.map +1 -1
- package/dist/tools/report-issue.d.ts.map +1 -1
- package/dist/tools/report-issue.js +0 -3
- package/dist/tools/report-issue.js.map +1 -1
- package/dist/tools/royalties.d.ts.map +1 -1
- package/dist/tools/royalties.js +3 -18
- package/dist/tools/royalties.js.map +1 -1
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +1 -10
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/send.d.ts.map +1 -1
- package/dist/tools/send.js +0 -9
- package/dist/tools/send.js.map +1 -1
- package/dist/tools/sessions.d.ts.map +1 -1
- package/dist/tools/sessions.js +0 -12
- package/dist/tools/sessions.js.map +1 -1
- package/dist/tools/settings.d.ts.map +1 -1
- package/dist/tools/settings.js +3 -30
- package/dist/tools/settings.js.map +1 -1
- package/dist/tools/share-links.d.ts.map +1 -1
- package/dist/tools/share-links.js +0 -15
- package/dist/tools/share-links.js.map +1 -1
- package/dist/tools/signup.d.ts.map +1 -1
- package/dist/tools/signup.js +0 -3
- package/dist/tools/signup.js.map +1 -1
- package/dist/tools/split-sheets.d.ts.map +1 -1
- package/dist/tools/split-sheets.js +1 -22
- 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 +0 -6
- package/dist/tools/storage-config.js.map +1 -1
- package/dist/tools/subscription.d.ts.map +1 -1
- package/dist/tools/subscription.js +10 -9
- package/dist/tools/subscription.js.map +1 -1
- package/dist/tools/sync-placements.d.ts.map +1 -1
- package/dist/tools/sync-placements.js +2 -20
- package/dist/tools/sync-placements.js.map +1 -1
- package/dist/tools/team.d.ts.map +1 -1
- package/dist/tools/team.js +0 -15
- package/dist/tools/team.js.map +1 -1
- package/dist/tools/telegram.d.ts.map +1 -1
- package/dist/tools/telegram.js +0 -9
- package/dist/tools/telegram.js.map +1 -1
- package/dist/tools/uploads.d.ts.map +1 -1
- package/dist/tools/uploads.js +0 -6
- package/dist/tools/uploads.js.map +1 -1
- package/dist/tools/works.d.ts.map +1 -1
- package/dist/tools/works.js +3 -37
- package/dist/tools/works.js.map +1 -1
- package/package.json +6 -7
- package/server.json +2 -2
- package/dist/prompts/creator-question-atlas.d.ts +0 -48
- package/dist/prompts/creator-question-atlas.d.ts.map +0 -1
- package/dist/prompts/creator-question-atlas.js +0 -618
- package/dist/prompts/creator-question-atlas.js.map +0 -1
- package/dist/skills/index.d.ts +0 -42
- package/dist/skills/index.d.ts.map +0 -1
- package/dist/skills/index.js +0 -59
- package/dist/skills/index.js.map +0 -1
- package/dist/skills/skills.generated.d.ts +0 -25
- package/dist/skills/skills.generated.d.ts.map +0 -1
- package/dist/skills/skills.generated.js +0 -86
- package/dist/skills/skills.generated.js.map +0 -1
- package/dist/tools/access-simulate.d.ts +0 -23
- package/dist/tools/access-simulate.d.ts.map +0 -1
- package/dist/tools/access-simulate.js +0 -165
- package/dist/tools/access-simulate.js.map +0 -1
- package/dist/tools/explainability.d.ts +0 -24
- package/dist/tools/explainability.d.ts.map +0 -1
- package/dist/tools/explainability.js +0 -137
- package/dist/tools/explainability.js.map +0 -1
- package/dist/tools/my-recent-questions.d.ts +0 -25
- package/dist/tools/my-recent-questions.d.ts.map +0 -1
- package/dist/tools/my-recent-questions.js +0 -186
- package/dist/tools/my-recent-questions.js.map +0 -1
- package/dist/tools/share-send.d.ts +0 -28
- package/dist/tools/share-send.d.ts.map +0 -1
- package/dist/tools/share-send.js +0 -131
- package/dist/tools/share-send.js.map +0 -1
- package/dist/tools/sharing.d.ts +0 -29
- package/dist/tools/sharing.d.ts.map +0 -1
- package/dist/tools/sharing.js +0 -131
- package/dist/tools/sharing.js.map +0 -1
- package/dist/tools/skills.d.ts +0 -25
- package/dist/tools/skills.d.ts.map +0 -1
- package/dist/tools/skills.js +0 -144
- package/dist/tools/skills.js.map +0 -1
- package/scripts/build-skills.ts +0 -229
package/dist/tools/index.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
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";
|
|
6
5
|
import { RecordingsTools } from "./recordings.js";
|
|
7
6
|
import { SearchTools } from "./search.js";
|
|
8
7
|
import { LicensingTools } from "./licensing.js";
|
|
@@ -11,7 +10,6 @@ import { AudioFilesTools } from "./audio-files.js";
|
|
|
11
10
|
import { MultimediaTools } from "./multimedia.js";
|
|
12
11
|
import { AgreementsTools } from "./agreements.js";
|
|
13
12
|
import { MemoryTools } from "./memory.js";
|
|
14
|
-
import { SkillsTools } from "./skills.js";
|
|
15
13
|
import { EnrichmentTools } from "./enrichment.js";
|
|
16
14
|
import { BulkTools } from "./bulk.js";
|
|
17
15
|
import { ExportTools } from "./exports.js";
|
|
@@ -20,6 +18,7 @@ import { DirectoryTools } from "./directory.js";
|
|
|
20
18
|
import { CollaboratorsTools } from "./collaborators.js";
|
|
21
19
|
import { ImportDocumentTools } from "./import-documents.js";
|
|
22
20
|
import { UploadTools } from "./uploads.js";
|
|
21
|
+
import { FilesTools } from "./files.js";
|
|
23
22
|
import { DocumentTools } from "./documents.js";
|
|
24
23
|
import { ImportTools } from "./import.js";
|
|
25
24
|
import { SendTools } from "./send.js";
|
|
@@ -45,13 +44,9 @@ import { SubscriptionTools } from "./subscription.js";
|
|
|
45
44
|
import { OnboardingTools } from "./onboarding.js";
|
|
46
45
|
import { ReportIssueTools } from "./report-issue.js";
|
|
47
46
|
import { MyReportedIssuesTools } from "./my-reported-issues.js";
|
|
48
|
-
import { MyRecentQuestionsTools } from "./my-recent-questions.js";
|
|
49
47
|
import { AgentIdentityTools } from "./agent-identity.js";
|
|
50
48
|
import { RoyaltiesTools } from "./royalties.js";
|
|
51
49
|
import { ShareLinksTools } from "./share-links.js";
|
|
52
|
-
import { SharingTools } from "./sharing.js";
|
|
53
|
-
import { ExplainabilityTools } from "./explainability.js";
|
|
54
|
-
import { AccessSimulateTools } from "./access-simulate.js";
|
|
55
50
|
import { DisputesTools } from "./disputes.js";
|
|
56
51
|
import { CustodyTools } from "./custody.js";
|
|
57
52
|
import { PurchasesTools } from "./purchases.js";
|
|
@@ -71,46 +66,6 @@ import { getToolMetadata } from "./metadata.js";
|
|
|
71
66
|
import { getRecoveryHint } from "./recovery-hints.js";
|
|
72
67
|
import { generateConfirmationToken, validateAndConsumeToken, } from "@withpica/mcp-utils";
|
|
73
68
|
import { buildSessionState, incrementSessionMutations, resetSinceLastBriefing, } from "@withpica/mcp-utils";
|
|
74
|
-
/**
|
|
75
|
-
* ADR-230 — derive the MCP-standard 3-value risk classification from the
|
|
76
|
-
* declared tier. Sourced once at registration and at audit-write time so
|
|
77
|
-
* the contract that hints, audit row, and confirmation requirement all
|
|
78
|
-
* agree is mechanical.
|
|
79
|
-
*/
|
|
80
|
-
export function tierToRiskLevel(tier) {
|
|
81
|
-
switch (tier) {
|
|
82
|
-
case "read":
|
|
83
|
-
return "safe";
|
|
84
|
-
case "draft":
|
|
85
|
-
case "write":
|
|
86
|
-
return "mutating";
|
|
87
|
-
case "destructive":
|
|
88
|
-
return "destructive";
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* ADR-230 — derive the required API-key scope from the declared tier.
|
|
93
|
-
*
|
|
94
|
-
* - `read` → `read:<resource>` (write:* satisfies via `hasScope`).
|
|
95
|
-
* - `draft|write` → `write:<resource>`.
|
|
96
|
-
* - `destructive` → `destructive:*` (admin satisfies via `hasScope`); the
|
|
97
|
-
* write:<resource> scope is also required for the
|
|
98
|
-
* underlying write surface.
|
|
99
|
-
*
|
|
100
|
-
* Returns the *additional* scope tier beyond per-resource. Resource scope
|
|
101
|
-
* is composed at call-site by `lib/services/mcp-scopes.ts`.
|
|
102
|
-
*/
|
|
103
|
-
export function tierToScopeKind(tier) {
|
|
104
|
-
switch (tier) {
|
|
105
|
-
case "read":
|
|
106
|
-
return "read";
|
|
107
|
-
case "draft":
|
|
108
|
-
case "write":
|
|
109
|
-
return "write";
|
|
110
|
-
case "destructive":
|
|
111
|
-
return "destructive";
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
69
|
/**
|
|
115
70
|
* Build a tool description with metadata injected.
|
|
116
71
|
* Format: "[category] original description"
|
|
@@ -130,62 +85,6 @@ export const CATEGORY_DISPLAY = {
|
|
|
130
85
|
comms: "send and share",
|
|
131
86
|
settings: "manage your account and team",
|
|
132
87
|
};
|
|
133
|
-
/**
|
|
134
|
-
* Sanitize tool parameters for `mcp_audit_log.parameters`. Pure function
|
|
135
|
-
* so it's directly testable; the registry's private method delegates here.
|
|
136
|
-
*
|
|
137
|
-
* Security audit 2026-05-11 P2. The audit log is queryable by anyone with
|
|
138
|
-
* team-portal access. Tools like `pica_sign_in`, `pica_share_links_*`,
|
|
139
|
-
* and `team_comms_send` previously landed bare emails / share tokens /
|
|
140
|
-
* OAuth refresh tokens in the log because the only redaction was a
|
|
141
|
-
* `delete sanitized.confirmation_token` + length truncation.
|
|
142
|
-
*
|
|
143
|
-
* Deny-list is keyed off the param name (case-insensitive substring
|
|
144
|
-
* match) — covers `password`, `api_key`, `token`, `secret`,
|
|
145
|
-
* `authorization`, plus `confirmation_token` via the `token` substring.
|
|
146
|
-
* Sister keys like `refresh_token` / `id_token` / `access_token` /
|
|
147
|
-
* `share_token` / `client_secret` also match.
|
|
148
|
-
*
|
|
149
|
-
* Email values (regardless of param key) get the local-part masked to
|
|
150
|
-
* the first two characters: `jane@example.com` → `ja***@example.com`.
|
|
151
|
-
* Locals ≤ 2 chars become all-asterisk.
|
|
152
|
-
*
|
|
153
|
-
* Strings > 500 chars are truncated to 500 + `...[truncated]`.
|
|
154
|
-
*
|
|
155
|
-
* Exported under `_internal` so test files can exercise the function
|
|
156
|
-
* without instantiating a `ToolRegistry`.
|
|
157
|
-
*/
|
|
158
|
-
function sanitizeAuditParams(args) {
|
|
159
|
-
const sanitized = { ...args };
|
|
160
|
-
const denyKeySubstrings = [
|
|
161
|
-
"password",
|
|
162
|
-
"api_key",
|
|
163
|
-
"token",
|
|
164
|
-
"secret",
|
|
165
|
-
"authorization",
|
|
166
|
-
];
|
|
167
|
-
for (const [key, value] of Object.entries(sanitized)) {
|
|
168
|
-
const lowerKey = key.toLowerCase();
|
|
169
|
-
if (denyKeySubstrings.some((needle) => lowerKey.includes(needle))) {
|
|
170
|
-
sanitized[key] = "[redacted]";
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
if (typeof value === "string" &&
|
|
174
|
-
/^[^\s@]{1,64}@[^\s@]{1,255}\.[^\s@]+$/.test(value)) {
|
|
175
|
-
const atIdx = value.indexOf("@");
|
|
176
|
-
const local = value.slice(0, atIdx);
|
|
177
|
-
const domain = value.slice(atIdx);
|
|
178
|
-
const maskedLocal = local.length <= 2 ? "*".repeat(local.length) : `${local.slice(0, 2)}***`;
|
|
179
|
-
sanitized[key] = `${maskedLocal}${domain}`;
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
if (typeof value === "string" && value.length > 500) {
|
|
183
|
-
sanitized[key] = value.substring(0, 500) + "...[truncated]";
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return sanitized;
|
|
187
|
-
}
|
|
188
|
-
export const _internal = { sanitizeAuditParams };
|
|
189
88
|
export class ToolRegistry {
|
|
190
89
|
tools;
|
|
191
90
|
pica;
|
|
@@ -212,25 +111,6 @@ export class ToolRegistry {
|
|
|
212
111
|
setCallerContext(context) {
|
|
213
112
|
this.callerContext = context;
|
|
214
113
|
}
|
|
215
|
-
/**
|
|
216
|
-
* Read clientInfo from the MCP `initialize` handshake. SDK populates this
|
|
217
|
-
* on the per-request `Server` for HTTP and on the long-lived `Server` for
|
|
218
|
-
* stdio; either way getClientVersion() returns the same shape after the
|
|
219
|
-
* handshake completes. Returns empty when ctx is absent or the handshake
|
|
220
|
-
* hasn't run (lobby-mode dispatches that bypass the per-request transport).
|
|
221
|
-
*
|
|
222
|
-
* Stamped onto audit rows as provenance only — never used as a permission
|
|
223
|
-
* boundary, since clientInfo is self-declared by the client.
|
|
224
|
-
*/
|
|
225
|
-
extractClientInfo(ctx) {
|
|
226
|
-
const info = ctx?.server?.getClientVersion?.();
|
|
227
|
-
if (!info)
|
|
228
|
-
return {};
|
|
229
|
-
return {
|
|
230
|
-
client_name: typeof info.name === "string" ? info.name : undefined,
|
|
231
|
-
client_version: typeof info.version === "string" ? info.version : undefined,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
114
|
/**
|
|
235
115
|
* Register all available tools
|
|
236
116
|
*/
|
|
@@ -279,11 +159,6 @@ export class ToolRegistry {
|
|
|
279
159
|
groupsTools.getTools().forEach((tool) => {
|
|
280
160
|
this.tools.set(tool.definition.name, tool);
|
|
281
161
|
});
|
|
282
|
-
// Share-send tool (ADR-231)
|
|
283
|
-
const shareSendTools = new ShareSendTools(pica);
|
|
284
|
-
shareSendTools.getTools().forEach((tool) => {
|
|
285
|
-
this.tools.set(tool.definition.name, tool);
|
|
286
|
-
});
|
|
287
162
|
// Recordings tools
|
|
288
163
|
const recordingsTools = new RecordingsTools(pica);
|
|
289
164
|
recordingsTools.getTools().forEach((tool) => {
|
|
@@ -324,11 +199,6 @@ export class ToolRegistry {
|
|
|
324
199
|
memoryTools.getTools().forEach((tool) => {
|
|
325
200
|
this.tools.set(tool.definition.name, tool);
|
|
326
201
|
});
|
|
327
|
-
// Skills tools (ADR-140 Phase 2b — SEP-2640 bridge tool lane)
|
|
328
|
-
const skillsTools = new SkillsTools();
|
|
329
|
-
skillsTools.getTools().forEach((tool) => {
|
|
330
|
-
this.tools.set(tool.definition.name, tool);
|
|
331
|
-
});
|
|
332
202
|
// Enrichment tools
|
|
333
203
|
const enrichmentTools = new EnrichmentTools(pica);
|
|
334
204
|
enrichmentTools.getTools().forEach((tool) => {
|
|
@@ -364,6 +234,12 @@ export class ToolRegistry {
|
|
|
364
234
|
uploadTools.getTools().forEach((tool) => {
|
|
365
235
|
this.tools.set(tool.definition.name, tool);
|
|
366
236
|
});
|
|
237
|
+
// File delivery tools — outbound sharing with recipient accountability
|
|
238
|
+
// and revocation. Wraps /admin/files/deliver + /deliver/[id]/revoke.
|
|
239
|
+
const filesTools = new FilesTools(pica);
|
|
240
|
+
filesTools.getTools().forEach((tool) => {
|
|
241
|
+
this.tools.set(tool.definition.name, tool);
|
|
242
|
+
});
|
|
367
243
|
// Document tools (AI analysis)
|
|
368
244
|
const documentTools = new DocumentTools(pica);
|
|
369
245
|
documentTools.getTools().forEach((tool) => {
|
|
@@ -501,21 +377,6 @@ export class ToolRegistry {
|
|
|
501
377
|
myReportedIssuesTools.getTools().forEach((tool) => {
|
|
502
378
|
this.tools.set(tool.definition.name, tool);
|
|
503
379
|
});
|
|
504
|
-
// My-recent-questions tool (ADR-226 Phase 5 — pica_my_recent_questions)
|
|
505
|
-
// — user-scoped read of the Phase 4 substrate (assistant_interactions
|
|
506
|
-
// joined with mcp_sessions for client identity). Atlas-resolved entry
|
|
507
|
-
// for "what did I ask the AI?".
|
|
508
|
-
//
|
|
509
|
-
// ADR-230 post-ship cleanup — the tier resolver is the registry's
|
|
510
|
-
// own `getToolTier` shape (declared `tier` on each tool definition).
|
|
511
|
-
// Lint Rule 13 makes tier required for every customer MCP tool, so
|
|
512
|
-
// this resolver returns a populated value for all known tools and
|
|
513
|
-
// `undefined` only for unknown / meta tools — `dominantTier` falls
|
|
514
|
-
// back to "read" in that case.
|
|
515
|
-
const myRecentQuestionsTools = new MyRecentQuestionsTools(pica, (name) => this.tools.get(name)?.definition.tier);
|
|
516
|
-
myRecentQuestionsTools.getTools().forEach((tool) => {
|
|
517
|
-
this.tools.set(tool.definition.name, tool);
|
|
518
|
-
});
|
|
519
380
|
// Agent identity tools (ADR-185 Part 1 — session-auth-only MCP surface).
|
|
520
381
|
// The three tools refuse grant-auth callers at the HTTP API layer;
|
|
521
382
|
// the stdio path carries an API key so behaviour is identical either
|
|
@@ -534,21 +395,6 @@ export class ToolRegistry {
|
|
|
534
395
|
shareLinksTools.getTools().forEach((tool) => {
|
|
535
396
|
this.tools.set(tool.definition.name, tool);
|
|
536
397
|
});
|
|
537
|
-
// Sharing trace tool (ADR-232 § Decision 2)
|
|
538
|
-
const sharingTools = new SharingTools(pica);
|
|
539
|
-
sharingTools.getTools().forEach((tool) => {
|
|
540
|
-
this.tools.set(tool.definition.name, tool);
|
|
541
|
-
});
|
|
542
|
-
// Explainability tools (ADR-232 § Decision 3)
|
|
543
|
-
const explainabilityTools = new ExplainabilityTools(pica);
|
|
544
|
-
explainabilityTools.getTools().forEach((tool) => {
|
|
545
|
-
this.tools.set(tool.definition.name, tool);
|
|
546
|
-
});
|
|
547
|
-
// Access simulator — read-only "who would see what?" preview.
|
|
548
|
-
const accessSimulateTools = new AccessSimulateTools(pica);
|
|
549
|
-
accessSimulateTools.getTools().forEach((tool) => {
|
|
550
|
-
this.tools.set(tool.definition.name, tool);
|
|
551
|
-
});
|
|
552
398
|
// Disputes tools (ADR-139 Step 3)
|
|
553
399
|
const disputesTools = new DisputesTools(pica);
|
|
554
400
|
disputesTools.getTools().forEach((tool) => {
|
|
@@ -610,15 +456,72 @@ export class ToolRegistry {
|
|
|
610
456
|
});
|
|
611
457
|
}
|
|
612
458
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
459
|
+
// ── Write-safety classification ──
|
|
460
|
+
// Destructive tools require confirmation before AND summary after.
|
|
461
|
+
// Mutating tools should present what they'll do and confirm after.
|
|
462
|
+
// Safe (read-only) tools need no confirmation.
|
|
463
|
+
static DESTRUCTIVE_PATTERNS = [
|
|
464
|
+
"_delete",
|
|
465
|
+
"_bulk_delete",
|
|
466
|
+
"_merge",
|
|
467
|
+
"_remove",
|
|
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
525
|
}
|
|
623
526
|
/**
|
|
624
527
|
* List all available tools with write-safety prefixes injected.
|
|
@@ -640,14 +543,9 @@ export class ToolRegistry {
|
|
|
640
543
|
return toolsToList.map((tool) => {
|
|
641
544
|
let definition = tool.definition;
|
|
642
545
|
const metadata = getToolMetadata(definition.name);
|
|
643
|
-
//
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
// that bypasses lint.
|
|
647
|
-
const declaredTier = definition.tier;
|
|
648
|
-
const isDestructive = declaredTier
|
|
649
|
-
? declaredTier === "destructive"
|
|
650
|
-
: metadata?.risk === "destructive";
|
|
546
|
+
// Inject confirmation_token into destructive tool schemas
|
|
547
|
+
const isDestructive = metadata?.risk === "destructive" ||
|
|
548
|
+
(!metadata && this.classifyTool(definition.name) === "destructive");
|
|
651
549
|
if (isDestructive) {
|
|
652
550
|
definition = {
|
|
653
551
|
...definition,
|
|
@@ -675,26 +573,6 @@ export class ToolRegistry {
|
|
|
675
573
|
},
|
|
676
574
|
};
|
|
677
575
|
}
|
|
678
|
-
// ADR-230 — derive hints from declared tier when present (every
|
|
679
|
-
// tool registered in mcp-server/src/tools/*.ts has tier as of
|
|
680
|
-
// PR-1; the metadata branch stays as a defensive code path).
|
|
681
|
-
if (declaredTier && metadata) {
|
|
682
|
-
return {
|
|
683
|
-
...definition,
|
|
684
|
-
description: injectMetadataIntoDescription(tool.definition.description, metadata),
|
|
685
|
-
annotations: {
|
|
686
|
-
title: metadata.display_name,
|
|
687
|
-
readOnlyHint: declaredTier === "read",
|
|
688
|
-
destructiveHint: declaredTier === "destructive",
|
|
689
|
-
idempotentHint: declaredTier !== "destructive" && metadata.retry_safe,
|
|
690
|
-
openWorldHint: false,
|
|
691
|
-
// ADR-199 extended annotation classes — read by ChatGPT/Claude.ai
|
|
692
|
-
// connectors directly. Derived from declared tier so the four
|
|
693
|
-
// surfaces (hint, riskLevel, audit row, scope) all agree.
|
|
694
|
-
...this.deriveExtendedAnnotations(definition, metadata, undefined, declaredTier),
|
|
695
|
-
},
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
576
|
if (metadata) {
|
|
699
577
|
return {
|
|
700
578
|
...definition,
|
|
@@ -705,25 +583,25 @@ export class ToolRegistry {
|
|
|
705
583
|
destructiveHint: metadata.risk === "destructive",
|
|
706
584
|
idempotentHint: metadata.risk !== "destructive" && metadata.retry_safe,
|
|
707
585
|
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.
|
|
708
589
|
...this.deriveExtendedAnnotations(definition, metadata),
|
|
709
590
|
},
|
|
710
591
|
};
|
|
711
592
|
}
|
|
712
|
-
//
|
|
713
|
-
|
|
714
|
-
// declare tier. Source hints from declared tier; default to read
|
|
715
|
-
// when tier is somehow missing (lint blocks; defensive only).
|
|
716
|
-
const tierForHints = declaredTier ?? "read";
|
|
593
|
+
// Fallback to old pattern-matching for any tool without explicit metadata
|
|
594
|
+
const classification = this.classifyTool(definition.name);
|
|
717
595
|
const annotations = {
|
|
718
596
|
title: definition.name,
|
|
719
|
-
readOnlyHint:
|
|
720
|
-
destructiveHint:
|
|
597
|
+
readOnlyHint: classification === "safe",
|
|
598
|
+
destructiveHint: classification === "destructive",
|
|
721
599
|
idempotentHint: false,
|
|
722
600
|
openWorldHint: false,
|
|
723
|
-
// ADR-199: derive the extended classes
|
|
724
|
-
//
|
|
725
|
-
//
|
|
726
|
-
...this.deriveExtendedAnnotations(definition, null,
|
|
601
|
+
// ADR-199: derive the extended classes even without explicit
|
|
602
|
+
// metadata so the fallback tools also surface category/risk to
|
|
603
|
+
// ChatGPT/Claude.ai connectors.
|
|
604
|
+
...this.deriveExtendedAnnotations(definition, null, classification),
|
|
727
605
|
};
|
|
728
606
|
// No risk prefix needed — annotations handle this structurally
|
|
729
607
|
return { ...definition, annotations };
|
|
@@ -738,16 +616,9 @@ export class ToolRegistry {
|
|
|
738
616
|
* edits required for the retrofit. Future tool authors can override by
|
|
739
617
|
* setting fields directly on definition.annotations.
|
|
740
618
|
*/
|
|
741
|
-
deriveExtendedAnnotations(definition, metadata,
|
|
619
|
+
deriveExtendedAnnotations(definition, metadata, classification) {
|
|
742
620
|
const name = definition.name;
|
|
743
|
-
|
|
744
|
-
// remains a quiet fallback for any tool that bypasses lint Rule 13.
|
|
745
|
-
// (The `_classification` parameter is retained as an unused
|
|
746
|
-
// positional slot so legacy call signatures continue to compile;
|
|
747
|
-
// its value is now ignored — classifyTool was removed in PR-3.)
|
|
748
|
-
const risk = declaredTier
|
|
749
|
-
? tierToRiskLevel(declaredTier)
|
|
750
|
-
: (metadata?.risk ?? "safe");
|
|
621
|
+
const risk = metadata?.risk ?? classification ?? "safe";
|
|
751
622
|
// Side-effecting external systems: telegram, send-hub, comms, broadcasts.
|
|
752
623
|
// Pattern-derived so new tools inherit without edits.
|
|
753
624
|
const externalSideEffectPatterns = [
|
|
@@ -767,16 +638,10 @@ export class ToolRegistry {
|
|
|
767
638
|
// the agent wants to confirm — the gap report flagged this as a useful
|
|
768
639
|
// class for ChatGPT to surface in the UI.
|
|
769
640
|
const requiresFollowUpInspection = risk === "mutating" || risk === "destructive";
|
|
770
|
-
// ADR-230 — destructive tier always requires confirmation. Other tiers
|
|
771
|
-
// never do via this annotation (per-tool opt-in for `write` tier is
|
|
772
|
-
// expressed through `previewMode: "two_step_token"`, not here).
|
|
773
|
-
const requiresConfirmation = declaredTier
|
|
774
|
-
? declaredTier === "destructive"
|
|
775
|
-
: risk === "destructive";
|
|
776
641
|
return {
|
|
777
642
|
riskLevel: risk,
|
|
778
643
|
category: metadata?.category,
|
|
779
|
-
requiresConfirmation,
|
|
644
|
+
requiresConfirmation: risk === "destructive",
|
|
780
645
|
createsExternalSideEffects,
|
|
781
646
|
returnsPaginated,
|
|
782
647
|
requiresFollowUpInspection,
|
|
@@ -969,11 +834,18 @@ export class ToolRegistry {
|
|
|
969
834
|
}
|
|
970
835
|
}
|
|
971
836
|
/**
|
|
972
|
-
* Sanitize tool parameters for audit logging
|
|
973
|
-
*
|
|
837
|
+
* Sanitize tool parameters for audit logging.
|
|
838
|
+
* Strips confirmation tokens and truncates large string values.
|
|
974
839
|
*/
|
|
975
840
|
sanitizeParams(args) {
|
|
976
|
-
|
|
841
|
+
const sanitized = { ...args };
|
|
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;
|
|
977
849
|
}
|
|
978
850
|
/**
|
|
979
851
|
* ADR-208 Primitive B — opaque per-org cache key.
|
|
@@ -1146,22 +1018,10 @@ export class ToolRegistry {
|
|
|
1146
1018
|
throw new ToolExecutionError(`Tool not found: ${name}`);
|
|
1147
1019
|
}
|
|
1148
1020
|
const metadata = getToolMetadata(name);
|
|
1149
|
-
// ADR-230 — declared tier is the source of truth for the freshness +
|
|
1150
|
-
// confirmation gates and the audit-log surfaces. Fall back to
|
|
1151
|
-
// metadata.risk only if the tool somehow lacks tier (lint blocks
|
|
1152
|
-
// missing tier on creator + team + directory MCPs as of PR-2).
|
|
1153
|
-
const declaredTier = tool.definition.tier;
|
|
1154
|
-
const fallbackRisk = metadata?.risk ?? "safe";
|
|
1155
|
-
const auditTier = declaredTier ??
|
|
1156
|
-
(fallbackRisk === "safe"
|
|
1157
|
-
? "read"
|
|
1158
|
-
: fallbackRisk === "destructive"
|
|
1159
|
-
? "destructive"
|
|
1160
|
-
: "write");
|
|
1161
|
-
const auditRiskLevel = tierToRiskLevel(auditTier);
|
|
1162
|
-
const isDestructive = auditTier === "destructive";
|
|
1163
1021
|
// ── Session freshness gate (ADR-151) ──
|
|
1164
|
-
if (
|
|
1022
|
+
if (metadata?.risk === "destructive" &&
|
|
1023
|
+
this.config &&
|
|
1024
|
+
!this.config.lobbyMode) {
|
|
1165
1025
|
const creds = readCredentials(this.config.credentialsPath);
|
|
1166
1026
|
if (creds?.authenticated_at) {
|
|
1167
1027
|
const authAge = Date.now() - new Date(creds.authenticated_at).getTime();
|
|
@@ -1179,15 +1039,12 @@ export class ToolRegistry {
|
|
|
1179
1039
|
}
|
|
1180
1040
|
}
|
|
1181
1041
|
}
|
|
1182
|
-
// ── Destructive confirmation gate
|
|
1183
|
-
if (
|
|
1042
|
+
// ── Destructive confirmation gate ──
|
|
1043
|
+
if (metadata?.risk === "destructive") {
|
|
1184
1044
|
const confirmationToken = args.confirmation_token;
|
|
1185
1045
|
if (!confirmationToken) {
|
|
1186
|
-
// First call — generate preview and return confirmation challenge
|
|
1187
|
-
|
|
1188
|
-
// Fluid Compute (or in-memory for stdio); both paths via the
|
|
1189
|
-
// active ConfirmationStore singleton, so callers stay unchanged.
|
|
1190
|
-
const token = await generateConfirmationToken(name, args);
|
|
1046
|
+
// First call — generate preview and return confirmation challenge
|
|
1047
|
+
const token = generateConfirmationToken(name, args);
|
|
1191
1048
|
const preview = await this.buildDestructivePreview(name, args);
|
|
1192
1049
|
const response = {
|
|
1193
1050
|
content: [
|
|
@@ -1213,7 +1070,6 @@ export class ToolRegistry {
|
|
|
1213
1070
|
?.logToolExecution({
|
|
1214
1071
|
tool_name: name,
|
|
1215
1072
|
tool_category: metadata?.category || "unknown",
|
|
1216
|
-
tier: "destructive",
|
|
1217
1073
|
risk_level: "destructive",
|
|
1218
1074
|
parameters: this.sanitizeParams(args),
|
|
1219
1075
|
result_status: "confirmation_required",
|
|
@@ -1221,14 +1077,12 @@ export class ToolRegistry {
|
|
|
1221
1077
|
execution_time_ms: Date.now() - startTime,
|
|
1222
1078
|
caller_identity: this.callerContext.callerIdentity,
|
|
1223
1079
|
transport: this.callerContext.transport,
|
|
1224
|
-
...this.extractClientInfo(ctx),
|
|
1225
1080
|
})
|
|
1226
1081
|
.catch(() => { }); // Never block on audit
|
|
1227
1082
|
return response;
|
|
1228
1083
|
}
|
|
1229
|
-
// Second call — validate token
|
|
1230
|
-
|
|
1231
|
-
const validation = await validateAndConsumeToken(confirmationToken, name, args);
|
|
1084
|
+
// Second call — validate token
|
|
1085
|
+
const validation = validateAndConsumeToken(confirmationToken, name, args);
|
|
1232
1086
|
if (!validation.valid) {
|
|
1233
1087
|
return {
|
|
1234
1088
|
content: [
|
|
@@ -1261,10 +1115,10 @@ export class ToolRegistry {
|
|
|
1261
1115
|
}
|
|
1262
1116
|
}
|
|
1263
1117
|
// ADR-208 Primitive B — ambient session-state on write-tool results.
|
|
1264
|
-
// Read tools (`
|
|
1265
|
-
// read path stays cheap. `result.isError === true` also
|
|
1266
|
-
// a failed mutation didn't change catalog state.
|
|
1267
|
-
const isWriteTool =
|
|
1118
|
+
// Read tools (`risk: safe` ⇒ `readOnlyHint: true`) skip this entirely so
|
|
1119
|
+
// the high-volume read path stays cheap. `result.isError === true` also
|
|
1120
|
+
// skips because a failed mutation didn't change catalog state.
|
|
1121
|
+
const isWriteTool = (metadata?.risk ?? this.classifyTool(name)) !== "safe";
|
|
1268
1122
|
if (isWriteTool && result && !result.isError) {
|
|
1269
1123
|
await this.attachSessionState(result, name);
|
|
1270
1124
|
}
|
|
@@ -1290,14 +1144,12 @@ export class ToolRegistry {
|
|
|
1290
1144
|
?.logToolExecution({
|
|
1291
1145
|
tool_name: name,
|
|
1292
1146
|
tool_category: metadata?.category || "unknown",
|
|
1293
|
-
|
|
1294
|
-
risk_level: auditRiskLevel,
|
|
1147
|
+
risk_level: metadata?.risk || this.classifyTool(name),
|
|
1295
1148
|
parameters: this.sanitizeParams(args),
|
|
1296
1149
|
result_status: "success",
|
|
1297
1150
|
execution_time_ms: Date.now() - startTime,
|
|
1298
1151
|
caller_identity: this.callerContext.callerIdentity,
|
|
1299
1152
|
transport: this.callerContext.transport,
|
|
1300
|
-
...this.extractClientInfo(ctx),
|
|
1301
1153
|
})
|
|
1302
1154
|
.catch(() => { }); // Never block on audit
|
|
1303
1155
|
return result;
|
|
@@ -1318,19 +1170,13 @@ export class ToolRegistry {
|
|
|
1318
1170
|
?.logToolExecution({
|
|
1319
1171
|
tool_name: name,
|
|
1320
1172
|
tool_category: metadata?.category || "unknown",
|
|
1321
|
-
|
|
1322
|
-
risk_level: auditRiskLevel,
|
|
1173
|
+
risk_level: metadata?.risk || this.classifyTool(name),
|
|
1323
1174
|
parameters: this.sanitizeParams(args),
|
|
1324
1175
|
result_status: "error",
|
|
1325
1176
|
error_code: parsed.error,
|
|
1326
|
-
// Stage A — forward the parsed body's message so mcp_audit_log
|
|
1327
|
-
// captures the underlying cause. Without this every error row had
|
|
1328
|
-
// error_message=null, leaving UNKNOWN_ERROR rows opaque.
|
|
1329
|
-
error_message: typeof parsed.message === "string" ? parsed.message : undefined,
|
|
1330
1177
|
execution_time_ms: Date.now() - startTime,
|
|
1331
1178
|
caller_identity: this.callerContext.callerIdentity,
|
|
1332
1179
|
transport: this.callerContext.transport,
|
|
1333
|
-
...this.extractClientInfo(ctx),
|
|
1334
1180
|
})
|
|
1335
1181
|
.catch(() => { }); // Never block on audit
|
|
1336
1182
|
return {
|