@commandable/mcp-core 0.2.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 (157) hide show
  1. package/LICENSE +10 -0
  2. package/README.md +8 -0
  3. package/dist/config/configApply.d.ts +16 -0
  4. package/dist/config/configApply.d.ts.map +1 -0
  5. package/dist/config/configApply.js +77 -0
  6. package/dist/config/configApply.js.map +1 -0
  7. package/dist/config/configLoader.d.ts +9 -0
  8. package/dist/config/configLoader.d.ts.map +1 -0
  9. package/dist/config/configLoader.js +75 -0
  10. package/dist/config/configLoader.js.map +1 -0
  11. package/dist/config/configSchema.d.ts +45 -0
  12. package/dist/config/configSchema.d.ts.map +1 -0
  13. package/dist/config/configSchema.js +23 -0
  14. package/dist/config/configSchema.js.map +1 -0
  15. package/dist/crypto/encryption.d.ts +3 -0
  16. package/dist/crypto/encryption.d.ts.map +1 -0
  17. package/dist/crypto/encryption.js +29 -0
  18. package/dist/crypto/encryption.js.map +1 -0
  19. package/dist/db/client.d.ts +24 -0
  20. package/dist/db/client.d.ts.map +1 -0
  21. package/dist/db/client.js +50 -0
  22. package/dist/db/client.js.map +1 -0
  23. package/dist/db/credentialStore.d.ts +15 -0
  24. package/dist/db/credentialStore.d.ts.map +1 -0
  25. package/dist/db/credentialStore.js +56 -0
  26. package/dist/db/credentialStore.js.map +1 -0
  27. package/dist/db/integrationStore.d.ts +14 -0
  28. package/dist/db/integrationStore.d.ts.map +1 -0
  29. package/dist/db/integrationStore.js +128 -0
  30. package/dist/db/integrationStore.js.map +1 -0
  31. package/dist/db/integrationTypeConfigStore.d.ts +7 -0
  32. package/dist/db/integrationTypeConfigStore.d.ts.map +1 -0
  33. package/dist/db/integrationTypeConfigStore.js +101 -0
  34. package/dist/db/integrationTypeConfigStore.js.map +1 -0
  35. package/dist/db/migrate.d.ts +3 -0
  36. package/dist/db/migrate.d.ts.map +1 -0
  37. package/dist/db/migrate.js +11 -0
  38. package/dist/db/migrate.js.map +1 -0
  39. package/dist/db/migrations/pg/0000_initial.sql +74 -0
  40. package/dist/db/migrations/pg/meta/_journal.json +13 -0
  41. package/dist/db/migrations/sqlite/0000_initial.sql +74 -0
  42. package/dist/db/migrations/sqlite/meta/_journal.json +13 -0
  43. package/dist/db/schema.d.ts +1863 -0
  44. package/dist/db/schema.d.ts.map +1 -0
  45. package/dist/db/schema.js +133 -0
  46. package/dist/db/schema.js.map +1 -0
  47. package/dist/db/toolDefinitionStore.d.ts +9 -0
  48. package/dist/db/toolDefinitionStore.d.ts.map +1 -0
  49. package/dist/db/toolDefinitionStore.js +117 -0
  50. package/dist/db/toolDefinitionStore.js.map +1 -0
  51. package/dist/errors/httpError.d.ts +6 -0
  52. package/dist/errors/httpError.d.ts.map +1 -0
  53. package/dist/errors/httpError.js +11 -0
  54. package/dist/errors/httpError.js.map +1 -0
  55. package/dist/index.d.ts +34 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +34 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/integrations/actionsFactory.d.ts +16 -0
  60. package/dist/integrations/actionsFactory.d.ts.map +1 -0
  61. package/dist/integrations/actionsFactory.js +98 -0
  62. package/dist/integrations/actionsFactory.js.map +1 -0
  63. package/dist/integrations/catalog.d.ts +8 -0
  64. package/dist/integrations/catalog.d.ts.map +1 -0
  65. package/dist/integrations/catalog.js +45 -0
  66. package/dist/integrations/catalog.js.map +1 -0
  67. package/dist/integrations/customToolFactory.d.ts +13 -0
  68. package/dist/integrations/customToolFactory.d.ts.map +1 -0
  69. package/dist/integrations/customToolFactory.js +31 -0
  70. package/dist/integrations/customToolFactory.js.map +1 -0
  71. package/dist/integrations/dataLoader.d.ts +3 -0
  72. package/dist/integrations/dataLoader.d.ts.map +1 -0
  73. package/dist/integrations/dataLoader.js +2 -0
  74. package/dist/integrations/dataLoader.js.map +1 -0
  75. package/dist/integrations/fileIntegrationTypeConfigStore.d.ts +7 -0
  76. package/dist/integrations/fileIntegrationTypeConfigStore.d.ts.map +1 -0
  77. package/dist/integrations/fileIntegrationTypeConfigStore.js +34 -0
  78. package/dist/integrations/fileIntegrationTypeConfigStore.js.map +1 -0
  79. package/dist/integrations/getIntegration.d.ts +14 -0
  80. package/dist/integrations/getIntegration.d.ts.map +1 -0
  81. package/dist/integrations/getIntegration.js +30 -0
  82. package/dist/integrations/getIntegration.js.map +1 -0
  83. package/dist/integrations/googleServiceAccount.d.ts +6 -0
  84. package/dist/integrations/googleServiceAccount.d.ts.map +1 -0
  85. package/dist/integrations/googleServiceAccount.js +54 -0
  86. package/dist/integrations/googleServiceAccount.js.map +1 -0
  87. package/dist/integrations/health.d.ts +20 -0
  88. package/dist/integrations/health.d.ts.map +1 -0
  89. package/dist/integrations/health.js +43 -0
  90. package/dist/integrations/health.js.map +1 -0
  91. package/dist/integrations/integrationTypeConfigLookup.d.ts +12 -0
  92. package/dist/integrations/integrationTypeConfigLookup.d.ts.map +1 -0
  93. package/dist/integrations/integrationTypeConfigLookup.js +11 -0
  94. package/dist/integrations/integrationTypeConfigLookup.js.map +1 -0
  95. package/dist/integrations/providerRegistry.d.ts +2 -0
  96. package/dist/integrations/providerRegistry.d.ts.map +1 -0
  97. package/dist/integrations/providerRegistry.js +72 -0
  98. package/dist/integrations/providerRegistry.js.map +1 -0
  99. package/dist/integrations/proxy.d.ts +19 -0
  100. package/dist/integrations/proxy.d.ts.map +1 -0
  101. package/dist/integrations/proxy.js +377 -0
  102. package/dist/integrations/proxy.js.map +1 -0
  103. package/dist/integrations/sandbox.d.ts +8 -0
  104. package/dist/integrations/sandbox.d.ts.map +1 -0
  105. package/dist/integrations/sandbox.js +221 -0
  106. package/dist/integrations/sandbox.js.map +1 -0
  107. package/dist/integrations/sandboxUtils.d.ts +15 -0
  108. package/dist/integrations/sandboxUtils.d.ts.map +1 -0
  109. package/dist/integrations/sandboxUtils.js +489 -0
  110. package/dist/integrations/sandboxUtils.js.map +1 -0
  111. package/dist/integrations/tools.d.ts +3 -0
  112. package/dist/integrations/tools.d.ts.map +1 -0
  113. package/dist/integrations/tools.js +70 -0
  114. package/dist/integrations/tools.js.map +1 -0
  115. package/dist/mcp/abilityCatalog.d.ts +51 -0
  116. package/dist/mcp/abilityCatalog.d.ts.map +1 -0
  117. package/dist/mcp/abilityCatalog.js +300 -0
  118. package/dist/mcp/abilityCatalog.js.map +1 -0
  119. package/dist/mcp/auth.d.ts +18 -0
  120. package/dist/mcp/auth.d.ts.map +1 -0
  121. package/dist/mcp/auth.js +45 -0
  122. package/dist/mcp/auth.js.map +1 -0
  123. package/dist/mcp/builder_guide.md +441 -0
  124. package/dist/mcp/commandable_readme.md +29 -0
  125. package/dist/mcp/handlers.d.ts +21 -0
  126. package/dist/mcp/handlers.d.ts.map +1 -0
  127. package/dist/mcp/handlers.js +86 -0
  128. package/dist/mcp/handlers.js.map +1 -0
  129. package/dist/mcp/metaTools.d.ts +79 -0
  130. package/dist/mcp/metaTools.d.ts.map +1 -0
  131. package/dist/mcp/metaTools.js +901 -0
  132. package/dist/mcp/metaTools.js.map +1 -0
  133. package/dist/mcp/server.d.ts +25 -0
  134. package/dist/mcp/server.d.ts.map +1 -0
  135. package/dist/mcp/server.js +14 -0
  136. package/dist/mcp/server.js.map +1 -0
  137. package/dist/mcp/sessionState.d.ts +19 -0
  138. package/dist/mcp/sessionState.d.ts.map +1 -0
  139. package/dist/mcp/sessionState.js +79 -0
  140. package/dist/mcp/sessionState.js.map +1 -0
  141. package/dist/mcp/toolAdapter.d.ts +34 -0
  142. package/dist/mcp/toolAdapter.d.ts.map +1 -0
  143. package/dist/mcp/toolAdapter.js +24 -0
  144. package/dist/mcp/toolAdapter.js.map +1 -0
  145. package/dist/runtime/credentialManager.d.ts +19 -0
  146. package/dist/runtime/credentialManager.d.ts.map +1 -0
  147. package/dist/runtime/credentialManager.js +82 -0
  148. package/dist/runtime/credentialManager.js.map +1 -0
  149. package/dist/runtime/stdioSession.d.ts +8 -0
  150. package/dist/runtime/stdioSession.d.ts.map +1 -0
  151. package/dist/runtime/stdioSession.js +79 -0
  152. package/dist/runtime/stdioSession.js.map +1 -0
  153. package/dist/types.d.ts +92 -0
  154. package/dist/types.d.ts.map +1 -0
  155. package/dist/types.js +2 -0
  156. package/dist/types.js.map +1 -0
  157. package/package.json +64 -0
@@ -0,0 +1,901 @@
1
+ import { BUILDER_ABILITY_ID } from './abilityCatalog.js';
2
+ import { buildMcpToolIndexForIntegrations } from './toolAdapter.js';
3
+ import { readFileSync } from 'node:fs';
4
+ import { fileURLToPath } from 'node:url';
5
+ import crypto from 'node:crypto';
6
+ import { listIntegrationCatalog } from '../integrations/catalog.js';
7
+ import { getBuiltInIntegrationTypeConfig } from '../integrations/fileIntegrationTypeConfigStore.js';
8
+ import { createGetIntegration } from '../integrations/getIntegration.js';
9
+ import { loadIntegrationManifest, loadIntegrationPrompt } from '../integrations/dataLoader.js';
10
+ import { deleteIntegrationById, listIntegrations, upsertIntegration } from '../db/integrationStore.js';
11
+ import { PROVIDERS } from '../integrations/providerRegistry.js';
12
+ import { createSafeHandlerFromString } from '../integrations/sandbox.js';
13
+ import { sanitizeJsonSchema } from '../integrations/tools.js';
14
+ import { deleteToolDefinitionByName, deleteToolDefinitionsForIntegration, getToolDefinitionByName, upsertToolDefinition, } from '../db/toolDefinitionStore.js';
15
+ import { buildExecutableToolFromDefinition } from '../integrations/customToolFactory.js';
16
+ import { deleteIntegrationTypeConfig, getIntegrationTypeConfig, upsertIntegrationTypeConfig } from '../db/integrationTypeConfigStore.js';
17
+ export const META_TOOL_NAMES = {
18
+ readme: 'commandable_readme',
19
+ searchTools: 'commandable_search_tools',
20
+ enableToolset: 'commandable_enable_toolset',
21
+ disableToolset: 'commandable_disable_toolset',
22
+ listPrebuiltIntegrations: 'commandable_list_prebuilt_integrations',
23
+ addPrebuiltIntegration: 'commandable_add_prebuilt_integration',
24
+ upsertCustomIntegration: 'commandable_upsert_custom_integration',
25
+ upsertCustomTool: 'commandable_upsert_custom_tool',
26
+ deleteCustomIntegration: 'commandable_delete_custom_integration',
27
+ deleteCustomTool: 'commandable_delete_custom_tool',
28
+ testCustomTool: 'commandable_test_custom_tool',
29
+ };
30
+ function normalizeHintMarkdown(value) {
31
+ return value
32
+ .replace(/\r\n/g, '\n')
33
+ .replace(/\\r\\n/g, '\n')
34
+ .replace(/\\n/g, '\n');
35
+ }
36
+ function buildCommandableReadme() {
37
+ const path = fileURLToPath(new URL('./commandable_readme.md', import.meta.url));
38
+ return readFileSync(path, 'utf8');
39
+ }
40
+ function buildBuilderGuide() {
41
+ const path = fileURLToPath(new URL('./builder_guide.md', import.meta.url));
42
+ return readFileSync(path, 'utf8');
43
+ }
44
+ function providerBaseUrl(integration) {
45
+ const provider = PROVIDERS[integration.type];
46
+ const base = provider?.baseUrl;
47
+ if (typeof base === 'function') {
48
+ try {
49
+ return String(base(integration, undefined) || '');
50
+ }
51
+ catch {
52
+ return '(dynamic baseUrl)';
53
+ }
54
+ }
55
+ return String(base || '');
56
+ }
57
+ function requireBuilderEnabled(sessionState, sessionId, toolName) {
58
+ if (!sessionState.isToolActive(sessionId, toolName)) {
59
+ throw new Error(`Tool not enabled in this session: ${toolName}. Enable the builder toolset (${BUILDER_ABILITY_ID}) via commandable_search_tools → commandable_enable_toolset first.`);
60
+ }
61
+ }
62
+ export function getMetaToolDefinitions() {
63
+ return [
64
+ {
65
+ name: META_TOOL_NAMES.readme,
66
+ description: 'Read this first. Returns a guide explaining how Commandable works and how to discover/add integrations safely.',
67
+ inputSchema: { type: 'object', additionalProperties: false, properties: {}, required: [] },
68
+ },
69
+ {
70
+ name: META_TOOL_NAMES.searchTools,
71
+ description: `Search available toolsets (integration/toolset bundles) you can enable in this session. Call \`${META_TOOL_NAMES.readme}\` first if you haven't yet.`,
72
+ inputSchema: {
73
+ type: 'object',
74
+ additionalProperties: false,
75
+ properties: {
76
+ query: { type: 'string', minLength: 1 },
77
+ limit: { type: 'number', minimum: 1, maximum: 50 },
78
+ },
79
+ required: ['query'],
80
+ },
81
+ },
82
+ {
83
+ name: META_TOOL_NAMES.enableToolset,
84
+ description: `Enable a toolset in the current session, making its tools available. Call \`${META_TOOL_NAMES.readme}\` first if you haven't yet.`,
85
+ inputSchema: {
86
+ type: 'object',
87
+ additionalProperties: false,
88
+ properties: {
89
+ toolset_id: { type: 'string', minLength: 1 },
90
+ },
91
+ required: ['toolset_id'],
92
+ },
93
+ },
94
+ {
95
+ name: META_TOOL_NAMES.disableToolset,
96
+ description: `Disable a toolset from the current session, removing its tools. Call \`${META_TOOL_NAMES.readme}\` first if you haven't yet.`,
97
+ inputSchema: {
98
+ type: 'object',
99
+ additionalProperties: false,
100
+ properties: {
101
+ toolset_id: { type: 'string', minLength: 1 },
102
+ },
103
+ required: ['toolset_id'],
104
+ },
105
+ },
106
+ ];
107
+ }
108
+ export function getBuilderToolDefinitions() {
109
+ return [
110
+ {
111
+ name: META_TOOL_NAMES.listPrebuiltIntegrations,
112
+ description: `Builder tool. List available pre-built integrations you can add (from the integration catalog) and show which are already configured.`,
113
+ inputSchema: {
114
+ type: 'object',
115
+ additionalProperties: false,
116
+ properties: {
117
+ query: { type: 'string' },
118
+ limit: { type: 'number', minimum: 1, maximum: 100 },
119
+ },
120
+ required: [],
121
+ },
122
+ },
123
+ {
124
+ name: META_TOOL_NAMES.addPrebuiltIntegration,
125
+ description: `Builder tool. Add a pre-built integration from the catalog to this Commandable instance (credentials are entered out-of-band).`,
126
+ inputSchema: {
127
+ type: 'object',
128
+ additionalProperties: false,
129
+ properties: {
130
+ type: { type: 'string', minLength: 1 },
131
+ label: { type: 'string' },
132
+ credential_variant: { type: 'string' },
133
+ max_scope: { type: 'string', enum: ['read', 'write'] },
134
+ enabled_toolsets: { type: 'array', items: { type: 'string' } },
135
+ disabled_tools: { type: 'array', items: { type: 'string' } },
136
+ },
137
+ required: ['type'],
138
+ },
139
+ },
140
+ {
141
+ name: META_TOOL_NAMES.upsertCustomIntegration,
142
+ description: 'Builder tool. Create or update a custom integration type (base URL + credential schema + auth injection rules). Omit type_slug to create a new one.',
143
+ inputSchema: {
144
+ type: 'object',
145
+ additionalProperties: false,
146
+ properties: {
147
+ type_slug: { type: 'string', minLength: 1 },
148
+ label: { type: 'string', minLength: 1 },
149
+ base_url: { type: 'string', minLength: 1 },
150
+ auth_type: { type: 'string', enum: ['basic', 'custom'] },
151
+ credential_fields: {
152
+ type: 'array',
153
+ minItems: 1,
154
+ items: {
155
+ type: 'object',
156
+ additionalProperties: false,
157
+ properties: {
158
+ name: { type: 'string', minLength: 1 },
159
+ label: { type: 'string', minLength: 1 },
160
+ description: { type: 'string' },
161
+ sensitive: { type: 'boolean' },
162
+ },
163
+ required: ['name', 'label'],
164
+ },
165
+ },
166
+ credential_injection: {
167
+ type: 'object',
168
+ additionalProperties: false,
169
+ properties: {
170
+ headers: { type: 'object', additionalProperties: { type: 'string' } },
171
+ query: { type: 'object', additionalProperties: { type: 'string' } },
172
+ },
173
+ required: [],
174
+ },
175
+ basic_username_field: { type: 'string' },
176
+ basic_password_field: { type: 'string' },
177
+ health_check_path: { type: 'string' },
178
+ connection_hint: {
179
+ type: 'string',
180
+ description: 'Markdown shown in the credential form. Must be a numbered list of every step the user needs to follow to obtain the credentials — starting from navigating to the correct web page..',
181
+ },
182
+ },
183
+ required: ['label', 'base_url', 'auth_type', 'credential_fields'],
184
+ },
185
+ },
186
+ {
187
+ name: META_TOOL_NAMES.testCustomTool,
188
+ description: 'Builder tool. Run a sandboxed tool handler with a test input (does not persist). Use this to iterate before saving a tool.',
189
+ inputSchema: {
190
+ type: 'object',
191
+ additionalProperties: false,
192
+ properties: {
193
+ integration_id: { type: 'string', minLength: 1 },
194
+ handler_code: { type: 'string', minLength: 1 },
195
+ input_schema: { type: 'object' },
196
+ test_input: { type: 'object' },
197
+ },
198
+ required: ['integration_id', 'handler_code'],
199
+ },
200
+ },
201
+ {
202
+ name: META_TOOL_NAMES.upsertCustomTool,
203
+ description: 'Builder tool. Upsert a custom tool on an existing integration, and register it so it can be used immediately.',
204
+ inputSchema: {
205
+ type: 'object',
206
+ additionalProperties: false,
207
+ properties: {
208
+ integration_id: { type: 'string', minLength: 1 },
209
+ name: { type: 'string', minLength: 1 },
210
+ label: { type: 'string' },
211
+ description: { type: 'string' },
212
+ scope: { type: 'string', enum: ['read', 'write', 'admin'] },
213
+ input_schema: { type: 'object' },
214
+ handler_code: { type: 'string', minLength: 1 },
215
+ },
216
+ required: ['integration_id', 'name', 'handler_code'],
217
+ },
218
+ },
219
+ {
220
+ name: META_TOOL_NAMES.deleteCustomTool,
221
+ description: 'Builder tool. Hard delete a custom tool from an integration by raw tool name (not the MCP materialized name).',
222
+ inputSchema: {
223
+ type: 'object',
224
+ additionalProperties: false,
225
+ properties: {
226
+ integration_id: { type: 'string', minLength: 1 },
227
+ name: { type: 'string', minLength: 1 },
228
+ },
229
+ required: ['integration_id', 'name'],
230
+ },
231
+ },
232
+ {
233
+ name: META_TOOL_NAMES.deleteCustomIntegration,
234
+ description: 'Builder tool. Hard delete a custom integration instance, including its custom tools and linked credentials.',
235
+ inputSchema: {
236
+ type: 'object',
237
+ additionalProperties: false,
238
+ properties: {
239
+ integration_id: { type: 'string', minLength: 1 },
240
+ },
241
+ required: ['integration_id'],
242
+ },
243
+ },
244
+ ];
245
+ }
246
+ export async function handleMetaToolCall(params) {
247
+ const { name, args, sessionId, catalog, sessionState, ctx } = params;
248
+ if (name === META_TOOL_NAMES.readme) {
249
+ return { handled: true, listChanged: false, result: { markdown: buildCommandableReadme() } };
250
+ }
251
+ if (name === META_TOOL_NAMES.searchTools) {
252
+ const query = String(args?.query || '').trim();
253
+ const limitRaw = args?.limit;
254
+ const limit = typeof limitRaw === 'number' && Number.isFinite(limitRaw) ? Math.max(1, Math.min(50, limitRaw)) : 10;
255
+ if (!query)
256
+ throw new Error('query is required');
257
+ const res = catalog.search(query, limit).map(a => ({
258
+ toolset_id: a.id,
259
+ label: a.label,
260
+ description: a.description,
261
+ integration_type: a.integrationtype,
262
+ integration_label: a.integrationLabel,
263
+ toolset_key: a.toolsetKey || null,
264
+ tool_count: a.toolCount,
265
+ score: a.score,
266
+ }));
267
+ return { handled: true, listChanged: false, result: { toolsets: res } };
268
+ }
269
+ if (name === META_TOOL_NAMES.enableToolset) {
270
+ const toolsetId = String(args?.toolset_id || '').trim();
271
+ if (!toolsetId)
272
+ throw new Error('toolset_id is required');
273
+ const ability = catalog.getAbility(toolsetId);
274
+ if (!ability)
275
+ throw new Error(`Unknown toolset_id: ${toolsetId}`);
276
+ const { newTools } = sessionState.loadAbility(sessionId, ability);
277
+ const integrationGuide = (() => {
278
+ if (ability.id === BUILDER_ABILITY_ID) {
279
+ const base = buildBuilderGuide();
280
+ const integrations = ctx?.integrationsRef?.current || [];
281
+ const lines = integrations.map((i) => {
282
+ const baseUrl = providerBaseUrl(i) || '(unknown baseUrl)';
283
+ return `- \`${i.referenceId || i.id}\`: ${i.label} (${i.type}), baseUrl: ${baseUrl}`;
284
+ }).join('\n');
285
+ return `${base}\n\n${lines ? `${lines}\n` : 'No integrations configured yet.\n'}`;
286
+ }
287
+ try {
288
+ return loadIntegrationPrompt(ability.integrationtype);
289
+ }
290
+ catch {
291
+ return null;
292
+ }
293
+ })();
294
+ return {
295
+ handled: true,
296
+ listChanged: newTools.length > 0,
297
+ result: {
298
+ loaded: true,
299
+ toolset_id: ability.id,
300
+ label: ability.label,
301
+ tool_count: ability.toolNames.length,
302
+ new_tools: newTools,
303
+ integration_guide: integrationGuide,
304
+ },
305
+ };
306
+ }
307
+ if (name === META_TOOL_NAMES.disableToolset) {
308
+ const toolsetId = String(args?.toolset_id || '').trim();
309
+ if (!toolsetId)
310
+ throw new Error('toolset_id is required');
311
+ const ability = catalog.getAbility(toolsetId);
312
+ if (!ability)
313
+ throw new Error(`Unknown toolset_id: ${toolsetId}`);
314
+ const { removedTools } = sessionState.unloadAbility(sessionId, ability);
315
+ return {
316
+ handled: true,
317
+ listChanged: removedTools.length > 0,
318
+ result: {
319
+ unloaded: true,
320
+ toolset_id: ability.id,
321
+ label: ability.label,
322
+ removed_tools: removedTools,
323
+ },
324
+ };
325
+ }
326
+ if (name === META_TOOL_NAMES.listPrebuiltIntegrations) {
327
+ requireBuilderEnabled(sessionState, sessionId, META_TOOL_NAMES.listPrebuiltIntegrations);
328
+ if (!ctx)
329
+ throw new Error('Integration management is not available in this server mode.');
330
+ const q = String(args?.query ?? '').trim().toLowerCase();
331
+ const limitRaw = args?.limit;
332
+ const limit = typeof limitRaw === 'number' && Number.isFinite(limitRaw) ? Math.max(1, Math.min(100, limitRaw)) : 50;
333
+ const catalogItems = listIntegrationCatalog();
334
+ const configured = await listIntegrations(ctx.db, ctx.spaceId);
335
+ const byType = new Map();
336
+ for (const it of configured) {
337
+ const arr = byType.get(it.type) || [];
338
+ arr.push(it);
339
+ byType.set(it.type, arr);
340
+ }
341
+ const items = [];
342
+ for (const it of catalogItems) {
343
+ if (q) {
344
+ const hay = `${it.type} ${it.name}`.toLowerCase();
345
+ if (!hay.includes(q))
346
+ continue;
347
+ }
348
+ const instances = byType.get(it.type) || [];
349
+ const instanceInfos = await Promise.all(instances.map(async (inst) => {
350
+ const hasCreds = inst.connectionMethod === 'credentials' && inst.credentialId
351
+ ? await ctx.credentialStore.hasCredentials(ctx.spaceId, inst.credentialId)
352
+ : false;
353
+ const baseUrl = ctx.credentialSetupBaseUrl ? ctx.credentialSetupBaseUrl.replace(/\/+$/, '') : null;
354
+ const credentialUrl = baseUrl && inst.connectionMethod === 'credentials'
355
+ ? `${baseUrl}/integrations/${encodeURIComponent(inst.id)}`
356
+ : null;
357
+ return {
358
+ id: inst.id,
359
+ label: inst.label,
360
+ enabled: inst.enabled !== false,
361
+ connection_method: inst.connectionMethod ?? null,
362
+ credential_variant: inst.credentialVariant ?? null,
363
+ has_credentials: hasCreds,
364
+ health_status: inst.healthStatus ?? null,
365
+ health_checked_at: inst.healthCheckedAt ? inst.healthCheckedAt.toISOString() : null,
366
+ credential_url: credentialUrl,
367
+ };
368
+ }));
369
+ items.push({
370
+ type: it.type,
371
+ name: it.name,
372
+ configured: instances.length > 0,
373
+ instances: instanceInfos,
374
+ supports_credentials: !!getBuiltInIntegrationTypeConfig(it.type),
375
+ });
376
+ if (items.length >= limit)
377
+ break;
378
+ }
379
+ return { handled: true, listChanged: false, result: { integrations: items } };
380
+ }
381
+ if (name === META_TOOL_NAMES.addPrebuiltIntegration) {
382
+ requireBuilderEnabled(sessionState, sessionId, META_TOOL_NAMES.addPrebuiltIntegration);
383
+ if (!ctx)
384
+ throw new Error('Integration management is not available in this server mode.');
385
+ const type = String(args?.type || '').trim();
386
+ if (!type)
387
+ throw new Error('type is required');
388
+ const catalogItems = listIntegrationCatalog();
389
+ const exists = catalogItems.some(i => i.type === type);
390
+ if (!exists)
391
+ throw new Error(`Unknown integration type: ${type}`);
392
+ const manifest = loadIntegrationManifest(type);
393
+ const defaultLabel = (manifest && typeof manifest === 'object' && typeof manifest.name === 'string' && manifest.name.trim().length)
394
+ ? manifest.name.trim()
395
+ : type;
396
+ const id = crypto.randomUUID ? crypto.randomUUID() : crypto.randomBytes(16).toString('hex');
397
+ const shortId = id.replace(/[^a-z0-9]/gi, '').slice(0, 8).toLowerCase();
398
+ const label = String(args?.label || '').trim() || defaultLabel;
399
+ const parseStringArray = (v) => {
400
+ if (Array.isArray(v))
401
+ return v.map((s) => String(s)).filter(Boolean);
402
+ if (typeof v === 'string') {
403
+ try {
404
+ const p = JSON.parse(v);
405
+ return Array.isArray(p) ? p.map((s) => String(s)).filter(Boolean) : undefined;
406
+ }
407
+ catch {
408
+ return undefined;
409
+ }
410
+ }
411
+ return undefined;
412
+ };
413
+ const enabledToolsets = parseStringArray(args?.enabled_toolsets);
414
+ const disabledTools = parseStringArray(args?.disabled_tools);
415
+ const maxScope = args?.max_scope === 'read' || args?.max_scope === 'write' ? args.max_scope : undefined;
416
+ const credentialVariant = typeof args?.credential_variant === 'string' && args.credential_variant.trim().length
417
+ ? args.credential_variant.trim()
418
+ : null;
419
+ const referenceId = `${type}-${shortId}`;
420
+ const integration = {
421
+ spaceId: ctx.spaceId,
422
+ id,
423
+ type,
424
+ referenceId,
425
+ label,
426
+ enabled: true,
427
+ enabledToolsets: enabledToolsets?.length ? enabledToolsets : undefined,
428
+ maxScope,
429
+ disabledTools: disabledTools?.length ? disabledTools : undefined,
430
+ credentialVariant,
431
+ // mark as credentials-based immediately; values are entered out-of-band via management UI
432
+ connectionMethod: 'credentials',
433
+ credentialId: `${referenceId}-creds`,
434
+ };
435
+ await upsertIntegration(ctx.db, integration);
436
+ // Refresh mutable integrations ref if present (keeps tool handlers from capturing stale config).
437
+ if (ctx.integrationsRef) {
438
+ try {
439
+ ctx.integrationsRef.current = await listIntegrations(ctx.db, ctx.spaceId);
440
+ }
441
+ catch { }
442
+ }
443
+ const typeConfig = getBuiltInIntegrationTypeConfig(type);
444
+ const selectedVariantKey = credentialVariant || typeConfig?.defaultVariant || null;
445
+ const selectedVariant = selectedVariantKey ? typeConfig?.variants[selectedVariantKey] : null;
446
+ const credentialFields = selectedVariant?.credentialSchema && typeof selectedVariant.credentialSchema === 'object'
447
+ ? Object.keys(selectedVariant.credentialSchema.properties || {})
448
+ : [];
449
+ const baseUrl = ctx.credentialSetupBaseUrl ? ctx.credentialSetupBaseUrl.replace(/\/+$/, '') : null;
450
+ const managementUrl = baseUrl ? `${baseUrl}/integrations` : null;
451
+ const credentialUrl = baseUrl ? `${baseUrl}/integrations/${encodeURIComponent(id)}` : null;
452
+ // Dynamic tool registration (create mode): materialize tools for this integration
453
+ // and register them into the live tool index + ability catalog.
454
+ let registeredTools = 0;
455
+ let registeredToolsets = [];
456
+ if (ctx.toolIndexRef && ctx.catalogRef) {
457
+ const toolIndex = buildMcpToolIndexForIntegrations({
458
+ spaceId: ctx.spaceId,
459
+ integrations: [integration],
460
+ proxy: ctx.proxy,
461
+ integrationsRef: ctx.integrationsRef,
462
+ });
463
+ for (const [toolName, tool] of toolIndex.byName.entries()) {
464
+ if (!ctx.toolIndexRef.byName.has(toolName)) {
465
+ ctx.toolIndexRef.byName.set(toolName, tool);
466
+ registeredTools++;
467
+ }
468
+ }
469
+ if (ctx.toolIndexRef.list) {
470
+ for (const t of toolIndex.tools) {
471
+ if (!ctx.toolIndexRef.list.find(x => x.name === t.name))
472
+ ctx.toolIndexRef.list.push(t);
473
+ }
474
+ }
475
+ const newAbilities = ctx.catalogRef.current.addIntegration(integration);
476
+ registeredToolsets = newAbilities.map(a => ({
477
+ toolset_id: a.id,
478
+ label: a.label,
479
+ tool_count: a.toolNames.length,
480
+ }));
481
+ }
482
+ return {
483
+ handled: true,
484
+ listChanged: false,
485
+ result: {
486
+ added: true,
487
+ integration: {
488
+ id,
489
+ type,
490
+ label,
491
+ reference_id: integration.referenceId,
492
+ credential_variant: credentialVariant,
493
+ },
494
+ registered_tools: registeredTools,
495
+ toolsets: registeredToolsets,
496
+ credentials_needed: !!typeConfig,
497
+ management_url: managementUrl,
498
+ credential_url: credentialUrl,
499
+ credential_fields: credentialFields,
500
+ next_steps: credentialUrl
501
+ ? ['Open credential_url to enter credentials, then enable a toolset and use tools.']
502
+ : ['Start the management UI (create mode) to get a credential URL, then enable a toolset and use tools.'],
503
+ },
504
+ };
505
+ }
506
+ if (name === META_TOOL_NAMES.upsertCustomIntegration) {
507
+ requireBuilderEnabled(sessionState, sessionId, META_TOOL_NAMES.upsertCustomIntegration);
508
+ if (!ctx)
509
+ throw new Error('Integration management is not available in this server mode.');
510
+ if (!ctx.integrationsRef)
511
+ throw new Error('integrationsRef is required for builder mode.');
512
+ if (!ctx.integrationTypeConfigsRef)
513
+ throw new Error('integrationTypeConfigsRef is required for custom integrations.');
514
+ const typeSlugInput = typeof args?.type_slug === 'string' ? args.type_slug.trim() : '';
515
+ const label = String(args?.label || '').trim();
516
+ const baseUrl = String(args?.base_url || '').trim();
517
+ const authType = (args?.auth_type === 'basic' || args?.auth_type === 'custom') ? String(args.auth_type) : null;
518
+ const rawCredFields = args?.credential_fields;
519
+ const credentialFields = Array.isArray(rawCredFields)
520
+ ? rawCredFields
521
+ : (typeof rawCredFields === 'string' ? (() => { try {
522
+ const p = JSON.parse(rawCredFields);
523
+ return Array.isArray(p) ? p : [];
524
+ }
525
+ catch {
526
+ return [];
527
+ } })() : []);
528
+ const rawCredInjection = args?.credential_injection;
529
+ const credentialInjection = rawCredInjection && typeof rawCredInjection === 'object' && !Array.isArray(rawCredInjection)
530
+ ? rawCredInjection
531
+ : (typeof rawCredInjection === 'string' ? (() => { try {
532
+ const p = JSON.parse(rawCredInjection);
533
+ return (p && typeof p === 'object' && !Array.isArray(p)) ? p : null;
534
+ }
535
+ catch {
536
+ return null;
537
+ } })() : null);
538
+ const basicUsernameField = typeof args?.basic_username_field === 'string' ? args.basic_username_field.trim() : '';
539
+ const basicPasswordField = typeof args?.basic_password_field === 'string' ? args.basic_password_field.trim() : '';
540
+ const healthCheckPath = typeof args?.health_check_path === 'string' ? args.health_check_path.trim() : '';
541
+ const connectionHint = typeof args?.connection_hint === 'string' ? normalizeHintMarkdown(args.connection_hint).trim() : '';
542
+ if (!label)
543
+ throw new Error('label is required');
544
+ if (!baseUrl)
545
+ throw new Error('base_url is required');
546
+ if (!authType)
547
+ throw new Error('auth_type must be basic or custom');
548
+ if (!credentialFields.length)
549
+ throw new Error('credential_fields is required');
550
+ if (authType === 'custom') {
551
+ const hasAny = credentialInjection && (credentialInjection.headers || credentialInjection.query);
552
+ if (!hasAny)
553
+ throw new Error('credential_injection is required when auth_type is custom');
554
+ }
555
+ else {
556
+ if (!basicUsernameField || !basicPasswordField)
557
+ throw new Error('basic_username_field and basic_password_field are required when auth_type is basic');
558
+ }
559
+ const toKebab = (s) => (s || '')
560
+ .toLowerCase()
561
+ .replace(/[^a-z0-9]+/g, '-')
562
+ .replace(/^-+|-+$/g, '')
563
+ .slice(0, 32) || 'integration';
564
+ const suffix = () => crypto.randomBytes(2).toString('hex');
565
+ const existingSlugs = new Set(ctx.integrationTypeConfigsRef.current
566
+ .filter(c => c.spaceId === ctx.spaceId)
567
+ .map(c => c.typeSlug));
568
+ let typeSlug = typeSlugInput;
569
+ if (!typeSlug) {
570
+ for (let i = 0; i < 20; i++) {
571
+ const candidate = `${toKebab(label)}-${suffix()}`;
572
+ if (!existingSlugs.has(candidate)) {
573
+ typeSlug = candidate;
574
+ break;
575
+ }
576
+ }
577
+ if (!typeSlug)
578
+ throw new Error('Failed to generate a unique type slug');
579
+ }
580
+ const existingCfg = typeSlugInput
581
+ ? await getIntegrationTypeConfig(ctx.db, ctx.spaceId, typeSlugInput)
582
+ : null;
583
+ if (typeSlugInput && !existingCfg)
584
+ throw new Error(`Unknown type_slug: ${typeSlugInput}`);
585
+ const schemaProps = {};
586
+ const required = [];
587
+ for (const f of credentialFields) {
588
+ const name = String(f?.name || '').trim();
589
+ const title = String(f?.label || '').trim();
590
+ if (!name || !title)
591
+ continue;
592
+ const description = typeof f?.description === 'string' ? f.description : undefined;
593
+ const sensitive = !!f?.sensitive;
594
+ schemaProps[name] = {
595
+ type: 'string',
596
+ title,
597
+ ...(description ? { description } : {}),
598
+ ...(sensitive ? { format: 'password' } : {}),
599
+ };
600
+ required.push(name);
601
+ }
602
+ if (!Object.keys(schemaProps).length)
603
+ throw new Error('credential_fields must include at least one valid field');
604
+ const credentialSchema = sanitizeJsonSchema({
605
+ type: 'object',
606
+ properties: schemaProps,
607
+ required,
608
+ additionalProperties: false,
609
+ });
610
+ const id = crypto.randomUUID ? crypto.randomUUID() : crypto.randomBytes(16).toString('hex');
611
+ const cfgId = existingCfg?.id || (crypto.randomUUID ? crypto.randomUUID() : crypto.randomBytes(16).toString('hex'));
612
+ const defaultVariantConfig = {
613
+ label,
614
+ credentialSchema,
615
+ auth: authType === 'basic'
616
+ ? { kind: 'basic', usernameField: basicUsernameField, passwordField: basicPasswordField }
617
+ : { kind: 'template', injection: credentialInjection || {} },
618
+ baseUrl,
619
+ healthCheck: healthCheckPath ? { path: healthCheckPath } : null,
620
+ hintMarkdown: connectionHint || null,
621
+ };
622
+ const customCfg = {
623
+ id: cfgId,
624
+ spaceId: ctx.spaceId,
625
+ typeSlug,
626
+ label,
627
+ defaultVariant: 'default',
628
+ variants: { default: defaultVariantConfig },
629
+ };
630
+ await upsertIntegrationTypeConfig(ctx.db, customCfg);
631
+ const cfgIndex = ctx.integrationTypeConfigsRef.current.findIndex(c => c.spaceId === ctx.spaceId && c.typeSlug === typeSlug);
632
+ if (cfgIndex >= 0)
633
+ ctx.integrationTypeConfigsRef.current[cfgIndex] = customCfg;
634
+ else
635
+ ctx.integrationTypeConfigsRef.current.push(customCfg);
636
+ const existingIntegration = ctx.integrationsRef.current.find(i => i.type === typeSlug);
637
+ const created = !existingIntegration;
638
+ const shortId = id.replace(/[^a-z0-9]/gi, '').slice(0, 8).toLowerCase();
639
+ const referenceId = existingIntegration?.referenceId || `${typeSlug}-${shortId}`;
640
+ const integration = existingIntegration
641
+ ? {
642
+ ...existingIntegration,
643
+ spaceId: ctx.spaceId,
644
+ type: typeSlug,
645
+ label,
646
+ }
647
+ : {
648
+ spaceId: ctx.spaceId,
649
+ id,
650
+ type: typeSlug,
651
+ referenceId,
652
+ label,
653
+ enabled: true,
654
+ connectionMethod: 'credentials',
655
+ credentialId: `${referenceId}-creds`,
656
+ };
657
+ await upsertIntegration(ctx.db, integration);
658
+ try {
659
+ ctx.integrationsRef.current = await listIntegrations(ctx.db, ctx.spaceId);
660
+ }
661
+ catch { }
662
+ const base = ctx.credentialSetupBaseUrl ? ctx.credentialSetupBaseUrl.replace(/\/+$/, '') : null;
663
+ const managementUrl = base ? `${base}/integrations` : null;
664
+ const credentialUrl = base ? `${base}/integrations/${encodeURIComponent(integration.id)}` : null;
665
+ return {
666
+ handled: true,
667
+ listChanged: false,
668
+ result: {
669
+ integration: {
670
+ id: integration.id,
671
+ type: typeSlug,
672
+ label,
673
+ reference_id: integration.referenceId,
674
+ auth_type: authType,
675
+ },
676
+ upserted: true,
677
+ created,
678
+ management_url: managementUrl,
679
+ credential_url: credentialUrl,
680
+ next_steps: credentialUrl
681
+ ? ['Open credential_url to enter credentials, then create tools with commandable_upsert_custom_tool.']
682
+ : ['Start the management UI (create mode) to get a credential URL, then create tools with commandable_upsert_custom_tool.'],
683
+ },
684
+ };
685
+ }
686
+ if (name === META_TOOL_NAMES.testCustomTool) {
687
+ requireBuilderEnabled(sessionState, sessionId, META_TOOL_NAMES.testCustomTool);
688
+ if (!ctx)
689
+ throw new Error('Tool building is not available in this server mode.');
690
+ const integrationId = String(args?.integration_id || '').trim();
691
+ const handlerCode = String(args?.handler_code || '').trim();
692
+ const inputSchemaRaw = args?.input_schema;
693
+ const testInput = (args?.test_input && typeof args.test_input === 'object') ? args.test_input : {};
694
+ if (!integrationId)
695
+ throw new Error('integration_id is required');
696
+ if (!handlerCode)
697
+ throw new Error('handler_code is required');
698
+ const integration = ctx.integrationsRef?.current?.find(i => i.id === integrationId || i.referenceId === integrationId);
699
+ if (!integration)
700
+ throw new Error(`Unknown integration_id: ${integrationId}`);
701
+ const getIntegration = createGetIntegration(ctx.integrationsRef, ctx.proxy);
702
+ const wrapper = `async (input) => {\n const integration = getIntegration('${integration.id}');\n const __inner = ${handlerCode};\n return await __inner(input);\n}`;
703
+ const safe = createSafeHandlerFromString(wrapper, getIntegration);
704
+ const res = await safe(testInput);
705
+ return { handled: true, listChanged: false, result: res };
706
+ }
707
+ if (name === META_TOOL_NAMES.upsertCustomTool) {
708
+ requireBuilderEnabled(sessionState, sessionId, META_TOOL_NAMES.upsertCustomTool);
709
+ if (!ctx)
710
+ throw new Error('Tool building is not available in this server mode.');
711
+ const integrationId = String(args?.integration_id || '').trim();
712
+ const toolNameRaw = String(args?.name || '').trim();
713
+ const label = typeof args?.label === 'string' ? args.label.trim() : '';
714
+ const description = typeof args?.description === 'string' ? args.description.trim() : '';
715
+ const handlerCode = String(args?.handler_code || '').trim();
716
+ const scope = (args?.scope === 'read' || args?.scope === 'write' || args?.scope === 'admin') ? args.scope : 'write';
717
+ const inputSchemaObj = (args?.input_schema && typeof args.input_schema === 'object') ? args.input_schema : {
718
+ type: 'object',
719
+ additionalProperties: true,
720
+ };
721
+ if (!integrationId)
722
+ throw new Error('integration_id is required');
723
+ if (!toolNameRaw)
724
+ throw new Error('name is required');
725
+ if (!handlerCode)
726
+ throw new Error('handler_code is required');
727
+ if (!/^async\s*\(\s*input\s*\)\s*=>/.test(handlerCode))
728
+ throw new Error('handler_code must start with: async (input) => { ... }');
729
+ const integration = ctx.integrationsRef?.current?.find(i => i.id === integrationId || i.referenceId === integrationId);
730
+ if (!integration)
731
+ throw new Error(`Unknown integration_id: ${integrationId}`);
732
+ const existing = await getToolDefinitionByName(ctx.db, ctx.spaceId, integration.id, toolNameRaw);
733
+ const created = !existing;
734
+ const id = existing?.id || (crypto.randomUUID ? crypto.randomUUID() : crypto.randomBytes(16).toString('hex'));
735
+ const inputSchema = sanitizeJsonSchema(inputSchemaObj);
736
+ await upsertToolDefinition(ctx.db, {
737
+ id,
738
+ spaceId: ctx.spaceId,
739
+ integrationId: integration.id,
740
+ name: toolNameRaw,
741
+ displayName: label || null,
742
+ description: description || toolNameRaw,
743
+ inputSchema,
744
+ handlerCode,
745
+ scope,
746
+ });
747
+ // Materialize executable tool and register into the live index.
748
+ const executable = buildExecutableToolFromDefinition({
749
+ spaceId: ctx.spaceId,
750
+ integration,
751
+ tool: {
752
+ id,
753
+ spaceId: ctx.spaceId,
754
+ integrationId: integration.id,
755
+ name: toolNameRaw,
756
+ displayName: label || null,
757
+ description: description || toolNameRaw,
758
+ inputSchema,
759
+ handlerCode,
760
+ scope,
761
+ },
762
+ proxy: ctx.proxy,
763
+ integrationsRef: ctx.integrationsRef,
764
+ });
765
+ if (ctx.toolIndexRef) {
766
+ ctx.toolIndexRef.byName.set(executable.name, executable);
767
+ if (ctx.toolIndexRef.list && !ctx.toolIndexRef.list.find(t => t.name === executable.name)) {
768
+ ctx.toolIndexRef.list.push({
769
+ name: executable.name,
770
+ description: executable.description,
771
+ inputSchema: executable.inputSchema,
772
+ });
773
+ }
774
+ }
775
+ let customAbilityId = null;
776
+ if (ctx.catalogRef) {
777
+ const ability = ctx.catalogRef.current.addCustomTool({ integration, toolName: executable.name });
778
+ customAbilityId = ability.id;
779
+ // Auto-load the custom tools ability so the newly created tool is immediately callable.
780
+ if (sessionId)
781
+ sessionState.loadAbility(sessionId, ability);
782
+ }
783
+ return {
784
+ handled: true,
785
+ listChanged: true,
786
+ result: {
787
+ upserted: true,
788
+ created,
789
+ tool: {
790
+ id,
791
+ name: executable.name,
792
+ display_name: executable.displayName,
793
+ scope,
794
+ },
795
+ custom_toolset_id: customAbilityId,
796
+ next_steps: [
797
+ 'Call the newly registered tool by name (it is now enabled in this session).',
798
+ ],
799
+ },
800
+ };
801
+ }
802
+ if (name === META_TOOL_NAMES.deleteCustomTool) {
803
+ requireBuilderEnabled(sessionState, sessionId, META_TOOL_NAMES.deleteCustomTool);
804
+ if (!ctx)
805
+ throw new Error('Tool building is not available in this server mode.');
806
+ const integrationId = String(args?.integration_id || '').trim();
807
+ const toolNameRaw = String(args?.name || '').trim();
808
+ if (!integrationId)
809
+ throw new Error('integration_id is required');
810
+ if (!toolNameRaw)
811
+ throw new Error('name is required');
812
+ const integration = ctx.integrationsRef?.current?.find(i => i.id === integrationId || i.referenceId === integrationId);
813
+ if (!integration)
814
+ throw new Error(`Unknown integration_id: ${integrationId}`);
815
+ const existing = await getToolDefinitionByName(ctx.db, ctx.spaceId, integration.id, toolNameRaw);
816
+ if (!existing) {
817
+ return {
818
+ handled: true,
819
+ listChanged: false,
820
+ result: { deleted: false, reason: 'not_found' },
821
+ };
822
+ }
823
+ const executable = buildExecutableToolFromDefinition({
824
+ spaceId: ctx.spaceId,
825
+ integration,
826
+ tool: existing,
827
+ proxy: ctx.proxy,
828
+ integrationsRef: ctx.integrationsRef,
829
+ });
830
+ await deleteToolDefinitionByName(ctx.db, ctx.spaceId, integration.id, toolNameRaw);
831
+ if (ctx.toolIndexRef) {
832
+ ctx.toolIndexRef.byName.delete(executable.name);
833
+ if (ctx.toolIndexRef.list)
834
+ ctx.toolIndexRef.list = ctx.toolIndexRef.list.filter(t => t.name !== executable.name);
835
+ }
836
+ if (ctx.catalogRef)
837
+ ctx.catalogRef.current.removeCustomTool({ integration, toolName: executable.name });
838
+ sessionState.removeToolFromAllSessions(executable.name);
839
+ return {
840
+ handled: true,
841
+ listChanged: true,
842
+ result: {
843
+ deleted: true,
844
+ tool: {
845
+ raw_name: toolNameRaw,
846
+ name: executable.name,
847
+ },
848
+ },
849
+ };
850
+ }
851
+ if (name === META_TOOL_NAMES.deleteCustomIntegration) {
852
+ requireBuilderEnabled(sessionState, sessionId, META_TOOL_NAMES.deleteCustomIntegration);
853
+ if (!ctx)
854
+ throw new Error('Integration management is not available in this server mode.');
855
+ if (!ctx.integrationsRef)
856
+ throw new Error('integrationsRef is required for builder mode.');
857
+ if (!ctx.integrationTypeConfigsRef)
858
+ throw new Error('integrationTypeConfigsRef is required for custom integrations.');
859
+ const integrationId = String(args?.integration_id || '').trim();
860
+ if (!integrationId)
861
+ throw new Error('integration_id is required');
862
+ const integration = ctx.integrationsRef.current.find(i => i.id === integrationId || i.referenceId === integrationId);
863
+ if (!integration)
864
+ throw new Error(`Unknown integration_id: ${integrationId}`);
865
+ const materializedToolNames = [...ctx.toolIndexRef?.byName.keys() || []]
866
+ .filter(toolName => toolName.endsWith(`__n${integration.id.replace(/[^a-z0-9]/gi, '').slice(0, 8).toLowerCase()}`));
867
+ for (const toolName of materializedToolNames) {
868
+ ctx.toolIndexRef?.byName.delete(toolName);
869
+ sessionState.removeToolFromAllSessions(toolName);
870
+ }
871
+ if (ctx.toolIndexRef?.list)
872
+ ctx.toolIndexRef.list = ctx.toolIndexRef.list.filter(t => !materializedToolNames.includes(t.name));
873
+ await deleteToolDefinitionsForIntegration(ctx.db, ctx.spaceId, integration.id);
874
+ if (integration.connectionMethod === 'credentials' && integration.credentialId)
875
+ await ctx.credentialStore.deleteCredentials(ctx.spaceId, integration.credentialId);
876
+ await deleteIntegrationById(ctx.db, integration.id);
877
+ const remainingIntegrations = await listIntegrations(ctx.db, ctx.spaceId);
878
+ ctx.integrationsRef.current = remainingIntegrations;
879
+ if (!remainingIntegrations.some(i => i.type === integration.type)) {
880
+ await deleteIntegrationTypeConfig(ctx.db, ctx.spaceId, integration.type);
881
+ ctx.integrationTypeConfigsRef.current = ctx.integrationTypeConfigsRef.current
882
+ .filter(cfg => !(cfg.spaceId === ctx.spaceId && cfg.typeSlug === integration.type));
883
+ }
884
+ if (ctx.catalogRef)
885
+ ctx.catalogRef.current.removeIntegrationAbilities(integration);
886
+ return {
887
+ handled: true,
888
+ listChanged: materializedToolNames.length > 0,
889
+ result: {
890
+ deleted: true,
891
+ integration: {
892
+ id: integration.id,
893
+ type: integration.type,
894
+ label: integration.label,
895
+ },
896
+ },
897
+ };
898
+ }
899
+ return { handled: false };
900
+ }
901
+ //# sourceMappingURL=metaTools.js.map