Skip to main content
Glama
karpikpl
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.

Maintenance

ActivityStale
ResponsivenessNo issues