extensible-mcp
MCP proxy with on-demand server loading, searchable tools, and pluggable filters for access control
Install / Use
claude mcp add mattdfuchs -- npx -y github:mattdfuchs/extensible-mcpIf the server publishes to npm under a different name, use that package instead — check the repo README.
MCP Server
Model Context Protocol server
Quality Score
Category
SecuritySupported Platforms
Skill content
View source on GitHubextensible-mcp
extensible-mcp is a proxy that sits between an LLM and the universe of MCP servers, providing on-demand tool retrieval and a deterministic enforcement point for access control. Tool definitions don't need to live in the prompt, sensitive credentials don't need to live in the LLM's context, and security policies are evaluated by code rather than by the model.
Why
Connecting an LLM client to a set of MCP servers is normally a startup-time decision: list servers in a config, launch the client, hope you guessed right. There's no clean way to add a server mid-conversation, or to have the LLM itself reach for a capability that wasn't pre-configured.
Even once servers are connected, the LLM client is handed a flat list of every tool from every server, injected wholesale into the context window. As the number of servers grows, this causes token bloat, degraded model performance, and hard context-limit failures — even when most tools aren't relevant to the current turn.
And there's no standard control plane. If you want to block dangerous operations, enforce argument-shape policies, or gate which servers an LLM is allowed to connect to in the first place, you have to build that into each client or each server individually.
The temptation is to push these decisions onto the LLM itself — but anything an LLM sees is both transported across the network on every turn and vulnerable to prompt injection from any document, tool result, or web page it reads. Secrets have to stay out of the model's context, and security cannot be left to LLMs communicating with external systems of any type. Enforcement has to live somewhere deterministic, between the model and the outside world.
extensible-mcp sits between the LLM and your MCP servers and addresses all three:
- Dynamic server loading — Connect to MCP servers at startup from config, or at runtime by URL. The LLM can pull in entirely new servers and their capabilities from across the network on demand, without restarting the client.
- RAG-based tool search and retrieval — Tool definitions are embedded into a vector index. The LLM searches semantically with
search_tools(query)and pulls back only the matches it needs, instead of every tool definition occupying space in every prompt. - Pluggable filter pipelines — Every operation (search, call, server load) passes through a filter chain. The proxy enforces one structural guarantee: the LLM can only call tools it has previously surfaced via
search_tools. Beyond that, the filter logic is yours: ship-with reference filters cover access control, Rego policy evaluation, and server-load whitelisting; bring your own for argument validation, audit logging, signed-claim verification, or anything else.
LLM <--> extensible-mcp <--> MCP Server(s)
|
+-- search_tools(query) → vector search over indexed tools
+-- call_tool(name, args) → proxied to the right server
+-- load_mcp_server(name, url) → connect a new server at runtime
How It Works
The proxy exposes three meta-tools to the LLM:
search_tools(query)— Describe what you want to do in natural language. The proxy embeds the query with all-MiniLM-L6-v2, runs cosine similarity against the tool index, and returns matching definitions.call_tool(tool_name, arguments)— Invoke a tool by its qualified name (e.g.github__create_issue). The proxy routes the call to the correct downstream server.load_mcp_server(server_name, url)— Connect to a new remote MCP server at runtime. Its tools are indexed immediately and become available for search and invocation.
Retrieval is model-driven: the LLM decides when to search and crafts its own queries, so there's no wasted retrieval on turns where no tools are needed.
Status
v1 of the proxy is working: dynamic server loading, RAG-based tool retrieval, an extensible filter pipeline, and credential handling all ship today. The pipeline enforces one structural guarantee — the LLM can only call tools it has discovered via search_tools — and ships reference filters for access control, Rego policy evaluation, and server-load whitelisting that you can use as-is, configure, or replace with your own. 104 tests pass; the example configs work against the official GitHub MCP server.
The pipeline is policy-engine-agnostic: Rego is hooked into the call filter today as a reference, but the architecture doesn't privilege any single engine — drop in OPA, Cedar, custom Python, or whatever fits your stack. Active research directions:
- Signed-claim verification at call time — push approvals, signed documents, Verifiable Credentials. See the threat-model section for the argument.
- Native Policy-as-Type integration — linking to the framework from Policy as Code, Policy as Type (Fuchs, 2025), which treats policies as dependent types. Properties of the policy can be mathematically proven rather than just tested.
Both directions extend the existing filter pipeline without architectural change.
Threat Model
LLMs cannot be trusted to manage their own security. They are open to prompt injection attacks from any material they ingest, they can be influenced by material in their training set in non-obvious ways, including treating data as instructions, they hallucinate, they can forget instructions, and any information passed to them must be considered compromised. Therefore any serious attempt to enforce rules must live outside the LLM in code not subject to all these weaknesses. That is our premise.
The pipeline allows for control at all points of contact between the LLM and the external world:
- At server loading time, we can filter and prohibit the agent from loading untrusted servers. Beyond just the tools, the server and tool descriptions can contain prompt injection attacks.
- At search time, we can, again, hide dangerous or untrusted tools. In the current release, we include a sample filter to hide any tool containing "delete"; not only can't such a tool be called, it can't be found.
- At call time, further policies can prevent illegitimate use of an allowed tool. In the sample code we prevent the closing of an issue, but allow other uses of the same tool to allow updating issues.
- The LLM cannot call any tools it didn't find during search. This ensures the LLM calls only tools in the protected set and is not vulnerable to attempts to call outside the protected envelope.
- We do not pass secrets (in particular, security tokens) to the LLM. Tokens to be used in HTTP Authorization headers are kept in a separate file. The LLM can prompt the user to update a token when it appears to have expired, but it never sees the tokens themselves.
Of course, we can only apply these protections within the context of the LLM itself. We cannot protect against:
- Security flaws in the user's configuration,
- The behavior of downstream servers (although limiting to trusted servers can mitigate that),
- Policies that trust unverified LLM claims (such as whether the user has agreed to some action)
- Otherwise ineffective policies (for example, our simple Rego script prohibits one action, but allows all others).
It's tempting to use required argument values as a way to extend policies, such as requiring confirmation: 'CONFIRM_DELETE' before a delete proceeds. We considered this and discarded it: an LLM that can be prompt-injected into deleting a file can also be prompt-injected into supplying the confirmation string. The user's acquiescence is unproven. The mechanism prevents accidents but not adversaries. We will address this pattern using signed claims, evidence whose validity depends on a channel the LLM cannot influence.
This becomes especially acute as agents communicate with other agents. A2A, which AP2 depends on, has the receiving agent process every message through an LLM, making every counterparty message a potential prompt injection vector. An LLM's judgment about what its negotiating partner has agreed to is structurally unsafe; the same signed-evidence architecture that addresses single-agent authorization is even more necessary in multi-agent settings.
By adding support for signed claims as parameters, we can ensure that values come from valid sources, such as the user, and cannot have been forged by the LLM. Examples of this include Duo or CIBA push approvals, W3C Verifiable Credentials (which are used for Google's AP2 and its extension, the Universal Commerce Protocol), or DocuSign-grade envelopes.
With the addition of signed claims, we can inject this level of security in three parts:
- First, before handing a tool definition to the LLM the prefilter modifies the parameter schemas to specify which must be signed.
- These requirements force the LLM to retrieve valid claims for these parameters, either from the user or from other parties. The signing requirement prevents the LLM from spoofing.
- Finally, at tool call time, policies validate the signed parameters as part of approving the call.
This addresses the unverified claims issue and can also be used to strengthen the guarantee that an MCP Server is permitted. Verified claims are now key to agentic commerce, as shown by Google's Universal Commerce Protocol, but the requirement will hold for many non-commercial operations, such as deleting files.
We currently ship Rego hooked into the call filter as a reference policy engine, but the pipeline isn't tied to it — any policy engine can plug in via a custom CallFilter. Rego's strength is broad ABAC expressiveness; its weakness is minimal support for type-checking policy correctness (input shapes can be checked with JSON Schema, but the policy logic itself isn't verified). We plan to link to the framework from Policy as Code, Policy as Type (Fuchs, 2025), which treats policies as dependent types and lets properties of a policy be mathematically proven rather than just tested.
Setup
Requires Python 3.11+.
# Clone and install
git clone https://github.com/mattdfuchs/extensible-mcp.git
cd extensible-mcp
uv sync
# Create a config file
cp config.example.json config.json
# Edit config.json with your MCP servers
config.example.json is intentionally a minimal starter — see the Configuration section below for the full set of options (URL servers, rego_policy, load_control, etc.).
If your config references $VAR_NAME-style values (e.g. "GITHUB_PERSONAL_ACCESS_TOKEN": "$GITHUB_PERSONAL_ACCESS_TOKEN" in a stdio server's env block), drop a .env file in the same directory as the loaded config or export the variables in your shell — the proxy resolves dotenv first, then os.environ. The .env lookup is per-config-directory, so a .env at the repo root won't apply to configs loaded from elsewhere.
Configuration
The config file uses the same mcpServers format as Claude Desktop, plus an optional filters section. Servers can be local (stdio via command) or remote (Streamable HTTP via url):
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "<your-token>"
}
},
"remote-tools": {
"url": "https://example.com/mcp"
}
},
"filters": {
"similarity_threshold": 0.3,
"access_control": {
"deny": ["github__delete_repo"],
"deny_patterns": ["*__drop_*", "*__delete_*"],
"allow_servers": ["filesystem", "github"]
},
"load_control": {
"deny_url_patterns": ["http://*"],
"allow_url_patterns": ["https://github.com/*", "https://internal.corp/*"]
}
}
}
Authentication
Truncated for display — read the full file on GitHub.
Related Skills
Agent-Reach
79.5kGive your AI agent eyes to see the entire internet. Read & search Twitter, Reddit, YouTube, GitHub, Bilibili, XiaoHongShu — one CLI, zero API fees.
ruflo
72.1k🌊 The original agent harness. Deploy intelligent multi-player swarms, coordinate autonomous workflows, and build conversational AI systems. Features adaptive memory, self-learning intelligence, federation, vector RAG integration, and native Claude Code / Codex / Hermes and many more Integrated
headroom
71.6kCompress tool outputs, logs, files, and RAG chunks before they reach the LLM. 20% fewer tokens for coding agents, 60-95% fewer tokens for JSON, same answers. Library, proxy, MCP server.
CowAgent
46.9kOpen-source super AI assistant & Agent Harness. Plans tasks, runs tools and skills, self-evolves with memory and knowledge. Multi-agent, multi-model, multi-channel. Lightweight, extensible, one-line install. (formerly chatgpt-on-wechat)
