800bus-mcp 1.0.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,25 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 800BUS
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.
22
+
23
+ 注意:本许可证仅覆盖本包的代码。通过本包获取的 800BUS 站点数据与图片
24
+ 遵循 CC BY-NC-ND 4.0(署名 800BUS、非商业用途、禁止演绎),且禁止用于
25
+ 模型训练或批量镜像。
@@ -0,0 +1,144 @@
1
+ Metadata-Version: 2.4
2
+ Name: 800bus-mcp
3
+ Version: 1.0.0
4
+ Summary: 800BUS 公交图片档案站的 MCP 服务(只读):让 AI 检索档案、资讯、地区、公司、用户公开资料、排行榜与地图标记
5
+ Author: 800BUS
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://800bus.cn/openapi/
8
+ Project-URL: Documentation, https://800bus.cn/yszc/openapi.html
9
+ Keywords: mcp,model-context-protocol,800bus,bus,photo,archive,openapi
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: mcp>=1.2.0
22
+ Requires-Dist: httpx>=0.27.0
23
+ Dynamic: license-file
24
+
25
+ # 800bus-mcp
26
+
27
+ 800BUS([800bus.cn](https://800bus.cn))公交图片档案站的 **MCP 服务**,把站点只读开放接口(`/api/v1`,12 个 GET 端点)封装成 AI 可直接调用的工具。
28
+
29
+ - 全部接口只读、匿名、无副作用:不会写入数据、不会发通知、不会改动任何状态
30
+ - 默认 60 次/分钟/IP 限流,超限时工具会返回 `rate_limited` 提示
31
+ - 不返回邮箱、精确拍摄地点、审核人等隐私字段;匿名评论统一显示为「匿名用户」
32
+ - 图片与数据遵循 **CC BY-NC-ND 4.0**:非商业用途、署名 800BUS、禁止演绎,禁止用于模型训练或批量镜像
33
+
34
+ ## 安装
35
+
36
+ ```bash
37
+ pip install 800bus-mcp
38
+ # 或免安装直接运行
39
+ uvx 800bus-mcp
40
+ ```
41
+
42
+ 要求 Python ≥ 3.10。
43
+
44
+ ## 配置
45
+
46
+ ### Claude Desktop
47
+
48
+ 编辑 `claude_desktop_config.json`(macOS/Linux:`~/Library/Application Support/Claude/`;Windows:`%APPDATA%\Claude\`):
49
+
50
+ ```json
51
+ {
52
+ "mcpServers": {
53
+ "800bus": {
54
+ "command": "uvx",
55
+ "args": ["800bus-mcp"]
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ 已用 pip 安装时也可以写成:
62
+
63
+ ```json
64
+ {
65
+ "mcpServers": {
66
+ "800bus": {
67
+ "command": "800bus-mcp"
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ ### Cursor
74
+
75
+ 在 `~/.cursor/mcp.json`(项目级为 `.cursor/mcp.json`)中加入:
76
+
77
+ ```json
78
+ {
79
+ "mcpServers": {
80
+ "800bus": {
81
+ "command": "uvx",
82
+ "args": ["800bus-mcp"]
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ ### 其他 MCP 客户端
89
+
90
+ 本服务使用 **stdio** 传输,启动命令 `800bus-mcp`(或 `python -m bus800_mcp`)即可。
91
+
92
+ ## 环境变量
93
+
94
+ | 变量 | 默认值 | 说明 |
95
+ | --- | --- | --- |
96
+ | `800BUS_API_BASE` | `https://800bus.cn` | 接口根地址,仅在自建反代或本地调试时需要修改 |
97
+
98
+ ## 可用工具
99
+
100
+ | 工具 | 对应接口 | 说明 |
101
+ | --- | --- | --- |
102
+ | `get_site_stats` | `/api/v1/stats` | 站点统计:档案、用户、公司、资讯、评论总数与分区分布 |
103
+ | `get_meta` | `/api/v1/meta` | 参数字典:分区 key、站点、排序、分页上限 |
104
+ | `list_photos` | `/api/v1/photos` | 按关键词 / 车牌 / 分区 / 地区 / 公司 / 作者筛选档案 |
105
+ | `get_photo` | `/api/v1/photos/{id}` | 档案详情(不含拍摄地点) |
106
+ | `get_photo_comments` | `/api/v1/photos/{id}/comments` | 档案评论与嵌套回复 |
107
+ | `list_news` | `/api/v1/news` | 已发布资讯列表(仅摘要) |
108
+ | `get_news` | `/api/v1/news/{id}` | 资讯详情(Markdown 正文) |
109
+ | `list_regions` | `/api/v1/regions` | 省 / 市 / 公司三级层级 |
110
+ | `get_company` | `/api/v1/companies/{id}` | 公司资料与作品列表 |
111
+ | `get_user` | `/api/v1/users/{id}` | 用户公开资料(无邮箱) |
112
+ | `get_rankings` | `/api/v1/rankings` | 每周最热档案 / 月度贡献者 |
113
+ | `get_map_markers` | `/api/v1/map/markers` | 矩形范围内允许公开位置的档案标记 |
114
+
115
+ 工具返回值为紧凑 JSON 文本,结构与开放接口的 `success / data / meta` 信封一致。
116
+
117
+ ## 使用示例
118
+
119
+ 配置好之后,可以直接对 AI 说:
120
+
121
+ - 「查一下 800BUS 现在有多少张档案、今日新增多少」
122
+ - 「搜索南昌的宇通客车档案,按评分排序,给我前 5 张」
123
+ - 「看看档案 14222 的详情和评论」
124
+ - 「读完最新的资讯,再列出江西省有哪些城市和公司」
125
+ - 「取南昌附近地图上的档案标记,按分区筛选」
126
+
127
+ 建议 AI 先调用 `get_meta` 拿到合法的分区 key 与排序枚举,再用 `list_photos` 检索,避免参数非法。
128
+
129
+ ## 自检与版本
130
+
131
+ ```bash
132
+ 800bus-mcp --version # 打印版本
133
+ 800bus-mcp --selftest # 请求 /api/v1/stats,验证网络与接口连通性
134
+ ```
135
+
136
+ ## 相关链接
137
+
138
+ - 开放平台说明:<https://800bus.cn/openapi/>
139
+ - 接口文档(OpenAPI 3.1):<https://800bus.cn/yszc/openapi.html>
140
+ - 问题反馈:站内工单 <https://800bus.cn/ticket.php>
141
+
142
+ ## 许可证
143
+
144
+ 本包代码使用 MIT 许可证,详见 `LICENSE`。通过本包获取的站点数据与图片遵循 CC BY-NC-ND 4.0。
@@ -0,0 +1,120 @@
1
+ # 800bus-mcp
2
+
3
+ 800BUS([800bus.cn](https://800bus.cn))公交图片档案站的 **MCP 服务**,把站点只读开放接口(`/api/v1`,12 个 GET 端点)封装成 AI 可直接调用的工具。
4
+
5
+ - 全部接口只读、匿名、无副作用:不会写入数据、不会发通知、不会改动任何状态
6
+ - 默认 60 次/分钟/IP 限流,超限时工具会返回 `rate_limited` 提示
7
+ - 不返回邮箱、精确拍摄地点、审核人等隐私字段;匿名评论统一显示为「匿名用户」
8
+ - 图片与数据遵循 **CC BY-NC-ND 4.0**:非商业用途、署名 800BUS、禁止演绎,禁止用于模型训练或批量镜像
9
+
10
+ ## 安装
11
+
12
+ ```bash
13
+ pip install 800bus-mcp
14
+ # 或免安装直接运行
15
+ uvx 800bus-mcp
16
+ ```
17
+
18
+ 要求 Python ≥ 3.10。
19
+
20
+ ## 配置
21
+
22
+ ### Claude Desktop
23
+
24
+ 编辑 `claude_desktop_config.json`(macOS/Linux:`~/Library/Application Support/Claude/`;Windows:`%APPDATA%\Claude\`):
25
+
26
+ ```json
27
+ {
28
+ "mcpServers": {
29
+ "800bus": {
30
+ "command": "uvx",
31
+ "args": ["800bus-mcp"]
32
+ }
33
+ }
34
+ }
35
+ ```
36
+
37
+ 已用 pip 安装时也可以写成:
38
+
39
+ ```json
40
+ {
41
+ "mcpServers": {
42
+ "800bus": {
43
+ "command": "800bus-mcp"
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ### Cursor
50
+
51
+ 在 `~/.cursor/mcp.json`(项目级为 `.cursor/mcp.json`)中加入:
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "800bus": {
57
+ "command": "uvx",
58
+ "args": ["800bus-mcp"]
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ ### 其他 MCP 客户端
65
+
66
+ 本服务使用 **stdio** 传输,启动命令 `800bus-mcp`(或 `python -m bus800_mcp`)即可。
67
+
68
+ ## 环境变量
69
+
70
+ | 变量 | 默认值 | 说明 |
71
+ | --- | --- | --- |
72
+ | `800BUS_API_BASE` | `https://800bus.cn` | 接口根地址,仅在自建反代或本地调试时需要修改 |
73
+
74
+ ## 可用工具
75
+
76
+ | 工具 | 对应接口 | 说明 |
77
+ | --- | --- | --- |
78
+ | `get_site_stats` | `/api/v1/stats` | 站点统计:档案、用户、公司、资讯、评论总数与分区分布 |
79
+ | `get_meta` | `/api/v1/meta` | 参数字典:分区 key、站点、排序、分页上限 |
80
+ | `list_photos` | `/api/v1/photos` | 按关键词 / 车牌 / 分区 / 地区 / 公司 / 作者筛选档案 |
81
+ | `get_photo` | `/api/v1/photos/{id}` | 档案详情(不含拍摄地点) |
82
+ | `get_photo_comments` | `/api/v1/photos/{id}/comments` | 档案评论与嵌套回复 |
83
+ | `list_news` | `/api/v1/news` | 已发布资讯列表(仅摘要) |
84
+ | `get_news` | `/api/v1/news/{id}` | 资讯详情(Markdown 正文) |
85
+ | `list_regions` | `/api/v1/regions` | 省 / 市 / 公司三级层级 |
86
+ | `get_company` | `/api/v1/companies/{id}` | 公司资料与作品列表 |
87
+ | `get_user` | `/api/v1/users/{id}` | 用户公开资料(无邮箱) |
88
+ | `get_rankings` | `/api/v1/rankings` | 每周最热档案 / 月度贡献者 |
89
+ | `get_map_markers` | `/api/v1/map/markers` | 矩形范围内允许公开位置的档案标记 |
90
+
91
+ 工具返回值为紧凑 JSON 文本,结构与开放接口的 `success / data / meta` 信封一致。
92
+
93
+ ## 使用示例
94
+
95
+ 配置好之后,可以直接对 AI 说:
96
+
97
+ - 「查一下 800BUS 现在有多少张档案、今日新增多少」
98
+ - 「搜索南昌的宇通客车档案,按评分排序,给我前 5 张」
99
+ - 「看看档案 14222 的详情和评论」
100
+ - 「读完最新的资讯,再列出江西省有哪些城市和公司」
101
+ - 「取南昌附近地图上的档案标记,按分区筛选」
102
+
103
+ 建议 AI 先调用 `get_meta` 拿到合法的分区 key 与排序枚举,再用 `list_photos` 检索,避免参数非法。
104
+
105
+ ## 自检与版本
106
+
107
+ ```bash
108
+ 800bus-mcp --version # 打印版本
109
+ 800bus-mcp --selftest # 请求 /api/v1/stats,验证网络与接口连通性
110
+ ```
111
+
112
+ ## 相关链接
113
+
114
+ - 开放平台说明:<https://800bus.cn/openapi/>
115
+ - 接口文档(OpenAPI 3.1):<https://800bus.cn/yszc/openapi.html>
116
+ - 问题反馈:站内工单 <https://800bus.cn/ticket.php>
117
+
118
+ ## 许可证
119
+
120
+ 本包代码使用 MIT 许可证,详见 `LICENSE`。通过本包获取的站点数据与图片遵循 CC BY-NC-ND 4.0。
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "800bus-mcp"
7
+ version = "1.0.0"
8
+ description = "800BUS 公交图片档案站的 MCP 服务(只读):让 AI 检索档案、资讯、地区、公司、用户公开资料、排行榜与地图标记"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "800BUS" }]
14
+ keywords = ["mcp", "model-context-protocol", "800bus", "bus", "photo", "archive", "openapi"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Topic :: Internet :: WWW/HTTP :: Indexing/Search",
24
+ ]
25
+ dependencies = [
26
+ "mcp>=1.2.0",
27
+ "httpx>=0.27.0",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://800bus.cn/openapi/"
32
+ Documentation = "https://800bus.cn/yszc/openapi.html"
33
+
34
+ [project.scripts]
35
+ 800bus-mcp = "bus800_mcp.server:main"
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,144 @@
1
+ Metadata-Version: 2.4
2
+ Name: 800bus-mcp
3
+ Version: 1.0.0
4
+ Summary: 800BUS 公交图片档案站的 MCP 服务(只读):让 AI 检索档案、资讯、地区、公司、用户公开资料、排行榜与地图标记
5
+ Author: 800BUS
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://800bus.cn/openapi/
8
+ Project-URL: Documentation, https://800bus.cn/yszc/openapi.html
9
+ Keywords: mcp,model-context-protocol,800bus,bus,photo,archive,openapi
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: mcp>=1.2.0
22
+ Requires-Dist: httpx>=0.27.0
23
+ Dynamic: license-file
24
+
25
+ # 800bus-mcp
26
+
27
+ 800BUS([800bus.cn](https://800bus.cn))公交图片档案站的 **MCP 服务**,把站点只读开放接口(`/api/v1`,12 个 GET 端点)封装成 AI 可直接调用的工具。
28
+
29
+ - 全部接口只读、匿名、无副作用:不会写入数据、不会发通知、不会改动任何状态
30
+ - 默认 60 次/分钟/IP 限流,超限时工具会返回 `rate_limited` 提示
31
+ - 不返回邮箱、精确拍摄地点、审核人等隐私字段;匿名评论统一显示为「匿名用户」
32
+ - 图片与数据遵循 **CC BY-NC-ND 4.0**:非商业用途、署名 800BUS、禁止演绎,禁止用于模型训练或批量镜像
33
+
34
+ ## 安装
35
+
36
+ ```bash
37
+ pip install 800bus-mcp
38
+ # 或免安装直接运行
39
+ uvx 800bus-mcp
40
+ ```
41
+
42
+ 要求 Python ≥ 3.10。
43
+
44
+ ## 配置
45
+
46
+ ### Claude Desktop
47
+
48
+ 编辑 `claude_desktop_config.json`(macOS/Linux:`~/Library/Application Support/Claude/`;Windows:`%APPDATA%\Claude\`):
49
+
50
+ ```json
51
+ {
52
+ "mcpServers": {
53
+ "800bus": {
54
+ "command": "uvx",
55
+ "args": ["800bus-mcp"]
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ 已用 pip 安装时也可以写成:
62
+
63
+ ```json
64
+ {
65
+ "mcpServers": {
66
+ "800bus": {
67
+ "command": "800bus-mcp"
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ ### Cursor
74
+
75
+ 在 `~/.cursor/mcp.json`(项目级为 `.cursor/mcp.json`)中加入:
76
+
77
+ ```json
78
+ {
79
+ "mcpServers": {
80
+ "800bus": {
81
+ "command": "uvx",
82
+ "args": ["800bus-mcp"]
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ ### 其他 MCP 客户端
89
+
90
+ 本服务使用 **stdio** 传输,启动命令 `800bus-mcp`(或 `python -m bus800_mcp`)即可。
91
+
92
+ ## 环境变量
93
+
94
+ | 变量 | 默认值 | 说明 |
95
+ | --- | --- | --- |
96
+ | `800BUS_API_BASE` | `https://800bus.cn` | 接口根地址,仅在自建反代或本地调试时需要修改 |
97
+
98
+ ## 可用工具
99
+
100
+ | 工具 | 对应接口 | 说明 |
101
+ | --- | --- | --- |
102
+ | `get_site_stats` | `/api/v1/stats` | 站点统计:档案、用户、公司、资讯、评论总数与分区分布 |
103
+ | `get_meta` | `/api/v1/meta` | 参数字典:分区 key、站点、排序、分页上限 |
104
+ | `list_photos` | `/api/v1/photos` | 按关键词 / 车牌 / 分区 / 地区 / 公司 / 作者筛选档案 |
105
+ | `get_photo` | `/api/v1/photos/{id}` | 档案详情(不含拍摄地点) |
106
+ | `get_photo_comments` | `/api/v1/photos/{id}/comments` | 档案评论与嵌套回复 |
107
+ | `list_news` | `/api/v1/news` | 已发布资讯列表(仅摘要) |
108
+ | `get_news` | `/api/v1/news/{id}` | 资讯详情(Markdown 正文) |
109
+ | `list_regions` | `/api/v1/regions` | 省 / 市 / 公司三级层级 |
110
+ | `get_company` | `/api/v1/companies/{id}` | 公司资料与作品列表 |
111
+ | `get_user` | `/api/v1/users/{id}` | 用户公开资料(无邮箱) |
112
+ | `get_rankings` | `/api/v1/rankings` | 每周最热档案 / 月度贡献者 |
113
+ | `get_map_markers` | `/api/v1/map/markers` | 矩形范围内允许公开位置的档案标记 |
114
+
115
+ 工具返回值为紧凑 JSON 文本,结构与开放接口的 `success / data / meta` 信封一致。
116
+
117
+ ## 使用示例
118
+
119
+ 配置好之后,可以直接对 AI 说:
120
+
121
+ - 「查一下 800BUS 现在有多少张档案、今日新增多少」
122
+ - 「搜索南昌的宇通客车档案,按评分排序,给我前 5 张」
123
+ - 「看看档案 14222 的详情和评论」
124
+ - 「读完最新的资讯,再列出江西省有哪些城市和公司」
125
+ - 「取南昌附近地图上的档案标记,按分区筛选」
126
+
127
+ 建议 AI 先调用 `get_meta` 拿到合法的分区 key 与排序枚举,再用 `list_photos` 检索,避免参数非法。
128
+
129
+ ## 自检与版本
130
+
131
+ ```bash
132
+ 800bus-mcp --version # 打印版本
133
+ 800bus-mcp --selftest # 请求 /api/v1/stats,验证网络与接口连通性
134
+ ```
135
+
136
+ ## 相关链接
137
+
138
+ - 开放平台说明:<https://800bus.cn/openapi/>
139
+ - 接口文档(OpenAPI 3.1):<https://800bus.cn/yszc/openapi.html>
140
+ - 问题反馈:站内工单 <https://800bus.cn/ticket.php>
141
+
142
+ ## 许可证
143
+
144
+ 本包代码使用 MIT 许可证,详见 `LICENSE`。通过本包获取的站点数据与图片遵循 CC BY-NC-ND 4.0。
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/800bus_mcp.egg-info/PKG-INFO
5
+ src/800bus_mcp.egg-info/SOURCES.txt
6
+ src/800bus_mcp.egg-info/dependency_links.txt
7
+ src/800bus_mcp.egg-info/entry_points.txt
8
+ src/800bus_mcp.egg-info/requires.txt
9
+ src/800bus_mcp.egg-info/top_level.txt
10
+ src/bus800_mcp/__init__.py
11
+ src/bus800_mcp/__main__.py
12
+ src/bus800_mcp/client.py
13
+ src/bus800_mcp/server.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ 800bus-mcp = bus800_mcp.server:main
@@ -0,0 +1,2 @@
1
+ mcp>=1.2.0
2
+ httpx>=0.27.0
@@ -0,0 +1 @@
1
+ bus800_mcp
@@ -0,0 +1,4 @@
1
+ """800BUS MCP 服务包。"""
2
+
3
+ __version__ = "1.0.0"
4
+ __all__ = ["__version__"]
@@ -0,0 +1,6 @@
1
+ """支持 `python -m bus800_mcp`。"""
2
+
3
+ from .server import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,104 @@
1
+ """800BUS 开放接口(/api/v1)的极简 HTTP 客户端。
2
+
3
+ 约定:
4
+ - 全部为 GET 请求,匿名调用,无需 API Key;
5
+ - 默认 60 次/分钟/IP,超限返回 429;
6
+ - 响应信封为 {"success": true, "data": ..., "meta": ...} 或
7
+ {"success": false, "error": {"code": ..., "message": ...}}。
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ from typing import Any, Mapping
14
+
15
+ import httpx
16
+
17
+ from . import __version__
18
+
19
+ DEFAULT_BASE_URL = "https://800bus.cn"
20
+ TIMEOUT_SECONDS = 20.0
21
+ USER_AGENT = f"800bus-mcp/{__version__} (+https://800bus.cn/openapi/)"
22
+
23
+ _client: httpx.Client | None = None
24
+
25
+
26
+ class ApiError(Exception):
27
+ """接口调用失败的统一异常,code 与开放接口的 error.code 保持一致。"""
28
+
29
+ def __init__(self, code: str, message: str) -> None:
30
+ super().__init__(message)
31
+ self.code = code
32
+
33
+
34
+ def base_url() -> str:
35
+ """接口根地址,可用环境变量 800BUS_API_BASE 覆盖(自建反代 / 调试用)。"""
36
+ raw = os.environ.get("800BUS_API_BASE") or DEFAULT_BASE_URL
37
+ return raw.strip().rstrip("/")
38
+
39
+
40
+ def _get_client() -> httpx.Client:
41
+ global _client
42
+ if _client is None:
43
+ _client = httpx.Client(
44
+ base_url=base_url(),
45
+ timeout=TIMEOUT_SECONDS,
46
+ headers={"User-Agent": USER_AGENT, "Accept": "application/json"},
47
+ follow_redirects=True,
48
+ )
49
+ return _client
50
+
51
+
52
+ def _clean_params(params: Mapping[str, Any] | None) -> dict[str, Any]:
53
+ """去掉 None,布尔值转小写字符串(PHP 侧按字符串判断)。"""
54
+ out: dict[str, Any] = {}
55
+ for key, value in (params or {}).items():
56
+ if value is None:
57
+ continue
58
+ if isinstance(value, bool):
59
+ out[key] = "true" if value else "false"
60
+ else:
61
+ out[key] = value
62
+ return out
63
+
64
+
65
+ def get_json(path: str, params: Mapping[str, Any] | None = None) -> dict[str, Any]:
66
+ """请求一个只读接口并返回解析后的 JSON 信封。失败时抛出 ApiError。"""
67
+ client = _get_client()
68
+ try:
69
+ response = client.get(path, params=_clean_params(params))
70
+ except httpx.TimeoutException:
71
+ raise ApiError("timeout", f"请求 {base_url()} 超时({int(TIMEOUT_SECONDS)} 秒),请稍后重试") from None
72
+ except httpx.HTTPError as exc:
73
+ raise ApiError("network_error", f"网络请求失败:{exc}") from None
74
+
75
+ if response.status_code == 429:
76
+ retry_after = response.headers.get("Retry-After") or "60"
77
+ raise ApiError(
78
+ "rate_limited",
79
+ f"请求过于频繁(默认 60 次/分钟/IP),请等待约 {retry_after} 秒后重试",
80
+ )
81
+
82
+ try:
83
+ payload = response.json()
84
+ except ValueError:
85
+ # 站点的 nginx 会把 404 响应替换为 HTML 错误页,这里兜底成可读提示
86
+ if response.status_code == 404:
87
+ raise ApiError("not_found", "资源不存在或已下架(HTTP 404)") from None
88
+ raise ApiError("bad_response", f"接口返回了非 JSON 内容(HTTP {response.status_code})") from None
89
+
90
+ if response.status_code >= 400 or not payload.get("success"):
91
+ error = payload.get("error") or {}
92
+ code = str(error.get("code") or f"http_{response.status_code}")
93
+ message = str(error.get("message") or f"接口返回 HTTP {response.status_code}")
94
+ raise ApiError(code, message)
95
+
96
+ return payload
97
+
98
+
99
+ def close() -> None:
100
+ """关闭底层连接池(主要用于测试)。"""
101
+ global _client
102
+ if _client is not None:
103
+ _client.close()
104
+ _client = None
@@ -0,0 +1,302 @@
1
+ """800BUS MCP 服务(stdio 传输)。
2
+
3
+ 把 800bus.cn 的只读 OpenAPI(12 个 GET 接口)封装成 MCP 工具,
4
+ 所有工具都没有副作用:不写数据、不发通知、不改状态。
5
+
6
+ 配置方式(Claude Desktop / Cursor 等)见 README.md。
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import sys
13
+ from typing import Any
14
+
15
+ try: # mcp 2.x 将 FastMCP 重命名为 MCPServer
16
+ from mcp.server.mcpserver import MCPServer as _MCPServer
17
+ except ImportError: # mcp 1.x
18
+ from mcp.server.fastmcp import FastMCP as _MCPServer
19
+
20
+ from . import __version__
21
+ from .client import ApiError, base_url, get_json
22
+
23
+ mcp = _MCPServer(
24
+ "800bus",
25
+ instructions=(
26
+ "800BUS(800bus.cn)公交图片档案站的只读开放接口。"
27
+ "所有工具均无副作用。"
28
+ "检索前建议先调用 get_meta 获取分区 key(section)与排序枚举(sort),再调用 list_photos。"
29
+ "接口默认 60 次/分钟/IP,超限会返回 rate_limited 提示,请降低调用频率。"
30
+ "不返回邮箱、精确拍摄地点、审核人等隐私字段;匿名评论固定显示为「匿名用户」。"
31
+ "图片遵循 CC BY-NC-ND 4.0:非商业、署名 800BUS、禁止演绎,禁止用于模型训练或批量镜像。"
32
+ ),
33
+ log_level="WARNING",
34
+ )
35
+
36
+
37
+ def _dump(payload: Any) -> str:
38
+ """紧凑输出,减少上下文占用。"""
39
+ return json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
40
+
41
+
42
+ def _call(path: str, params: dict[str, Any] | None = None) -> str:
43
+ """统一请求 + 错误包装,保证工具永远返回可读文本而不是抛异常。"""
44
+ try:
45
+ return _dump(get_json(path, params))
46
+ except ApiError as exc:
47
+ return _dump(
48
+ {
49
+ "success": False,
50
+ "error": {"code": exc.code, "message": str(exc)},
51
+ }
52
+ )
53
+
54
+
55
+ @mcp.tool()
56
+ def get_site_stats() -> str:
57
+ """获取 800BUS 站点统计:档案、用户、公司、资讯、评论总数,今日上传数以及分区分布。无参数。"""
58
+ return _call("/api/v1/stats")
59
+
60
+
61
+ @mcp.tool()
62
+ def get_meta() -> str:
63
+ """获取参数字典:分区 key(sections / draw_sections)、站点(sites)、排序(sorts)与分页上限。
64
+
65
+ 构造 list_photos 等查询参数前建议先调用本工具,避免使用不存在的分区 key。
66
+ """
67
+ return _call("/api/v1/meta")
68
+
69
+
70
+ @mcp.tool()
71
+ def list_photos(
72
+ q: str | None = None,
73
+ plate: str | None = None,
74
+ section: str | None = None,
75
+ region: str | None = None,
76
+ company_id: int | None = None,
77
+ author_id: int | None = None,
78
+ site: str = "all",
79
+ sort: str = "latest",
80
+ featured: bool = False,
81
+ page: int = 1,
82
+ limit: int = 20,
83
+ ) -> str:
84
+ """搜索 / 列出已审核公开的图片档案,返回分页列表(含缩略图地址、评分、作者)。
85
+
86
+ 参数:
87
+ - q:关键词,匹配品牌、车牌、地区、描述、公司名、作者名(≤60 字)
88
+ - plate:车牌片段,例如 B12345
89
+ - section:分区 key,取值先用 get_meta 获取
90
+ - region:地区文本片段,例如「南昌」
91
+ - company_id:公司 ID
92
+ - author_id:作者用户 ID
93
+ - site:all(默认)/ main(公交主站)/ draw(绘画站)
94
+ - sort:latest(最新)/ oldest(最早)/ hot(热门)/ rating(评分)/ featured(精选优先)
95
+ - featured:true 时仅返回精选档案
96
+ - page:页码,从 1 开始
97
+ - limit:每页条数,1–50,默认 20
98
+ """
99
+ return _call(
100
+ "/api/v1/photos",
101
+ {
102
+ "q": q,
103
+ "plate": plate,
104
+ "section": section,
105
+ "region": region,
106
+ "company_id": company_id,
107
+ "author_id": author_id,
108
+ "site": site,
109
+ "sort": sort,
110
+ "featured": True if featured else None,
111
+ "page": page,
112
+ "limit": limit,
113
+ },
114
+ )
115
+
116
+
117
+ @mcp.tool()
118
+ def get_photo(photo_id: int) -> str:
119
+ """获取单个图片档案的完整公开信息(不含拍摄地点)。
120
+
121
+ 参数:
122
+ - photo_id:档案 ID,可从 list_photos 结果或 /photo/{id} 链接中获得
123
+ """
124
+ return _call(f"/api/v1/photos/{int(photo_id)}")
125
+
126
+
127
+ @mcp.tool()
128
+ def get_photo_comments(photo_id: int, page: int = 1, limit: int = 20) -> str:
129
+ """获取某个档案下已通过审核的评论及嵌套回复。
130
+
131
+ 参数:
132
+ - photo_id:档案 ID
133
+ - page:页码,从 1 开始
134
+ - limit:每页条数,1–50,默认 20
135
+ """
136
+ return _call(
137
+ f"/api/v1/photos/{int(photo_id)}/comments",
138
+ {"page": page, "limit": limit},
139
+ )
140
+
141
+
142
+ @mcp.tool()
143
+ def list_news(
144
+ category: str | None = None,
145
+ province_id: int | None = None,
146
+ city_id: int | None = None,
147
+ page: int = 1,
148
+ limit: int = 12,
149
+ ) -> str:
150
+ """列出已发布的资讯(按发布时间倒序),列表仅含摘要,正文请再调用 get_news。
151
+
152
+ 参数:
153
+ - category:分类名(可选)
154
+ - province_id:省份 ID(可选)
155
+ - city_id:城市 ID(可选)
156
+ - page:页码,从 1 开始
157
+ - limit:每页条数,1–50,默认 12
158
+ """
159
+ return _call(
160
+ "/api/v1/news",
161
+ {
162
+ "category": category,
163
+ "province_id": province_id,
164
+ "city_id": city_id,
165
+ "page": page,
166
+ "limit": limit,
167
+ },
168
+ )
169
+
170
+
171
+ @mcp.tool()
172
+ def get_news(news_id: int) -> str:
173
+ """获取单篇资讯详情,包含 Markdown 正文与图片列表。
174
+
175
+ 参数:
176
+ - news_id:资讯 ID,可从 list_news 结果中获得
177
+ """
178
+ return _call(f"/api/v1/news/{int(news_id)}")
179
+
180
+
181
+ @mcp.tool()
182
+ def list_regions(
183
+ level: str = "province",
184
+ parent_id: int | None = None,
185
+ city_id: int | None = None,
186
+ ) -> str:
187
+ """获取省 / 市 / 公司三级层级,含城市数、公司数与档案数。
188
+
189
+ 参数:
190
+ - level:province(省份,默认)/ city(城市)/ company(公司)
191
+ - parent_id:level=city 时传省份 ID;level=company 时可传城市 ID
192
+ - city_id:level=company 时可用(与 parent_id 二选一)
193
+ """
194
+ return _call(
195
+ "/api/v1/regions",
196
+ {"level": level, "parent_id": parent_id, "city_id": city_id},
197
+ )
198
+
199
+
200
+ @mcp.tool()
201
+ def get_company(company_id: int, page: int = 1, limit: int = 12) -> str:
202
+ """获取公司资料(名称、城市、省份、作品数)以及该公司最新的作品列表。
203
+
204
+ 参数:
205
+ - company_id:公司 ID,可从 list_regions(level="company") 结果中获得
206
+ - page:作品页码,从 1 开始
207
+ - limit:每页作品数,1–50,默认 12
208
+ """
209
+ return _call(
210
+ f"/api/v1/companies/{int(company_id)}",
211
+ {"page": page, "limit": limit},
212
+ )
213
+
214
+
215
+ @mcp.tool()
216
+ def get_user(user_id: int) -> str:
217
+ """获取用户公开资料:昵称、头像、简介、加入时间、作品数与平均分、关注与粉丝数(不含邮箱)。
218
+
219
+ 参数:
220
+ - user_id:用户 ID,可从档案作者字段或 /user/{id} 链接中获得
221
+ """
222
+ return _call(f"/api/v1/users/{int(user_id)}")
223
+
224
+
225
+ @mcp.tool()
226
+ def get_rankings(ranking_type: str = "weekly", limit: int = 10) -> str:
227
+ """获取排行榜。
228
+
229
+ 参数:
230
+ - ranking_type:weekly(近 7 天最热档案,默认)/ monthly(近 30 天贡献者)
231
+ - limit:条数,1–20,默认 10
232
+ """
233
+ return _call("/api/v1/rankings", {"type": ranking_type, "limit": limit})
234
+
235
+
236
+ @mcp.tool()
237
+ def get_map_markers(
238
+ sw_lat: float | None = None,
239
+ sw_lng: float | None = None,
240
+ ne_lat: float | None = None,
241
+ ne_lng: float | None = None,
242
+ bbox: str | None = None,
243
+ section: str | None = None,
244
+ limit: int = 200,
245
+ ) -> str:
246
+ """获取矩形范围内允许公开位置的档案标记(坐标保留 4 位小数,不含详细地址)。
247
+
248
+ 参数:
249
+ - sw_lat / sw_lng / ne_lat / ne_lng:西南角与东北角经纬度
250
+ - bbox:minLng,minLat,maxLng,maxLat,可替代上面四个参数
251
+ - section:分区 key(可选)
252
+ - limit:条数,1–500,默认 200(建议按可视范围分批取,避免一次拉太多)
253
+ """
254
+ return _call(
255
+ "/api/v1/map/markers",
256
+ {
257
+ "sw_lat": sw_lat,
258
+ "sw_lng": sw_lng,
259
+ "ne_lat": ne_lat,
260
+ "ne_lng": ne_lng,
261
+ "bbox": bbox,
262
+ "section": section,
263
+ "limit": limit,
264
+ },
265
+ )
266
+
267
+
268
+ def _selftest() -> None:
269
+ """连通性自检:请求 /api/v1/stats,打印关键计数。"""
270
+ try:
271
+ payload = get_json("/api/v1/stats")
272
+ except ApiError as exc:
273
+ print(f"FAIL {base_url()} -> [{exc.code}] {exc}", file=sys.stderr)
274
+ raise SystemExit(1) from None
275
+
276
+ data = payload.get("data") or {}
277
+ print(
278
+ "OK {base} 档案 {photos} 用户 {users} 公司 {companies} 资讯 {news} 评论 {comments}".format(
279
+ base=base_url(),
280
+ photos=data.get("photos", "-"),
281
+ users=data.get("users", "-"),
282
+ companies=data.get("companies", "-"),
283
+ news=data.get("news", "-"),
284
+ comments=data.get("comments", "-"),
285
+ )
286
+ )
287
+
288
+
289
+ def main() -> None:
290
+ """命令行入口:默认启动 stdio MCP 服务,支持 --selftest / --version。"""
291
+ args = set(sys.argv[1:])
292
+ if "--version" in args:
293
+ print(__version__)
294
+ return
295
+ if "--selftest" in args:
296
+ _selftest()
297
+ return
298
+ mcp.run()
299
+
300
+
301
+ if __name__ == "__main__":
302
+ main()