@elsium-ai/mcp 0.1.6

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.
@@ -0,0 +1,24 @@
1
+ import type { Tool } from '@elsium-ai/tools';
2
+ export interface MCPClientConfig {
3
+ name: string;
4
+ transport: 'stdio';
5
+ command: string;
6
+ args?: string[];
7
+ env?: Record<string, string>;
8
+ timeoutMs?: number;
9
+ }
10
+ export interface MCPToolInfo {
11
+ name: string;
12
+ description: string;
13
+ inputSchema: Record<string, unknown>;
14
+ }
15
+ export interface MCPClient {
16
+ connect(): Promise<void>;
17
+ disconnect(): Promise<void>;
18
+ listTools(): Promise<MCPToolInfo[]>;
19
+ callTool(name: string, args: Record<string, unknown>): Promise<unknown>;
20
+ toElsiumTools(): Promise<Tool[]>;
21
+ readonly connected: boolean;
22
+ }
23
+ export declare function createMCPClient(config: MCPClientConfig): MCPClient;
24
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAoC,MAAM,kBAAkB,CAAA;AAE9E,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACpC;AAgBD,MAAM,WAAW,SAAS;IACzB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,SAAS,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACvE,aAAa,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;IAChC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;CAC3B;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAuQlE"}
@@ -0,0 +1,5 @@
1
+ export { createMCPClient } from './client';
2
+ export type { MCPClient, MCPClientConfig, MCPToolInfo } from './client';
3
+ export { createMCPServer } from './server';
4
+ export type { MCPServer, MCPServerConfig } from './server';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC1C,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAGvE,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC1C,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,530 @@
1
+ // @bun
2
+ // src/client.ts
3
+ import { spawn } from "child_process";
4
+
5
+ // ../core/src/errors.ts
6
+ class ElsiumError extends Error {
7
+ code;
8
+ provider;
9
+ model;
10
+ statusCode;
11
+ retryable;
12
+ retryAfterMs;
13
+ cause;
14
+ metadata;
15
+ constructor(details) {
16
+ super(details.message);
17
+ this.name = "ElsiumError";
18
+ this.code = details.code;
19
+ this.provider = details.provider;
20
+ this.model = details.model;
21
+ this.statusCode = details.statusCode;
22
+ this.retryable = details.retryable;
23
+ this.retryAfterMs = details.retryAfterMs;
24
+ this.cause = details.cause;
25
+ this.metadata = details.metadata;
26
+ }
27
+ toJSON() {
28
+ return {
29
+ name: this.name,
30
+ code: this.code,
31
+ message: this.message,
32
+ provider: this.provider,
33
+ model: this.model,
34
+ statusCode: this.statusCode,
35
+ retryable: this.retryable,
36
+ retryAfterMs: this.retryAfterMs,
37
+ metadata: this.metadata
38
+ };
39
+ }
40
+ static providerError(message, opts) {
41
+ return new ElsiumError({
42
+ code: "PROVIDER_ERROR",
43
+ message,
44
+ provider: opts.provider,
45
+ statusCode: opts.statusCode,
46
+ retryable: opts.retryable ?? false,
47
+ cause: opts.cause
48
+ });
49
+ }
50
+ static rateLimit(provider, retryAfterMs) {
51
+ return new ElsiumError({
52
+ code: "RATE_LIMIT",
53
+ message: `Rate limited by ${provider}`,
54
+ provider,
55
+ statusCode: 429,
56
+ retryable: true,
57
+ retryAfterMs
58
+ });
59
+ }
60
+ static authError(provider) {
61
+ return new ElsiumError({
62
+ code: "AUTH_ERROR",
63
+ message: `Authentication failed for ${provider}. Check your API key.`,
64
+ provider,
65
+ statusCode: 401,
66
+ retryable: false
67
+ });
68
+ }
69
+ static timeout(provider, timeoutMs) {
70
+ return new ElsiumError({
71
+ code: "TIMEOUT",
72
+ message: `Request to ${provider} timed out after ${timeoutMs}ms`,
73
+ provider,
74
+ retryable: true
75
+ });
76
+ }
77
+ static validation(message, metadata) {
78
+ return new ElsiumError({
79
+ code: "VALIDATION_ERROR",
80
+ message,
81
+ retryable: false,
82
+ metadata
83
+ });
84
+ }
85
+ static budgetExceeded(spent, budget) {
86
+ return new ElsiumError({
87
+ code: "BUDGET_EXCEEDED",
88
+ message: `Token budget exceeded: spent ${spent}, budget ${budget}`,
89
+ retryable: false,
90
+ metadata: { spent, budget }
91
+ });
92
+ }
93
+ }
94
+ // ../core/src/utils.ts
95
+ import { randomBytes } from "crypto";
96
+ function cryptoHex(bytes) {
97
+ return randomBytes(bytes).toString("hex");
98
+ }
99
+ function generateId(prefix = "els") {
100
+ const timestamp = Date.now().toString(36);
101
+ const random = cryptoHex(4);
102
+ return `${prefix}_${timestamp}_${random}`;
103
+ }
104
+ // ../core/src/logger.ts
105
+ var LOG_LEVELS = {
106
+ debug: 0,
107
+ info: 1,
108
+ warn: 2,
109
+ error: 3
110
+ };
111
+ function createLogger(options = {}) {
112
+ const { level = "info", pretty = false, context = {} } = options;
113
+ const minLevel = LOG_LEVELS[level];
114
+ function log(logLevel, message, data) {
115
+ if (LOG_LEVELS[logLevel] < minLevel)
116
+ return;
117
+ const entry = {
118
+ ...context,
119
+ level: logLevel,
120
+ message,
121
+ timestamp: new Date().toISOString(),
122
+ ...data ? { data } : {}
123
+ };
124
+ const output = pretty ? JSON.stringify(entry, null, 2) : JSON.stringify(entry);
125
+ if (logLevel === "error") {
126
+ console.error(output);
127
+ } else if (logLevel === "warn") {
128
+ console.warn(output);
129
+ } else {
130
+ console.log(output);
131
+ }
132
+ }
133
+ return {
134
+ debug: (msg, data) => log("debug", msg, data),
135
+ info: (msg, data) => log("info", msg, data),
136
+ warn: (msg, data) => log("warn", msg, data),
137
+ error: (msg, data) => log("error", msg, data),
138
+ child(childContext) {
139
+ return createLogger({
140
+ level,
141
+ pretty,
142
+ context: { ...context, ...childContext }
143
+ });
144
+ }
145
+ };
146
+ }
147
+ // src/client.ts
148
+ function createMCPClient(config) {
149
+ let process2 = null;
150
+ let connected = false;
151
+ let requestId = 0;
152
+ const pendingRequests = new Map;
153
+ let buffer = "";
154
+ const timeoutMs = config.timeoutMs ?? 30000;
155
+ function sendRequest(method, params) {
156
+ if (!process2?.stdin) {
157
+ return Promise.reject(new ElsiumError({
158
+ code: "NETWORK_ERROR",
159
+ message: "MCP client not connected",
160
+ retryable: false
161
+ }));
162
+ }
163
+ const id = ++requestId;
164
+ const request = {
165
+ jsonrpc: "2.0",
166
+ id,
167
+ method,
168
+ ...params ? { params } : {}
169
+ };
170
+ return new Promise((resolve, reject) => {
171
+ const timer = setTimeout(() => {
172
+ pendingRequests.delete(id);
173
+ reject(new ElsiumError({
174
+ code: "TIMEOUT",
175
+ message: `MCP request timed out after ${timeoutMs}ms`,
176
+ retryable: true
177
+ }));
178
+ }, timeoutMs);
179
+ pendingRequests.set(id, {
180
+ resolve: (value) => {
181
+ clearTimeout(timer);
182
+ resolve(value);
183
+ },
184
+ reject: (error) => {
185
+ clearTimeout(timer);
186
+ reject(error);
187
+ }
188
+ });
189
+ const proc = process2;
190
+ if (proc?.stdin) {
191
+ proc.stdin.write(`${JSON.stringify(request)}
192
+ `);
193
+ }
194
+ });
195
+ }
196
+ function sendNotification(method, params) {
197
+ const proc = process2;
198
+ if (!proc?.stdin)
199
+ return;
200
+ const notification = {
201
+ jsonrpc: "2.0",
202
+ method,
203
+ ...params ? { params } : {}
204
+ };
205
+ proc.stdin.write(`${JSON.stringify(notification)}
206
+ `);
207
+ }
208
+ const MAX_LINE_LENGTH = 1024 * 1024;
209
+ function processResponseLine(line) {
210
+ if (!line.trim())
211
+ return;
212
+ let response;
213
+ try {
214
+ response = JSON.parse(line);
215
+ } catch {
216
+ return;
217
+ }
218
+ const pending = pendingRequests.get(response.id);
219
+ if (!pending)
220
+ return;
221
+ pendingRequests.delete(response.id);
222
+ if (response.error) {
223
+ pending.reject(new ElsiumError({
224
+ code: "PROVIDER_ERROR",
225
+ message: `MCP error: ${response.error.message}`,
226
+ retryable: false,
227
+ metadata: { code: response.error.code }
228
+ }));
229
+ } else {
230
+ pending.resolve(response.result);
231
+ }
232
+ }
233
+ function handleData(data) {
234
+ buffer += data;
235
+ if (buffer.length > MAX_LINE_LENGTH) {
236
+ buffer = "";
237
+ return;
238
+ }
239
+ const lines = buffer.split(`
240
+ `);
241
+ buffer = lines.pop() ?? "";
242
+ for (const line of lines) {
243
+ processResponseLine(line);
244
+ }
245
+ }
246
+ return {
247
+ get connected() {
248
+ return connected;
249
+ },
250
+ async connect() {
251
+ if (connected)
252
+ return;
253
+ const childEnv = {
254
+ PATH: globalThis.process?.env?.PATH ?? "",
255
+ HOME: globalThis.process?.env?.HOME ?? "",
256
+ ...config.env ?? {}
257
+ };
258
+ process2 = spawn(config.command, config.args ?? [], {
259
+ stdio: ["pipe", "pipe", "pipe"],
260
+ env: childEnv
261
+ });
262
+ process2.stdout?.setEncoding("utf-8");
263
+ process2.stdout?.on("data", handleData);
264
+ process2.on("error", (err2) => {
265
+ connected = false;
266
+ for (const [, pending] of pendingRequests) {
267
+ pending.reject(err2);
268
+ }
269
+ pendingRequests.clear();
270
+ });
271
+ process2.on("exit", (code) => {
272
+ connected = false;
273
+ if (pendingRequests.size > 0) {
274
+ const err2 = new Error(`MCP subprocess exited with code ${code}`);
275
+ for (const [, pending] of pendingRequests) {
276
+ pending.reject(err2);
277
+ }
278
+ pendingRequests.clear();
279
+ }
280
+ });
281
+ await sendRequest("initialize", {
282
+ protocolVersion: "2024-11-05",
283
+ capabilities: {},
284
+ clientInfo: { name: `elsium-mcp-${config.name}`, version: "0.1.0" }
285
+ });
286
+ sendNotification("notifications/initialized");
287
+ connected = true;
288
+ },
289
+ async disconnect() {
290
+ if (!connected || !process2)
291
+ return;
292
+ try {
293
+ process2.stdin?.end();
294
+ process2.kill();
295
+ } catch {}
296
+ connected = false;
297
+ process2 = null;
298
+ for (const [, { reject }] of pendingRequests) {
299
+ reject(new Error("MCP client disconnected"));
300
+ }
301
+ pendingRequests.clear();
302
+ },
303
+ async listTools() {
304
+ const result = await sendRequest("tools/list");
305
+ return result.tools ?? [];
306
+ },
307
+ async callTool(name, args) {
308
+ const result = await sendRequest("tools/call", { name, arguments: args });
309
+ const textContent = result.content?.filter((c) => c.type === "text").map((c) => c.text).join(`
310
+ `);
311
+ return textContent ?? result;
312
+ },
313
+ async toElsiumTools() {
314
+ const mcpTools = await this.listTools();
315
+ const client = this;
316
+ return mcpTools.map((mcpTool) => {
317
+ const tool = {
318
+ name: mcpTool.name,
319
+ description: mcpTool.description,
320
+ inputSchema: { _def: { typeName: "ZodObject" } },
321
+ rawSchema: mcpTool.inputSchema,
322
+ timeoutMs,
323
+ async execute(input, partialCtx) {
324
+ const toolCallId = partialCtx?.toolCallId ?? generateId("tc");
325
+ const startTime = performance.now();
326
+ try {
327
+ const result = await client.callTool(mcpTool.name, input ?? {});
328
+ return {
329
+ success: true,
330
+ data: result,
331
+ toolCallId,
332
+ durationMs: Math.round(performance.now() - startTime)
333
+ };
334
+ } catch (error) {
335
+ return {
336
+ success: false,
337
+ error: error instanceof Error ? error.message : String(error),
338
+ toolCallId,
339
+ durationMs: Math.round(performance.now() - startTime)
340
+ };
341
+ }
342
+ },
343
+ toDefinition() {
344
+ return {
345
+ name: mcpTool.name,
346
+ description: mcpTool.description,
347
+ inputSchema: mcpTool.inputSchema
348
+ };
349
+ }
350
+ };
351
+ return tool;
352
+ });
353
+ }
354
+ };
355
+ }
356
+ // src/server.ts
357
+ var log = createLogger();
358
+ function createMCPServer(config) {
359
+ let running = false;
360
+ const toolMap = new Map(config.tools.map((t) => [t.name, t]));
361
+ function handleRequest(request) {
362
+ const id = request.id ?? 0;
363
+ switch (request.method) {
364
+ case "initialize": {
365
+ return {
366
+ jsonrpc: "2.0",
367
+ id,
368
+ result: {
369
+ protocolVersion: "2024-11-05",
370
+ capabilities: { tools: {} },
371
+ serverInfo: {
372
+ name: config.name,
373
+ version: config.version ?? "0.1.0"
374
+ }
375
+ }
376
+ };
377
+ }
378
+ case "notifications/initialized": {
379
+ return null;
380
+ }
381
+ case "tools/list": {
382
+ const tools = config.tools.map((t) => {
383
+ const def = t.toDefinition();
384
+ return {
385
+ name: def.name,
386
+ description: def.description,
387
+ inputSchema: def.inputSchema
388
+ };
389
+ });
390
+ return {
391
+ jsonrpc: "2.0",
392
+ id,
393
+ result: { tools }
394
+ };
395
+ }
396
+ case "tools/call": {
397
+ return null;
398
+ }
399
+ default: {
400
+ return {
401
+ jsonrpc: "2.0",
402
+ id,
403
+ error: {
404
+ code: -32601,
405
+ message: `Method not found: ${request.method}`
406
+ }
407
+ };
408
+ }
409
+ }
410
+ }
411
+ async function handleToolCall(request) {
412
+ const id = request.id ?? 0;
413
+ const name = request.params?.name;
414
+ const args = request.params?.arguments ?? {};
415
+ const tool = toolMap.get(name);
416
+ if (!tool) {
417
+ return {
418
+ jsonrpc: "2.0",
419
+ id,
420
+ error: {
421
+ code: -32602,
422
+ message: `Unknown tool: ${name}`
423
+ }
424
+ };
425
+ }
426
+ const result = await tool.execute(args, { toolCallId: generateId("tc") });
427
+ if (result.success) {
428
+ return {
429
+ jsonrpc: "2.0",
430
+ id,
431
+ result: {
432
+ content: [
433
+ {
434
+ type: "text",
435
+ text: typeof result.data === "string" ? result.data : JSON.stringify(result.data, null, 2)
436
+ }
437
+ ]
438
+ }
439
+ };
440
+ }
441
+ return {
442
+ jsonrpc: "2.0",
443
+ id,
444
+ result: {
445
+ content: [{ type: "text", text: result.error ?? "Tool execution failed" }],
446
+ isError: true
447
+ }
448
+ };
449
+ }
450
+ function writeLine(data) {
451
+ process.stdout.write(`${JSON.stringify(data)}
452
+ `);
453
+ }
454
+ async function processRequestLine(line) {
455
+ if (!line.trim())
456
+ return;
457
+ let request;
458
+ try {
459
+ request = JSON.parse(line);
460
+ } catch {
461
+ return;
462
+ }
463
+ if (request.method === "tools/call") {
464
+ const response2 = await handleToolCall(request);
465
+ writeLine(response2);
466
+ return;
467
+ }
468
+ const response = handleRequest(request);
469
+ if (response) {
470
+ writeLine(response);
471
+ }
472
+ }
473
+ return {
474
+ get running() {
475
+ return running;
476
+ },
477
+ async start() {
478
+ if (running)
479
+ return;
480
+ running = true;
481
+ const MAX_BUFFER_SIZE = 1024 * 1024;
482
+ process.stdin.setEncoding("utf-8");
483
+ let buffer = "";
484
+ let processing = false;
485
+ const pendingChunks = [];
486
+ async function drainPendingChunks() {
487
+ while (pendingChunks.length > 0) {
488
+ const chunk = pendingChunks[0];
489
+ pendingChunks.shift();
490
+ buffer += chunk;
491
+ if (buffer.length > MAX_BUFFER_SIZE) {
492
+ log.error("MCP server: buffer size limit exceeded, resetting");
493
+ buffer = "";
494
+ continue;
495
+ }
496
+ const lines = buffer.split(`
497
+ `);
498
+ buffer = lines.pop() ?? "";
499
+ for (const line of lines) {
500
+ await processRequestLine(line);
501
+ }
502
+ }
503
+ }
504
+ async function processQueue() {
505
+ if (processing)
506
+ return;
507
+ processing = true;
508
+ try {
509
+ await drainPendingChunks();
510
+ } finally {
511
+ processing = false;
512
+ }
513
+ }
514
+ process.stdin.on("data", (chunk) => {
515
+ pendingChunks.push(chunk);
516
+ processQueue();
517
+ });
518
+ process.stdin.on("end", () => {
519
+ running = false;
520
+ });
521
+ },
522
+ stop() {
523
+ running = false;
524
+ }
525
+ };
526
+ }
527
+ export {
528
+ createMCPServer,
529
+ createMCPClient
530
+ };
@@ -0,0 +1,13 @@
1
+ import type { Tool } from '@elsium-ai/tools';
2
+ export interface MCPServerConfig {
3
+ name: string;
4
+ version?: string;
5
+ tools: Tool[];
6
+ }
7
+ export interface MCPServer {
8
+ start(): Promise<void>;
9
+ stop(): void;
10
+ readonly running: boolean;
11
+ }
12
+ export declare function createMCPServer(config: MCPServerConfig): MCPServer;
13
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAI5C,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,IAAI,EAAE,CAAA;CACb;AAgBD,MAAM,WAAW,SAAS;IACzB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,IAAI,IAAI,IAAI,CAAA;IACZ,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CACzB;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAmMlE"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@elsium-ai/mcp",
3
+ "version": "0.1.6",
4
+ "description": "Model Context Protocol (MCP) support for ElsiumAI — bidirectional bridge",
5
+ "license": "MIT",
6
+ "author": "Eric Utrera <ebutrera9103@gmail.com>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/elsium-ai/elsium-ai",
10
+ "directory": "packages/mcp"
11
+ },
12
+ "type": "module",
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "import": "./dist/index.js",
18
+ "types": "./dist/index.d.ts"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "scripts": {
25
+ "build": "bun build ./src/index.ts --outdir ./dist --target bun && bun x tsc -p tsconfig.build.json --emitDeclarationOnly",
26
+ "dev": "bun --watch src/index.ts"
27
+ },
28
+ "dependencies": {
29
+ "@elsium-ai/core": "workspace:*",
30
+ "@elsium-ai/tools": "workspace:*"
31
+ },
32
+ "devDependencies": {
33
+ "bun-types": "^1.3.0",
34
+ "typescript": "^5.7.0"
35
+ }
36
+ }