One Server. Every Protocol. All Your Tools.
Multi-tenant MCP server with namespace-based routing, exposing Python tools via OpenAPI, MCP, and Web GUI.
Disclaimer: ToolDock is still under active development and best treated as a demo/preview.
Use at your own risk, especially in production environments.
# Clone and start
git clone https://github.com/ponmeloco/ToolDock.git
cd ToolDock
./start.sh
# Force rebuild images
./start.sh --rebuildOr manually:
cp .env.example .env
nano .env # Set BEARER_TOKEN
docker compose up -dVerify:
curl http://localhost:18006/health # OpenAPI
curl http://localhost:18007/health # MCP
curl http://localhost:13000 # Admin UIOn first start, ToolDock installs and auto-starts a demo FastMCP server
from the modelcontextprotocol/servers repo (time server). You can disable or change it
via FASTMCP_DEMO_* in .env.
Screenshots (from docs/images/):
If ./start.sh --rebuild fails with:
failed to update builder last activity time: ... permission denied
It means Docker Buildx created files in ~/.docker as root (often from a past sudo docker ... run).
Fix by resetting ownership, then retry:
sudo chown -R $USER:$USER ~/.docker
./start.sh --rebuildThis only fixes Docker’s local metadata directory; it does not affect your ToolDock data in tooldock_data/.
| Feature | Description |
|---|---|
| Admin UI | React dashboard with code editor, playground, and logs |
| Multi-Tenant | Each folder becomes a separate MCP endpoint |
| Dual Transport | OpenAPI + MCP from the same codebase |
| Hot Reload | Reload tools without server restart |
| Playground | Test tools via OpenAPI or MCP (real servers) |
| Persistent Logs | Daily JSON log files with auto-cleanup |
| Metrics | Error rates + tool call counts via SQLite + in-memory queue |
| FastMCP Servers | Install from MCP Registry or add manual servers |
| Config Editor | Edit server configs with YAML/JSON syntax highlighting |
| Unified Namespaces | View all namespace types (native, fastmcp, external) |
┌──────────────────┐ ┌─────────────────────────────────────┐
│ Admin UI │ │ ToolDock Backend │
│ (React) │ ├─────────────────────────────────────┤
│ Port 13000 │────→│ Port 18006 → OpenAPI/REST │
│ │ │ Port 18007 → MCP HTTP │
└──────────────────┘ │ Port 18080 → Backend API │
├─────────────────────────────────────┤
LiteLLM ────────────────→│ /mcp/shared → shared/ tools │
Claude Desktop ─────────→│ /mcp/team1 → team1/ tools │
└─────────────────────────────────────┘
# Required
BEARER_TOKEN=your_secure_token_here
# Optional - Ports
OPENAPI_PORT=18006
MCP_PORT=18007
WEB_PORT=18080
ADMIN_PORT=13000
# Optional - Logging
LOG_RETENTION_DAYS=30 # Auto-delete logs after N days
METRICS_RETENTION_DAYS=30 # SQLite metrics retention window
# Optional - MCP Protocol (strict mode)
MCP_PROTOCOL_VERSION=2025-11-25
MCP_PROTOCOL_VERSIONS=2025-11-25,2025-03-26
# Optional - Host display (Admin UI)
HOST_DATA_DIR=./tooldock_data
# Optional - External MCP
GITHUB_TOKEN=ghp_xxxxxxxxxxxxEach folder in tooldock_data/tools/ becomes a separate endpoint:
| Folder | MCP Endpoint |
|---|---|
tools/shared/ |
/mcp/shared |
tools/team1/ |
/mcp/team1 |
tools/finance/ |
/mcp/finance |
Install FastMCP servers from the MCP Registry and expose them via namespaces. Use the FastMCP tab in the Admin UI to search, install, start, stop, and delete servers.
Two installation methods:
-
From Registry - Search the MCP Registry for PyPI/npm packages:
- Search by name in the Admin UI
- Select a server and choose a namespace
- Click Install to download and configure
-
Manual Server - Add servers using Claude Desktop config format:
- Specify command (e.g.,
python,node,npx) - Add arguments (e.g.,
-m my_module --config config.yaml) - Set environment variables (e.g.,
API_KEY=xxx)
- Specify command (e.g.,
Server Management:
Click on any installed server to open the detail panel:
- View server status, PID, and port
- Edit start command (command, args, env)
- Edit config files with syntax highlighting (YAML/JSON)
- Start, stop, or delete the server
Config files are stored in tooldock_data/external/servers/{namespace}/.
The default database is SQLite at /data/db/tooldock.db. You can switch to Postgres via:
DATABASE_URL=postgresql+psycopg://user:pass@host:5432/tooldock
Configure external servers in tooldock_data/external/config.yaml (or $DATA_DIR/external/config.yaml) and reload without restart:
curl -X POST http://localhost:18080/api/servers/reload \
-H "Authorization: Bearer change_me"These servers are exposed as namespaces like /mcp/github.
- Open http://localhost:13000
- Tools → select namespace → New Tool
- Edit the template → Save (valid code required; auto-reloads)
Create tooldock_data/tools/shared/my_tool.py:
from pydantic import BaseModel, Field, ConfigDict
from app.registry import ToolDefinition, ToolRegistry
class MyToolInput(BaseModel):
model_config = ConfigDict(extra="forbid")
query: str = Field(..., description="The query")
async def handler(payload: MyToolInput) -> dict:
return {"result": f"Processed: {payload.query}"}
def register_tools(registry: ToolRegistry) -> None:
MyToolInput.model_rebuild(force=True)
registry.register(ToolDefinition(
name="my_tool",
description="Processes a query",
input_model=MyToolInput,
handler=handler,
))Then reload:
curl -X POST http://localhost:18080/api/reload/shared \
-H "Authorization: Bearer change_me"Each namespace gets its own Python venv stored in tooldock_data/venvs/{namespace}.
Install dependencies via the Admin UI Tools → Dependencies, or via API:
curl -X POST http://localhost:18080/api/folders/shared/tools/deps/install \
-H "Authorization: Bearer change_me" \
-H "Content-Type: application/json" \
-d '{"packages": ["requests==2.32.0"]}'Create/Delete venv:
curl -X POST http://localhost:18080/api/folders/shared/tools/deps/create \
-H "Authorization: Bearer change_me"
curl -X POST http://localhost:18080/api/folders/shared/tools/deps/delete \
-H "Authorization: Bearer change_me"After install, ToolDock auto-reloads the namespace so imports work immediately.
Uninstall (pip/setuptools/wheel are protected):
curl -X POST http://localhost:18080/api/folders/shared/tools/deps/uninstall \
-H "Authorization: Bearer change_me" \
-H "Content-Type: application/json" \
-d '{"packages": ["requests"]}'mcp_servers:
- server_name: "tooldock"
# If LiteLLM runs in Docker on the same network, use the service name:
url: "http://tooldock-backend:8007/mcp/shared"
api_key_header: "Authorization"
api_key_value: "Bearer change_me"If LiteLLM runs on the host (not in Docker), use:
http://localhost:18007/mcp/shared
Add to ~/.config/Claude/claude_desktop_config.json:
{
"mcpServers": {
"tooldock": {
"url": "http://localhost:18007/mcp/shared",
"headers": {
"Authorization": "Bearer change_me"
}
}
}
}| Endpoint | Method | Description |
|---|---|---|
/health |
GET | Health check |
/tools |
GET | List tools |
/tools/{name} |
POST | Execute tool |
| Endpoint | Method | Description |
|---|---|---|
/health |
GET | Health check |
/mcp/namespaces |
GET | List namespaces |
/mcp |
POST | JSON-RPC endpoint (all namespaces) |
/mcp/{namespace} |
POST | JSON-RPC endpoint (namespace) |
/mcp/info |
GET | Non-standard discovery |
/mcp/{namespace}/info |
GET | Non-standard discovery |
| Endpoint | Method | Description |
|---|---|---|
/api/folders |
GET | List namespaces |
/api/folders/{ns}/tools |
GET/POST | List/upload tools |
/api/reload |
POST | Hot reload all |
/api/reload/{ns} |
POST | Hot reload namespace |
/api/admin/logs |
GET | View logs |
/api/admin/logs/files |
GET | List log files |
/api/admin/metrics |
GET | Metrics for dashboard (error rates + tool calls) |
/api/admin/namespaces |
GET | List all namespaces (native, fastmcp, external) |
/api/playground/tools |
GET | List tools for playground |
/api/playground/execute |
POST | Execute tool (OpenAPI/MCP via real servers) |
| Endpoint | Method | Description |
|---|---|---|
/api/fastmcp/registry/servers |
GET | Search MCP Registry |
/api/fastmcp/registry/health |
GET | Check registry connectivity |
/api/fastmcp/servers |
GET | List installed servers |
/api/fastmcp/servers |
POST | Install from registry |
/api/fastmcp/servers/manual |
POST | Add manual server |
/api/fastmcp/servers/{id} |
GET | Get server details |
/api/fastmcp/servers/{id} |
PUT | Update server config |
/api/fastmcp/servers/{id} |
DELETE | Delete server |
/api/fastmcp/servers/{id}/start |
POST | Start server |
/api/fastmcp/servers/{id}/stop |
POST | Stop server |
/api/fastmcp/servers/{id}/config |
GET | Get config file content |
/api/fastmcp/servers/{id}/config |
PUT | Update config file |
/api/fastmcp/servers/{id}/config/files |
GET | List config files |
GET /mcpandGET /mcp/{namespace}open SSE streams (requiresAccept: text/event-stream).- POST requests require
Accept: application/json(includetext/event-streamif you can handle streaming responses). - JSON-RPC batching is rejected.
- Notifications-only requests return 202 with no body.
Originheader is validated againstCORS_ORIGINS.MCP-Protocol-Versionis validated if present; supported versions configured viaMCP_PROTOCOL_VERSIONS.
- Metrics are aggregated from a hybrid in-memory queue + SQLite store at
tooldock_data/metrics.sqlite. - Dashboard reads
GET /api/admin/metricsfor error rates and tool call counts. - Retention is controlled by
METRICS_RETENTION_DAYS(default 30 days).
# Run all tests (470+)
pytest tests/ -v
# Run with coverage
pytest tests/ --cov=app --cov-report=html
# Install test dependencies (optional, for development)
pip install pytest pytest-asyncio pytest-covNote: Tests are skipped automatically on production servers without pytest installed. When using
./start.sh, tests run inside the backend container (Python 3.12) to avoid host interpreter incompatibilities.
MIT License - see LICENSE for details.




