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.
@@ -0,0 +1,8 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.egg-info/
4
+ .pytest_cache/
5
+ build/
6
+ dist/
7
+ .DS_Store
8
+ .env
@@ -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
@@ -0,0 +1,3 @@
1
+ """Filtered MCP proxy for Qoder Cloud Agents (QCA)."""
2
+
3
+ __version__ = "0.1.0"