MCP Task Proxy
by karpikpl
README.md
# MCP Task Proxy
MCP Task Proxy wraps tools from a remote MCP server with protocol-native
background task support. The upstream server does not need to implement MCP
tasks.
Container image: `ghcr.io/karpikpl/mcp-task-proxy:latest`
The proxy:
- mirrors upstream tool names, descriptions, schemas, annotations, and results;
- advertises each tool with `execution.taskSupport: "optional"`;
- runs task-augmented calls in its own background worker;
- exposes `tasks/get`, `tasks/result`, `tasks/list`, and `tasks/cancel`;
- returns `io.modelcontextprotocol/related-task` metadata with final results;
- forwards upstream progress notifications into task status updates; and
- continues to support ordinary synchronous tool calls.
## Configuration
| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `UPSTREAM_MCP_URL` | Yes | — | Streamable HTTP endpoint of the source MCP server |
| `UPSTREAM_MCP_HEADERS` | No | `{}` | JSON object containing headers sent upstream |
| `UPSTREAM_FORWARD_HEADERS` | No | `Authorization` | Comma-separated incoming headers to forward upstream |
| `HOST` | No | `0.0.0.0` | Proxy listen address |
| `PORT` | No | `8080` | Proxy listen port |
| `FASTMCP_DOCKET_URL` | No | `memory://` | Task backend; use Redis for persistence and scaling |
The default in-memory task backend is suitable for local development and a
single server process. Use a shared Redis or Valkey endpoint for production:
```bash
export FASTMCP_DOCKET_URL=redis://redis:6379/0
```
## Run locally
```bash
export UPSTREAM_MCP_URL=https://example.com/mcp
uv sync
uv run mcp-task-proxy
```
The proxy MCP endpoint is `http://localhost:8080/mcp`; health is available at
`http://localhost:8080/health`.
## Docker
```bash
docker build -t mcp-task-proxy .
docker run --rm -p 8080:8080 \
-e UPSTREAM_MCP_URL=https://example.com/mcp \
mcp-task-proxy
```
To pass upstream headers:
```bash
docker run --rm -p 8080:8080 \
-e UPSTREAM_MCP_URL=https://example.com/mcp \
-e 'UPSTREAM_MCP_HEADERS={"Authorization":"Bearer token"}' \
mcp-task-proxy
```
## Client behavior
Task-aware clients can request background execution:
```python
import asyncio
from fastmcp import Client
async def main() -> None:
async with Client("http://localhost:8080/mcp") as client:
task = await client.call_tool(
"slow_upstream_tool",
{"value": "example"},
task=True,
)
print(task.task_id)
print(await task.result())
asyncio.run(main())
```
Clients that do not augment the call with task metadata receive the upstream
result synchronously.
The proxy forwards the incoming `Authorization` header during authenticated
tool discovery, synchronous calls, and background task execution. Forwarding is
restricted to `UPSTREAM_FORWARD_HEADERS`; request headers override matching
static values in `UPSTREAM_MCP_HEADERS`. The proxy relays credentials but does
not validate them itself.
For OAuth-capable upstream servers, the proxy exposes RFC 9728 metadata at
`/.well-known/oauth-protected-resource` and
`/.well-known/oauth-protected-resource/mcp`. It retrieves the path-aware
metadata document from the upstream server, preserves its authorization servers
and scopes, and rewrites `resource` to the proxy's public `/mcp` URL. For
Databricks Genie One, this preserves the required `genie` and `offline_access`
scopes and the workspace `/oidc` authorization server.
Upstream tools are discovered and cached on the first authenticated client
request. Restart the proxy after the upstream adds, removes, or changes tools.
## Limitations
- The proxy can report real progress only when the upstream sends MCP progress
notifications. Otherwise task status remains `working` until completion.
- Cancellation stops the proxy task cooperatively but cannot guarantee that the
upstream operation stops.
- In-memory tasks are lost when the proxy restarts and cannot be shared across
replicas.
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues