MCP Task Proxy
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, andtasks/cancel;returns
io.modelcontextprotocol/related-taskmetadata with final results;forwards upstream progress notifications into task status updates; and
continues to support ordinary synchronous tool calls.
Configuration
Variable | Required | Default | Description |
| Yes | — | Streamable HTTP endpoint of the source MCP server |
| No |
| JSON object containing headers sent upstream |
| No |
| Comma-separated incoming headers to forward upstream |
| No |
| Proxy listen address |
| No |
| Proxy listen port |
| No |
| 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:
export FASTMCP_DOCKET_URL=redis://redis:6379/0Run locally
export UPSTREAM_MCP_URL=https://example.com/mcp
uv sync
uv run mcp-task-proxyThe proxy MCP endpoint is http://localhost:8080/mcp; health is available at
http://localhost:8080/health.
Docker
docker build -t mcp-task-proxy .
docker run --rm -p 8080:8080 \
-e UPSTREAM_MCP_URL=https://example.com/mcp \
mcp-task-proxyTo pass upstream headers:
docker run --rm -p 8080:8080 \
-e UPSTREAM_MCP_URL=https://example.com/mcp \
-e 'UPSTREAM_MCP_HEADERS={"Authorization":"Bearer token"}' \
mcp-task-proxyClient behavior
Task-aware clients can request background execution:
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
workinguntil 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.