Skip to content

Commit 9594351

Browse files
committed
examples(mcp.proxy): wire identity guard for bearer pass-through to github
Adds a passthrough identity guard at north_http_server that captures the inbound Authorization bearer via {credentials}, and references it from south_mcp_client_github's options.authorization. mcp(client) re-stamps Authorization: Bearer <token> on the outbound request to github:3003 so github-mcp-server receives the client's PAT for its own validation. The everything and time mcp(client) bindings remain unguarded — no Authorization is added on outbound to those upstreams. Without a client token, github-mcp-server's HTTP 403 + WWW-Authenticate (from --scope-challenge) still flows back through the proxy unchanged. README walks through the full client-driven auth flow end to end, and the top-level examples index description is refreshed to match the current demonstrated capabilities.
1 parent b065fe0 commit 9594351

3 files changed

Lines changed: 50 additions & 10 deletions

File tree

examples/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Make sure you have the `latest` version of Zilla by running the `docker pull ghc
5050
| [http.kafka.proto.oneway](http.kafka.proto.oneway) | Publish a Protobuf serialized object over HTTP onto a Kafka topic |
5151
| [http.kafka.sync](http.kafka.sync) | Correlates HTTP requests and responses over separate Kafka topics |
5252
| [http.proxy](http.proxy) | Proxy request sent to the HTTP server from an HTTP client |
53-
| [mcp.proxy](mcp.proxy) | Aggregates multiple upstream MCP servers behind one JWT-guarded Streamable HTTP endpoint |
53+
| [mcp.proxy](mcp.proxy) | Aggregates multiple upstream MCP servers behind one Streamable HTTP endpoint, with per-route bearer pass-through to upstream |
5454
| [mqtt.proxy.jwt](mqtt.proxy.jwt) | Proxies request sent to the MQTT server from a JWT-authorized MQTT client |
5555
| [mqtt.kafka.broker](mqtt.kafka.proxy) | Forwards MQTT publish messages to Kafka, broadcasting to all subscribed MQTT clients |
5656
| [openapi.asyncapi.kakfa.proxy](openapi.asyncapi.kakfa.proxy) | Create an HTTP to Kafka REST proxy using OpenAPI and AsyncAPI schemas |

examples/mcp.proxy/README.md

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,16 @@ The proxy demonstrates, in one configuration:
3030
through the gateway with no extra configuration. The `everything` reference
3131
server's `trigger-elicitation-request-async` and `simulate-research-query`
3232
(with `ambiguous: true`) tools exercise this directly.
33-
- **Upstream-driven auth** — the gateway adds no Authorization of its own.
34-
Clients pass `Authorization: Bearer <PAT>` through Zilla to the upstream; if
35-
the upstream needs additional scopes or rejects the token, it returns an MCP
36-
scope-challenge elicitation and Zilla forwards that back to the client.
37-
`github-mcp-server` is started with `--scope-challenge` to exercise this flow.
33+
- **Client → upstream bearer pass-through via the `identity` guard** — clients
34+
send `Authorization: Bearer <PAT>` to Zilla; the `identity` guard captures
35+
the token at `north_http_server` and `mcp(client)` for the github route
36+
re-stamps it on the outbound request. The everything and time routes have no
37+
guard configured, so no Authorization is added on outbound to those upstreams.
38+
- **Auth-driven challenge pass-through**`github-mcp-server` is started with
39+
`--scope-challenge`, so when an inbound token is missing or under-scoped the
40+
upstream's `HTTP 403 + WWW-Authenticate` flows back through the gateway
41+
unchanged. Clients that implement RFC 6750 / MCP OAuth can complete the
42+
flow externally and retry with the new token.
3843

3944
## Requirements
4045

@@ -97,19 +102,43 @@ JSON-RPC request bound for the client. Zilla forwards it back through
97102
Use MCP Inspector for a full interactive round-trip (it knows how to handle
98103
the elicitation message and reply via `elicitation/result`).
99104

100-
### Call a GitHub tool (auth flows from client to upstream)
105+
### Call a GitHub tool (bearer flows from client through to upstream)
106+
107+
First start the github upstream:
108+
109+
```bash
110+
docker compose --profile github up -d
111+
```
112+
113+
Issue a github tool call without an `Authorization` header — `github-mcp-server`
114+
returns `HTTP 403` with a `WWW-Authenticate` challenge that propagates back
115+
through the proxy:
116+
117+
```bash
118+
curl -i -N http://localhost:7114/mcp \
119+
-H "Content-Type: application/json" \
120+
-H "Accept: application/json, text/event-stream" \
121+
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"github/get_me"}}'
122+
```
123+
124+
Mint a [GitHub Personal Access Token](https://github.com/settings/tokens),
125+
then retry with `Authorization: Bearer <PAT>`. Zilla's `identity` guard captures
126+
the bearer at `north_http_server`, and the `mcp(client)` for the github route
127+
re-stamps it on the outbound request — `github-mcp-server` accepts the token
128+
and the tool call completes:
101129

102130
```bash
131+
export GITHUB_PERSONAL_ACCESS_TOKEN=ghp_...
103132
curl -N http://localhost:7114/mcp \
104133
-H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" \
105134
-H "Content-Type: application/json" \
106135
-H "Accept: application/json, text/event-stream" \
107136
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"github/get_me"}}'
108137
```
109138

110-
Omit the `Authorization` header to observe `github-mcp-server`'s scope-challenge
111-
elicitation flowing back through the proxy — this is the same pass-through path
112-
as the `everything` example above, just driven by auth instead of a tool.
139+
The everything and time routes have no `options.authorization` on their
140+
`mcp(client)` bindings — they receive no Authorization header even when the
141+
client supplies one to Zilla.
113142

114143
### Observe the cache
115144

examples/mcp.proxy/etc/zilla.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
---
22
name: example
3+
guards:
4+
passthrough:
5+
type: identity
36
stores:
47
cache:
58
type: memory
@@ -21,6 +24,11 @@ bindings:
2124
options:
2225
access-control:
2326
policy: cross-origin
27+
authorization:
28+
passthrough:
29+
credentials:
30+
headers:
31+
authorization: Bearer {credentials}
2432
routes:
2533
- when:
2634
- headers:
@@ -77,6 +85,9 @@ bindings:
7785
south_mcp_client_github:
7886
type: mcp
7987
kind: client
88+
options:
89+
authorization:
90+
name: passthrough
8091
routes:
8192
- exit: south_http_client
8293
with:

0 commit comments

Comments
 (0)