Skip to content

Programmatic API

The muxed package exposes a full JavaScript/TypeScript client for managing MCP servers, calling tools, reading resources, and controlling muxed — all programmatically.

Terminal window
npm install muxed
import { createClient } from 'muxed';
// or
import { createClient } from 'muxed/client';
import { createClient } from 'muxed';
const client = await createClient();
// or with options:
const client = await createClient({
configPath: './muxed.config.json',
autoStart: true, // default: true — starts daemon if not running
});
OptionTypeDefaultDescription
configPathstringPath to muxed.config.json. Uses default resolution if omitted.
autoStartbooleantrueAuto-starts the daemon if not running. Set to false to throw if the daemon is not running.

List all tools, optionally filtered by server name.

const tools = await client.tools();
const tools = await client.tools('filesystem');
// Returns: Array<{ server: string; tool: Tool }>

Get a single tool’s schema by its fully qualified name.

const tool = await client.tool('filesystem/read_file');
// Returns: Tool

Search tools by name or description.

const matches = await client.grep('search');
// Returns: Array<{ server: string; tool: Tool }>

Call a tool and wait for the result.

const result = await client.call('filesystem/read_file', { path: '/tmp/file.txt' });
// Returns: CallResult

Pass a timeout (in milliseconds) to limit how long the call can take:

const result = await client.call('server/tool', args, { timeout: 5000 });

Extract specific fields from the response to reduce context window usage:

const result = await client.call('db/query', { sql: 'SELECT * FROM users' }, {
fields: ['rows[].name', 'rows[].email'],
});

Field filtering only applies to JSON-parseable content (structuredContent or text blocks with valid JSON). Non-JSON responses are returned unchanged.

Validate arguments against a tool’s schema without executing the call (dry-run mode).

const result = await client.validate('postgres/query', { sql: 'DROP TABLE users' });
// Returns: ValidationResult { valid, errors, warnings, tool? }
if (!result.valid) {
console.error('Validation errors:', result.errors);
}
for (const warning of result.warnings) {
console.warn(warning); // e.g. "Tool is marked as destructive."
}

Checks required fields, unknown fields, type mismatches, and enum values. Warnings include tool annotation hints (destructive, not idempotent, not read-only).

Call a tool asynchronously. Returns a task handle you can poll for completion.

const task = await client.callAsync('analytics/export', { range: '30d' });
// Returns: TaskHandle { taskId, server, status }

List resources, optionally filtered by server name.

const resources = await client.resources();
const resources = await client.resources('server-name');
// Returns: Array<{ server: string; resource: Resource }>

Read a resource by server name and URI.

const content = await client.read('server-name', 'resource://uri');
// Returns: ReadResourceResult

List prompts, optionally filtered by server name.

const prompts = await client.prompts();
const prompts = await client.prompts('server-name');
// Returns: Array<{ server: string; prompt: Prompt }>

Get a prompt result by server name and prompt name.

const result = await client.prompt('server-name', 'prompt-name', { arg: 'value' });
// Returns: GetPromptResult

Request argument completions from a server.

const result = await client.complete(
'server-name',
{ type: 'ref/prompt', name: 'prompt-name' },
{ name: 'argument', value: 'partial-value' }
);
// Returns: CompleteResult

List tasks, optionally filtered by server name.

const tasks = await client.tasks();
const tasks = await client.tasks('server-name');
// Returns: Array<{ server: string; tasks: Array<Record<string, unknown>> }>

Get the status of a specific task.

const status = await client.task('server-name', 'task-id');

Get the result of a completed task.

const result = await client.taskResult('server-name', 'task-id');

Cancel a running task.

const cancelled = await client.taskCancel('server-name', 'task-id');

List all configured servers and their state.

const servers = await client.servers();
// Returns: ServerState[]

Get the current daemon status.

const status = await client.status();
// Returns: DaemonStatus { pid, uptime, serverCount, servers }

Reload the daemon configuration. Returns a summary of what changed.

const changes = await client.reload();
// Returns: ReloadResult { added, removed, changed }

Stop the daemon process.

await client.stop();

Close the client connection. Call this when you are done using the client.

client.close();
type CallResult = {
content: Array<{
type: string;
text?: string;
mimeType?: string;
data?: string;
name?: string;
uri?: string;
resource?: { text?: string; blob?: string; mimeType?: string };
}>;
structuredContent?: Record<string, unknown>;
isError?: boolean;
};
type CallOptions = {
timeout?: number;
fields?: string[];
};
type ValidationResult = {
valid: boolean;
errors: string[];
warnings: string[];
tool?: { name: string; annotations?: Record<string, unknown> };
};
type TaskHandle = { taskId: string; server: string; status: string };

The following types are available as named exports from the muxed package:

Tool, Resource, Prompt, ServerState, ServerConfig, DaemonStatus, ReloadResult, TaskHandle, ValidationResult, MuxedError

Run muxed typegen to generate TypeScript types from your live MCP tool schemas:

Terminal window
muxed typegen

This generates muxed.generated.d.ts in node_modules/muxed/ using module augmentation on the MuxedToolMap interface. After running typegen:

  • call() gets autocomplete on tool names
  • Arguments are typed based on the tool’s inputSchema
  • Results are typed based on the tool’s outputSchema (when available)
  • Tool and property descriptions become JSDoc comments
  • Unknown tools fall back to untyped CallResult

Uses json-schema-to-typescript for schema conversion. Refresh with muxed typegen when tools change — same workflow as prisma generate.

import { createClient, MuxedError } from 'muxed';
const client = await createClient();
try {
await client.call('server/nonexistent-tool');
} catch (err) {
if (err instanceof MuxedError) {
console.error(`RPC error ${err.code}: ${err.message}`);
// err.data contains structured error info
const data = err.data as { code: string; suggestion: string; context?: Record<string, unknown> };
console.error(`Error code: ${data.code}`); // e.g. "TOOL_NOT_FOUND"
console.error(`Suggestion: ${data.suggestion}`); // e.g. "Did you mean: server/similar_tool?"
if (data.context?.similarTools) {
console.error(`Similar tools: ${(data.context.similarTools as string[]).join(', ')}`);
}
}
}

MuxedError is thrown for JSON-RPC errors (tool not found, server not found, etc.). Standard Error is thrown for transport failures.

All errors include a machine-readable code in err.data:

CodeDescription
TOOL_NOT_FOUNDTool doesn’t exist (includes fuzzy-matched similar tool names)
SERVER_NOT_FOUNDServer doesn’t exist (includes available server names)
SERVER_NOT_CONNECTEDServer exists but isn’t connected
INVALID_FORMATTool name is not in server/tool format
MISSING_PARAMETERRequired parameter not provided
INVALID_ARGUMENTSArguments failed schema validation
TIMEOUTTool call exceeded timeout
const client = await createClient();
const tools = await client.tools();
console.log(`Found ${tools.length} tools`);
const result = await client.call('filesystem/read_file', { path: '/etc/hostname' });
console.log(result.content[0]?.text);
client.close();
const client = await createClient();
const matches = await client.grep('search');
for (const { server, tool } of matches) {
console.log(`${server}/${tool.name}: ${tool.description}`);
}
const client = await createClient();
const [users, tickets] = await Promise.all([
client.call('posthog/query-run', { query: { kind: 'HogQLQuery', query: 'SELECT ...' } }),
client.call('intercom/search-conversations', { query: 'billing', limit: 10 }),
]);
const client = await createClient();
const handle = await client.callAsync('server/long-running-tool', { input: 'data' });
let status = await client.task(handle.server, handle.taskId);
while (status.status !== 'completed' && status.status !== 'failed') {
await new Promise(r => setTimeout(r, 1000));
status = await client.task(handle.server, handle.taskId);
}
const result = await client.taskResult(handle.server, handle.taskId);