MohibShaikh

mcp-uplift

Community MohibShaikh
Updated

Run legacy MCP stdio servers behind the 2026-07-28 protocol. Translates the removed initialize handshake, sessions, and server-initiated requests.

mcp-uplift

MCP 2026-07-28 removed the initialization handshake, sessions, ping,logging configuration, resource subscriptions, and server-initiated requests.Older MCP servers still use those protocol features. mcp-uplift wraps onelegacy stdio server and presents it as a modern stateless server.

Security: wrapping a server executes that server with your operating-systempermissions. This bridge is not a sandbox. Only run packages and commands youtrust.

Demo

mcp-uplift translating a real legacy MCP server

The unmodified official @modelcontextprotocol/server-filesystem running behindthe 2026-07-28 protocol: server/discover is synthesized from the legacyhandshake, the server's own roots/list request becomes a keyed input_requiredresult, and answering it resumes the call and returns all 14 tools.

Usage

Run without installing:

npx mcp-uplift <legacy-command> [args...]

For example:

npx mcp-uplift npx -y @modelcontextprotocol/server-filesystem /tmp

The wrapped command receives a minimal environment by default. Forward a neededcredential explicitly, before --:

npx mcp-uplift --env BRAVE_API_KEY -- npx -y @modelcontextprotocol/server-brave-search

--inherit-env is available for compatibility but exposes every environmentvariable to the wrapped process. Run npx mcp-uplift --help for resource andtimeout controls.

Client configuration

Before, a client launches the legacy server directly:

{
  "mcpServers": {
    "files": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
    }
  }
}

After, launch the same command through mcp-uplift:

{
  "mcpServers": {
    "files": {
      "command": "npx",
      "args": ["mcp-uplift", "npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
    }
  }
}

Compatibility

Because 2026-07-28 is stateless, every request must carry its own envelope inparams._meta: the protocol version and the client's capabilities, plusoptional client identity. A request missing them is rejected rather thanguessed at.

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": { "roots": {} },
      "io.modelcontextprotocol/clientInfo": { "name": "my-client", "version": "1.0.0" }
    }
  }
}

If a wrapped server needs a capability the client did not declare, the bridgereturns -32021 naming what was required, rather than failing deep inside thecall.

Feature Behavior
Legacy initialize Performed once and exposed as server/discover
Per-request version metadata Validated as 2026-07-28, else -32022
Tools, prompts, and resources Forwarded with modern result and cache metadata
Legacy resource-not-found errors Mapped from -32002 to -32602
Sampling, elicitation, and roots requests Translated into multi-round-trip input_required results
Removed methods Rejected with -32601 method-not-found
Upstream failures Returned as -32603 internal errors

Answering a server's question

A legacy server can interrupt its own call to ask the client something.Because a modern client cannot receive that push, the bridge returns aninput_required result instead, keyed by request id:

{
  "resultType": "input_required",
  "inputRequests": {
    "ir_1000": {
      "method": "elicitation/create",
      "params": { "message": "Which environment?" }
    }
  },
  "requestState": "c0a2db7b-62bc-4420-b2e9-31da87f8f999"
}

To resume, send the same request again with the requestState you weregiven and an inputResponses map using those same keys. Each value is theresponse body itself, not wrapped in result:

{
  "name": "deploy",
  "arguments": {},
  "requestState": "c0a2db7b-62bc-4420-b2e9-31da87f8f999",
  "inputResponses": {
    "ir_1000": { "action": "accept", "content": { "env": "prod" } }
  }
}

The parked call then finishes and returns its ordinary "complete" result.Shapes per method: elicitation/create takes { action, content } withaction one of accept, decline, or cancel; roots/list takes{ roots: [...] }; sampling/createMessage takes { model, role, content }.

The resumed request must match the original, every key must be answered, andrequestState is single-use and expires, so an invalid resume is rejectedrather than half-applied.

Known limitations

Real-world validation confirmed discovery and tool listing against 39 distinctlegacy MCP packages. The official filesystem server also completed a realroots/list MRTR round trip and returned all 14 tools. These are testedexamples, not a guarantee that every server or session-dependent behavior canbe translated.

  • All legacy calls are serialized. Thelegacy protocol never links a sampling/createMessage, elicitation/create,or roots/list request back to the call that caused it, so the bridgekeeps one call in flight through all of its MRTR rounds. This favors correctattribution over throughput.
  • Legacy notifications have no home and are dropped. 2026-07-28 movedstreamed notifications onto a dedicated subscriptions/listen stream; thisbridge is a plain one-request-one-response stdio proxy and does notimplement that stream, so notifications/progress andnotifications/message from the wrapped server are discarded rather thandelivered.
  • Real-server checks require downloads and remain outside the offline suite.
  • The kill switch terminates the launched process tree on POSIX and Windows,but trusted code can deliberately daemonize into a new OS process session.Use an OS sandbox or container when stronger confinement is required.

Security reports

Do not include credentials or exploit details in a public issue. Report asuspected vulnerability privately through the repository's GitHub securityadvisory page. Ordinary bugs can use the public issue tracker.

MCP Server · Populars

MCP Server · New