initial commit: wrapper Dockerfile + CI
Build & push patched image / build (push) Failing after 1m13s
Build & push patched image / build (push) Failing after 1m13s
The wrapper sed-patches the two host-wide OAuth 2.1 discovery route registrations in MetaMCP's compiled backend bundle so spec-compliant MCP clients do not force an interactive login flow on public endpoints. See README for full rationale (RFC 9728, MCP Authorization spec 2026-03-26 revision) and the diagnostic evidence. Guarded with grep pre/post-conditions so a future upstream release that renames or restructures these routes fails the build loudly rather than silently shipping an unpatched image.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# metamcp-patched
|
||||
|
||||
A thin wrapper around [`ghcr.io/metatool-ai/metamcp`](https://github.com/metatool-ai/metamcp)
|
||||
that hides the host-wide OAuth 2.1 discovery endpoints so spec-compliant MCP
|
||||
clients don't force an interactive login on public endpoints.
|
||||
|
||||
## The problem this fixes
|
||||
|
||||
MetaMCP unconditionally publishes RFC 9728 protected-resource metadata and
|
||||
RFC 8414 authorization-server metadata at:
|
||||
|
||||
- `/.well-known/oauth-protected-resource`
|
||||
- `/.well-known/oauth-authorization-server`
|
||||
|
||||
Both documents describe the whole MetaMCP host as OAuth-protected — even
|
||||
if every configured endpoint is public. Per the MCP Authorization spec
|
||||
(2026-03-26 revision), a spec-compliant client — Claude.ai's remote-MCP
|
||||
integration, Claude Desktop, and the reference SDKs — probes these paths
|
||||
on connect. When it sees them, it kicks off the OAuth flow (with dynamic
|
||||
client registration and PKCE) **before** calling any tool. Users get an
|
||||
interactive login page even when trying to connect to an endpoint that
|
||||
doesn't require auth.
|
||||
|
||||
The route mounts live in a compiled bundle with no env-var gate — the
|
||||
line `app.use(oauth_default)` in `apps/backend/dist/index.js` pulls the
|
||||
metadata router in unconditionally. There is no admin toggle for it in
|
||||
the shipping build.
|
||||
|
||||
This repo produces a wrapper image that sed-patches the two discovery
|
||||
route registrations to bogus paths, so Express falls through to its
|
||||
default 404 for those requests. Nothing else in MetaMCP changes.
|
||||
|
||||
Endpoints that DO enforce auth still 401 correctly; they just can't be
|
||||
consumed via OAuth dynamic-discovery clients any more (consume them via
|
||||
a bearer token in the MCP client's config instead).
|
||||
|
||||
## Using the pre-built image
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
services:
|
||||
metamcp:
|
||||
image: git.jpaul.io/justin/metamcp-patched:latest
|
||||
pull_policy: always
|
||||
# ... rest of your existing metamcp service config unchanged
|
||||
```
|
||||
|
||||
## Or build from source locally
|
||||
|
||||
```yaml
|
||||
services:
|
||||
metamcp:
|
||||
image: metamcp-patched:local
|
||||
pull_policy: never
|
||||
build:
|
||||
context: https://git.jpaul.io/justin/metamcp-patched.git
|
||||
# or a local clone / vendored path
|
||||
```
|
||||
|
||||
Bump upstream at any time with:
|
||||
|
||||
```bash
|
||||
docker compose build --pull metamcp && docker compose up -d metamcp
|
||||
```
|
||||
|
||||
The Dockerfile has `grep` guards that fail the build loudly if a future
|
||||
upstream release renames or restructures the OAuth routes, so a bad bump
|
||||
never silently ships an unpatched image.
|
||||
|
||||
## Verify it worked
|
||||
|
||||
```bash
|
||||
UA="Mozilla/5.0 (X11; Linux x86_64) Firefox/120.0"
|
||||
|
||||
# want 404, was 200 pre-patch
|
||||
curl -sSo /dev/null -w "%{http_code}\n" -H "User-Agent: $UA" \
|
||||
"https://<your-metamcp-host>/.well-known/oauth-protected-resource"
|
||||
curl -sSo /dev/null -w "%{http_code}\n" -H "User-Agent: $UA" \
|
||||
"https://<your-metamcp-host>/.well-known/oauth-authorization-server"
|
||||
|
||||
# want 200 (unchanged) — proves you didn't collaterally break the endpoint
|
||||
curl -sSo /dev/null -w "%{http_code}\n" -H "User-Agent: $UA" \
|
||||
-H "Accept: application/json, text/event-stream" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"diag","version":"1.0"}}}' \
|
||||
"https://<your-metamcp-host>/metamcp/<a-public-endpoint>/mcp"
|
||||
```
|
||||
|
||||
## Delete this repo the day upstream ships a toggle
|
||||
|
||||
The right fix belongs in MetaMCP itself — either an env var like
|
||||
`DISABLE_OAUTH_DISCOVERY=true`, or per-endpoint RFC 9728 metadata
|
||||
scoping so public endpoints don't advertise. Track upstream at
|
||||
<https://github.com/metatool-ai/metamcp>.
|
||||
|
||||
## License
|
||||
|
||||
MIT for the wrapper contents (Dockerfile, workflow, docs). The base
|
||||
image and everything it contains remain under their upstream licenses.
|
||||
Reference in New Issue
Block a user