# zidane/mcp

> The Model Context Protocol client that connects servers and turns their tools into agent tools.

Import: `import { … } from 'zidane/mcp'`. Source: [src/mcp/index.ts](https://github.com/Tahul/zidane/blob/main/src/mcp/index.ts).

Use `connectMcpServers` to get MCP tools outside an agent. It namespaces each discovered tool as `mcp_<server>_<tool>`. `close()` on the returned connection closes every server. The rest of the entry point covers config normalization, environment placeholder expansion, result flattening and the remote URL and header rules. Agents connect servers through the `mcpServers` option, described in [MCP servers](https://zidane.si/docs/mcp.md).

```ts
import { connectMcpServers } from 'zidane/mcp'

const connection = await connectMcpServers([
  { name: 'fs', transport: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', '.'] },
])
console.log(Object.keys(connection.tools))
await connection.close()
```

## Functions

### `attachStderrWarnPump`

Function.

Reads a stdio child's stderr to the end and fires `mcp:warn` for each non-empty line. Does nothing when the transport has no stderr stream.

```ts
function attachStderrWarnPump(
  transport: { stderr?: { on: (event: string, listener: (...args: unknown[]) => void) => unknown } | null },
  serverName: string,
  hooks: Hookable<AgentHooks, HookKeys<AgentHooks>> | undefined,
): void
```

**Parameters**

- `transport` (`{ stderr?: { on: (event: string, listener: (...args: unknown[]) => void) => unknown } | null }`): Transport that may expose a `stderr` stream.
- `serverName` (`string`): Server name reported in each `mcp:warn` payload.
- `hooks` (`Hookable<AgentHooks, HookKeys<AgentHooks>> | undefined`): Agent hooks to fire `mcp:warn` on.

Source: [src/mcp/transport.ts:268](https://github.com/Tahul/zidane/blob/main/src/mcp/transport.ts#L268)

### `connectMcpServers`

Function.

Connect to MCP servers and discover their tools.

Each tool is namespaced as `mcp_{serverName}_{toolName}` to avoid
collisions with agent tools or tools from other servers.

```ts
function connectMcpServers(
  configs: McpServerConfig[],
  _clientFactory?: McpClientFactory,
  hooks?: Hookable<AgentHooks, HookKeys<AgentHooks>>,
  options?: ConnectMcpServersOptions,
): Promise<McpConnection>
```

**Parameters**

- `configs` (`McpServerConfig[]`): Array of MCP server configurations
- `_clientFactory?` (`McpClientFactory`): Internal: override client construction for testing
- `hooks?` (`Hookable<AgentHooks, HookKeys<AgentHooks>>`): Optional agent hooks for firing mcp:connect, mcp:error, mcp:close events
- `options?` (`ConnectMcpServersOptions`): Optional bootstrap knobs (e.g. OAuth provider injection)

**Returns** `Promise<McpConnection>`

**Example**

```ts
import { connectMcpServers } from 'zidane/mcp'

const connection = await connectMcpServers([
  { name: 'fs', transport: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', '.'] },
])
console.log(Object.keys(connection.tools))
await connection.close()
```

Source: [src/mcp/index.ts:390](https://github.com/Tahul/zidane/blob/main/src/mcp/index.ts#L390)

### `createMcpServerPool`

Function.

Creates the pool of host-spawned stdio MCP servers that `connectMcpServers`
batches share. Create one per host.

```ts
function createMcpServerPool(options?: McpServerPoolOptions): McpServerPool
```

**Parameters**

- `options?` (`McpServerPoolOptions`, default `{}`): Where shared warnings go (`logger`) and where pids are recorded (`recordDir`).

**Returns** `McpServerPool`: The pool.

Source: [src/mcp/server-pool.ts:190](https://github.com/Tahul/zidane/blob/main/src/mcp/server-pool.ts#L190)

### `createStderrLineEmitter`

Function.

Line-splitting core shared by the two stderr pumps: the Node-stream one
below (host stdio children) and the async-iterable one in
`./context-transport.ts` (children living in an execution context).
Same buffering, same overlong-line cap, same fire-and-forget `mcp:warn`.

```ts
function createStderrLineEmitter(
  serverName: string,
  hooks: Hookable<AgentHooks, HookKeys<AgentHooks>> | undefined,
): { flush: () => void; push: (text: string) => void }
```

**Parameters**

- `serverName` (`string`): Server name reported in each `mcp:warn` payload.
- `hooks` (`Hookable<AgentHooks, HookKeys<AgentHooks>> | undefined`): Agent hooks to fire `mcp:warn` on. Lines are dropped when `undefined`.

**Returns** `{ flush: () => void; push: (text: string) => void }`

Source: [src/mcp/transport.ts:223](https://github.com/Tahul/zidane/blob/main/src/mcp/transport.ts#L223)

### `deriveMcpToolMeta`

Function.

Recover a discovered tool's `{ server, toolName }` from its namespaced name
(`mcp_<server>_<tool>`). Both server and tool names can contain underscores,
so this matches against the known server list by longest server-name prefix
rather than splitting blindly. Tools that match no configured server (custom
connectors with a different naming scheme) fall back to
`{ server: '', toolName: <namespacedName> }`.

```ts
function deriveMcpToolMeta(
  namespacedName: string,
  servers: readonly McpServerConfig[],
): McpToolMeta
```

**Parameters**

- `namespacedName` (`string`): Tool name of the form `mcp_<server>_<tool>`.
- `servers` (`readonly McpServerConfig[]`): Configured servers to match the prefix against.

**Returns** `McpToolMeta`

Source: [src/mcp/tool-meta.ts:39](https://github.com/Tahul/zidane/blob/main/src/mcp/tool-meta.ts#L39)

### `expandEnvPlaceholders`

Function.

Expand environment-variable placeholders in one config string. The
`:-default` form follows shell semantics — the default substitutes when
the variable is unset OR EMPTY (that's what `:-` means in every shell,
and what users pasting the form expect; empty-but-set without a default
still expands to the empty string, as `${VAR}` does in a shell). An
unset variable without a default is left VERBATIM (matching Claude
Code) — a literal `${…}` failing loudly at the server beats an empty
token failing mysteriously — and a debug-gated warning names the
missing variable.

```ts
function expandEnvPlaceholders(
  value: string,
  env?: Record<string, string | undefined>,
): string
```

**Parameters**

- `value` (`string`): String that may contain `${VAR}`, `${VAR:-default}` or `${env:VAR}`.
- `env?` (`Record<string, string | undefined>`, default `process.env`): Variables to resolve against. Default: `process.env`.

**Returns** `string`

Source: [src/mcp/server-config.ts:107](https://github.com/Tahul/zidane/blob/main/src/mcp/server-config.ts#L107)

### `expandMcpConfigEnv`

Function.

Expanded COPY of a server config for runtime use — `${VAR}` placeholders
resolved across every string field that can carry secrets or
machine-specific paths (command, args, env/header values, url, cwd).

Deliberately NOT part of `normalizeMcpServers`: normalized configs are
round-tripped back to disk by config writers (e.g. the Claude import
copies parsed entries into zidane-owned `mcps.json`), and expanding
there would MATERIALIZE resolved tokens into files. Expansion happens
only at the transport boundary (`createTransport`, the login
flow's mirror) — secrets exist in the connecting process's memory and
nowhere else.

```ts
function expandMcpConfigEnv(
  config: McpServerConfig,
  env?: Record<string, string | undefined>,
): McpServerConfig
```

**Parameters**

- `config` (`McpServerConfig`): Server config to expand.
- `env?` (`Record<string, string | undefined>`, default `process.env`): Variables to resolve against. Default: `process.env`.

**Returns** `McpServerConfig`

Source: [src/mcp/server-config.ts:142](https://github.com/Tahul/zidane/blob/main/src/mcp/server-config.ts#L142)

### `isAllowedRemoteUrl`

Function.

The transport-reachability rule for a remote MCP endpoint: HTTPS anywhere,
plaintext HTTP only when it stays on the machine, nothing else.

THE single encoding of that policy. It's enforced at two points on opposite
sides of the plugin boundary — once when validating a plugin's `mcp.json`
(`plugins/mcp.ts`), and again per redirect hop inside
[`originPinnedFetch`](https://zidane.si/docs/reference/mcp.md#originPinnedFetch) — and those two must never disagree. If they did,
a URL the loader refused could still be reached by redirect, which is the
exact hole the pinning exists to close. Relaxing the rule (private ranges,
`*.localhost`, …) is then a one-line change that both sites inherit.

Scheme and host only. Authoring rules that apply to a CONFIGURED url but
have no meaning for a redirect target — no userinfo, no fragment — stay with
the loader.

```ts
function isAllowedRemoteUrl(url: URL): boolean
```

**Parameters**

- `url` (`URL`): Endpoint to check.

**Returns** `boolean`

Source: [src/mcp/origin-pinned-fetch.ts:73](https://github.com/Tahul/zidane/blob/main/src/mcp/origin-pinned-fetch.ts#L73)

### `nonOwningMcpView`

Function.

Non-owning view of a live MCP connection: the SAME tools and
instructions, but a no-op `close`. Handed to consumers that must never
tear down transports they don't own — the loop's per-call
`ToolContext.mcpConnection` and child agents spawned off the parent's
connection — because only the owning agent's destroy path may close the
real thing, and a doc comment alone is too weak a guard. Single shared
builder so the call sites can't drift when this shape grows a field.

```ts
function nonOwningMcpView(
  connection: Pick<McpConnection, 'tools' | 'instructions'>,
): McpConnection
```

**Parameters**

- `connection` (`Pick<McpConnection, 'tools' | 'instructions'>`): Connection to view. Only its `tools` and `instructions` are read.

**Returns** `McpConnection`

Source: [src/mcp/tool-meta.ts:21](https://github.com/Tahul/zidane/blob/main/src/mcp/tool-meta.ts#L21)

### `normalizeMcpBlocks`

Function.

Normalize MCP `CallToolResult.content` to zidane's [`[]`](https://zidane.si/docs/reference/types.md#ToolResultContent) shape.

Handles the MCP content block types:

- `text` → preserved as `{type:'text', text}`
- `image` → preserved as `{type:'image', mediaType, data}` (MCP uses `mimeType`)
- `audio` → `{type:'audio', mediaType, data}`; mediaType reconciled/sniffed when
  the declared `mimeType` is missing or not an `audio/*` type
- `video` → `{type:'video', mediaType, data}` (non-standard, by analogy)
- `resource` with embedded text → flattened to a text block
- `resource` with embedded blob → routed to image/audio/video by sniffing bytes
  first, with declared `mimeType` as a fallback when bytes are unidentifiable
  (it's OPTIONAL on EmbeddedResource per the MCP spec, and often imprecise)
- Any unrecognized block → JSON-stringified fallback text block (lossy but safe)

Returns `null` when the input is not an array — callers should fall back to an empty
result in that case.

```ts
function normalizeMcpBlocks(content: unknown): ToolResultContent[] | null
```

**Parameters**

- `content` (`unknown`): `CallToolResult.content` from an MCP tool call.

**Returns** `ToolResultContent[] | null`

Source: [src/mcp/tool-result.ts:121](https://github.com/Tahul/zidane/blob/main/src/mcp/tool-result.ts#L121)

### `normalizeMcpServers`

Function.

Normalize MCP server configs from any common shape to `McpServerConfig[]`.

Accepts:

- `McpServerConfig[]` — zidane native (pass-through).
- `McpServerConfig` — a single config object (wrapped to a 1-element array).
- `Record<string, RawShape>` — name-keyed map (common in host-SDK configs), where the key is the server name.
- Mixed shapes with `type` vs `transport`, `httpUrl`/`sseUrl` vs `url`.

Returns `[]` when `input` is nullish. Throws a descriptive error when the transport
cannot be inferred from a given entry, or when the input shape is unsupported.

```ts
function normalizeMcpServers(input: unknown): McpServerConfig[]
```

**Parameters**

- `input` (`unknown`): Server configs in any of the accepted shapes, or nullish.

**Returns** `McpServerConfig[]`

Source: [src/mcp/server-config.ts:320](https://github.com/Tahul/zidane/blob/main/src/mcp/server-config.ts#L320)

### `originPinnedFetch`

Function.

Wrap `baseFetch` so `headerNames` are dropped on any hop that leaves the
origin of `configuredUrl`, and so a redirect cannot downgrade the session
to cleartext.

`authorization` is ALWAYS protected, whether or not the caller listed it.
The MCP SDK's OAuth provider injects that header per request rather than
through `McpServerConfig.headers` — `bootstrapServer` attaches a provider to
a flagless server whenever stored tokens exist — so a strip list derived
only from the configured header names would leave the bearer token exposed
on exactly the servers that have one.

```ts
function originPinnedFetch(
  configuredUrl: string,
  headerNames: readonly string[],
  baseFetch?: PinnedFetch,
): PinnedFetch
```

**Parameters**

- `configuredUrl` (`string`): Configured server URL. Its origin is the pinned origin.
- `headerNames` (`readonly string[]`): Configured header names to drop when a redirect leaves the pinned origin.
- `baseFetch?` (`PinnedFetch`, default `fetch`): Fetch implementation to wrap. Default: the global `fetch`.

**Returns** `PinnedFetch`

Source: [src/mcp/origin-pinned-fetch.ts:113](https://github.com/Tahul/zidane/blob/main/src/mcp/origin-pinned-fetch.ts#L113)

### `resolveTransportConfig`

Function.

The config a transport is actually built from.

`${VAR}` placeholders resolve HERE, not at normalize time — see
[`expandMcpConfigEnv`](https://zidane.si/docs/reference/mcp.md#expandMcpConfigEnv) for why (config writers round-trip normalized
configs to disk; expanded secrets must never ride along).

A `preExpanded` config skips the pass entirely. The Agent Plugins loader
has already substituted the only two placeholders that spec permits, in
only the three fields it permits, so a second pass would both widen the
scope (into `command` / `url` / `headers`) and let an ambient
`PLUGIN_ROOT` env var reach text the spec says stays literal. See
`McpServerConfig.preExpanded`.

```ts
function resolveTransportConfig(config: McpServerConfig): McpServerConfig
```

**Parameters**

- `config` (`McpServerConfig`): Normalized server config.

**Returns** `McpServerConfig`

Source: [src/mcp/server-config.ts:62](https://github.com/Tahul/zidane/blob/main/src/mcp/server-config.ts#L62)

### `resultToString`

Function.

Lossy flattener — converts MCP `CallToolResult.content` blocks to a single
string. Text blocks are extracted; non-text blocks are JSON-stringified.

Use this only at UI / log boundaries that require a string. The agent
loop itself routes through [`normalizeMcpBlocks`](https://zidane.si/docs/reference/mcp.md#normalizeMcpBlocks) so image blocks
survive into provider-native tool_result content (Anthropic blocks,
OpenAI companion-user-message).

```ts
function resultToString(content: unknown): string
```

**Parameters**

- `content` (`unknown`): `CallToolResult.content` from an MCP tool call. Anything other than an array gives an empty string.

**Returns** `string`

Source: [src/mcp/tool-result.ts:18](https://github.com/Tahul/zidane/blob/main/src/mcp/tool-result.ts#L18)

### `withoutEnvPlaceholders`

Function.

A config from a source that must never read this process's environment
(a machine's project file, `docs/MACHINES.md`): every `${VAR}` placeholder
resolved NOW to its `:-default`, else to the empty string, so the
transport's own expansion has nothing of this process's secrets to put
into a command, an env value, a URL or a header.

```ts
function withoutEnvPlaceholders(config: McpServerConfig): McpServerConfig
```

**Parameters**

- `config` (`McpServerConfig`): Server config to strip.

**Returns** `McpServerConfig`

Source: [src/mcp/server-config.ts:162](https://github.com/Tahul/zidane/blob/main/src/mcp/server-config.ts#L162)

### `wrapDiscoveredMcpTools`

Function.

Apply an [`McpToolWrap`](https://zidane.si/docs/reference/agent.md#McpToolWrap) to every entry of a discovered-tool map,
resolving each tool's [`McpToolMeta`](https://zidane.si/docs/reference/agent.md#McpToolMeta) from `servers`. Returns a fresh
map; the input is untouched. Used by the agent to apply the
`AgentOptions.mcpToolWrap` hook to discovered tools.

```ts
function wrapDiscoveredMcpTools(
  tools: Record<string, ToolDef>,
  wrap: McpToolWrap,
  servers: readonly McpServerConfig[],
): Record<string, ToolDef>
```

**Parameters**

- `tools` (`Record<string, ToolDef>`): Discovered tools keyed by namespaced name.
- `wrap` (`McpToolWrap`): Wrapper applied to each tool.
- `servers` (`readonly McpServerConfig[]`): Configured servers used to resolve each tool's metadata.

**Returns** `Record<string, ToolDef>`

Source: [src/mcp/tool-meta.ts:60](https://github.com/Tahul/zidane/blob/main/src/mcp/tool-meta.ts#L60)

## Constants

### `MCP_POOL_IDLE_CLOSE_MS`

Constant.

How long a server nobody leases stays up, ready for the next session. Kept
by design: a stdio server costs seconds to start, and sessions come and go.

```ts
const MCP_POOL_IDLE_CLOSE_MS: number
```

Source: [src/mcp/server-pool.ts:48](https://github.com/Tahul/zidane/blob/main/src/mcp/server-pool.ts#L48)

### `MCP_POOL_KILL_SWITCH`

Constant.

Set to `1`, every server is private to its batch, as without a pool.

```ts
const MCP_POOL_KILL_SWITCH = 'ZIDANE_NO_MCP_POOL'
```

Source: [src/mcp/server-pool.ts:51](https://github.com/Tahul/zidane/blob/main/src/mcp/server-pool.ts#L51)

## Interfaces

### `ConnectMcpServersOptions`

Interface.

Optional knobs threaded into per-server bootstrap. Reserved for runtime
concerns the agent shell injects (credential storage, OAuth providers);
the `McpServerConfig` shape stays pure data.

```ts
interface ConnectMcpServersOptions
```

**Properties**

- `buildAuthProvider?` (`(config: McpServerConfig) => OAuthClientProvider | undefined`): Build a non-interactive `OAuthClientProvider` for a given server. Called
  lazily — only invoked when the bootstrap actually needs auth (explicit
  `auth: 'oauth'` flag, or a UnauthorizedError that meets the auto-detect
  criteria). Return `undefined` to opt this server out of OAuth even if it
  was requested (e.g. the host doesn't have a credential store for it).

  "Non-interactive" — the provider must NOT trigger a browser flow during
  bootstrap. Use a `redirectUri` of `undefined` so the SDK reads stored
  tokens, refreshes on expiry, and throws `UnauthorizedError` if tokens
  are missing or refresh fails. The host then surfaces an
  `mcp:auth:required` event so the user can opt in to interactive login.
- `execution?` (`McpExecutionBinding`): Execution context binding for servers declaring `scope: 'context'`.
  Safe to pass unconditionally: the binding is lazy, and its
  `getHandle()` is invoked only when a context-scoped stdio server
  actually builds its transport — host-only configs never touch it, so
  no spawn cost is paid for merely supplying it. Omitting it simply
  makes any `scope: 'context'` server fail bootstrap with a pointed
  error.
- `pool?` (`McpServerPool`): Share host-spawned stdio servers with every other batch connected through
  the same pool (`createMcpServerPool`): a server whose config matches one
  already running is leased instead of started again, and `close()` gives
  the lease back instead of ending the process. Hosts running many agents
  at once pass one pool for all of them. Omitted: every server is private
  to this batch.
- `session?` (`string`): The session this batch runs for, with `pool`: a `shared: false` server
  runs once per session (its agent and their subagents share it) instead
  of once per batch, and the pool counts the sessions holding a server.

Source: [src/mcp/index.ts:311](https://github.com/Tahul/zidane/blob/main/src/mcp/index.ts#L311)

### `McpConnection`

Interface.

A live connection to a set of MCP servers: the discovered tools and a way to close every transport.

```ts
interface McpConnection
```

**Properties**

- `close` (`() => Promise<void>`): Closes every server connection. Servers that fail to close in time are force-stopped.
- `instructions?` (`Map<string, string>`): Per-server `instructions` payload from the MCP `initialize` handshake,
  keyed by server name. Each server may return free-form guidance the
  model is meant to read alongside the tool catalog — typical content
  is "the database is already provisioned, use `apply_migration`
  directly" or "always wrap SELECTs in a transaction". The agent
  renders these into a system-prompt section when
  `behavior.surfaceMcpInstructions` is on (default).

  Servers that omit the field, return an empty string, or fail to
  connect contribute no entry. Lazy-connected servers contribute an
  entry only once their first connect resolves — until then the model
  never sees their instructions, matching the schema-loading semantics
  of `lazyConnect`.

  Map (not Record) so insertion order matches server config order and
  the rendered section is byte-stable for the cache breakpoint.

  Optional so custom `mcpConnector` implementations may omit it; the
  framework's own `connectMcpServers` always returns a Map (possibly
  empty).
- `refresh?` (`() => Promise<void>`): Moves each pooled server whose instance was retired (its config, toggle
  or sign-in changed) to the current one; the tools stay the same objects.
  The agent calls it when a run starts. Never rejects. Absent without a pool.
- `tools` (`Record<string, ToolDef>`): Discovered tools keyed by namespaced name, `mcp_<server>_<tool>`.

Source: [src/mcp/index.ts:53](https://github.com/Tahul/zidane/blob/main/src/mcp/index.ts#L53)

### `McpExecutionBinding`

Interface.

The execution context + live handle a `scope: 'context'` stdio server is
spawned into. Supplied by the agent once its handle exists; absent for
host-only setups, which is why every field is required here but the
binding itself is optional at the call site.

```ts
interface McpExecutionBinding
```

**Properties**

- `context` (`ExecutionContext`): Execution context that hosts the server process.
- `getHandle` (`() => Promise<ExecutionHandle>`): Resolve the execution handle, invoked ONLY when a `scope: 'context'`
  stdio server actually builds its transport.

  Lazy on purpose. An eagerly-resolved handle would make merely *having* a
  context-scoped server in the config force a sandbox spawn — including
  for sub-agents, which inherit `mcpServers` but reuse the parent's live
  connection through `nonOwningMcpConnector` and never touch this binding.
  That would cold-boot a fresh sandbox per `spawn()` for nothing. Deferring
  also means the agent needs no ordering special case: the handle is
  created at the moment of first use, which is necessarily before the
  `execStream` call that needs it.

Source: [src/mcp/transport.ts:298](https://github.com/Tahul/zidane/blob/main/src/mcp/transport.ts#L298)

### `McpServerInstance`

Interface.

One pooled server process, for a host's MCP page.

```ts
interface McpServerInstance
```

**Properties**

- `cwd` (`string`): Its folder; empty when it runs in the host's own.
- `name` (`string`): The server's name (`McpServerConfig.name`).
- `pid?` (`number`): Its process, while it runs.
- `retired` (`boolean`): Its config, toggle or sign-in changed: it closes once its last session lets go.
- `sessions` (`number`): How many sessions hold it now; 0 while it idles before closing.
- `shared` (`boolean`): One process for every session (`true`), or this one session's (`shared: false`).

Source: [src/mcp/server-pool.ts:82](https://github.com/Tahul/zidane/blob/main/src/mcp/server-pool.ts#L82)

### `McpServerMatch`

Interface.

Which pooled servers a call means: by name, in one folder or all.

```ts
interface McpServerMatch
```

**Properties**

- `cwd?` (`string`): The server's folder (its `cwd`, the session's when it has none). Omitted: every folder.
- `name` (`string`): The server's name (`McpServerConfig.name`).

Source: [src/mcp/server-pool.ts:74](https://github.com/Tahul/zidane/blob/main/src/mcp/server-pool.ts#L74)

### `McpServerPool`

Interface.

Host-spawned stdio MCP servers shared across `connectMcpServers` batches. Create one per host with [`createMcpServerPool`](https://zidane.si/docs/reference/mcp.md#createMcpServerPool).

```ts
interface McpServerPool
```

**Properties**

- `dispose` (`() => Promise<void>`): Close every server now, leased or not: the host is shutting down.
- `instances` (`(filter?: { cwd?: string }) => McpServerInstance[]`): Every pooled server, `cwd`'s only when given.
- `lease` (`(config: McpServerConfig, hooks: Hookable<AgentHooks, HookKeys<AgentHooks>> | undefined, open: (serverHooks: Hookable<AgentHooks>) => PooledServer, options?: McpServerLeaseOptions) => ServerHold`): Lease `config`'s server for one batch. `open` builds it on first use;
  the server's `mcp:warn` events (stderr lines, a dropped connection)
  reach `hooks` while that batch is the only one with a call in flight,
  the pool's logger otherwise. Used by `connectMcpServers`.
- `restart` (`(match: McpServerMatch) => Promise<number>`): End the matching processes now. Calls in flight fail with "<name> restarted"; the next call starts a fresh process. Resolves with how many were restarted.
- `retire` (`(match: McpServerMatch) => void`): The server changed: new leases get a fresh process, the current ones close once released.

Source: [src/mcp/server-pool.ts:104](https://github.com/Tahul/zidane/blob/main/src/mcp/server-pool.ts#L104)

### `McpServerPoolOptions`

Interface.

Options for [`createMcpServerPool`](https://zidane.si/docs/reference/mcp.md#createMcpServerPool).

```ts
interface McpServerPoolOptions
```

**Properties**

- `logger?` (`Logger`): Where a shared server's warnings go when no single batch's call owns them: the host's log. Dropped without one.
- `recordDir?` (`string`): Record each pooled server's pid in this directory (`./pid-record.ts`),
  and end on creation the ones a host that died left there.

Source: [src/mcp/server-pool.ts:123](https://github.com/Tahul/zidane/blob/main/src/mcp/server-pool.ts#L123)

### `McpToolSchema`

Interface.

Slim shape of an upstream MCP tool descriptor — what `client.listTools()`
returns per entry. Exposed publicly so hosts can persist the schemas
between runs and feed them back via `McpServerConfig.cachedTools`
to skip the `tools/list` round-trip on subsequent bootstraps.

```ts
interface McpToolSchema
```

**Properties**

- `description?` (`string | null`): Human-readable description of the tool.
- `inputSchema?` (`unknown`): JSON Schema of the tool's input.
- `name` (`string`): Tool name on the server, without the `mcp_<server>_` prefix.

Source: [src/types.ts:71](https://github.com/Tahul/zidane/blob/main/src/types.ts#L71)

## Types

### `PinnedFetch`

Type.

The fetch shape this wrapper supports — deliberately narrower than
`typeof fetch`, matching the MCP SDK's `FetchLike`.

A `Request` input is NOT accepted: the wrapper rebuilds each hop from
`init`, so it would silently drop a Request's method, headers and body and
issue a bare credential-less GET. Narrowing the type makes that shape
unrepresentable rather than a quiet failure on a security-relevant helper.
Both SDK transports only ever pass a URL plus init.

```ts
type PinnedFetch = (url: string | URL, init?: RequestInit) => Promise<Response>
```

Source: [src/mcp/origin-pinned-fetch.ts:96](https://github.com/Tahul/zidane/blob/main/src/mcp/origin-pinned-fetch.ts#L96)

## Re-exports

These names are documented on the entry point that owns them.

- [zidane](https://zidane.si/docs/reference/index.md): [`McpOAuthClientConfig`](https://zidane.si/docs/reference/index.md#McpOAuthClientConfig)
- [zidane/types](https://zidane.si/docs/reference/types.md): [`McpServerConfig`](https://zidane.si/docs/reference/types.md#McpServerConfig)
