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 +266 -0
- package/dist/cli.mjs +112 -112
- package/dist/client/index.d.mts +9 -9
- package/dist/client/index.mjs +20 -20
- package/package.json +13 -11
- package/LICENSE +0 -21
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
|
|
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", "
|
|
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(), "
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
197
|
-
return path.join(os.homedir(), ".
|
|
196
|
+
function getMuxedDir() {
|
|
197
|
+
return path.join(os.homedir(), ".muxed");
|
|
198
198
|
}
|
|
199
199
|
function getSocketPath() {
|
|
200
|
-
return path.join(
|
|
200
|
+
return path.join(getMuxedDir(), "muxed.sock");
|
|
201
201
|
}
|
|
202
202
|
function getPidPath() {
|
|
203
|
-
return path.join(
|
|
203
|
+
return path.join(getMuxedDir(), "muxed.pid");
|
|
204
204
|
}
|
|
205
205
|
function getLogPath() {
|
|
206
|
-
return path.join(
|
|
206
|
+
return path.join(getMuxedDir(), "muxed.log");
|
|
207
207
|
}
|
|
208
|
-
function
|
|
209
|
-
fs.mkdirSync(
|
|
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(
|
|
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: "
|
|
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 = "
|
|
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: "
|
|
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>
|
|
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
|
|
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>
|
|
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: "
|
|
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 \`
|
|
1322
|
-
// Run \`
|
|
1321
|
+
return `// Auto-generated by \`muxed typegen\` – do not edit
|
|
1322
|
+
// Run \`muxed typegen\` to regenerate
|
|
1323
1323
|
|
|
1324
|
-
declare module '
|
|
1325
|
-
interface
|
|
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
|
|
1878
|
-
const outputPath = path.join(
|
|
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 (
|
|
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
|
-
|
|
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(
|
|
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
|
|
1968
|
+
function isMuxedProcess(pid) {
|
|
1969
1969
|
try {
|
|
1970
1970
|
const cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, "utf-8");
|
|
1971
|
-
return cmdline.includes("
|
|
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
|
-
|
|
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) || !
|
|
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 (!
|
|
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) && !
|
|
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
|
|
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 = "
|
|
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 `
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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 === "
|
|
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
|
|
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
|
|
3046
|
+
function getMuxedEntry(agent) {
|
|
3047
3047
|
if (agent.serversKey === "servers") return {
|
|
3048
3048
|
type: "stdio",
|
|
3049
3049
|
command: "npx",
|
|
3050
|
-
args: ["
|
|
3050
|
+
args: ["muxed@latest", "proxy"]
|
|
3051
3051
|
};
|
|
3052
3052
|
return {
|
|
3053
3053
|
command: "npx",
|
|
3054
|
-
args: ["
|
|
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] = {
|
|
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
|
|
3066
|
+
function getMuxedConfigPath(scope, explicitPath) {
|
|
3067
3067
|
if (explicitPath) return explicitPath;
|
|
3068
|
-
if (scope === "local") return path.join(process.cwd(), "
|
|
3069
|
-
return path.join(home, ".config", "
|
|
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
|
|
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
|
|
3147
|
+
const muxedPath = getMuxedConfigPath(discovered.some((d) => d.agent.scope === "local") ? "local" : "global", configPath);
|
|
3148
3148
|
let existingServers = {};
|
|
3149
|
-
if (fs.existsSync(
|
|
3150
|
-
existingServers = JSON.parse(fs.readFileSync(
|
|
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)
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
3243
|
-
2. You MUST call 'npx
|
|
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
|
|
3248
|
-
**ALWAYS** run npx
|
|
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
|
|
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
|
|
3266
|
-
npx
|
|
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
|
|
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
|
|
3273
|
-
npx
|
|
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
|
|
3277
|
-
npx
|
|
3278
|
-
npx
|
|
3279
|
-
npx
|
|
3280
|
-
npx
|
|
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
|
|
3288
|
-
[Calls npx
|
|
3289
|
-
Assistant: I need to check the schema first. Let me call \`npx
|
|
3290
|
-
[Calls npx
|
|
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
|
|
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
|
|
3298
|
-
[Calls npx
|
|
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
|
|
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
|
|
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
|
|
3308
|
+
[Calls npx muxed grep "email/*copy*". No results found.]
|
|
3309
3309
|
Assistant: Let me try another pattern.
|
|
3310
|
-
[Calls npx
|
|
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
|
|
3312
|
+
[Calls npx muxed tools email]
|
|
3313
3313
|
Assistant: Let me check the schema first.
|
|
3314
|
-
[Calls npx
|
|
3314
|
+
[Calls npx muxed info email/duplicate]
|
|
3315
3315
|
Assistant: Now I have the schema. Let me make the call.
|
|
3316
|
-
[Calls npx
|
|
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
|
|
3324
|
-
WRONG - You must call npx
|
|
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
|
|
3331
|
-
WRONG - Pre-approved permissions don't mean you know the schema. ALWAYS call npx
|
|
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
|
|
3337
|
-
WRONG - You must call npx
|
|
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
|
|
3344
|
-
npx
|
|
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
|
|
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
|
|
3350
|
+
npx muxed call weather/get_location '{}'
|
|
3351
3351
|
|
|
3352
3352
|
# Tool call with parameters
|
|
3353
|
-
npx
|
|
3353
|
+
npx muxed call database/query '{"table": "users", "limit": 10}'
|
|
3354
3354
|
|
|
3355
3355
|
# Complex JSON using stdin (for nested objects/arrays)
|
|
3356
|
-
npx
|
|
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
|
|
3365
|
+
Call the \`npx muxed -h\` to see all available commands.
|
|
3366
3366
|
|
|
3367
|
-
Below are the instructions for the connected MCP servers in
|
|
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: "
|
|
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
|
-
|
|
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
|
|
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
|
|
3617
|
-
const outputPath = path.join(
|
|
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("
|
|
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:");
|
package/dist/client/index.d.mts
CHANGED
|
@@ -67,18 +67,18 @@ type ServerState = {
|
|
|
67
67
|
};
|
|
68
68
|
//#endregion
|
|
69
69
|
//#region src/client/socket.d.ts
|
|
70
|
-
declare class
|
|
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 `
|
|
78
|
-
interface
|
|
79
|
-
type HasTools = keyof
|
|
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
|
|
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
|
|
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
|
|
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<
|
|
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,
|
|
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 };
|
package/dist/client/index.mjs
CHANGED
|
@@ -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
|
|
7
|
-
return path.join(os.homedir(), ".
|
|
6
|
+
function getMuxedDir() {
|
|
7
|
+
return path.join(os.homedir(), ".muxed");
|
|
8
8
|
}
|
|
9
9
|
function getSocketPath() {
|
|
10
|
-
return path.join(
|
|
10
|
+
return path.join(getMuxedDir(), "muxed.sock");
|
|
11
11
|
}
|
|
12
12
|
function getPidPath() {
|
|
13
|
-
return path.join(
|
|
13
|
+
return path.join(getMuxedDir(), "muxed.pid");
|
|
14
14
|
}
|
|
15
|
-
function
|
|
16
|
-
fs.mkdirSync(
|
|
15
|
+
function ensureMuxedDir() {
|
|
16
|
+
fs.mkdirSync(getMuxedDir(), { recursive: true });
|
|
17
17
|
}
|
|
18
18
|
function getLockPath() {
|
|
19
|
-
return path.join(
|
|
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
|
|
38
|
+
function isMuxedProcess(pid) {
|
|
39
39
|
try {
|
|
40
40
|
const cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, "utf-8");
|
|
41
|
-
return cmdline.includes("
|
|
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
|
-
|
|
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) || !
|
|
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 (!
|
|
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) && !
|
|
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
|
|
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 = "
|
|
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 `
|
|
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
|
|
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
|
|
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
|
|
349
|
+
return new MuxedClient(sendRequest);
|
|
350
350
|
}
|
|
351
|
-
export {
|
|
351
|
+
export { MuxedClient, MuxedError, createClient };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muxed",
|
|
3
|
-
"version": "0.1.
|
|
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.
|