Skip to content

fix(connectors): 内部 MCP 的 GET/DELETE 也要拒绝已停用的连接器实例 - #1915

Open
lihongyuan99 wants to merge 1 commit into
TencentCloud:developfrom
lihongyuan99:fix/internal-mcp-active-only
Open

lihongyuan99 wants to merge 1 commit into
TencentCloud:developfrom
lihongyuan99:fix/internal-mcp-active-only

Conversation

@lihongyuan99

@lihongyuan99 lihongyuan99 commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

src/octop/api/routers/internal_mcp.py 有三个内部 MCP 端点,同一个「实例是否存在 / kind 是否匹配 / 是否处于 active」的判定在 POST 里写全了,但 GET 和 DELETE 漏了 status 这一项:

  • POST(:59):if inst is None or inst.kind != kind or inst.status != "active"
  • GET(:109,修复前):if inst is None or inst.kind != kind
  • DELETE(:145,修复前):if inst is None or inst.kind != kind

于是把连接器实例停用之后(PATCH /connector-instances/{id} 传 status="disabled",落到 repo.update_status),它已经不再被挂载进 agent 的工具集(infra/connectors/builder.py:520,616,669 都以 inst.status != "active" 为准),verify_internal_token 也不区分状态(infra/connectors/service.py:633 只比对存下来的 internal_token),但持有该实例 internal token 的客户端仍然可以:

  • 用 GET 打开 streamable-HTTP 的长连接 SSE 通道(返回 text/event-stream,一路挂着 keepalive);
  • 用 DELETE 结束会话(204)。

只有 POST 会正确地返回 CONNECTOR_NOT_FOUND。改动就是把 GET/DELETE 的判定补齐成和 POST 一致,停用实例在这三个入口上的行为统一为 CONNECTOR_NOT_FOUND。

Target branch

  • Base is develop (feature / fix — default)
  • Base is main (release/* or hotfix/* only)

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Refactor / chore
  • Release / hotfix

影响面

「停用」是连接器实例在 UI 上就能做的操作,不是删除:配置和 internal_token 都还在库里,token 校验照样通过。停用后 POST 建不了新会话,但停用之前已经拿到 session id 的客户端可以用 GET 把 SSE 通道重新连起来,长挂一条到 asyncio.sleep(3600) 的 keepalive 连接,DELETE 也仍然受理——也就是「停用之后这个连接器彻底不服务」这个预期在这条路径上是不成立的。修复只加两个短路条件,不改任何合法路径的行为。

internal_mcp.py 在 develop 上的测试覆盖只有一个 POST 用例(tests/integration/test_connectors_api.py:762-803:错 token → 401,active 实例 tools/list → 200)加一个纯客户端的 URL 拼接用例(tests/unit/connectors/test_internal_mcp_url.py),GET / DELETE 两个入口和「实例已停用」这个状态在测试里一次都没出现过——这也正是漏掉 status 判定能长期存着的原因。新增 tests/unit/api/test_internal_mcp_active_only.py 把三个入口的停用分支、active 分支的 GET(StreamingResponse / 无 session header 时 405)和 DELETE(204)都固定下来。

与 #1845 的关系(同文件、不同函数)

#1845(cy1990,2026-10-09 开,base 也是 develop)改的是同一个文件里的 _service(),只加了一行 user_repo=server.services.repos.user_repo,;本 PR 的两个 hunk 在 internal_mcp_get(:109)和 internal_mcp_delete(:145),与它的 hunk 区间不重叠,两侧都能独立合并、互不冲突。两个 PR 修的也不是同一件事:#1845 是给 _service() 多传一个依赖,本 PR 是补 status 判定。这里主动声明是同文件撞车披露,不是重复提交。

撞车核对范围:370 个 open PR 里 369 个的改动文件清单逐个拉过(剩下一个 #1205 是我自己正在 rebase 的分支,改动文件来自本地 diff),src/octop/api/routers/internal_mcp.py 只有 #1845 碰;365 条 open issue 的标题全扫了一遍,与 MCP 内部端点 / 连接器停用沾边的只有 #1854(MCP Apps 渲染)、#1827(streamable_http 收到 401 应走 OAuth)和 #1614(第二次调用某连接器报 stream_error),都跟「实例停用后内部端点是否还受理」无关,所以这条没有对应的 issue,是读代码发现的。

Test plan

新增 8 条用例(tests/unit/api/test_internal_mcp_active_only.py),直接调用 router 函数 + SimpleNamespace 桩 server,沿用 tests/unit/api/test_harness_tabs.py 的写法:

  • disabled 实例:GET / DELETE / POST 三个入口都抛 OctopError(CONNECTOR_NOT_FOUND);
  • active 实例 + 错 token 的 GET:AUTH_FAILED(说明新增的短路没有把鉴权分支吃掉);
  • active 实例 + Mcp-Session-Id:GET 返回 StreamingResponse;无 session header:GET 返回 405 JSONResponse;
  • active 实例:DELETE 返回 204;
  • 实例不存在:CONNECTOR_NOT_FOUND。

改动前(develop@97d6e7ff4,只把测试文件放进工作树)是红的,红的正好是 disabled 的 GET 和 DELETE 两条:

FAILED ...::test_get_of_disabled_instance_is_not_found
FAILED ...::test_delete_of_disabled_instance_is_not_found
2 failed, 6 passed in 3.17s

改动后同一文件 8 passed in 2.45s。

make all 没有跑(会构建 dashboard 前端,本地无 node_modules);跑的是与后端门禁等价的四条命令(直接用 uv sync 出来的 venv,避免 uv run 改写 uv.lock):

ruff check src tests            # All checks passed!
ruff format --check src tests   # 1180 files already formatted
mypy src/octop                  # Success: no issues found in 570 source files
NO_PROXY=127.0.0.1,localhost pytest -q -m "not live" -n 2
                                # 4635 passed, 26 skipped, 0 failed in 831.18s

(-n 2 而不是 -n auto:同一台机器上同时在跑本分支 rebase 的门禁,为了不打断它们只用了 2 个 worker,用例数与结果不受影响。)

同一 develop sha(97d6e7ff4)的基线全量结果是 4627 passed, 26 skipped, 0 failed in 194.09s,本 PR 的差集正好是新增的 8 条用例(4627 + 8 = 4635)。

  • make all passes locally(见上,未跑;已跑等价的 ruff / mypy / pytest)
  • Added/updated tests

Checklist

  • Updated CHANGELOG.md (if user-facing)
  • README / docs updated (if needed)

Rebased onto develop@87a8d52c2 (2026-10-14); head 9b16a31e6 -> c8739541b.

CHANGELOG.md is again the only conflict. develop's file is kept verbatim and this PR's own
bullet is re-inserted into the current ## [Unreleased] list, which is now flat (no ###
subsections), so the diff against the new base is 1 added / 0 deleted there and the
2 code/test file(s) are byte-identical before and after (hunk headers aside) — checked
mechanically, not by eye. Gates re-run by hand on the new head (make all still does not apply:
no dashboard/node_modules in this clone; the venv belongs to this worktree, so
/Users/macos/Documents/Qoder/octop-pr-mining/repos/wt-new-1012/src/octop/__init__.py is this branch's own code):

  • RC ruff=0 format=0 mypy=0 targeted=0 full=0
  • targeted tests for this branch: 8 passed in 7.33s
  • full suite: 4687 passed, 26 skipped, 41 warnings in 232.56s (0:03:52), against develop@87a8d52c2 measured in the same environment today: 4679 passed, 26 skipped, 0 failed
  • All checks passed!; 1186 files already formatted; Success: no issues found in 571 source files

Rebased onto develop@99e479a6b (2026-10-16); head 5cd85f862 -> 6f1061deb.

CHANGELOG.md was again the only conflict: develop's file is kept verbatim and this PR's own bullet is re-inserted under its matching ### subsection of the current ## [Unreleased] block, so the diff against the new base is 1 added / 0 deleted there and the 2 code/test file(s) are byte-identical before and after the rebase (checked mechanically on the patch hash, not by eye). Gates re-run by hand on the new head (make all still does not apply — no dashboard/node_modules in this clone; the venv belongs to this worktree, so the checkout under test is this branch's own code):

  • RC ruff=0 format=0 mypy=0 targeted=0 full=0
  • targeted tests for this branch: 8 passed in 9.06s
  • full suite: 4788 passed, 32 skipped, 41 warnings in 487.34s (0:08:07), against develop@99e479a6b measured today in its own per-worktree venv on the same machine: 4780 passed, 32 skipped, 0 failed in 495.68s
  • All checks passed!; 1204 files already formatted; Success: no issues found in 581 source files

@lihongyuan99
lihongyuan99 force-pushed the fix/internal-mcp-active-only branch 2 times, most recently from c873954 to 5cd85f8 Compare October 15, 2026 00:05
@lihongyuan99
lihongyuan99 force-pushed the fix/internal-mcp-active-only branch from 5cd85f8 to 6f1061d Compare October 16, 2026 00:06

This branch has not been deployed

No deployments
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