Skip to content

feat: expose per-server endpoints in mcporter serve#194

Merged
steipete merged 2 commits into
openclaw:mainfrom
zm2231:feat/per-server-serve-endpoints
Jun 8, 2026
Merged

feat: expose per-server endpoints in mcporter serve#194
steipete merged 2 commits into
openclaw:mainfrom
zm2231:feat/per-server-serve-endpoints

Conversation

@zm2231

@zm2231 zm2231 commented May 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #172. mcporter serve exposes all daemon-managed keep-alive
servers under one aggregate endpoint (/mcp), namespacing every tool as
server__tool. That is the right default for a single bridge, but it has a sharp
edge: registering the aggregate endpoint in an MCP client surfaces every server's
tools under one client key.

In practice that is a lot of tools. On my setup the aggregate mcporter
registration shows 206 tools across about a dozen servers (visible in the
client's MCP server list), so a client that registers it pays the full context
cost of all 206 even when a session only needs one server, e.g. gitnexus's 13
tools. There is no way to mount just one server today.

You can approximate this today by running a separate mcporter serve --servers <name> process per server, but that means one process, port, and launchd plist
each, and those names still come out server__tool-prefixed. Per-server
endpoints give the same isolation from a single serve process, with the server's
original tool names.

This adds per-server endpoints. GET/POST /mcp/<server> serves only that
server's tools, under their original (unprefixed) names, so a client can register
gitnexus on its own and see its 13 tools instead of all 206. /mcp is
unchanged and still aggregates everything with server__tool naming.

What's Included

  • src/serve.ts
    • Adds a bare serve mode: a single server whose tools are exposed under their
      original names, with no server__tool prefixing or decoding.
    • serveHttp routes /mcp/<server> to a bare single-server bridge; /mcp
      keeps the aggregate bridge.
    • Unknown server returns 404 (with a text/plain content type so the echoed
      name is never sniffed as HTML); malformed percent-encoding returns 400.
    • createBridgeServer enforces exactly one served server in bare mode.
    • The aggregate path and tool-name encoding are unchanged.

Tests

Added coverage in tests/serve.test.ts:

  • Unprefixed tool listing and calling through /mcp/<server>.
  • Unknown-server 404 and malformed-path 400.
  • Bare mode rejects more than one served server.

Full suite: 689 passed, 3 skipped (121 files). pnpm check clean.

@clawsweeper

clawsweeper Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

Codex review: found issues before merge. Reviewed May 31, 2026, 8:34 PM ET / 00:34 UTC.

Summary
The PR adds /mcp/<server> routing to mcporter serve so one keep-alive server can be exposed with unprefixed tool names while keeping aggregate /mcp unchanged.

Reproducibility: not applicable. as a feature PR rather than a bug report. Source inspection shows current main lacks /mcp/<server>, and the PR proof/tests demonstrate the new behavior on the branch.

Review metrics: 2 noteworthy metrics.

  • Diff size: 2 files changed; 135 additions, 7 deletions. The implementation is focused on the serve bridge plus targeted regression coverage.
  • Docs/help coverage: 0 docs or help files changed. A new public HTTP route is added while current user-facing text still mentions only /mcp.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Document /mcp/<server> in README and mcporter serve --help, or record maintainer acceptance that the endpoint should ship with low discoverability.

Risk before merge

  • [P1] The new endpoint is a public HTTP serve surface but README and mcporter serve --help still describe only /mcp, so maintainers should either document /mcp/<server> before merge or explicitly accept low discoverability.

Maintainer options:

  1. Decide the mitigation before merge
    Land the focused bridge change after the public endpoint is documented or maintainer-approved as intentionally undiscoverable, while preserving aggregate /mcp behavior.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P2] A maintainer should decide the docs/help requirement for the new public endpoint; no blocking source-level correctness defect needs an automated repair lane.

Security
Cleared: No concrete security or supply-chain regression was found; the diff adds local HTTP path routing and tests without new dependencies, scripts, permissions, or secret handling.

Review findings

  • [P3] Document the per-server HTTP route — src/serve.ts:60-73
Review details

Best possible solution:

Land the focused bridge change after the public endpoint is documented or maintainer-approved as intentionally undiscoverable, while preserving aggregate /mcp behavior.

Do we have a high-confidence way to reproduce the issue?

Not applicable as a feature PR rather than a bug report. Source inspection shows current main lacks /mcp/<server>, and the PR proof/tests demonstrate the new behavior on the branch.

Is this the best way to solve the issue?

Unclear until maintainer signs off the public docs/help surface. The code path itself is narrow, preserves /mcp, and reuses the existing bridge/runtime structure.

Full review comments:

  • [P3] Document the per-server HTTP route — src/serve.ts:60-73
    This patch makes /mcp/<server> a public serve endpoint, but the serve help and README still say HTTP serving is on /mcp only. Please add the route to user-facing docs/help, or get maintainer sign-off that this should ship as intentionally low-discoverability.
    Confidence: 0.88

Overall correctness: patch is correct
Overall confidence: 0.88

AGENTS.md: found and applied where relevant.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 2bf7a5eab23f.

Label changes

Label changes:

  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): Contributor and maintainer proof show after-fix /mcp/<server> list/call behavior, aggregate /mcp unchanged, and visible split client registrations.
  • remove status: 🔁 re-review loop: Current PR status label is status: 👀 ready for maintainer look.

Label justifications:

  • P2: This is a bounded user-facing mcporter serve improvement with limited blast radius and no evidence of an urgent regression.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): Contributor and maintainer proof show after-fix /mcp/<server> list/call behavior, aggregate /mcp unchanged, and visible split client registrations.
  • proof: sufficient: Contributor real behavior proof is sufficient. Contributor and maintainer proof show after-fix /mcp/<server> list/call behavior, aggregate /mcp unchanged, and visible split client registrations.
Evidence reviewed

What I checked:

  • Current main only routes aggregate /mcp: On current main, serveHttp returns 404 for every path except /mcp, so the requested per-server endpoint is not already implemented. (src/serve.ts:56, 2bf7a5eab23f)
  • PR adds per-server route: The merge result routes /mcp/<server> through a single-server bare bridge after validating the decoded server name. (src/serve.ts:60, 5b450838ed77)
  • PR adds focused tests: The merge result adds tests for bare unprefixed tools, /mcp/<server> list/call behavior, unknown-server 404, and malformed percent-encoding 400. (tests/serve.test.ts:300, 5b450838ed77)
  • Docs still mention only /mcp: The README daemon section still says HTTP serve is available on localhost at /mcp; the PR does not update README or CLI help for /mcp/<server>. (README.md:202, 2bf7a5eab23f)
  • Real behavior proof inspected: Contributor proof shows current main returns 404 for /mcp/alpha and the branch lists/calls unprefixed tools there; maintainer-side proof also verified /mcp/proof, aggregate /mcp, 404, and targeted tests. (ecc5d2930642)

Likely related people:

  • zm2231: The existing mcporter serve bridge was introduced in merged commits by this author, and this PR extends that same feature rather than only proposing unrelated new code. (role: serve bridge feature owner; confidence: high; commits: 6879a69f49e7, bfe727150c94, 89f5053c15b2; files: src/serve.ts, src/cli/serve-command.ts, tests/serve.test.ts)
  • Peter Steinberger: Adjusted served tool naming/readability after the original serve work and the current main history carries the serve files through the v0.11.3 release commit. (role: recent area contributor and release integrator; confidence: medium; commits: 907ba78d9834, 94e65ba0572e; files: src/serve.ts, tests/serve.test.ts, README.md)
  • LDMB123: Provided maintainer-side live proof for this PR head and recently landed adjacent runtime work on current main, but did not appear in the central serve-file history. (role: proof verifier and adjacent runtime contributor; confidence: low; commits: 2bf7a5eab23f; files: src/runtime.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal priority bug or improvement with limited blast radius. labels May 31, 2026

LDMB123 commented May 31, 2026

Copy link
Copy Markdown
Contributor

Maintainer-side proof for the /mcp/<server> behavior on this PR head (ecc5d29306420d46ef9ab8c9ab9296819f0f88bc), using a disposable local MCP stdio fixture under a temporary HOME with no secrets.

Commands/evidence:

  • mcporter serve --servers proof --http 0 --host 127.0.0.1 --config tmp/pr194-proof-config.json printed MCPorter serve HTTP bridge http://127.0.0.1:61635/mcp.
  • http://127.0.0.1:61635/mcp/proof listed unprefixed tools: ["ping"].
  • calling ping on /mcp/proof returned pong-pr194.
  • http://127.0.0.1:61635/mcp still listed the aggregate namespaced tool ["proof__ping"].
  • calling proof__ping on /mcp returned pong-pr194.
  • GET /mcp/nope returned 404, text/plain; charset=utf-8, body Unknown server 'nope'.
  • pnpm exec vitest run tests/serve.test.ts passed outside the sandbox: 13 passed.

This covers the live behavior proof blocker I could verify from the maintainer side. The docs/help surface decision for the new endpoint remains a maintainer/product call unless the contributor updates this branch.

@clawsweeper re-review

@zm2231

zm2231 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Adding real-behavior proof for this PR.

Before the aggregate mcporter endpoint (/mcp) registers every server as a single entry (~206 tools), with no way to mount one server on its own:

Before MCPorter Serve

After registering each server at /mcp/<server> (the same single serve process) splits them into individually mountable entries, each with its own tool count:

After MCPorter

Reproducible from a throwaway config of 3 keep-alive stdio servers (alpha/beta/gamma), 5 tools each = 15 aggregate:

Before, current main (only /mcp exists, all tools under one registration):

/mcp        ->  15 tools, all namespaced: ["alpha__ping", ... ,"gamma__version"]
/mcp/alpha  ->  HTTP 404  body: Not found

After, this branch (/mcp unchanged; /mcp/<server> serves one server, unprefixed):

/mcp        ->  15 tools (unchanged, namespaced)
/mcp/alpha  ->  5 tools, unprefixed: ["ping","echo","status","info","version"]
/mcp/nope         ->  HTTP 404  content-type: text/plain; charset=utf-8  body: Unknown server 'nope'
/mcp/%E0%A4%A     ->  HTTP 400  (malformed percent-encoding)

pnpm check clean; pnpm exec vitest run tests/serve.test.ts -> 13 passed.

Why not just serve --servers?

Isolating one server was approximately possible before, but only by running a separate serve process per server, each on its own port and kept alive by its own launchd plist, and the tool names still came out server__tool-prefixed:

# pre-PR: one process + port + plist per server, names still prefixed
mcporter serve --http 4801 --servers cadence
mcporter serve --http 4802 --servers gitnexus
# ... N of these

This PR does it with one serve process and path routing, with unprefixed tool names:

mcporter serve --http 4748        # serves /mcp (aggregate) and /mcp/<server> (per-server)
"gitnexus": { "type": "http", "url": "http://127.0.0.1:4748/mcp/gitnexus" }

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 🔁 re-review loop A fresh ClawSweeper review was explicitly requested after the latest review. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. status: 🔁 re-review loop A fresh ClawSweeper review was explicitly requested after the latest review. labels Jun 1, 2026
zm2231 and others added 2 commits June 8, 2026 20:07
…ixed tools

Add per-server HTTP routes alongside the aggregate /mcp endpoint. /mcp/<server>
serves a single server's tools under their original (unprefixed) names so the
server can be registered under its own client key, while /mcp keeps the
server__tool namespacing. Unknown servers 404; malformed paths 400.
@steipete
steipete force-pushed the feat/per-server-serve-endpoints branch from ecc5d29 to 3498fdf Compare June 8, 2026 19:14
@steipete
steipete merged commit 0fb1358 into openclaw:main Jun 8, 2026
3 checks passed
@steipete

steipete commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Landed as 0fb1358 after rebasing the contributor commit onto current main and adding a maintainer docs/help/test fixup.

Proof:

  • ./runner pnpm exec vitest run tests/serve.test.ts tests/cli-help-shortcuts.test.ts
  • ./runner pnpm build
  • node dist/cli.js serve --help shows /mcp/<server> in HTTP help
  • Real CLI E2E with disposable keep-alive stdio servers and an isolated daemon: mcporter serve --http 0 --host 127.0.0.1 exposed aggregate /mcp tools as [alpha__echo, alpha__ping, beta__echo, beta__ping], /mcp/alpha tools as [echo, ping], /mcp/beta tools as [echo, ping]; aggregate alpha__ping returned pong-alpha; per-server ping returned pong-alpha; per-server echo returned beta:ok; /mcp/nope returned 404 text/plain with Unknown server 'nope'; malformed /mcp/%E0%A4%A returned 400
  • ./runner pnpm docs:list
  • ./runner pnpm check
  • ./runner pnpm test
  • autoreview clean: /Users/steipete/Projects/agent-scripts/skills/autoreview/scripts/autoreview --mode branch --base main
  • CI green on macOS, Ubuntu, and Windows for 3498fdf

Caveats: first manual E2E attempt used a long nested temp daemon directory and hit the macOS Unix-socket path limit; rerun used a short isolated /tmp daemon directory and passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants