mcp-apollo-explorer 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.
Files changed (26) hide show
  1. mcp_apollo_explorer-0.1.0/.idea/.gitignore +5 -0
  2. mcp_apollo_explorer-0.1.0/.idea/encodings.xml +4 -0
  3. mcp_apollo_explorer-0.1.0/.idea/inspectionProfiles/Project_Default.xml +12 -0
  4. mcp_apollo_explorer-0.1.0/.idea/inspectionProfiles/profiles_settings.xml +6 -0
  5. mcp_apollo_explorer-0.1.0/.idea/mcp-apollo-explorer.iml +15 -0
  6. mcp_apollo_explorer-0.1.0/.idea/misc.xml +6 -0
  7. mcp_apollo_explorer-0.1.0/.idea/modules.xml +8 -0
  8. mcp_apollo_explorer-0.1.0/.idea/pyProjectModel.xml +7 -0
  9. mcp_apollo_explorer-0.1.0/.idea/workspace.xml +188 -0
  10. mcp_apollo_explorer-0.1.0/CLAUDE.md +36 -0
  11. mcp_apollo_explorer-0.1.0/PKG-INFO +7 -0
  12. mcp_apollo_explorer-0.1.0/README.md +88 -0
  13. mcp_apollo_explorer-0.1.0/pyproject.toml +26 -0
  14. mcp_apollo_explorer-0.1.0/scripts/probe.py +132 -0
  15. mcp_apollo_explorer-0.1.0/src/mcp_apollo_explorer/__init__.py +3 -0
  16. mcp_apollo_explorer-0.1.0/src/mcp_apollo_explorer/apollo_client.py +178 -0
  17. mcp_apollo_explorer-0.1.0/src/mcp_apollo_explorer/diff.py +220 -0
  18. mcp_apollo_explorer-0.1.0/src/mcp_apollo_explorer/server.py +202 -0
  19. mcp_apollo_explorer-0.1.0/src/mcp_apollo_explorer/xml_parent.py +117 -0
  20. mcp_apollo_explorer-0.1.0/tests/conftest.py +12 -0
  21. mcp_apollo_explorer-0.1.0/tests/test_apollo_client.py +48 -0
  22. mcp_apollo_explorer-0.1.0/tests/test_diff.py +286 -0
  23. mcp_apollo_explorer-0.1.0/tests/test_real_apollo.py +287 -0
  24. mcp_apollo_explorer-0.1.0/tests/test_time_filter.py +46 -0
  25. mcp_apollo_explorer-0.1.0/tests/test_xml_parent.py +90 -0
  26. mcp_apollo_explorer-0.1.0/uv.lock +1109 -0
@@ -0,0 +1,5 @@
1
+ # 默认忽略的文件
2
+ /shelf/
3
+ /workspace.xml
4
+ # 基于编辑器的 HTTP 客户端请求
5
+ /httpRequests/
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
4
+ </project>
@@ -0,0 +1,12 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="PyStubPackagesAdvertiser" enabled="true" level="WARNING" enabled_by_default="true">
5
+ <option name="ignoredPackages">
6
+ <list>
7
+ <option value="pandas" />
8
+ </list>
9
+ </option>
10
+ </inspection_tool>
11
+ </profile>
12
+ </component>
@@ -0,0 +1,6 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="USE_PROJECT_PROFILE" value="false" />
4
+ <version value="1.0" />
5
+ </settings>
6
+ </component>
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module external.system.id="pyproject.toml" type="PYTHON_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
6
+ <excludeFolder url="file://$MODULE_DIR$/.venv" />
7
+ </content>
8
+ <orderEntry type="jdk" jdkName="uv (mcp_apollo_explorer)" jdkType="Python SDK" />
9
+ <orderEntry type="sourceFolder" forTests="false" />
10
+ </component>
11
+ <component name="PyDocumentationSettings">
12
+ <option name="format" value="PLAIN" />
13
+ <option name="myDocStringFormat" value="Plain" />
14
+ </component>
15
+ </module>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Black">
4
+ <option name="sdkName" value="uv (mcp_apollo_explorer)" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/mcp-apollo-explorer.iml" filepath="$PROJECT_DIR$/.idea/mcp-apollo-explorer.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="PyProjectModelSettings">
4
+ <option name="showConfigurationNotification" value="false" />
5
+ <option name="usePyprojectToml" value="true" />
6
+ </component>
7
+ </project>
@@ -0,0 +1,188 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="AutoImportSettings">
4
+ <option name="autoReloadType" value="SELECTIVE" />
5
+ </component>
6
+ <component name="ChangeListManager">
7
+ <list default="true" id="0e28e81a-c2ac-4074-add8-dcf66e85c164" name="更改" comment="" />
8
+ <option name="SHOW_DIALOG" value="false" />
9
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
10
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
11
+ <option name="LAST_RESOLUTION" value="IGNORE" />
12
+ </component>
13
+ <component name="ProjectColorInfo">{
14
+ &quot;associatedIndex&quot;: 8,
15
+ &quot;fromUser&quot;: false
16
+ }</component>
17
+ <component name="ProjectId" id="3Gd4sHqTcMbQSggMtEdREUKslfm" />
18
+ <component name="ProjectViewState">
19
+ <option name="hideEmptyMiddlePackages" value="true" />
20
+ <option name="showLibraryContents" value="true" />
21
+ </component>
22
+ <component name="PropertiesComponent">{
23
+ &quot;keyToString&quot;: {
24
+ &quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
25
+ &quot;Python 测试.tests.test_apollo_client.test_list_namespaces_ok 的 Python 测试.executor&quot;: &quot;Run&quot;,
26
+ &quot;Python 测试.tests.test_apollo_client.test_missing_cookie_raises 的 Python 测试.executor&quot;: &quot;Run&quot;,
27
+ &quot;Python 测试.tests.test_real_apollo.test_change_history_user_filter_real 的 Python 测试.executor&quot;: &quot;Run&quot;,
28
+ &quot;Python 测试.tests.test_real_apollo.test_change_history_xml_parent_paths_real 的 Python 测试.executor&quot;: &quot;Run&quot;,
29
+ &quot;Python 测试.tests.test_real_apollo.test_diff_commit 的 Python 测试.executor&quot;: &quot;Run&quot;,
30
+ &quot;Python 测试.tests.test_real_apollo.test_fetch_commits_desc_by_time 的 Python 测试.executor&quot;: &quot;Run&quot;,
31
+ &quot;Python 测试.tests.test_real_apollo.test_fetch_releases_desc_by_time 的 Python 测试.executor&quot;: &quot;Run&quot;,
32
+ &quot;Python 测试.tests.test_real_apollo.test_get_namespace_content 的 Python 测试.executor&quot;: &quot;Run&quot;,
33
+ &quot;Python 测试.tests.test_real_apollo.test_get_namespace_content_xml 的 Python 测试.executor&quot;: &quot;Debug&quot;,
34
+ &quot;Python 测试.tests.test_real_apollo.test_list_namespaces_returns_non_empty_list 的 Python 测试.executor&quot;: &quot;Run&quot;,
35
+ &quot;Python 测试.tests.test_real_apollo.test_release_base_info_fields 的 Python 测试.executor&quot;: &quot;Run&quot;,
36
+ &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
37
+ &quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
38
+ &quot;RunOnceActivity.typescript.service.memoryLimit.init&quot;: &quot;true&quot;,
39
+ &quot;codeWithMe.voiceChat.enabledByDefault&quot;: &quot;false&quot;,
40
+ &quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
41
+ &quot;last_opened_file_path&quot;: &quot;D:/Coding/mcp-server/mcp_apollo_explorer&quot;,
42
+ &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
43
+ &quot;settings.editor.selected.configurable&quot;: &quot;preferences.customizations&quot;
44
+ }
45
+ }</component>
46
+ <component name="RunManager" selected="Python 测试.tests.test_real_apollo.test_change_history_xml_parent_paths_real 的 Python 测试">
47
+ <configuration name="tests.test_real_apollo.test_change_history_user_filter_real 的 Python 测试" type="tests" factoryName="Autodetect" temporary="true" nameIsGenerated="true">
48
+ <module name="mcp-apollo-explorer" />
49
+ <option name="ENV_FILES" value="" />
50
+ <option name="INTERPRETER_OPTIONS" value="" />
51
+ <option name="PARENT_ENVS" value="true" />
52
+ <option name="SDK_HOME" value="" />
53
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
54
+ <option name="IS_MODULE_SDK" value="true" />
55
+ <option name="ADD_CONTENT_ROOTS" value="true" />
56
+ <option name="ADD_SOURCE_ROOTS" value="true" />
57
+ <option name="DEBUG_JUST_MY_CODE" value="false" />
58
+ <option name="RUN_TOOL" value="" />
59
+ <option name="_new_additionalArguments" value="&quot;&quot;" />
60
+ <option name="_new_target" value="&quot;tests.test_real_apollo.test_change_history_user_filter_real&quot;" />
61
+ <option name="_new_targetType" value="&quot;PYTHON&quot;" />
62
+ <method v="2" />
63
+ </configuration>
64
+ <configuration name="tests.test_real_apollo.test_change_history_xml_parent_paths_real 的 Python 测试" type="tests" factoryName="Autodetect" temporary="true" nameIsGenerated="true">
65
+ <module name="mcp-apollo-explorer" />
66
+ <option name="ENV_FILES" value="" />
67
+ <option name="INTERPRETER_OPTIONS" value="" />
68
+ <option name="PARENT_ENVS" value="true" />
69
+ <option name="SDK_HOME" value="" />
70
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
71
+ <option name="IS_MODULE_SDK" value="true" />
72
+ <option name="ADD_CONTENT_ROOTS" value="true" />
73
+ <option name="ADD_SOURCE_ROOTS" value="true" />
74
+ <option name="DEBUG_JUST_MY_CODE" value="false" />
75
+ <option name="RUN_TOOL" value="" />
76
+ <option name="_new_additionalArguments" value="&quot;&quot;" />
77
+ <option name="_new_target" value="&quot;tests.test_real_apollo.test_change_history_xml_parent_paths_real&quot;" />
78
+ <option name="_new_targetType" value="&quot;PYTHON&quot;" />
79
+ <method v="2" />
80
+ </configuration>
81
+ <configuration name="tests.test_real_apollo.test_diff_commit 的 Python 测试" type="tests" factoryName="Autodetect" temporary="true" nameIsGenerated="true">
82
+ <module name="mcp-apollo-explorer" />
83
+ <option name="ENV_FILES" value="" />
84
+ <option name="INTERPRETER_OPTIONS" value="" />
85
+ <option name="PARENT_ENVS" value="true" />
86
+ <option name="SDK_HOME" value="" />
87
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
88
+ <option name="IS_MODULE_SDK" value="true" />
89
+ <option name="ADD_CONTENT_ROOTS" value="true" />
90
+ <option name="ADD_SOURCE_ROOTS" value="true" />
91
+ <option name="DEBUG_JUST_MY_CODE" value="false" />
92
+ <option name="RUN_TOOL" value="" />
93
+ <option name="_new_additionalArguments" value="&quot;&quot;" />
94
+ <option name="_new_target" value="&quot;tests.test_real_apollo.test_diff_commit&quot;" />
95
+ <option name="_new_targetType" value="&quot;PYTHON&quot;" />
96
+ <method v="2" />
97
+ </configuration>
98
+ <configuration name="tests.test_real_apollo.test_fetch_commits_desc_by_time 的 Python 测试" type="tests" factoryName="Autodetect" temporary="true" nameIsGenerated="true">
99
+ <module name="mcp-apollo-explorer" />
100
+ <option name="ENV_FILES" value="" />
101
+ <option name="INTERPRETER_OPTIONS" value="" />
102
+ <option name="PARENT_ENVS" value="true" />
103
+ <option name="SDK_HOME" value="" />
104
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
105
+ <option name="IS_MODULE_SDK" value="true" />
106
+ <option name="ADD_CONTENT_ROOTS" value="true" />
107
+ <option name="ADD_SOURCE_ROOTS" value="true" />
108
+ <option name="DEBUG_JUST_MY_CODE" value="false" />
109
+ <option name="RUN_TOOL" value="" />
110
+ <option name="_new_additionalArguments" value="&quot;&quot;" />
111
+ <option name="_new_target" value="&quot;tests.test_real_apollo.test_fetch_commits_desc_by_time&quot;" />
112
+ <option name="_new_targetType" value="&quot;PYTHON&quot;" />
113
+ <method v="2" />
114
+ </configuration>
115
+ <configuration name="tests.test_real_apollo.test_list_namespaces_returns_non_empty_list 的 Python 测试" type="tests" factoryName="Autodetect" temporary="true" nameIsGenerated="true">
116
+ <module name="mcp-apollo-explorer" />
117
+ <option name="ENV_FILES" value="" />
118
+ <option name="INTERPRETER_OPTIONS" value="" />
119
+ <option name="PARENT_ENVS" value="true" />
120
+ <option name="SDK_HOME" value="" />
121
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
122
+ <option name="IS_MODULE_SDK" value="true" />
123
+ <option name="ADD_CONTENT_ROOTS" value="true" />
124
+ <option name="ADD_SOURCE_ROOTS" value="true" />
125
+ <option name="DEBUG_JUST_MY_CODE" value="false" />
126
+ <option name="RUN_TOOL" value="" />
127
+ <option name="_new_additionalArguments" value="&quot;&quot;" />
128
+ <option name="_new_target" value="&quot;tests.test_real_apollo.test_list_namespaces_returns_non_empty_list&quot;" />
129
+ <option name="_new_targetType" value="&quot;PYTHON&quot;" />
130
+ <method v="2" />
131
+ </configuration>
132
+ <recent_temporary>
133
+ <list>
134
+ <item itemvalue="Python 测试.tests.test_real_apollo.test_change_history_xml_parent_paths_real 的 Python 测试" />
135
+ <item itemvalue="Python 测试.tests.test_real_apollo.test_diff_commit 的 Python 测试" />
136
+ <item itemvalue="Python 测试.tests.test_real_apollo.test_list_namespaces_returns_non_empty_list 的 Python 测试" />
137
+ <item itemvalue="Python 测试.tests.test_real_apollo.test_change_history_user_filter_real 的 Python 测试" />
138
+ <item itemvalue="Python 测试.tests.test_real_apollo.test_fetch_commits_desc_by_time 的 Python 测试" />
139
+ </list>
140
+ </recent_temporary>
141
+ </component>
142
+ <component name="SharedIndexes">
143
+ <attachedChunks>
144
+ <set>
145
+ <option value="bundled-python-sdk-c59985aa861c-c2ffad84badb-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-261.24374.152" />
146
+ </set>
147
+ </attachedChunks>
148
+ </component>
149
+ <component name="TaskManager">
150
+ <task active="true" id="Default" summary="默认任务">
151
+ <changelist id="0e28e81a-c2ac-4074-add8-dcf66e85c164" name="更改" comment="" />
152
+ <created>1784288318301</created>
153
+ <option name="number" value="Default" />
154
+ <option name="presentableId" value="Default" />
155
+ <updated>1784288318301</updated>
156
+ </task>
157
+ <servers />
158
+ </component>
159
+ <component name="TypeScriptGeneratedFilesManager">
160
+ <option name="version" value="3" />
161
+ </component>
162
+ <component name="XDebuggerManager">
163
+ <breakpoint-manager>
164
+ <breakpoints>
165
+ <line-breakpoint enabled="true" suspend="THREAD" type="python-line">
166
+ <url>file://$PROJECT_DIR$/tests/test_real_apollo.py</url>
167
+ <line>66</line>
168
+ <option name="timeStamp" value="6" />
169
+ </line-breakpoint>
170
+ <line-breakpoint enabled="true" suspend="THREAD" type="python-line">
171
+ <url>file://$PROJECT_DIR$/tests/test_real_apollo.py</url>
172
+ <line>93</line>
173
+ <option name="timeStamp" value="7" />
174
+ </line-breakpoint>
175
+ <line-breakpoint enabled="true" suspend="THREAD" type="python-line">
176
+ <url>file://$PROJECT_DIR$/tests/test_real_apollo.py</url>
177
+ <line>121</line>
178
+ <option name="timeStamp" value="8" />
179
+ </line-breakpoint>
180
+ <line-breakpoint enabled="true" suspend="THREAD" type="python-line">
181
+ <url>file://$PROJECT_DIR$/tests/test_real_apollo.py</url>
182
+ <line>119</line>
183
+ <option name="timeStamp" value="9" />
184
+ </line-breakpoint>
185
+ </breakpoints>
186
+ </breakpoint-manager>
187
+ </component>
188
+ </project>
@@ -0,0 +1,36 @@
1
+ # mcp-apollo-explorer
2
+
3
+ 基于 cookie 认证查询 Apollo 配置中心的 MCP 服务(Python)。
4
+
5
+ ## 技术约束
6
+ - Python 3.10+,全类型注解
7
+ - 依赖最小化:`mcp`(官方 SDK)、`httpx`;`difflib`、`xml.parsers.expat` 用标准库
8
+ - 不引入 web 框架、不引入 ORM
9
+
10
+ ## 三个工具
11
+ 1. `apollo_change_history` - 查变更历史(入参 app_ids 列表 + since,可选 until 截止时间 / user 筛改动人;基于 commit,user 筛改动人)
12
+ 2. `apollo_list_namespaces` - 根据 appId 查 namespace 数量/列表
13
+ 3. `apollo_get_namespace_content` - 根据 appId+namespace 查当前内容
14
+
15
+ ## 配置(环境变量)
16
+ - `APOLLO_COOKIE`(必需):Portal 登录 cookie 整段值
17
+ - `APOLLO_PORTAL_URL`(可选,默认 `http://config-portal.beisencorp.com`)
18
+ - env / cluster 作为工具参数,默认 `DEV` / `default`
19
+
20
+ ## 编码规范
21
+ - 工具返回结构化 JSON(dict / list),不返回大段文本
22
+ - 行 diff 用 `difflib`,输出 `{type, key, old_lines, new_lines, parent_paths}`;xml 文件型 namespace 的 `parent_paths` 为改动行所属 xml 元素的父路径(expat 按行号定位,如 `/root/config`),非 xml 为空列表
23
+ - 错误处理:cookie 失效(登录页检测)、网络超时、分页截断标注 `truncated`
24
+ - 安全:cookie 与配置值一律不得写入日志或异常消息
25
+ - 所有 HTTP 请求统一走 `apollo_client.py`,带 `Cookie` + `Accept: application/json`
26
+
27
+ ## Apollo API(已验证路径)
28
+ - namespace 列表:`GET /apps/{appId}/envs/{env}/clusters/{cluster}/namespaces`
29
+ - namespace 详情:`GET /apps/{appId}/envs/{env}/clusters/{cluster}/namespaces/{ns}`(返回完整 items;xml/json/yaml 等文件型 namespace 的 list 接口不回填 items,需走此详情接口)
30
+ - 提交历史:`GET /apps/{appId}/envs/{env}/clusters/{cluster}/namespaces/{ns}/commits?page={n}&size=200`
31
+ - commit 结构:`{dataChangeCreatedBy,dataChangeCreatedTime,changeSets}`,按时间倒序([0] 最新);`changeSets` 为 JSON 字符串(xml/文件型 namespace 实测)或 dict,解析后 `{createItems,updateItems,deleteItems}`,item 为 `{oldItem,newItem}`(各含 `{key,value}`)
32
+
33
+ ## 开发
34
+ - `uv sync` 装依赖
35
+ - `uvx --from . mcp-apollo-explorer` 本地跑
36
+ - 测试用真实 cookie 验证 3 个工具
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp-apollo-explorer
3
+ Version: 0.1.0
4
+ Summary: MCP server for exploring Apollo config center (history/namespace/content) via cookie auth
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: httpx>=0.27.0
7
+ Requires-Dist: mcp[cli]>=1.2.0
@@ -0,0 +1,88 @@
1
+ # mcp-apollo-explorer
2
+
3
+ 基于 cookie 认证查询 Apollo 配置中心(携程开源版)的 MCP 服务(Python)。
4
+
5
+ 提供 3 个工具:
6
+
7
+ 1. `apollo_change_history` - 查变更历史(入参 app_ids 列表 + since,可选 user 筛发布人)
8
+ 2. `apollo_list_namespaces` - 根据 appId 查 namespace 数量/列表
9
+ 3. `apollo_get_namespace_content` - 根据 appId + namespace 查当前内容
10
+
11
+ ## 依赖
12
+ - Python 3.10+
13
+ - Apollo Portal(携程开源版),需能通过浏览器登录拿到 cookie
14
+
15
+ ## 安装
16
+ ```bash
17
+ cd D:/Coding/mcp-server
18
+ uv sync
19
+ ```
20
+
21
+ ## 配置(环境变量)
22
+ | 变量 | 必需 | 说明 |
23
+ |------|------|------|
24
+ | `APOLLO_COOKIE` | 是 | Portal 登录后的 cookie 整段值 |
25
+ | `APOLLO_PORTAL_URL` | 否 | Portal 地址,默认 `http://config-portal.beisencorp.com` |
26
+
27
+ ### 获取 cookie
28
+ 1. 浏览器登录 Apollo Portal
29
+ 2. F12 -> Network -> 点任意请求 -> Request Headers 复制 `Cookie:` 整段值
30
+ 3. 去掉 `Cookie:` 前缀,只留值
31
+
32
+ > cookie 是登录态,等同账号权限,注意保密。失效后重新导出即可(无需账号密码)。
33
+
34
+ ## MCP 配置(.claude.json)
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "apollo-explorer": {
39
+ "command": "uvx",
40
+ "args": ["--from", "D:/Coding/mcp-server", "mcp-apollo-explorer"],
41
+ "env": {
42
+ "APOLLO_COOKIE": "<粘贴 cookie 值>"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ## 工具说明
50
+
51
+ ### apollo_list_namespaces
52
+ 根据 appId 查 namespace 数量与列表。
53
+ 参数:`app_id`(必填)、`env="DEV"`、`cluster="default"`
54
+ 返回:`{app_id, env, cluster, count, namespaces:[name,...]}`
55
+
56
+ ### apollo_get_namespace_content
57
+ 根据 appId + namespace 查当前配置内容。
58
+ 参数:`app_id`、`namespace`(必填)、`env="DEV"`、`cluster="default"`
59
+ 返回:`{app_id, namespace, env, cluster, format, items:[{key,value}]}`
60
+
61
+ ### apollo_change_history
62
+ 查变更历史:按 appId 列表查指定时间起的发布,每次发布含行级 diff。
63
+ 参数:
64
+ - `app_ids: list[str]`(必填)
65
+ - `since: str`(必填,ISO 日期如 `2026-06-07`)
66
+ - `env="DEV"`、`cluster="default"`
67
+ - `user: str | None`(可选,按发布人 userId 过滤)
68
+ - `max_releases_per_namespace=500`
69
+
70
+ 返回每个匹配发布的 `changes`:`{key, type:added|removed|modified, old_lines, new_lines}`,多行配置(如 XML)按行 diff,只返回变化的行。
71
+
72
+ > 在「谁改谁发」的环境下,发布人 = 修改人。若存在「你改别人发」,发布人维度会漏,需用 DB 查 commit。
73
+
74
+ ## 本地调试
75
+ ```bash
76
+ uv run mcp-apollo-explorer # 启动 stdio server
77
+ uv run mcp dev src/mcp_apollo_explorer/server.py # MCP inspector
78
+ ```
79
+
80
+ cookie 失效会报 `ApolloAuthError: Apollo returned login page...`,重新导出 cookie 即可。
81
+
82
+ ## 项目结构
83
+ ```
84
+ src/mcp_apollo_explorer/
85
+ server.py # FastMCP + 3 工具 + main
86
+ apollo_client.py # Portal API(cookie 认证、分页、登录页检测)
87
+ diff.py # difflib 行级 diff
88
+ ```
@@ -0,0 +1,26 @@
1
+ [project]
2
+ name = "mcp-apollo-explorer"
3
+ version = "0.1.0"
4
+ description = "MCP server for exploring Apollo config center (history/namespace/content) via cookie auth"
5
+ requires-python = ">=3.10"
6
+ dependencies = [
7
+ "mcp[cli]>=1.2.0",
8
+ "httpx>=0.27.0",
9
+ ]
10
+
11
+ [project.scripts]
12
+ mcp-apollo-explorer = "mcp_apollo_explorer.server:main"
13
+
14
+ [build-system]
15
+ requires = ["hatchling"]
16
+ build-backend = "hatchling.build"
17
+
18
+ [tool.hatch.build.targets.wheel]
19
+ packages = ["src/mcp_apollo_explorer"]
20
+
21
+ [dependency-groups]
22
+ dev = ["pytest>=8.0"]
23
+
24
+ [tool.pytest.ini_options]
25
+ testpaths = ["tests"]
26
+ pythonpath = ["src"]
@@ -0,0 +1,132 @@
1
+ """真实请求 Apollo 探测脚本:跑通三个工具并打印返回值。
2
+
3
+ 用法:
4
+ # 先把整段 cookie 放进环境变量(三种方式任选其一)
5
+ export APOLLO_COOKIE='你的cookie整段' # 当前 shell
6
+ # 或一次性前缀:
7
+ APOLLO_COOKIE='...' uv run python scripts/probe.py <app_id>
8
+ # 或写到系统环境变量后重启终端
9
+
10
+ uv run python scripts/probe.py <app_id> [--env DEV] [--cluster default] [--namespace NS] [--full]
11
+
12
+ 安全:
13
+ - cookie 仅从环境变量读取,绝不打印。
14
+ - 配置 value / diff 行默认脱敏(只显示字符数),--full 才显示明文。
15
+ 明文可能含密钥,如需贴回对话请用默认脱敏模式。
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import argparse
21
+ import json
22
+ import sys
23
+
24
+ from mcp_apollo_explorer.apollo_client import ApolloAuthError, ApolloClient
25
+
26
+
27
+ def mask(v: object, full: bool) -> object:
28
+ if full or v is None:
29
+ return v
30
+ return f"<{len(str(v))} chars>"
31
+
32
+
33
+ def mask_items(items: list[dict], full: bool) -> list[dict]:
34
+ return [{"key": i.get("key"), "value": mask(i.get("value"), full)} for i in items]
35
+
36
+
37
+ def mask_changes(changes: list[dict], full: bool) -> list[dict]:
38
+ return [
39
+ {
40
+ "key": c["key"],
41
+ "type": c["type"],
42
+ "old_lines": [mask(l, full) for l in c["old_lines"]],
43
+ "new_lines": [mask(l, full) for l in c["new_lines"]],
44
+ }
45
+ for c in changes
46
+ ]
47
+
48
+
49
+ def main() -> None:
50
+ ap = argparse.ArgumentParser(description="Apollo 真实探测")
51
+ ap.add_argument("app_id")
52
+ ap.add_argument("--env", default="DEV")
53
+ ap.add_argument("--cluster", default="default")
54
+ ap.add_argument("--namespace", default=None, help="指定 namespace,默认取列表第一个")
55
+ ap.add_argument("--full", action="store_true", help="显示配置明文(默认脱敏)")
56
+ ap.add_argument("--releases", type=int, default=5, help="拉取的 release 条数,默认 5")
57
+ args = ap.parse_args()
58
+
59
+ try:
60
+ client = ApolloClient()
61
+ except ApolloAuthError as e:
62
+ print(f"[auth] {e}", file=sys.stderr)
63
+ print("请先设置 APOLLO_COOKIE 环境变量。", file=sys.stderr)
64
+ sys.exit(2)
65
+
66
+ print(f"portal : {client.portal_url}")
67
+ print(f"cookie : <set, {len(client.cookie)} chars>")
68
+ print(f"app_id : {args.app_id} env={args.env} cluster={args.cluster} full={args.full}")
69
+ print("=" * 70)
70
+
71
+ # 1. list_namespaces
72
+ print("\n[1] list_namespaces")
73
+ try:
74
+ ns_list = client.list_namespaces(args.app_id, args.env, args.cluster)
75
+ except Exception as e: # noqa: BLE001
76
+ print(f" ERROR: {e}", file=sys.stderr)
77
+ sys.exit(1)
78
+ names = [client._ns_name(n) for n in ns_list]
79
+ print(json.dumps({"count": len(names), "namespaces": names}, ensure_ascii=False, indent=2))
80
+ if not names:
81
+ print("无 namespace,结束。")
82
+ return
83
+ ns = args.namespace or names[0]
84
+
85
+ # 2. get_namespace_content
86
+ print(f"\n[2] get_namespace_content namespace={ns}")
87
+ try:
88
+ content = client.get_namespace_content(args.app_id, ns, args.env, args.cluster)
89
+ except Exception as e: # noqa: BLE001
90
+ print(f" ERROR: {e}", file=sys.stderr)
91
+ else:
92
+ view = {k: content.get(k) for k in ("app_id", "namespace", "env", "cluster", "format")}
93
+ view["items"] = mask_items(content.get("items") or [], args.full)
94
+ print(json.dumps(view, ensure_ascii=False, indent=2))
95
+
96
+ # 3. fetch_releases
97
+ print(f"\n[3] fetch_releases namespace={ns} max={args.releases}")
98
+ try:
99
+ rels = client.fetch_releases(
100
+ args.app_id, ns, args.env, args.cluster, max_releases=args.releases
101
+ )
102
+ except Exception as e: # noqa: BLE001
103
+ print(f" ERROR: {e}", file=sys.stderr)
104
+ return
105
+ print(f" 共 {len(rels)} 条 (倒序,[0] 最新)")
106
+ for idx, r in enumerate(rels[: args.releases]):
107
+ b = r.get("baseInfo") or {}
108
+ items = r.get("items") or []
109
+ print(
110
+ f" [{idx}] {b.get('dataChangeCreatedTime')} "
111
+ f"by={b.get('dataChangeCreatedBy')} name={b.get('name')} "
112
+ f"abandoned={b.get('isAbandoned')} items={len(items)}"
113
+ )
114
+ print(f" comment: {b.get('comment')}")
115
+ print(f" keys : {[i.get('key') for i in items]}")
116
+
117
+ # 4. 最近一条相对前一条的 diff
118
+ if len(rels) >= 1:
119
+ from mcp_apollo_explorer.diff import diff_items
120
+
121
+ print(f"\n[4] diff 最新一条 vs 前一条")
122
+ cur = rels[0]
123
+ prev = rels[1] if len(rels) > 1 else None
124
+ changes = diff_items(
125
+ (prev.get("items") if prev else None),
126
+ cur.get("items") or [],
127
+ )
128
+ print(json.dumps(mask_changes(changes, args.full), ensure_ascii=False, indent=2))
129
+
130
+
131
+ if __name__ == "__main__":
132
+ main()
@@ -0,0 +1,3 @@
1
+ """mcp-apollo-explorer: Apollo config center explorer MCP server."""
2
+
3
+ __version__ = "0.1.0"