npm package: @0xkobold/pi-mcp
Description: Model Context Protocol (MCP) integration for pi-coding-agent. Connect to any MCP server (stdio, SSE, StreamableHTTP, WebSocket) and use its tools, resources, and prompts natively.
Author: 0xKobold
Latest version: 0.4.0

# @0xkobold/pi-mcp

> Model Context Protocol (MCP) integration for [pi-coding-agent](https://github.com/nicholasgasior/pi-coding-agent)

Connect to any MCP server and use its tools, resources, and prompts natively within your pi agent.

## Features

- 🌐 **Four Transport Types** - stdio, SSE, StreamableHTTP, and WebSocket
- 🔧 **Auto Tool Registration** - MCP tools appear as native pi tools (with progressive dispatch for 50+ tool servers)
- 🔒 **Tool Filtering** - Allowlist/denylist to control which tools are registered
- 📦 **Resource Access** - Read MCP server resources directly (with TTL caching)
- 💬 **Prompt Templates** - Use MCP prompt templates
- 🌱 **Roots Support** - Servers can discover workspace roots
- 🔄 **Auto-Reconnect** - Reconnects on disconnect with exponential backoff
- 🔐 **Env Interpolation** - `${VAR}` in config resolved from environment
- 📥 **Claude Desktop Import** - Import servers from `~/.claude/mcp.json`
- 💊 **Health Monitoring** - Uptime, call counts, error tracking in `/mcp status`
- 🧪 **Sampling Support** - Handle MCP `sampling/createMessage` requests via configurable callback
- 🗂️ **Multi-Project Config** - `.0xkobold/mcp.json` merges with global config
- ⚙️ **Hot Config** - Add/remove servers without restart (commands)

## Quick Start

### 1. Configure servers

Edit `~/.0xkobold/mcp.json`:

```json
{
  "servers": [
    {
      "name": "filesystem",
      "transport": {
        "type": "stdio",
        "command": "npx",
        "args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/user"]
      },
      "enabled": true,
      "autoReconnect": true
    }
  ],
  "importClaudeDesktop": true
}
```

### 2. Or use commands

```
/mcp add filesystem npx -y @modelcontextprotocol/server-filesystem /home/user
/mcp enable filesystem
/mcp connect filesystem
```

### 3. Use tools

The agent will automatically discover and use MCP tools. You can also:

```
/mcp list          # See all configured servers
/mcp status        # See active connections
/mcp discover      # Find available tools
```

## Commands

| Command | Description |
|---------|-------------|
| `/mcp` | Show help |
| `/mcp list` | List all configured servers |
| `/mcp connect <name>` | Connect to a server |
| `/mcp disconnect <name>` | Disconnect from a server |
| `/mcp enable <name>` | Enable auto-connect for a server |
| `/mcp disable <name>` | Disable auto-connect for a server |
| `/mcp add <name> <cmd> [args]` | Add a stdio server |
| `/mcp add-http <name> <url>` | Add an HTTP server |
| `/mcp add-ws <name> <url>` | Add a WebSocket server |
| `/mcp filter <name> allow <tools>` | Only register listed tools for server |
| `/mcp filter <name> deny <tools>` | Register all except listed tools |
| `/mcp filter <name> clear` | Remove tool filters for server |
| `/mcp remove <name>` | Remove a server from config |
| `/mcp refresh <name>` | Re-discover tools/resources/prompts |
| `/mcp import` | Import servers from Claude Desktop config |
| `/mcp status` | Show active connections |

## Tools

| Tool | Description |
|------|-------------|
| `mcp_discover` | List available MCP tools, resources, and prompts |
| `mcp_call_tool` | Call any MCP tool by server + tool name |
| `mcp_<server>_<tool>` | Individual tools (auto-registered per server) |
| `mcp_<server>_read_resource` | Read a resource from an MCP server |
| `mcp_<server>_get_prompt` | Get a prompt from an MCP server |

## Transport Types

### stdio
Spawns a local process and communicates via stdin/stdout:
```json
{
  "type": "stdio",
  "command": "npx",
  "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"],
  "env": { "API_KEY": "..." },
  "cwd": "/working/dir"
}
```

### StreamableHTTP
Modern HTTP transport (recommended for remote servers):
```json
{
  "type": "streamable-http",
  "url": "https://example.com/mcp",
  "headers": { "Authorization": "Bearer ..." }
}
```

### SSE (legacy)
Server-Sent Events transport:
```json
{
  "type": "sse",
  "url": "https://example.com/sse"
}
```

### WebSocket
Real-time bidirectional transport:
```json
{
  "type": "websocket",
  "url": "ws://localhost:8080/mcp"
}
```

## Tool Filtering

Control which tools are registered per server using allowlist or denylist:

```json
// Only register specific tools (allowlist)
{
  "name": "filesystem",
  "allowedTools": ["read_file", "list_directory"],
  ...
}

// Register all except (denylist)
{
  "name": "github",
  "deniedTools": ["delete_repository", "create_issue"],
  ...
}
```

Or via commands:
```
/mcp filter github allow search_repositories,get_file_contents
/mcp filter filesystem deny write_file,delete_file
/mcp filter github clear
```

## Environment Variable Interpolation

Use `${ENV_VAR}` patterns in config values to avoid hardcoding secrets:

```json
{
  "name": "github",
  "transport": {
    "type": "stdio",
    "command": "npx",
    "args": ["-y", "@modelcontextprotocol/server-github"],
    "env": {
      "GITHUB_TOKEN": "${GITHUB_TOKEN}"
    }
  }
}
```

HTTP headers also support interpolation:
```json
{
  "transport": {
    "type": "streamable-http",
    "url": "https://api.example.com/mcp",
    "headers": {
      "Authorization": "Bearer ${API_TOKEN}"
    }
  }
}
```

## As a Library

You can use `@0xkobold/pi-mcp` as a standalone library, without pi-coding-agent:

```typescript
// Main entry — extension + all re-exports
import mcpExtension, {
  MCPConnectionManager,
  ResourceCache,
  interpolateEnv,
  loadConfig,
  saveConfig,
  createDefaultConfig,
  isToolAllowed,
} from "@0xkobold/pi-mcp";

// Client subpath — connection management only
import { MCPConnectionManager, type MCPServerConfig } from "@0xkobold/pi-mcp/client";

// Config subpath — config loading/saving only
import { loadConfig, upsertServer, type MCPConfig } from "@0xkobold/pi-mcp/config";

// Tools subpath — tool registration utilities only
import { isToolAllowed, DEFAULT_MAX_TOOLS } from "@0xkobold/pi-mcp/tools";
```

Quick start without pi-coding-agent:

```typescript
import { MCPConnectionManager } from "@0xkobold/pi-mcp/client";

const manager = new MCPConnectionManager([process.cwd()]);

const serverConfig = {
  name: "filesystem",
  transport: { type: "stdio", command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"] },
  enabled: true,
  autoReconnect: true,
};

const info = await manager.connect(serverConfig);
console.log(`Connected: ${info.tools.length} tools`);

const result = await manager.callTool("filesystem", "read_file", { path: "/tmp/hello.txt" });
console.log(result);
```

## Architecture

```
src/
├── index.ts          # Extension entry point (commands, tools, lifecycle)
├── client/
│   └── index.ts      # MCPConnectionManager, ResourceCache, env interpolation
├── config/
│   └── index.ts      # Config loading/saving, Claude Desktop import, project merge
└── tools/
    └── index.ts      # Tool bridge - MCP tools → pi tools (with filtering)
```

## Configuration

**Config file**: `~/.0xkobold/mcp.json`

**Full schema**:
```json
{
  "servers": [
    {
      "name": "string (required)",
      "transport": {
        "type": "stdio | sse | streamable-http | websocket",
        // stdio fields:
        "command": "string",
        "args": ["string"],
        "env": {},
        "cwd": "string",
        // http/sse fields:
        "url": "string",
        "headers": {},
        "sessionId": "string",
        // websocket fields:
        "url": "ws://..."
      },
      "enabled": false,
      "autoReconnect": true,
      "maxReconnectAttempts": 5,
      "reconnectDelayMs": 1000,
      "connectTimeoutMs": 30000,
      "allowedTools": ["tool_name"],
      "deniedTools": ["tool_name"],
      "maxTools": 50
    }
  ],
  "importClaudeDesktop": true
}
```

## Claude Desktop Compatibility

Set `"importClaudeDesktop": true` to automatically discover servers from:
- `~/.claude/mcp.json`
- `~/.config/claude-code/mcp.json`

Or run `/mcp import` to manually import.

## Development

```bash
# Install dependencies
bun install

# Build
bun run build

# Development watch
bun run dev

# Test
bun test    # 95 tests, 182 assertions
bun test test/unit.test.ts  # Unit tests only
bun test test/integration.test.ts  # Integration tests (requires npx)
```

## Sampling Support

MCP servers can request LLM sampling via `sampling/createMessage`. The extension registers a handler for this capability.

- **Default handler**: Logs the request and returns a placeholder response (pi extensions don't have direct LLM completion access)
- **Custom handler**: Pass a `SamplingHandler` function to `MCPConnectionManager` constructor:

```typescript
import { MCPConnectionManager, type SamplingHandler } from "@0xkobold/pi-mcp/client";

const handler: SamplingHandler = async (params) => {
  // Bridge to your LLM here
  return {
    role: "assistant",
    content: { type: "text", text: "LLM response" },
    model: "your-model",
    stopReason: "endTurn",
  };
};

const manager = new MCPConnectionManager(["/workspace"], 50, 300_000, handler);
```

## License

MIT