@withpica/mcp-server 2.5.2 → 2.5.4

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 (154) hide show
  1. package/assets/fonts/GeistSans-Light.woff2 +0 -0
  2. package/assets/fonts/InstrumentSerif-Italic.woff2 +0 -0
  3. package/assets/fonts/InstrumentSerif-Regular.woff2 +0 -0
  4. package/dist/apps/briefing.d.ts +2 -0
  5. package/dist/apps/briefing.d.ts.map +1 -0
  6. package/dist/apps/briefing.js +308 -0
  7. package/dist/apps/briefing.js.map +1 -0
  8. package/dist/apps/generated/shared-bundle.d.ts +5 -0
  9. package/dist/apps/generated/shared-bundle.d.ts.map +1 -0
  10. package/dist/apps/generated/shared-bundle.js +7 -0
  11. package/dist/apps/generated/shared-bundle.js.map +1 -0
  12. package/dist/apps/shared.d.ts +4 -0
  13. package/dist/apps/shared.d.ts.map +1 -0
  14. package/dist/apps/shared.js +268 -0
  15. package/dist/apps/shared.js.map +1 -0
  16. package/dist/config.d.ts +3 -25
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +25 -12
  19. package/dist/config.js.map +1 -1
  20. package/dist/index.js +3 -3
  21. package/dist/index.js.map +1 -1
  22. package/dist/pica-sdk.d.ts +11 -0
  23. package/dist/pica-sdk.d.ts.map +1 -1
  24. package/dist/pica-sdk.js +16 -0
  25. package/dist/pica-sdk.js.map +1 -1
  26. package/dist/prompts/index.js +24 -24
  27. package/dist/prompts/index.js.map +1 -1
  28. package/dist/resources/index.d.ts +3 -1
  29. package/dist/resources/index.d.ts.map +1 -1
  30. package/dist/resources/index.js +101 -53
  31. package/dist/resources/index.js.map +1 -1
  32. package/dist/resources/llms-primer.d.ts +1 -1
  33. package/dist/resources/llms-primer.d.ts.map +1 -1
  34. package/dist/resources/llms-primer.js +4 -4
  35. package/dist/server-instructions.d.ts +9 -0
  36. package/dist/server-instructions.d.ts.map +1 -0
  37. package/dist/server-instructions.js +34 -0
  38. package/dist/server-instructions.js.map +1 -0
  39. package/dist/server.d.ts +4 -0
  40. package/dist/server.d.ts.map +1 -1
  41. package/dist/server.js +30 -8
  42. package/dist/server.js.map +1 -1
  43. package/dist/tools/agreement-types.d.ts +9 -19
  44. package/dist/tools/agreement-types.d.ts.map +1 -1
  45. package/dist/tools/agreement-types.js +201 -417
  46. package/dist/tools/agreement-types.js.map +1 -1
  47. package/dist/tools/agreements.d.ts +2 -3
  48. package/dist/tools/agreements.d.ts.map +1 -1
  49. package/dist/tools/agreements.js +71 -51
  50. package/dist/tools/agreements.js.map +1 -1
  51. package/dist/tools/app-tools.d.ts +18 -0
  52. package/dist/tools/app-tools.d.ts.map +1 -0
  53. package/dist/tools/app-tools.js +87 -0
  54. package/dist/tools/app-tools.js.map +1 -0
  55. package/dist/tools/assets.d.ts +1 -3
  56. package/dist/tools/assets.d.ts.map +1 -1
  57. package/dist/tools/assets.js +63 -45
  58. package/dist/tools/assets.js.map +1 -1
  59. package/dist/tools/audio-files.d.ts +2 -3
  60. package/dist/tools/audio-files.d.ts.map +1 -1
  61. package/dist/tools/audio-files.js +61 -33
  62. package/dist/tools/audio-files.js.map +1 -1
  63. package/dist/tools/auth.d.ts +20 -0
  64. package/dist/tools/auth.d.ts.map +1 -0
  65. package/dist/tools/auth.js +195 -0
  66. package/dist/tools/auth.js.map +1 -0
  67. package/dist/tools/collaborators.js +1 -1
  68. package/dist/tools/collaborators.js.map +1 -1
  69. package/dist/tools/credits.d.ts.map +1 -1
  70. package/dist/tools/credits.js +128 -18
  71. package/dist/tools/credits.js.map +1 -1
  72. package/dist/tools/dashboard.d.ts +1 -2
  73. package/dist/tools/dashboard.d.ts.map +1 -1
  74. package/dist/tools/dashboard.js +19 -27
  75. package/dist/tools/dashboard.js.map +1 -1
  76. package/dist/tools/duplicates.d.ts.map +1 -1
  77. package/dist/tools/duplicates.js +15 -0
  78. package/dist/tools/duplicates.js.map +1 -1
  79. package/dist/tools/enrichment.d.ts +0 -3
  80. package/dist/tools/enrichment.d.ts.map +1 -1
  81. package/dist/tools/enrichment.js +17 -120
  82. package/dist/tools/enrichment.js.map +1 -1
  83. package/dist/tools/import-documents.d.ts +3 -3
  84. package/dist/tools/import-documents.d.ts.map +1 -1
  85. package/dist/tools/import-documents.js +64 -50
  86. package/dist/tools/import-documents.js.map +1 -1
  87. package/dist/tools/import.d.ts +1 -0
  88. package/dist/tools/import.d.ts.map +1 -1
  89. package/dist/tools/import.js +63 -1
  90. package/dist/tools/import.js.map +1 -1
  91. package/dist/tools/index.d.ts +44 -3
  92. package/dist/tools/index.d.ts.map +1 -1
  93. package/dist/tools/index.js +410 -81
  94. package/dist/tools/index.js.map +1 -1
  95. package/dist/tools/metadata.d.ts +15 -0
  96. package/dist/tools/metadata.d.ts.map +1 -0
  97. package/dist/tools/metadata.js +1057 -0
  98. package/dist/tools/metadata.js.map +1 -0
  99. package/dist/tools/people.d.ts +4 -12
  100. package/dist/tools/people.d.ts.map +1 -1
  101. package/dist/tools/people.js +148 -109
  102. package/dist/tools/people.js.map +1 -1
  103. package/dist/tools/publishers.d.ts +16 -0
  104. package/dist/tools/publishers.d.ts.map +1 -0
  105. package/dist/tools/publishers.js +69 -0
  106. package/dist/tools/publishers.js.map +1 -0
  107. package/dist/tools/recordings.d.ts +8 -8
  108. package/dist/tools/recordings.d.ts.map +1 -1
  109. package/dist/tools/recordings.js +121 -48
  110. package/dist/tools/recordings.js.map +1 -1
  111. package/dist/tools/recovery-hints.d.ts +14 -0
  112. package/dist/tools/recovery-hints.d.ts.map +1 -0
  113. package/dist/tools/recovery-hints.js +277 -0
  114. package/dist/tools/recovery-hints.js.map +1 -0
  115. package/dist/tools/royalties.js +1 -1
  116. package/dist/tools/royalties.js.map +1 -1
  117. package/dist/tools/search.js +2 -2
  118. package/dist/tools/search.js.map +1 -1
  119. package/dist/tools/send.d.ts +1 -2
  120. package/dist/tools/send.d.ts.map +1 -1
  121. package/dist/tools/send.js +19 -27
  122. package/dist/tools/send.js.map +1 -1
  123. package/dist/tools/split-sheets.d.ts.map +1 -1
  124. package/dist/tools/split-sheets.js +12 -0
  125. package/dist/tools/split-sheets.js.map +1 -1
  126. package/dist/tools/works.d.ts +4 -12
  127. package/dist/tools/works.d.ts.map +1 -1
  128. package/dist/tools/works.js +201 -116
  129. package/dist/tools/works.js.map +1 -1
  130. package/dist/utils/audit.d.ts +28 -0
  131. package/dist/utils/audit.d.ts.map +1 -0
  132. package/dist/utils/audit.js +28 -0
  133. package/dist/utils/audit.js.map +1 -0
  134. package/dist/utils/confirmation.d.ts +12 -0
  135. package/dist/utils/confirmation.d.ts.map +1 -0
  136. package/dist/utils/confirmation.js +78 -0
  137. package/dist/utils/confirmation.js.map +1 -0
  138. package/dist/utils/credentials.d.ts +11 -0
  139. package/dist/utils/credentials.d.ts.map +1 -0
  140. package/dist/utils/credentials.js +47 -0
  141. package/dist/utils/credentials.js.map +1 -0
  142. package/dist/utils/enrichment-format.d.ts +31 -0
  143. package/dist/utils/enrichment-format.d.ts.map +1 -0
  144. package/dist/utils/enrichment-format.js +69 -0
  145. package/dist/utils/enrichment-format.js.map +1 -0
  146. package/dist/utils/errors.d.ts.map +1 -1
  147. package/dist/utils/errors.js +29 -5
  148. package/dist/utils/errors.js.map +1 -1
  149. package/dist/utils/formatting.d.ts +1 -0
  150. package/dist/utils/formatting.d.ts.map +1 -1
  151. package/dist/utils/formatting.js +30 -2
  152. package/dist/utils/formatting.js.map +1 -1
  153. package/package.json +3 -1
  154. package/scripts/bundle-apps.ts +61 -0
@@ -5,14 +5,11 @@ import { RecordingsTools } from "./recordings.js";
5
5
  import { SearchTools } from "./search.js";
6
6
  import { LicensingTools } from "./licensing.js";
7
7
  import { CreditsTools } from "./credits.js";
8
- import { PicaScoreTools } from "./pica-score.js";
9
8
  import { AudioFilesTools } from "./audio-files.js";
10
9
  import { MultimediaTools } from "./multimedia.js";
11
10
  import { AgreementsTools } from "./agreements.js";
12
11
  import { MemoryTools } from "./memory.js";
13
12
  import { EnrichmentTools } from "./enrichment.js";
14
- import { RegistrationTools } from "./registration.js";
15
- import { HealthTools } from "./health.js";
16
13
  import { BulkTools } from "./bulk.js";
17
14
  import { ExportTools } from "./exports.js";
18
15
  import { DuplicatesTools } from "./duplicates.js";
@@ -43,239 +40,281 @@ import { ShareLinksTools } from "./share-links.js";
43
40
  import { DisputesTools } from "./disputes.js";
44
41
  import { PurchasesTools } from "./purchases.js";
45
42
  import { TelegramTools } from "./telegram.js";
43
+ import { PublishersTools } from "./publishers.js";
44
+ import { AuthTools } from "./auth.js";
45
+ import { AppTools } from "./app-tools.js";
46
+ import { readCredentials } from "../utils/credentials.js";
46
47
  import { formatError, ToolExecutionError } from "../utils/errors.js";
48
+ import { guardResponseSize } from "../utils/formatting.js";
49
+ import { getToolMetadata } from "./metadata.js";
50
+ import { getRecoveryHint } from "./recovery-hints.js";
51
+ import { generateConfirmationToken, validateAndConsumeToken, } from "../utils/confirmation.js";
52
+ /**
53
+ * Build a tool description with metadata injected.
54
+ * Format: "[category] original description"
55
+ */
56
+ export function injectMetadataIntoDescription(description, metadata) {
57
+ // Category tag helps the agent understand tool grouping.
58
+ // Risk prefixes removed — annotations (readOnlyHint, destructiveHint) handle this structurally.
59
+ // Behavioral guidance (tell user what you're doing) is in server instructions (one copy).
60
+ return `[${metadata.category}] ${description}`;
61
+ }
62
+ export const CATEGORY_DISPLAY = {
63
+ catalog: "manage your catalog",
64
+ enrichment: "enrich your metadata",
65
+ business: "handle business and agreements",
66
+ discovery: "search and explore",
67
+ media: "manage photos, audio, and video",
68
+ comms: "send and share",
69
+ settings: "manage your account and team",
70
+ };
47
71
  export class ToolRegistry {
48
72
  tools;
49
73
  pica;
50
- constructor(pica) {
74
+ config;
75
+ reinitializeCallback;
76
+ auditLogger;
77
+ constructor(pica, config, reinitializeCallback) {
51
78
  this.pica = pica;
79
+ this.config = config;
80
+ this.reinitializeCallback = reinitializeCallback;
52
81
  this.tools = new Map();
53
82
  this.registerAllTools();
54
83
  }
84
+ setAuditLogger(logger) {
85
+ this.auditLogger = logger;
86
+ }
55
87
  /**
56
88
  * Register all available tools
57
89
  */
58
90
  registerAllTools() {
91
+ // Auth tools — always registered (lobby + authenticated mode)
92
+ if (this.config) {
93
+ const authTools = new AuthTools(this.config, this.reinitializeCallback || (() => { }));
94
+ authTools.getTools().forEach((tool) => {
95
+ this.tools.set(tool.definition.name, tool);
96
+ });
97
+ }
98
+ // In lobby mode, only auth tools are available
99
+ if (this.config?.lobbyMode || !this.pica) {
100
+ return;
101
+ }
102
+ const pica = this.pica;
59
103
  // Works tools
60
- const worksTools = new WorksTools(this.pica);
104
+ const worksTools = new WorksTools(pica);
61
105
  worksTools.getTools().forEach((tool) => {
62
106
  this.tools.set(tool.definition.name, tool);
63
107
  });
64
108
  // People tools
65
- const peopleTools = new PeopleTools(this.pica);
109
+ const peopleTools = new PeopleTools(pica);
66
110
  peopleTools.getTools().forEach((tool) => {
67
111
  this.tools.set(tool.definition.name, tool);
68
112
  });
69
113
  // Recordings tools
70
- const recordingsTools = new RecordingsTools(this.pica);
114
+ const recordingsTools = new RecordingsTools(pica);
71
115
  recordingsTools.getTools().forEach((tool) => {
72
116
  this.tools.set(tool.definition.name, tool);
73
117
  });
74
118
  // Search tools
75
- const searchTools = new SearchTools(this.pica);
119
+ const searchTools = new SearchTools(pica);
76
120
  searchTools.getTools().forEach((tool) => {
77
121
  this.tools.set(tool.definition.name, tool);
78
122
  });
79
123
  // Licensing tools
80
- const licensingTools = new LicensingTools(this.pica);
124
+ const licensingTools = new LicensingTools(pica);
81
125
  licensingTools.getTools().forEach((tool) => {
82
126
  this.tools.set(tool.definition.name, tool);
83
127
  });
84
128
  // Credits tools
85
- const creditsTools = new CreditsTools(this.pica);
129
+ const creditsTools = new CreditsTools(pica);
86
130
  creditsTools.getTools().forEach((tool) => {
87
131
  this.tools.set(tool.definition.name, tool);
88
132
  });
89
- // PICA Score tools
90
- const picaScoreTools = new PicaScoreTools(this.pica);
91
- picaScoreTools.getTools().forEach((tool) => {
92
- this.tools.set(tool.definition.name, tool);
93
- });
94
133
  // Audio Files tools
95
- const audioFilesTools = new AudioFilesTools(this.pica);
134
+ const audioFilesTools = new AudioFilesTools(pica);
96
135
  audioFilesTools.getTools().forEach((tool) => {
97
136
  this.tools.set(tool.definition.name, tool);
98
137
  });
99
138
  // Multimedia tools
100
- const multimediaTools = new MultimediaTools(this.pica);
139
+ const multimediaTools = new MultimediaTools(pica);
101
140
  multimediaTools.getTools().forEach((tool) => {
102
141
  this.tools.set(tool.definition.name, tool);
103
142
  });
104
143
  // Agreements tools
105
- const agreementsTools = new AgreementsTools(this.pica);
144
+ const agreementsTools = new AgreementsTools(pica);
106
145
  agreementsTools.getTools().forEach((tool) => {
107
146
  this.tools.set(tool.definition.name, tool);
108
147
  });
109
148
  // Memory tools
110
- const memoryTools = new MemoryTools(this.pica);
149
+ const memoryTools = new MemoryTools(pica);
111
150
  memoryTools.getTools().forEach((tool) => {
112
151
  this.tools.set(tool.definition.name, tool);
113
152
  });
114
153
  // Enrichment tools
115
- const enrichmentTools = new EnrichmentTools(this.pica);
154
+ const enrichmentTools = new EnrichmentTools(pica);
116
155
  enrichmentTools.getTools().forEach((tool) => {
117
156
  this.tools.set(tool.definition.name, tool);
118
157
  });
119
- // Registration tools
120
- const registrationTools = new RegistrationTools(this.pica);
121
- registrationTools.getTools().forEach((tool) => {
122
- this.tools.set(tool.definition.name, tool);
123
- });
124
- // Health tools
125
- const healthTools = new HealthTools(this.pica);
126
- healthTools.getTools().forEach((tool) => {
127
- this.tools.set(tool.definition.name, tool);
128
- });
129
158
  // Bulk operations tools
130
- const bulkTools = new BulkTools(this.pica);
159
+ const bulkTools = new BulkTools(pica);
131
160
  bulkTools.getTools().forEach((tool) => {
132
161
  this.tools.set(tool.definition.name, tool);
133
162
  });
134
163
  // Export tools
135
- const exportTools = new ExportTools(this.pica);
164
+ const exportTools = new ExportTools(pica);
136
165
  exportTools.getTools().forEach((tool) => {
137
166
  this.tools.set(tool.definition.name, tool);
138
167
  });
139
168
  // Duplicates tools
140
- const duplicatesTools = new DuplicatesTools(this.pica);
169
+ const duplicatesTools = new DuplicatesTools(pica);
141
170
  duplicatesTools.getTools().forEach((tool) => {
142
171
  this.tools.set(tool.definition.name, tool);
143
172
  });
144
173
  // Directory tools
145
- const directoryTools = new DirectoryTools(this.pica);
174
+ const directoryTools = new DirectoryTools(pica);
146
175
  directoryTools.getTools().forEach((tool) => {
147
176
  this.tools.set(tool.definition.name, tool);
148
177
  });
149
178
  // Collaborator tools
150
- const collaboratorsTools = new CollaboratorsTools(this.pica);
179
+ const collaboratorsTools = new CollaboratorsTools(pica);
151
180
  collaboratorsTools.getTools().forEach((tool) => {
152
181
  this.tools.set(tool.definition.name, tool);
153
182
  });
154
183
  // Upload tools (generic file upload — images, videos, documents)
155
- const uploadTools = new UploadTools(this.pica);
184
+ const uploadTools = new UploadTools(pica);
156
185
  uploadTools.getTools().forEach((tool) => {
157
186
  this.tools.set(tool.definition.name, tool);
158
187
  });
159
188
  // Document tools (AI analysis)
160
- const documentTools = new DocumentTools(this.pica);
189
+ const documentTools = new DocumentTools(pica);
161
190
  documentTools.getTools().forEach((tool) => {
162
191
  this.tools.set(tool.definition.name, tool);
163
192
  });
164
193
  // Bulk import tools (CSV import pipeline)
165
- const importTools = new ImportTools(this.pica);
194
+ const importTools = new ImportTools(pica);
166
195
  importTools.getTools().forEach((tool) => {
167
196
  this.tools.set(tool.definition.name, tool);
168
197
  });
169
198
  // Comparison tools (ADR-116 Phase 2: enrichment + registration comparison)
170
- const comparisonTools = new ComparisonTools(this.pica);
199
+ const comparisonTools = new ComparisonTools(pica);
171
200
  comparisonTools.getTools().forEach((tool) => {
172
201
  this.tools.set(tool.definition.name, tool);
173
202
  });
174
203
  // Send Hub tools (messages, invites, file requests, audio shares)
175
- const sendTools = new SendTools(this.pica);
204
+ const sendTools = new SendTools(pica);
176
205
  sendTools.getTools().forEach((tool) => {
177
206
  this.tools.set(tool.definition.name, tool);
178
207
  });
179
208
  // Import document tools
180
- const importDocumentTools = new ImportDocumentTools(this.pica);
209
+ const importDocumentTools = new ImportDocumentTools(pica);
181
210
  importDocumentTools.getTools().forEach((tool) => {
182
211
  this.tools.set(tool.definition.name, tool);
183
212
  });
184
213
  // Physical assets tools (equipment, instruments, studio gear)
185
- const assetsTools = new AssetsTools(this.pica);
214
+ const assetsTools = new AssetsTools(pica);
186
215
  assetsTools.getTools().forEach((tool) => {
187
216
  this.tools.set(tool.definition.name, tool);
188
217
  });
189
218
  // Work sessions tools (studio session tracking)
190
- const sessionsTools = new SessionsTools(this.pica);
219
+ const sessionsTools = new SessionsTools(pica);
191
220
  sessionsTools.getTools().forEach((tool) => {
192
221
  this.tools.set(tool.definition.name, tool);
193
222
  });
194
223
  // Analytics tools (carbon, provenance, diligence)
195
- const analyticsTools = new AnalyticsTools(this.pica);
224
+ const analyticsTools = new AnalyticsTools(pica);
196
225
  analyticsTools.getTools().forEach((tool) => {
197
226
  this.tools.set(tool.definition.name, tool);
198
227
  });
199
228
  // Notifications tools
200
- const notificationsTools = new NotificationsTools(this.pica);
229
+ const notificationsTools = new NotificationsTools(pica);
201
230
  notificationsTools.getTools().forEach((tool) => {
202
231
  this.tools.set(tool.definition.name, tool);
203
232
  });
204
233
  // Notes tools (catalog notes on works, people, general observations)
205
- const notesTools = new NotesTools(this.pica);
234
+ const notesTools = new NotesTools(pica);
206
235
  notesTools.getTools().forEach((tool) => {
207
236
  this.tools.set(tool.definition.name, tool);
208
237
  });
209
238
  // Team management tools (invite, list, update, remove members)
210
- const teamTools = new TeamTools(this.pica);
239
+ const teamTools = new TeamTools(pica);
211
240
  teamTools.getTools().forEach((tool) => {
212
241
  this.tools.set(tool.definition.name, tool);
213
242
  });
214
243
  // Projects tools (albums, EPs, compilations — grouping works)
215
- const projectsTools = new ProjectsTools(this.pica);
244
+ const projectsTools = new ProjectsTools(pica);
216
245
  projectsTools.getTools().forEach((tool) => {
217
246
  this.tools.set(tool.definition.name, tool);
218
247
  });
219
248
  // Releases tools (albums, EPs, singles with dates, labels, UPCs)
220
- const releasesTools = new ReleasesTools(this.pica);
249
+ const releasesTools = new ReleasesTools(pica);
221
250
  releasesTools.getTools().forEach((tool) => {
222
251
  this.tools.set(tool.definition.name, tool);
223
252
  });
224
253
  // Calendar tools
225
- const calendarTools = new CalendarTools(this.pica);
254
+ const calendarTools = new CalendarTools(pica);
226
255
  calendarTools.getTools().forEach((tool) => {
227
256
  this.tools.set(tool.definition.name, tool);
228
257
  });
229
258
  // Split sheets & recording splits tools
230
- const splitSheetsTools = new SplitSheetsTools(this.pica);
259
+ const splitSheetsTools = new SplitSheetsTools(pica);
231
260
  splitSheetsTools.getTools().forEach((tool) => {
232
261
  this.tools.set(tool.definition.name, tool);
233
262
  });
234
263
  // Agreement types tools (templates, producer agreements, work-for-hire)
235
- const agreementTypesTools = new AgreementTypesTools(this.pica);
264
+ const agreementTypesTools = new AgreementTypesTools(pica);
236
265
  agreementTypesTools.getTools().forEach((tool) => {
237
266
  this.tools.set(tool.definition.name, tool);
238
267
  });
239
268
  // Dashboard & discovery tools
240
- const dashboardTools = new DashboardTools(this.pica);
269
+ const dashboardTools = new DashboardTools(pica);
241
270
  dashboardTools.getTools().forEach((tool) => {
242
271
  this.tools.set(tool.definition.name, tool);
243
272
  });
244
273
  // Integrations tools (connection status)
245
- const integrationsTools = new IntegrationsTools(this.pica);
274
+ const integrationsTools = new IntegrationsTools(pica);
246
275
  integrationsTools.getTools().forEach((tool) => {
247
276
  this.tools.set(tool.definition.name, tool);
248
277
  });
249
278
  // Settings tools (credits history, storage, org profile)
250
- const settingsTools = new SettingsTools(this.pica);
279
+ const settingsTools = new SettingsTools(pica);
251
280
  settingsTools.getTools().forEach((tool) => {
252
281
  this.tools.set(tool.definition.name, tool);
253
282
  });
254
283
  // Royalties & statements tools (ADR-139 Step 2)
255
- const royaltiesTools = new RoyaltiesTools(this.pica);
284
+ const royaltiesTools = new RoyaltiesTools(pica);
256
285
  royaltiesTools.getTools().forEach((tool) => {
257
286
  this.tools.set(tool.definition.name, tool);
258
287
  });
259
288
  // Share links tools (ADR-139 Step 3)
260
- const shareLinksTools = new ShareLinksTools(this.pica);
289
+ const shareLinksTools = new ShareLinksTools(pica);
261
290
  shareLinksTools.getTools().forEach((tool) => {
262
291
  this.tools.set(tool.definition.name, tool);
263
292
  });
264
293
  // Disputes tools (ADR-139 Step 3)
265
- const disputesTools = new DisputesTools(this.pica);
294
+ const disputesTools = new DisputesTools(pica);
266
295
  disputesTools.getTools().forEach((tool) => {
267
296
  this.tools.set(tool.definition.name, tool);
268
297
  });
269
298
  // Purchases tools (credit checkout)
270
- const purchasesTools = new PurchasesTools(this.pica);
299
+ const purchasesTools = new PurchasesTools(pica);
271
300
  purchasesTools.getTools().forEach((tool) => {
272
301
  this.tools.set(tool.definition.name, tool);
273
302
  });
274
303
  // Telegram tools (user notification channel)
275
- const telegramTools = new TelegramTools(this.pica);
304
+ const telegramTools = new TelegramTools(pica);
276
305
  telegramTools.getTools().forEach((tool) => {
277
306
  this.tools.set(tool.definition.name, tool);
278
307
  });
308
+ // Publishers tools (lookup + create)
309
+ const publishersTools = new PublishersTools(pica);
310
+ publishersTools.getTools().forEach((tool) => {
311
+ this.tools.set(tool.definition.name, tool);
312
+ });
313
+ // App tools (MCP Apps — interactive UI cards)
314
+ const appTools = new AppTools(pica);
315
+ appTools.getTools().forEach((tool) => {
316
+ this.tools.set(tool.definition.name, tool);
317
+ });
279
318
  }
280
319
  // ── Write-safety classification ──
281
320
  // Destructive tools require confirmation before AND summary after.
@@ -320,25 +359,18 @@ export class ToolRegistry {
320
359
  // Read-only tools that match mutating patterns by name but are actually safe
321
360
  static SAFE_OVERRIDES = new Set([
322
361
  "pica_collaborators_invites_list",
323
- "pica_completeness_low",
324
362
  "pica_enrichment_candidates",
325
363
  "pica_enrichment_compare",
326
- "pica_enrichment_status",
327
364
  "pica_find_duplicates",
328
365
  "pica_import_analyze",
329
366
  "pica_import_validate",
330
367
  "pica_import_fields",
331
368
  "pica_import_template",
332
- "pica_import_documents_list",
333
- "pica_import_documents_get",
334
- "pica_import_documents_diff",
335
- "pica_send_list",
336
- "pica_send_pending",
369
+ "pica_import_documents_query",
370
+ "pica_import_documents_inspect",
371
+ "pica_send_query",
337
372
  "pica_share_links_list",
338
- "pica_work_completeness",
339
373
  ]);
340
- static DESTRUCTIVE_PREFIX = "⚠️ DESTRUCTIVE: Before calling this tool, you MUST describe exactly what will be deleted/merged to the user and get explicit confirmation. After execution, confirm what was done and what was affected. ";
341
- static MUTATING_PREFIX = "✏️ WRITE OPERATION: Before calling this tool, briefly tell the user what you're about to do. After execution, confirm what was created/changed. ";
342
374
  classifyTool(name) {
343
375
  if (ToolRegistry.SAFE_OVERRIDES.has(name)) {
344
376
  return "safe";
@@ -356,33 +388,330 @@ export class ToolRegistry {
356
388
  */
357
389
  listTools() {
358
390
  return Array.from(this.tools.values()).map((tool) => {
359
- const classification = this.classifyTool(tool.definition.name);
360
- if (classification === "safe") {
361
- return tool.definition;
391
+ let definition = tool.definition;
392
+ const metadata = getToolMetadata(definition.name);
393
+ // Inject confirmation_token into destructive tool schemas
394
+ const isDestructive = metadata?.risk === "destructive" ||
395
+ (!metadata && this.classifyTool(definition.name) === "destructive");
396
+ if (isDestructive) {
397
+ definition = {
398
+ ...definition,
399
+ inputSchema: {
400
+ ...definition.inputSchema,
401
+ properties: {
402
+ ...definition.inputSchema.properties,
403
+ confirmation_token: {
404
+ type: "string",
405
+ description: "Confirmation token from a previous call. Required to execute destructive operations. " +
406
+ "Call this tool once without a token to get a preview and token, then call again with the token to confirm.",
407
+ },
408
+ },
409
+ },
410
+ };
362
411
  }
363
- const prefix = classification === "destructive"
364
- ? ToolRegistry.DESTRUCTIVE_PREFIX
365
- : ToolRegistry.MUTATING_PREFIX;
366
- return {
367
- ...tool.definition,
368
- description: prefix + tool.definition.description,
412
+ // Normalize _meta.ui.resourceUri legacy "ui/resourceUri" key for older MCP Apps clients
413
+ if (definition._meta?.ui?.resourceUri &&
414
+ !definition._meta["ui/resourceUri"]) {
415
+ definition = {
416
+ ...definition,
417
+ _meta: {
418
+ ...definition._meta,
419
+ "ui/resourceUri": definition._meta.ui.resourceUri,
420
+ },
421
+ };
422
+ }
423
+ if (metadata) {
424
+ return {
425
+ ...definition,
426
+ description: injectMetadataIntoDescription(tool.definition.description, metadata),
427
+ annotations: {
428
+ title: metadata.display_name,
429
+ readOnlyHint: metadata.risk === "safe",
430
+ destructiveHint: metadata.risk === "destructive",
431
+ idempotentHint: metadata.risk === "safe" && metadata.retry_safe,
432
+ openWorldHint: false,
433
+ },
434
+ };
435
+ }
436
+ // Fallback to old pattern-matching for any tool without explicit metadata
437
+ const classification = this.classifyTool(definition.name);
438
+ const annotations = {
439
+ title: definition.name,
440
+ readOnlyHint: classification === "safe",
441
+ destructiveHint: classification === "destructive",
442
+ idempotentHint: false,
443
+ openWorldHint: false,
369
444
  };
445
+ // No risk prefix needed — annotations handle this structurally
446
+ return { ...definition, annotations };
370
447
  });
371
448
  }
449
+ /**
450
+ * Build a human-readable preview of what a destructive operation will affect.
451
+ */
452
+ async buildDestructivePreview(name, args) {
453
+ const metadata = getToolMetadata(name);
454
+ const displayName = metadata?.display_name || name;
455
+ // buildDestructivePreview is only called during tool execution,
456
+ // which is gated behind lobby mode check — pica is always available here.
457
+ const pica = this.pica;
458
+ try {
459
+ switch (name) {
460
+ case "pica_works_delete": {
461
+ const work = await pica.works.get(args.id).catch(() => null);
462
+ const title = work?.title || args.id;
463
+ return {
464
+ action: "delete work",
465
+ target: title,
466
+ warning: "this will remove linked credits, agreement links, and audio files. recordings will be unlinked. this cannot be undone for works without verified identifiers.",
467
+ display_name: displayName,
468
+ };
469
+ }
470
+ case "pica_works_bulk_delete": {
471
+ const count = args.ids?.length || 0;
472
+ return {
473
+ action: "bulk delete works",
474
+ target: `${count} works`,
475
+ warning: `this will delete ${count} work(s) and cascade to their credits and agreement links. this cannot be undone for works without verified identifiers.`,
476
+ display_name: displayName,
477
+ };
478
+ }
479
+ case "pica_people_delete": {
480
+ const person = await pica.people.get(args.id).catch(() => null);
481
+ const personName = person?.first_name
482
+ ? `${person.first_name} ${person.last_name || ""}`.trim()
483
+ : args.id;
484
+ return {
485
+ action: "remove person",
486
+ target: personName,
487
+ warning: "the person record will be soft-deleted. credits and agreements will remain linked.",
488
+ display_name: displayName,
489
+ };
490
+ }
491
+ case "pica_agreements_delete": {
492
+ const agreementResult = await pica.agreements
493
+ .get(args.id)
494
+ .catch(() => null);
495
+ const title = agreementResult?.agreement?.title || args.id;
496
+ return {
497
+ action: "delete agreement",
498
+ target: title,
499
+ warning: "this will remove all linked work and party records. this cannot be undone.",
500
+ display_name: displayName,
501
+ };
502
+ }
503
+ case "pica_merge_duplicates": {
504
+ const loserCount = args.loser_ids?.length || 0;
505
+ const entityType = args.entity_type || "entities";
506
+ return {
507
+ action: `merge ${entityType}`,
508
+ target: `${loserCount} ${entityType} into winner`,
509
+ warning: `all credits, recordings, and agreements from ${loserCount} loser(s) will be moved to the winner. loser records will be permanently deleted. this cannot be undone.`,
510
+ display_name: displayName,
511
+ };
512
+ }
513
+ case "pica_projects_delete": {
514
+ const project = await pica.projects?.get(args.id).catch(() => null);
515
+ const projectName = project?.name || args.id;
516
+ return {
517
+ action: "delete project",
518
+ target: projectName,
519
+ warning: "linked works will be unlinked but not deleted. this cannot be undone.",
520
+ display_name: displayName,
521
+ };
522
+ }
523
+ case "pica_team_remove": {
524
+ return {
525
+ action: "remove team member",
526
+ target: args.id,
527
+ warning: "their user account will be permanently deleted. works and agreements they contributed will remain. this cannot be undone.",
528
+ display_name: displayName,
529
+ };
530
+ }
531
+ default: {
532
+ return {
533
+ action: displayName,
534
+ target: args.id || "unknown",
535
+ warning: "this cannot be undone.",
536
+ display_name: displayName,
537
+ };
538
+ }
539
+ }
540
+ }
541
+ catch {
542
+ return {
543
+ action: displayName,
544
+ target: args.id || "unknown",
545
+ warning: "this cannot be undone.",
546
+ display_name: displayName,
547
+ };
548
+ }
549
+ }
550
+ /**
551
+ * Sanitize tool parameters for audit logging.
552
+ * Strips confirmation tokens and truncates large string values.
553
+ */
554
+ sanitizeParams(args) {
555
+ const sanitized = { ...args };
556
+ delete sanitized.confirmation_token;
557
+ for (const [key, value] of Object.entries(sanitized)) {
558
+ if (typeof value === "string" && value.length > 500) {
559
+ sanitized[key] = value.substring(0, 500) + "...[truncated]";
560
+ }
561
+ }
562
+ return sanitized;
563
+ }
372
564
  /**
373
565
  * Execute a tool by name
374
566
  */
375
567
  async executeTool(name, args) {
568
+ const startTime = Date.now();
376
569
  const tool = this.tools.get(name);
377
570
  if (!tool) {
378
571
  throw new ToolExecutionError(`Tool not found: ${name}`);
379
572
  }
573
+ const metadata = getToolMetadata(name);
574
+ // ── Session freshness gate (ADR-151) ──
575
+ if (metadata?.risk === "destructive" &&
576
+ this.config &&
577
+ !this.config.lobbyMode) {
578
+ const creds = readCredentials(this.config.credentialsPath);
579
+ if (creds?.authenticated_at) {
580
+ const authAge = Date.now() - new Date(creds.authenticated_at).getTime();
581
+ const sevenDays = 7 * 24 * 60 * 60 * 1000;
582
+ if (authAge > sevenDays) {
583
+ return {
584
+ content: [
585
+ {
586
+ type: "text",
587
+ text: 'this action requires re-verification — say "sign me in" to confirm your identity',
588
+ },
589
+ ],
590
+ isError: true,
591
+ };
592
+ }
593
+ }
594
+ }
595
+ // ── Destructive confirmation gate ──
596
+ if (metadata?.risk === "destructive") {
597
+ const confirmationToken = args.confirmation_token;
598
+ if (!confirmationToken) {
599
+ // First call — generate preview and return confirmation challenge
600
+ const token = generateConfirmationToken(name, args);
601
+ const preview = await this.buildDestructivePreview(name, args);
602
+ const response = {
603
+ content: [
604
+ {
605
+ type: "text",
606
+ text: JSON.stringify({
607
+ status: "confirmation_required",
608
+ confirmation_token: token,
609
+ expires_in: 120,
610
+ preview,
611
+ }, null, 2),
612
+ },
613
+ ],
614
+ structuredContent: {
615
+ status: "confirmation_required",
616
+ confirmation_token: token,
617
+ expires_in: 120,
618
+ preview,
619
+ },
620
+ };
621
+ // Fire-and-forget audit for confirmation challenge
622
+ this.auditLogger
623
+ ?.logToolExecution({
624
+ tool_name: name,
625
+ tool_category: metadata?.category || "unknown",
626
+ risk_level: "destructive",
627
+ parameters: this.sanitizeParams(args),
628
+ result_status: "confirmation_required",
629
+ confirmation_token: token,
630
+ execution_time_ms: Date.now() - startTime,
631
+ // TODO(ADR-149): Wire caller_identity from API key hash and transport from server context
632
+ caller_identity: "unknown",
633
+ transport: "stdio",
634
+ })
635
+ .catch(() => { }); // Never block on audit
636
+ return response;
637
+ }
638
+ // Second call — validate token
639
+ const validation = validateAndConsumeToken(confirmationToken, name, args);
640
+ if (!validation.valid) {
641
+ return {
642
+ content: [
643
+ {
644
+ type: "text",
645
+ text: JSON.stringify({
646
+ error: validation.reason?.includes("expired")
647
+ ? "CONFIRMATION_EXPIRED"
648
+ : "CONFIRMATION_INVALID",
649
+ message: validation.reason,
650
+ retry_safe: false,
651
+ suggestion: "start the operation again without the confirmation token",
652
+ }, null, 2),
653
+ },
654
+ ],
655
+ isError: true,
656
+ };
657
+ }
658
+ // Token valid — fall through to execute
659
+ }
660
+ // ── Normal execution ──
380
661
  try {
381
- return await tool.executor(args);
662
+ const result = await tool.executor(args);
663
+ // Guard response size — truncate oversized payloads with recovery hint
664
+ if (result?.content) {
665
+ for (const block of result.content) {
666
+ if (block.type === "text" && typeof block.text === "string") {
667
+ block.text = guardResponseSize(block.text, name);
668
+ }
669
+ }
670
+ }
671
+ // Fire-and-forget audit
672
+ this.auditLogger
673
+ ?.logToolExecution({
674
+ tool_name: name,
675
+ tool_category: metadata?.category || "unknown",
676
+ risk_level: metadata?.risk || this.classifyTool(name),
677
+ parameters: this.sanitizeParams(args),
678
+ result_status: "success",
679
+ execution_time_ms: Date.now() - startTime,
680
+ // TODO(ADR-149): Wire caller_identity from API key hash and transport from server context
681
+ caller_identity: "unknown",
682
+ transport: "stdio",
683
+ })
684
+ .catch(() => { }); // Never block on audit
685
+ return result;
382
686
  }
383
687
  catch (error) {
688
+ const formatted = formatError(error);
689
+ const parsed = JSON.parse(formatted.text);
690
+ // Attach recovery hint if available for this tool + error code
691
+ const hint = getRecoveryHint(name, parsed.error);
692
+ if (hint) {
693
+ parsed.suggestion = hint.suggestion;
694
+ if (hint.next_tool) {
695
+ parsed.next_tool = hint.next_tool;
696
+ }
697
+ }
698
+ // Fire-and-forget audit for errors
699
+ this.auditLogger
700
+ ?.logToolExecution({
701
+ tool_name: name,
702
+ tool_category: metadata?.category || "unknown",
703
+ risk_level: metadata?.risk || this.classifyTool(name),
704
+ parameters: this.sanitizeParams(args),
705
+ result_status: "error",
706
+ error_code: parsed.error,
707
+ execution_time_ms: Date.now() - startTime,
708
+ // TODO(ADR-149): Wire caller_identity from API key hash and transport from server context
709
+ caller_identity: "unknown",
710
+ transport: "stdio",
711
+ })
712
+ .catch(() => { }); // Never block on audit
384
713
  return {
385
- content: [formatError(error)],
714
+ content: [{ type: "text", text: JSON.stringify(parsed, null, 2) }],
386
715
  isError: true,
387
716
  };
388
717
  }