qca-mcp-proxy 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qca_mcp_proxy-0.1.0/.gitignore +8 -0
- qca_mcp_proxy-0.1.0/LICENSE +21 -0
- qca_mcp_proxy-0.1.0/PKG-INFO +203 -0
- qca_mcp_proxy-0.1.0/README.md +178 -0
- qca_mcp_proxy-0.1.0/pyproject.toml +40 -0
- qca_mcp_proxy-0.1.0/scripts/release.sh +146 -0
- qca_mcp_proxy-0.1.0/src/qca_mcp_proxy/__init__.py +3 -0
- qca_mcp_proxy-0.1.0/src/qca_mcp_proxy/__main__.py +152 -0
- qca_mcp_proxy-0.1.0/src/qca_mcp_proxy/config.py +118 -0
- qca_mcp_proxy-0.1.0/src/qca_mcp_proxy/server.py +165 -0
- qca_mcp_proxy-0.1.0/src/qca_mcp_proxy/upstream.py +128 -0
- qca_mcp_proxy-0.1.0/tests/test_config.py +75 -0
- qca_mcp_proxy-0.1.0/tests/test_http.py +70 -0
- qca_mcp_proxy-0.1.0/tests/test_proxy.py +179 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Larry
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: qca-mcp-proxy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Filtered MCP proxy in front of the Qoder Cloud Agents (QCA) remote MCP server, over stdio or Streamable HTTP
|
|
5
|
+
Project-URL: Homepage, https://github.com/xiaochus/qca_mcp_proxy
|
|
6
|
+
Project-URL: Repository, https://github.com/xiaochus/qca_mcp_proxy
|
|
7
|
+
Project-URL: Issues, https://github.com/xiaochus/qca_mcp_proxy/issues
|
|
8
|
+
Author-email: Larry <xiaochus@live.cn>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: cloud-agents,mcp,model-context-protocol,proxy,qoder,stdio
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Internet :: Proxy Servers
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: mcp<3,>=2.0
|
|
23
|
+
Requires-Dist: uvicorn>=0.31
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# qca-mcp-proxy
|
|
27
|
+
|
|
28
|
+
把 Qoder Cloud Agents(QCA)的远程 MCP 服务包一层 MCP 服务:只转发 **session 相关工具**,外加 `list_agents` / `list_environments` / `list_vaults` 三个只读查询和 `list_files` / `get_file` 两个文件元数据查询。其余工具(agent / environment / vault / skill / deployment 的写操作、session 资源等)既不列出也不可调用。
|
|
29
|
+
|
|
30
|
+
- 上游:QCA 远程 MCP,Streamable HTTP + `Authorization: Bearer <PAT>`
|
|
31
|
+
- 下游:两种跑法,同一套白名单
|
|
32
|
+
- `--transport stdio`(默认):给 Qoder / Claude Desktop 这类拉起子进程的客户端
|
|
33
|
+
- `--transport http`:起一个 `http://127.0.0.1:8790/mcp` 的服务地址,客户端填 URL 即可
|
|
34
|
+
- 实现用 MCP Python SDK v2(`mcp>=2,<3`)
|
|
35
|
+
- 工具列表每次 5 分钟内缓存一次,避免每次 `tools/list` 都打上游
|
|
36
|
+
|
|
37
|
+
## 暴露的工具(13 个)
|
|
38
|
+
|
|
39
|
+
| 分组 | 工具 |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| session 生命周期 | `create_session` `get_session` `list_sessions` `update_session` `cancel_session` |
|
|
42
|
+
| session 事件 | `send_session_events` `list_session_event_summaries` `list_session_events` |
|
|
43
|
+
| 只读查询 | `list_agents` `list_environments` `list_vaults` |
|
|
44
|
+
| 文件元数据 | `list_files`(传 `scope_id=<session_id>` 查会话产出)`get_file` |
|
|
45
|
+
|
|
46
|
+
工具名与参数 schema 全部原样透传上游,不做改写;返回的 `content` / `structured_content` / `is_error` 也原样返回。
|
|
47
|
+
|
|
48
|
+
刻意不在白名单里(`tools/list` 不返回,`tools/call` 也拒绝):
|
|
49
|
+
|
|
50
|
+
- session 资源四件套 `add_session_resource` / `get_session_resource` / `list_session_resources` / `delete_session_resource`:文件、GitHub 仓库、memory store 只能在 `create_session` 的 `resources` 里一次性挂上,会话中途不能再挂
|
|
51
|
+
- `archive_session` / `delete_session`:只能 `cancel_session` 停掉会话,清理交给控制台或 `QCA_EXTRA_TOOLS` 追加
|
|
52
|
+
- agent / environment / vault / skill / deployment 的写操作全部隐藏,只留三个 `list_*`
|
|
53
|
+
|
|
54
|
+
## 安装
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# 1) 直接跑源码(推荐本地开发)
|
|
58
|
+
uvx --from /Users/wbc/Desktop/ascend_adapter/qca-mcp-proxy qca-mcp-proxy --check
|
|
59
|
+
|
|
60
|
+
# 2) 从 git 安装
|
|
61
|
+
uvx --from git+https://github.com/<you>/qca-mcp-proxy qca-mcp-proxy
|
|
62
|
+
|
|
63
|
+
# 3) 装成常驻命令行
|
|
64
|
+
uv tool install /Users/wbc/Desktop/ascend_adapter/qca-mcp-proxy
|
|
65
|
+
qca-mcp-proxy --check
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`--check` 会用当前 PAT 连一次上游,打印「上游工具数 / 已暴露 / 被隐藏」后退出,用来确认 PAT 和区域对不对。
|
|
69
|
+
|
|
70
|
+
## 以服务地址跑(Streamable HTTP)
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
cd /Users/wbc/Desktop/ascend_adapter/qca-mcp-proxy
|
|
74
|
+
export QCA_PAT=<你的 PAT>
|
|
75
|
+
export QCA_REGION=cn
|
|
76
|
+
|
|
77
|
+
# 起服务(默认监听 127.0.0.1:8790)
|
|
78
|
+
.venv/bin/qca-mcp-proxy --transport http
|
|
79
|
+
# 或者: uvx --from . qca-mcp-proxy --transport http
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
启动成功会打印:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
[qca-proxy] INFO qca_mcp_proxy.server: serving Streamable HTTP on http://127.0.0.1:8790/mcp
|
|
86
|
+
[qca-proxy] INFO qca_mcp_proxy.upstream: connected to https://mcp.qoder.cn/... (cn region)
|
|
87
|
+
[qca-proxy] INFO qca_mcp_proxy.server: qca proxy ready: region=cn ...
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
然后在 agent 里把服务地址填成 `http://127.0.0.1:8790/mcp`;如果客户端用 `mcpServers` 配置,写:
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{"mcpServers": {"qca": {"url": "http://127.0.0.1:8790/mcp"}}}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
相关开关:
|
|
97
|
+
|
|
98
|
+
| 参数 | 默认 | 说明 |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| `--host` | `127.0.0.1` | 只给本机用就保持默认;要同网段访问改 `0.0.0.0`(SDK 只对回环地址做 DNS rebinding 防护,改后请自行加 `--auth-token`) |
|
|
101
|
+
| `--port` | `8790` | 监听端口,端口冲突时换一个 |
|
|
102
|
+
| `--stateless` | 关 | 不带 `mcp-session-id` 的无状态模式。有些 agent 每个请求都新开连接,用它会报 “session not found”,这时加这个参数 |
|
|
103
|
+
| `--auth-token` | 空 | 要求请求带 `Authorization: Bearer <token>`,也可用 `QCA_HTTP_TOKEN`。只在本机测可以不加 |
|
|
104
|
+
|
|
105
|
+
启动前会先探一次上游:PAT 不对或区域不匹配时直接一行报错退出,不会起一个连不上上游的空服务:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
[qca-proxy] cannot reach QCA: MCPError(-32603): Server returned an error response
|
|
109
|
+
|
|
110
|
+
The cn endpoint (https://mcp.qoder.cn/api/v1/mcp/servers/qca) rejected the request. ...
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## 配置
|
|
114
|
+
|
|
115
|
+
### Qoder / 兼容 `mcpServers` 的客户端
|
|
116
|
+
|
|
117
|
+
`args` 里的 `--from` 有两种来源,按安装方式二选一。
|
|
118
|
+
|
|
119
|
+
从 git 安装(推荐,换机器不用改路径):
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"mcpServers": {
|
|
124
|
+
"qca": {
|
|
125
|
+
"command": "uvx",
|
|
126
|
+
"args": ["--from", "git+https://github.com/xiaochus/qca_mcp_proxy", "qca-mcp-proxy"],
|
|
127
|
+
"env": {
|
|
128
|
+
"QCA_PAT": "<你的 PAT>",
|
|
129
|
+
"QCA_REGION": "cn"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
从本机源码安装(改代码即时生效):
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"mcpServers": {
|
|
141
|
+
"qca": {
|
|
142
|
+
"command": "uvx",
|
|
143
|
+
"args": ["--from", "/Users/wbc/Desktop/ascend_adapter/qca-mcp-proxy", "qca-mcp-proxy"],
|
|
144
|
+
"env": {
|
|
145
|
+
"QCA_PAT": "<你的 PAT>",
|
|
146
|
+
"QCA_REGION": "cn"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
等价命令行(先在终端验证能不能起):
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
uvx --from git+https://github.com/xiaochus/qca_mcp_proxy qca-mcp-proxy --check
|
|
157
|
+
|
|
158
|
+
# 固定到某个 commit / tag,避免每次拉到不一样的代码
|
|
159
|
+
uvx --from git+https://github.com/xiaochus/qca_mcp_proxy@v0.1.0 qca-mcp-proxy --check
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`uvx` 会把该仓库缓存到本地 `~/.cache/uv`,改了远端代码后需要 `--refresh` 或清缓存才会更新。
|
|
163
|
+
|
|
164
|
+
### 环境变量
|
|
165
|
+
|
|
166
|
+
| 变量 | 必填 | 默认 | 说明 |
|
|
167
|
+
| --- | --- | --- | --- |
|
|
168
|
+
| `QCA_PAT` | 是 | – | 个人访问令牌;也接受 `QODER_PAT` / `QCA_TOKEN`,带不带 `Bearer ` 前缀都可以 |
|
|
169
|
+
| `QCA_REGION` | 否 | `cn` | `cn` → `https://mcp.qoder.cn/api/v1/mcp/servers/qca`;`global` → `https://mcp.qoder.com/api/v1/mcp/servers/qca` |
|
|
170
|
+
| `QCA_MCP_URL` | 否 | 由 region 决定 | 直接指定上游 MCP 地址,优先于 region |
|
|
171
|
+
| `QCA_EXTRA_TOOLS` | 否 | 空 | 在默认白名单上追加工具(逗号分隔),例如 `list_accessible_workspaces` |
|
|
172
|
+
| `QCA_ALLOWED_TOOLS` | 否 | 默认白名单 | **整体替换**默认白名单(逗号分隔) |
|
|
173
|
+
| `QCA_TOOLS_TTL` | 否 | `300` | 工具列表缓存秒数 |
|
|
174
|
+
| `QCA_CONNECT_TIMEOUT` | 否 | `30` | 连接上游超时(秒) |
|
|
175
|
+
| `QCA_READ_TIMEOUT` | 否 | `300` | 上游单次读取超时(秒) |
|
|
176
|
+
| `QCA_DEBUG` | 否 | 关 | `1` 打开 DEBUG 日志(含 SDK 与 HTTP 层) |
|
|
177
|
+
| `QCA_HTTP_TOKEN` | 否 | 空 | 仅 `--transport http` 生效:要求请求带 `Authorization: Bearer <值>` |
|
|
178
|
+
|
|
179
|
+
常用扩展例子:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# 需要工作区选择能力时
|
|
183
|
+
QCA_EXTRA_TOOLS=list_accessible_workspaces
|
|
184
|
+
|
|
185
|
+
# 只要读,不要写
|
|
186
|
+
QCA_ALLOWED_TOOLS=get_session,list_sessions,list_session_event_summaries,list_session_events,list_agents,list_environments,list_vaults,list_files,get_file
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## 典型用法
|
|
190
|
+
|
|
191
|
+
1. `list_agents` / `list_environments` / `list_vaults` 拿到 `agent_xxx` / `env_xxx` / `vault_xxx`
|
|
192
|
+
2. `create_session` 传 `agent` + `environment_id`(需要凭证时再传 `vault_ids`,需要挂载文件/仓库/记忆库时传 `resources`)
|
|
193
|
+
3. `send_session_events` 发一条 `user.message` 事件
|
|
194
|
+
4. `list_session_event_summaries` 轮询进度(不带载荷,省 token),需要内容时再用 `list_session_events` 加 `types` 过滤、`limit` 调小
|
|
195
|
+
5. 取产物:`list_files` 传 `scope_id=<session_id>` 拿到 `file_id`,再用 `get_file` 看元数据;下载走 HTTP `GET /files/{id}/content`(见上文)
|
|
196
|
+
6. 结束:`cancel_session`(没有归档/删除权限)
|
|
197
|
+
|
|
198
|
+
## 行为约定
|
|
199
|
+
|
|
200
|
+
- **启动即连上游**:生命周期里建立一条长连接,PAT 无效或区域不匹配时启动直接失败,stderr 会给出换区域的提示(日志只走 stderr,stdout 留给协议)。这样也避免了在请求处理任务里创建 MCP 客户端任务组导致的 cancel scope 破坏。
|
|
201
|
+
- **写操作不重试**:`create_session` / `send_session_events` 等调用失败不再自动重放,避免上游重复执行;失败以 `is_error=true` 的工具结果返回,模型可自行决定下一步。
|
|
202
|
+
- **未授权工具双重拦截**:`tools/list` 不返回,`tools/call` 也直接拒绝并给出可调用的工具名。
|
|
203
|
+
- **不暴露 resources / prompts**:上游只有 tools,本代理也只声明 tools 能力。
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# qca-mcp-proxy
|
|
2
|
+
|
|
3
|
+
把 Qoder Cloud Agents(QCA)的远程 MCP 服务包一层 MCP 服务:只转发 **session 相关工具**,外加 `list_agents` / `list_environments` / `list_vaults` 三个只读查询和 `list_files` / `get_file` 两个文件元数据查询。其余工具(agent / environment / vault / skill / deployment 的写操作、session 资源等)既不列出也不可调用。
|
|
4
|
+
|
|
5
|
+
- 上游:QCA 远程 MCP,Streamable HTTP + `Authorization: Bearer <PAT>`
|
|
6
|
+
- 下游:两种跑法,同一套白名单
|
|
7
|
+
- `--transport stdio`(默认):给 Qoder / Claude Desktop 这类拉起子进程的客户端
|
|
8
|
+
- `--transport http`:起一个 `http://127.0.0.1:8790/mcp` 的服务地址,客户端填 URL 即可
|
|
9
|
+
- 实现用 MCP Python SDK v2(`mcp>=2,<3`)
|
|
10
|
+
- 工具列表每次 5 分钟内缓存一次,避免每次 `tools/list` 都打上游
|
|
11
|
+
|
|
12
|
+
## 暴露的工具(13 个)
|
|
13
|
+
|
|
14
|
+
| 分组 | 工具 |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| session 生命周期 | `create_session` `get_session` `list_sessions` `update_session` `cancel_session` |
|
|
17
|
+
| session 事件 | `send_session_events` `list_session_event_summaries` `list_session_events` |
|
|
18
|
+
| 只读查询 | `list_agents` `list_environments` `list_vaults` |
|
|
19
|
+
| 文件元数据 | `list_files`(传 `scope_id=<session_id>` 查会话产出)`get_file` |
|
|
20
|
+
|
|
21
|
+
工具名与参数 schema 全部原样透传上游,不做改写;返回的 `content` / `structured_content` / `is_error` 也原样返回。
|
|
22
|
+
|
|
23
|
+
刻意不在白名单里(`tools/list` 不返回,`tools/call` 也拒绝):
|
|
24
|
+
|
|
25
|
+
- session 资源四件套 `add_session_resource` / `get_session_resource` / `list_session_resources` / `delete_session_resource`:文件、GitHub 仓库、memory store 只能在 `create_session` 的 `resources` 里一次性挂上,会话中途不能再挂
|
|
26
|
+
- `archive_session` / `delete_session`:只能 `cancel_session` 停掉会话,清理交给控制台或 `QCA_EXTRA_TOOLS` 追加
|
|
27
|
+
- agent / environment / vault / skill / deployment 的写操作全部隐藏,只留三个 `list_*`
|
|
28
|
+
|
|
29
|
+
## 安装
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# 1) 直接跑源码(推荐本地开发)
|
|
33
|
+
uvx --from /Users/wbc/Desktop/ascend_adapter/qca-mcp-proxy qca-mcp-proxy --check
|
|
34
|
+
|
|
35
|
+
# 2) 从 git 安装
|
|
36
|
+
uvx --from git+https://github.com/<you>/qca-mcp-proxy qca-mcp-proxy
|
|
37
|
+
|
|
38
|
+
# 3) 装成常驻命令行
|
|
39
|
+
uv tool install /Users/wbc/Desktop/ascend_adapter/qca-mcp-proxy
|
|
40
|
+
qca-mcp-proxy --check
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`--check` 会用当前 PAT 连一次上游,打印「上游工具数 / 已暴露 / 被隐藏」后退出,用来确认 PAT 和区域对不对。
|
|
44
|
+
|
|
45
|
+
## 以服务地址跑(Streamable HTTP)
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
cd /Users/wbc/Desktop/ascend_adapter/qca-mcp-proxy
|
|
49
|
+
export QCA_PAT=<你的 PAT>
|
|
50
|
+
export QCA_REGION=cn
|
|
51
|
+
|
|
52
|
+
# 起服务(默认监听 127.0.0.1:8790)
|
|
53
|
+
.venv/bin/qca-mcp-proxy --transport http
|
|
54
|
+
# 或者: uvx --from . qca-mcp-proxy --transport http
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
启动成功会打印:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
[qca-proxy] INFO qca_mcp_proxy.server: serving Streamable HTTP on http://127.0.0.1:8790/mcp
|
|
61
|
+
[qca-proxy] INFO qca_mcp_proxy.upstream: connected to https://mcp.qoder.cn/... (cn region)
|
|
62
|
+
[qca-proxy] INFO qca_mcp_proxy.server: qca proxy ready: region=cn ...
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
然后在 agent 里把服务地址填成 `http://127.0.0.1:8790/mcp`;如果客户端用 `mcpServers` 配置,写:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{"mcpServers": {"qca": {"url": "http://127.0.0.1:8790/mcp"}}}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
相关开关:
|
|
72
|
+
|
|
73
|
+
| 参数 | 默认 | 说明 |
|
|
74
|
+
| --- | --- | --- |
|
|
75
|
+
| `--host` | `127.0.0.1` | 只给本机用就保持默认;要同网段访问改 `0.0.0.0`(SDK 只对回环地址做 DNS rebinding 防护,改后请自行加 `--auth-token`) |
|
|
76
|
+
| `--port` | `8790` | 监听端口,端口冲突时换一个 |
|
|
77
|
+
| `--stateless` | 关 | 不带 `mcp-session-id` 的无状态模式。有些 agent 每个请求都新开连接,用它会报 “session not found”,这时加这个参数 |
|
|
78
|
+
| `--auth-token` | 空 | 要求请求带 `Authorization: Bearer <token>`,也可用 `QCA_HTTP_TOKEN`。只在本机测可以不加 |
|
|
79
|
+
|
|
80
|
+
启动前会先探一次上游:PAT 不对或区域不匹配时直接一行报错退出,不会起一个连不上上游的空服务:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
[qca-proxy] cannot reach QCA: MCPError(-32603): Server returned an error response
|
|
84
|
+
|
|
85
|
+
The cn endpoint (https://mcp.qoder.cn/api/v1/mcp/servers/qca) rejected the request. ...
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 配置
|
|
89
|
+
|
|
90
|
+
### Qoder / 兼容 `mcpServers` 的客户端
|
|
91
|
+
|
|
92
|
+
`args` 里的 `--from` 有两种来源,按安装方式二选一。
|
|
93
|
+
|
|
94
|
+
从 git 安装(推荐,换机器不用改路径):
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"mcpServers": {
|
|
99
|
+
"qca": {
|
|
100
|
+
"command": "uvx",
|
|
101
|
+
"args": ["--from", "git+https://github.com/xiaochus/qca_mcp_proxy", "qca-mcp-proxy"],
|
|
102
|
+
"env": {
|
|
103
|
+
"QCA_PAT": "<你的 PAT>",
|
|
104
|
+
"QCA_REGION": "cn"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
从本机源码安装(改代码即时生效):
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"mcpServers": {
|
|
116
|
+
"qca": {
|
|
117
|
+
"command": "uvx",
|
|
118
|
+
"args": ["--from", "/Users/wbc/Desktop/ascend_adapter/qca-mcp-proxy", "qca-mcp-proxy"],
|
|
119
|
+
"env": {
|
|
120
|
+
"QCA_PAT": "<你的 PAT>",
|
|
121
|
+
"QCA_REGION": "cn"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
等价命令行(先在终端验证能不能起):
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
uvx --from git+https://github.com/xiaochus/qca_mcp_proxy qca-mcp-proxy --check
|
|
132
|
+
|
|
133
|
+
# 固定到某个 commit / tag,避免每次拉到不一样的代码
|
|
134
|
+
uvx --from git+https://github.com/xiaochus/qca_mcp_proxy@v0.1.0 qca-mcp-proxy --check
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`uvx` 会把该仓库缓存到本地 `~/.cache/uv`,改了远端代码后需要 `--refresh` 或清缓存才会更新。
|
|
138
|
+
|
|
139
|
+
### 环境变量
|
|
140
|
+
|
|
141
|
+
| 变量 | 必填 | 默认 | 说明 |
|
|
142
|
+
| --- | --- | --- | --- |
|
|
143
|
+
| `QCA_PAT` | 是 | – | 个人访问令牌;也接受 `QODER_PAT` / `QCA_TOKEN`,带不带 `Bearer ` 前缀都可以 |
|
|
144
|
+
| `QCA_REGION` | 否 | `cn` | `cn` → `https://mcp.qoder.cn/api/v1/mcp/servers/qca`;`global` → `https://mcp.qoder.com/api/v1/mcp/servers/qca` |
|
|
145
|
+
| `QCA_MCP_URL` | 否 | 由 region 决定 | 直接指定上游 MCP 地址,优先于 region |
|
|
146
|
+
| `QCA_EXTRA_TOOLS` | 否 | 空 | 在默认白名单上追加工具(逗号分隔),例如 `list_accessible_workspaces` |
|
|
147
|
+
| `QCA_ALLOWED_TOOLS` | 否 | 默认白名单 | **整体替换**默认白名单(逗号分隔) |
|
|
148
|
+
| `QCA_TOOLS_TTL` | 否 | `300` | 工具列表缓存秒数 |
|
|
149
|
+
| `QCA_CONNECT_TIMEOUT` | 否 | `30` | 连接上游超时(秒) |
|
|
150
|
+
| `QCA_READ_TIMEOUT` | 否 | `300` | 上游单次读取超时(秒) |
|
|
151
|
+
| `QCA_DEBUG` | 否 | 关 | `1` 打开 DEBUG 日志(含 SDK 与 HTTP 层) |
|
|
152
|
+
| `QCA_HTTP_TOKEN` | 否 | 空 | 仅 `--transport http` 生效:要求请求带 `Authorization: Bearer <值>` |
|
|
153
|
+
|
|
154
|
+
常用扩展例子:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# 需要工作区选择能力时
|
|
158
|
+
QCA_EXTRA_TOOLS=list_accessible_workspaces
|
|
159
|
+
|
|
160
|
+
# 只要读,不要写
|
|
161
|
+
QCA_ALLOWED_TOOLS=get_session,list_sessions,list_session_event_summaries,list_session_events,list_agents,list_environments,list_vaults,list_files,get_file
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## 典型用法
|
|
165
|
+
|
|
166
|
+
1. `list_agents` / `list_environments` / `list_vaults` 拿到 `agent_xxx` / `env_xxx` / `vault_xxx`
|
|
167
|
+
2. `create_session` 传 `agent` + `environment_id`(需要凭证时再传 `vault_ids`,需要挂载文件/仓库/记忆库时传 `resources`)
|
|
168
|
+
3. `send_session_events` 发一条 `user.message` 事件
|
|
169
|
+
4. `list_session_event_summaries` 轮询进度(不带载荷,省 token),需要内容时再用 `list_session_events` 加 `types` 过滤、`limit` 调小
|
|
170
|
+
5. 取产物:`list_files` 传 `scope_id=<session_id>` 拿到 `file_id`,再用 `get_file` 看元数据;下载走 HTTP `GET /files/{id}/content`(见上文)
|
|
171
|
+
6. 结束:`cancel_session`(没有归档/删除权限)
|
|
172
|
+
|
|
173
|
+
## 行为约定
|
|
174
|
+
|
|
175
|
+
- **启动即连上游**:生命周期里建立一条长连接,PAT 无效或区域不匹配时启动直接失败,stderr 会给出换区域的提示(日志只走 stderr,stdout 留给协议)。这样也避免了在请求处理任务里创建 MCP 客户端任务组导致的 cancel scope 破坏。
|
|
176
|
+
- **写操作不重试**:`create_session` / `send_session_events` 等调用失败不再自动重放,避免上游重复执行;失败以 `is_error=true` 的工具结果返回,模型可自行决定下一步。
|
|
177
|
+
- **未授权工具双重拦截**:`tools/list` 不返回,`tools/call` 也直接拒绝并给出可调用的工具名。
|
|
178
|
+
- **不暴露 resources / prompts**:上游只有 tools,本代理也只声明 tools 能力。
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "qca-mcp-proxy"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Filtered MCP proxy in front of the Qoder Cloud Agents (QCA) remote MCP server, over stdio or Streamable HTTP"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Larry", email = "xiaochus@live.cn" }]
|
|
14
|
+
keywords = ["mcp", "model-context-protocol", "proxy", "qoder", "cloud-agents", "stdio"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Topic :: Internet :: Proxy Servers",
|
|
24
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
25
|
+
]
|
|
26
|
+
dependencies = ["mcp>=2.0,<3", "uvicorn>=0.31"]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/xiaochus/qca_mcp_proxy"
|
|
30
|
+
Repository = "https://github.com/xiaochus/qca_mcp_proxy"
|
|
31
|
+
Issues = "https://github.com/xiaochus/qca_mcp_proxy/issues"
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
qca-mcp-proxy = "qca_mcp_proxy.__main__:main"
|
|
35
|
+
|
|
36
|
+
[tool.hatch.build.targets.wheel]
|
|
37
|
+
packages = ["src/qca_mcp_proxy"]
|
|
38
|
+
|
|
39
|
+
[dependency-groups]
|
|
40
|
+
dev = ["pytest>=8", "uvicorn>=0.30"]
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# qca-mcp-proxy 发布脚本:校验 -> 构建 -> twine check -> 上传
|
|
4
|
+
#
|
|
5
|
+
# scripts/release.sh --test # 演练:上传到 TestPyPI
|
|
6
|
+
# scripts/release.sh --prod # 正式发布到 PyPI
|
|
7
|
+
#
|
|
8
|
+
# 可选:HTTPS_PROXY=http://127.0.0.1:7897 scripts/release.sh --prod
|
|
9
|
+
#
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
13
|
+
cd "$ROOT"
|
|
14
|
+
|
|
15
|
+
# 本机 ~/.config/pip/pip.conf 设了 user = true,会让 venv 及 build 的隔离环境
|
|
16
|
+
# 报 "Can not perform a '--user' install"。导出给所有子进程覆盖掉。
|
|
17
|
+
export PIP_USER=false
|
|
18
|
+
|
|
19
|
+
TARGET="pypi"
|
|
20
|
+
ALLOW_DIRTY=0
|
|
21
|
+
ASSUME_YES=0
|
|
22
|
+
|
|
23
|
+
usage() {
|
|
24
|
+
printf '用法: scripts/release.sh [选项]\n\n'
|
|
25
|
+
printf ' --test, --testpypi 上传到 TestPyPI(演练,可随便覆盖)\n'
|
|
26
|
+
printf ' --prod, --pypi 上传到正式 PyPI(默认,版本号不可复用)\n'
|
|
27
|
+
printf ' --allow-dirty 允许 git 工作区有未提交改动\n'
|
|
28
|
+
printf ' -y, --yes 跳过上传前的确认提示\n'
|
|
29
|
+
printf ' -h, --help 显示本帮助\n'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for arg in "$@"; do
|
|
33
|
+
case "$arg" in
|
|
34
|
+
--test|--testpypi) TARGET="testpypi" ;;
|
|
35
|
+
--prod|--pypi) TARGET="pypi" ;;
|
|
36
|
+
--allow-dirty) ALLOW_DIRTY=1 ;;
|
|
37
|
+
-y|--yes) ASSUME_YES=1 ;;
|
|
38
|
+
-h|--help) usage; exit 0 ;;
|
|
39
|
+
*) printf '未知参数: %s(用 --help 查看用法)\n' "$arg" >&2; exit 2 ;;
|
|
40
|
+
esac
|
|
41
|
+
done
|
|
42
|
+
|
|
43
|
+
step() { printf '\n\033[1;34m==> %s\033[0m\n' "$1"; }
|
|
44
|
+
die() { printf '\033[1;31m错误: %s\033[0m\n' "$1" >&2; exit 1; }
|
|
45
|
+
|
|
46
|
+
# ---------------------------------------------------------------- 1. 构建环境
|
|
47
|
+
step "1/6 检查构建环境"
|
|
48
|
+
if [[ ! -x .venv/bin/python ]]; then
|
|
49
|
+
echo "未找到 .venv,创建虚拟环境并安装 build/twine ..."
|
|
50
|
+
python3 -m venv .venv
|
|
51
|
+
.venv/bin/pip install -q -U build twine
|
|
52
|
+
fi
|
|
53
|
+
if ! .venv/bin/python -c "import build, twine" >/dev/null 2>&1; then
|
|
54
|
+
echo "补装 build/twine ..."
|
|
55
|
+
.venv/bin/pip install -q -U build twine
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# ---------------------------------------------------------------- 2. 版本一致
|
|
59
|
+
step "2/6 校验版本号"
|
|
60
|
+
VERSION="$(.venv/bin/python - <<'PY'
|
|
61
|
+
import pathlib, tomllib
|
|
62
|
+
data = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8"))
|
|
63
|
+
print(data["project"]["version"])
|
|
64
|
+
PY
|
|
65
|
+
)"
|
|
66
|
+
INIT_VERSION="$(.venv/bin/python - <<'PY'
|
|
67
|
+
import ast, pathlib
|
|
68
|
+
tree = ast.parse(pathlib.Path("src/qca_mcp_proxy/__init__.py").read_text(encoding="utf-8"))
|
|
69
|
+
for node in tree.body:
|
|
70
|
+
if isinstance(node, ast.Assign):
|
|
71
|
+
for target in node.targets:
|
|
72
|
+
if getattr(target, "id", "") == "__version__":
|
|
73
|
+
print(ast.literal_eval(node.value))
|
|
74
|
+
raise SystemExit
|
|
75
|
+
print("")
|
|
76
|
+
PY
|
|
77
|
+
)"
|
|
78
|
+
printf ' pyproject.toml version = %s\n' "$VERSION"
|
|
79
|
+
printf ' __init__.py __version__ = %s\n' "${INIT_VERSION:-<缺失>}"
|
|
80
|
+
[[ -n "$INIT_VERSION" ]] || die "src/qca_mcp_proxy/__init__.py 里没有 __version__"
|
|
81
|
+
[[ "$VERSION" == "$INIT_VERSION" ]] || die "两处版本号不一致,先同步再发布"
|
|
82
|
+
|
|
83
|
+
# ---------------------------------------------------------------- 3. 工作区
|
|
84
|
+
step "3/6 检查 git 工作区"
|
|
85
|
+
if [[ -n "$(git status --porcelain)" ]]; then
|
|
86
|
+
git status --short
|
|
87
|
+
if [[ $ALLOW_DIRTY -eq 1 ]]; then
|
|
88
|
+
echo "工作区有未提交改动,按 --allow-dirty 继续"
|
|
89
|
+
else
|
|
90
|
+
die "工作区有未提交改动,请先 commit(或加 --allow-dirty 强制继续)"
|
|
91
|
+
fi
|
|
92
|
+
fi
|
|
93
|
+
if git rev-parse -q --verify "refs/tags/v$VERSION" >/dev/null 2>&1; then
|
|
94
|
+
die "已存在 tag v$VERSION,该版本可能已发布;请先提升 version"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# ---------------------------------------------------------------- 4. 构建
|
|
98
|
+
step "4/6 构建分发包"
|
|
99
|
+
rm -rf dist
|
|
100
|
+
.venv/bin/python -m build
|
|
101
|
+
ls -1 dist/
|
|
102
|
+
|
|
103
|
+
# ---------------------------------------------------------------- 5. 校验
|
|
104
|
+
step "5/6 元数据校验"
|
|
105
|
+
.venv/bin/twine check dist/*
|
|
106
|
+
|
|
107
|
+
# ---------------------------------------------------------------- 6. 上传
|
|
108
|
+
step "6/6 上传到 $TARGET"
|
|
109
|
+
|
|
110
|
+
# 凭据预检。twine 的优先级:命令行 > 环境变量 > ~/.pypirc > 钥匙串 > 交互输入。
|
|
111
|
+
KEYRING_SERVICE="https://upload.pypi.org/legacy/"
|
|
112
|
+
[[ "$TARGET" == "testpypi" ]] && KEYRING_SERVICE="https://test.pypi.org/legacy/"
|
|
113
|
+
if [[ -n "${TWINE_PASSWORD:-}" ]]; then
|
|
114
|
+
echo "凭据来源:环境变量 TWINE_PASSWORD"
|
|
115
|
+
elif .venv/bin/python -c "import keyring, sys; sys.exit(0 if keyring.get_password('$KEYRING_SERVICE', '__token__') else 1)" >/dev/null 2>&1; then
|
|
116
|
+
echo "凭据来源:macOS 钥匙串"
|
|
117
|
+
elif [[ -t 0 ]]; then
|
|
118
|
+
echo "钥匙串中未找到该仓库的 token,twine 将提示输入。"
|
|
119
|
+
echo " 想永久保存:.venv/bin/keyring set \"$KEYRING_SERVICE\" \"__token__\""
|
|
120
|
+
else
|
|
121
|
+
die "非交互环境无法输入 token,请先执行 keyring set \"$KEYRING_SERVICE\" \"__token__\""
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
if [[ $ASSUME_YES -eq 0 ]]; then
|
|
125
|
+
printf '即将上传 %s 到 %s,确认?(yes/no) ' "$VERSION" "$TARGET"
|
|
126
|
+
read -r reply
|
|
127
|
+
[[ "$reply" == "yes" ]] || { echo "已取消"; exit 1; }
|
|
128
|
+
fi
|
|
129
|
+
if [[ "$TARGET" == "testpypi" ]]; then
|
|
130
|
+
.venv/bin/twine upload --repository testpypi dist/*
|
|
131
|
+
else
|
|
132
|
+
.venv/bin/twine upload dist/*
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
printf '\n\033[1;32m==> 完成:%s 已发布到 %s\033[0m\n\n' "$VERSION" "$TARGET"
|
|
136
|
+
|
|
137
|
+
if [[ "$TARGET" == "testpypi" ]]; then
|
|
138
|
+
printf '验证安装:\n'
|
|
139
|
+
printf ' pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple qca-mcp-proxy==%s\n\n' "$VERSION"
|
|
140
|
+
else
|
|
141
|
+
printf '验证安装:\n'
|
|
142
|
+
printf ' pip install qca-mcp-proxy==%s\n\n' "$VERSION"
|
|
143
|
+
printf '打 tag 并推送:\n'
|
|
144
|
+
printf ' git tag -a v%s -m "release v%s"\n' "$VERSION" "$VERSION"
|
|
145
|
+
printf ' git push origin v%s\n' "$VERSION"
|
|
146
|
+
fi
|