[PR #5301] plugin/client: add mcp_stdio plugin #5235

Open
opened 2026-05-05 14:57:14 -06:00 by gitea-mirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/fatedier/frp/pull/5301
Author: @ailenshen
Created: 4/29/2026
Status: 🔄 Open

Base: devHead: feature/mcp-stdio-plugin


📝 Commits (3)

  • 5822468 plugin/client: add mcp_stdio plugin
  • 1f3d55a plugin/client: refactor mcp_stdio to use worker goroutine for child I/O
  • 85e9150 plugin/client: address mcp_stdio review findings

📊 Changes

6 files changed (+665 additions, -1 deletions)

View changed files

📝 README.md (+1 -1)
📝 conf/frpc_full_example.toml (+16 -0)
📝 pkg/config/v1/proxy_plugin.go (+27 -0)
📝 pkg/config/v1/validation/plugin.go (+12 -0)
pkg/plugin/client/mcp_stdio.go (+443 -0)
pkg/plugin/client/mcp_stdio_test.go (+166 -0)

📄 Description

WHY

Most MCP (Model Context Protocol) servers in the wild ship as stdio
binaries — npx -y @org/mcp-..., uvx ... and similar — and only
know how to speak JSON-RPC over stdin/stdout. To make them reachable
by remote MCP clients like Claude Desktop or the Anthropic API today,
you have to run a separate stdio↔HTTP translator (e.g. mcp-proxy)
locally alongside frpc.

This PR adds an mcp_stdio client plugin that folds that translation
into frpc itself, so a single frpc process is enough to expose any
number of stdio MCP servers as remote Streamable HTTP endpoints — no
extra translator process and no new runtime dependencies.

What it does

  • Spawns the configured stdio command lazily on the first request.
  • For each inbound HTTP POST, writes the body as a JSON-RPC line to
    the child's stdin and returns the next stdout line as the HTTP
    response. Notifications (frames without an id) are accepted with
    HTTP 202 and produce no stdio output.
  • Optional idleTimeoutSeconds: kill the child after N seconds of
    inactivity. The MCP initialize handshake is cached and replayed
    when the child is respawned, so sessions stay healthy across reaps
    and the next spawn picks up any updated package version on its own
    (useful with @latest style npx commands).

Configuration

```toml
proxies
name = "apple-notes"
type = "tcp"
remotePort = 3101
[proxies.plugin]
type = "mcp_stdio"
command = ["npx", "-y", "@modelcontextprotocol/server-everything"]
idleTimeoutSeconds = 300
```

Tested with

  • Real apple-notes and filesystem MCP servers end-to-end through a
    public frps, reachable from Claude Desktop and claude.ai.
  • Unit tests cover JSON-RPC classification, dispatch, notifications,
    and the respawn-with-replay path.
  • `golangci-lint run ./pkg/plugin/client/... ./pkg/config/v1/...`
    passes.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/fatedier/frp/pull/5301 **Author:** [@ailenshen](https://github.com/ailenshen) **Created:** 4/29/2026 **Status:** 🔄 Open **Base:** `dev` ← **Head:** `feature/mcp-stdio-plugin` --- ### 📝 Commits (3) - [`5822468`](https://github.com/fatedier/frp/commit/582246859df71ff097836cfcf2c5bb3cb662c67b) plugin/client: add mcp_stdio plugin - [`1f3d55a`](https://github.com/fatedier/frp/commit/1f3d55a012bec3e034aa8c5e2d9f4fde49f77cd5) plugin/client: refactor mcp_stdio to use worker goroutine for child I/O - [`85e9150`](https://github.com/fatedier/frp/commit/85e9150e62b4ad386d4a49d50d22005b0dd56788) plugin/client: address mcp_stdio review findings ### 📊 Changes **6 files changed** (+665 additions, -1 deletions) <details> <summary>View changed files</summary> 📝 `README.md` (+1 -1) 📝 `conf/frpc_full_example.toml` (+16 -0) 📝 `pkg/config/v1/proxy_plugin.go` (+27 -0) 📝 `pkg/config/v1/validation/plugin.go` (+12 -0) ➕ `pkg/plugin/client/mcp_stdio.go` (+443 -0) ➕ `pkg/plugin/client/mcp_stdio_test.go` (+166 -0) </details> ### 📄 Description ### WHY Most MCP (Model Context Protocol) servers in the wild ship as stdio binaries — `npx -y @org/mcp-...`, `uvx ...` and similar — and only know how to speak JSON-RPC over stdin/stdout. To make them reachable by remote MCP clients like Claude Desktop or the Anthropic API today, you have to run a separate stdio↔HTTP translator (e.g. `mcp-proxy`) locally alongside frpc. This PR adds an `mcp_stdio` client plugin that folds that translation into frpc itself, so a single frpc process is enough to expose any number of stdio MCP servers as remote Streamable HTTP endpoints — no extra translator process and no new runtime dependencies. ### What it does - Spawns the configured stdio command lazily on the first request. - For each inbound HTTP POST, writes the body as a JSON-RPC line to the child's stdin and returns the next stdout line as the HTTP response. Notifications (frames without an `id`) are accepted with HTTP 202 and produce no stdio output. - Optional `idleTimeoutSeconds`: kill the child after N seconds of inactivity. The MCP `initialize` handshake is cached and replayed when the child is respawned, so sessions stay healthy across reaps and the next spawn picks up any updated package version on its own (useful with `@latest` style npx commands). ### Configuration \`\`\`toml [[proxies]] name = "apple-notes" type = "tcp" remotePort = 3101 [proxies.plugin] type = "mcp_stdio" command = ["npx", "-y", "@modelcontextprotocol/server-everything"] idleTimeoutSeconds = 300 \`\`\` ### Tested with - Real apple-notes and filesystem MCP servers end-to-end through a public frps, reachable from Claude Desktop and claude.ai. - Unit tests cover JSON-RPC classification, dispatch, notifications, and the respawn-with-replay path. - \`golangci-lint run ./pkg/plugin/client/... ./pkg/config/v1/...\` passes. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
gitea-mirror added the
pull-request
label 2026-05-05 14:57:14 -06:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: github-starred/frp#5235
No description provided.