muxed 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.
@@ -0,0 +1,171 @@
1
+ import { CompleteResult, GetPromptResult, Implementation, Prompt, Prompt as Prompt$1, ReadResourceResult, Resource, Resource as Resource$1, ServerCapabilities, Tool, Tool as Tool$1 } from "@modelcontextprotocol/sdk/types.js";
2
+
3
+ //#region src/core/types.d.ts
4
+ type StdioServerConfig = {
5
+ command: string;
6
+ args?: string[];
7
+ env?: Record<string, string>;
8
+ cwd?: string;
9
+ };
10
+ type ClientCredentialsAuth = {
11
+ type: 'client_credentials';
12
+ clientId: string;
13
+ clientSecret: string;
14
+ scope?: string;
15
+ };
16
+ type AuthorizationCodeAuth = {
17
+ type: 'authorization_code';
18
+ clientId?: string;
19
+ clientSecret?: string;
20
+ scope?: string;
21
+ callbackPort?: number;
22
+ };
23
+ type OAuthConfig = ClientCredentialsAuth | AuthorizationCodeAuth;
24
+ type HttpServerConfig = {
25
+ url: string;
26
+ transport?: 'streamable-http' | 'sse';
27
+ headers?: Record<string, string>;
28
+ sessionId?: string;
29
+ reconnection?: {
30
+ maxDelay?: number;
31
+ initialDelay?: number;
32
+ growFactor?: number;
33
+ maxRetries?: number;
34
+ };
35
+ auth?: OAuthConfig;
36
+ };
37
+ type ServerConfig = StdioServerConfig | HttpServerConfig;
38
+ type DaemonConfig = {
39
+ idleTimeout?: number;
40
+ connectTimeout?: number;
41
+ requestTimeout?: number;
42
+ healthCheckInterval?: number;
43
+ maxRestartAttempts?: number;
44
+ maxTotalTimeout?: number;
45
+ taskExpiryTimeout?: number;
46
+ logLevel?: 'debug' | 'info' | 'warn' | 'error';
47
+ shutdownTimeout?: number;
48
+ http?: {
49
+ enabled?: boolean;
50
+ port?: number;
51
+ host?: string;
52
+ };
53
+ };
54
+ type ServerConnectionStatus = 'connecting' | 'connected' | 'error' | 'closed';
55
+ type ServerState = {
56
+ name: string;
57
+ config: ServerConfig;
58
+ status: ServerConnectionStatus;
59
+ error?: string;
60
+ serverInfo?: Implementation;
61
+ capabilities?: ServerCapabilities;
62
+ protocolVersion?: string;
63
+ instructions?: string;
64
+ restartCount?: number;
65
+ lastHealthCheck?: string;
66
+ consecutiveFailures?: number;
67
+ };
68
+ //#endregion
69
+ //#region src/client/socket.d.ts
70
+ declare class TooldError extends Error {
71
+ readonly code: number;
72
+ readonly data?: unknown;
73
+ constructor(code: number, message: string, data?: unknown);
74
+ }
75
+ //#endregion
76
+ //#region src/client/index.d.ts
77
+ /** Tool type map – empty by default, populated by `toold typegen`. */
78
+ interface TooldToolMap {}
79
+ type HasTools = keyof TooldToolMap extends never ? false : true;
80
+ type CreateClientOptions = {
81
+ /** Path to toold.config.json. Uses default resolution if omitted. */configPath?: string; /** Skip auto-starting the daemon. Throws if daemon is not running. Default: true. */
82
+ autoStart?: boolean;
83
+ };
84
+ type CallOptions = {
85
+ /** Request timeout in milliseconds. */timeout?: number;
86
+ };
87
+ type CallResult = {
88
+ content: Array<{
89
+ type: string;
90
+ text?: string;
91
+ mimeType?: string;
92
+ data?: string;
93
+ name?: string;
94
+ uri?: string;
95
+ resource?: {
96
+ text?: string;
97
+ blob?: string;
98
+ mimeType?: string;
99
+ };
100
+ }>;
101
+ structuredContent?: Record<string, unknown>;
102
+ isError?: boolean;
103
+ };
104
+ type TaskHandle = {
105
+ taskId: string;
106
+ server: string;
107
+ status: string;
108
+ };
109
+ type DaemonStatus = {
110
+ pid: number;
111
+ uptime: number;
112
+ serverCount: number;
113
+ servers: ServerState[];
114
+ };
115
+ type ReloadResult = {
116
+ added: string[];
117
+ removed: string[];
118
+ changed: string[];
119
+ };
120
+ type TaskStatus = Record<string, unknown>;
121
+ type TaskResult = Record<string, unknown>;
122
+ type TaskCancelResult = Record<string, unknown>;
123
+ declare class TooldClient {
124
+ #private;
125
+ /** @internal Use `createClient()` instead. */
126
+ constructor(send: (method: string, params?: Record<string, unknown>) => Promise<unknown>);
127
+ servers(): Promise<ServerState[]>;
128
+ tools(server?: string): Promise<Array<{
129
+ server: string;
130
+ tool: Tool$1;
131
+ }>>;
132
+ tool(name: string): Promise<Tool$1>;
133
+ grep(pattern: string): Promise<Array<{
134
+ server: string;
135
+ tool: Tool$1;
136
+ }>>;
137
+ call<K extends string>(name: HasTools extends true ? (K extends keyof TooldToolMap ? K : K & {}) : string, args?: K extends keyof TooldToolMap ? TooldToolMap[K]['input'] : Record<string, unknown>, options?: CallOptions): Promise<K extends keyof TooldToolMap ? TooldToolMap[K]['output'] : CallResult>;
138
+ callAsync(name: string, args?: Record<string, unknown>): Promise<TaskHandle>;
139
+ resources(server?: string): Promise<Array<{
140
+ server: string;
141
+ resource: Resource$1;
142
+ }>>;
143
+ read(server: string, uri: string): Promise<ReadResourceResult>;
144
+ prompts(server?: string): Promise<Array<{
145
+ server: string;
146
+ prompt: Prompt$1;
147
+ }>>;
148
+ prompt(server: string, name: string, args?: Record<string, string>): Promise<GetPromptResult>;
149
+ complete(server: string, ref: {
150
+ type: string;
151
+ name: string;
152
+ uri?: string;
153
+ }, argument: {
154
+ name: string;
155
+ value: string;
156
+ }): Promise<CompleteResult>;
157
+ tasks(server?: string): Promise<Array<{
158
+ server: string;
159
+ tasks: Array<Record<string, unknown>>;
160
+ }>>;
161
+ task(server: string, taskId: string): Promise<TaskStatus>;
162
+ taskResult(server: string, taskId: string): Promise<TaskResult>;
163
+ taskCancel(server: string, taskId: string): Promise<TaskCancelResult>;
164
+ status(): Promise<DaemonStatus>;
165
+ reload(configPath?: string): Promise<ReloadResult>;
166
+ stop(): Promise<void>;
167
+ close(): void;
168
+ }
169
+ declare function createClient(options?: CreateClientOptions): Promise<TooldClient>;
170
+ //#endregion
171
+ export { CallOptions, CallResult, CreateClientOptions, type DaemonConfig, DaemonStatus, type HttpServerConfig, type Prompt, ReloadResult, type Resource, type ServerConfig, type ServerState, type StdioServerConfig, TaskCancelResult, TaskHandle, TaskResult, TaskStatus, type Tool, TooldClient, TooldError, TooldToolMap, createClient };
@@ -0,0 +1,351 @@
1
+ import net from "node:net";
2
+ import { fork } from "node:child_process";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import os from "node:os";
6
+ function getTooldDir() {
7
+ return path.join(os.homedir(), ".toold");
8
+ }
9
+ function getSocketPath() {
10
+ return path.join(getTooldDir(), "toold.sock");
11
+ }
12
+ function getPidPath() {
13
+ return path.join(getTooldDir(), "toold.pid");
14
+ }
15
+ function ensureTooldDir() {
16
+ fs.mkdirSync(getTooldDir(), { recursive: true });
17
+ }
18
+ function getLockPath() {
19
+ return path.join(getTooldDir(), "toold.lock");
20
+ }
21
+ function getDaemonPid() {
22
+ try {
23
+ const content = fs.readFileSync(getPidPath(), "utf-8").trim();
24
+ const pid = parseInt(content, 10);
25
+ return Number.isFinite(pid) && pid > 0 ? pid : null;
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+ function isProcessAlive(pid) {
31
+ try {
32
+ process.kill(pid, 0);
33
+ return true;
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+ function isTooldProcess(pid) {
39
+ try {
40
+ const cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, "utf-8");
41
+ return cmdline.includes("toold") || cmdline.includes("node");
42
+ } catch {
43
+ return isProcessAlive(pid);
44
+ }
45
+ }
46
+ function tryConnectSocket(socketPath) {
47
+ return new Promise((resolve) => {
48
+ const socket = net.createConnection(socketPath);
49
+ const timeout = setTimeout(() => {
50
+ socket.destroy();
51
+ resolve(false);
52
+ }, 2e3);
53
+ socket.on("connect", () => {
54
+ clearTimeout(timeout);
55
+ socket.destroy();
56
+ resolve(true);
57
+ });
58
+ socket.on("error", () => {
59
+ clearTimeout(timeout);
60
+ resolve(false);
61
+ });
62
+ });
63
+ }
64
+ function acquireLock() {
65
+ const lockPath = getLockPath();
66
+ try {
67
+ ensureTooldDir();
68
+ fs.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
69
+ return true;
70
+ } catch (err) {
71
+ if (err.code === "EEXIST") try {
72
+ const lockPid = parseInt(fs.readFileSync(lockPath, "utf-8").trim(), 10);
73
+ if (!Number.isFinite(lockPid) || !isProcessAlive(lockPid) || !isTooldProcess(lockPid)) {
74
+ fs.unlinkSync(lockPath);
75
+ try {
76
+ fs.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
77
+ return true;
78
+ } catch {
79
+ return false;
80
+ }
81
+ }
82
+ } catch {
83
+ try {
84
+ fs.unlinkSync(lockPath);
85
+ } catch {}
86
+ }
87
+ return false;
88
+ }
89
+ }
90
+ function releaseLock() {
91
+ try {
92
+ fs.unlinkSync(getLockPath());
93
+ } catch {}
94
+ }
95
+ async function isDaemonRunning() {
96
+ const pid = getDaemonPid();
97
+ if (pid === null) return false;
98
+ if (!isProcessAlive(pid)) return false;
99
+ if (!isTooldProcess(pid)) return false;
100
+ return tryConnectSocket(getSocketPath());
101
+ }
102
+ async function cleanupStaleFiles() {
103
+ const pidPath = getPidPath();
104
+ const socketPath = getSocketPath();
105
+ const pid = getDaemonPid();
106
+ if (pid !== null && !isProcessAlive(pid)) {
107
+ try {
108
+ fs.unlinkSync(pidPath);
109
+ } catch {}
110
+ try {
111
+ fs.unlinkSync(socketPath);
112
+ } catch {}
113
+ releaseLock();
114
+ return;
115
+ }
116
+ if (pid !== null && isProcessAlive(pid) && !isTooldProcess(pid)) {
117
+ try {
118
+ fs.unlinkSync(pidPath);
119
+ } catch {}
120
+ try {
121
+ fs.unlinkSync(socketPath);
122
+ } catch {}
123
+ releaseLock();
124
+ return;
125
+ }
126
+ if (pid === null) {
127
+ try {
128
+ fs.unlinkSync(socketPath);
129
+ } catch {}
130
+ releaseLock();
131
+ }
132
+ }
133
+ async function daemonize(configPath) {
134
+ if (!acquireLock()) {
135
+ for (let i = 0; i < 10; i++) {
136
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
137
+ if (await isDaemonRunning()) return;
138
+ }
139
+ throw new Error("Timed out waiting for another process to start the daemon.");
140
+ }
141
+ try {
142
+ const cliEntry = process.argv[1];
143
+ const args = ["--daemon"];
144
+ if (configPath) args.push("--config", configPath);
145
+ await new Promise((resolve, reject) => {
146
+ const child = fork(cliEntry, args, {
147
+ detached: true,
148
+ stdio: [
149
+ "ignore",
150
+ "ignore",
151
+ "ignore",
152
+ "ipc"
153
+ ]
154
+ });
155
+ const timeout = setTimeout(() => {
156
+ child.unref();
157
+ child.disconnect?.();
158
+ reject(/* @__PURE__ */ new Error("Daemon failed to start: timeout waiting for ready signal"));
159
+ }, 1e4);
160
+ child.on("message", (msg) => {
161
+ if (msg === "ready") {
162
+ clearTimeout(timeout);
163
+ child.unref();
164
+ child.disconnect?.();
165
+ resolve();
166
+ }
167
+ });
168
+ child.on("error", (err) => {
169
+ clearTimeout(timeout);
170
+ reject(/* @__PURE__ */ new Error(`Daemon failed to start: ${err.message}`));
171
+ });
172
+ child.on("exit", (code) => {
173
+ clearTimeout(timeout);
174
+ if (code !== 0) reject(/* @__PURE__ */ new Error(`Daemon exited with code ${code}`));
175
+ });
176
+ });
177
+ } finally {
178
+ releaseLock();
179
+ }
180
+ }
181
+ var TooldError = class extends Error {
182
+ code;
183
+ data;
184
+ constructor(code, message, data) {
185
+ super(message);
186
+ this.name = "TooldError";
187
+ this.code = code;
188
+ this.data = data;
189
+ }
190
+ };
191
+ async function waitForSocket(socketPath, retries) {
192
+ for (const delay of retries) {
193
+ await new Promise((resolve) => setTimeout(resolve, delay));
194
+ if (await new Promise((resolve) => {
195
+ const sock = net.createConnection(socketPath);
196
+ const timeout = setTimeout(() => {
197
+ sock.destroy();
198
+ resolve(false);
199
+ }, 2e3);
200
+ sock.on("connect", () => {
201
+ clearTimeout(timeout);
202
+ sock.destroy();
203
+ resolve(true);
204
+ });
205
+ sock.on("error", () => {
206
+ clearTimeout(timeout);
207
+ resolve(false);
208
+ });
209
+ })) return;
210
+ }
211
+ throw new Error("Daemon started but socket is not responding");
212
+ }
213
+ async function ensureDaemon(configPath) {
214
+ if (await isDaemonRunning()) return;
215
+ await cleanupStaleFiles();
216
+ await daemonize(configPath);
217
+ await waitForSocket(getSocketPath(), [
218
+ 100,
219
+ 200,
220
+ 400
221
+ ]);
222
+ }
223
+ async function sendRequest(method, params) {
224
+ const socketPath = getSocketPath();
225
+ return new Promise((resolve, reject) => {
226
+ const socket = net.createConnection(socketPath);
227
+ let buffer = "";
228
+ socket.on("error", (err) => {
229
+ if (err.code === "ENOENT") reject(/* @__PURE__ */ new Error("Daemon is not running. Run `toold status` to check."));
230
+ else if (err.code === "ECONNREFUSED") reject(/* @__PURE__ */ new Error("Daemon may have crashed. Try running a command to auto-restart it."));
231
+ else reject(err);
232
+ });
233
+ socket.on("connect", () => {
234
+ const request = {
235
+ jsonrpc: "2.0",
236
+ id: 1,
237
+ method,
238
+ ...params ? { params } : {}
239
+ };
240
+ socket.write(JSON.stringify(request) + "\n");
241
+ });
242
+ socket.on("data", (data) => {
243
+ buffer += data.toString();
244
+ const newlineIndex = buffer.indexOf("\n");
245
+ if (newlineIndex === -1) return;
246
+ const line = buffer.slice(0, newlineIndex).trim();
247
+ socket.destroy();
248
+ try {
249
+ const response = JSON.parse(line);
250
+ if (response.error) reject(new TooldError(response.error.code, response.error.message, response.error.data));
251
+ else resolve(response.result);
252
+ } catch {
253
+ reject(/* @__PURE__ */ new Error("Invalid response from daemon"));
254
+ }
255
+ });
256
+ });
257
+ }
258
+ var TooldClient = class {
259
+ #send;
260
+ constructor(send) {
261
+ this.#send = send;
262
+ }
263
+ async servers() {
264
+ return await this.#send("servers/list");
265
+ }
266
+ async tools(server) {
267
+ return await this.#send("tools/list", server ? { server } : void 0);
268
+ }
269
+ async tool(name) {
270
+ return await this.#send("tools/info", { name });
271
+ }
272
+ async grep(pattern) {
273
+ return await this.#send("tools/grep", { pattern });
274
+ }
275
+ async call(name, args, options) {
276
+ return await this.#send("tools/call", {
277
+ name,
278
+ arguments: args ?? {},
279
+ ...options?.timeout ? { timeout: options.timeout } : {}
280
+ });
281
+ }
282
+ async callAsync(name, args) {
283
+ return await this.#send("tools/call-async", {
284
+ name,
285
+ arguments: args ?? {}
286
+ });
287
+ }
288
+ async resources(server) {
289
+ return await this.#send("resources/list", server ? { server } : void 0);
290
+ }
291
+ async read(server, uri) {
292
+ return await this.#send("resources/read", {
293
+ server,
294
+ uri
295
+ });
296
+ }
297
+ async prompts(server) {
298
+ return await this.#send("prompts/list", server ? { server } : void 0);
299
+ }
300
+ async prompt(server, name, args) {
301
+ return await this.#send("prompts/get", {
302
+ server,
303
+ name,
304
+ ...args ? { arguments: args } : {}
305
+ });
306
+ }
307
+ async complete(server, ref, argument) {
308
+ return await this.#send("completions/complete", {
309
+ server,
310
+ ref,
311
+ argument
312
+ });
313
+ }
314
+ async tasks(server) {
315
+ return await this.#send("tasks/list", server ? { server } : void 0);
316
+ }
317
+ async task(server, taskId) {
318
+ return await this.#send("tasks/get", {
319
+ server,
320
+ taskId
321
+ });
322
+ }
323
+ async taskResult(server, taskId) {
324
+ return await this.#send("tasks/result", {
325
+ server,
326
+ taskId
327
+ });
328
+ }
329
+ async taskCancel(server, taskId) {
330
+ return await this.#send("tasks/cancel", {
331
+ server,
332
+ taskId
333
+ });
334
+ }
335
+ async status() {
336
+ return await this.#send("daemon/status");
337
+ }
338
+ async reload(configPath) {
339
+ return await this.#send("config/reload", configPath ? { configPath } : void 0);
340
+ }
341
+ async stop() {
342
+ await this.#send("daemon/stop");
343
+ }
344
+ close() {}
345
+ };
346
+ async function createClient(options) {
347
+ const { configPath, autoStart = true } = options ?? {};
348
+ if (autoStart) await ensureDaemon(configPath);
349
+ return new TooldClient(sendRequest);
350
+ }
351
+ export { TooldClient, TooldError, createClient };
@@ -0,0 +1,2 @@
1
+ // Stub – run `muxed typegen` to populate with tool types
2
+ export {};
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "muxed",
3
+ "version": "0.1.0",
4
+ "description": "MCP server daemon and aggregator CLI – aggregate all your Model Context Protocol servers behind a single background daemon with lazy start, auto-reconnect, and idle shutdown",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "import": "./dist/client/index.mjs",
9
+ "types": "./dist/client/index.d.mts"
10
+ },
11
+ "./client": {
12
+ "import": "./dist/client/index.mjs",
13
+ "types": "./dist/client/index.d.mts"
14
+ }
15
+ },
16
+ "bin": {
17
+ "muxed": "./bin/cli.mjs"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "bin",
22
+ "muxed.generated.d.ts"
23
+ ],
24
+ "engines": {
25
+ "node": ">=20"
26
+ },
27
+ "keywords": [
28
+ "mcp",
29
+ "model-context-protocol",
30
+ "mcp-server",
31
+ "mcp-proxy",
32
+ "mcp-aggregator",
33
+ "mcp-daemon",
34
+ "mcp-cli",
35
+ "mcp-tools",
36
+ "ai-coding",
37
+ "claude-code",
38
+ "cursor",
39
+ "windsurf",
40
+ "ai-agent",
41
+ "tool-server",
42
+ "daemon",
43
+ "cli"
44
+ ],
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/skoob13/muxed.git",
48
+ "directory": "packages/muxed"
49
+ },
50
+ "homepage": "https://github.com/skoob13/muxed#readme",
51
+ "bugs": {
52
+ "url": "https://github.com/skoob13/muxed/issues"
53
+ },
54
+ "author": "Georgiy Tarasov",
55
+ "license": "MIT",
56
+ "dependencies": {
57
+ "@modelcontextprotocol/sdk": "^1.26.0",
58
+ "commander": "^14.0.0",
59
+ "json-schema-to-typescript": "^15.0.4",
60
+ "zod": "^4.3.6"
61
+ },
62
+ "devDependencies": {
63
+ "@modelcontextprotocol/server-everything": "^2026.1.26",
64
+ "@types/node": "^22.0.0",
65
+ "obuild": "^0.4.22",
66
+ "prettier": "^3.8.1",
67
+ "typescript": "^5.9.3",
68
+ "vitest": "^4.0.17"
69
+ },
70
+ "scripts": {
71
+ "build": "obuild",
72
+ "dev": "node src/cli.ts",
73
+ "format": "prettier --write 'src/**/*.ts'",
74
+ "format:check": "prettier --check 'src/**/*.ts'",
75
+ "test": "vitest",
76
+ "type-check": "tsc --noEmit"
77
+ }
78
+ }