Skip to content

[SILO-1181] feat: support configurable subpath prefix for MCP server#106

Open
Prashant-Surya wants to merge 2 commits into
mainfrom
feat/mcp-path-prefix
Open

[SILO-1181] feat: support configurable subpath prefix for MCP server#106
Prashant-Surya wants to merge 2 commits into
mainfrom
feat/mcp-path-prefix

Conversation

@Prashant-Surya
Copy link
Copy Markdown
Member

@Prashant-Surya Prashant-Surya commented Apr 13, 2026

Summary

  • Adds an optional MCP_PATH_PREFIX env var so the HTTP server can be deployed behind an ingress that serves it under a subpath (e.g. /mcp).
  • All three transports — OAuth streamable-http (/http/mcp), header-auth streamable-http (/http/api-key/mcp), and SSE (/sse) — are now mounted under the prefix.
  • Each OAuth provider's base_url is built with the prefix so advertised metadata (issuer, authorize, token, register, and protected-resource URLs) resolves to the externally-visible path.
  • mcp_path for well-known routes stays at /mcp and /sse (relative to base_url) to avoid double-prefixing the advertised resource URL.
  • When MCP_PATH_PREFIX is unset, routing and metadata are identical to before — no behavior change for existing deployments.

Tracks: SILO-1181.

Test plan

  • Start locally with MCP_PATH_PREFIX=/mcp, PLANE_OAUTH_PROVIDER_BASE_URL=http://localhost:8211 (no prefix in env var).
  • Verify GET /.well-known/oauth-authorization-server/mcp/http returns issuer http://localhost:8211/mcp/http and registration endpoint http://localhost:8211/mcp/http/register.
  • Verify GET /.well-known/oauth-authorization-server/mcp (SSE) returns issuer http://localhost:8211/mcp.
  • Verify GET /.well-known/oauth-protected-resource/mcp/http/mcp advertises resource http://localhost:8211/mcp/http/mcp.
  • POST /mcp/http/mcp returns 401 (OAuth challenge) — not 404.
  • POST /mcp/http/api-key/mcp with valid x-workspace-slug + authorization headers initializes successfully.
  • GET /mcp/sse returns 401 (not 404).
  • End-to-end OAuth flow via test_client.py::test_oauth_mcp against http://localhost:8211/mcp/http/mcp completes successfully.
  • Without MCP_PATH_PREFIX set, existing URLs (/http/mcp, /http/api-key/mcp, /sse) continue to work unchanged.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added support for an environment variable to set an API path prefix, allowing OAuth and realtime (SSE) endpoints to be mounted under custom paths.
  • Bug Fixes
    • Improved mount behavior to avoid empty-path issues for realtime connections.
    • Improved server-to-server endpoint selection to better handle varied deployment configurations.

Review Change Stack

@makeplane
Copy link
Copy Markdown

makeplane Bot commented Apr 13, 2026

Linked to Plane Work Item(s)

This comment was auto-generated by Plane

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 13, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6f0b37f2-58e1-4ccc-8a09-ad8c6990d9a0

📥 Commits

Reviewing files that changed from the base of the PR and between b5ad536 and 54ece08.

📒 Files selected for processing (2)
  • plane_mcp/__main__.py
  • plane_mcp/auth/plane_oauth_provider.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • plane_mcp/main.py

📝 Walkthrough

Walkthrough

Adds MCP_PATH_PREFIX support in HTTP mode to compute a mount prefix used for OAuth endpoints and Starlette mounts, and refactors PlaneOAuthProvider to prefer settings.plane_internal_base_url, then the PLANE_INTERNAL_BASE_URL env var, then the original base URL.

Changes

HTTP Path Prefix Configuration

Layer / File(s) Summary
MCP prefix computation and route mounts
plane_mcp/__main__.py
Compute prefix from MCP_PATH_PREFIX (default ""), pass prefix into OAuth app creation (prefix + "/http" and prefix for SSE), update Starlette Mount paths to prefix + "/http/api-key", prefix + "/http", and prefix or "/" for SSE mounts.
plane_internal_url fallback ordering
plane_mcp/auth/plane_oauth_provider.py
Change plane_internal_url resolution to prefer settings.plane_internal_base_url, then PLANE_INTERNAL_BASE_URL from environment, then plane_base_url_final.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A prefix hops onto the trail,
Routes now mount without fail,
OAuth finds its proper place,
Internal URLs fall back with grace,
I nibble code and leave a trail. 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding support for a configurable subpath prefix (MCP_PATH_PREFIX) for the MCP server.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mcp-path-prefix

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
plane_mcp/__main__.py (2)

136-136: ⚠️ Potential issue | 🟡 Minor

Startup log message no longer matches mounted paths.

The message still says /mcp and /header/mcp, but mounts are now under /http/mcp and /http/api-key/mcp (plus optional prefix). Update this to avoid operational confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plane_mcp/__main__.py` at line 136, Update the startup log in __main__.py so
the logger.info message reflects the actual mounted routes; replace the outdated
"/mcp" and "/header/mcp" text in the logger.info call with the current paths
"/http/mcp" and "/http/api-key/mcp" (and include any configured optional prefix
if your app prepends one) so the log accurately shows where the HTTP endpoints
are mounted.

91-115: ⚠️ Potential issue | 🟠 Major

Normalize MCP_PATH_PREFIX before composing routes.

Raw concatenation can produce malformed paths (for example ///http, mcpmcp/http). Normalize once, then reuse.

Proposed fix
-        prefix = os.getenv("MCP_PATH_PREFIX") or ""
+        raw_prefix = (os.getenv("MCP_PATH_PREFIX") or "").strip()
+        if raw_prefix in {"", "/"}:
+            prefix = ""
+        else:
+            prefix = raw_prefix if raw_prefix.startswith("/") else f"/{raw_prefix}"
+            prefix = prefix.rstrip("/")

         oauth_mcp = get_oauth_mcp(prefix + "/http")
         oauth_app = oauth_mcp.http_app(stateless_http=True)
         header_app = get_header_mcp().http_app(stateless_http=True)

         sse_mcp = get_oauth_mcp(prefix)
         sse_app = sse_mcp.http_app(transport="sse")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plane_mcp/__main__.py` around lines 91 - 115, Normalize the MCP_PATH_PREFIX
string once instead of raw concatenation: compute prefix_raw =
os.getenv("MCP_PATH_PREFIX") or "" then normalize to prefix = ("/" +
prefix_raw.strip("/")) if prefix_raw.strip("/") else "" so the value either is
"" or begins with a single leading slash and has no trailing slash; then update
calls that currently do prefix + "/http" and prefix + "/http/api-key" (the
get_oauth_mcp calls and the Mount arguments) to use f"{prefix}/http" and
f"{prefix}/http/api-key" (or construct the path with the normalized prefix), and
keep Mount(prefix or "/", app=sse_app) as-is so routes do not get double
slashes; change the prefix assignment near where prefix is defined and leave
get_oauth_mcp, get_header_mcp, oauth_mcp, sse_mcp, oauth_well_known,
sse_well_known, and the Starlette routes/Mount entries otherwise unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@plane_mcp/__main__.py`:
- Line 136: Update the startup log in __main__.py so the logger.info message
reflects the actual mounted routes; replace the outdated "/mcp" and
"/header/mcp" text in the logger.info call with the current paths "/http/mcp"
and "/http/api-key/mcp" (and include any configured optional prefix if your app
prepends one) so the log accurately shows where the HTTP endpoints are mounted.
- Around line 91-115: Normalize the MCP_PATH_PREFIX string once instead of raw
concatenation: compute prefix_raw = os.getenv("MCP_PATH_PREFIX") or "" then
normalize to prefix = ("/" + prefix_raw.strip("/")) if prefix_raw.strip("/")
else "" so the value either is "" or begins with a single leading slash and has
no trailing slash; then update calls that currently do prefix + "/http" and
prefix + "/http/api-key" (the get_oauth_mcp calls and the Mount arguments) to
use f"{prefix}/http" and f"{prefix}/http/api-key" (or construct the path with
the normalized prefix), and keep Mount(prefix or "/", app=sse_app) as-is so
routes do not get double slashes; change the prefix assignment near where prefix
is defined and leave get_oauth_mcp, get_header_mcp, oauth_mcp, sse_mcp,
oauth_well_known, sse_well_known, and the Starlette routes/Mount entries
otherwise unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: db30d750-6489-470a-b2c3-a821ab3f05b4

📥 Commits

Reviewing files that changed from the base of the PR and between 24565ab and b5ad536.

📒 Files selected for processing (1)
  • plane_mcp/__main__.py

Prashant-Surya and others added 2 commits May 13, 2026 09:33
Allows deploying the HTTP server under an ingress subpath (e.g. /mcp) by
mounting the OAuth, header-auth, and SSE transports under the prefix and
passing the prefix into each OAuth provider's base_url so advertised
metadata URLs (issuer, authorize, token, register, resource) resolve to
the externally-visible path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Prashant-Surya Prashant-Surya force-pushed the feat/mcp-path-prefix branch from b5ad536 to 54ece08 Compare May 13, 2026 06:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant