Skip to main content

Easy MCP Proxy - Filter, transform, and compose MCP tools

Project description

Easy MCP Proxy

An MCP proxy server that aggregates tools from multiple upstream MCP servers and exposes them through tool views — filtered, transformed, and composed subsets of tools.

Features

  • Tool aggregation: Connect to multiple upstream MCP servers (stdio or HTTP)
  • Tool filtering: Expose only specific tools from each server
  • Tool renaming: Expose tools under different names
  • Parameter binding: Hide, rename, or set defaults for tool parameters
  • Description overrides: Customize tool descriptions with {original} placeholder
  • Tool views: Named configurations exposing different tool subsets
  • Parallel composition: Fan-out tools that call multiple upstream tools concurrently
  • Custom tools: Python-defined tools with full access to upstream servers
  • Pre/post hooks: Intercept and modify tool calls and results
  • Multi-transport: Serve via stdio or HTTP with view-based routing
  • CLI management: Add servers, create views, and manage configuration

Installation

# Install from source
uv pip install -e .

# With dev dependencies
uv pip install -e ".[dev]"

Quick Start

1. Create a configuration file

# config.yaml
mcp_servers:
  memory:
    command: uv
    args: [tool, run, --from, agent-memory-server, agent-memory, mcp]
    env:
      REDIS_URL: redis://localhost:6379

tool_views:
  assistant:
    description: "Memory tools for AI assistant"
    tools:
      memory:
        search_long_term_memory: {}
        create_long_term_memories: {}

2. Start the proxy

# Stdio transport (for MCP clients)
mcp-proxy serve --config config.yaml

# HTTP transport (for web clients)
mcp-proxy serve --config config.yaml --transport http --port 8000

3. Use with Claude Desktop

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "proxy": {
    
      command: uv
      "args": ["run", "--directory", "/path/to/repository/checkout", "serve", "--config", "/path/to/config.yaml"]
    }
  }
}

NOTE: If you don't pass -c, you will use the default config file at ~/.config/mcp-proxy/config.yaml.

Configuration

Upstream Servers

Define MCP servers to connect to:

mcp_servers:
  # Stdio-based server (local command)
  local-server:
    command: python
    args: [-m, my_mcp_server]
    env:
      API_KEY: ${MY_API_KEY}  # Environment variable expansion

  # Stdio server with working directory
  # (useful for servers that resolve relative paths against CWD)
  filesystem:
    command: npx
    args: [-y, "@modelcontextprotocol/server-filesystem", /data/files]
    cwd: /data/files  # Relative paths in tool calls resolve against this

  # HTTP-based server (remote)
  zapier:
    url: "https://actions.zapier.com/mcp/YOUR_MCP_ID/sse"
    headers:
      Authorization: "Bearer ${ZAPIER_MCP_API_KEY}"

Tool Filtering

Filter tools at the server level:

mcp_servers:
  github:
    command: npx
    args: [-y, "@github/mcp-server"]
    env:
      GITHUB_PERSONAL_ACCESS_TOKEN: ${GITHUB_TOKEN}
    tools:
      search_code: {}
      search_issues: {}
      # Other tools from this server are not exposed

Description Overrides

Customize tool descriptions:

mcp_servers:
  memory:
    command: agent-memory
    tools:
      search_long_term_memory:
        description: |
          Search saved memories about past incidents.

          {original}

The {original} placeholder is replaced with the upstream tool's description.

Tool Renaming

Expose tools under different names:

mcp_servers:
  filesystem:
    command: npx
    args: [-y, "@modelcontextprotocol/server-filesystem", /data]
    tools:
      read_file:
        name: read_document  # Expose as 'read_document' instead of 'read_file'
      list_directory:
        name: list_folders
        description: "List folders in the data directory"

Parameter Binding

Hide, rename, or set defaults for tool parameters. This is useful when wrapping generic tools (like filesystem operations) to create domain-specific interfaces:

mcp_servers:
  skills-server:
    command: npx
    args: [-y, "@modelcontextprotocol/server-filesystem", /home/user/skills]
    tools:
      directory_tree:
        name: get_skills_structure
        description: "Get the structure of the skills library"
        parameters:
          path:
            hidden: true      # Remove from exposed schema
            default: "."      # Always use root directory

      list_directory:
        name: list_skill_categories
        parameters:
          path:
            rename: category  # Expose as 'category' instead of 'path'
            default: "."      # Optional with default
            description: "Category folder to list (e.g., 'deployment')"

      read_file:
        name: read_skill
        parameters:
          path:
            rename: skill_path
            description: "Path to skill file (e.g., 'deployment/kubernetes.md')"

Parameter binding options:

  • hidden: true - Remove parameter from exposed schema
  • default: value - Default value (makes parameter optional, injected at call time)
  • rename: new_name - Expose under a different name
  • description: text - Override parameter description

Tool Views

Create named views exposing different tool subsets:

tool_views:
  # Direct mode: tools exposed with their names
  search-tools:
    description: "Read-only search tools"
    exposure_mode: direct
    tools:
      github:
        search_code: {}
        search_issues: {}

  # Search mode: exposes search_tools + call_tool meta-tools
  all-github:
    description: "All GitHub tools via search"
    exposure_mode: search
    include_all: true

Parallel Composition

Create tools that call multiple upstream tools concurrently:

tool_views:
  unified:
    composite_tools:
      search_everywhere:
        description: "Search all sources in parallel"
        inputs:
          query: { type: string, required: true }
        parallel:
          code:
            tool: github.search_code
            args: { query: "{inputs.query}" }
          memory:
            tool: memory.search_long_term_memory
            args: { text: "{inputs.query}" }

Hooks

Attach pre/post call hooks to views:

tool_views:
  monitored:
    hooks:
      pre_call: myapp.hooks.validate_args
      post_call: myapp.hooks.log_result
    tools:
      server:
        some_tool: {}

Hook implementation:

# myapp/hooks.py
from mcp_proxy.hooks import HookResult, ToolCallContext

async def validate_args(args: dict, context: ToolCallContext) -> HookResult:
    # Modify args or abort
    return HookResult(args=args)

async def log_result(result, args: dict, context: ToolCallContext) -> HookResult:
    print(f"Tool {context.tool_name} returned: {result}")
    return HookResult(result=result)

Custom Tools

Define Python tools with upstream access:

# myapp/tools.py
from mcp_proxy.custom_tools import custom_tool, ProxyContext

@custom_tool(
    name="smart_search",
    description="Search with context enrichment"
)
async def smart_search(query: str, ctx: ProxyContext) -> dict:
    # Call upstream tools
    memory = await ctx.call_tool("memory.search_long_term_memory", text=query)
    code = await ctx.call_tool("github.search_code", query=query)
    return {"memory": memory, "code": code}

Register in config:

tool_views:
  smart:
    custom_tools:
      - module: myapp.tools.smart_search

CLI Reference

Server Management

# List configured servers
mcp-proxy server list
mcp-proxy server list --verbose  # Show details
mcp-proxy server list --json     # JSON output

# Add a stdio server
mcp-proxy server add myserver --command python --args "-m,mymodule"

# Add an HTTP server
mcp-proxy server add remote --url "https://api.example.com/mcp/"

# Add with environment variables
mcp-proxy server add myserver --command myapp --env "API_KEY=xxx" --env "DEBUG=1"

# Remove a server
mcp-proxy server remove myserver
mcp-proxy server remove myserver --force  # Remove even if referenced by views

Tool Configuration

# Set tool allowlist for a server (comma-separated)
mcp-proxy server set-tools myserver "tool1,tool2,tool3"

# Clear tool filter (expose all tools)
mcp-proxy server clear-tools myserver

# Rename a tool
mcp-proxy server rename-tool myserver original_name new_name

# Set custom tool description
mcp-proxy server set-tool-description myserver mytool "Custom description. {original}"

# Configure parameter binding
mcp-proxy server set-tool-param myserver mytool path --hidden --default "."
mcp-proxy server set-tool-param myserver mytool path --rename folder
mcp-proxy server set-tool-param myserver mytool query --description "Search query"
mcp-proxy server set-tool-param myserver mytool path --clear  # Remove config

View Management

# List views
mcp-proxy view list
mcp-proxy view list --verbose
mcp-proxy view list --json

# Create a view
mcp-proxy view create myview --description "My tools"
mcp-proxy view create searchview --exposure-mode search

# Delete a view
mcp-proxy view delete myview

# Add/remove servers from views
mcp-proxy view add-server myview myserver --tools "tool1,tool2"
mcp-proxy view add-server myview myserver --all  # Include all tools
mcp-proxy view remove-server myview myserver

# Configure tools within a view
mcp-proxy view set-tools myview myserver "tool1,tool2,tool3"
mcp-proxy view clear-tools myview myserver
mcp-proxy view rename-tool myview myserver original_name new_name
mcp-proxy view set-tool-description myview myserver mytool "Description"
mcp-proxy view set-tool-param myview myserver mytool path --hidden --default "."

Inspection and Debugging

# Show tool schemas from upstream servers
mcp-proxy schema
mcp-proxy schema myserver.mytool
mcp-proxy schema --server myserver
mcp-proxy schema --server myserver --json

# Validate configuration
mcp-proxy validate
mcp-proxy validate --check-connections  # Test upstream connectivity

# Call a tool directly (for testing)
mcp-proxy call myserver.mytool --arg key=value --arg count=10

# Show configuration
mcp-proxy config
mcp-proxy config --resolved  # With environment variables expanded

# Generate example files
mcp-proxy init config  # Example config.yaml
mcp-proxy init hooks   # Example hooks.py

Running the Proxy

# Stdio transport (for MCP clients like Claude Desktop)
mcp-proxy serve
mcp-proxy serve --config /path/to/config.yaml

# HTTP transport (for web clients)
mcp-proxy serve --transport http --port 8000

# Load environment from .env file
mcp-proxy serve --env-file .env

HTTP Endpoints

When running with --transport http, the proxy exposes:

Endpoint Description
/mcp Default MCP endpoint (all server tools)
/view/{name}/mcp View-specific MCP endpoint
/views List all available views
/views/{name} Get view details
/health Health check

Example requests:

# List views
curl http://localhost:8000/views

# Get view info
curl http://localhost:8000/views/assistant

# Health check
curl http://localhost:8000/health

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        MCP Proxy Server                         │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                      Tool Views                            │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐    │  │
│  │  │  assistant  │  │   search    │  │   all-tools     │    │  │
│  │  │  - memory   │  │ - search_*  │  │ - include_all   │    │  │
│  │  └──────┬──────┘  └──────┬──────┘  └────────┬────────┘    │  │
│  └─────────┼────────────────┼──────────────────┼─────────────┘  │
│            │                │                  │                 │
│  ┌─────────▼────────────────▼──────────────────▼─────────────┐  │
│  │                    Hook System                             │  │
│  │  pre_call(args, ctx) → modified_args                       │  │
│  │  post_call(result, args, ctx) → modified_result            │  │
│  └─────────┬────────────────┬──────────────────┬─────────────┘  │
│            │                │                  │                 │
│  ┌─────────▼────────────────▼──────────────────▼─────────────┐  │
│  │                 Upstream MCP Clients                       │  │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │  │
│  │  │   memory     │  │   github     │  │    zapier    │     │  │
│  │  │   (stdio)    │  │   (stdio)    │  │   (http)     │     │  │
│  │  └──────────────┘  └──────────────┘  └──────────────┘     │  │
│  └───────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

Development

# Install dev dependencies
uv pip install -e ".[dev]"

# Run tests
pytest

# Run linting
ruff check .

# Format code
ruff format .

License

Copyright (C) 2025 Andrew Brookins

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

See LICENSE for the full license text.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

easy_mcp_proxy-0.1.0.tar.gz (208.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

easy_mcp_proxy-0.1.0-py3-none-any.whl (49.1 kB view details)

Uploaded Python 3

File details

Details for the file easy_mcp_proxy-0.1.0.tar.gz.

File metadata

  • Download URL: easy_mcp_proxy-0.1.0.tar.gz
  • Upload date:
  • Size: 208.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for easy_mcp_proxy-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2f76a5e355bfad6b77cf1101f0fe5ba8313676b01ca8dc75ed3f599efcdeb1fc
MD5 f6d00c4f404a8dc319058855fb546e09
BLAKE2b-256 58d526e4be1c60bcbb4a7aec6590cae7ff88ed604a78fc6353201c9e199d1ef1

See more details on using hashes here.

Provenance

The following attestation bundles were made for easy_mcp_proxy-0.1.0.tar.gz:

Publisher: pypi.yml on abrookins/easy-mcp-proxy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file easy_mcp_proxy-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: easy_mcp_proxy-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 49.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for easy_mcp_proxy-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e86477aa0a8706b44b0c81874d711787c42dac7375df8779700d4e61aaff939b
MD5 cf5fa99da62cd2d365d91191d9996d1d
BLAKE2b-256 3abc0068a0db57cf5963c9757d9d3617a1186f210593421284b1bc0b3dc12265

See more details on using hashes here.

Provenance

The following attestation bundles were made for easy_mcp_proxy-0.1.0-py3-none-any.whl:

Publisher: pypi.yml on abrookins/easy-mcp-proxy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page