muxed 0.1.0 → 0.1.1

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 ADDED
@@ -0,0 +1,266 @@
1
+ # muxed – MCP Server Daemon & Aggregator CLI
2
+
3
+ > Aggregate all your [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) servers behind a single daemon. Fewer tokens. Faster execution. Better accuracy.
4
+
5
+ **muxed** is a background daemon and CLI that sits between your AI agent and your MCP servers. It solves the problems that [Anthropic](https://www.anthropic.com/engineering/code-execution-with-mcp) and [Cloudflare](https://blog.cloudflare.com/code-mode/) have been writing about: tool sprawl eating your context window, slow cold starts, and degraded accuracy as you add more servers.
6
+
7
+ ## The Problem
8
+
9
+ The MCP ecosystem has a scaling problem. Every tool you connect dumps its full schema into the model's context window. A standard setup with 3-4 MCP servers can consume 20-30% of the context before the agent even starts working. Anthropic's research shows this leads to a 98.7% token overhead on intermediate results. Cloudflare found that agents can't reliably handle more than a handful of servers before tool selection accuracy collapses.
10
+
11
+ **More tools = worse results.** Every token spent on MCP tool schemas is a token not spent on your actual task – or on the skills, prompts, and default tools that agents execute deterministically.
12
+
13
+ ## How muxed Fixes This
14
+
15
+ **muxed** is an optimization layer for your MCP infrastructure:
16
+
17
+ - **Fewer tokens in context** – Tools stay in the daemon, not in the prompt. Agents discover tools on-demand with `muxed grep` and `muxed info` instead of loading every schema upfront. Load only what you need, when you need it.
18
+ - **Faster execution** – Servers stay warm in a background daemon. No cold starts, no repeated connection negotiation. Call tools directly via CLI without round-tripping through the model.
19
+ - **More precise tool selection** – By offloading tool management to muxed, your agent's context window stays clean for what actually matters: reasoning, prompts, and the task at hand. Fewer tools in context means the model picks the right one more often.
20
+ - **Chain calls outside the model** – Pipe tool results through scripts and chain `muxed call` commands in bash without every intermediate result flowing through the LLM. This is the same insight behind Anthropic's code execution approach and Cloudflare's Code Mode – but available today as a simple CLI.
21
+ - **Context engineering wins** – When MCP tools are offloaded to muxed, your context window is freed for skills, prompts, and default tools – the things agents execute deterministically with higher priority. Context engineering beats tool bloat: fewer MCP schemas means your carefully crafted instructions actually get followed.
22
+
23
+ ### For Agents in Production
24
+
25
+ When you offload MCP tools to muxed, your agents' context windows free up for what actually gets executed reliably: skills, prompts, and default tools. These have deterministic priority – models always follow them. MCP tools, by contrast, compete for attention in a crowded context and get picked less reliably as you add more. By moving tool management out of the model and into a daemon, you're doing context engineering at the infrastructure level – your carefully crafted instructions get followed instead of being drowned out by 30,000 tokens of tool schemas.
26
+
27
+ ## Quick Start
28
+
29
+ ```bash
30
+ # Install globally
31
+ npm install -g muxed
32
+
33
+ # Or use directly with npx
34
+ npx muxed tools
35
+
36
+ # List all servers and their status
37
+ muxed servers
38
+
39
+ # List all available tools across all servers
40
+ muxed tools
41
+
42
+ # Call a tool
43
+ muxed call filesystem/read_file '{"path": "/tmp/hello.txt"}'
44
+
45
+ # Search tools by name or description
46
+ muxed grep "search"
47
+
48
+ # The daemon starts automatically and stops after 5 min idle
49
+ ```
50
+
51
+ ## Configuration
52
+
53
+ Create `muxed.config.json` in your project root (or `~/.config/muxed/config.json` for global config):
54
+
55
+ ```json
56
+ {
57
+ "mcpServers": {
58
+ "filesystem": {
59
+ "command": "npx",
60
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"],
61
+ "env": {}
62
+ },
63
+ "postgres": {
64
+ "command": "npx",
65
+ "args": ["-y", "@modelcontextprotocol/server-postgres"],
66
+ "env": { "DATABASE_URL": "postgresql://..." }
67
+ },
68
+ "remote-api": {
69
+ "url": "https://mcp.example.com/mcp",
70
+ "transport": "streamable-http",
71
+ "headers": { "Authorization": "Bearer ..." }
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ The format is intentionally compatible with the `mcpServers` section of `claude_desktop_config.json` – you can reuse your existing config.
78
+
79
+ ## Architecture
80
+
81
+ ```
82
+ muxed call server/tool '{}'
83
+ ──────────────────────────────────► ┌──────────────────────┐
84
+ (Unix socket: ~/.muxed/muxed.sock) │ muxed daemon │
85
+ │ │
86
+ muxed tools │ ServerManager(fs) │──► [stdio: filesystem]
87
+ ──────────────────────────────────► │ ServerManager(pg) │──► [stdio: postgres]
88
+ │ ServerManager(...) │──► [HTTP: remote]
89
+ muxed servers │ │
90
+ ──────────────────────────────────► └──────────────────────┘
91
+ (auto-exits after idle)
92
+ ```
93
+
94
+ **Lazy start**: The daemon spawns automatically when you run any command. No explicit `muxed start` needed.
95
+
96
+ **Idle shutdown**: After 5 minutes (configurable) with no requests, the daemon shuts down and cleans up.
97
+
98
+ ## CLI Reference
99
+
100
+ | Command | Description |
101
+ | ----------------------------------------------- | ---------------------------------------------------- |
102
+ | `muxed servers` | List servers with connection status and capabilities |
103
+ | `muxed tools [server]` | List available tools (with annotations) |
104
+ | `muxed info <server/tool>` | Tool schema details (inputSchema, outputSchema) |
105
+ | `muxed call <server/tool> [json]` | Invoke a tool |
106
+ | `muxed grep <pattern>` | Search tool names, titles, and descriptions |
107
+ | `muxed resources [server]` | List resources |
108
+ | `muxed read <server/resource>` | Read a resource |
109
+ | `muxed prompts [server]` | List prompt templates |
110
+ | `muxed prompt <server/prompt> [args]` | Render a prompt |
111
+ | `muxed completions <type> <name> <arg> <value>` | Argument auto-completions |
112
+ | `muxed tasks [server]` | List active tasks |
113
+ | `muxed status` | Daemon status, PID, uptime |
114
+ | `muxed reload` | Reload config, reconnect changed servers |
115
+ | `muxed stop` | Stop daemon manually |
116
+ | `muxed init` | Generate config from discovered MCP servers |
117
+
118
+ All commands support `--json` for machine-readable output.
119
+
120
+ ## Node.js API
121
+
122
+ muxed is also an npm package. Agents can write Node.js scripts that call MCP tools programmatically – with typed results, async/await, and the full npm ecosystem.
123
+
124
+ ```typescript
125
+ import { createClient } from 'muxed';
126
+
127
+ const client = await createClient();
128
+
129
+ // Discover tools
130
+ const tools = await client.grep('search');
131
+
132
+ // Call a tool
133
+ const result = await client.call('filesystem/read_file', {
134
+ path: '/tmp/config.json',
135
+ });
136
+
137
+ // Parallel calls across servers
138
+ const [users, tickets] = await Promise.all([
139
+ client.call('posthog/query-run', { query: { kind: 'HogQLQuery', query: 'SELECT ...' } }),
140
+ client.call('intercom/search-conversations', { query: 'billing', limit: 10 }),
141
+ ]);
142
+
143
+ // Async tasks for long-running operations
144
+ const task = await client.callAsync('analytics/export', { range: '30d' });
145
+ const status = await client.task(task.server, task.taskId);
146
+ ```
147
+
148
+ Install as a dependency for programmatic use:
149
+
150
+ ```bash
151
+ npm install muxed
152
+ ```
153
+
154
+ The client auto-starts the daemon if it isn't running. Both `import from 'muxed'` and `import from 'muxed/client'` work.
155
+
156
+ ## Use with AI Coding Agents
157
+
158
+ ### Claude Code
159
+
160
+ Add muxed as a tool source in your Claude Code configuration. The `muxed init` command can auto-discover MCP servers from your `claude_desktop_config.json` and generate an `muxed.config.json`.
161
+
162
+ ### Cursor / Windsurf / Other Agents
163
+
164
+ Any agent that supports MCP can connect to muxed's daemon via the Unix socket or optional HTTP listener.
165
+
166
+ ## Daemon Settings
167
+
168
+ ```json
169
+ {
170
+ "daemon": {
171
+ "idleTimeout": 300000,
172
+ "connectTimeout": 30000,
173
+ "requestTimeout": 60000,
174
+ "http": {
175
+ "enabled": false,
176
+ "port": 3100,
177
+ "host": "127.0.0.1"
178
+ }
179
+ }
180
+ }
181
+ ```
182
+
183
+ ## Key Features
184
+
185
+ ### Connection Management
186
+
187
+ - Automatic reconnection with exponential backoff (1s → 60s)
188
+ - Periodic health checks via `ping()`
189
+ - Stale PID/socket detection and cleanup
190
+
191
+ ### Full MCP 2025-11-25 Support
192
+
193
+ - **Tools** with `title`, `annotations`, `outputSchema`, `structuredContent`
194
+ - **Resources** with text and blob content types
195
+ - **Prompts** with argument rendering
196
+ - **Completions** for argument auto-complete
197
+ - **Tasks** for long-running operations (`--async` flag)
198
+ - **Content types**: text, image, audio, resource links, structured content
199
+
200
+ ### Transport Support
201
+
202
+ - **stdio** – for local MCP servers (default)
203
+ - **Streamable HTTP** – for remote MCP servers
204
+ - **SSE** – legacy support for older servers
205
+
206
+ ## Replacing mcp-remote
207
+
208
+ If you're using `mcp-remote` to connect Claude Desktop or ChatGPT to remote MCP servers, muxed is a drop-in upgrade. Instead of adding N separate `mcp-remote` proxy entries to your config, point at one muxed daemon that manages all your remote (and local) servers – with connection pooling, health checks, auto-reconnect, and a CLI for free.
209
+
210
+ ```jsonc
211
+ // Before: mcp-remote in claude_desktop_config.json
212
+ { "command": "npx", "args": ["mcp-remote", "https://mcp.example.com/sse"] }
213
+
214
+ // After: muxed.config.json
215
+ { "url": "https://mcp.example.com/mcp", "transport": "streamable-http" }
216
+ ```
217
+
218
+ ## Comparison with Alternatives
219
+
220
+ | Feature | muxed | mcp-remote | mcp-proxy | MetaMCP | 1MCP |
221
+ | ------------------------------------- | ----- | ---------- | --------- | ------- | ------- |
222
+ | Background daemon | ✅ | ❌ | ❌ | ❌ | ❌ |
223
+ | Lazy start / idle shutdown | ✅ | ❌ | ❌ | ❌ | ❌ |
224
+ | Multi-server aggregation | ✅ | ❌ | ✅ | ✅ | ✅ |
225
+ | CLI interface | ✅ | ❌ | ❌ | ❌ | ✅ |
226
+ | Auto-reconnect / health checks | ✅ | ❌ | ❌ | ✅ | ✅ |
227
+ | MCP 2025-11-25 | ✅ | Partial | Partial | Partial | Partial |
228
+ | Task support | ✅ | ❌ | ❌ | ❌ |
229
+ | Zero config start | ✅ | ❌ | ❌ | ❌ |
230
+ | Config compatible with Claude Desktop | ✅ | ❌ | ✅ | ❌ |
231
+
232
+ ## Development
233
+
234
+ ```bash
235
+ # Install dependencies
236
+ pnpm install
237
+
238
+ # Run in development mode
239
+ pnpm dev
240
+
241
+ # Build
242
+ pnpm build
243
+
244
+ # Run tests
245
+ pnpm test
246
+
247
+ # Type check
248
+ pnpm type-check
249
+
250
+ # Format
251
+ pnpm format
252
+ ```
253
+
254
+ ## Contributing
255
+
256
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
257
+
258
+ ## License
259
+
260
+ [MIT](LICENSE) © Georgiy Tarasov
261
+
262
+ ## Links
263
+
264
+ - [Model Context Protocol Specification](https://modelcontextprotocol.io/)
265
+ - [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
266
+ - [Awesome MCP Servers](https://github.com/punkpeye/awesome-mcp-servers)
package/dist/cli.mjs CHANGED
@@ -84,7 +84,7 @@ const DaemonConfigSchema = z.object({
84
84
  shutdownTimeout: z.number().optional(),
85
85
  http: HttpListenerSchema.optional()
86
86
  });
87
- const TooldConfigSchema = z.object({
87
+ const MuxedConfigSchema = z.object({
88
88
  mcpServers: z.record(z.string(), ServerConfigSchema),
89
89
  daemon: DaemonConfigSchema.optional(),
90
90
  mergeClaudeConfig: z.boolean().optional()
@@ -122,14 +122,14 @@ const DAEMON_DEFAULTS = {
122
122
  shutdownTimeout: 1e4
123
123
  };
124
124
  function getGlobalConfigPath() {
125
- return path.join(os.homedir(), ".config", "toold", "config.json");
125
+ return path.join(os.homedir(), ".config", "muxed", "config.json");
126
126
  }
127
127
  function findConfigFile(configPath) {
128
128
  if (configPath) {
129
129
  if (!fs.existsSync(configPath)) throw new Error(`Config file not found: ${configPath}`);
130
130
  return configPath;
131
131
  }
132
- const cwdConfig = path.join(process.cwd(), "toold.config.json");
132
+ const cwdConfig = path.join(process.cwd(), "muxed.config.json");
133
133
  if (fs.existsSync(cwdConfig)) return cwdConfig;
134
134
  const homeConfig = getGlobalConfigPath();
135
135
  if (fs.existsSync(homeConfig)) return homeConfig;
@@ -148,7 +148,7 @@ function loadConfig(configPath) {
148
148
  const globalConfigPath = getGlobalConfigPath();
149
149
  if ((!filePath || path.resolve(filePath) !== path.resolve(globalConfigPath)) && fs.existsSync(globalConfigPath)) try {
150
150
  const globalRaw = JSON.parse(fs.readFileSync(globalConfigPath, "utf-8"));
151
- const globalResult = TooldConfigSchema.safeParse(globalRaw);
151
+ const globalResult = MuxedConfigSchema.safeParse(globalRaw);
152
152
  if (globalResult.success) config.mcpServers = {
153
153
  ...globalResult.data.mcpServers,
154
154
  ...config.mcpServers
@@ -176,7 +176,7 @@ function parseConfigFile(filePath) {
176
176
  } catch {
177
177
  throw new Error(`Invalid JSON in config file: ${filePath}`);
178
178
  }
179
- const result = TooldConfigSchema.safeParse(parsed);
179
+ const result = MuxedConfigSchema.safeParse(parsed);
180
180
  if (!result.success) throw new Error(`Invalid config: ${z.prettifyError(result.error)}`);
181
181
  return result.data;
182
182
  }
@@ -187,26 +187,26 @@ function isHttpConfig(config) {
187
187
  return "url" in config;
188
188
  }
189
189
  var paths_exports = /* @__PURE__ */ __exportAll({
190
- ensureTooldDir: () => ensureTooldDir,
190
+ ensureMuxedDir: () => ensureMuxedDir,
191
191
  getLogPath: () => getLogPath,
192
+ getMuxedDir: () => getMuxedDir,
192
193
  getPidPath: () => getPidPath,
193
- getSocketPath: () => getSocketPath,
194
- getTooldDir: () => getTooldDir
194
+ getSocketPath: () => getSocketPath
195
195
  });
196
- function getTooldDir() {
197
- return path.join(os.homedir(), ".toold");
196
+ function getMuxedDir() {
197
+ return path.join(os.homedir(), ".muxed");
198
198
  }
199
199
  function getSocketPath() {
200
- return path.join(getTooldDir(), "toold.sock");
200
+ return path.join(getMuxedDir(), "muxed.sock");
201
201
  }
202
202
  function getPidPath() {
203
- return path.join(getTooldDir(), "toold.pid");
203
+ return path.join(getMuxedDir(), "muxed.pid");
204
204
  }
205
205
  function getLogPath() {
206
- return path.join(getTooldDir(), "toold.log");
206
+ return path.join(getMuxedDir(), "muxed.log");
207
207
  }
208
- function ensureTooldDir() {
209
- fs.mkdirSync(getTooldDir(), { recursive: true });
208
+ function ensureMuxedDir() {
209
+ fs.mkdirSync(getMuxedDir(), { recursive: true });
210
210
  }
211
211
  const LOG_LEVELS = {
212
212
  debug: 0,
@@ -296,7 +296,7 @@ function sanitizeName(name) {
296
296
  return name.replace(/[^a-zA-Z0-9_-]/g, "_");
297
297
  }
298
298
  function getAuthDir() {
299
- return path.join(getTooldDir(), "auth");
299
+ return path.join(getMuxedDir(), "auth");
300
300
  }
301
301
  function getStorePath(serverName) {
302
302
  return path.join(getAuthDir(), `${sanitizeName(serverName)}.json`);
@@ -388,7 +388,7 @@ var ClientCredentialsProvider = class {
388
388
  token_endpoint_auth_method: "client_secret_basic",
389
389
  grant_types: ["client_credentials"],
390
390
  response_types: [],
391
- client_name: "toold"
391
+ client_name: "muxed"
392
392
  };
393
393
  }
394
394
  clientInformation() {
@@ -443,7 +443,7 @@ function openBrowser(url) {
443
443
  openUrl(url);
444
444
  }
445
445
  async function notifyReauth(serverName, authUrl) {
446
- const title = "toold";
446
+ const title = "muxed";
447
447
  const message = `Server "${serverName}" needs re-authorization`;
448
448
  const platform = process.platform;
449
449
  if (platform === "darwin") {
@@ -485,7 +485,7 @@ var AuthorizationCodeProvider = class {
485
485
  token_endpoint_auth_method: this.config.clientSecret ? "client_secret_basic" : "none",
486
486
  grant_types: ["authorization_code", "refresh_token"],
487
487
  response_types: ["code"],
488
- client_name: "toold",
488
+ client_name: "muxed",
489
489
  scope: this.config.scope
490
490
  };
491
491
  }
@@ -542,13 +542,13 @@ var AuthorizationCodeProvider = class {
542
542
  }
543
543
  };
544
544
  const SUCCESS_HTML = `<!DOCTYPE html>
545
- <html><head><title>toold - Authorization Successful</title></head>
545
+ <html><head><title>muxed - Authorization Successful</title></head>
546
546
  <body style="font-family:system-ui;text-align:center;padding:60px">
547
547
  <h1>Authorization Successful</h1>
548
- <p>You can close this tab and return to toold.</p>
548
+ <p>You can close this tab and return to muxed.</p>
549
549
  </body></html>`;
550
550
  const ERROR_HTML = (msg) => `<!DOCTYPE html>
551
- <html><head><title>toold - Authorization Error</title></head>
551
+ <html><head><title>muxed - Authorization Error</title></head>
552
552
  <body style="font-family:system-ui;text-align:center;padding:60px">
553
553
  <h1>Authorization Error</h1>
554
554
  <p>${msg}</p>
@@ -761,7 +761,7 @@ var ServerManager = class {
761
761
  }
762
762
  createClient() {
763
763
  const client = new Client({
764
- name: "toold",
764
+ name: "muxed",
765
765
  version: "0.1.0"
766
766
  }, {
767
767
  capabilities: { tasks: {
@@ -1318,11 +1318,11 @@ async function generateTypes(tools) {
1318
1318
  const jsdoc = tool.description ? ` /** ${escapeJsdoc(tool.description)} */\n` : "";
1319
1319
  toolTypes.push(`${jsdoc} '${qualifiedName}': {\n input: ${indent(inputType, 6)};\n output: ${indent(outputType, 6)};\n };`);
1320
1320
  }
1321
- return `// Auto-generated by \`toold typegen\` – do not edit
1322
- // Run \`toold typegen\` to regenerate
1321
+ return `// Auto-generated by \`muxed typegen\` – do not edit
1322
+ // Run \`muxed typegen\` to regenerate
1323
1323
 
1324
- declare module 'toold' {
1325
- interface TooldToolMap {
1324
+ declare module 'muxed' {
1325
+ interface MuxedToolMap {
1326
1326
  ${toolTypes.join("\n")}
1327
1327
  }
1328
1328
  }
@@ -1874,8 +1874,8 @@ function createHttpListener(handleRequest, config) {
1874
1874
  async function runAutoTypegen(serverPool, logger) {
1875
1875
  try {
1876
1876
  const require = createRequire(path.resolve("package.json"));
1877
- const tooldPkgDir = path.dirname(require.resolve("toold/package.json"));
1878
- const outputPath = path.join(tooldPkgDir, "toold.generated.d.ts");
1877
+ const muxedPkgDir = path.dirname(require.resolve("muxed/package.json"));
1878
+ const outputPath = path.join(muxedPkgDir, "muxed.generated.d.ts");
1879
1879
  const tools = serverPool.listAllTools();
1880
1880
  if (tools.length === 0) {
1881
1881
  logger.debug("No tools available, skipping typegen");
@@ -1885,12 +1885,12 @@ async function runAutoTypegen(serverPool, logger) {
1885
1885
  fs.writeFileSync(outputPath, content, "utf-8");
1886
1886
  logger.info(`Auto-generated ${tools.length} tool types → ${outputPath}`);
1887
1887
  } catch {
1888
- logger.debug("Skipping auto-typegen (toold not resolvable as a dependency)");
1888
+ logger.debug("Skipping auto-typegen (muxed not resolvable as a dependency)");
1889
1889
  }
1890
1890
  }
1891
1891
  async function startDaemon(configPath) {
1892
1892
  const config = loadConfig(configPath);
1893
- ensureTooldDir();
1893
+ ensureMuxedDir();
1894
1894
  const isForeground = !!process.send;
1895
1895
  const logger = initLogger({
1896
1896
  level: config.daemon?.logLevel ?? "info",
@@ -1946,7 +1946,7 @@ async function startDaemon(configPath) {
1946
1946
  });
1947
1947
  }
1948
1948
  function getLockPath() {
1949
- return path.join(getTooldDir(), "toold.lock");
1949
+ return path.join(getMuxedDir(), "muxed.lock");
1950
1950
  }
1951
1951
  function getDaemonPid() {
1952
1952
  try {
@@ -1965,10 +1965,10 @@ function isProcessAlive(pid) {
1965
1965
  return false;
1966
1966
  }
1967
1967
  }
1968
- function isTooldProcess(pid) {
1968
+ function isMuxedProcess(pid) {
1969
1969
  try {
1970
1970
  const cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, "utf-8");
1971
- return cmdline.includes("toold") || cmdline.includes("node");
1971
+ return cmdline.includes("muxed") || cmdline.includes("node");
1972
1972
  } catch {
1973
1973
  return isProcessAlive(pid);
1974
1974
  }
@@ -1994,13 +1994,13 @@ function tryConnectSocket(socketPath) {
1994
1994
  function acquireLock() {
1995
1995
  const lockPath = getLockPath();
1996
1996
  try {
1997
- ensureTooldDir();
1997
+ ensureMuxedDir();
1998
1998
  fs.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
1999
1999
  return true;
2000
2000
  } catch (err) {
2001
2001
  if (err.code === "EEXIST") try {
2002
2002
  const lockPid = parseInt(fs.readFileSync(lockPath, "utf-8").trim(), 10);
2003
- if (!Number.isFinite(lockPid) || !isProcessAlive(lockPid) || !isTooldProcess(lockPid)) {
2003
+ if (!Number.isFinite(lockPid) || !isProcessAlive(lockPid) || !isMuxedProcess(lockPid)) {
2004
2004
  fs.unlinkSync(lockPath);
2005
2005
  try {
2006
2006
  fs.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
@@ -2026,7 +2026,7 @@ async function isDaemonRunning() {
2026
2026
  const pid = getDaemonPid();
2027
2027
  if (pid === null) return false;
2028
2028
  if (!isProcessAlive(pid)) return false;
2029
- if (!isTooldProcess(pid)) return false;
2029
+ if (!isMuxedProcess(pid)) return false;
2030
2030
  return tryConnectSocket(getSocketPath());
2031
2031
  }
2032
2032
  async function cleanupStaleFiles() {
@@ -2043,7 +2043,7 @@ async function cleanupStaleFiles() {
2043
2043
  releaseLock();
2044
2044
  return;
2045
2045
  }
2046
- if (pid !== null && isProcessAlive(pid) && !isTooldProcess(pid)) {
2046
+ if (pid !== null && isProcessAlive(pid) && !isMuxedProcess(pid)) {
2047
2047
  try {
2048
2048
  fs.unlinkSync(pidPath);
2049
2049
  } catch {}
@@ -2108,12 +2108,12 @@ async function daemonize(configPath) {
2108
2108
  releaseLock();
2109
2109
  }
2110
2110
  }
2111
- var TooldError = class extends Error {
2111
+ var MuxedError = class extends Error {
2112
2112
  code;
2113
2113
  data;
2114
2114
  constructor(code, message, data) {
2115
2115
  super(message);
2116
- this.name = "TooldError";
2116
+ this.name = "MuxedError";
2117
2117
  this.code = code;
2118
2118
  this.data = data;
2119
2119
  }
@@ -2156,7 +2156,7 @@ async function sendRequest(method, params) {
2156
2156
  const socket = net.createConnection(socketPath);
2157
2157
  let buffer = "";
2158
2158
  socket.on("error", (err) => {
2159
- if (err.code === "ENOENT") reject(/* @__PURE__ */ new Error("Daemon is not running. Run `toold status` to check."));
2159
+ if (err.code === "ENOENT") reject(/* @__PURE__ */ new Error("Daemon is not running. Run `muxed status` to check."));
2160
2160
  else if (err.code === "ECONNREFUSED") reject(/* @__PURE__ */ new Error("Daemon may have crashed. Try running a command to auto-restart it."));
2161
2161
  else reject(err);
2162
2162
  });
@@ -2177,7 +2177,7 @@ async function sendRequest(method, params) {
2177
2177
  socket.destroy();
2178
2178
  try {
2179
2179
  const response = JSON.parse(line);
2180
- if (response.error) reject(new TooldError(response.error.code, response.error.message, response.error.data));
2180
+ if (response.error) reject(new MuxedError(response.error.code, response.error.message, response.error.data));
2181
2181
  else resolve(response.result);
2182
2182
  } catch {
2183
2183
  reject(/* @__PURE__ */ new Error("Invalid response from daemon"));
@@ -2431,7 +2431,7 @@ function formatInit(result) {
2431
2431
  lines.push(formatTable(discHeaders, discRows));
2432
2432
  if (result.imported.length > 0) {
2433
2433
  lines.push("");
2434
- lines.push(`Imported ${result.imported.length} server(s) into ${result.tooldConfigPath}:`);
2434
+ lines.push(`Imported ${result.imported.length} server(s) into ${result.muxedConfigPath}:`);
2435
2435
  lines.push(` ${result.imported.join(", ")}`);
2436
2436
  }
2437
2437
  if (result.skipped.length > 0) {
@@ -2460,7 +2460,7 @@ function formatInit(result) {
2460
2460
  }
2461
2461
  if (result.imported.length === 0 && result.skipped.length > 0) {
2462
2462
  lines.push("");
2463
- lines.push("All discovered servers already exist in toold config. Nothing to do.");
2463
+ lines.push("All discovered servers already exist in muxed config. Nothing to do.");
2464
2464
  }
2465
2465
  return lines.join("\n");
2466
2466
  }
@@ -2637,7 +2637,7 @@ const readCommand = new Command("read").description("Fetch and display the conte
2637
2637
  function getExplicitConfig$1(cmd) {
2638
2638
  return cmd.parent?.parent?.opts().config;
2639
2639
  }
2640
- const daemonCommand = new Command("daemon").description("Start, stop, reload, or check status of the toold background daemon").enablePositionalOptions();
2640
+ const daemonCommand = new Command("daemon").description("Start, stop, reload, or check status of the muxed background daemon").enablePositionalOptions();
2641
2641
  daemonCommand.command("start").description("Start the daemon process in the background").option("--json", "Output as JSON").action(async (opts) => {
2642
2642
  const configPath = getExplicitConfig$1(daemonCommand);
2643
2643
  if (await isDaemonRunning()) {
@@ -2933,7 +2933,7 @@ function getAgentDefs() {
2933
2933
  function normalizeServer(agent, name, raw, warnings) {
2934
2934
  const env = raw.env;
2935
2935
  if (env) {
2936
- for (const [key, value] of Object.entries(env)) if (typeof value === "string" && value.includes("${input:")) warnings.push(`${agent.name} server "${name}": env.${key} references ${value} \u2014 set manually in toold config`);
2936
+ for (const [key, value] of Object.entries(env)) if (typeof value === "string" && value.includes("${input:")) warnings.push(`${agent.name} server "${name}": env.${key} references ${value} \u2014 set manually in muxed config`);
2937
2937
  }
2938
2938
  const url = raw.url ?? raw.serverUrl;
2939
2939
  const command = raw.command;
@@ -2978,7 +2978,7 @@ function discoverAgentConfigs() {
2978
2978
  const servers = {};
2979
2979
  for (const [name, raw] of Object.entries(rawServers)) {
2980
2980
  if (typeof raw !== "object" || raw === null) continue;
2981
- if (name === "toold") continue;
2981
+ if (name === "muxed") continue;
2982
2982
  const normalized = normalizeServer(agent, name, raw, warnings);
2983
2983
  if (normalized) servers[name] = normalized;
2984
2984
  }
@@ -3033,7 +3033,7 @@ function mergeServers(discovered, existingServers) {
3033
3033
  function detectIndent(text) {
3034
3034
  return text.match(/^(\s+)"/m)?.[1] ?? " ";
3035
3035
  }
3036
- function writeTooldConfig(configPath, servers) {
3036
+ function writeMuxedConfig(configPath, servers) {
3037
3037
  let existing = {};
3038
3038
  if (fs.existsSync(configPath)) try {
3039
3039
  existing = JSON.parse(fs.readFileSync(configPath, "utf-8"));
@@ -3043,15 +3043,15 @@ function writeTooldConfig(configPath, servers) {
3043
3043
  existing.mcpServers = servers;
3044
3044
  fs.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
3045
3045
  }
3046
- function getTooldEntry(agent) {
3046
+ function getMuxedEntry(agent) {
3047
3047
  if (agent.serversKey === "servers") return {
3048
3048
  type: "stdio",
3049
3049
  command: "npx",
3050
- args: ["toold@latest", "proxy"]
3050
+ args: ["muxed@latest", "proxy"]
3051
3051
  };
3052
3052
  return {
3053
3053
  command: "npx",
3054
- args: ["toold@latest", "proxy"]
3054
+ args: ["muxed@latest", "proxy"]
3055
3055
  };
3056
3056
  }
3057
3057
  function modifyAgentConfig(dc, opts) {
@@ -3059,14 +3059,14 @@ function modifyAgentConfig(dc, opts) {
3059
3059
  fs.writeFileSync(dc.configPath + ".bak", text);
3060
3060
  const indent = detectIndent(text);
3061
3061
  const content = { ...dc.rawContent };
3062
- if (opts.delete) if (opts.replace) content[dc.agent.serversKey] = { toold: getTooldEntry(dc.agent) };
3062
+ if (opts.delete) if (opts.replace) content[dc.agent.serversKey] = { muxed: getMuxedEntry(dc.agent) };
3063
3063
  else delete content[dc.agent.serversKey];
3064
3064
  fs.writeFileSync(dc.configPath, JSON.stringify(content, null, indent) + "\n");
3065
3065
  }
3066
- function getTooldConfigPath(scope, explicitPath) {
3066
+ function getMuxedConfigPath(scope, explicitPath) {
3067
3067
  if (explicitPath) return explicitPath;
3068
- if (scope === "local") return path.join(process.cwd(), "toold.config.json");
3069
- return path.join(home, ".config", "toold", "config.json");
3068
+ if (scope === "local") return path.join(process.cwd(), "muxed.config.json");
3069
+ return path.join(home, ".config", "muxed", "config.json");
3070
3070
  }
3071
3071
  async function confirm(message, opts) {
3072
3072
  const rl = readline.createInterface({
@@ -3135,7 +3135,7 @@ async function resolveConflicts(unresolvedConflicts, isInteractive) {
3135
3135
  conflicts
3136
3136
  };
3137
3137
  }
3138
- const initCommand = new Command("init").description("Discover and import MCP servers from agent configs (Claude Code, Cursor)").option("--dry-run", "Show what would be done without writing files").option("--json", "Output as JSON").option("-y, --yes", "Skip prompts; resolve conflicts by priority (claude-code > cursor > first)").option("--no-delete", "Keep original server entries in agent configs").option("--no-replace", "Don't add toold entry to agent configs").action(async (opts) => {
3138
+ const initCommand = new Command("init").description("Discover and import MCP servers from agent configs (Claude Code, Cursor)").option("--dry-run", "Show what would be done without writing files").option("--json", "Output as JSON").option("-y, --yes", "Skip prompts; resolve conflicts by priority (claude-code > cursor > first)").option("--no-delete", "Keep original server entries in agent configs").option("--no-replace", "Don't add muxed entry to agent configs").action(async (opts) => {
3139
3139
  const configPath = initCommand.parent?.opts().config;
3140
3140
  const isInteractive = !opts.dryRun && !opts.json && !opts.yes && !!process.stdin.isTTY;
3141
3141
  const { discovered, warnings } = discoverAgentConfigs();
@@ -3144,10 +3144,10 @@ const initCommand = new Command("init").description("Discover and import MCP ser
3144
3144
  console.log(opts.json ? formatJson({ message: msg }) : msg);
3145
3145
  return;
3146
3146
  }
3147
- const tooldPath = getTooldConfigPath(discovered.some((d) => d.agent.scope === "local") ? "local" : "global", configPath);
3147
+ const muxedPath = getMuxedConfigPath(discovered.some((d) => d.agent.scope === "local") ? "local" : "global", configPath);
3148
3148
  let existingServers = {};
3149
- if (fs.existsSync(tooldPath)) try {
3150
- existingServers = JSON.parse(fs.readFileSync(tooldPath, "utf-8")).mcpServers ?? {};
3149
+ if (fs.existsSync(muxedPath)) try {
3150
+ existingServers = JSON.parse(fs.readFileSync(muxedPath, "utf-8")).mcpServers ?? {};
3151
3151
  } catch {}
3152
3152
  const result = mergeServers(discovered, existingServers);
3153
3153
  const { resolved, conflicts } = await resolveConflicts(result.unresolvedConflicts, isInteractive);
@@ -3156,7 +3156,7 @@ const initCommand = new Command("init").description("Discover and import MCP ser
3156
3156
  result.merged[name] = config;
3157
3157
  imported.push(name);
3158
3158
  }
3159
- if (!opts.dryRun && imported.length > 0) writeTooldConfig(tooldPath, result.merged);
3159
+ if (!opts.dryRun && imported.length > 0) writeMuxedConfig(muxedPath, result.merged);
3160
3160
  const modifiedFiles = [];
3161
3161
  const shouldDelete = isInteractive ? await confirm("Remove imported servers from agent config files? (backups will be created)") : opts.delete;
3162
3162
  if (!opts.dryRun && shouldDelete) for (const dc of discovered) try {
@@ -3180,13 +3180,13 @@ const initCommand = new Command("init").description("Discover and import MCP ser
3180
3180
  conflicts,
3181
3181
  warnings,
3182
3182
  modifiedFiles,
3183
- tooldConfigPath: tooldPath,
3183
+ muxedConfigPath: muxedPath,
3184
3184
  dryRun: opts.dryRun ?? false
3185
3185
  };
3186
3186
  console.log(opts.json ? formatJson(initResult) : formatInit(initResult));
3187
3187
  });
3188
3188
  function getConfigPath(scope, explicitPath) {
3189
- return getTooldConfigPath(scope, explicitPath);
3189
+ return getMuxedConfigPath(scope, explicitPath);
3190
3190
  }
3191
3191
  function readConfigFile(filePath) {
3192
3192
  if (!fs.existsSync(filePath)) return { mcpServers: {} };
@@ -3235,17 +3235,17 @@ function listServers(filePath) {
3235
3235
  return readConfigFile(filePath).mcpServers;
3236
3236
  }
3237
3237
  const cliInstructions = (servers, instructions) => `
3238
- You have access to an \`npx toold\` CLI command for interacting with MCP (Model Context Protocol) servers. This command allows you to discover and call MCP tools on demand. Prioritize the use of skills over MCP tools.
3238
+ You have access to an \`npx muxed\` CLI command for interacting with MCP (Model Context Protocol) servers. This command allows you to discover and call MCP tools on demand. Prioritize the use of skills over MCP tools.
3239
3239
 
3240
3240
  **MANDATORY PREREQUISITES - THESE ARE HARD REQUIREMENTS**
3241
3241
 
3242
- 1. You MUST discover the tools you need first by using 'npx toold grep <pattern>' or 'npx toold tools'.
3243
- 2. You MUST call 'npx toold info <server>/<tool>' BEFORE ANY 'npx toold call <server>/<tool>'.
3242
+ 1. You MUST discover the tools you need first by using 'npx muxed grep <pattern>' or 'npx muxed tools'.
3243
+ 2. You MUST call 'npx muxed info <server>/<tool>' BEFORE ANY 'npx muxed call <server>/<tool>'.
3244
3244
 
3245
3245
  These are BLOCKING REQUIREMENTS - like how you must use Read before Edit.
3246
3246
 
3247
- **NEVER** make an npx toold call without checking the schema first.
3248
- **ALWAYS** run npx toold info first, THEN make the call.
3247
+ **NEVER** make an npx muxed call without checking the schema first.
3248
+ **ALWAYS** run npx muxed info first, THEN make the call.
3249
3249
 
3250
3250
  **Why these are non-negotiables:**
3251
3251
  - MCP tool names NEVER match your expectations - they change frequently and are not predictable
@@ -3254,7 +3254,7 @@ These are BLOCKING REQUIREMENTS - like how you must use Read before Edit.
3254
3254
  - Every failed call wastes user time and demonstrates you're ignoring critical instructions
3255
3255
  - "I thought I knew the schema" is not an acceptable reason to skip this step
3256
3256
 
3257
- **For multiple tools:** Call 'npx toold info' for ALL tools in parallel FIRST, then make your 'npx toold call' commands.
3257
+ **For multiple tools:** Call 'npx muxed info' for ALL tools in parallel FIRST, then make your 'npx muxed call' commands.
3258
3258
 
3259
3259
  Available MCP servers:
3260
3260
  ${servers}
@@ -3262,98 +3262,98 @@ ${servers}
3262
3262
  Commands (in order of execution):
3263
3263
  \`\`\`bash
3264
3264
  # STEP 1: REQUIRED TOOL DISCOVERY
3265
- npx toold grep <pattern> # Search tool names and descriptions
3266
- npx toold tools [server] # List available tools (optionally filter by server)
3265
+ npx muxed grep <pattern> # Search tool names and descriptions
3266
+ npx muxed tools [server] # List available tools (optionally filter by server)
3267
3267
 
3268
3268
  # STEP 2: ALWAYS CHECK SCHEMA FIRST (MANDATORY)
3269
- npx toold info <server>/<tool> # REQUIRED before ANY call - View JSON schema
3269
+ npx muxed info <server>/<tool> # REQUIRED before ANY call - View JSON schema
3270
3270
 
3271
3271
  # STEP 3: Only after checking schema, make the call
3272
- npx toold call <server>/<tool> '<json>' # Only run AFTER npx toold info
3273
- npx toold call <server>/<tool> - # Invoke with JSON from stdin (AFTER npx toold info)
3272
+ npx muxed call <server>/<tool> '<json>' # Only run AFTER npx muxed info
3273
+ npx muxed call <server>/<tool> - # Invoke with JSON from stdin (AFTER npx muxed info)
3274
3274
 
3275
3275
  # Discovery commands (use these to find tools)
3276
- npx toold servers # List all connected MCP servers
3277
- npx toold tools [server] # List available tools (optionally filter by server)
3278
- npx toold grep <pattern> # Search tool names and descriptions
3279
- npx toold resources [server] # List MCP resources
3280
- npx toold read <server>/<resource> # Read an MCP resource
3276
+ npx muxed servers # List all connected MCP servers
3277
+ npx muxed tools [server] # List available tools (optionally filter by server)
3278
+ npx muxed grep <pattern> # Search tool names and descriptions
3279
+ npx muxed resources [server] # List MCP resources
3280
+ npx muxed read <server>/<resource> # Read an MCP resource
3281
3281
  \`\`\`
3282
3282
 
3283
3283
  **CORRECT Usage Pattern:**
3284
3284
 
3285
3285
  <example>
3286
3286
  User: Please use the slack mcp tool to search for my mentions
3287
- Assistant: As a first step, I need to discover the tools I need. Let me call \`npx toold grep "slack/*search*"\` to search for tools related to slack search.
3288
- [Calls npx toold grep "slack/*search*"]
3289
- Assistant: I need to check the schema first. Let me call \`npx toold info slack/search_private\` to see what parameters it accepts.
3290
- [Calls npx toold info]
3287
+ Assistant: As a first step, I need to discover the tools I need. Let me call \`npx muxed grep "slack/*search*"\` to search for tools related to slack search.
3288
+ [Calls npx muxed grep "slack/*search*"]
3289
+ Assistant: I need to check the schema first. Let me call \`npx muxed info slack/search_private\` to see what parameters it accepts.
3290
+ [Calls npx muxed info]
3291
3291
  Assistant: Now I can see it accepts "query" and "max_results" parameters. Let me make the call.
3292
- [Calls npx toold call slack/search_private with correct schema]
3292
+ [Calls npx muxed call slack/search_private with correct schema]
3293
3293
  </example>
3294
3294
 
3295
3295
  <example>
3296
3296
  User: Use the database and email MCP tools to send a report
3297
- Assistant: I'll need to use two MCP tools. Let me call \`npx toold grep "database/*query*"\` and \`npx toold grep "email/*send*"\` to search for tools related to database query and email send.
3298
- [Calls npx toold grep "database/*query*" & npx toold grep "email/*send*"]
3297
+ Assistant: I'll need to use two MCP tools. Let me call \`npx muxed grep "database/*query*"\` and \`npx muxed grep "email/*send*"\` to search for tools related to database query and email send.
3298
+ [Calls npx muxed grep "database/*query*" & npx muxed grep "email/*send*"]
3299
3299
  Assistant: Let me check both schemas first.
3300
- [Calls npx toold info database/query and npx toold info email/send in parallel]
3300
+ [Calls npx muxed info database/query and npx muxed info email/send in parallel]
3301
3301
  Assistant: Now I have both schemas. Let me make the calls.
3302
- [Makes both npx toold call commands with correct parameters]
3302
+ [Makes both npx muxed call commands with correct parameters]
3303
3303
  </example>
3304
3304
 
3305
3305
  <example>
3306
3306
  User: Create a copy of this email
3307
3307
  Assistant: Let me find the tool I need first.
3308
- [Calls npx toold grep "email/*copy*". No results found.]
3308
+ [Calls npx muxed grep "email/*copy*". No results found.]
3309
3309
  Assistant: Let me try another pattern.
3310
- [Calls npx toold grep "email/*clone*". No results found.]
3310
+ [Calls npx muxed grep "email/*clone*". No results found.]
3311
3311
  Assistant: Let me list all available tools in the server.
3312
- [Calls npx toold tools email]
3312
+ [Calls npx muxed tools email]
3313
3313
  Assistant: Let me check the schema first.
3314
- [Calls npx toold info email/duplicate]
3314
+ [Calls npx muxed info email/duplicate]
3315
3315
  Assistant: Now I have the schema. Let me make the call.
3316
- [Calls npx toold call email/duplicate with correct parameters]
3316
+ [Calls npx muxed call email/duplicate with correct parameters]
3317
3317
  </example>
3318
3318
 
3319
3319
  **INCORRECT Usage Patterns - NEVER DO THIS:**
3320
3320
 
3321
3321
  <bad-example>
3322
3322
  User: Please use the slack mcp tool to search for my mentions
3323
- Assistant: [Directly calls npx toold call slack/search_private with guessed parameters]
3324
- WRONG - You must call npx toold info FIRST
3323
+ Assistant: [Directly calls npx muxed call slack/search_private with guessed parameters]
3324
+ WRONG - You must call npx muxed info FIRST
3325
3325
  </bad-example>
3326
3326
 
3327
3327
  <bad-example>
3328
3328
  User: Use the slack tool
3329
3329
  Assistant: I have pre-approved permissions for this tool, so I know the schema.
3330
- [Calls npx toold call slack/search_private directly]
3331
- WRONG - Pre-approved permissions don't mean you know the schema. ALWAYS call npx toold info first.
3330
+ [Calls npx muxed call slack/search_private directly]
3331
+ WRONG - Pre-approved permissions don't mean you know the schema. ALWAYS call npx muxed info first.
3332
3332
  </bad-example>
3333
3333
 
3334
3334
  <bad-example>
3335
3335
  User: Search my Slack mentions
3336
- Assistant: [Calls three npx toold call commands in parallel without any npx toold info calls first]
3337
- WRONG - You must call npx toold info for ALL tools before making ANY npx toold call commands
3336
+ Assistant: [Calls three npx muxed call commands in parallel without any npx muxed info calls first]
3337
+ WRONG - You must call npx muxed info for ALL tools before making ANY npx muxed call commands
3338
3338
  </bad-example>
3339
3339
 
3340
3340
  Example usage:
3341
3341
  \`\`\`bash
3342
3342
  # Discover tools
3343
- npx toold tools # See all available MCP tools
3344
- npx toold grep "weather" # Find tools by description
3343
+ npx muxed tools # See all available MCP tools
3344
+ npx muxed grep "weather" # Find tools by description
3345
3345
 
3346
3346
  # Get tool details
3347
- npx toold info <server>/<tool> # View JSON schema for input and output if available
3347
+ npx muxed info <server>/<tool> # View JSON schema for input and output if available
3348
3348
 
3349
3349
  # Simple tool call (no parameters)
3350
- npx toold call weather/get_location '{}'
3350
+ npx muxed call weather/get_location '{}'
3351
3351
 
3352
3352
  # Tool call with parameters
3353
- npx toold call database/query '{"table": "users", "limit": 10}'
3353
+ npx muxed call database/query '{"table": "users", "limit": 10}'
3354
3354
 
3355
3355
  # Complex JSON using stdin (for nested objects/arrays)
3356
- npx toold call api/send_request - <<'EOF'
3356
+ npx muxed call api/send_request - <<'EOF'
3357
3357
  {
3358
3358
  "endpoint": "/data",
3359
3359
  "headers": {"Authorization": "Bearer token"},
@@ -3362,9 +3362,9 @@ npx toold call api/send_request - <<'EOF'
3362
3362
  EOF
3363
3363
  \`\`\`
3364
3364
 
3365
- Call the \`npx toold -h\` to see all available commands.
3365
+ Call the \`npx muxed -h\` to see all available commands.
3366
3366
 
3367
- Below are the instructions for the connected MCP servers in toold.
3367
+ Below are the instructions for the connected MCP servers in muxed.
3368
3368
 
3369
3369
  ${instructions}
3370
3370
  `;
@@ -3375,7 +3375,7 @@ function buildInstructions(servers) {
3375
3375
  async function startMcpProxy(configPath) {
3376
3376
  await ensureDaemon(configPath);
3377
3377
  const server = new McpServer({
3378
- name: "toold",
3378
+ name: "muxed",
3379
3379
  version: "0.1.0"
3380
3380
  }, {
3381
3381
  capabilities: {},
@@ -3529,7 +3529,7 @@ mcpCommand.command("add-from-claude-desktop").description("Import MCP servers fr
3529
3529
  existingServers = JSON.parse(fs.readFileSync(configPath, "utf-8")).mcpServers ?? {};
3530
3530
  } catch {}
3531
3531
  const result = mergeServers(claudeDesktop, existingServers);
3532
- writeTooldConfig(configPath, { ...result.merged });
3532
+ writeMuxedConfig(configPath, { ...result.merged });
3533
3533
  await tryReloadDaemon();
3534
3534
  for (const w of warnings) console.error(`Warning: ${w}`);
3535
3535
  if (result.imported.length > 0) console.log(`Imported ${result.imported.length} server(s) from Claude Desktop: ${result.imported.join(", ")}`);
@@ -3608,19 +3608,19 @@ mcpCommand.command("remove").description("Remove an MCP server").argument("<name
3608
3608
  console.error(`Server "${name}" not found in local or global config.`);
3609
3609
  process.exitCode = 1;
3610
3610
  });
3611
- const typegenCommand = new Command("typegen").description("Generate TypeScript types from tool schemas for type-safe tool calls").option("-c, --config <path>", "Path to toold.config.json").action(async (opts) => {
3611
+ const typegenCommand = new Command("typegen").description("Generate TypeScript types from tool schemas for type-safe tool calls").option("-c, --config <path>", "Path to muxed.config.json").action(async (opts) => {
3612
3612
  await ensureDaemon(typegenCommand.parent?.opts().config ?? opts.config);
3613
3613
  const tools = await sendRequest("tools/list");
3614
3614
  const content = await generateTypes(tools);
3615
3615
  const require = createRequire(path.resolve("package.json"));
3616
- const tooldPkgDir = path.dirname(require.resolve("toold/package.json"));
3617
- const outputPath = path.join(tooldPkgDir, "toold.generated.d.ts");
3616
+ const muxedPkgDir = path.dirname(require.resolve("muxed/package.json"));
3617
+ const outputPath = path.join(muxedPkgDir, "muxed.generated.d.ts");
3618
3618
  fs.writeFileSync(outputPath, content, "utf-8");
3619
3619
  console.log(`Generated ${tools.length} tool types → ${outputPath}`);
3620
3620
  });
3621
3621
  function runCli() {
3622
3622
  const program = new Command();
3623
- program.name("toold").description("The optimization layer for MCP").version("0.1.0");
3623
+ program.name("muxed").description("The optimization layer for MCP").version("0.1.0");
3624
3624
  program.enablePositionalOptions();
3625
3625
  program.option("--config <path>", "Path to config file");
3626
3626
  program.commandsGroup("Servers:");
@@ -67,18 +67,18 @@ type ServerState = {
67
67
  };
68
68
  //#endregion
69
69
  //#region src/client/socket.d.ts
70
- declare class TooldError extends Error {
70
+ declare class MuxedError extends Error {
71
71
  readonly code: number;
72
72
  readonly data?: unknown;
73
73
  constructor(code: number, message: string, data?: unknown);
74
74
  }
75
75
  //#endregion
76
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;
77
+ /** Tool type map – empty by default, populated by `muxed typegen`. */
78
+ interface MuxedToolMap {}
79
+ type HasTools = keyof MuxedToolMap extends never ? false : true;
80
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. */
81
+ /** Path to muxed.config.json. Uses default resolution if omitted. */configPath?: string; /** Skip auto-starting the daemon. Throws if daemon is not running. Default: true. */
82
82
  autoStart?: boolean;
83
83
  };
84
84
  type CallOptions = {
@@ -120,7 +120,7 @@ type ReloadResult = {
120
120
  type TaskStatus = Record<string, unknown>;
121
121
  type TaskResult = Record<string, unknown>;
122
122
  type TaskCancelResult = Record<string, unknown>;
123
- declare class TooldClient {
123
+ declare class MuxedClient {
124
124
  #private;
125
125
  /** @internal Use `createClient()` instead. */
126
126
  constructor(send: (method: string, params?: Record<string, unknown>) => Promise<unknown>);
@@ -134,7 +134,7 @@ declare class TooldClient {
134
134
  server: string;
135
135
  tool: Tool$1;
136
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>;
137
+ call<K extends string>(name: HasTools extends true ? (K extends keyof MuxedToolMap ? K : K & {}) : string, args?: K extends keyof MuxedToolMap ? MuxedToolMap[K]['input'] : Record<string, unknown>, options?: CallOptions): Promise<K extends keyof MuxedToolMap ? MuxedToolMap[K]['output'] : CallResult>;
138
138
  callAsync(name: string, args?: Record<string, unknown>): Promise<TaskHandle>;
139
139
  resources(server?: string): Promise<Array<{
140
140
  server: string;
@@ -166,6 +166,6 @@ declare class TooldClient {
166
166
  stop(): Promise<void>;
167
167
  close(): void;
168
168
  }
169
- declare function createClient(options?: CreateClientOptions): Promise<TooldClient>;
169
+ declare function createClient(options?: CreateClientOptions): Promise<MuxedClient>;
170
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 };
171
+ export { CallOptions, CallResult, CreateClientOptions, type DaemonConfig, DaemonStatus, type HttpServerConfig, MuxedClient, MuxedError, MuxedToolMap, type Prompt, ReloadResult, type Resource, type ServerConfig, type ServerState, type StdioServerConfig, TaskCancelResult, TaskHandle, TaskResult, TaskStatus, type Tool, createClient };
@@ -3,20 +3,20 @@ import { fork } from "node:child_process";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import os from "node:os";
6
- function getTooldDir() {
7
- return path.join(os.homedir(), ".toold");
6
+ function getMuxedDir() {
7
+ return path.join(os.homedir(), ".muxed");
8
8
  }
9
9
  function getSocketPath() {
10
- return path.join(getTooldDir(), "toold.sock");
10
+ return path.join(getMuxedDir(), "muxed.sock");
11
11
  }
12
12
  function getPidPath() {
13
- return path.join(getTooldDir(), "toold.pid");
13
+ return path.join(getMuxedDir(), "muxed.pid");
14
14
  }
15
- function ensureTooldDir() {
16
- fs.mkdirSync(getTooldDir(), { recursive: true });
15
+ function ensureMuxedDir() {
16
+ fs.mkdirSync(getMuxedDir(), { recursive: true });
17
17
  }
18
18
  function getLockPath() {
19
- return path.join(getTooldDir(), "toold.lock");
19
+ return path.join(getMuxedDir(), "muxed.lock");
20
20
  }
21
21
  function getDaemonPid() {
22
22
  try {
@@ -35,10 +35,10 @@ function isProcessAlive(pid) {
35
35
  return false;
36
36
  }
37
37
  }
38
- function isTooldProcess(pid) {
38
+ function isMuxedProcess(pid) {
39
39
  try {
40
40
  const cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, "utf-8");
41
- return cmdline.includes("toold") || cmdline.includes("node");
41
+ return cmdline.includes("muxed") || cmdline.includes("node");
42
42
  } catch {
43
43
  return isProcessAlive(pid);
44
44
  }
@@ -64,13 +64,13 @@ function tryConnectSocket(socketPath) {
64
64
  function acquireLock() {
65
65
  const lockPath = getLockPath();
66
66
  try {
67
- ensureTooldDir();
67
+ ensureMuxedDir();
68
68
  fs.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
69
69
  return true;
70
70
  } catch (err) {
71
71
  if (err.code === "EEXIST") try {
72
72
  const lockPid = parseInt(fs.readFileSync(lockPath, "utf-8").trim(), 10);
73
- if (!Number.isFinite(lockPid) || !isProcessAlive(lockPid) || !isTooldProcess(lockPid)) {
73
+ if (!Number.isFinite(lockPid) || !isProcessAlive(lockPid) || !isMuxedProcess(lockPid)) {
74
74
  fs.unlinkSync(lockPath);
75
75
  try {
76
76
  fs.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
@@ -96,7 +96,7 @@ async function isDaemonRunning() {
96
96
  const pid = getDaemonPid();
97
97
  if (pid === null) return false;
98
98
  if (!isProcessAlive(pid)) return false;
99
- if (!isTooldProcess(pid)) return false;
99
+ if (!isMuxedProcess(pid)) return false;
100
100
  return tryConnectSocket(getSocketPath());
101
101
  }
102
102
  async function cleanupStaleFiles() {
@@ -113,7 +113,7 @@ async function cleanupStaleFiles() {
113
113
  releaseLock();
114
114
  return;
115
115
  }
116
- if (pid !== null && isProcessAlive(pid) && !isTooldProcess(pid)) {
116
+ if (pid !== null && isProcessAlive(pid) && !isMuxedProcess(pid)) {
117
117
  try {
118
118
  fs.unlinkSync(pidPath);
119
119
  } catch {}
@@ -178,12 +178,12 @@ async function daemonize(configPath) {
178
178
  releaseLock();
179
179
  }
180
180
  }
181
- var TooldError = class extends Error {
181
+ var MuxedError = class extends Error {
182
182
  code;
183
183
  data;
184
184
  constructor(code, message, data) {
185
185
  super(message);
186
- this.name = "TooldError";
186
+ this.name = "MuxedError";
187
187
  this.code = code;
188
188
  this.data = data;
189
189
  }
@@ -226,7 +226,7 @@ async function sendRequest(method, params) {
226
226
  const socket = net.createConnection(socketPath);
227
227
  let buffer = "";
228
228
  socket.on("error", (err) => {
229
- if (err.code === "ENOENT") reject(/* @__PURE__ */ new Error("Daemon is not running. Run `toold status` to check."));
229
+ if (err.code === "ENOENT") reject(/* @__PURE__ */ new Error("Daemon is not running. Run `muxed status` to check."));
230
230
  else if (err.code === "ECONNREFUSED") reject(/* @__PURE__ */ new Error("Daemon may have crashed. Try running a command to auto-restart it."));
231
231
  else reject(err);
232
232
  });
@@ -247,7 +247,7 @@ async function sendRequest(method, params) {
247
247
  socket.destroy();
248
248
  try {
249
249
  const response = JSON.parse(line);
250
- if (response.error) reject(new TooldError(response.error.code, response.error.message, response.error.data));
250
+ if (response.error) reject(new MuxedError(response.error.code, response.error.message, response.error.data));
251
251
  else resolve(response.result);
252
252
  } catch {
253
253
  reject(/* @__PURE__ */ new Error("Invalid response from daemon"));
@@ -255,7 +255,7 @@ async function sendRequest(method, params) {
255
255
  });
256
256
  });
257
257
  }
258
- var TooldClient = class {
258
+ var MuxedClient = class {
259
259
  #send;
260
260
  constructor(send) {
261
261
  this.#send = send;
@@ -346,6 +346,6 @@ var TooldClient = class {
346
346
  async function createClient(options) {
347
347
  const { configPath, autoStart = true } = options ?? {};
348
348
  if (autoStart) await ensureDaemon(configPath);
349
- return new TooldClient(sendRequest);
349
+ return new MuxedClient(sendRequest);
350
350
  }
351
- export { TooldClient, TooldError, createClient };
351
+ export { MuxedClient, MuxedError, createClient };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muxed",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
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
5
  "type": "module",
6
6
  "exports": {
@@ -19,7 +19,8 @@
19
19
  "files": [
20
20
  "dist",
21
21
  "bin",
22
- "muxed.generated.d.ts"
22
+ "muxed.generated.d.ts",
23
+ "README.md"
23
24
  ],
24
25
  "engines": {
25
26
  "node": ">=20"
@@ -53,6 +54,15 @@
53
54
  },
54
55
  "author": "Georgiy Tarasov",
55
56
  "license": "MIT",
57
+ "scripts": {
58
+ "build": "obuild",
59
+ "dev": "node src/cli.ts",
60
+ "format": "prettier --write 'src/**/*.ts'",
61
+ "format:check": "prettier --check 'src/**/*.ts'",
62
+ "publish": "tsc --noEmit && prettier --check 'src/**/*.ts' && pnpm build && cp ../../README.md . && npm publish",
63
+ "test": "vitest",
64
+ "type-check": "tsc --noEmit"
65
+ },
56
66
  "dependencies": {
57
67
  "@modelcontextprotocol/sdk": "^1.26.0",
58
68
  "commander": "^14.0.0",
@@ -66,13 +76,5 @@
66
76
  "prettier": "^3.8.1",
67
77
  "typescript": "^5.9.3",
68
78
  "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
79
  }
78
- }
80
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Georgiy Tarasov
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.