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.
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/bin/draftgo.js +9 -0
- package/package.json +70 -0
- package/resources/project-design/README.md +42 -0
- package/resources/skill/SKILL.md +62 -0
- package/resources/skill/init/SKILL.md +41 -0
- package/resources/skill/manifest.json +35 -0
- package/resources/skill/references/ai.md +41 -0
- package/resources/skill/references/app-api.md +97 -0
- package/resources/skill/references/architecture.md +13 -0
- package/resources/skill/references/chat-sdk.md +205 -0
- package/resources/skill/references/checkout.md +140 -0
- package/resources/skill/references/data.md +49 -0
- package/resources/skill/references/db-relations.md +29 -0
- package/resources/skill/references/delivery.md +33 -0
- package/resources/skill/references/development.md +41 -0
- package/resources/skill/references/diagnostics.md +50 -0
- package/resources/skill/references/frontend.md +158 -0
- package/resources/skill/references/mcp.md +110 -0
- package/resources/skill/references/methods.md +143 -0
- package/resources/skill/references/modules.md +75 -0
- package/resources/skill/references/runtime.md +109 -0
- package/resources/skill/references/services.md +32 -0
- package/src/apiContractCache.js +120 -0
- package/src/cli.js +100 -0
- package/src/commandRegistry.js +46 -0
- package/src/commands/api.js +244 -0
- package/src/commands/apiKey.js +30 -0
- package/src/commands/autoPush.js +36 -0
- package/src/commands/capabilities.js +100 -0
- package/src/commands/check.js +82 -0
- package/src/commands/checkout.js +18 -0
- package/src/commands/clean.js +72 -0
- package/src/commands/commit.js +47 -0
- package/src/commands/components.js +554 -0
- package/src/commands/conflict.js +30 -0
- package/src/commands/conflicts.js +16 -0
- package/src/commands/connect.js +91 -0
- package/src/commands/delete.js +95 -0
- package/src/commands/deploy.js +77 -0
- package/src/commands/diff.js +39 -0
- package/src/commands/group.js +37 -0
- package/src/commands/help.js +190 -0
- package/src/commands/init.js +126 -0
- package/src/commands/listTargets.js +13 -0
- package/src/commands/local.js +79 -0
- package/src/commands/map.js +395 -0
- package/src/commands/mcp.js +150 -0
- package/src/commands/reconcile.js +20 -0
- package/src/commands/role.js +31 -0
- package/src/commands/status.js +98 -0
- package/src/commands/uninstall.js +52 -0
- package/src/commands/update.js +79 -0
- package/src/commands/verify.js +188 -0
- package/src/commands/visualVerify.js +281 -0
- package/src/commands/worklog.js +117 -0
- package/src/consoleEncoding.js +34 -0
- package/src/contractCompatibility.js +65 -0
- package/src/detect.js +25 -0
- package/src/diffReport.js +106 -0
- package/src/fsx.js +67 -0
- package/src/index.js +46 -0
- package/src/localRuntime/compose.js +119 -0
- package/src/localRuntime/detect.js +77 -0
- package/src/localRuntime/index.js +211 -0
- package/src/localRuntime/mysqlClient.js +155 -0
- package/src/localRuntime/services.js +117 -0
- package/src/logger.js +37 -0
- package/src/mcp/client.js +558 -0
- package/src/mcp/hosts.js +520 -0
- package/src/mcp/parallel.js +54 -0
- package/src/mcp/protocol.js +223 -0
- package/src/mcp/stdio.js +300 -0
- package/src/mcp/tools.js +51 -0
- package/src/paths.js +32 -0
- package/src/platforms.js +110 -0
- package/src/projectConfig.js +139 -0
- package/src/projectDesign.js +19 -0
- package/src/projectHealth.js +33 -0
- package/src/projectMap.js +220 -0
- package/src/prompt.js +94 -0
- package/src/releaseInstall.js +105 -0
- package/src/runtimeFiles.js +45 -0
- package/src/skill.js +295 -0
- package/src/targets.js +43 -0
- package/src/timeout.js +18 -0
- package/src/updateCheck.js +100 -0
- package/src/worklog.js +276 -0
- package/src/worktree/backend.js +438 -0
- package/src/worktree/errors.js +28 -0
- package/src/worktree/index.js +751 -0
- package/src/worktree/inlineScripts.js +99 -0
- package/src/worktree/locks.js +52 -0
- package/src/worktree/manifest.js +89 -0
- package/src/worktree/status.js +124 -0
- package/src/worktree/streams.js +200 -0
- package/src/worktree/types.js +103 -0
- 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
|
+
};
|