Skip to content

feat: meta-MCP proxy — encapsulate authenticated HTTP MCP services and expose them over stdio #57

Description

@hartsock

Background

Multiple agents (Claude Code, Codex, and local tools like
newt-agent) can all
benefit from the same set of enterprise MCP services (Confluence, Jira,
GitLab, PagerDuty, OneDrive, Outlook, etc.). These services are hosted by
NVIDIA's MaaS platform (nvaihub.nvidia.com/maas/…) and require ECI OAuth
bearer tokens that are managed per-session by whichever agent harness is
currently running.

The problem: each agent has to independently solve authentication.
Claude Code has its own OAuth flow. Codex has another. A local Rust agent
like newt has neither, so all 15+ MaaS servers fail with HTTP 401 at startup
(see newt-agent#303).

Proposed feature

Extend modulex-mcp to act as a meta-MCP proxy — a single stdio-based
MCP server that:

  1. Holds credentials for downstream HTTP MCP services (ECI tokens, OAuth
    flows, static API keys, etc.)
  2. Proxies tool calls from any MCP client through to the real upstream
    service, injecting auth headers transparently
  3. Presents a unified tool surface to the calling agent over stdio,
    so clients never need to speak HTTP or manage tokens themselves
  4. Handles token lifecycle — TTL-aware refresh, OAuth PKCE flows,
    env-var token injection — all hidden from the client

Why modulex-mcp?

modulex-mcp already runs as a stdio server that aggregates multiple
tool surfaces (core, store, board, routines). The brokering
architecture is already present. Adding a proxy layer that forwards to
external HTTP MCP endpoints is a natural extension of this pattern.

Desired interface

Config (e.g. modulex.toml)

[[proxy.upstream]]
name    = "MaaS-Confluence"
url     = "https://nvaihub.nvidia.com/maas/confluence/mcp/"
auth    = { type = "bearer_env", env = "ECI_TOKEN" }

[[proxy.upstream]]
name    = "MaaS-Jira"
url     = "https://nvaihub.nvidia.com/maas/jira/mcp/"
auth    = { type = "bearer_env", env = "ECI_TOKEN" }

[[proxy.upstream]]
name    = "MaaS-Outlook"
url     = "https://maas.prd.astra.nvidia.com/maas/outlook/mcp"
auth    = { type = "oauth_pkce", client_id = "eci-prd-pub-9ab40d21-…", token_cache = "~/.modulex/outlook_token.json" }

Runtime behavior

  1. At startup, modulex-mcp reads the proxy config, connects to each upstream
    (using the configured auth), and fetches the upstream tool list
  2. Tools from each upstream are namespaced upstream__toolname and
    advertised to the downstream stdio client
  3. On tools/call, modulex-mcp forwards the call to the appropriate upstream
    with full auth headers, and relays the response back
  4. If an upstream token expires mid-session, modulex-mcp silently refreshes
    it (or prompts once via stderr) without any change to the client

Auth strategies to support

Strategy How it works
bearer_env Read $ENV_VAR at startup; inject as Authorization: Bearer
bearer_file Read first non-empty line of a file; inject as bearer
oauth_pkce Full browser-redirect PKCE flow; cache + refresh token
static_header Arbitrary Key: Value header from config

stdio-only transport

The downstream client (newt, or any other MCP consumer) speaks pure stdio
JSON-RPC to modulex-mcp. It never needs to:

  • Know the upstream URLs
  • Handle HTTP auth
  • Manage token TTL or refresh
  • Implement OAuth flows

This makes modulex-mcp an auth firewall: one place where credentials
are managed, one binary to configure, and every agent gets the same tool
surface for free.

Use cases

  1. newt-agent: configure modulex as a stdio MCP server; newt gets all 15
    MaaS tools through a single stdio connection with zero HTTP auth code in
    the Rust codebase. Fixes newt-agent#303.

  2. Codex: wire modulex-mcp as the single MCP server entry. All enterprise
    tools work without Codex needing per-service auth.

  3. Claude Code (sharing session): instead of each Claude Code session
    re-authenticating to 15 services, a long-lived modulex-mcp daemon holds
    refreshed tokens and all sessions use it. Token refresh happens once, not
    once per agent session.

  4. Any MCP client that can only do stdio: VSCode extensions, custom
    agents, or CLI tools that support stdio MCP but not OAuth HTTP.

Implementation sketch

Phase 1 — bearer_env / bearer_file (simplest, highest immediate value):

  • New [proxy] config section in modulex.toml
  • At init, spawn HTTP connections to each upstream, fetch tool lists
  • Expose proxied tools in tools/list response (namespaced)
  • Route tools/call → HTTP POST to upstream with injected header
  • Log upstream errors clearly; skip failed upstreams at startup without
    aborting the whole server

Phase 2 — OAuth PKCE:

  • Browser-redirect flow for first auth
  • Token serialized to ~/.modulex/<name>_token.json
  • Background refresh goroutine / task

Phase 3 — daemon mode:

  • modulex-mcp --daemon runs as a persistent process
  • Clients connect via Unix domain socket (still stdio framing)
  • Single token store shared by all agent sessions on the machine

What this is NOT

  • Not a security proxy / WAF — it trusts the credentials it holds
  • Not a remote service — it runs locally, the agent connects to it via stdio
  • Not trying to solve multi-user or server-side auth (that's MaaS's job)

References

  • newt-agent#303 — the immediate pain point this fixes
  • MCP spec: streamable-HTTP transport (2025-03-26)
  • ECI / MaaS: nvaihub.nvidia.com/maas/
  • MaaS-Outlook PKCE config: client_id = "eci-prd-pub-9ab40d21-b129-4075-8e82-842df4cb5045"

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions