@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.
Files changed (247) hide show
  1. package/.claude/settings.local.json +5 -0
  2. package/CHANGELOG.md +0 -41
  3. package/dist/prompts/index.d.ts +0 -32
  4. package/dist/prompts/index.d.ts.map +1 -1
  5. package/dist/prompts/index.js +0 -235
  6. package/dist/prompts/index.js.map +1 -1
  7. package/dist/resources/index.d.ts +0 -10
  8. package/dist/resources/index.d.ts.map +1 -1
  9. package/dist/resources/index.js +1 -134
  10. package/dist/resources/index.js.map +1 -1
  11. package/dist/server-instructions.d.ts +3 -4
  12. package/dist/server-instructions.d.ts.map +1 -1
  13. package/dist/server-instructions.js +1 -4
  14. package/dist/server-instructions.js.map +1 -1
  15. package/dist/server.d.ts +0 -26
  16. package/dist/server.d.ts.map +1 -1
  17. package/dist/server.js +10 -108
  18. package/dist/server.js.map +1 -1
  19. package/dist/tools/agent-identity.d.ts.map +1 -1
  20. package/dist/tools/agent-identity.js +0 -15
  21. package/dist/tools/agent-identity.js.map +1 -1
  22. package/dist/tools/agreement-types.d.ts.map +1 -1
  23. package/dist/tools/agreement-types.js +0 -24
  24. package/dist/tools/agreement-types.js.map +1 -1
  25. package/dist/tools/agreements.d.ts.map +1 -1
  26. package/dist/tools/agreements.js +3 -21
  27. package/dist/tools/agreements.js.map +1 -1
  28. package/dist/tools/analytics.d.ts.map +1 -1
  29. package/dist/tools/analytics.js +1 -19
  30. package/dist/tools/analytics.js.map +1 -1
  31. package/dist/tools/app-tools.d.ts.map +1 -1
  32. package/dist/tools/app-tools.js +2 -11
  33. package/dist/tools/app-tools.js.map +1 -1
  34. package/dist/tools/assets.d.ts.map +1 -1
  35. package/dist/tools/assets.js +0 -33
  36. package/dist/tools/assets.js.map +1 -1
  37. package/dist/tools/audio-files.d.ts +0 -5
  38. package/dist/tools/audio-files.d.ts.map +1 -1
  39. package/dist/tools/audio-files.js +0 -91
  40. package/dist/tools/audio-files.js.map +1 -1
  41. package/dist/tools/audit.d.ts.map +1 -1
  42. package/dist/tools/audit.js +2 -11
  43. package/dist/tools/audit.js.map +1 -1
  44. package/dist/tools/auth.d.ts.map +1 -1
  45. package/dist/tools/auth.js +0 -6
  46. package/dist/tools/auth.js.map +1 -1
  47. package/dist/tools/bulk.d.ts.map +1 -1
  48. package/dist/tools/bulk.js +0 -6
  49. package/dist/tools/bulk.js.map +1 -1
  50. package/dist/tools/calendar.d.ts.map +1 -1
  51. package/dist/tools/calendar.js +0 -3
  52. package/dist/tools/calendar.js.map +1 -1
  53. package/dist/tools/collaborators.d.ts.map +1 -1
  54. package/dist/tools/collaborators.js +3 -24
  55. package/dist/tools/collaborators.js.map +1 -1
  56. package/dist/tools/comparisons.d.ts.map +1 -1
  57. package/dist/tools/comparisons.js +0 -6
  58. package/dist/tools/comparisons.js.map +1 -1
  59. package/dist/tools/credits.d.ts +0 -18
  60. package/dist/tools/credits.d.ts.map +1 -1
  61. package/dist/tools/credits.js +4 -344
  62. package/dist/tools/credits.js.map +1 -1
  63. package/dist/tools/custody.d.ts.map +1 -1
  64. package/dist/tools/custody.js +2 -23
  65. package/dist/tools/custody.js.map +1 -1
  66. package/dist/tools/dashboard.d.ts.map +1 -1
  67. package/dist/tools/dashboard.js +7 -43
  68. package/dist/tools/dashboard.js.map +1 -1
  69. package/dist/tools/directory.d.ts.map +1 -1
  70. package/dist/tools/directory.js +0 -3
  71. package/dist/tools/directory.js.map +1 -1
  72. package/dist/tools/discovery.d.ts.map +1 -1
  73. package/dist/tools/discovery.js +7 -94
  74. package/dist/tools/discovery.js.map +1 -1
  75. package/dist/tools/disputes.d.ts.map +1 -1
  76. package/dist/tools/disputes.js +1 -4
  77. package/dist/tools/disputes.js.map +1 -1
  78. package/dist/tools/documents.d.ts.map +1 -1
  79. package/dist/tools/documents.js +0 -3
  80. package/dist/tools/documents.js.map +1 -1
  81. package/dist/tools/duplicates.d.ts.map +1 -1
  82. package/dist/tools/duplicates.js +0 -6
  83. package/dist/tools/duplicates.js.map +1 -1
  84. package/dist/tools/enrichment.d.ts.map +1 -1
  85. package/dist/tools/enrichment.js +0 -33
  86. package/dist/tools/enrichment.js.map +1 -1
  87. package/dist/tools/exports.d.ts.map +1 -1
  88. package/dist/tools/exports.js +3 -18
  89. package/dist/tools/exports.js.map +1 -1
  90. package/dist/tools/feedback.d.ts.map +1 -1
  91. package/dist/tools/feedback.js +0 -3
  92. package/dist/tools/feedback.js.map +1 -1
  93. package/dist/tools/files.d.ts +30 -0
  94. package/dist/tools/files.d.ts.map +1 -0
  95. package/dist/tools/files.js +98 -0
  96. package/dist/tools/files.js.map +1 -0
  97. package/dist/tools/groups.d.ts.map +1 -1
  98. package/dist/tools/groups.js +0 -12
  99. package/dist/tools/groups.js.map +1 -1
  100. package/dist/tools/import-documents.d.ts.map +1 -1
  101. package/dist/tools/import-documents.js +1 -10
  102. package/dist/tools/import-documents.js.map +1 -1
  103. package/dist/tools/import.d.ts.map +1 -1
  104. package/dist/tools/import.js +3 -36
  105. package/dist/tools/import.js.map +1 -1
  106. package/dist/tools/index.d.ts +6 -142
  107. package/dist/tools/index.d.ts.map +1 -1
  108. package/dist/tools/index.js +115 -269
  109. package/dist/tools/index.js.map +1 -1
  110. package/dist/tools/integrations.d.ts.map +1 -1
  111. package/dist/tools/integrations.js +8 -28
  112. package/dist/tools/integrations.js.map +1 -1
  113. package/dist/tools/labels.d.ts.map +1 -1
  114. package/dist/tools/labels.js +0 -3
  115. package/dist/tools/labels.js.map +1 -1
  116. package/dist/tools/licensing.d.ts.map +1 -1
  117. package/dist/tools/licensing.js +0 -15
  118. package/dist/tools/licensing.js.map +1 -1
  119. package/dist/tools/memory.d.ts.map +1 -1
  120. package/dist/tools/memory.js +3 -15
  121. package/dist/tools/memory.js.map +1 -1
  122. package/dist/tools/metadata.d.ts.map +1 -1
  123. package/dist/tools/metadata.js +15 -77
  124. package/dist/tools/metadata.js.map +1 -1
  125. package/dist/tools/multimedia.d.ts.map +1 -1
  126. package/dist/tools/multimedia.js +0 -15
  127. package/dist/tools/multimedia.js.map +1 -1
  128. package/dist/tools/my-reported-issues.d.ts.map +1 -1
  129. package/dist/tools/my-reported-issues.js +0 -3
  130. package/dist/tools/my-reported-issues.js.map +1 -1
  131. package/dist/tools/notes.d.ts.map +1 -1
  132. package/dist/tools/notes.js +0 -12
  133. package/dist/tools/notes.js.map +1 -1
  134. package/dist/tools/notifications.d.ts.map +1 -1
  135. package/dist/tools/notifications.js +1 -25
  136. package/dist/tools/notifications.js.map +1 -1
  137. package/dist/tools/onboarding.d.ts.map +1 -1
  138. package/dist/tools/onboarding.js +0 -3
  139. package/dist/tools/onboarding.js.map +1 -1
  140. package/dist/tools/people.d.ts.map +1 -1
  141. package/dist/tools/people.js +1 -16
  142. package/dist/tools/people.js.map +1 -1
  143. package/dist/tools/projects.d.ts.map +1 -1
  144. package/dist/tools/projects.js +0 -18
  145. package/dist/tools/projects.js.map +1 -1
  146. package/dist/tools/publishers.d.ts.map +1 -1
  147. package/dist/tools/publishers.js +0 -6
  148. package/dist/tools/publishers.js.map +1 -1
  149. package/dist/tools/recordings.d.ts.map +1 -1
  150. package/dist/tools/recordings.js +0 -15
  151. package/dist/tools/recordings.js.map +1 -1
  152. package/dist/tools/recovery-hints.d.ts.map +1 -1
  153. package/dist/tools/recovery-hints.js +2 -28
  154. package/dist/tools/recovery-hints.js.map +1 -1
  155. package/dist/tools/release-rich.d.ts.map +1 -1
  156. package/dist/tools/release-rich.js +2 -4
  157. package/dist/tools/release-rich.js.map +1 -1
  158. package/dist/tools/releases.d.ts.map +1 -1
  159. package/dist/tools/releases.js +0 -55
  160. package/dist/tools/releases.js.map +1 -1
  161. package/dist/tools/report-issue.d.ts.map +1 -1
  162. package/dist/tools/report-issue.js +0 -3
  163. package/dist/tools/report-issue.js.map +1 -1
  164. package/dist/tools/royalties.d.ts.map +1 -1
  165. package/dist/tools/royalties.js +3 -18
  166. package/dist/tools/royalties.js.map +1 -1
  167. package/dist/tools/search.d.ts.map +1 -1
  168. package/dist/tools/search.js +1 -10
  169. package/dist/tools/search.js.map +1 -1
  170. package/dist/tools/send.d.ts.map +1 -1
  171. package/dist/tools/send.js +0 -9
  172. package/dist/tools/send.js.map +1 -1
  173. package/dist/tools/sessions.d.ts.map +1 -1
  174. package/dist/tools/sessions.js +0 -12
  175. package/dist/tools/sessions.js.map +1 -1
  176. package/dist/tools/settings.d.ts.map +1 -1
  177. package/dist/tools/settings.js +3 -30
  178. package/dist/tools/settings.js.map +1 -1
  179. package/dist/tools/share-links.d.ts.map +1 -1
  180. package/dist/tools/share-links.js +0 -15
  181. package/dist/tools/share-links.js.map +1 -1
  182. package/dist/tools/signup.d.ts.map +1 -1
  183. package/dist/tools/signup.js +0 -3
  184. package/dist/tools/signup.js.map +1 -1
  185. package/dist/tools/split-sheets.d.ts.map +1 -1
  186. package/dist/tools/split-sheets.js +1 -22
  187. package/dist/tools/split-sheets.js.map +1 -1
  188. package/dist/tools/storage-config.d.ts.map +1 -1
  189. package/dist/tools/storage-config.js +0 -6
  190. package/dist/tools/storage-config.js.map +1 -1
  191. package/dist/tools/subscription.d.ts.map +1 -1
  192. package/dist/tools/subscription.js +10 -9
  193. package/dist/tools/subscription.js.map +1 -1
  194. package/dist/tools/sync-placements.d.ts.map +1 -1
  195. package/dist/tools/sync-placements.js +2 -20
  196. package/dist/tools/sync-placements.js.map +1 -1
  197. package/dist/tools/team.d.ts.map +1 -1
  198. package/dist/tools/team.js +0 -15
  199. package/dist/tools/team.js.map +1 -1
  200. package/dist/tools/telegram.d.ts.map +1 -1
  201. package/dist/tools/telegram.js +0 -9
  202. package/dist/tools/telegram.js.map +1 -1
  203. package/dist/tools/uploads.d.ts.map +1 -1
  204. package/dist/tools/uploads.js +0 -6
  205. package/dist/tools/uploads.js.map +1 -1
  206. package/dist/tools/works.d.ts.map +1 -1
  207. package/dist/tools/works.js +3 -37
  208. package/dist/tools/works.js.map +1 -1
  209. package/package.json +6 -7
  210. package/server.json +2 -2
  211. package/dist/prompts/creator-question-atlas.d.ts +0 -48
  212. package/dist/prompts/creator-question-atlas.d.ts.map +0 -1
  213. package/dist/prompts/creator-question-atlas.js +0 -618
  214. package/dist/prompts/creator-question-atlas.js.map +0 -1
  215. package/dist/skills/index.d.ts +0 -42
  216. package/dist/skills/index.d.ts.map +0 -1
  217. package/dist/skills/index.js +0 -59
  218. package/dist/skills/index.js.map +0 -1
  219. package/dist/skills/skills.generated.d.ts +0 -25
  220. package/dist/skills/skills.generated.d.ts.map +0 -1
  221. package/dist/skills/skills.generated.js +0 -86
  222. package/dist/skills/skills.generated.js.map +0 -1
  223. package/dist/tools/access-simulate.d.ts +0 -23
  224. package/dist/tools/access-simulate.d.ts.map +0 -1
  225. package/dist/tools/access-simulate.js +0 -165
  226. package/dist/tools/access-simulate.js.map +0 -1
  227. package/dist/tools/explainability.d.ts +0 -24
  228. package/dist/tools/explainability.d.ts.map +0 -1
  229. package/dist/tools/explainability.js +0 -137
  230. package/dist/tools/explainability.js.map +0 -1
  231. package/dist/tools/my-recent-questions.d.ts +0 -25
  232. package/dist/tools/my-recent-questions.d.ts.map +0 -1
  233. package/dist/tools/my-recent-questions.js +0 -186
  234. package/dist/tools/my-recent-questions.js.map +0 -1
  235. package/dist/tools/share-send.d.ts +0 -28
  236. package/dist/tools/share-send.d.ts.map +0 -1
  237. package/dist/tools/share-send.js +0 -131
  238. package/dist/tools/share-send.js.map +0 -1
  239. package/dist/tools/sharing.d.ts +0 -29
  240. package/dist/tools/sharing.d.ts.map +0 -1
  241. package/dist/tools/sharing.js +0 -131
  242. package/dist/tools/sharing.js.map +0 -1
  243. package/dist/tools/skills.d.ts +0 -25
  244. package/dist/tools/skills.d.ts.map +0 -1
  245. package/dist/tools/skills.js +0 -144
  246. package/dist/tools/skills.js.map +0 -1
  247. package/scripts/build-skills.ts +0 -229
@@ -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
- * ADR-230 declared tier lookup by tool name. Used by the HTTP MCP
615
- * dispatcher (`app/api/mcp/route.ts`) to enforce the elevated
616
- * `destructive:*` scope on top of the resource-level scope from
617
- * `lib/services/mcp-scopes`. Returns undefined for unknown tools or
618
- * tools that haven't declared tier yet (legacy tolerance).
619
- */
620
- getToolTier(name) {
621
- return this.tools.get(name)?.definition.tier;
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
- // ADR-230 declared tier is the source of truth for hints + risk.
644
- // Lint Rule 13 enforces tier presence on every customer MCP tool;
645
- // metadata.risk is a quiet fallback for the unlikely case of a tool
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
- // ADR-230 tools without a TOOL_METADATA entry (the 5 lobby
713
- // meta-tools sign_in/out/discover/tool_details/execute) still
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: tierForHints === "read",
720
- destructiveHint: tierForHints === "destructive",
597
+ readOnlyHint: classification === "safe",
598
+ destructiveHint: classification === "destructive",
721
599
  idempotentHint: false,
722
600
  openWorldHint: false,
723
- // ADR-199: derive the extended classes from declared tier so the
724
- // category/risk surface stays aligned with the rest of the
725
- // hint/audit/scope chain.
726
- ...this.deriveExtendedAnnotations(definition, null, undefined, tierForHints),
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, _classification, declaredTier) {
619
+ deriveExtendedAnnotations(definition, metadata, classification) {
742
620
  const name = definition.name;
743
- // ADR-230 declared tier is the source of truth. metadata.risk
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 — delegates to the
973
- * pure-function impl below so it can be exercised directly in tests.
837
+ * Sanitize tool parameters for audit logging.
838
+ * Strips confirmation tokens and truncates large string values.
974
839
  */
975
840
  sanitizeParams(args) {
976
- return sanitizeAuditParams(args);
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 (isDestructive && this.config && !this.config.lobbyMode) {
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 (ADR-230 — keyed on declared tier) ──
1183
- if (isDestructive) {
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
- // ADR-230 PR-4 — token state lives in Postgres for HTTP MCP under
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 (Postgres-backed under Fluid Compute
1230
- // since ADR-230 PR-4; the active store handles cross-instance state).
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 (`tier: "read"`) skip this entirely so the high-volume
1265
- // read path stays cheap. `result.isError === true` also skips because
1266
- // a failed mutation didn't change catalog state.
1267
- const isWriteTool = auditRiskLevel !== "safe";
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
- tier: auditTier,
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
- tier: auditTier,
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 {