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