@civic/passthrough-mcp-server v0.2.1
Passthrough MCP Server
A Model Context Protocol (MCP) server that passes all requests through to another MCP server with support for tRPC-based hook middleware for validating and modifying tool calls.
Features
- Acts as a proxy server between MCP clients and another MCP server
- Configurable to connect to different backend MCP servers
- Supports various transport methods for MCP (HTTP SSE, HTTP Stream, Stdio)
- tRPC-based hook system for request/response interception and modification
- Comprehensive test coverage with modular, testable architecture
Usage
Installation
pnpm install
Build
pnpm build
Test
# Run tests
pnpm test
# Run tests with coverage
pnpm test:coverage
Run
# Start the server with default HTTP Stream transport
pnpm start
# Start with stdio transport
pnpm start:stdio
# Development mode with auto-reload
pnpm dev
Configuration
The server can be configured through environment variables:
PORT
: HTTP port to listen on (default: 34000)TARGET_SERVER_URL
: URL of the target MCP server to connect toTARGET_SERVER_TRANSPORT
: Transport type for connecting to the target server (httpStream, sse)HOOKS
: Comma-separated list of tRPC hook server URLs for middleware processing
Hook Middleware
You can specify multiple tRPC hook servers as middleware to process tool calls before they reach the target server:
# Single hook
HOOKS=http://localhost:33004 pnpm start
# Multiple hooks
HOOKS=http://localhost:33004,http://localhost:33005 pnpm start
Hook servers are processed in sequence, forming a middleware chain:
- Requests: Processed in order (first to last)
- Responses: Processed in reverse order (last to first)
Each hook can: 1. Allow the tool call to proceed (potentially with modifications) 2. Reject the tool call, preventing it from reaching the target server
This is useful for implementing validation, security checks, audit logging, or transformations.
Programmatic Usage
The passthrough MCP server can be used programmatically in your Node.js applications, allowing you to embed a passthrough proxy within your own systems.
Installation for Library Use
npm install @civic/passthrough-mcp-server
Basic Programmatic Usage
import { createPassthroughProxy } from '@civic/passthrough-mcp-server';
// Create and start the proxy
const proxy = await createPassthroughProxy({
transportType: "httpStream",
port: 34000,
target: {
url: "http://localhost:33000",
transportType: "httpStream"
},
serverInfo: {
name: "my-passthrough-server",
version: "1.0.0"
}
});
// Later, stop the proxy
await proxy.stop();
Advanced Programmatic Features
Manual Start
const proxy = await createPassthroughProxy({
transportType: "httpStream",
port: 34000,
target: {
url: "http://localhost:33000",
transportType: "httpStream"
},
autoStart: false
});
// Perform additional setup...
// Start when ready
await proxy.start();
Custom Client Factory
You can provide a custom client factory to control how connections to the target server are created:
import type { ClientFactory, PassthroughClient } from '@civic/passthrough-mcp-server';
const customClientFactory: ClientFactory = // your factory implementation here;
const proxy = await createPassthroughProxy({
transportType: "httpStream",
port: 34000,
target: {
url: "http://localhost:33000",
transportType: "httpStream"
},
clientFactory: customClientFactory
});
With Hooks in Code
Configure hooks programmatically:
const proxy = await createPassthroughProxy({
transportType: "httpStream",
port: 34000,
target: {
url: "http://localhost:33000",
transportType: "httpStream"
},
hooks: [
{
url: "http://localhost:8080/trpc",
name: "audit-hook"
},
{
url: "http://localhost:8081/trpc",
name: "security-hook"
}
]
});
API Reference
createPassthroughProxy(options)
Creates and optionally starts a passthrough MCP proxy server.
Parameters:
options.transportType
(required): Transport type for the server ("httpStream", "sse", "stdio")options.port
(required for non-stdio transports): Port number for the serveroptions.target
(required): Target server configurationurl
: URL of the target MCP servertransportType
: Transport type ("httpStream", "sse")
options.serverInfo
(optional): Server metadataname
: Server nameversion
: Server version
options.clientInfo
(optional): Client metadataoptions.hooks
(optional): Array of hook configurationsurl
: Hook endpoint URLname
: Hook name
options.clientFactory
(optional): Custom factory for creating target clientsoptions.autoStart
(optional): Whether to start the server immediately (default: true)
Returns:
A PassthroughProxy
object with:
server
: The underlying FastMCP server instancestart()
: Method to start the server (if not auto-started)stop()
: Method to stop the server
Loading Configuration from Environment
When using programmatically, you can still leverage environment variables:
import { loadConfig, createPassthroughProxy } from '@civic/passthrough-mcp-server';
// Load configuration from environment
const config = loadConfig();
// Create proxy with environment-based config
const proxy = await createPassthroughProxy({
...config
});
Type Exports
The package exports several TypeScript types for better type safety:
import type {
ClientConfig,
ClientFactory,
PassthroughClient,
PassthroughProxy,
PassthroughProxyOptions
} from '@civic/passthrough-mcp-server';
Hook API
The passthrough server provides a comprehensive API for applying hooks to requests and responses, making it easy to integrate hook functionality into other services like the MCP Hub.
Key Features
- Unified
applyHooks
function: Single entry point for processing both request and response hooks - Hook creation utilities:
createHookClient
andcreateHookClients
for easy hook instantiation - Type exports: All necessary types from
@civic/hook-common
are re-exported for convenience - AbstractHook base class: Simplifies creating custom local hooks
Hook-Related Exports
applyHooks
- Main function for applying hooks to datacreateHookClient
,createHookClients
- Utilities for creating hook instancesAbstractHook
- Base class for implementing custom hooks- Types:
Hook
,HookClient
,HookResponse
,ToolCall
,HookType
,HookResult
,HookContext
Examples
For a complete example of using the hook API, see:
examples/hook-api-example.ts
- Hook API usage patterns and implementation
See the examples/
directory for additional working examples of programmatic usage.
Implementation
This server uses:
- fastMCP: For the MCP server implementation
- @modelcontextprotocol/sdk: For the MCP client to connect to target servers
- tRPC: For communication with hook servers
Architecture
The codebase is organized into small, focused modules with single responsibilities:
- hooks/manager.ts - Manages hook client instances and caching
- hooks/processor.ts - Processes tool calls through hook chains
- server/passthrough.ts - Main passthrough handler orchestration
- utils/session.ts - Session management and client connections
- utils/config.ts - Configuration parsing and validation
Each module has accompanying unit tests located alongside the source files for easy maintenance and testing.
Creating Custom Hooks
To create a custom tRPC hook:
- Install
hook-common
as a dependency - Implement the
Hook
interface fromhook-common/types
- Create a tRPC server using
createHTTPServer
andcreateHookRouter
- Start the server on a specific port
- Add the hook URL to the
HOOKS
environment variable
See the audit-hook and guardrail-hook packages for examples.
Example Setup
# Terminal 1: Start a target MCP server (e.g., sample-mcp-server)
cd ../sample-mcp-server
pnpm start
# Terminal 2: Start hook servers
cd ../audit-hook
pnpm start # Port 33004
# Terminal 3: Start another hook
cd ../guardrail-hook
pnpm start # Port 33005
# Terminal 4: Start passthrough with hooks
cd ../passthrough-mcp-server
export TARGET_SERVER_URL=http://localhost:3000
export HOOKS=http://localhost:33004,http://localhost:33005
pnpm start
Now clients can connect to the passthrough server on port 34000, and all requests will be: 1. Logged by the audit hook 2. Validated by the guardrail hook 3. Forwarded to the target server