This is an automated email from the ASF dual-hosted git repository.
shreemaan-abhishek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new a5ddc1cf7 feat!: remove the deprecated mcp-bridge plugin (#14019)
a5ddc1cf7 is described below
commit a5ddc1cf74a0a23c2c6f90f4d6b83021a84c8fdd
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Fri Oct 9 15:24:37 2026 +0800
feat!: remove the deprecated mcp-bridge plugin (#14019)
---
.github/workflows/cli.yml | 1 -
.gitignore | 1 -
Makefile | 6 -
README.md | 2 -
apisix/cli/config.lua | 1 -
apisix/cli/ngx_tpl.lua | 2 +-
apisix/plugins/mcp-bridge.lua | 173 -------------------
apisix/plugins/mcp/broker/shared_dict.lua | 90 ----------
apisix/plugins/mcp/broker/utils.lua | 21 ---
apisix/plugins/mcp/server.lua | 116 -------------
apisix/plugins/mcp/server_wrapper.lua | 106 ------------
apisix/plugins/mcp/transport/sse.lua | 44 -----
ci/init-common-test-service.sh | 4 -
ci/prepare_filesystem_mcp.sh | 47 -----
conf/config.yaml.example | 1 -
docs/en/latest/config.json | 1 -
docs/en/latest/plugins/mcp-bridge.md | 69 --------
t/admin/plugins.t | 1 -
t/cli/test_mcp_bridge.sh | 64 -------
t/cli/test_openapi_to_mcp.sh | 3 +-
t/plugin/mcp-bridge.spec.ts | 85 ---------
t/plugin/mcp-bridge.t | 59 -------
t/plugin/mcp/assets/bridge-list-tools.json | 266 -----------------------------
23 files changed, 2 insertions(+), 1161 deletions(-)
diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml
index 5995ae0f4..5a2e5bd14 100644
--- a/.github/workflows/cli.yml
+++ b/.github/workflows/cli.yml
@@ -52,7 +52,6 @@ jobs:
- name: Linux launch common services
run: |
project_compose_ci=ci/pod/docker-compose.common.yml make ci-env-up
- ./ci/prepare_filesystem_mcp.sh
- name: Linux Before install
run: sudo ./ci/${{ matrix.job_name }}_runner.sh before_install
diff --git a/.gitignore b/.gitignore
index 496e1f303..108ae9d91 100644
--- a/.gitignore
+++ b/.gitignore
@@ -84,7 +84,6 @@ t/plugin/grpc-web/grpc-web-server
t/plugin/grpc-web/node_modules/
pack
pack-v*-linux.tgz*
-t/plugin/mcp/servers/
t/node_modules/
# release tar package
diff --git a/Makefile b/Makefile
index b6df7615f..bcbb1e6bb 100644
--- a/Makefile
+++ b/Makefile
@@ -413,12 +413,6 @@ install: runtime
$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/plugins/ai-lakera-guard
$(ENV_INSTALL) apisix/plugins/ai-lakera-guard/*.lua
$(ENV_INST_LUADIR)/apisix/plugins/ai-lakera-guard
- $(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/plugins/mcp/broker
- $(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/plugins/mcp/transport
- $(ENV_INSTALL) apisix/plugins/mcp/*.lua
$(ENV_INST_LUADIR)/apisix/plugins/mcp
- $(ENV_INSTALL) apisix/plugins/mcp/broker/*.lua
$(ENV_INST_LUADIR)/apisix/plugins/mcp/broker
- $(ENV_INSTALL) apisix/plugins/mcp/transport/*.lua
$(ENV_INST_LUADIR)/apisix/plugins/mcp/transport
-
$(ENV_INSTALL) -d
$(ENV_INST_LUADIR)/apisix/plugins/openapi-to-mcp/openapi
$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/plugins/openapi-to-mcp/tools
$(ENV_INSTALL) -d
$(ENV_INST_LUADIR)/apisix/plugins/openapi-to-mcp/transport
diff --git a/README.md b/README.md
index 8f3179d4d..13922c2c1 100644
--- a/README.md
+++ b/README.md
@@ -65,8 +65,6 @@ APISIX can serve as an **[AI
Gateway](https://apisix.apache.org/ai-gateway/)** t
- **Token-based rate limiting** to control cost and protect upstream models.
- **Robust security** for authentication, authorization, and traffic control
on AI workloads.
-APISIX also provides the
[`mcp-bridge`](https://apisix.apache.org/blog/2025/04/21/host-mcp-server-with-api-gateway/)
plugin to seamlessly convert stdio-based MCP servers to scalable HTTP SSE
services.
-
## Get Started
Install and run APISIX with a single command using the quickstart script
(requires [Docker](https://docs.docker.com/get-docker/)):
diff --git a/apisix/cli/config.lua b/apisix/cli/config.lua
index ab034eeea..b019dbbe1 100644
--- a/apisix/cli/config.lua
+++ b/apisix/cli/config.lua
@@ -273,7 +273,6 @@ local _M = {
"response-rewrite",
"openapi-to-mcp",
"oas-validator",
- "mcp-bridge",
"degraphql",
"kafka-proxy",
"websocket-proxy",
diff --git a/apisix/cli/ngx_tpl.lua b/apisix/cli/ngx_tpl.lua
index 8688fcfe2..e7262a354 100644
--- a/apisix/cli/ngx_tpl.lua
+++ b/apisix/cli/ngx_tpl.lua
@@ -512,7 +512,7 @@ http {
lua_shared_dict ext-plugin {* http.lua_shared_dict["ext-plugin"] *}; #
cache for ext-plugin
{% end %}
- {% if enabled_plugins["mcp-bridge"] or enabled_plugins["openapi-to-mcp"]
then %}
+ {% if enabled_plugins["openapi-to-mcp"] then %}
lua_shared_dict mcp-session {* http.lua_shared_dict["mcp-session"] *}; #
cache for mcp-session
{% end %}
diff --git a/apisix/plugins/mcp-bridge.lua b/apisix/plugins/mcp-bridge.lua
deleted file mode 100644
index a73d94393..000000000
--- a/apisix/plugins/mcp-bridge.lua
+++ /dev/null
@@ -1,173 +0,0 @@
---
--- Licensed to the Apache Software Foundation (ASF) under one or more
--- contributor license agreements. See the NOTICE file distributed with
--- this work for additional information regarding copyright ownership.
--- The ASF licenses this file to You under the Apache License, Version 2.0
--- (the "License"); you may not use this file except in compliance with
--- the License. You may obtain a copy of the License at
---
--- http://www.apache.org/licenses/LICENSE-2.0
---
--- Unless required by applicable law or agreed to in writing, software
--- distributed under the License is distributed on an "AS IS" BASIS,
--- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--- See the License for the specific language governing permissions and
--- limitations under the License.
---
-local unpack = unpack
-local ngx = ngx
-local thread_spawn = ngx.thread.spawn
-local thread_kill = ngx.thread.kill
-local worker_exiting = ngx.worker.exiting
-local resty_signal = require("resty.signal")
-local core = require("apisix.core")
-local pipe = require("ngx.pipe")
-
-local mcp_server_wrapper = require("apisix.plugins.mcp.server_wrapper")
-
-local schema = {
- type = "object",
- properties = {
- base_uri = {
- type = "string",
- minLength = 1,
- default = "",
- },
- command = {
- type = "string",
- minLength = 1,
- },
- args = {
- type = "array",
- items = {
- type = "string",
- },
- minItems = 0,
- },
- },
- required = {
- "command"
- },
-}
-
-local plugin_name = "mcp-bridge"
-
-local _M = {
- version = 0.1,
- priority = 510,
- name = plugin_name,
- schema = schema,
-}
-
-
-function _M.check_schema(conf, schema_type)
- return core.schema.check(schema, conf)
-end
-
-
-local function on_connect(conf, ctx)
- return function(additional)
- local proc, err = pipe.spawn({conf.command, unpack(conf.args or {})})
- if not proc then
- core.log.error("failed to spawn mcp process: ", err)
- return 500
- end
- proc:set_timeouts(nil, 100, 100)
- ctx.mcp_bridge_proc = proc
-
- local server = additional.server
-
- -- ngx_pipe is a yield operation, so we no longer need
- -- to explicitly yield to other threads by ngx_sleep
- ctx.mcp_bridge_proc_event_loop = thread_spawn(function ()
- local stdout_partial, stderr_partial, need_exit
- while not worker_exiting() do
- -- read all the messages in stdout's pipe, line by line
- -- if there is an incomplete message it is buffered and
- -- spliced before the next message
- repeat
- local line, _
- line, _, stdout_partial = proc:stdout_read_line()
- if line then
- local ok, err = server.transport:send(
- stdout_partial and stdout_partial .. line or line
- )
- if not ok then
- core.log.info("session ", server.session_id,
- " exit, failed to send response
message: ", err)
- need_exit = true
- break
- end
- stdout_partial = nil -- luacheck: ignore
- end
- until not line
- if need_exit then
- break
- end
-
- repeat
- local line, _
- line, _, stderr_partial = proc:stderr_read_line()
- if line then
- local ok, err = server.transport:send(
-
'{"jsonrpc":"2.0","method":"notifications/stderr","params":{"content":"'
- .. (stderr_partial and stderr_partial .. line or
line) .. '"}}')
- if not ok then
- core.log.info("session ", server.session_id,
- " exit, failed to send response
message: ", err)
- need_exit = true
- break
- end
- stderr_partial = "" -- luacheck: ignore
- end
- until not line
- if need_exit then
- break
- end
- end
- end)
- end
-end
-
-
-local function on_client_message(conf, ctx)
- return function(message, additional)
- core.log.info("session ", additional.server.session_id,
- " send message to mcp server: ", additional.raw)
- ctx.mcp_bridge_proc:write(additional.raw .. "\n")
- end
-end
-
-
-local function on_disconnect(conf, ctx)
- return function()
- if ctx.mcp_bridge_proc_event_loop then
- thread_kill(ctx.mcp_bridge_proc_event_loop)
- ctx.mcp_bridge_proc_event_loop = nil
- end
-
- local proc = ctx.mcp_bridge_proc
- if proc then
- proc:shutdown("stdin")
- proc:wait()
- local _, err = proc:wait() -- check if process not exited then
kill it
- if err ~= "exited" then
- proc:kill(resty_signal.signum("KILL") or 9)
- end
- end
- end
-end
-
-
-function _M.access(conf, ctx)
- return mcp_server_wrapper.access(conf, ctx, {
- event_handler = {
- on_connect = on_connect(conf, ctx),
- on_client_message = on_client_message(conf, ctx),
- on_disconnect = on_disconnect(conf, ctx),
- },
- })
-end
-
-
-return _M
diff --git a/apisix/plugins/mcp/broker/shared_dict.lua
b/apisix/plugins/mcp/broker/shared_dict.lua
deleted file mode 100644
index 83e3d86d7..000000000
--- a/apisix/plugins/mcp/broker/shared_dict.lua
+++ /dev/null
@@ -1,90 +0,0 @@
---
--- Licensed to the Apache Software Foundation (ASF) under one or more
--- contributor license agreements. See the NOTICE file distributed with
--- this work for additional information regarding copyright ownership.
--- The ASF licenses this file to You under the Apache License, Version 2.0
--- (the "License"); you may not use this file except in compliance with
--- the License. You may obtain a copy of the License at
---
--- http://www.apache.org/licenses/LICENSE-2.0
---
--- Unless required by applicable law or agreed to in writing, software
--- distributed under the License is distributed on an "AS IS" BASIS,
--- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--- See the License for the specific language governing permissions and
--- limitations under the License.
---
-local type = type
-local setmetatable = setmetatable
-local ngx = ngx
-local ngx_sleep = ngx.sleep
-local thread_spawn = ngx.thread.spawn
-local thread_kill = ngx.thread.kill
-local worker_exiting = ngx.worker.exiting
-local shared_dict = ngx.shared["mcp-session"] -- TODO: rename to something
like mcp-broker
-local core = require("apisix.core")
-local broker_utils = require("apisix.plugins.mcp.broker.utils")
-
-local _M = {}
-local mt = { __index = _M }
-
-
-local STORAGE_SUFFIX_QUEUE = ":queue"
-
-
-function _M.new(opts)
- return setmetatable({
- session_id = opts.session_id,
- event_handler = {}
- }, mt)
-end
-
-
-function _M.on(self, event, cb)
- self.event_handler[event] = cb
-end
-
-
-function _M.push(self, message)
- if not message then
- return nil, "message is nil"
- end
- local ok, err = shared_dict:rpush(self.session_id .. STORAGE_SUFFIX_QUEUE,
message)
- if not ok then
- return nil, "failed to push message to queue: " .. err
- end
- return true
-end
-
-
-function _M.start(self)
- self.thread = thread_spawn(function()
- while not worker_exiting() do
- local item, err = shared_dict:lpop(self.session_id ..
STORAGE_SUFFIX_QUEUE)
- if err then
- core.log.info("session ", self.session_id,
- " exit, failed to pop message from queue: ", err)
- break
- end
- if item and type(item) == "string"
- and type(self.event_handler[broker_utils.EVENT_MESSAGE]) ==
"function" then
- self.event_handler[broker_utils.EVENT_MESSAGE](
- core.json.decode(item), { raw = item }
- )
- end
-
- ngx_sleep(0.1) -- yield to other light threads
- end
- end)
-end
-
-
-function _M.close(self)
- if self.thread then
- thread_kill(self.thread)
- self.thread = nil
- end
-end
-
-
-return _M
diff --git a/apisix/plugins/mcp/broker/utils.lua
b/apisix/plugins/mcp/broker/utils.lua
deleted file mode 100644
index ded12ae51..000000000
--- a/apisix/plugins/mcp/broker/utils.lua
+++ /dev/null
@@ -1,21 +0,0 @@
---
--- Licensed to the Apache Software Foundation (ASF) under one or more
--- contributor license agreements. See the NOTICE file distributed with
--- this work for additional information regarding copyright ownership.
--- The ASF licenses this file to You under the Apache License, Version 2.0
--- (the "License"); you may not use this file except in compliance with
--- the License. You may obtain a copy of the License at
---
--- http://www.apache.org/licenses/LICENSE-2.0
---
--- Unless required by applicable law or agreed to in writing, software
--- distributed under the License is distributed on an "AS IS" BASIS,
--- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--- See the License for the specific language governing permissions and
--- limitations under the License.
---
-local _M = {}
-
-_M.EVENT_MESSAGE = "message"
-
-return _M
diff --git a/apisix/plugins/mcp/server.lua b/apisix/plugins/mcp/server.lua
deleted file mode 100644
index 11a41b9de..000000000
--- a/apisix/plugins/mcp/server.lua
+++ /dev/null
@@ -1,116 +0,0 @@
---
--- Licensed to the Apache Software Foundation (ASF) under one or more
--- contributor license agreements. See the NOTICE file distributed with
--- this work for additional information regarding copyright ownership.
--- The ASF licenses this file to You under the Apache License, Version 2.0
--- (the "License"); you may not use this file except in compliance with
--- the License. You may obtain a copy of the License at
---
--- http://www.apache.org/licenses/LICENSE-2.0
---
--- Unless required by applicable law or agreed to in writing, software
--- distributed under the License is distributed on an "AS IS" BASIS,
--- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--- See the License for the specific language governing permissions and
--- limitations under the License.
---
-local require = require
-local setmetatable = setmetatable
-local ngx = ngx
-local ngx_sleep = ngx.sleep
-local thread_spwan = ngx.thread.spawn
-local thread_wait = ngx.thread.wait
-local thread_kill = ngx.thread.kill
-local worker_exiting = ngx.worker.exiting
-local core = require("apisix.core")
-local broker_utils = require("apisix.plugins.mcp.broker.utils")
-
-
-local _M = {}
-local mt = { __index = _M }
-
-
-_M.EVENT_CLIENT_MESSAGE = "event:client_message"
-
-
--- TODO: ping requester and handler
-function _M.new(opts)
- local session_id = opts.session_id or core.id.gen_uuid_v4()
-
- -- TODO: configurable broker type
- local message_broker =
require("apisix.plugins.mcp.broker.shared_dict").new({
- session_id = session_id,
- })
-
- -- TODO: configurable transport type
- local transport = require("apisix.plugins.mcp.transport.sse").new()
-
- local obj = setmetatable({
- opts = opts,
- session_id = session_id,
- next_ping_id = 0,
- transport = transport,
- message_broker = message_broker,
- event_handler = {},
- need_exit = false,
- }, mt)
-
- message_broker:on(broker_utils.EVENT_MESSAGE, function (message,
additional)
- if obj.event_handler[_M.EVENT_CLIENT_MESSAGE] then
- obj.event_handler[_M.EVENT_CLIENT_MESSAGE](message, additional)
- end
- end)
-
- return obj
-end
-
-
-function _M.on(self, event, cb)
- self.event_handler[event] = cb
-end
-
-
-function _M.start(self)
- self.message_broker:start()
-
- -- ping loop
- local ping = thread_spwan(function()
- while not worker_exiting() do
- if self.need_exit then
- break
- end
-
- self.next_ping_id = self.next_ping_id + 1
- local ok, err = self.transport:send(
- '{"jsonrpc": "2.0","method": "ping","id":"ping:' ..
self.next_ping_id .. '"}')
- if not ok then
- core.log.info("session ", self.session_id,
- " exit, failed to send ping message: ", err)
- self.need_exit = true
- break
- end
- ngx_sleep(30)
- end
- end)
- thread_wait(ping)
- thread_kill(ping)
-end
-
-
-function _M.close(self)
- if self.message_broker then
- self.message_broker:close()
- end
-end
-
-
-function _M.push_message(self, message)
- local ok, err = self.message_broker:push(message)
- if not ok then
- return nil, "failed to push message to broker: " .. err
- end
- return true
-end
-
-
-return _M
diff --git a/apisix/plugins/mcp/server_wrapper.lua
b/apisix/plugins/mcp/server_wrapper.lua
deleted file mode 100644
index 5b0ed8831..000000000
--- a/apisix/plugins/mcp/server_wrapper.lua
+++ /dev/null
@@ -1,106 +0,0 @@
---
--- Licensed to the Apache Software Foundation (ASF) under one or more
--- contributor license agreements. See the NOTICE file distributed with
--- this work for additional information regarding copyright ownership.
--- The ASF licenses this file to You under the Apache License, Version 2.0
--- (the "License"); you may not use this file except in compliance with
--- the License. You may obtain a copy of the License at
---
--- http://www.apache.org/licenses/LICENSE-2.0
---
--- Unless required by applicable law or agreed to in writing, software
--- distributed under the License is distributed on an "AS IS" BASIS,
--- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--- See the License for the specific language governing permissions and
--- limitations under the License.
---
-local ngx = ngx
-local ngx_exit = ngx.exit
-local re_match = ngx.re.match
-local core = require("apisix.core")
-local mcp_server = require("apisix.plugins.mcp.server")
-
-local _M = {}
-
-local V241105_ENDPOINT_SSE = "sse"
-local V241105_ENDPOINT_MESSAGE = "message"
-
-
-local function sse_handler(conf, ctx, opts)
- -- send SSE headers and first chunk
- core.response.set_header("Content-Type", "text/event-stream")
- core.response.set_header("Cache-Control", "no-cache")
-
- local server = opts.server
-
- -- send endpoint event to advertise the message endpoint
- server.transport:send(conf.base_uri .. "/message?sessionId=" ..
server.session_id, "endpoint")
-
- if opts.event_handler and opts.event_handler.on_client_message then
- server:on(mcp_server.EVENT_CLIENT_MESSAGE, function(message,
additional)
- additional.server = server
- opts.event_handler.on_client_message(message, additional)
- end)
- end
-
- if opts.event_handler and opts.event_handler.on_connect then
- local code, body = opts.event_handler.on_connect({ server = server })
- if code then
- return code, body
- end
- server:start() -- this is a sync call that only returns when the
client disconnects
- end
-
- if opts.event_handler.on_disconnect then
- opts.event_handler.on_disconnect({ server = server })
- server:close()
- end
-
- ngx_exit(0) -- exit current phase, skip the upstream module
-end
-
-
-local function message_handler(conf, ctx, opts)
- local body = core.request.get_body(nil, ctx)
- if not body then
- return 400
- end
-
- local ok, err = opts.server:push_message(body)
- if not ok then
- core.log.error("failed to add task to queue: ", err)
- return 500
- end
-
- return 202
-end
-
-
-function _M.access(conf, ctx, opts)
- local m, err = re_match(ctx.var.uri, "^" .. conf.base_uri .. "/(.*)", "jo")
- if err then
- core.log.info("failed to mcp base uri: ", err)
- return core.response.exit(404)
- end
- local action = m and m[1] or false
- if not action then
- return core.response.exit(404)
- end
-
- if action == V241105_ENDPOINT_SSE and core.request.get_method() == "GET"
then
- opts.server = mcp_server.new({})
- return sse_handler(conf, ctx, opts)
- end
-
- if action == V241105_ENDPOINT_MESSAGE and core.request.get_method() ==
"POST" then
- -- TODO: check ctx.var.arg_sessionId
- -- recover server instead of create
- opts.server = mcp_server.new({ session_id = ctx.var.arg_sessionId })
- return core.response.exit(message_handler(conf, ctx, opts))
- end
-
- return core.response.exit(404)
-end
-
-
-return _M
diff --git a/apisix/plugins/mcp/transport/sse.lua
b/apisix/plugins/mcp/transport/sse.lua
deleted file mode 100644
index 83d72a189..000000000
--- a/apisix/plugins/mcp/transport/sse.lua
+++ /dev/null
@@ -1,44 +0,0 @@
---
--- Licensed to the Apache Software Foundation (ASF) under one or more
--- contributor license agreements. See the NOTICE file distributed with
--- this work for additional information regarding copyright ownership.
--- The ASF licenses this file to You under the Apache License, Version 2.0
--- (the "License"); you may not use this file except in compliance with
--- the License. You may obtain a copy of the License at
---
--- http://www.apache.org/licenses/LICENSE-2.0
---
--- Unless required by applicable law or agreed to in writing, software
--- distributed under the License is distributed on an "AS IS" BASIS,
--- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--- See the License for the specific language governing permissions and
--- limitations under the License.
---
-local setmetatable = setmetatable
-local type = type
-local ngx = ngx
-local ngx_print = ngx.print
-local ngx_flush = ngx.flush
-local core = require("apisix.core")
-
-local _M = {}
-local mt = { __index = _M }
-
-
-function _M.new()
- return setmetatable({}, mt)
-end
-
-
-function _M.send(self, message, event_type)
- local data = type(message) == "table" and core.json.encode(message) or
message
- local ok, err = ngx_print("event: " .. (event_type or "message") ..
- "\ndata: " .. data .. "\n\n")
- if not ok then
- return ok, "failed to write buffer: " .. err
- end
- return ngx_flush(true)
-end
-
-
-return _M
diff --git a/ci/init-common-test-service.sh b/ci/init-common-test-service.sh
index dc34f7550..8b3dbd33a 100755
--- a/ci/init-common-test-service.sh
+++ b/ci/init-common-test-service.sh
@@ -50,7 +50,3 @@ docker exec -i localstack sh -c "awslocal secretsmanager
create-secret --name ap
docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name
apisix-mysql --description 'APISIX Secret' --secret-string 'secret'"
docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name
'john/secret' --description 'APISIX Secret' --secret-string
'{\"john-key-auth\":\"value\"}'"
docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name
'apisix/full/path' --description 'APISIX Secret' --secret-string 'full-value'"
-
-# prepare filesystem mcp server
-sleep 3s
-./ci/prepare_filesystem_mcp.sh
diff --git a/ci/prepare_filesystem_mcp.sh b/ci/prepare_filesystem_mcp.sh
deleted file mode 100755
index 51613bdb2..000000000
--- a/ci/prepare_filesystem_mcp.sh
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env bash
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-#!/usr/bin/env bash
-set -euo pipefail
-
-SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
-REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
-
-VERSION="2025.7.1"
-URL="https://github.com/modelcontextprotocol/servers/archive/refs/tags/${VERSION}.tar.gz"
-
-WORKDIR="$(mktemp -d)"
-DEST_DIR="${REPO_ROOT}/t/plugin/mcp/servers"
-
-curl -fsSL --retry 3 --retry-delay 5 "${URL}" | tar -xz -C "${WORKDIR}"
-
-rm -rf "${DEST_DIR}"
-mkdir -p "$(dirname "${DEST_DIR}")"
-
-cp -R "${WORKDIR}/servers-${VERSION}" "${DEST_DIR}"
-
-(
- cd "${DEST_DIR}"
- npm install
- # Note: Although dlx specifies the package version, it does not use a
lockfile,
- # so dependency resolution is not reproducible. Only the package-lock.json
included
- # in the release package can ensure that the entire dependency tree is fully
locked.
- npm run build -w @modelcontextprotocol/server-filesystem
-)
-
-echo "[OK] filesystem MCP ready: ${DEST_DIR}"
diff --git a/conf/config.yaml.example b/conf/config.yaml.example
index 6a1361aec..ded74f6ce 100644
--- a/conf/config.yaml.example
+++ b/conf/config.yaml.example
@@ -620,7 +620,6 @@ plugins: # plugin list (sorted by
priority)
- response-rewrite # priority: 899
- openapi-to-mcp # priority: 540
- oas-validator # priority: 512
- - mcp-bridge # priority: 510
- degraphql # priority: 509
- kafka-proxy # priority: 508
#- dubbo-proxy # priority: 507
diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json
index bf2424a50..e1e0b3152 100644
--- a/docs/en/latest/config.json
+++ b/docs/en/latest/config.json
@@ -256,7 +256,6 @@
"plugins/kafka-proxy",
"plugins/websocket-proxy",
"plugins/http-dubbo",
- "plugins/mcp-bridge",
"plugins/openapi-to-mcp"
]
}
diff --git a/docs/en/latest/plugins/mcp-bridge.md
b/docs/en/latest/plugins/mcp-bridge.md
deleted file mode 100644
index 99ebbdf43..000000000
--- a/docs/en/latest/plugins/mcp-bridge.md
+++ /dev/null
@@ -1,69 +0,0 @@
----
-title: mcp-bridge
-keywords:
- - Apache APISIX
- - API Gateway
- - Plugin
- - MCP Bridge
- - mcp-bridge
-description: This document contains information about the Apache APISIX
mcp-bridge Plugin, which bridges an SSE/HTTP client to a stdio-based MCP server
process.
----
-
-<!--
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
--->
-
-## Description
-
-The `mcp-bridge` Plugin bridges an HTTP client speaking the [Model Context
Protocol](https://modelcontextprotocol.io/) (MCP) over SSE to a stdio-based MCP
server. APISIX spawns the configured command as a backend process and relays
messages between the SSE connection and the process's standard input and output.
-
-:::warning
-
-The `mcp-bridge` Plugin is deprecated and will be removed in a future release.
We recommend not using it for new deployments.
-
-:::
-
-## Attributes
-
-| Name | Type | Required | Default | Description
|
-|----------|-----------------|----------|---------|----------------------------------------------------------------|
-| command | string | True | | Command used to launch the
MCP server process, e.g. `npx`. |
-| args | array[string] | False | | Arguments passed to the
command. |
-| base_uri | string | False | "" | Base URI used to build the
SSE and message endpoint paths. |
-
-## Example usage
-
-Create a Route with the `mcp-bridge` Plugin, pointing `command` and `args` at
the MCP server you want to expose:
-
-```shell
-curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
- -H "X-API-KEY: ${admin_key}" \
- -d '{
- "id": "mcp-route",
- "uri": "/mcp/*",
- "plugins": {
- "mcp-bridge": {
- "base_uri": "/mcp",
- "command": "npx",
- "args": ["-y", "@modelcontextprotocol/server-filesystem", "/"]
- }
- }
- }'
-```
-
-An MCP client can then connect to the SSE endpoint at `/mcp/sse` and exchange
messages through `/mcp/message`.
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index b15b64d69..7edaf7827 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -130,7 +130,6 @@ response-rewrite
openapi-to-mcp
oas-validator
websocket-proxy
-mcp-bridge
degraphql
kafka-proxy
grpc-transcode
diff --git a/t/cli/test_mcp_bridge.sh b/t/cli/test_mcp_bridge.sh
deleted file mode 100755
index ee0fc5465..000000000
--- a/t/cli/test_mcp_bridge.sh
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/env bash
-
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-. ./t/cli/common.sh
-
-# schema validation
-echo '
-apisix:
- node_listen: 1984
- enable_admin: false
-deployment:
- role: data_plane
- role_data_plane:
- config_provider: yaml
-plugins:
- - mcp-bridge
-' > conf/config.yaml
-
-echo '
-routes:
- -
- uri: /mcp/*
- plugins:
- mcp-bridge:
- base_uri: /mcp
- command: node
- args:
- - "t/plugin/mcp/servers/src/filesystem/dist/index.js"
- - "/"
-#END
-' > conf/apisix.yaml
-
-make init
-make stop || true
-sleep 0.5
-make run
-sleep 1
-
-# run the mcp client test
-pushd t
-if ! timeout 60 pnpm test plugin/mcp-bridge.spec.ts 2>&1; then
- echo "failed: mcp-bridge client test failed"
- popd
- exit 1
-fi
-popd
-
-echo "passed: mcp-bridge client test"
diff --git a/t/cli/test_openapi_to_mcp.sh b/t/cli/test_openapi_to_mcp.sh
index 3aef7dde8..74043cee3 100755
--- a/t/cli/test_openapi_to_mcp.sh
+++ b/t/cli/test_openapi_to_mcp.sh
@@ -18,8 +18,7 @@
. ./t/cli/common.sh
-# openapi-to-mcp keeps its SSE sessions in the mcp-session shared dict, which
-# is also what mcp-bridge uses. Enabling either one declares it.
+# openapi-to-mcp keeps its SSE sessions in the mcp-session shared dict.
echo '
plugins:
diff --git a/t/plugin/mcp-bridge.spec.ts b/t/plugin/mcp-bridge.spec.ts
deleted file mode 100644
index 0147e6266..000000000
--- a/t/plugin/mcp-bridge.spec.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import { readFileSync } from 'node:fs';
-import { unlink, writeFile } from 'node:fs/promises';
-
-import { Client } from '@modelcontextprotocol/sdk/client/index.js';
-import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
-import { resolve } from 'node:path';
-
-
-const tools = JSON.parse(
- readFileSync(resolve(`./plugin/mcp/assets/bridge-list-tools.json`), 'utf-8'),
-);
-const sseEndpoint = new URL('http://localhost:1984/mcp/sse');
-
-describe('mcp-bridge', () => {
- let client: Client;
-
- beforeEach(async () => {
- client = new Client({ name: 'apisix-e2e-test', version: '1.0.0' });
- await expect(
- client.connect(new SSEClientTransport(sseEndpoint)),
- ).resolves.not.toThrow();
- });
-
- afterEach(() => expect(client.close()).resolves.not.toThrow());
-
- it('should list tools', () =>
- expect(client.listTools()).resolves.toMatchObject(tools));
-
- it('should call tool', async () => {
- const result = await client.callTool({
- name: 'list_directory',
- arguments: { path: '/' },
- });
- expect((result.content as { text: string }[])[0].text).toContain('[DIR] ');
- });
-
- it('should call both clients at the same time', async () => {
- // write test file
- await writeFile('/tmp/test.txt', 'test file');
-
- // create client2
- const client2 = new Client({ name: 'apisix-e2e-test', version: '1.0.0' });
- await expect(
- client2.connect(new SSEClientTransport(sseEndpoint)),
- ).resolves.not.toThrow();
-
- // list tools both clients
- await expect(client.listTools()).resolves.toMatchObject(tools);
- await expect(client2.listTools()).resolves.toMatchObject(tools);
-
- // list directory both clients
- const result1 = await client.callTool({
- name: 'list_directory',
- arguments: { path: '/' },
- });
- const result2 = await client2.callTool({
- name: 'list_directory',
- arguments: { path: '/tmp' },
- });
- expect((result1.content as { text: string }[])[0].text).toContain('[DIR]
home');
- expect((result2.content as { text: string }[])[0].text).toContain('[FILE]
test.txt');
-
- // close client2
- await expect(client2.close()).resolves.not.toThrow();
-
- // remove test file
- await unlink('/tmp/test.txt');
- });
-});
diff --git a/t/plugin/mcp-bridge.t b/t/plugin/mcp-bridge.t
deleted file mode 100644
index 25369f02f..000000000
--- a/t/plugin/mcp-bridge.t
+++ /dev/null
@@ -1,59 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-use t::APISIX 'no_plan';
-
-repeat_each(1);
-no_long_string();
-no_root_location();
-
-add_block_preprocessor(sub {
- my ($block) = @_;
-
- if (!defined $block->request) {
- $block->set_value("request", "GET /t");
- }
-});
-
-run_tests();
-
-__DATA__
-
-=== TEST 1: sanity
---- config
- location /t {
- content_by_lua_block {
- local test_cases = {
- {command = "npx"},
- {},
- {command = 123},
- {command = "npx", args = { "-y", "test" }},
- {command = "npx", args = "test"},
- }
- local plugin = require("apisix.plugins.mcp-bridge")
-
- for _, case in ipairs(test_cases) do
- local ok, err = plugin.check_schema(case)
- ngx.say(ok and "done" or err)
- end
- }
- }
---- response_body
-done
-property "command" is required
-property "command" validation failed: wrong type: expected string, got number
-done
-property "args" validation failed: wrong type: expected array, got string
diff --git a/t/plugin/mcp/assets/bridge-list-tools.json
b/t/plugin/mcp/assets/bridge-list-tools.json
deleted file mode 100644
index 1d8216b8c..000000000
--- a/t/plugin/mcp/assets/bridge-list-tools.json
+++ /dev/null
@@ -1,266 +0,0 @@
-{
- "tools": [
- {
- "name": "read_file",
- "description": "Read the complete contents of a file from the file
system. Handles various text encodings and provides detailed error messages if
the file cannot be read. Use this tool when you need to examine the contents of
a single file. Use the 'head' parameter to read only the first N lines of a
file, or the 'tail' parameter to read only the last N lines of a file. Only
works within allowed directories.",
- "inputSchema": {
- "type": "object",
- "properties": {
- "path": {
- "type": "string"
- },
- "tail": {
- "type": "number",
- "description": "If provided, returns only the last N lines of the
file"
- },
- "head": {
- "type": "number",
- "description": "If provided, returns only the first N lines of the
file"
- }
- },
- "required": [
- "path"
- ],
- "additionalProperties": false,
- "$schema": "http://json-schema.org/draft-07/schema#"
- }
- },
- {
- "name": "read_multiple_files",
- "description": "Read the contents of multiple files simultaneously. This
is more efficient than reading files one by one when you need to analyze or
compare multiple files. Each file's content is returned with its path as a
reference. Failed reads for individual files won't stop the entire operation.
Only works within allowed directories.",
- "inputSchema": {
- "type": "object",
- "properties": {
- "paths": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- },
- "required": [
- "paths"
- ],
- "additionalProperties": false,
- "$schema": "http://json-schema.org/draft-07/schema#"
- }
- },
- {
- "name": "write_file",
- "description": "Create a new file or completely overwrite an existing
file with new content. Use with caution as it will overwrite existing files
without warning. Handles text content with proper encoding. Only works within
allowed directories.",
- "inputSchema": {
- "type": "object",
- "properties": {
- "path": {
- "type": "string"
- },
- "content": {
- "type": "string"
- }
- },
- "required": [
- "path",
- "content"
- ],
- "additionalProperties": false,
- "$schema": "http://json-schema.org/draft-07/schema#"
- }
- },
- {
- "name": "edit_file",
- "description": "Make line-based edits to a text file. Each edit replaces
exact line sequences with new content. Returns a git-style diff showing the
changes made. Only works within allowed directories.",
- "inputSchema": {
- "type": "object",
- "properties": {
- "path": {
- "type": "string"
- },
- "edits": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "oldText": {
- "type": "string",
- "description": "Text to search for - must match exactly"
- },
- "newText": {
- "type": "string",
- "description": "Text to replace with"
- }
- },
- "required": [
- "oldText",
- "newText"
- ],
- "additionalProperties": false
- }
- },
- "dryRun": {
- "type": "boolean",
- "default": false,
- "description": "Preview changes using git-style diff format"
- }
- },
- "required": [
- "path",
- "edits"
- ],
- "additionalProperties": false,
- "$schema": "http://json-schema.org/draft-07/schema#"
- }
- },
- {
- "name": "create_directory",
- "description": "Create a new directory or ensure a directory exists. Can
create multiple nested directories in one operation. If the directory already
exists, this operation will succeed silently. Perfect for setting up directory
structures for projects or ensuring required paths exist. Only works within
allowed directories.",
- "inputSchema": {
- "type": "object",
- "properties": {
- "path": {
- "type": "string"
- }
- },
- "required": [
- "path"
- ],
- "additionalProperties": false,
- "$schema": "http://json-schema.org/draft-07/schema#"
- }
- },
- {
- "name": "list_directory",
- "description": "Get a detailed listing of all files and directories in a
specified path. Results clearly distinguish between files and directories with
[FILE] and [DIR] prefixes. This tool is essential for understanding directory
structure and finding specific files within a directory. Only works within
allowed directories.",
- "inputSchema": {
- "type": "object",
- "properties": {
- "path": {
- "type": "string"
- }
- },
- "required": [
- "path"
- ],
- "additionalProperties": false,
- "$schema": "http://json-schema.org/draft-07/schema#"
- }
- },
- {
- "name": "list_directory_with_sizes",
- "description": "Get a detailed listing of all files and directories in a
specified path, including sizes. Results clearly distinguish between files and
directories with [FILE] and [DIR] prefixes. This tool is useful for
understanding directory structure and finding specific files within a
directory. Only works within allowed directories.",
- "inputSchema": {
- "type": "object",
- "properties": {
- "path": {
- "type": "string"
- },
- "sortBy": {
- "type": "string",
- "enum": [
- "name",
- "size"
- ],
- "default": "name",
- "description": "Sort entries by name or size"
- }
- },
- "required": [
- "path"
- ],
- "additionalProperties": false,
- "$schema": "http://json-schema.org/draft-07/schema#"
- }
- },
- {
- "name": "directory_tree",
- "description": "Get a recursive tree view of files and directories as a
JSON structure. Each entry includes 'name', 'type' (file/directory), and
'children' for directories. Files have no children array, while directories
always have a children array (which may be empty). The output is formatted with
2-space indentation for readability. Only works within allowed directories.",
- "inputSchema": {
- "type": "object",
- "properties": {
- "path": {
- "type": "string"
- }
- },
- "required": [
- "path"
- ],
- "additionalProperties": false,
- "$schema": "http://json-schema.org/draft-07/schema#"
- }
- },
- {
- "name": "move_file",
- "description": "Move or rename files and directories. Can move files
between directories and rename them in a single operation. If the destination
exists, the operation will fail. Works across different directories and can be
used for simple renaming within the same directory. Both source and destination
must be within allowed directories.",
- "inputSchema": {
- "type": "object",
- "properties": {
- "source": {
- "type": "string"
- },
- "destination": {
- "type": "string"
- }
- },
- "required": [
- "source",
- "destination"
- ],
- "additionalProperties": false,
- "$schema": "http://json-schema.org/draft-07/schema#"
- }
- },
- {
- "name": "search_files",
- "description": "Recursively search for files and directories matching a
pattern. Searches through all subdirectories from the starting path. The search
is case-insensitive and matches partial names. Returns full paths to all
matching items. Great for finding files when you don't know their exact
location. Only searches within allowed directories.",
- "inputSchema": {
- "type": "object",
- "properties": {
- "path": {
- "type": "string"
- },
- "pattern": {
- "type": "string"
- },
- "excludePatterns": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "default": []
- }
- },
- "required": [
- "path",
- "pattern"
- ],
- "additionalProperties": false,
- "$schema": "http://json-schema.org/draft-07/schema#"
- }
- },
- {
- "name": "get_file_info",
- "description": "Retrieve detailed metadata about a file or directory.
Returns comprehensive information including size, creation time, last modified
time, permissions, and type. This tool is perfect for understanding file
characteristics without reading the actual content. Only works within allowed
directories.",
- "inputSchema": {
- "type": "object",
- "properties": {
- "path": {
- "type": "string"
- }
- },
- "required": [
- "path"
- ],
- "additionalProperties": false,
- "$schema": "http://json-schema.org/draft-07/schema#"
- }
- },
- {
- "name": "list_allowed_directories",
- "description": "Returns the list of directories that this server is
allowed to access. Use this to understand which directories are available
before trying to access files.",
- "inputSchema": {
- "type": "object",
- "properties": {},
- "required": []
- }
- }
- ]
-}