draftgo-cli 1.0.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 (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +249 -0
  3. package/bin/draftgo.js +9 -0
  4. package/package.json +70 -0
  5. package/resources/project-design/README.md +42 -0
  6. package/resources/skill/SKILL.md +62 -0
  7. package/resources/skill/init/SKILL.md +41 -0
  8. package/resources/skill/manifest.json +35 -0
  9. package/resources/skill/references/ai.md +41 -0
  10. package/resources/skill/references/app-api.md +97 -0
  11. package/resources/skill/references/architecture.md +13 -0
  12. package/resources/skill/references/chat-sdk.md +205 -0
  13. package/resources/skill/references/checkout.md +140 -0
  14. package/resources/skill/references/data.md +49 -0
  15. package/resources/skill/references/db-relations.md +29 -0
  16. package/resources/skill/references/delivery.md +33 -0
  17. package/resources/skill/references/development.md +41 -0
  18. package/resources/skill/references/diagnostics.md +50 -0
  19. package/resources/skill/references/frontend.md +158 -0
  20. package/resources/skill/references/mcp.md +110 -0
  21. package/resources/skill/references/methods.md +143 -0
  22. package/resources/skill/references/modules.md +75 -0
  23. package/resources/skill/references/runtime.md +109 -0
  24. package/resources/skill/references/services.md +32 -0
  25. package/src/apiContractCache.js +120 -0
  26. package/src/cli.js +100 -0
  27. package/src/commandRegistry.js +46 -0
  28. package/src/commands/api.js +244 -0
  29. package/src/commands/apiKey.js +30 -0
  30. package/src/commands/autoPush.js +36 -0
  31. package/src/commands/capabilities.js +100 -0
  32. package/src/commands/check.js +82 -0
  33. package/src/commands/checkout.js +18 -0
  34. package/src/commands/clean.js +72 -0
  35. package/src/commands/commit.js +47 -0
  36. package/src/commands/components.js +554 -0
  37. package/src/commands/conflict.js +30 -0
  38. package/src/commands/conflicts.js +16 -0
  39. package/src/commands/connect.js +91 -0
  40. package/src/commands/delete.js +95 -0
  41. package/src/commands/deploy.js +77 -0
  42. package/src/commands/diff.js +39 -0
  43. package/src/commands/group.js +37 -0
  44. package/src/commands/help.js +190 -0
  45. package/src/commands/init.js +126 -0
  46. package/src/commands/listTargets.js +13 -0
  47. package/src/commands/local.js +79 -0
  48. package/src/commands/map.js +395 -0
  49. package/src/commands/mcp.js +150 -0
  50. package/src/commands/reconcile.js +20 -0
  51. package/src/commands/role.js +31 -0
  52. package/src/commands/status.js +98 -0
  53. package/src/commands/uninstall.js +52 -0
  54. package/src/commands/update.js +79 -0
  55. package/src/commands/verify.js +188 -0
  56. package/src/commands/visualVerify.js +281 -0
  57. package/src/commands/worklog.js +117 -0
  58. package/src/consoleEncoding.js +34 -0
  59. package/src/contractCompatibility.js +65 -0
  60. package/src/detect.js +25 -0
  61. package/src/diffReport.js +106 -0
  62. package/src/fsx.js +67 -0
  63. package/src/index.js +46 -0
  64. package/src/localRuntime/compose.js +119 -0
  65. package/src/localRuntime/detect.js +77 -0
  66. package/src/localRuntime/index.js +211 -0
  67. package/src/localRuntime/mysqlClient.js +155 -0
  68. package/src/localRuntime/services.js +117 -0
  69. package/src/logger.js +37 -0
  70. package/src/mcp/client.js +558 -0
  71. package/src/mcp/hosts.js +520 -0
  72. package/src/mcp/parallel.js +54 -0
  73. package/src/mcp/protocol.js +223 -0
  74. package/src/mcp/stdio.js +300 -0
  75. package/src/mcp/tools.js +51 -0
  76. package/src/paths.js +32 -0
  77. package/src/platforms.js +110 -0
  78. package/src/projectConfig.js +139 -0
  79. package/src/projectDesign.js +19 -0
  80. package/src/projectHealth.js +33 -0
  81. package/src/projectMap.js +220 -0
  82. package/src/prompt.js +94 -0
  83. package/src/releaseInstall.js +105 -0
  84. package/src/runtimeFiles.js +45 -0
  85. package/src/skill.js +295 -0
  86. package/src/targets.js +43 -0
  87. package/src/timeout.js +18 -0
  88. package/src/updateCheck.js +100 -0
  89. package/src/worklog.js +276 -0
  90. package/src/worktree/backend.js +438 -0
  91. package/src/worktree/errors.js +28 -0
  92. package/src/worktree/index.js +751 -0
  93. package/src/worktree/inlineScripts.js +99 -0
  94. package/src/worktree/locks.js +52 -0
  95. package/src/worktree/manifest.js +89 -0
  96. package/src/worktree/status.js +124 -0
  97. package/src/worktree/streams.js +200 -0
  98. package/src/worktree/types.js +103 -0
  99. package/src/worktree/validate.js +37 -0
@@ -0,0 +1,558 @@
1
+ 'use strict';
2
+
3
+ const pkg = require('../../package.json');
4
+ const {
5
+ DEFAULT_PROTOCOL_VERSION,
6
+ McpHttpError,
7
+ postJsonRpc,
8
+ redactText,
9
+ } = require('./protocol');
10
+ const { allWithAbort } = require('./parallel');
11
+
12
+ const SAFE_TEST_TOOLS = [
13
+ 'draftgo_project_overview',
14
+ 'draftgo_resource_list',
15
+ 'draftgo_api_search',
16
+ 'draftgo_api_describe',
17
+ 'draftgo_api_call',
18
+ ];
19
+
20
+ const REQUIRED_DRAFTGO_TOOLS = [
21
+ 'draftgo_api_search',
22
+ 'draftgo_api_describe',
23
+ 'draftgo_api_call',
24
+ ];
25
+
26
+ const TOOL_ALIASES = Object.freeze({
27
+ draftgo_api_search: Object.freeze([
28
+ 'draftgo_api_search', 'draftgo_operation_search', 'search_operations', 'search',
29
+ ]),
30
+ draftgo_api_describe: Object.freeze([
31
+ 'draftgo_api_describe', 'draftgo_operation_describe', 'describe_operation', 'describe',
32
+ ]),
33
+ draftgo_api_call: Object.freeze([
34
+ 'draftgo_api_invoke', 'draftgo_api_call', 'draftgo_operation_invoke', 'invoke_operation', 'invoke', 'call',
35
+ ]),
36
+ });
37
+
38
+ function diagnosticArguments(canonicalName) {
39
+ if (canonicalName === 'draftgo_resource_list') {
40
+ return { resource_type: 'pages', limit: 1 };
41
+ }
42
+ if (canonicalName === 'draftgo_api_search') return { query: 'listDbMeta', limit: 1 };
43
+ return {};
44
+ }
45
+
46
+ function diagnosticData(result) {
47
+ let value = result;
48
+ if (value && value.structuredContent) value = value.structuredContent;
49
+ else if (value && value.structured_content) value = value.structured_content;
50
+ if (value && value.data) value = value.data;
51
+ if (value && Object.prototype.hasOwnProperty.call(value, 'value')) value = value.value;
52
+ return value;
53
+ }
54
+
55
+ const CONNECTION_PROBE_OPERATION = 'getCurrentUserAPIKey';
56
+
57
+ function findConnectionProbeOperation(result) {
58
+ const value = diagnosticData(result);
59
+ const items = value && Array.isArray(value.items) ? value.items : [];
60
+ return items.find((operation) => operation
61
+ && operation.operation_id === CONNECTION_PROBE_OPERATION
62
+ && String(operation.method || '').toUpperCase() === 'GET'
63
+ && String(operation.risk || '').toLowerCase() === 'low'
64
+ && operation.destructive !== true) || null;
65
+ }
66
+
67
+ function assertSafeConnectionProbe(description) {
68
+ const value = diagnosticData(description);
69
+ const operation = value && value.operation && typeof value.operation === 'object'
70
+ ? value.operation : value;
71
+ const properties = operation && operation.input_schema && operation.input_schema.properties || {};
72
+ const required = operation && operation.input_schema && operation.input_schema.required || [];
73
+ const inputKeys = Object.keys(properties).filter((key) => key !== 'confirm');
74
+ if (!operation || operation.operation_id !== CONNECTION_PROBE_OPERATION
75
+ || String(operation.method || '').toUpperCase() !== 'GET'
76
+ || String(operation.risk || '').toLowerCase() !== 'low'
77
+ || operation.destructive === true || required.length > 0 || inputKeys.length > 0) {
78
+ throw new McpRpcError(`DraftGo Registry does not expose a safe ${CONNECTION_PROBE_OPERATION} probe.`, {
79
+ code: -32601,
80
+ });
81
+ }
82
+ return operation;
83
+ }
84
+
85
+ class McpRpcError extends Error {
86
+ constructor(message, { code = -32000, data, id = null } = {}) {
87
+ super(message);
88
+ this.name = 'McpRpcError';
89
+ this.code = code;
90
+ this.data = data;
91
+ this.id = id;
92
+ }
93
+ }
94
+
95
+ const SESSION_INVALID_CODES = new Set([
96
+ -32002,
97
+ 'SESSION_EXPIRED',
98
+ 'SESSION_NOT_FOUND',
99
+ 'MCP_SESSION_EXPIRED',
100
+ 'MCP_SESSION_NOT_FOUND',
101
+ ]);
102
+
103
+ function isSessionInvalidError(error) {
104
+ if (!error) return false;
105
+ if (Number(error.status) === 404) return true;
106
+ const code = error.code != null
107
+ ? error.code
108
+ : error.rpc && error.rpc.error && error.rpc.error.code;
109
+ if (SESSION_INVALID_CODES.has(code)) return true;
110
+ const message = String(error.message
111
+ || error.rpc && error.rpc.error && error.rpc.error.message
112
+ || '');
113
+ return /\b(?:mcp\s+)?session(?:\s+id)?\s+(?:was\s+)?(?:not\s+found|expired|invalid|unknown|lost)\b/i.test(message)
114
+ || /\b(?:server|mcp)\s+(?:is\s+)?(?:not initialized|uninitialized)\b/i.test(message)
115
+ || /\binitialize first\b/i.test(message);
116
+ }
117
+
118
+ function requestIdKeys(message) {
119
+ const messages = Array.isArray(message) ? message : [message];
120
+ return new Set(messages
121
+ .filter((item) => isObject(item) && typeof item.method === 'string' && item.id != null)
122
+ .map((item) => `${typeof item.id}:${JSON.stringify(item.id)}`));
123
+ }
124
+
125
+ function sessionInvalidResponse(message, expectedIds) {
126
+ const messages = Array.isArray(message) ? message : [message];
127
+ return messages.find((item) => isObject(item)
128
+ && item.error
129
+ && expectedIds.has(`${typeof item.id}:${JSON.stringify(item.id)}`)
130
+ && isSessionInvalidError(item.error)) || null;
131
+ }
132
+
133
+ function redactValue(value, secrets = [], seen = new WeakMap()) {
134
+ if (typeof value === 'string') return redactText(value, secrets);
135
+ if (!value || typeof value !== 'object') return value;
136
+ if (seen.has(value)) return seen.get(value);
137
+ if (Array.isArray(value)) {
138
+ const result = [];
139
+ seen.set(value, result);
140
+ for (const item of value) result.push(redactValue(item, secrets, seen));
141
+ return result;
142
+ }
143
+ const result = {};
144
+ seen.set(value, result);
145
+ for (const [key, item] of Object.entries(value)) {
146
+ result[key] = redactValue(item, secrets, seen);
147
+ }
148
+ return result;
149
+ }
150
+
151
+ function isObject(value) {
152
+ return !!value && typeof value === 'object' && !Array.isArray(value);
153
+ }
154
+
155
+ function sameId(left, right) {
156
+ return left === right;
157
+ }
158
+
159
+ function findInitializeRequest(message) {
160
+ const messages = Array.isArray(message) ? message : [message];
161
+ return messages.find((item) => isObject(item) && item.method === 'initialize' && item.id != null);
162
+ }
163
+
164
+ function findResponse(messages, id) {
165
+ return messages.find((message) => isObject(message)
166
+ && Object.prototype.hasOwnProperty.call(message, 'id')
167
+ && sameId(message.id, id)
168
+ && !message.method);
169
+ }
170
+
171
+ function toolMatches(name, candidate) {
172
+ const aliases = TOOL_ALIASES[candidate] || [candidate];
173
+ return aliases.some((alias) => name === alias || name.endsWith(`.${alias}`)
174
+ || name.endsWith(`/${alias}`) || name.endsWith(`:${alias}`));
175
+ }
176
+
177
+ class DraftGoMcpClient {
178
+ constructor(config, options = {}) {
179
+ if (!config || typeof config !== 'object') throw new TypeError('DraftGo MCP config is required.');
180
+ const token = String(config.token || config.sat || '').trim();
181
+ if (!token) throw new Error('DraftGo MCP config is missing API Key.');
182
+ if (!config.server && !config.mcp_url) throw new Error('DraftGo MCP config is missing server.');
183
+
184
+ this.config = { ...config, token };
185
+ this.secrets = [token];
186
+ this.sessionId = null;
187
+ this.protocolVersion = options.protocolVersion || DEFAULT_PROTOCOL_VERSION;
188
+ this.nextId = 1;
189
+ this.sessionGeneration = 0;
190
+ this.sessionRecovery = null;
191
+ this.sessionRecoveryCount = 0;
192
+ this.onMessage = typeof options.onMessage === 'function' ? options.onMessage : null;
193
+ }
194
+
195
+ async forward(message, options = {}) {
196
+ if (!isObject(message) && !Array.isArray(message)) {
197
+ throw new TypeError('MCP message must be a JSON object or batch.');
198
+ }
199
+
200
+ const initialize = findInitializeRequest(message);
201
+ const internalRecovery = options._sessionRecoveryInternal === true;
202
+ if (!initialize && !internalRecovery && this.sessionRecovery) {
203
+ await this.sessionRecovery;
204
+ }
205
+ if (initialize) this.sessionId = null;
206
+ const requestedVersion = initialize
207
+ && initialize.params
208
+ && initialize.params.protocolVersion;
209
+ const delivered = [];
210
+ const expectedIds = requestIdKeys(message);
211
+ const attemptedSessionId = initialize ? null : this.sessionId;
212
+ const attemptedGeneration = this.sessionGeneration;
213
+ const canRecover = !initialize
214
+ && !internalRecovery
215
+ && options._sessionRecoveryAttempted !== true
216
+ && attemptedSessionId != null;
217
+ let suppressedSessionError = null;
218
+
219
+ try {
220
+ await postJsonRpc(this.config, message, {
221
+ signal: options.signal,
222
+ timeoutMs: options.timeoutMs,
223
+ sessionId: attemptedSessionId,
224
+ protocolVersion: requestedVersion || this.protocolVersion,
225
+ onSession: (sessionId) => {
226
+ if (initialize || (this.sessionGeneration === attemptedGeneration
227
+ && this.sessionId === attemptedSessionId)) {
228
+ this.sessionId = sessionId;
229
+ }
230
+ },
231
+ onMessage: (remoteMessage) => {
232
+ const safe = redactValue(remoteMessage, this.secrets);
233
+ delivered.push(safe);
234
+ const invalid = canRecover && sessionInvalidResponse(safe, expectedIds);
235
+ if (invalid) {
236
+ suppressedSessionError = invalid;
237
+ return;
238
+ }
239
+ if (options._suppressCallbacks === true) return;
240
+ if (this.onMessage) this.onMessage(safe);
241
+ if (typeof options.onMessage === 'function') options.onMessage(safe);
242
+ },
243
+ });
244
+ if (suppressedSessionError) {
245
+ const remoteError = suppressedSessionError.error;
246
+ const error = new McpRpcError(
247
+ remoteError.message || `MCP error ${remoteError.code}`,
248
+ { code: remoteError.code, data: remoteError.data, id: suppressedSessionError.id },
249
+ );
250
+ error.rpc = suppressedSessionError;
251
+ throw error;
252
+ }
253
+ } catch (error) {
254
+ if (error && error.rpc) error.rpc = redactValue(error.rpc, this.secrets);
255
+ if (error && error.message) error.message = redactText(error.message, this.secrets);
256
+ if (canRecover && isSessionInvalidError(error)) {
257
+ await this.recoverSession(attemptedSessionId, attemptedGeneration, options);
258
+ return this.forward(message, { ...options, _sessionRecoveryAttempted: true });
259
+ }
260
+ throw error;
261
+ }
262
+
263
+ if (initialize) {
264
+ const response = findResponse(delivered, initialize.id);
265
+ const negotiated = response && response.result && response.result.protocolVersion;
266
+ if (negotiated) this.protocolVersion = negotiated;
267
+ }
268
+ return delivered;
269
+ }
270
+
271
+ async recoverSession(failedSessionId, failedGeneration, options = {}) {
272
+ if (this.sessionGeneration !== failedGeneration) return;
273
+ if (this.sessionId != null && this.sessionId !== failedSessionId) return;
274
+ if (this.sessionRecovery) return this.sessionRecovery;
275
+
276
+ const recoveryNumber = this.sessionRecoveryCount + 1;
277
+ this.sessionId = null;
278
+ const recovery = (async () => {
279
+ if (typeof options.onSessionRecovery === 'function') {
280
+ options.onSessionRecovery('started', { attempt: recoveryNumber });
281
+ }
282
+ try {
283
+ await this.initialize({
284
+ id: `draftgo-session-recovery-${recoveryNumber}`,
285
+ protocolVersion: this.protocolVersion,
286
+ timeoutMs: options.timeoutMs,
287
+ signal: options.signal,
288
+ _sessionRecoveryInternal: true,
289
+ _suppressCallbacks: true,
290
+ });
291
+ this.sessionRecoveryCount = recoveryNumber;
292
+ if (typeof options.onSessionRecovery === 'function') {
293
+ options.onSessionRecovery('succeeded', { attempt: recoveryNumber });
294
+ }
295
+ } catch (error) {
296
+ this.sessionId = null;
297
+ if (typeof options.onSessionRecovery === 'function') {
298
+ options.onSessionRecovery('failed', { attempt: recoveryNumber });
299
+ }
300
+ const message = redactText(error && error.message ? error.message : error, this.secrets);
301
+ const wrapped = new McpRpcError(`DraftGo MCP session recovery failed: ${message}`, {
302
+ code: error && error.code != null ? error.code : -32000,
303
+ data: error && error.data,
304
+ });
305
+ wrapped.status = Number(error && error.status) || 0;
306
+ wrapped.sessionRecovery = true;
307
+ wrapped.cause = error;
308
+ throw wrapped;
309
+ }
310
+ })();
311
+ this.sessionRecovery = recovery;
312
+ try {
313
+ await recovery;
314
+ } finally {
315
+ if (this.sessionRecovery === recovery) this.sessionRecovery = null;
316
+ }
317
+ }
318
+
319
+ async request(method, params, options = {}) {
320
+ const id = options.id == null ? this.nextId++ : options.id;
321
+ const message = { jsonrpc: '2.0', id, method };
322
+ if (params !== undefined) message.params = params;
323
+ const messages = await this.forward(message, options);
324
+ const response = findResponse(messages, id);
325
+ if (!response) {
326
+ throw new McpRpcError(`DraftGo MCP returned no response for ${method}.`, { id });
327
+ }
328
+ if (response.error) {
329
+ throw new McpRpcError(
330
+ redactText(response.error.message || `MCP error ${response.error.code}`, this.secrets),
331
+ {
332
+ code: response.error.code,
333
+ data: redactValue(response.error.data, this.secrets),
334
+ id,
335
+ },
336
+ );
337
+ }
338
+ return response.result;
339
+ }
340
+
341
+ async notify(method, params, options = {}) {
342
+ const message = { jsonrpc: '2.0', method };
343
+ if (params !== undefined) message.params = params;
344
+ await this.forward(message, options);
345
+ }
346
+
347
+ async initialize(options = {}) {
348
+ if (typeof options.onStage === 'function') options.onStage('initialize', 'started');
349
+ let result;
350
+ try {
351
+ result = await this.request('initialize', {
352
+ protocolVersion: options.protocolVersion || this.protocolVersion,
353
+ capabilities: options.capabilities || {},
354
+ clientInfo: options.clientInfo || {
355
+ name: 'draftgo-cli',
356
+ version: pkg.version,
357
+ },
358
+ }, options);
359
+ } catch (error) {
360
+ if (typeof options.onStage === 'function') options.onStage('initialize', 'failed');
361
+ throw error;
362
+ }
363
+ if (typeof options.onStage === 'function') options.onStage('initialize', 'succeeded', result);
364
+ if (result && result.protocolVersion) this.protocolVersion = result.protocolVersion;
365
+ try {
366
+ await this.notify('notifications/initialized', undefined, options);
367
+ } catch (error) {
368
+ if (typeof options.onStage === 'function') options.onStage('initialized', 'failed');
369
+ throw error;
370
+ }
371
+ if (typeof options.onStage === 'function') options.onStage('initialized', 'succeeded');
372
+ this.sessionGeneration += 1;
373
+ return result;
374
+ }
375
+
376
+ async toolsList(cursor, options = {}) {
377
+ const params = cursor ? { cursor } : {};
378
+ return this.request('tools/list', params, options);
379
+ }
380
+
381
+ async listAllTools(options = {}) {
382
+ const tools = [];
383
+ const seenCursors = new Set();
384
+ let cursor;
385
+ const maxPages = Number(options.maxPages || 100);
386
+ for (let page = 0; page < maxPages; page += 1) {
387
+ const result = await this.toolsList(cursor, options);
388
+ if (result && Array.isArray(result.tools)) tools.push(...result.tools);
389
+ const next = result && result.nextCursor;
390
+ if (!next) return tools;
391
+ if (seenCursors.has(next)) throw new McpRpcError('DraftGo MCP repeated a tools/list cursor.');
392
+ seenCursors.add(next);
393
+ cursor = next;
394
+ }
395
+ throw new McpRpcError(`DraftGo MCP tools/list exceeded ${maxPages} pages.`);
396
+ }
397
+
398
+ async toolsCall(name, args = {}, options = {}) {
399
+ const result = await this.request('tools/call', { name, arguments: args }, options);
400
+ if (result && result.isError === true) {
401
+ const structured = result.structuredContent || result.structured_content || {};
402
+ const text = Array.isArray(result.content)
403
+ ? result.content.find((part) => part && part.type === 'text' && typeof part.text === 'string')
404
+ : null;
405
+ const message = structured.message || structured.error && structured.error.message
406
+ || text && text.text || `DraftGo tool failed: ${name}`;
407
+ throw new McpRpcError(redactText(String(message).slice(0, 4096), this.secrets), {
408
+ code: structured.code || structured.error && structured.error.code || -32000,
409
+ data: redactValue(structured, this.secrets),
410
+ });
411
+ }
412
+ return result;
413
+ }
414
+
415
+ async testConnection(options = {}) {
416
+ const initialized = await this.initialize(options);
417
+ if (typeof options.onStage === 'function') options.onStage('tools/list', 'started');
418
+ let tools;
419
+ try {
420
+ tools = await this.listAllTools(options);
421
+ } catch (error) {
422
+ if (typeof options.onStage === 'function') options.onStage('tools/list', 'failed');
423
+ throw error;
424
+ }
425
+ if (typeof options.onStage === 'function') options.onStage('tools/list', 'succeeded', { count: tools.length });
426
+ const requiredTools = options.requiredTools || REQUIRED_DRAFTGO_TOOLS;
427
+ const missing = requiredTools.filter((expected) => !tools.some((tool) =>
428
+ tool && typeof tool.name === 'string' && toolMatches(tool.name, expected)));
429
+ if (missing.length) {
430
+ throw new McpRpcError(`DraftGo MCP is missing required tools: ${missing.join(', ')}.`, { code: -32601 });
431
+ }
432
+ const safeTools = options.safeTools || SAFE_TEST_TOOLS;
433
+ const hasDiagnosticOverrides = Boolean(options.requiredTools || options.safeTools);
434
+ const firstSafeTool = tools.find((tool) => tool && typeof tool.name === 'string'
435
+ && safeTools.some((canonical) => toolMatches(tool.name, canonical)));
436
+ if (!firstSafeTool) {
437
+ throw new McpRpcError(
438
+ `DraftGo MCP exposes no safe diagnostic tool (${safeTools.join(', ')}).`,
439
+ { code: -32601 },
440
+ );
441
+ }
442
+
443
+ const selected = safeTools.map((canonical) => ({
444
+ canonical,
445
+ tool: tools.find((candidate) => candidate && typeof candidate.name === 'string'
446
+ && toolMatches(candidate.name, canonical)),
447
+ })).filter((entry) => entry.tool);
448
+ const byCanonical = Object.fromEntries(selected.map((entry) => [entry.canonical, entry.tool]));
449
+ const overrideCanonical = safeTools.find((canonical) => toolMatches(firstSafeTool.name, canonical));
450
+ const defaultPlan = hasDiagnosticOverrides ? [{
451
+ label: overrideCanonical,
452
+ canonical: overrideCanonical,
453
+ tool: firstSafeTool,
454
+ args: diagnosticArguments(overrideCanonical),
455
+ }] : [
456
+ { label: 'api:catalog', canonical: 'draftgo_api_search', tool: byCanonical.draftgo_api_search, args: { query: CONNECTION_PROBE_OPERATION, limit: 100 } },
457
+ ];
458
+ const runPlan = (plan, startIndex = 0) => allWithAbort(plan.map((entry, offset) =>
459
+ async (queryOptions) => {
460
+ if (typeof options.onStage === 'function') options.onStage(`tools/call:${entry.label}`, 'started');
461
+ let result;
462
+ try {
463
+ result = await this.toolsCall(
464
+ entry.tool.name,
465
+ options.toolArguments && startIndex + offset === 0
466
+ ? options.toolArguments
467
+ : entry.args,
468
+ queryOptions,
469
+ );
470
+ } catch (error) {
471
+ if (typeof options.onStage === 'function') options.onStage(`tools/call:${entry.label}`, 'failed');
472
+ throw error;
473
+ }
474
+ if (typeof options.onStage === 'function') options.onStage(`tools/call:${entry.label}`, 'succeeded');
475
+ return ({
476
+ label: entry.label,
477
+ canonical: entry.canonical,
478
+ name: entry.tool.name,
479
+ arguments: options.toolArguments && startIndex + offset === 0
480
+ ? options.toolArguments
481
+ : entry.args,
482
+ result,
483
+ });
484
+ }), options);
485
+ const tested = await runPlan(defaultPlan);
486
+ if (!hasDiagnosticOverrides) {
487
+ const discovery = tested.find((entry) => entry.label === 'api:catalog');
488
+ const catalog = diagnosticData(discovery && discovery.result);
489
+ const operation = findConnectionProbeOperation(discovery && discovery.result);
490
+ if (!operation || !operation.operation_id) {
491
+ throw new McpRpcError(`DraftGo Registry returned no safe ${CONNECTION_PROBE_OPERATION} operation.`, {
492
+ code: -32601,
493
+ });
494
+ }
495
+ const operationID = String(operation.operation_id);
496
+ const revision = String(catalog.registry_revision || '').trim();
497
+ if (!revision) {
498
+ throw new McpRpcError('DraftGo api_search did not return registry_revision.');
499
+ }
500
+ const [described] = await runPlan([{
501
+ label: 'api:describe',
502
+ canonical: 'draftgo_api_describe',
503
+ tool: byCanonical.draftgo_api_describe,
504
+ args: { operation_id: operationID, registry_revision: revision },
505
+ }], tested.length);
506
+ tested.push(described);
507
+ assertSafeConnectionProbe(described.result);
508
+ tested.push(...await runPlan([{
509
+ label: 'api:invoke',
510
+ canonical: 'draftgo_api_call',
511
+ tool: byCanonical.draftgo_api_call,
512
+ args: { operation_id: operationID, registry_revision: revision },
513
+ }], tested.length));
514
+ }
515
+ return {
516
+ initialized,
517
+ protocolVersion: this.protocolVersion,
518
+ sessionId: this.sessionId,
519
+ sessionRecoveries: this.sessionRecoveryCount,
520
+ tools,
521
+ testedTool: tested[0].name,
522
+ toolResult: tested[0].result,
523
+ testedTools: [...new Set(tested.map((entry) => entry.name))],
524
+ testedCalls: tested.map((entry) => entry.label),
525
+ diagnosticResults: tested,
526
+ };
527
+ }
528
+ }
529
+
530
+ async function testConnection(config, options = {}) {
531
+ const client = new DraftGoMcpClient(config, options);
532
+ return client.testConnection(options);
533
+ }
534
+
535
+ async function callTool(config, name, args = {}, options = {}) {
536
+ const client = options.client || new DraftGoMcpClient(config, options);
537
+ if (!options.initialized) await client.initialize(options);
538
+ return client.toolsCall(name, args, options);
539
+ }
540
+
541
+ module.exports = {
542
+ DraftGoMcpClient,
543
+ McpClient: DraftGoMcpClient,
544
+ McpRpcError,
545
+ McpHttpError,
546
+ SAFE_TEST_TOOLS,
547
+ REQUIRED_DRAFTGO_TOOLS,
548
+ TOOL_ALIASES,
549
+ diagnosticArguments,
550
+ diagnosticData,
551
+ findConnectionProbeOperation,
552
+ assertSafeConnectionProbe,
553
+ isSessionInvalidError,
554
+ redactValue,
555
+ testConnection,
556
+ callTool,
557
+ toolMatches,
558
+ };