Content
# Meta MCP
[](https://github.com/anirudhlath/meta-mcp/actions/workflows/ci.yml)
An intelligent MCP (Model Context Protocol) router. Meta MCP spawns your child
MCP servers, embeds their tools, and exposes them to an MCP client through a
single connection — either proxying every tool directly (namespaced) or, via
the `find_tools` meta-tool, letting the client ask "what tool should I use for
X?" and get back the most relevant ones instead of the whole list.
There are two ways to run it:
- **`meta-mcp serve`** — an MCP server over stdio for Claude Desktop / Claude
Code (or any MCP client). This is the integration most people want.
- **`meta-mcp start`** — a standalone dashboard/router process with a Gradio
web UI, useful for development, debugging tool selection, and inspecting
child-server health outside of an MCP client.
Not on PyPI: the `meta-mcp` name is squatted there, so installation is
git-based via `uvx`/`uv tool install` as shown below.
## Prerequisites
- **Python 3.11+**
- **[uv](https://docs.astral.sh/uv/getting-started/installation/)** — provides
the `uvx` and `uv` commands used throughout this README. If you only have
`pipx`, run `pipx install uv` to get `uvx`.
- **Docker or [Apple Container](https://github.com/apple/container)** (macOS
Apple Silicon) — needed to run Qdrant, which backs vector-based tool
selection. Optional if you only ever use `--no-setup` against an
already-running Qdrant, or don't need tool-selection routing at all.
- **[LM Studio](https://lmstudio.ai/)** (optional) — for local embeddings and
LLM-based tool selection. Without it, Meta MCP falls back to a bundled
`sentence-transformers` model automatically.
## Use with Claude Desktop / Claude Code
This is the `meta-mcp serve` path: an MCP server over stdio that exposes every
child tool as `{server}__{tool}` plus a `find_tools` meta-tool. `stdout` is
reserved for the JSON-RPC protocol — all logs and human-readable output go to
stderr, so this is safe to run under any MCP client's process supervisor.
Add to your MCP client config (Claude Desktop's
`claude_desktop_config.json`, or Claude Code's `.mcp.json`):
```json
{
"mcpServers": {
"meta-mcp": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/anirudhlath/meta-mcp",
"meta-mcp",
"serve",
"--mcp-servers-json",
"/absolute/path/to/mcp-servers.json"
]
}
}
}
```
`mcp-servers.json` uses the same `mcpServers` shape Claude Desktop itself
uses, so you can point `--mcp-servers-json` at your existing Claude Desktop
config to re-expose the same child servers through Meta MCP's tool-selection
layer:
```json
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/files"]
}
}
}
```
Working from a local checkout instead of git+https (e.g. while developing)?
Point `uv run --project` at it instead of `uvx`:
```json
{
"mcpServers": {
"meta-mcp": {
"command": "uv",
"args": [
"run",
"--project",
"/path/to/meta-mcp",
"meta-mcp",
"serve",
"--mcp-servers-json",
"/absolute/path/to/mcp-servers.json"
]
}
}
}
```
`serve` supports `--setup` (default `--no-setup`) if you want it to also
detect/start a container runtime and Qdrant before serving — see
`meta-mcp serve --help`. Restart Claude Desktop / Claude Code after editing
the config.
The Gradio web UI is **disabled on the `serve` path** even if your config sets
`web_ui.enabled: true` — Gradio's `launch()` prints to stdout, which would
corrupt the JSON-RPC channel. Use `meta-mcp start` when you want the dashboard.
### `find_tools` and tool namespacing
Every child tool is published under `{server_name}__{tool_name}` (dots aren't
legal in MCP tool names, so `server.tool` becomes `server__tool`; any other
disallowed character is replaced with `-`, and the name is truncated to the
MCP-mandated 64 characters). Call these directly like any other MCP tool.
`find_tools` is a built-in meta-tool, always listed first, that runs Meta
MCP's intelligent selection (vector / LLM / RAG, depending on config and what
initialized successfully) against a natural-language query:
```json
{"name": "find_tools", "arguments": {"query": "read a file from disk", "max_results": 5}}
```
It returns a JSON list of `{"name": ..., "description": ..., "server": ...}`
for the most relevant tools, which you then call directly by their namespaced
name. This is the main point of Meta MCP: instead of a client seeing every
tool from every child server at once, it can ask for just the ones relevant
to the current task.
## Quick Start (dashboard mode)
Run the dashboard/router (`start`) straight from this repository with `uvx`:
```bash
# Automatic setup: detects Docker/Apple Container, starts Qdrant, opens the
# web UI on http://localhost:8080
uvx --from git+https://github.com/anirudhlath/meta-mcp meta-mcp
# With explicit config
uvx --from git+https://github.com/anirudhlath/meta-mcp meta-mcp \
--config my-config.yaml --mcp-servers-json my-servers.json
# Or install it as a persistent CLI tool
uv tool install git+https://github.com/anirudhlath/meta-mcp
meta-mcp
```
Running `meta-mcp` with no arguments (or with top-level flags like
`--config`/`--web-ui`, with no subcommand) runs `start`. On startup it will:
- Detect and set up a container runtime (Docker or Apple Container Framework)
- Start the Qdrant vector database (unless `--no-setup`)
- Auto-detect an existing `mcp-servers.json` or Claude Desktop config in
standard locations (read-only — it does **not** write or modify your Claude
Desktop config)
- Start the Meta MCP server with the web UI at `http://localhost:8080`
## Architecture
```mermaid
flowchart TD
subgraph Server["Meta MCP server"]
Engine["Routing engine<br/>(primary strategy + fallback)"]
Vector["Vector search router"]
LLM["LLM router"]
RAG["RAG router"]
Pipeline["RAG pipeline<br/>(doc chunking + retrieval)"]
Emb["Embedding service"]
Manager["Child server manager"]
Engine --> Vector
Engine --> LLM
Engine --> RAG
RAG --> Pipeline
Vector --> Emb
Pipeline --> Emb
Engine -->|selected tools / proxied calls| Manager
end
Client["MCP client<br/>(Claude Desktop / Claude Code)"] -->|"MCP over stdio<br/>(meta-mcp serve)"| Engine
Vector --> Qdrant[("Qdrant<br/>tool + doc embeddings")]
Pipeline --> Qdrant
Emb -->|primary| LMS["LM Studio<br/>embeddings + local LLM"]
Emb -.->|fallback| ST["sentence-transformers<br/>(local model)"]
LLM --> LMS
Pipeline --> LMS
Manager --> C1["Child MCP server<br/>(e.g. filesystem)"]
Manager --> C2["Child MCP server<br/>(e.g. github)"]
Manager --> C3["Child MCP server<br/>(...)"]
```
Main components (all under `src/meta_mcp/`):
- **North-bound MCP server** (`server/mcp_stdio.py`): the `meta-mcp serve`
entry point — wraps `MetaMCPServer` in the MCP stdio protocol, publishes
`{server}__{tool}` names, and provides `find_tools`
- **Server core** (`server/meta_server.py`): initializes and owns every other
component; resilient startup means a failed embedding/vector-store/LLM/RAG
component is logged as a warning and left `None` rather than crashing —
child tools are still exposed even with no Qdrant/LM Studio running
- **Routing strategies** (`routing/`): vector search (`vector_router.py`),
LLM selection (`llm_router.py`), and RAG-based selection (`rag_router.py`)
- **RAG pipeline** (`rag/pipeline.py`): chunks and indexes child-server
documentation, retrieves relevant context, and augments selection queries
- **Embedding service** (`embeddings/service.py`): LM Studio embeddings when
available, with automatic sentence-transformers fallback and local caching
- **Vector store** (`vector_store/qdrant_client.py`): Qdrant-based storage and
similarity search for tool and documentation embeddings
- **Child server manager** (`child_servers/`): spawns and manages the
lifecycle of downstream MCP servers and proxies tool calls to them
- **Web interface** (`web_ui/`): Gradio-based real-time monitoring and
configuration dashboard (`start` only; not used by `serve`)
- **Health / auto-setup** (`health/`): infrastructure detection, health
checks, and automatic Docker/Apple Container + Qdrant setup
## Features
### Intelligent Tool Selection
- **Vector Search** (default): fast semantic similarity using embeddings
- **LLM Selection**: AI-powered tool selection using a local LLM (LM Studio)
- **RAG-Based Selection**: context-augmented selection using retrieved
child-server documentation
### Automatic Setup (`start` / `--setup`)
- Container runtime detection: Apple Container Framework on Apple Silicon
macOS, or Docker elsewhere
- Starts Qdrant automatically
- Auto-detects an existing `mcp-servers.json` or Claude Desktop config
### Web Dashboard (`start` only)
- Real-time server monitoring and logs
- Interactive configuration editor
- Tool usage analytics and metrics
- Child server status monitoring
- Optional HTTP basic auth (`web_ui.auth_enabled` + `username`/`password`;
fails closed — the UI refuses to start if enabled without both credentials)
## Configuration
### Auto-Detection
`meta-mcp start` (and bare `meta-mcp`) looks for configuration files in these
locations when `--config`/`--mcp-servers-json` aren't given:
**Main Config (meta-server.yaml):**
- `./config/meta-server.yaml`
- `./meta-server.yaml`
- `~/.meta-mcp/config.yaml`
- `/etc/meta-mcp/config.yaml`
**MCP Servers Config (JSON), read-only — never written to:**
- `./mcp-servers.json`
- `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
- `~/.config/claude/claude_desktop_config.json` (Linux/Windows)
- `~/.claude/claude_desktop_config.json`
`meta-mcp serve` does not auto-detect a Claude Desktop `mcp-servers.json`
(pass `--mcp-servers-json` explicitly — see the Claude Desktop/Code section
above), but when `--config` is omitted it still searches the same main-config
locations as `start`, in order: `./config/meta-server.yaml`,
`./meta-server.yaml`, `~/.meta-mcp/config.yaml`, `/etc/meta-mcp/config.yaml`
(falling back to built-in defaults if none exist).
### Creating Custom Config
`mcp-servers.json` (Claude Desktop format):
```json
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/files"]
},
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"]
}
}
}
```
`meta-server.yaml` (every field is real and validated — unknown fields are
rejected; see `examples/simple-config.yaml` and `examples/advanced-config.yaml`
for complete, working examples):
```yaml
strategy:
primary: "vector" # vector, llm, or rag
fallback: "vector" # fallback strategy
vector_threshold: 0.4 # similarity threshold
max_tools: 10 # max tools to return
web_ui:
enabled: true
port: 8080
auth_enabled: false # set true + username/password for basic auth
embeddings:
# Primary: LM Studio (optional). Canonical endpoint form ends in /v1 —
# /v1/ and /v1/embeddings are also accepted and normalized.
lm_studio_endpoint: "http://localhost:1234/v1"
lm_studio_model: "nomic-embed-text-v1.5"
# Fallback: local sentence-transformers model (automatic)
fallback_model: "all-MiniLM-L6-v2"
vector_store:
type: "qdrant"
host: "localhost"
port: 6333
```
Validate any config file before relying on it:
```bash
uv run meta-mcp validate-config path/to/meta-server.yaml
```
## Commands
```
meta-mcp [OPTIONS] COMMAND [ARGS]...
```
Running `meta-mcp` with no subcommand, or with a top-level flag (e.g.
`meta-mcp --config x.yaml --web-ui`), routes to `start`.
| Command | Purpose |
|---|---|
| `serve` | Run the MCP server over stdio for Claude Desktop/Code (see above) |
| `start` | Dashboard/full-stack mode with auto-setup + web UI (default command) |
| `run` | Start the server without auto-setup or config auto-detection |
| `validate-config FILE` | Validate a configuration file |
| `list-strategies` | List available tool-selection strategies |
| `debug-vector` | Run a test query against the vector search index |
| `regenerate-embeddings` | Recompute tool embeddings (`--force` to clear and rebuild) |
| `init-config` | Write a default `meta-server.yaml` |
| `health` | Check system health and dependencies |
Every command supports `--help` for its exact flags, e.g.
`meta-mcp serve --help`. When running via `uvx`, prefix these with
`uvx --from git+https://github.com/anirudhlath/meta-mcp`.
### `health`
```bash
uv run meta-mcp health # text output, exits non-zero on issues
uv run meta-mcp health --output-format json
uv run meta-mcp health --fix --setup-docker --download-models
```
## Docker
`docker-compose.yml` runs Qdrant plus the `meta-mcp` dashboard service
(built from the repo `Dockerfile`, using `config/docker.yaml` which binds the
web UI to `0.0.0.0:8080` and points `vector_store.host` at the `qdrant`
service):
```bash
docker-compose up -d
# Web UI: http://localhost:8080
# Qdrant: http://localhost:6333/collections
```
The container's `CMD` is `meta-mcp start --no-setup --config
/app/config/docker.yaml` (Qdrant is provided by compose, so setup is skipped);
its `HEALTHCHECK` curls `http://localhost:8080/` (the Gradio dashboard root —
there is no `/health` HTTP endpoint).
For running Qdrant via Apple's container framework instead of Docker, see
[`docs/apple-container-setup.md`](docs/apple-container-setup.md).
## Development
```bash
git clone https://github.com/anirudhlath/meta-mcp.git
cd meta-mcp
uv sync --extra dev
uv run pre-commit install
uv run pytest
uv run ruff check src/ tests/
uv run ruff format src/ tests/
uv run mypy src/
# or all at once:
./scripts/check-all.sh
# Run the stdio server against a local checkout:
uv run meta-mcp serve --no-setup --mcp-servers-json path/to/mcp-servers.json --log-level DEBUG
# Run dashboard mode against a local checkout:
uv run meta-mcp start --log-level DEBUG
```
Tests are marked `unit`, `integration` (may spawn real subprocesses; no
Docker/Qdrant required — resilient init is exercised directly), and `slow`.
## Troubleshooting
**Qdrant connection failed**
```bash
curl http://localhost:6333/collections
uv run meta-mcp health --setup-docker
```
**Upgrading from before v0.2.0**: vector-store point IDs and embedding cache
keys changed (the old scheme used a per-process salted hash that produced
duplicate points on every restart). Run this once after upgrading:
```bash
uv run meta-mcp regenerate-embeddings --force
```
**No MCP servers found**: create an `mcp-servers.json` file, or point
`--mcp-servers-json` at an existing Claude Desktop config.
**Web UI not accessible**: check the port isn't already in use
(`lsof -i :8080`) or pick another with `--port`.
**LM Studio not being used**: confirm the endpoint responds at
`http://localhost:1234/v1/models`, and that `lm_studio_endpoint` is set (it's
`null`/unset by default — the fallback `sentence-transformers` model is used
unless you configure it explicitly).
Logs: stderr in `serve` mode; `./logs/meta-server.log` and the web UI's log
viewer in `start`/`run` mode (path from `logging.file` in your config).
## Security Considerations
- Run child servers with minimal privileges
- Use environment variables for sensitive configuration (`${VAR}` expansion
in child-server `env` blocks)
- Review child server configurations before use
- Enable `web_ui.auth_enabled` (+ `username`/`password`) if the dashboard is
reachable beyond localhost
## Contributing
1. Fork the repository and clone your fork
2. `uv sync --extra dev && uv run pre-commit install`
3. Create a feature branch, make your changes with tests (pre-commit runs
Ruff format/lint and mypy on commit)
4. `./scripts/check-all.sh` before opening a PR
## License
MIT License - see [LICENSE](LICENSE) file for details.
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available