If you build Python tools for AI agents, you've probably written the same function multiple times, exposing it as MCP for agents that need MCP, as REST for web APIs and as a CLI for local use. I didn't love that duplication, so I built toolaccess. > [!NOTE] Get the Package > Install from PyPI: [toolaccess](https://pypi.org/project/toolaccess/) | Source on [GitHub](https://github.com/whogben/toolaccess) ## The Problem AI tool servers need to speak multiple protocols. Your web app wants REST. Your AI agent wants MCP. You want a CLI for local testing. The underlying logic is identical, but each protocol demands its own boilerplate: route decorators, tool registrations, command definitions. Every time you change a function signature, you update it in three places. Every new tool means three new files. It's tedious and it drifts. ## How It Works toolaccess has four core pieces: - **ToolDefinition** wraps a callable with metadata (name, description, HTTP method). Pass a bare function and it builds one automatically from the function name and docstring. - **ToolService** groups related tools under a name. Mount the same service onto multiple servers to keep them in sync. - **Servers** each know how to expose a ToolService in their protocol: `OpenAPIServer` (REST via FastAPI), `SSEMCPServer` (MCP via FastMCP), and `CLIServer` (CLI via Typer). - **ServerManager** hosts everything. It owns the FastAPI app, the Typer CLI, and a dynamic ASGI dispatcher that routes requests by path prefix. Here's a complete working example: ```python from toolaccess import ( ServerManager, ToolService, OpenAPIServer, SSEMCPServer, CLIServer, ) def add(a: int, b: int) -> int: """Add two numbers.""" return a + b async def greet(name: str) -> str: """Return a greeting.""" return f"Hello, {name}!" service = ToolService("math", [add, greet]) rest = OpenAPIServer(path_prefix="/api", title="Math API") rest.mount(service) mcp = SSEMCPServer("math") mcp.mount(service) cli = CLIServer("math") cli.mount(service) manager = ServerManager(name="my-tools") manager.add_server(rest) manager.add_server(mcp) manager.add_server(cli) manager.run() ``` That single file gives you all of this: | Interface | Access | |---|---| | REST API | `POST /api/add`, `POST /api/greet` | | OpenAPI docs | `GET /api/docs` | | MCP (SSE) | `http://localhost:8000/mcp/math/sse` | | MCP (stdio) | `python app.py mcp-run --name math` | | CLI | `python app.py math add 1 2` | | Health check | `GET /health` | ## Scaling Up The architecture supports more than a single flat list of tools. You can create separate servers for different audiences and mount different services onto each: ```python public_api = OpenAPIServer("/public", "Public API") public_api.mount(public_service) admin_api = OpenAPIServer("/admin", "Admin API") admin_api.mount(admin_service) ``` Servers can be added and removed at runtime, so you can build systems that reconfigure themselves. Lifespan support lets you wire in setup and teardown logic (database connections, model loading) that runs for both the HTTP server and CLI execution. And if you have an existing FastAPI app, you can mount it alongside your tool servers with `MountableApp`. ## Get Started ```bash pip install toolaccess ``` The full docs and source are on [GitHub](https://github.com/whogben/toolaccess). Requires Python 3.10+.