Files
metamcp-patched/.gitea/workflows/build.yml
T
justin 26b27b2d94
Build & push patched image / build (push) Successful in 1m8s
sql: per-caller usage attribution via dedicated endpoint chains
MetaMCP proxies with a fresh outbound connection and forwards no client
identity — not User-Agent, not X-MCP-Client, not initialize.clientInfo.
A downstream server that logs client identity therefore sees only
MetaMCP's own Node UA, and every client behind the gateway collapses
into one undifferentiated bucket. Verified by probe: three distinct
identity values in, {"name": "node", "user_agent": "node"} logged out.

Not patchable at reasonable cost: downstream connections are pooled and
pre-warmed (idleSessions[serverUuid] / createIdleSessionAsync), so one
connection serves many inbound callers and headers bind at connection
creation. No per-request injection point exists without disabling
pooling, re-keying the pool by (server, caller), or threading
AsyncLocalStorage through the transport.

But MetaMCP already supports static per-server custom headers —
mcp_servers.headers is jsonb and flows into the outbound
requestInit.headers. So give each caller its own endpoint → namespace →
server chain, pointing at the SAME backend URL, differing only in a
stamped X-MCP-Client header. Same container, same corpus, nothing
duplicated downstream.

Adds sql/zsupport-endpoint.sql (the worked example, applied to the live
gateway 2026-07-27 — zSupport now attributes correctly) and a README
section covering the pattern and its two gotchas: the UNIQUE (name,
user_id) constraint forcing a new server name, and the resulting change
of tool-name prefix that any client allow-list must track.

sql/** added to the workflow's paths-ignore — config, not image.
2026-07-27 10:44:36 -04:00

87 lines
3.0 KiB
YAML

name: Build & push patched image
# Wraps ghcr.io/metatool-ai/metamcp:latest with our sed-patch to hide
# the OAuth 2.1 discovery endpoints, then pushes the resulting image
# to the git.jpaul.io registry.
#
# Runs on push to main, on the nightly cron so we automatically pick up
# upstream :latest updates, and on demand via workflow_dispatch. The
# Dockerfile has grep guards that fail the build loudly if upstream
# renames or restructures the OAuth routes, so a bad bump is loud —
# nightly failures show up as red workflow runs, not a silently broken
# unpatched image.
on:
push:
branches: [main]
paths-ignore:
- "README.md"
- "LICENSE"
- ".gitignore"
# sql/ holds MetaMCP *config* (rows applied to its Postgres), which has
# nothing to do with the image contents — don't rebuild on those edits.
- "sql/**"
schedule:
# Every day at 08:00 UTC — pulls upstream :latest and rebuilds.
- cron: "0 8 * * *"
workflow_dispatch:
env:
# Push to the plain-HTTP LAN endpoint (bypasses Cloudflare's body cap
# on multi-hundred-MB layers). Pull consumers use the FQDN over TLS.
REGISTRY_PUSH: 192.168.0.2:1234
REGISTRY_PULL: git.jpaul.io
IMAGE: justin/metamcp-patched
jobs:
build:
runs-on: docker
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: image=moby/buildkit:latest
buildkitd-config-inline: |
[registry."192.168.0.2:1234"]
http = true
insecure = true
- name: Registry login (hand-written config — login-action 403s on plain HTTP)
shell: bash
run: |
AUTH=$(printf '%s' "justin:${{ secrets.REGISTRY_TOKEN }}" | base64 -w0)
mkdir -p ~/.docker
printf '{"auths":{"%s":{"auth":"%s"}}}' "${REGISTRY_PUSH}" "$AUTH" > ~/.docker/config.json
- name: Compute tag
id: tags
shell: bash
run: |
SHA="$(echo "${{ github.sha }}" | cut -c1-12)"
echo "sha=${SHA}" >> "$GITHUB_OUTPUT"
- name: Build & push
shell: bash
run: |
docker buildx build \
--push \
--pull \
-t "${REGISTRY_PUSH}/${IMAGE}:latest" \
-t "${REGISTRY_PUSH}/${IMAGE}:${{ steps.tags.outputs.sha }}" \
--label "org.opencontainers.image.source=https://git.jpaul.io/${IMAGE}" \
--label "org.opencontainers.image.description=MetaMCP with host-wide OAuth 2.1 discovery routes hidden" \
.
echo "Pushed ${REGISTRY_PULL}/${IMAGE}:{latest,${{ steps.tags.outputs.sha }}}"
- name: Link package to repo (idempotent)
shell: bash
run: |
curl -s -o /dev/null -w "link HTTP %{http_code}\n" \
-H "Authorization: token ${{ secrets.REGISTRY_TOKEN }}" \
-X POST "http://${REGISTRY_PUSH}/api/v1/packages/justin/container/metamcp-patched/-/link/metamcp-patched" || true