@drumcode/runner 0.1.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.
- package/README.md +139 -0
- package/dist/chunk-3CUTJWRW.mjs +358 -0
- package/dist/chunk-4ITFFQXW.mjs +716 -0
- package/dist/chunk-5NEEOHT4.mjs +632 -0
- package/dist/chunk-6BC7SCK5.mjs +720 -0
- package/dist/chunk-E3F3IKO5.mjs +359 -0
- package/dist/chunk-JN5YMFB7.mjs +1061 -0
- package/dist/chunk-T7I5AI3A.mjs +558 -0
- package/dist/chunk-VDJVZU3Q.mjs +908 -0
- package/dist/chunk-VYYR5EI4.mjs +1061 -0
- package/dist/chunk-ZOZS5KN7.mjs +713 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1432 -0
- package/dist/cli.mjs +360 -0
- package/dist/index.d.mts +192 -0
- package/dist/index.d.ts +192 -0
- package/dist/index.js +1085 -0
- package/dist/index.mjs +8 -0
- package/package.json +56 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1432 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/cli.ts
|
|
27
|
+
var import_commander = require("commander");
|
|
28
|
+
var import_dotenv = require("dotenv");
|
|
29
|
+
|
|
30
|
+
// src/server.ts
|
|
31
|
+
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
32
|
+
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
33
|
+
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
34
|
+
|
|
35
|
+
// src/runner.ts
|
|
36
|
+
var import_core = require("@drumcode/core");
|
|
37
|
+
var DrumcodeRunner = class {
|
|
38
|
+
options;
|
|
39
|
+
toolCache = /* @__PURE__ */ new Map();
|
|
40
|
+
skillCache = /* @__PURE__ */ new Map();
|
|
41
|
+
manifestCache = null;
|
|
42
|
+
authContext = {
|
|
43
|
+
isAuthenticated: false,
|
|
44
|
+
projectId: null,
|
|
45
|
+
tokenId: null,
|
|
46
|
+
tokenName: null,
|
|
47
|
+
profileId: null
|
|
48
|
+
};
|
|
49
|
+
authInitialized = false;
|
|
50
|
+
constructor(options) {
|
|
51
|
+
this.options = options;
|
|
52
|
+
this.log("info", "Drumcode Runner initialized", {
|
|
53
|
+
registryUrl: options.registryUrl,
|
|
54
|
+
cacheEnabled: options.cacheEnabled
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Authentication
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
/**
|
|
61
|
+
* Initialize authentication by validating the token with the registry
|
|
62
|
+
* This should be called before processing any requests
|
|
63
|
+
*/
|
|
64
|
+
async initializeAuth() {
|
|
65
|
+
if (this.authInitialized) {
|
|
66
|
+
return this.authContext;
|
|
67
|
+
}
|
|
68
|
+
if (!this.options.token) {
|
|
69
|
+
this.log("info", "No token provided, running in demo mode");
|
|
70
|
+
this.authInitialized = true;
|
|
71
|
+
return this.authContext;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
this.log("debug", "Validating token with registry");
|
|
75
|
+
const response = await fetch(`${this.options.registryUrl}/v1/auth/token`, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: this.getAuthHeaders()
|
|
78
|
+
});
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
const errorData = await response.json().catch(() => ({}));
|
|
81
|
+
this.log("warn", "Token validation failed", {
|
|
82
|
+
status: response.status,
|
|
83
|
+
error: errorData.error?.message
|
|
84
|
+
});
|
|
85
|
+
this.authInitialized = true;
|
|
86
|
+
return this.authContext;
|
|
87
|
+
}
|
|
88
|
+
const data = await response.json();
|
|
89
|
+
if (data.valid && data.project && data.token) {
|
|
90
|
+
this.authContext = {
|
|
91
|
+
isAuthenticated: true,
|
|
92
|
+
projectId: data.project.id,
|
|
93
|
+
tokenId: data.token.id,
|
|
94
|
+
tokenName: data.token.name,
|
|
95
|
+
profileId: data.token.profileId
|
|
96
|
+
};
|
|
97
|
+
this.log("info", "Token validated successfully", {
|
|
98
|
+
projectId: this.authContext.projectId,
|
|
99
|
+
tokenName: this.authContext.tokenName,
|
|
100
|
+
hasProfile: !!this.authContext.profileId
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
} catch (error) {
|
|
104
|
+
this.log("error", "Token validation error", {
|
|
105
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
this.authInitialized = true;
|
|
109
|
+
return this.authContext;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get the current authentication context
|
|
113
|
+
*/
|
|
114
|
+
getAuthContext() {
|
|
115
|
+
return this.authContext;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Check if the runner is authenticated
|
|
119
|
+
*/
|
|
120
|
+
isAuthenticated() {
|
|
121
|
+
return this.authContext.isAuthenticated;
|
|
122
|
+
}
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// Tool Discovery
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
/**
|
|
127
|
+
* Get the list of tools to expose to the client
|
|
128
|
+
* Uses Polyfill strategy for non-Anthropic clients
|
|
129
|
+
*/
|
|
130
|
+
async getToolList(capabilities) {
|
|
131
|
+
if (capabilities.supportsAdvancedToolUse && capabilities.supportsDeferredLoading) {
|
|
132
|
+
this.log("debug", "Using Strategy A: Full manifest with deferred loading");
|
|
133
|
+
return this.getFullManifest();
|
|
134
|
+
}
|
|
135
|
+
this.log("debug", "Using Strategy B: Polyfill mode with core toolset");
|
|
136
|
+
return import_core.CORE_TOOLSET;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Fetch the full tool manifest from the Registry
|
|
140
|
+
*/
|
|
141
|
+
async getFullManifest() {
|
|
142
|
+
if (this.manifestCache && this.options.cacheEnabled) {
|
|
143
|
+
return this.manifestCache;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const response = await fetch(`${this.options.registryUrl}/v1/manifest`, {
|
|
147
|
+
headers: this.getAuthHeaders()
|
|
148
|
+
});
|
|
149
|
+
if (!response.ok) {
|
|
150
|
+
throw new Error(`Registry returned ${response.status}`);
|
|
151
|
+
}
|
|
152
|
+
const data = await response.json();
|
|
153
|
+
this.manifestCache = data.tools;
|
|
154
|
+
return this.manifestCache;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
this.log("warn", "Failed to fetch manifest, using cached or fallback", { error });
|
|
157
|
+
return this.manifestCache || import_core.CORE_TOOLSET;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// Tool Execution
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
/**
|
|
164
|
+
* Execute a tool call
|
|
165
|
+
*/
|
|
166
|
+
async executeTool(request) {
|
|
167
|
+
const startTime = Date.now();
|
|
168
|
+
try {
|
|
169
|
+
if (request.name === "drumcode_search_tools") {
|
|
170
|
+
return this.handleSearchTools(request.arguments);
|
|
171
|
+
}
|
|
172
|
+
if (request.name === "drumcode_get_tool_schema") {
|
|
173
|
+
return this.handleGetToolSchema(request.arguments);
|
|
174
|
+
}
|
|
175
|
+
return this.executeRegularTool(request);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
const latency = Date.now() - startTime;
|
|
178
|
+
this.log("error", "Tool execution failed", {
|
|
179
|
+
tool: request.name,
|
|
180
|
+
error,
|
|
181
|
+
latencyMs: latency
|
|
182
|
+
});
|
|
183
|
+
return {
|
|
184
|
+
content: [{
|
|
185
|
+
type: "text",
|
|
186
|
+
text: `Error executing tool: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
187
|
+
}],
|
|
188
|
+
isError: true
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Handle drumcode_search_tools meta-tool
|
|
194
|
+
*/
|
|
195
|
+
async handleSearchTools(args) {
|
|
196
|
+
this.log("debug", "Searching tools", { query: args.query });
|
|
197
|
+
try {
|
|
198
|
+
const response = await fetch(`${this.options.registryUrl}/v1/tools/search`, {
|
|
199
|
+
method: "POST",
|
|
200
|
+
headers: {
|
|
201
|
+
...this.getAuthHeaders(),
|
|
202
|
+
"Content-Type": "application/json"
|
|
203
|
+
},
|
|
204
|
+
body: JSON.stringify({
|
|
205
|
+
query: args.query,
|
|
206
|
+
project_token: this.options.token,
|
|
207
|
+
limit: 5
|
|
208
|
+
})
|
|
209
|
+
});
|
|
210
|
+
if (!response.ok) {
|
|
211
|
+
throw new Error(`Search failed with status ${response.status}`);
|
|
212
|
+
}
|
|
213
|
+
const data = await response.json();
|
|
214
|
+
const resultText = data.matches.length > 0 ? data.matches.map(
|
|
215
|
+
(m, i) => `${i + 1}. **${m.name}** (relevance: ${(m.relevance * 100).toFixed(0)}%)
|
|
216
|
+
${m.description}`
|
|
217
|
+
).join("\n\n") : "No matching tools found. Try a different search query.";
|
|
218
|
+
return {
|
|
219
|
+
content: [{
|
|
220
|
+
type: "text",
|
|
221
|
+
text: `Found ${data.matches.length} matching tools:
|
|
222
|
+
|
|
223
|
+
${resultText}
|
|
224
|
+
|
|
225
|
+
Use \`drumcode_get_tool_schema\` to get detailed arguments for any of these tools.`
|
|
226
|
+
}]
|
|
227
|
+
};
|
|
228
|
+
} catch (error) {
|
|
229
|
+
this.log("error", "Search failed", { error });
|
|
230
|
+
return {
|
|
231
|
+
content: [{
|
|
232
|
+
type: "text",
|
|
233
|
+
text: `Search failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
234
|
+
}],
|
|
235
|
+
isError: true
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Handle drumcode_get_tool_schema meta-tool
|
|
241
|
+
*/
|
|
242
|
+
async handleGetToolSchema(args) {
|
|
243
|
+
this.log("debug", "Fetching tool schema", { toolName: args.tool_name });
|
|
244
|
+
if (this.toolCache.has(args.tool_name)) {
|
|
245
|
+
const cached = this.toolCache.get(args.tool_name);
|
|
246
|
+
return {
|
|
247
|
+
content: [{
|
|
248
|
+
type: "text",
|
|
249
|
+
text: this.formatToolSchema(cached)
|
|
250
|
+
}]
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
const response = await fetch(
|
|
255
|
+
`${this.options.registryUrl}/v1/tools/${encodeURIComponent(args.tool_name)}`,
|
|
256
|
+
{ headers: this.getAuthHeaders() }
|
|
257
|
+
);
|
|
258
|
+
if (!response.ok) {
|
|
259
|
+
if (response.status === 404) {
|
|
260
|
+
return {
|
|
261
|
+
content: [{
|
|
262
|
+
type: "text",
|
|
263
|
+
text: `Tool "${args.tool_name}" not found. Use drumcode_search_tools to find available tools.`
|
|
264
|
+
}],
|
|
265
|
+
isError: true
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
throw new Error(`Failed to fetch schema: ${response.status}`);
|
|
269
|
+
}
|
|
270
|
+
const schema = await response.json();
|
|
271
|
+
const tool = {
|
|
272
|
+
name: schema.name,
|
|
273
|
+
description: schema.description,
|
|
274
|
+
input_schema: schema.input_schema
|
|
275
|
+
};
|
|
276
|
+
this.toolCache.set(args.tool_name, tool);
|
|
277
|
+
return {
|
|
278
|
+
content: [{
|
|
279
|
+
type: "text",
|
|
280
|
+
text: this.formatToolSchema(tool)
|
|
281
|
+
}]
|
|
282
|
+
};
|
|
283
|
+
} catch (error) {
|
|
284
|
+
this.log("error", "Schema fetch failed", { error });
|
|
285
|
+
return {
|
|
286
|
+
content: [{
|
|
287
|
+
type: "text",
|
|
288
|
+
text: `Failed to fetch schema: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
289
|
+
}],
|
|
290
|
+
isError: true
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
async fetchRegistryToolSchema(name) {
|
|
295
|
+
try {
|
|
296
|
+
const response = await fetch(
|
|
297
|
+
`${this.options.registryUrl}/v1/tools/${encodeURIComponent(name)}`,
|
|
298
|
+
{ headers: this.getAuthHeaders() }
|
|
299
|
+
);
|
|
300
|
+
if (!response.ok) {
|
|
301
|
+
if (response.status === 404) {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
this.log("warn", "Registry tool fetch failed", { name, status: response.status });
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
return await response.json();
|
|
308
|
+
} catch (error) {
|
|
309
|
+
this.log("error", "Registry tool fetch error", { name, error });
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Execute a regular (non-meta) tool
|
|
315
|
+
*/
|
|
316
|
+
async executeRegularTool(request) {
|
|
317
|
+
if (request.name === "drumcode_echo") {
|
|
318
|
+
return {
|
|
319
|
+
content: [{
|
|
320
|
+
type: "text",
|
|
321
|
+
text: JSON.stringify(request.arguments, null, 2)
|
|
322
|
+
}]
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
if (request.name === "drumcode_http_get") {
|
|
326
|
+
const url = request.arguments.url;
|
|
327
|
+
try {
|
|
328
|
+
const response = await fetch(url);
|
|
329
|
+
const text = await response.text();
|
|
330
|
+
return {
|
|
331
|
+
content: [{
|
|
332
|
+
type: "text",
|
|
333
|
+
text: `Status: ${response.status}
|
|
334
|
+
|
|
335
|
+
${text.slice(0, 5e3)}${text.length > 5e3 ? "...(truncated)" : ""}`
|
|
336
|
+
}]
|
|
337
|
+
};
|
|
338
|
+
} catch (error) {
|
|
339
|
+
return {
|
|
340
|
+
content: [{
|
|
341
|
+
type: "text",
|
|
342
|
+
text: `HTTP request failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
343
|
+
}],
|
|
344
|
+
isError: true
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (request.name.startsWith("arxiv_")) {
|
|
349
|
+
return this.executeArxivTool(request);
|
|
350
|
+
}
|
|
351
|
+
const skill = await this.fetchSkill(request.name);
|
|
352
|
+
if (skill) {
|
|
353
|
+
return this.executeSkill(skill, request.arguments);
|
|
354
|
+
}
|
|
355
|
+
const registryResult = await this.executeRegistryTool(request);
|
|
356
|
+
if (registryResult) {
|
|
357
|
+
return registryResult;
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
content: [{
|
|
361
|
+
type: "text",
|
|
362
|
+
text: `Tool "${request.name}" is not yet implemented. This tool needs to be fetched and executed via the Registry.`
|
|
363
|
+
}],
|
|
364
|
+
isError: true
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
async executeRegistryTool(request) {
|
|
368
|
+
const schema = await this.fetchRegistryToolSchema(request.name);
|
|
369
|
+
if (!schema) {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
if (!schema.http_method || !schema.http_path) {
|
|
373
|
+
return {
|
|
374
|
+
content: [{
|
|
375
|
+
type: "text",
|
|
376
|
+
text: `Tool "${request.name}" is missing http_method/http_path in the registry.`
|
|
377
|
+
}],
|
|
378
|
+
isError: true
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
const integrationName = schema.integration?.name;
|
|
382
|
+
const baseUrl = this.getIntegrationBaseUrl(integrationName);
|
|
383
|
+
if (!baseUrl) {
|
|
384
|
+
return {
|
|
385
|
+
content: [{
|
|
386
|
+
type: "text",
|
|
387
|
+
text: `Missing base URL for integration "${integrationName || "unknown"}". Set DRUMCODE_INTEGRATION_BASE_URLS for this integration.`
|
|
388
|
+
}],
|
|
389
|
+
isError: true
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
const method = schema.http_method.toUpperCase();
|
|
393
|
+
let requestUrl;
|
|
394
|
+
let body;
|
|
395
|
+
try {
|
|
396
|
+
({ url: requestUrl, body } = this.buildToolRequest(
|
|
397
|
+
baseUrl,
|
|
398
|
+
schema.http_path,
|
|
399
|
+
request.arguments,
|
|
400
|
+
method
|
|
401
|
+
));
|
|
402
|
+
} catch (error) {
|
|
403
|
+
return {
|
|
404
|
+
content: [{
|
|
405
|
+
type: "text",
|
|
406
|
+
text: `Failed to build request for "${request.name}": ${error instanceof Error ? error.message : "Unknown error"}`
|
|
407
|
+
}],
|
|
408
|
+
isError: true
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
const headers = {
|
|
412
|
+
...this.getIntegrationHeaders(integrationName)
|
|
413
|
+
};
|
|
414
|
+
if (body && !headers["Content-Type"]) {
|
|
415
|
+
headers["Content-Type"] = "application/json";
|
|
416
|
+
}
|
|
417
|
+
try {
|
|
418
|
+
const response = await fetch(requestUrl, {
|
|
419
|
+
method,
|
|
420
|
+
headers,
|
|
421
|
+
body: body ? JSON.stringify(body) : void 0
|
|
422
|
+
});
|
|
423
|
+
const text = await response.text();
|
|
424
|
+
const truncated = text.length > 5e3 ? "...(truncated)" : "";
|
|
425
|
+
return {
|
|
426
|
+
content: [{
|
|
427
|
+
type: "text",
|
|
428
|
+
text: `Status: ${response.status}
|
|
429
|
+
|
|
430
|
+
${text.slice(0, 5e3)}${truncated}`
|
|
431
|
+
}],
|
|
432
|
+
isError: !response.ok
|
|
433
|
+
};
|
|
434
|
+
} catch (error) {
|
|
435
|
+
return {
|
|
436
|
+
content: [{
|
|
437
|
+
type: "text",
|
|
438
|
+
text: `HTTP request failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
439
|
+
}],
|
|
440
|
+
isError: true
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
// ---------------------------------------------------------------------------
|
|
445
|
+
// Skill Execution
|
|
446
|
+
// ---------------------------------------------------------------------------
|
|
447
|
+
/**
|
|
448
|
+
* Fetch skill definition from registry
|
|
449
|
+
*/
|
|
450
|
+
async fetchSkill(name) {
|
|
451
|
+
if (this.skillCache.has(name)) {
|
|
452
|
+
return this.skillCache.get(name);
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
const response = await fetch(
|
|
456
|
+
`${this.options.registryUrl}/v1/skills/${encodeURIComponent(name)}`,
|
|
457
|
+
{ headers: this.getAuthHeaders() }
|
|
458
|
+
);
|
|
459
|
+
if (!response.ok) {
|
|
460
|
+
if (response.status === 404) {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
this.log("warn", "Skill fetch failed", { name, status: response.status });
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
const skill = await response.json();
|
|
467
|
+
this.skillCache.set(name, skill);
|
|
468
|
+
this.log("debug", "Fetched skill", { name, type: skill.skill_type });
|
|
469
|
+
return skill;
|
|
470
|
+
} catch (error) {
|
|
471
|
+
this.log("error", "Skill fetch error", { name, error });
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Execute a skill (validator or workflow)
|
|
477
|
+
*/
|
|
478
|
+
async executeSkill(skill, input) {
|
|
479
|
+
this.log("info", "Executing skill", { name: skill.name, type: skill.skill_type });
|
|
480
|
+
const steps = skill.logic_layer.steps;
|
|
481
|
+
const stepResults = {};
|
|
482
|
+
const context = { input, steps: stepResults };
|
|
483
|
+
try {
|
|
484
|
+
for (let i = 0; i < steps.length; i++) {
|
|
485
|
+
const step = steps[i];
|
|
486
|
+
if (step.action === "check_policy") {
|
|
487
|
+
const policyStep = step;
|
|
488
|
+
const passed = this.evaluateRule(policyStep.rule, context);
|
|
489
|
+
if (!passed) {
|
|
490
|
+
this.log("warn", "Policy check failed", {
|
|
491
|
+
skill: skill.name,
|
|
492
|
+
step: i,
|
|
493
|
+
rule: policyStep.rule
|
|
494
|
+
});
|
|
495
|
+
return {
|
|
496
|
+
content: [{
|
|
497
|
+
type: "text",
|
|
498
|
+
text: `Validation failed: ${policyStep.error_msg}`
|
|
499
|
+
}],
|
|
500
|
+
isError: true
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
this.log("debug", "Policy check passed", { step: i });
|
|
504
|
+
} else if (step.action === "execute_atomic") {
|
|
505
|
+
const execStep = step;
|
|
506
|
+
const toolArgs = {};
|
|
507
|
+
if (execStep.args_map) {
|
|
508
|
+
for (const [argName, mapping] of Object.entries(execStep.args_map)) {
|
|
509
|
+
toolArgs[argName] = this.evaluateMapping(mapping, context);
|
|
510
|
+
}
|
|
511
|
+
} else {
|
|
512
|
+
Object.assign(toolArgs, input);
|
|
513
|
+
}
|
|
514
|
+
this.log("debug", "Executing atomic tool", {
|
|
515
|
+
tool: execStep.tool,
|
|
516
|
+
args: Object.keys(toolArgs)
|
|
517
|
+
});
|
|
518
|
+
const result = await this.executeTool({
|
|
519
|
+
name: execStep.tool,
|
|
520
|
+
arguments: toolArgs
|
|
521
|
+
});
|
|
522
|
+
if (execStep.output_key) {
|
|
523
|
+
stepResults[execStep.output_key] = this.parseToolResult(result);
|
|
524
|
+
}
|
|
525
|
+
if (i === steps.length - 1 && !skill.logic_layer.output) {
|
|
526
|
+
return result;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (skill.logic_layer.output) {
|
|
531
|
+
const output = {};
|
|
532
|
+
for (const [key, mapping] of Object.entries(skill.logic_layer.output)) {
|
|
533
|
+
output[key] = this.evaluateMapping(mapping, context);
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
content: [{
|
|
537
|
+
type: "text",
|
|
538
|
+
text: JSON.stringify(output, null, 2)
|
|
539
|
+
}]
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
return {
|
|
543
|
+
content: [{
|
|
544
|
+
type: "text",
|
|
545
|
+
text: `Skill "${skill.name}" completed successfully.`
|
|
546
|
+
}]
|
|
547
|
+
};
|
|
548
|
+
} catch (error) {
|
|
549
|
+
this.log("error", "Skill execution failed", { skill: skill.name, error });
|
|
550
|
+
return {
|
|
551
|
+
content: [{
|
|
552
|
+
type: "text",
|
|
553
|
+
text: `Skill execution failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
554
|
+
}],
|
|
555
|
+
isError: true
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Evaluate a policy rule against context
|
|
561
|
+
* Simple expression evaluator for rules like "input.query && input.query.length >= 2"
|
|
562
|
+
*/
|
|
563
|
+
evaluateRule(rule, context) {
|
|
564
|
+
try {
|
|
565
|
+
const { input, steps } = context;
|
|
566
|
+
const evalFn = new Function("input", "steps", `
|
|
567
|
+
try {
|
|
568
|
+
return Boolean(${rule});
|
|
569
|
+
} catch (e) {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
`);
|
|
573
|
+
return evalFn(input, steps);
|
|
574
|
+
} catch (error) {
|
|
575
|
+
this.log("error", "Rule evaluation failed", { rule, error });
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Evaluate an argument mapping expression
|
|
581
|
+
* Supports expressions like "input.query", "input.max_results || 5", "steps.search_results.papers[0].id"
|
|
582
|
+
*/
|
|
583
|
+
evaluateMapping(mapping, context) {
|
|
584
|
+
try {
|
|
585
|
+
const { input, steps } = context;
|
|
586
|
+
const evalFn = new Function("input", "steps", `
|
|
587
|
+
try {
|
|
588
|
+
return ${mapping};
|
|
589
|
+
} catch (e) {
|
|
590
|
+
return undefined;
|
|
591
|
+
}
|
|
592
|
+
`);
|
|
593
|
+
return evalFn(input, steps);
|
|
594
|
+
} catch (error) {
|
|
595
|
+
this.log("error", "Mapping evaluation failed", { mapping, error });
|
|
596
|
+
return void 0;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Parse tool result content to extract structured data
|
|
601
|
+
*/
|
|
602
|
+
parseToolResult(result) {
|
|
603
|
+
if (result.isError) {
|
|
604
|
+
return { error: true, message: result.content[0]?.text };
|
|
605
|
+
}
|
|
606
|
+
const text = result.content[0]?.text || "";
|
|
607
|
+
try {
|
|
608
|
+
return JSON.parse(text);
|
|
609
|
+
} catch {
|
|
610
|
+
if (text.includes("Found") && text.includes("papers")) {
|
|
611
|
+
const papers = [];
|
|
612
|
+
const paperMatches = text.matchAll(/ID: ([^\n]+)[\s\S]*?\*\*([^*]+)\*\*/g);
|
|
613
|
+
for (const match of paperMatches) {
|
|
614
|
+
papers.push({ id: match[1].trim(), title: match[2].trim() });
|
|
615
|
+
}
|
|
616
|
+
const altMatches = text.matchAll(/\*\*([^*]+)\*\*[^(]*\(([^)]+)\)/g);
|
|
617
|
+
for (const match of altMatches) {
|
|
618
|
+
if (!papers.find((p) => p.id === match[2].trim())) {
|
|
619
|
+
papers.push({ id: match[2].trim(), title: match[1].trim() });
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return { papers, raw: text };
|
|
623
|
+
}
|
|
624
|
+
return { raw: text };
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Execute arXiv tools via the arXiv API
|
|
629
|
+
*/
|
|
630
|
+
async executeArxivTool(request) {
|
|
631
|
+
const baseUrl = "http://export.arxiv.org/api/query";
|
|
632
|
+
try {
|
|
633
|
+
switch (request.name) {
|
|
634
|
+
case "arxiv_search_papers": {
|
|
635
|
+
const { query, max_results = 10, categories } = request.arguments;
|
|
636
|
+
let searchQuery = query;
|
|
637
|
+
if (categories && categories.length > 0) {
|
|
638
|
+
const catFilter = categories.map((c) => `cat:${c}`).join(" OR ");
|
|
639
|
+
searchQuery = `(${query}) AND (${catFilter})`;
|
|
640
|
+
}
|
|
641
|
+
const params = new URLSearchParams({
|
|
642
|
+
search_query: `all:${searchQuery}`,
|
|
643
|
+
start: "0",
|
|
644
|
+
max_results: String(Math.min(max_results, 20)),
|
|
645
|
+
sortBy: "submittedDate",
|
|
646
|
+
sortOrder: "descending"
|
|
647
|
+
});
|
|
648
|
+
const response = await fetch(`${baseUrl}?${params}`);
|
|
649
|
+
const xml = await response.text();
|
|
650
|
+
const papers = this.parseArxivResponse(xml);
|
|
651
|
+
return {
|
|
652
|
+
content: [{
|
|
653
|
+
type: "text",
|
|
654
|
+
text: `Found ${papers.length} papers:
|
|
655
|
+
|
|
656
|
+
${papers.map(
|
|
657
|
+
(p, i) => `${i + 1}. **${p.title}**
|
|
658
|
+
ID: ${p.id}
|
|
659
|
+
Authors: ${p.authors}
|
|
660
|
+
Published: ${p.published}
|
|
661
|
+
${p.summary.slice(0, 200)}...`
|
|
662
|
+
).join("\n\n")}`
|
|
663
|
+
}]
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
case "arxiv_get_paper":
|
|
667
|
+
case "arxiv_get_abstract": {
|
|
668
|
+
const { paper_id } = request.arguments;
|
|
669
|
+
const params = new URLSearchParams({ id_list: paper_id });
|
|
670
|
+
const response = await fetch(`${baseUrl}?${params}`);
|
|
671
|
+
const xml = await response.text();
|
|
672
|
+
const papers = this.parseArxivResponse(xml);
|
|
673
|
+
if (papers.length === 0) {
|
|
674
|
+
return {
|
|
675
|
+
content: [{ type: "text", text: `Paper ${paper_id} not found.` }],
|
|
676
|
+
isError: true
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
const p = papers[0];
|
|
680
|
+
return {
|
|
681
|
+
content: [{
|
|
682
|
+
type: "text",
|
|
683
|
+
text: `**${p.title}**
|
|
684
|
+
|
|
685
|
+
ID: ${p.id}
|
|
686
|
+
Authors: ${p.authors}
|
|
687
|
+
Published: ${p.published}
|
|
688
|
+
Categories: ${p.categories}
|
|
689
|
+
PDF: ${p.pdf_url}
|
|
690
|
+
|
|
691
|
+
**Abstract:**
|
|
692
|
+
${p.summary}`
|
|
693
|
+
}]
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
case "arxiv_search_by_author": {
|
|
697
|
+
const { author, max_results = 10 } = request.arguments;
|
|
698
|
+
const params = new URLSearchParams({
|
|
699
|
+
search_query: `au:${author}`,
|
|
700
|
+
start: "0",
|
|
701
|
+
max_results: String(Math.min(max_results, 20)),
|
|
702
|
+
sortBy: "submittedDate",
|
|
703
|
+
sortOrder: "descending"
|
|
704
|
+
});
|
|
705
|
+
const response = await fetch(`${baseUrl}?${params}`);
|
|
706
|
+
const xml = await response.text();
|
|
707
|
+
const papers = this.parseArxivResponse(xml);
|
|
708
|
+
return {
|
|
709
|
+
content: [{
|
|
710
|
+
type: "text",
|
|
711
|
+
text: `Found ${papers.length} papers by "${author}":
|
|
712
|
+
|
|
713
|
+
${papers.map(
|
|
714
|
+
(p, i) => `${i + 1}. **${p.title}** (${p.id})
|
|
715
|
+
${p.published}`
|
|
716
|
+
).join("\n\n")}`
|
|
717
|
+
}]
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
case "arxiv_search_by_category": {
|
|
721
|
+
const { category, max_results = 10 } = request.arguments;
|
|
722
|
+
const params = new URLSearchParams({
|
|
723
|
+
search_query: `cat:${category}`,
|
|
724
|
+
start: "0",
|
|
725
|
+
max_results: String(Math.min(max_results, 20)),
|
|
726
|
+
sortBy: "submittedDate",
|
|
727
|
+
sortOrder: "descending"
|
|
728
|
+
});
|
|
729
|
+
const response = await fetch(`${baseUrl}?${params}`);
|
|
730
|
+
const xml = await response.text();
|
|
731
|
+
const papers = this.parseArxivResponse(xml);
|
|
732
|
+
return {
|
|
733
|
+
content: [{
|
|
734
|
+
type: "text",
|
|
735
|
+
text: `Found ${papers.length} recent papers in ${category}:
|
|
736
|
+
|
|
737
|
+
${papers.map(
|
|
738
|
+
(p, i) => `${i + 1}. **${p.title}** (${p.id})
|
|
739
|
+
Authors: ${p.authors}
|
|
740
|
+
${p.published}`
|
|
741
|
+
).join("\n\n")}`
|
|
742
|
+
}]
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
default:
|
|
746
|
+
return {
|
|
747
|
+
content: [{
|
|
748
|
+
type: "text",
|
|
749
|
+
text: `arXiv tool "${request.name}" is not yet implemented.`
|
|
750
|
+
}],
|
|
751
|
+
isError: true
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
} catch (error) {
|
|
755
|
+
this.log("error", "arXiv API call failed", { error });
|
|
756
|
+
return {
|
|
757
|
+
content: [{
|
|
758
|
+
type: "text",
|
|
759
|
+
text: `arXiv API error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
760
|
+
}],
|
|
761
|
+
isError: true
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Parse arXiv Atom XML response
|
|
767
|
+
*/
|
|
768
|
+
parseArxivResponse(xml) {
|
|
769
|
+
const papers = [];
|
|
770
|
+
const entryRegex = /<entry>([\s\S]*?)<\/entry>/g;
|
|
771
|
+
let match;
|
|
772
|
+
while ((match = entryRegex.exec(xml)) !== null) {
|
|
773
|
+
const entry = match[1];
|
|
774
|
+
const getId = (s) => {
|
|
775
|
+
const m = s.match(/<id>(.*?)<\/id>/);
|
|
776
|
+
return m ? m[1].replace("http://arxiv.org/abs/", "") : "";
|
|
777
|
+
};
|
|
778
|
+
const getTitle = (s) => {
|
|
779
|
+
const m = s.match(/<title>([\s\S]*?)<\/title>/);
|
|
780
|
+
return m ? m[1].replace(/\s+/g, " ").trim() : "";
|
|
781
|
+
};
|
|
782
|
+
const getSummary = (s) => {
|
|
783
|
+
const m = s.match(/<summary>([\s\S]*?)<\/summary>/);
|
|
784
|
+
return m ? m[1].replace(/\s+/g, " ").trim() : "";
|
|
785
|
+
};
|
|
786
|
+
const getAuthors = (s) => {
|
|
787
|
+
const authors = [];
|
|
788
|
+
const authorRegex = /<author>\s*<name>(.*?)<\/name>/g;
|
|
789
|
+
let authorMatch;
|
|
790
|
+
while ((authorMatch = authorRegex.exec(s)) !== null) {
|
|
791
|
+
authors.push(authorMatch[1]);
|
|
792
|
+
}
|
|
793
|
+
return authors.join(", ");
|
|
794
|
+
};
|
|
795
|
+
const getPublished = (s) => {
|
|
796
|
+
const m = s.match(/<published>(.*?)<\/published>/);
|
|
797
|
+
return m ? m[1].split("T")[0] : "";
|
|
798
|
+
};
|
|
799
|
+
const getCategories = (s) => {
|
|
800
|
+
const cats = [];
|
|
801
|
+
const catRegex = /<category[^>]*term="([^"]+)"/g;
|
|
802
|
+
let catMatch;
|
|
803
|
+
while ((catMatch = catRegex.exec(s)) !== null) {
|
|
804
|
+
cats.push(catMatch[1]);
|
|
805
|
+
}
|
|
806
|
+
return cats.join(", ");
|
|
807
|
+
};
|
|
808
|
+
const getPdfUrl = (s) => {
|
|
809
|
+
const m = s.match(/<link[^>]*title="pdf"[^>]*href="([^"]+)"/);
|
|
810
|
+
return m ? m[1] : "";
|
|
811
|
+
};
|
|
812
|
+
papers.push({
|
|
813
|
+
id: getId(entry),
|
|
814
|
+
title: getTitle(entry),
|
|
815
|
+
summary: getSummary(entry),
|
|
816
|
+
authors: getAuthors(entry),
|
|
817
|
+
published: getPublished(entry),
|
|
818
|
+
categories: getCategories(entry),
|
|
819
|
+
pdf_url: getPdfUrl(entry)
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
return papers;
|
|
823
|
+
}
|
|
824
|
+
// ---------------------------------------------------------------------------
|
|
825
|
+
// Helpers
|
|
826
|
+
// ---------------------------------------------------------------------------
|
|
827
|
+
getIntegrationBaseUrl(integrationName) {
|
|
828
|
+
if (!integrationName) {
|
|
829
|
+
return this.options.integrationBaseUrls?.default || null;
|
|
830
|
+
}
|
|
831
|
+
const direct = this.options.integrationBaseUrls?.[integrationName] || this.options.integrationBaseUrls?.[integrationName.toLowerCase()];
|
|
832
|
+
return direct || this.options.integrationBaseUrls?.default || null;
|
|
833
|
+
}
|
|
834
|
+
getIntegrationHeaders(integrationName) {
|
|
835
|
+
if (!integrationName) {
|
|
836
|
+
return this.options.integrationHeaders?.default || {};
|
|
837
|
+
}
|
|
838
|
+
return this.options.integrationHeaders?.[integrationName] || this.options.integrationHeaders?.[integrationName.toLowerCase()] || this.options.integrationHeaders?.default || {};
|
|
839
|
+
}
|
|
840
|
+
buildToolRequest(baseUrl, path, args, method) {
|
|
841
|
+
const normalizedBase = baseUrl.replace(/\/+$/, "");
|
|
842
|
+
let resolvedPath = path;
|
|
843
|
+
const remainingArgs = { ...args };
|
|
844
|
+
const pathParams = [...path.matchAll(/{([^}]+)}/g)].map((match) => match[1]);
|
|
845
|
+
for (const param of pathParams) {
|
|
846
|
+
const value = remainingArgs[param];
|
|
847
|
+
if (value === void 0 || value === null) {
|
|
848
|
+
throw new Error(`Missing required path param "${param}"`);
|
|
849
|
+
}
|
|
850
|
+
resolvedPath = resolvedPath.replace(`{${param}}`, encodeURIComponent(String(value)));
|
|
851
|
+
delete remainingArgs[param];
|
|
852
|
+
}
|
|
853
|
+
const normalizedPath = resolvedPath.startsWith("/") ? resolvedPath : `/${resolvedPath}`;
|
|
854
|
+
let url = `${normalizedBase}${normalizedPath}`;
|
|
855
|
+
if (method === "GET" || method === "DELETE") {
|
|
856
|
+
const params = new URLSearchParams();
|
|
857
|
+
for (const [key, value] of Object.entries(remainingArgs)) {
|
|
858
|
+
if (value === void 0 || value === null) continue;
|
|
859
|
+
if (Array.isArray(value)) {
|
|
860
|
+
for (const item of value) {
|
|
861
|
+
params.append(key, String(item));
|
|
862
|
+
}
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
if (typeof value === "object") {
|
|
866
|
+
params.append(key, JSON.stringify(value));
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
params.append(key, String(value));
|
|
870
|
+
}
|
|
871
|
+
const query = params.toString();
|
|
872
|
+
if (query) {
|
|
873
|
+
url = `${url}?${query}`;
|
|
874
|
+
}
|
|
875
|
+
return { url, body: null };
|
|
876
|
+
}
|
|
877
|
+
return { url, body: remainingArgs };
|
|
878
|
+
}
|
|
879
|
+
getAuthHeaders() {
|
|
880
|
+
const headers = {
|
|
881
|
+
"User-Agent": "drumcode-runner/0.1.0"
|
|
882
|
+
};
|
|
883
|
+
if (this.options.token) {
|
|
884
|
+
headers["Authorization"] = `Bearer ${this.options.token}`;
|
|
885
|
+
}
|
|
886
|
+
return headers;
|
|
887
|
+
}
|
|
888
|
+
formatToolSchema(tool) {
|
|
889
|
+
const props = tool.input_schema.properties;
|
|
890
|
+
const required = tool.input_schema.required || [];
|
|
891
|
+
const argsDescription = Object.entries(props).map(([name, prop]) => {
|
|
892
|
+
const req = required.includes(name) ? " (required)" : " (optional)";
|
|
893
|
+
return ` - ${name}${req}: ${prop.type}${prop.description ? ` - ${prop.description}` : ""}`;
|
|
894
|
+
}).join("\n");
|
|
895
|
+
return `## ${tool.name}
|
|
896
|
+
|
|
897
|
+
${tool.description}
|
|
898
|
+
|
|
899
|
+
### Arguments:
|
|
900
|
+
${argsDescription || " No arguments required."}
|
|
901
|
+
|
|
902
|
+
### Usage:
|
|
903
|
+
Call this tool with the arguments above to execute it.`;
|
|
904
|
+
}
|
|
905
|
+
log(level, message, data) {
|
|
906
|
+
const levels = ["debug", "info", "warn", "error"];
|
|
907
|
+
const currentLevel = levels.indexOf(this.options.logLevel);
|
|
908
|
+
const messageLevel = levels.indexOf(level);
|
|
909
|
+
if (messageLevel >= currentLevel) {
|
|
910
|
+
const safeData = data ? JSON.parse((0, import_core.redactSecrets)(JSON.stringify(data))) : void 0;
|
|
911
|
+
console.error(`[drumcode] ${message}`, safeData ? safeData : "");
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
// src/server.ts
|
|
917
|
+
async function createServer(options) {
|
|
918
|
+
const runner = new DrumcodeRunner(options);
|
|
919
|
+
const authContext = await runner.initializeAuth();
|
|
920
|
+
if (authContext.isAuthenticated) {
|
|
921
|
+
console.error(`[drumcode] Authenticated as: ${authContext.tokenName || "unnamed token"}`);
|
|
922
|
+
console.error(`[drumcode] Project ID: ${authContext.projectId}`);
|
|
923
|
+
if (authContext.profileId) {
|
|
924
|
+
console.error(`[drumcode] Permission profile: ${authContext.profileId}`);
|
|
925
|
+
}
|
|
926
|
+
} else if (options.token) {
|
|
927
|
+
console.error("[drumcode] Warning: Token provided but authentication failed");
|
|
928
|
+
}
|
|
929
|
+
const server = new import_server.Server(
|
|
930
|
+
{
|
|
931
|
+
name: "drumcode",
|
|
932
|
+
version: "0.1.0"
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
capabilities: {
|
|
936
|
+
tools: {}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
);
|
|
940
|
+
server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
|
|
941
|
+
const capabilities = {
|
|
942
|
+
type: options.clientMode === "full" ? "anthropic" : "unknown",
|
|
943
|
+
supportsAdvancedToolUse: options.clientMode === "full",
|
|
944
|
+
supportsDeferredLoading: options.clientMode === "full"
|
|
945
|
+
};
|
|
946
|
+
const tools = await runner.getToolList(capabilities);
|
|
947
|
+
const includeAuxTools = options.clientMode === "full";
|
|
948
|
+
const demoTools2 = includeAuxTools ? [
|
|
949
|
+
{
|
|
950
|
+
name: "drumcode_echo",
|
|
951
|
+
description: "Echo back the input arguments. Useful for testing.",
|
|
952
|
+
input_schema: {
|
|
953
|
+
type: "object",
|
|
954
|
+
properties: {
|
|
955
|
+
message: {
|
|
956
|
+
type: "string",
|
|
957
|
+
description: "The message to echo back"
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
required: ["message"]
|
|
961
|
+
}
|
|
962
|
+
},
|
|
963
|
+
{
|
|
964
|
+
name: "drumcode_http_get",
|
|
965
|
+
description: "Make an HTTP GET request to a URL and return the response.",
|
|
966
|
+
input_schema: {
|
|
967
|
+
type: "object",
|
|
968
|
+
properties: {
|
|
969
|
+
url: {
|
|
970
|
+
type: "string",
|
|
971
|
+
description: "The URL to fetch"
|
|
972
|
+
}
|
|
973
|
+
},
|
|
974
|
+
required: ["url"]
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
] : [];
|
|
978
|
+
const arxivTools2 = includeAuxTools ? [
|
|
979
|
+
{
|
|
980
|
+
name: "arxiv_search_papers",
|
|
981
|
+
description: "Search for academic papers on arXiv. Returns papers matching the query with optional filters.",
|
|
982
|
+
input_schema: {
|
|
983
|
+
type: "object",
|
|
984
|
+
properties: {
|
|
985
|
+
query: {
|
|
986
|
+
type: "string",
|
|
987
|
+
description: 'Search query keywords (e.g., "machine learning", "quantum computing")'
|
|
988
|
+
},
|
|
989
|
+
max_results: {
|
|
990
|
+
type: "number",
|
|
991
|
+
description: "Maximum number of results to return (1-20)"
|
|
992
|
+
},
|
|
993
|
+
categories: {
|
|
994
|
+
type: "string",
|
|
995
|
+
description: 'Comma-separated arXiv categories to filter by (e.g., "cs.AI,cs.LG")'
|
|
996
|
+
}
|
|
997
|
+
},
|
|
998
|
+
required: ["query"]
|
|
999
|
+
}
|
|
1000
|
+
},
|
|
1001
|
+
{
|
|
1002
|
+
name: "arxiv_get_paper",
|
|
1003
|
+
description: "Get detailed metadata and abstract for a specific paper by its arXiv ID.",
|
|
1004
|
+
input_schema: {
|
|
1005
|
+
type: "object",
|
|
1006
|
+
properties: {
|
|
1007
|
+
paper_id: {
|
|
1008
|
+
type: "string",
|
|
1009
|
+
description: 'The arXiv paper ID (e.g., "2301.07041" or "1706.03762")'
|
|
1010
|
+
}
|
|
1011
|
+
},
|
|
1012
|
+
required: ["paper_id"]
|
|
1013
|
+
}
|
|
1014
|
+
},
|
|
1015
|
+
{
|
|
1016
|
+
name: "arxiv_search_by_author",
|
|
1017
|
+
description: "Search for papers by a specific author on arXiv.",
|
|
1018
|
+
input_schema: {
|
|
1019
|
+
type: "object",
|
|
1020
|
+
properties: {
|
|
1021
|
+
author: {
|
|
1022
|
+
type: "string",
|
|
1023
|
+
description: "Author name to search for"
|
|
1024
|
+
},
|
|
1025
|
+
max_results: {
|
|
1026
|
+
type: "number",
|
|
1027
|
+
description: "Maximum number of results (1-20)"
|
|
1028
|
+
}
|
|
1029
|
+
},
|
|
1030
|
+
required: ["author"]
|
|
1031
|
+
}
|
|
1032
|
+
},
|
|
1033
|
+
{
|
|
1034
|
+
name: "arxiv_search_by_category",
|
|
1035
|
+
description: "Search for recent papers in a specific arXiv category.",
|
|
1036
|
+
input_schema: {
|
|
1037
|
+
type: "object",
|
|
1038
|
+
properties: {
|
|
1039
|
+
category: {
|
|
1040
|
+
type: "string",
|
|
1041
|
+
description: 'arXiv category (e.g., "cs.AI", "math.CO", "physics.hep-th")'
|
|
1042
|
+
},
|
|
1043
|
+
max_results: {
|
|
1044
|
+
type: "number",
|
|
1045
|
+
description: "Maximum number of results (1-20)"
|
|
1046
|
+
}
|
|
1047
|
+
},
|
|
1048
|
+
required: ["category"]
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
] : [];
|
|
1052
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
1053
|
+
for (const t of [...demoTools2, ...arxivTools2]) {
|
|
1054
|
+
toolMap.set(t.name, t);
|
|
1055
|
+
}
|
|
1056
|
+
for (const t of tools) {
|
|
1057
|
+
toolMap.set(t.name, t);
|
|
1058
|
+
}
|
|
1059
|
+
const allTools = Array.from(toolMap.values()).map((t) => ({
|
|
1060
|
+
name: t.name,
|
|
1061
|
+
description: t.description,
|
|
1062
|
+
inputSchema: {
|
|
1063
|
+
type: "object",
|
|
1064
|
+
properties: t.input_schema.properties,
|
|
1065
|
+
required: t.input_schema.required
|
|
1066
|
+
}
|
|
1067
|
+
}));
|
|
1068
|
+
return { tools: allTools };
|
|
1069
|
+
});
|
|
1070
|
+
server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
|
|
1071
|
+
const { name, arguments: args } = request.params;
|
|
1072
|
+
const result = await runner.executeTool({
|
|
1073
|
+
name,
|
|
1074
|
+
arguments: args ?? {}
|
|
1075
|
+
});
|
|
1076
|
+
return {
|
|
1077
|
+
content: result.content,
|
|
1078
|
+
isError: result.isError
|
|
1079
|
+
};
|
|
1080
|
+
});
|
|
1081
|
+
const transport = new import_stdio.StdioServerTransport();
|
|
1082
|
+
await server.connect(transport);
|
|
1083
|
+
console.error("[drumcode] MCP Server started");
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// src/sse-server.ts
|
|
1087
|
+
var import_express = __toESM(require("express"));
|
|
1088
|
+
var import_cors = __toESM(require("cors"));
|
|
1089
|
+
var import_server2 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
1090
|
+
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
1091
|
+
var import_sse = require("@modelcontextprotocol/sdk/server/sse.js");
|
|
1092
|
+
var import_types2 = require("@modelcontextprotocol/sdk/types.js");
|
|
1093
|
+
var demoTools = [
|
|
1094
|
+
{
|
|
1095
|
+
name: "drumcode_echo",
|
|
1096
|
+
description: "Echo back the input arguments. Useful for testing.",
|
|
1097
|
+
input_schema: {
|
|
1098
|
+
type: "object",
|
|
1099
|
+
properties: {
|
|
1100
|
+
message: {
|
|
1101
|
+
type: "string",
|
|
1102
|
+
description: "The message to echo back"
|
|
1103
|
+
}
|
|
1104
|
+
},
|
|
1105
|
+
required: ["message"]
|
|
1106
|
+
}
|
|
1107
|
+
},
|
|
1108
|
+
{
|
|
1109
|
+
name: "drumcode_http_get",
|
|
1110
|
+
description: "Make an HTTP GET request to a URL and return the response.",
|
|
1111
|
+
input_schema: {
|
|
1112
|
+
type: "object",
|
|
1113
|
+
properties: {
|
|
1114
|
+
url: {
|
|
1115
|
+
type: "string",
|
|
1116
|
+
description: "The URL to fetch"
|
|
1117
|
+
}
|
|
1118
|
+
},
|
|
1119
|
+
required: ["url"]
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
];
|
|
1123
|
+
var arxivTools = [
|
|
1124
|
+
{
|
|
1125
|
+
name: "arxiv_search_papers",
|
|
1126
|
+
description: "Search for academic papers on arXiv. Returns papers matching the query with optional filters.",
|
|
1127
|
+
input_schema: {
|
|
1128
|
+
type: "object",
|
|
1129
|
+
properties: {
|
|
1130
|
+
query: {
|
|
1131
|
+
type: "string",
|
|
1132
|
+
description: 'Search query keywords (e.g., "machine learning", "quantum computing")'
|
|
1133
|
+
},
|
|
1134
|
+
max_results: {
|
|
1135
|
+
type: "number",
|
|
1136
|
+
description: "Maximum number of results to return (1-20)"
|
|
1137
|
+
},
|
|
1138
|
+
categories: {
|
|
1139
|
+
type: "string",
|
|
1140
|
+
description: 'Comma-separated arXiv categories to filter by (e.g., "cs.AI,cs.LG")'
|
|
1141
|
+
}
|
|
1142
|
+
},
|
|
1143
|
+
required: ["query"]
|
|
1144
|
+
}
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
name: "arxiv_get_paper",
|
|
1148
|
+
description: "Get detailed metadata and abstract for a specific paper by its arXiv ID.",
|
|
1149
|
+
input_schema: {
|
|
1150
|
+
type: "object",
|
|
1151
|
+
properties: {
|
|
1152
|
+
paper_id: {
|
|
1153
|
+
type: "string",
|
|
1154
|
+
description: 'The arXiv paper ID (e.g., "2301.07041" or "1706.03762")'
|
|
1155
|
+
}
|
|
1156
|
+
},
|
|
1157
|
+
required: ["paper_id"]
|
|
1158
|
+
}
|
|
1159
|
+
},
|
|
1160
|
+
{
|
|
1161
|
+
name: "arxiv_search_by_author",
|
|
1162
|
+
description: "Search for papers by a specific author on arXiv.",
|
|
1163
|
+
input_schema: {
|
|
1164
|
+
type: "object",
|
|
1165
|
+
properties: {
|
|
1166
|
+
author: {
|
|
1167
|
+
type: "string",
|
|
1168
|
+
description: "Author name to search for"
|
|
1169
|
+
},
|
|
1170
|
+
max_results: {
|
|
1171
|
+
type: "number",
|
|
1172
|
+
description: "Maximum number of results (1-20)"
|
|
1173
|
+
}
|
|
1174
|
+
},
|
|
1175
|
+
required: ["author"]
|
|
1176
|
+
}
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
name: "arxiv_search_by_category",
|
|
1180
|
+
description: "Search for recent papers in a specific arXiv category.",
|
|
1181
|
+
input_schema: {
|
|
1182
|
+
type: "object",
|
|
1183
|
+
properties: {
|
|
1184
|
+
category: {
|
|
1185
|
+
type: "string",
|
|
1186
|
+
description: 'arXiv category (e.g., "cs.AI", "math.CO", "physics.hep-th")'
|
|
1187
|
+
},
|
|
1188
|
+
max_results: {
|
|
1189
|
+
type: "number",
|
|
1190
|
+
description: "Maximum number of results (1-20)"
|
|
1191
|
+
}
|
|
1192
|
+
},
|
|
1193
|
+
required: ["category"]
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
];
|
|
1197
|
+
async function createSSEServer(options) {
|
|
1198
|
+
const runner = new DrumcodeRunner(options);
|
|
1199
|
+
const authContext = await runner.initializeAuth();
|
|
1200
|
+
if (authContext.isAuthenticated) {
|
|
1201
|
+
console.error(`[drumcode] Authenticated as: ${authContext.tokenName || "unnamed token"}`);
|
|
1202
|
+
console.error(`[drumcode] Project ID: ${authContext.projectId}`);
|
|
1203
|
+
if (authContext.profileId) {
|
|
1204
|
+
console.error(`[drumcode] Permission profile: ${authContext.profileId}`);
|
|
1205
|
+
}
|
|
1206
|
+
} else if (options.token) {
|
|
1207
|
+
console.error("[drumcode] Warning: Token provided but authentication failed");
|
|
1208
|
+
}
|
|
1209
|
+
const app = (0, import_express.default)();
|
|
1210
|
+
app.use((0, import_cors.default)({
|
|
1211
|
+
origin: "*",
|
|
1212
|
+
methods: ["GET", "POST", "OPTIONS"],
|
|
1213
|
+
allowedHeaders: ["Content-Type", "Authorization", "Mcp-Session-Id"],
|
|
1214
|
+
exposedHeaders: ["Mcp-Session-Id"]
|
|
1215
|
+
}));
|
|
1216
|
+
app.use(import_express.default.json());
|
|
1217
|
+
app.get("/health", (_req, res) => {
|
|
1218
|
+
res.json({
|
|
1219
|
+
status: "ok",
|
|
1220
|
+
version: "0.1.0",
|
|
1221
|
+
transport: "sse",
|
|
1222
|
+
authenticated: authContext.isAuthenticated
|
|
1223
|
+
});
|
|
1224
|
+
});
|
|
1225
|
+
const legacyTransports = /* @__PURE__ */ new Map();
|
|
1226
|
+
const createMCPServer = () => {
|
|
1227
|
+
const server2 = new import_server2.Server(
|
|
1228
|
+
{
|
|
1229
|
+
name: "drumcode",
|
|
1230
|
+
version: "0.1.0"
|
|
1231
|
+
},
|
|
1232
|
+
{
|
|
1233
|
+
capabilities: {
|
|
1234
|
+
tools: {}
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
);
|
|
1238
|
+
server2.setRequestHandler(import_types2.ListToolsRequestSchema, async () => {
|
|
1239
|
+
const capabilities = {
|
|
1240
|
+
type: options.clientMode === "full" ? "anthropic" : "unknown",
|
|
1241
|
+
supportsAdvancedToolUse: options.clientMode === "full",
|
|
1242
|
+
supportsDeferredLoading: options.clientMode === "full"
|
|
1243
|
+
};
|
|
1244
|
+
const tools = await runner.getToolList(capabilities);
|
|
1245
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
1246
|
+
for (const t of [...demoTools, ...arxivTools]) {
|
|
1247
|
+
toolMap.set(t.name, t);
|
|
1248
|
+
}
|
|
1249
|
+
for (const t of tools) {
|
|
1250
|
+
toolMap.set(t.name, t);
|
|
1251
|
+
}
|
|
1252
|
+
const allTools = Array.from(toolMap.values()).map((t) => ({
|
|
1253
|
+
name: t.name,
|
|
1254
|
+
description: t.description,
|
|
1255
|
+
inputSchema: {
|
|
1256
|
+
type: "object",
|
|
1257
|
+
properties: t.input_schema.properties,
|
|
1258
|
+
required: t.input_schema.required
|
|
1259
|
+
}
|
|
1260
|
+
}));
|
|
1261
|
+
return { tools: allTools };
|
|
1262
|
+
});
|
|
1263
|
+
server2.setRequestHandler(import_types2.CallToolRequestSchema, async (request) => {
|
|
1264
|
+
const { name, arguments: args } = request.params;
|
|
1265
|
+
const result = await runner.executeTool({
|
|
1266
|
+
name,
|
|
1267
|
+
arguments: args ?? {}
|
|
1268
|
+
});
|
|
1269
|
+
return {
|
|
1270
|
+
content: result.content,
|
|
1271
|
+
isError: result.isError
|
|
1272
|
+
};
|
|
1273
|
+
});
|
|
1274
|
+
return server2;
|
|
1275
|
+
};
|
|
1276
|
+
app.get("/sse", async (req, res) => {
|
|
1277
|
+
console.error("[drumcode] Legacy SSE connection requested");
|
|
1278
|
+
const transport = new import_sse.SSEServerTransport("/messages", res);
|
|
1279
|
+
const server2 = createMCPServer();
|
|
1280
|
+
await server2.connect(transport);
|
|
1281
|
+
const sessionId = transport.sessionId;
|
|
1282
|
+
legacyTransports.set(sessionId, transport);
|
|
1283
|
+
console.error(`[drumcode] Legacy SSE session started: ${sessionId}`);
|
|
1284
|
+
res.on("close", () => {
|
|
1285
|
+
legacyTransports.delete(sessionId);
|
|
1286
|
+
console.error(`[drumcode] Legacy SSE session closed: ${sessionId}`);
|
|
1287
|
+
});
|
|
1288
|
+
});
|
|
1289
|
+
app.post("/messages", async (req, res) => {
|
|
1290
|
+
const sessionId = req.query.sessionId;
|
|
1291
|
+
if (!sessionId) {
|
|
1292
|
+
res.status(400).json({ error: "Missing sessionId query parameter" });
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
const transport = legacyTransports.get(sessionId);
|
|
1296
|
+
if (!transport) {
|
|
1297
|
+
res.status(404).json({ error: "Session not found. Connect to /sse first." });
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
await transport.handlePostMessage(req, res, req.body);
|
|
1301
|
+
});
|
|
1302
|
+
app.all("/mcp", async (req, res) => {
|
|
1303
|
+
const authHeader = req.headers.authorization;
|
|
1304
|
+
if (options.token && authHeader) {
|
|
1305
|
+
const providedToken = authHeader.replace("Bearer ", "");
|
|
1306
|
+
if (providedToken !== options.token) {
|
|
1307
|
+
console.error("[drumcode] Invalid token in Authorization header");
|
|
1308
|
+
res.status(401).json({ error: "Invalid token" });
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
const transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
1313
|
+
sessionIdGenerator: void 0
|
|
1314
|
+
// Stateless mode - no session tracking
|
|
1315
|
+
});
|
|
1316
|
+
const server2 = createMCPServer();
|
|
1317
|
+
await server2.connect(transport);
|
|
1318
|
+
await transport.handleRequest(req, res, req.body);
|
|
1319
|
+
});
|
|
1320
|
+
const server = app.listen(options.port, options.host, () => {
|
|
1321
|
+
console.error(`[drumcode] SSE MCP Server started`);
|
|
1322
|
+
console.error(`[drumcode] Listening on http://${options.host}:${options.port}`);
|
|
1323
|
+
console.error(`[drumcode] MCP endpoint: http://${options.host}:${options.port}/mcp`);
|
|
1324
|
+
console.error(`[drumcode] Health check: http://${options.host}:${options.port}/health`);
|
|
1325
|
+
});
|
|
1326
|
+
const shutdown = () => {
|
|
1327
|
+
console.error("[drumcode] Shutting down...");
|
|
1328
|
+
server.close(() => {
|
|
1329
|
+
console.error("[drumcode] Server closed");
|
|
1330
|
+
process.exit(0);
|
|
1331
|
+
});
|
|
1332
|
+
};
|
|
1333
|
+
process.on("SIGTERM", shutdown);
|
|
1334
|
+
process.on("SIGINT", shutdown);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
// src/types.ts
|
|
1338
|
+
var import_zod = require("zod");
|
|
1339
|
+
var runnerOptionsSchema = import_zod.z.object({
|
|
1340
|
+
token: import_zod.z.string().optional(),
|
|
1341
|
+
project: import_zod.z.string().optional(),
|
|
1342
|
+
registryUrl: import_zod.z.string().url().default("https://api.drumcode.ai"),
|
|
1343
|
+
cacheEnabled: import_zod.z.boolean().default(true),
|
|
1344
|
+
logLevel: import_zod.z.enum(["debug", "info", "warn", "error"]).default("info"),
|
|
1345
|
+
transport: import_zod.z.enum(["stdio", "sse"]).default("stdio"),
|
|
1346
|
+
/**
|
|
1347
|
+
* Client mode affects how tools are exposed:
|
|
1348
|
+
* - 'polyfill': Only expose core meta-tools (search + get_schema) - default for generic clients
|
|
1349
|
+
* - 'full': Expose full manifest from registry - for clients that support large tool lists
|
|
1350
|
+
*/
|
|
1351
|
+
clientMode: import_zod.z.enum(["polyfill", "full"]).default("polyfill"),
|
|
1352
|
+
/**
|
|
1353
|
+
* Port for SSE server (only used when transport is 'sse')
|
|
1354
|
+
*/
|
|
1355
|
+
port: import_zod.z.number().default(3001),
|
|
1356
|
+
/**
|
|
1357
|
+
* Host for SSE server (only used when transport is 'sse')
|
|
1358
|
+
*/
|
|
1359
|
+
host: import_zod.z.string().default("0.0.0.0"),
|
|
1360
|
+
/**
|
|
1361
|
+
* Integration base URLs for registry tools (keyed by integration name)
|
|
1362
|
+
*/
|
|
1363
|
+
integrationBaseUrls: import_zod.z.record(import_zod.z.string()).optional(),
|
|
1364
|
+
/**
|
|
1365
|
+
* Integration headers for registry tools (keyed by integration name)
|
|
1366
|
+
*/
|
|
1367
|
+
integrationHeaders: import_zod.z.record(import_zod.z.record(import_zod.z.string())).optional()
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
// src/cli.ts
|
|
1371
|
+
(0, import_dotenv.config)();
|
|
1372
|
+
function parseJsonEnv(value, name) {
|
|
1373
|
+
if (!value) return void 0;
|
|
1374
|
+
try {
|
|
1375
|
+
return JSON.parse(value);
|
|
1376
|
+
} catch {
|
|
1377
|
+
throw new Error(`Invalid JSON in ${name}`);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
var program = new import_commander.Command();
|
|
1381
|
+
program.name("drumcode").description("Drumcode MCP Runner - AI-Ready SaaS Infrastructure").version("0.1.0");
|
|
1382
|
+
program.command("start").description("Start the Drumcode MCP server").option("-t, --token <token>", "Drumcode API token").option("-p, --project <project>", "Project name").option("-r, --registry <url>", "Registry URL", "https://drumcode-studio.vercel.app/api").option("--no-cache", "Disable caching").option("-l, --log-level <level>", "Log level (debug, info, warn, error)", "info").option("--transport <type>", "Transport type (stdio, sse)", "stdio").option("--port <port>", "Port for SSE server (default: 3001)", "3001").option("--host <host>", "Host for SSE server (default: 0.0.0.0)", "0.0.0.0").option("-m, --mode <mode>", "Client mode: polyfill (meta-tools only) or full (all tools from registry)", "polyfill").action(async (opts) => {
|
|
1383
|
+
try {
|
|
1384
|
+
const integrationBaseUrls = parseJsonEnv(
|
|
1385
|
+
process.env.DRUMCODE_INTEGRATION_BASE_URLS,
|
|
1386
|
+
"DRUMCODE_INTEGRATION_BASE_URLS"
|
|
1387
|
+
);
|
|
1388
|
+
const integrationHeaders = parseJsonEnv(
|
|
1389
|
+
process.env.DRUMCODE_INTEGRATION_HEADERS,
|
|
1390
|
+
"DRUMCODE_INTEGRATION_HEADERS"
|
|
1391
|
+
);
|
|
1392
|
+
const options = runnerOptionsSchema.parse({
|
|
1393
|
+
token: opts.token || process.env.DRUMCODE_TOKEN,
|
|
1394
|
+
project: opts.project || process.env.DRUMCODE_PROJECT,
|
|
1395
|
+
registryUrl: opts.registry || process.env.DRUMCODE_REGISTRY_URL,
|
|
1396
|
+
cacheEnabled: opts.cache !== false,
|
|
1397
|
+
logLevel: opts.logLevel,
|
|
1398
|
+
transport: opts.transport,
|
|
1399
|
+
port: parseInt(opts.port, 10),
|
|
1400
|
+
host: opts.host,
|
|
1401
|
+
clientMode: opts.mode || process.env.DRUMCODE_CLIENT_MODE || "polyfill",
|
|
1402
|
+
integrationBaseUrls,
|
|
1403
|
+
integrationHeaders
|
|
1404
|
+
});
|
|
1405
|
+
console.error("[drumcode] Starting MCP server...");
|
|
1406
|
+
console.error("[drumcode] Registry:", options.registryUrl);
|
|
1407
|
+
console.error("[drumcode] Log level:", options.logLevel);
|
|
1408
|
+
console.error("[drumcode] Transport:", options.transport);
|
|
1409
|
+
console.error("[drumcode] Client mode:", options.clientMode);
|
|
1410
|
+
if (!options.token) {
|
|
1411
|
+
console.error("[drumcode] Warning: No token provided. Running in demo mode.");
|
|
1412
|
+
console.error("[drumcode] Set DRUMCODE_TOKEN or use --token to connect to Registry.");
|
|
1413
|
+
}
|
|
1414
|
+
if (options.clientMode === "full") {
|
|
1415
|
+
console.error("[drumcode] Full manifest mode: All tools from registry will be exposed");
|
|
1416
|
+
} else {
|
|
1417
|
+
console.error("[drumcode] Polyfill mode: Only meta-tools + hardcoded tools will be exposed");
|
|
1418
|
+
}
|
|
1419
|
+
if (options.transport === "sse") {
|
|
1420
|
+
await createSSEServer(options);
|
|
1421
|
+
} else {
|
|
1422
|
+
await createServer(options);
|
|
1423
|
+
}
|
|
1424
|
+
} catch (error) {
|
|
1425
|
+
console.error("[drumcode] Failed to start:", error);
|
|
1426
|
+
process.exit(1);
|
|
1427
|
+
}
|
|
1428
|
+
});
|
|
1429
|
+
program.command("version").description("Show version information").action(() => {
|
|
1430
|
+
console.log("Drumcode Runner v0.1.0");
|
|
1431
|
+
});
|
|
1432
|
+
program.parse();
|