@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/dist/cli.mjs ADDED
@@ -0,0 +1,360 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DrumcodeRunner,
4
+ createServer
5
+ } from "./chunk-VYYR5EI4.mjs";
6
+
7
+ // src/cli.ts
8
+ import { Command } from "commander";
9
+ import { config } from "dotenv";
10
+
11
+ // src/sse-server.ts
12
+ import express from "express";
13
+ import cors from "cors";
14
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
15
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
16
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
17
+ import {
18
+ CallToolRequestSchema,
19
+ ListToolsRequestSchema
20
+ } from "@modelcontextprotocol/sdk/types.js";
21
+ var demoTools = [
22
+ {
23
+ name: "drumcode_echo",
24
+ description: "Echo back the input arguments. Useful for testing.",
25
+ input_schema: {
26
+ type: "object",
27
+ properties: {
28
+ message: {
29
+ type: "string",
30
+ description: "The message to echo back"
31
+ }
32
+ },
33
+ required: ["message"]
34
+ }
35
+ },
36
+ {
37
+ name: "drumcode_http_get",
38
+ description: "Make an HTTP GET request to a URL and return the response.",
39
+ input_schema: {
40
+ type: "object",
41
+ properties: {
42
+ url: {
43
+ type: "string",
44
+ description: "The URL to fetch"
45
+ }
46
+ },
47
+ required: ["url"]
48
+ }
49
+ }
50
+ ];
51
+ var arxivTools = [
52
+ {
53
+ name: "arxiv_search_papers",
54
+ description: "Search for academic papers on arXiv. Returns papers matching the query with optional filters.",
55
+ input_schema: {
56
+ type: "object",
57
+ properties: {
58
+ query: {
59
+ type: "string",
60
+ description: 'Search query keywords (e.g., "machine learning", "quantum computing")'
61
+ },
62
+ max_results: {
63
+ type: "number",
64
+ description: "Maximum number of results to return (1-20)"
65
+ },
66
+ categories: {
67
+ type: "string",
68
+ description: 'Comma-separated arXiv categories to filter by (e.g., "cs.AI,cs.LG")'
69
+ }
70
+ },
71
+ required: ["query"]
72
+ }
73
+ },
74
+ {
75
+ name: "arxiv_get_paper",
76
+ description: "Get detailed metadata and abstract for a specific paper by its arXiv ID.",
77
+ input_schema: {
78
+ type: "object",
79
+ properties: {
80
+ paper_id: {
81
+ type: "string",
82
+ description: 'The arXiv paper ID (e.g., "2301.07041" or "1706.03762")'
83
+ }
84
+ },
85
+ required: ["paper_id"]
86
+ }
87
+ },
88
+ {
89
+ name: "arxiv_search_by_author",
90
+ description: "Search for papers by a specific author on arXiv.",
91
+ input_schema: {
92
+ type: "object",
93
+ properties: {
94
+ author: {
95
+ type: "string",
96
+ description: "Author name to search for"
97
+ },
98
+ max_results: {
99
+ type: "number",
100
+ description: "Maximum number of results (1-20)"
101
+ }
102
+ },
103
+ required: ["author"]
104
+ }
105
+ },
106
+ {
107
+ name: "arxiv_search_by_category",
108
+ description: "Search for recent papers in a specific arXiv category.",
109
+ input_schema: {
110
+ type: "object",
111
+ properties: {
112
+ category: {
113
+ type: "string",
114
+ description: 'arXiv category (e.g., "cs.AI", "math.CO", "physics.hep-th")'
115
+ },
116
+ max_results: {
117
+ type: "number",
118
+ description: "Maximum number of results (1-20)"
119
+ }
120
+ },
121
+ required: ["category"]
122
+ }
123
+ }
124
+ ];
125
+ async function createSSEServer(options) {
126
+ const runner = new DrumcodeRunner(options);
127
+ const authContext = await runner.initializeAuth();
128
+ if (authContext.isAuthenticated) {
129
+ console.error(`[drumcode] Authenticated as: ${authContext.tokenName || "unnamed token"}`);
130
+ console.error(`[drumcode] Project ID: ${authContext.projectId}`);
131
+ if (authContext.profileId) {
132
+ console.error(`[drumcode] Permission profile: ${authContext.profileId}`);
133
+ }
134
+ } else if (options.token) {
135
+ console.error("[drumcode] Warning: Token provided but authentication failed");
136
+ }
137
+ const app = express();
138
+ app.use(cors({
139
+ origin: "*",
140
+ methods: ["GET", "POST", "OPTIONS"],
141
+ allowedHeaders: ["Content-Type", "Authorization", "Mcp-Session-Id"],
142
+ exposedHeaders: ["Mcp-Session-Id"]
143
+ }));
144
+ app.use(express.json());
145
+ app.get("/health", (_req, res) => {
146
+ res.json({
147
+ status: "ok",
148
+ version: "0.1.0",
149
+ transport: "sse",
150
+ authenticated: authContext.isAuthenticated
151
+ });
152
+ });
153
+ const legacyTransports = /* @__PURE__ */ new Map();
154
+ const createMCPServer = () => {
155
+ const server2 = new Server(
156
+ {
157
+ name: "drumcode",
158
+ version: "0.1.0"
159
+ },
160
+ {
161
+ capabilities: {
162
+ tools: {}
163
+ }
164
+ }
165
+ );
166
+ server2.setRequestHandler(ListToolsRequestSchema, async () => {
167
+ const capabilities = {
168
+ type: options.clientMode === "full" ? "anthropic" : "unknown",
169
+ supportsAdvancedToolUse: options.clientMode === "full",
170
+ supportsDeferredLoading: options.clientMode === "full"
171
+ };
172
+ const tools = await runner.getToolList(capabilities);
173
+ const toolMap = /* @__PURE__ */ new Map();
174
+ for (const t of [...demoTools, ...arxivTools]) {
175
+ toolMap.set(t.name, t);
176
+ }
177
+ for (const t of tools) {
178
+ toolMap.set(t.name, t);
179
+ }
180
+ const allTools = Array.from(toolMap.values()).map((t) => ({
181
+ name: t.name,
182
+ description: t.description,
183
+ inputSchema: {
184
+ type: "object",
185
+ properties: t.input_schema.properties,
186
+ required: t.input_schema.required
187
+ }
188
+ }));
189
+ return { tools: allTools };
190
+ });
191
+ server2.setRequestHandler(CallToolRequestSchema, async (request) => {
192
+ const { name, arguments: args } = request.params;
193
+ const result = await runner.executeTool({
194
+ name,
195
+ arguments: args ?? {}
196
+ });
197
+ return {
198
+ content: result.content,
199
+ isError: result.isError
200
+ };
201
+ });
202
+ return server2;
203
+ };
204
+ app.get("/sse", async (req, res) => {
205
+ console.error("[drumcode] Legacy SSE connection requested");
206
+ const transport = new SSEServerTransport("/messages", res);
207
+ const server2 = createMCPServer();
208
+ await server2.connect(transport);
209
+ const sessionId = transport.sessionId;
210
+ legacyTransports.set(sessionId, transport);
211
+ console.error(`[drumcode] Legacy SSE session started: ${sessionId}`);
212
+ res.on("close", () => {
213
+ legacyTransports.delete(sessionId);
214
+ console.error(`[drumcode] Legacy SSE session closed: ${sessionId}`);
215
+ });
216
+ });
217
+ app.post("/messages", async (req, res) => {
218
+ const sessionId = req.query.sessionId;
219
+ if (!sessionId) {
220
+ res.status(400).json({ error: "Missing sessionId query parameter" });
221
+ return;
222
+ }
223
+ const transport = legacyTransports.get(sessionId);
224
+ if (!transport) {
225
+ res.status(404).json({ error: "Session not found. Connect to /sse first." });
226
+ return;
227
+ }
228
+ await transport.handlePostMessage(req, res, req.body);
229
+ });
230
+ app.all("/mcp", async (req, res) => {
231
+ const authHeader = req.headers.authorization;
232
+ if (options.token && authHeader) {
233
+ const providedToken = authHeader.replace("Bearer ", "");
234
+ if (providedToken !== options.token) {
235
+ console.error("[drumcode] Invalid token in Authorization header");
236
+ res.status(401).json({ error: "Invalid token" });
237
+ return;
238
+ }
239
+ }
240
+ const transport = new StreamableHTTPServerTransport({
241
+ sessionIdGenerator: void 0
242
+ // Stateless mode - no session tracking
243
+ });
244
+ const server2 = createMCPServer();
245
+ await server2.connect(transport);
246
+ await transport.handleRequest(req, res, req.body);
247
+ });
248
+ const server = app.listen(options.port, options.host, () => {
249
+ console.error(`[drumcode] SSE MCP Server started`);
250
+ console.error(`[drumcode] Listening on http://${options.host}:${options.port}`);
251
+ console.error(`[drumcode] MCP endpoint: http://${options.host}:${options.port}/mcp`);
252
+ console.error(`[drumcode] Health check: http://${options.host}:${options.port}/health`);
253
+ });
254
+ const shutdown = () => {
255
+ console.error("[drumcode] Shutting down...");
256
+ server.close(() => {
257
+ console.error("[drumcode] Server closed");
258
+ process.exit(0);
259
+ });
260
+ };
261
+ process.on("SIGTERM", shutdown);
262
+ process.on("SIGINT", shutdown);
263
+ }
264
+
265
+ // src/types.ts
266
+ import { z } from "zod";
267
+ var runnerOptionsSchema = z.object({
268
+ token: z.string().optional(),
269
+ project: z.string().optional(),
270
+ registryUrl: z.string().url().default("https://api.drumcode.ai"),
271
+ cacheEnabled: z.boolean().default(true),
272
+ logLevel: z.enum(["debug", "info", "warn", "error"]).default("info"),
273
+ transport: z.enum(["stdio", "sse"]).default("stdio"),
274
+ /**
275
+ * Client mode affects how tools are exposed:
276
+ * - 'polyfill': Only expose core meta-tools (search + get_schema) - default for generic clients
277
+ * - 'full': Expose full manifest from registry - for clients that support large tool lists
278
+ */
279
+ clientMode: z.enum(["polyfill", "full"]).default("polyfill"),
280
+ /**
281
+ * Port for SSE server (only used when transport is 'sse')
282
+ */
283
+ port: z.number().default(3001),
284
+ /**
285
+ * Host for SSE server (only used when transport is 'sse')
286
+ */
287
+ host: z.string().default("0.0.0.0"),
288
+ /**
289
+ * Integration base URLs for registry tools (keyed by integration name)
290
+ */
291
+ integrationBaseUrls: z.record(z.string()).optional(),
292
+ /**
293
+ * Integration headers for registry tools (keyed by integration name)
294
+ */
295
+ integrationHeaders: z.record(z.record(z.string())).optional()
296
+ });
297
+
298
+ // src/cli.ts
299
+ config();
300
+ function parseJsonEnv(value, name) {
301
+ if (!value) return void 0;
302
+ try {
303
+ return JSON.parse(value);
304
+ } catch {
305
+ throw new Error(`Invalid JSON in ${name}`);
306
+ }
307
+ }
308
+ var program = new Command();
309
+ program.name("drumcode").description("Drumcode MCP Runner - AI-Ready SaaS Infrastructure").version("0.1.0");
310
+ 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) => {
311
+ try {
312
+ const integrationBaseUrls = parseJsonEnv(
313
+ process.env.DRUMCODE_INTEGRATION_BASE_URLS,
314
+ "DRUMCODE_INTEGRATION_BASE_URLS"
315
+ );
316
+ const integrationHeaders = parseJsonEnv(
317
+ process.env.DRUMCODE_INTEGRATION_HEADERS,
318
+ "DRUMCODE_INTEGRATION_HEADERS"
319
+ );
320
+ const options = runnerOptionsSchema.parse({
321
+ token: opts.token || process.env.DRUMCODE_TOKEN,
322
+ project: opts.project || process.env.DRUMCODE_PROJECT,
323
+ registryUrl: opts.registry || process.env.DRUMCODE_REGISTRY_URL,
324
+ cacheEnabled: opts.cache !== false,
325
+ logLevel: opts.logLevel,
326
+ transport: opts.transport,
327
+ port: parseInt(opts.port, 10),
328
+ host: opts.host,
329
+ clientMode: opts.mode || process.env.DRUMCODE_CLIENT_MODE || "polyfill",
330
+ integrationBaseUrls,
331
+ integrationHeaders
332
+ });
333
+ console.error("[drumcode] Starting MCP server...");
334
+ console.error("[drumcode] Registry:", options.registryUrl);
335
+ console.error("[drumcode] Log level:", options.logLevel);
336
+ console.error("[drumcode] Transport:", options.transport);
337
+ console.error("[drumcode] Client mode:", options.clientMode);
338
+ if (!options.token) {
339
+ console.error("[drumcode] Warning: No token provided. Running in demo mode.");
340
+ console.error("[drumcode] Set DRUMCODE_TOKEN or use --token to connect to Registry.");
341
+ }
342
+ if (options.clientMode === "full") {
343
+ console.error("[drumcode] Full manifest mode: All tools from registry will be exposed");
344
+ } else {
345
+ console.error("[drumcode] Polyfill mode: Only meta-tools + hardcoded tools will be exposed");
346
+ }
347
+ if (options.transport === "sse") {
348
+ await createSSEServer(options);
349
+ } else {
350
+ await createServer(options);
351
+ }
352
+ } catch (error) {
353
+ console.error("[drumcode] Failed to start:", error);
354
+ process.exit(1);
355
+ }
356
+ });
357
+ program.command("version").description("Show version information").action(() => {
358
+ console.log("Drumcode Runner v0.1.0");
359
+ });
360
+ program.parse();
@@ -0,0 +1,192 @@
1
+ import { Tool } from '@drumcode/core';
2
+ import { z } from 'zod';
3
+
4
+ declare const runnerOptionsSchema: z.ZodObject<{
5
+ token: z.ZodOptional<z.ZodString>;
6
+ project: z.ZodOptional<z.ZodString>;
7
+ registryUrl: z.ZodDefault<z.ZodString>;
8
+ cacheEnabled: z.ZodDefault<z.ZodBoolean>;
9
+ logLevel: z.ZodDefault<z.ZodEnum<["debug", "info", "warn", "error"]>>;
10
+ transport: z.ZodDefault<z.ZodEnum<["stdio", "sse"]>>;
11
+ /**
12
+ * Client mode affects how tools are exposed:
13
+ * - 'polyfill': Only expose core meta-tools (search + get_schema) - default for generic clients
14
+ * - 'full': Expose full manifest from registry - for clients that support large tool lists
15
+ */
16
+ clientMode: z.ZodDefault<z.ZodEnum<["polyfill", "full"]>>;
17
+ /**
18
+ * Port for SSE server (only used when transport is 'sse')
19
+ */
20
+ port: z.ZodDefault<z.ZodNumber>;
21
+ /**
22
+ * Host for SSE server (only used when transport is 'sse')
23
+ */
24
+ host: z.ZodDefault<z.ZodString>;
25
+ /**
26
+ * Integration base URLs for registry tools (keyed by integration name)
27
+ */
28
+ integrationBaseUrls: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
29
+ /**
30
+ * Integration headers for registry tools (keyed by integration name)
31
+ */
32
+ integrationHeaders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>>;
33
+ }, "strip", z.ZodTypeAny, {
34
+ registryUrl: string;
35
+ cacheEnabled: boolean;
36
+ logLevel: "info" | "debug" | "warn" | "error";
37
+ transport: "stdio" | "sse";
38
+ clientMode: "polyfill" | "full";
39
+ port: number;
40
+ host: string;
41
+ token?: string | undefined;
42
+ project?: string | undefined;
43
+ integrationBaseUrls?: Record<string, string> | undefined;
44
+ integrationHeaders?: Record<string, Record<string, string>> | undefined;
45
+ }, {
46
+ token?: string | undefined;
47
+ project?: string | undefined;
48
+ registryUrl?: string | undefined;
49
+ cacheEnabled?: boolean | undefined;
50
+ logLevel?: "info" | "debug" | "warn" | "error" | undefined;
51
+ transport?: "stdio" | "sse" | undefined;
52
+ clientMode?: "polyfill" | "full" | undefined;
53
+ port?: number | undefined;
54
+ host?: string | undefined;
55
+ integrationBaseUrls?: Record<string, string> | undefined;
56
+ integrationHeaders?: Record<string, Record<string, string>> | undefined;
57
+ }>;
58
+ type RunnerOptions = z.infer<typeof runnerOptionsSchema>;
59
+ interface ToolCallRequest {
60
+ name: string;
61
+ arguments: Record<string, unknown>;
62
+ }
63
+ interface ToolCallResult {
64
+ content: Array<{
65
+ type: 'text' | 'image' | 'resource';
66
+ text?: string;
67
+ data?: string;
68
+ mimeType?: string;
69
+ }>;
70
+ isError?: boolean;
71
+ }
72
+ interface TokenValidationResponse {
73
+ valid: boolean;
74
+ project?: {
75
+ id: string;
76
+ };
77
+ token?: {
78
+ id: string;
79
+ name: string | null;
80
+ profileId: string | null;
81
+ };
82
+ error?: {
83
+ code: string;
84
+ message: string;
85
+ };
86
+ }
87
+ interface AuthenticatedContext {
88
+ isAuthenticated: boolean;
89
+ projectId: string | null;
90
+ tokenId: string | null;
91
+ tokenName: string | null;
92
+ profileId: string | null;
93
+ }
94
+ type ClientType = 'anthropic' | 'openai' | 'cursor' | 'langchain' | 'unknown';
95
+ interface ClientCapabilities {
96
+ type: ClientType;
97
+ supportsAdvancedToolUse: boolean;
98
+ supportsDeferredLoading: boolean;
99
+ }
100
+
101
+ declare class DrumcodeRunner {
102
+ private options;
103
+ private toolCache;
104
+ private skillCache;
105
+ private manifestCache;
106
+ private authContext;
107
+ private authInitialized;
108
+ constructor(options: RunnerOptions);
109
+ /**
110
+ * Initialize authentication by validating the token with the registry
111
+ * This should be called before processing any requests
112
+ */
113
+ initializeAuth(): Promise<AuthenticatedContext>;
114
+ /**
115
+ * Get the current authentication context
116
+ */
117
+ getAuthContext(): AuthenticatedContext;
118
+ /**
119
+ * Check if the runner is authenticated
120
+ */
121
+ isAuthenticated(): boolean;
122
+ /**
123
+ * Get the list of tools to expose to the client
124
+ * Uses Polyfill strategy for non-Anthropic clients
125
+ */
126
+ getToolList(capabilities: ClientCapabilities): Promise<Tool[]>;
127
+ /**
128
+ * Fetch the full tool manifest from the Registry
129
+ */
130
+ private getFullManifest;
131
+ /**
132
+ * Execute a tool call
133
+ */
134
+ executeTool(request: ToolCallRequest): Promise<ToolCallResult>;
135
+ /**
136
+ * Handle drumcode_search_tools meta-tool
137
+ */
138
+ private handleSearchTools;
139
+ /**
140
+ * Handle drumcode_get_tool_schema meta-tool
141
+ */
142
+ private handleGetToolSchema;
143
+ private fetchRegistryToolSchema;
144
+ /**
145
+ * Execute a regular (non-meta) tool
146
+ */
147
+ private executeRegularTool;
148
+ private executeRegistryTool;
149
+ /**
150
+ * Fetch skill definition from registry
151
+ */
152
+ private fetchSkill;
153
+ /**
154
+ * Execute a skill (validator or workflow)
155
+ */
156
+ private executeSkill;
157
+ /**
158
+ * Evaluate a policy rule against context
159
+ * Simple expression evaluator for rules like "input.query && input.query.length >= 2"
160
+ */
161
+ private evaluateRule;
162
+ /**
163
+ * Evaluate an argument mapping expression
164
+ * Supports expressions like "input.query", "input.max_results || 5", "steps.search_results.papers[0].id"
165
+ */
166
+ private evaluateMapping;
167
+ /**
168
+ * Parse tool result content to extract structured data
169
+ */
170
+ private parseToolResult;
171
+ /**
172
+ * Execute arXiv tools via the arXiv API
173
+ */
174
+ private executeArxivTool;
175
+ /**
176
+ * Parse arXiv Atom XML response
177
+ */
178
+ private parseArxivResponse;
179
+ private getIntegrationBaseUrl;
180
+ private getIntegrationHeaders;
181
+ private buildToolRequest;
182
+ private getAuthHeaders;
183
+ private formatToolSchema;
184
+ private log;
185
+ }
186
+
187
+ /**
188
+ * Create and configure the MCP server
189
+ */
190
+ declare function createServer(options: RunnerOptions): Promise<void>;
191
+
192
+ export { type AuthenticatedContext, DrumcodeRunner, type RunnerOptions, type TokenValidationResponse, createServer };