内容
# MCP 服务器
一个模块化的Python MCP(模型上下文协议)服务器,为AI客户端提供几类工具:
- **文件系统** — 读取、写入、列出、搜索和检查文件
- **数据库** — 查询和修改SQLite数据库(按名称别名,而不是原始路径)
- **API** — 调用外部HTTP/REST API(按服务名称别名,而不是原始URL/密钥)
- **自定义** — 实用工具和添加您自己的业务逻辑的模板
对文件和数据库的访问由`config.toml`控制 — 模型只能访问您明确允许的内容。
## 安装
```bash
pip install -e .
```
需要Python 3.11+。
## 配置
复制示例配置并在启动服务器前编辑它:
```bash
cp config.toml.example config.toml
```
```toml
[filesystem]
# 模型允许访问的目录(绝对路径)。
# 空列表 = 禁止所有访问。
allowed_paths = [
"/home/user/data",
"/tmp/workspace",
]
# 设置为false使服务器只读。
allow_write = true
[database]
# 命名别名 → 实际文件路径。
# 模型使用别名(例如“mydb”),而非实际路径。
[database.connections]
mydb = "/home/user/data/mydb.sqlite"
analytics = "/home/user/data/analytics.sqlite"
[export]
# 大型查询结果的目录(db_query_to_file,
# gms_history_values(to_file=true))。必须已存在。
# 自动添加到allowed_paths。自动清理旧于7天的旧文件。
dir = "/home/user/data/mcp_exports"
# 可选:通过HTTP提供导出的CSV,以便不同的机器的MCP服务器可以下载它们。
# 启用时,需要advertise_host(此机器的地址,如其他机器所见)。
# 参见config.toml.example中的所有字段(download_host, download_port, url_ttl_minutes)和安全模型。
serve_downloads = false
```
您可以通过`MCP_CONFIG`环境变量指定自定义配置位置:
```bash
MCP_CONFIG=/etc/mcp/config.toml python -m mcp_server.server
```
## 运行服务器
```bash
# 标准输入输出传输(适用于Claude Desktop,MCP Inspector)
python -m mcp_server.server
# SSE / HTTP传输(适用于Open WebUI,Ollama,网页客户端)
python -m mcp_server.server --transport sse --host 0.0.0.0 --port 8080
```
## 客户端设置
### Claude Desktop
添加到`claude_desktop_config.json`:
```json
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["-m", "mcp_server.server"],
"cwd": "/absolute/path/to/MCP_server"
}
}
}
```
保存后重启Claude Desktop。
### Open WebUI + Ollama(本地模型)
Open WebUI通过SSE传输支持MCP:
1. 以SSE模式启动服务器:
```bash
python -m mcp_server.server --transport sse --port 8080
```
2. 在Open WebUI → **设置 → 工具** → 添加新的工具服务器:
- URL:`http://localhost:8080/sse`
3. 为您的模型会话启用工具服务器。
> **注意:**MCP服务器本身是模型无关的。相同的服务器代码适用于Claude,Ollama,LM Studio(通过mcp-proxy)或任何支持MCP工具调用的代理框架。只有客户端配置不同。
### MCP Inspector(测试)
```bash
npx @modelcontextprotocol/inspector python -m mcp_server.server
```
在`http://localhost:5173`打开浏览器UI进行交互式工具测试。
## 可用工具
### 文件系统
所有文件系统工具都会检查路径是否在`config.toml`的`allowed_paths`内。
| 工具 | 描述 |
|------|-------------|
| `read_file(path)` | 读取文本文件内容(截断为1 MB) |
| `write_file(path, content, mode)` | 写入或追加 — 需要`allow_write = true` |
| `list_directory(path, recursive)` | 列出目录条目和元数据 |
| `search_files(directory, pattern, recursive)` | 文件的全局搜索 |
| `file_info(path)` | 获取文件/目录元数据 |
| `delete_file(path)` | 删除文件 — 需要`allow_write = true` |
### 数据库(SQLite)
工具使用`config.toml`中的`db_name`别名而不是原始文件路径。调用`db_list_databases()`首先查看可用数据库。
| 工具 | 描述 |
|------|-------------|
| `db_list_databases()` | 列出配置的数据库名称 |
| `db_query(db_name, sql, params)` | SELECT查询,返回行作为字典 |
| `db_execute(db_name, sql, params)` | INSERT / UPDATE / DELETE |
| `db_list_tables(db_name)` | 列出所有表 |
| `db_table_schema(db_name, table_name)` | 获取列定义 |
| `db_execute_script(db_name, script)` | 运行多语句SQL脚本 |
| `db_query_to_file(db_name, sql, params, filename)` | 类似`db_query`,但将完整结果写入导出目录中的CSV,并仅返回`{path, columns, row_count, preview (first 5 rows), size_kb}` — 用于大型结果集而不是`db_query`。当启用`[export] serve_downloads`时,结果还包括一个时间有限的`download_url`,供不同的机器的MCP服务器通过HTTP流式传输CSV |
### GMS(压缩空气点/标签/值查询)
特定领域的工具,编码了压缩空气设备的固定查询逻辑:PostgreSQL模式前缀,Oracle区域/系统表命名(`FACCIMTAB.ZONE{1|2}_{building}_{GMS|PMS}`),>10标签批处理和1天历史上限。它们从与`db_query`相同的连接读取(目录:`postgreSQL_CIM`,实时:`oracle`,在`[database.connections]`下配置)— 优于这些自定义SQL查询;对于临时查询,请回退到`db_query`。
| 工具 | 描述 |
|------|-------------|
| `gms_list_equipment(building, category, equipment_type, floor)` | 从PostgreSQL主数据库列出设备(所有筛选器可选) |
| `gms_list_points(building, device_id, category, equipment_type, keyword)` | 列出一个设备的监控点/标签;`category`(广泛,例如 空压机/干燥机/真空机)和/或`equipment_type`(具体,例如 离心机/变频螺旋机)区分重复的`device_id`s |
| `gms_list_pipe_points(building, system_name)` | 列出管道网络点(HCDA/LCDA/HVAC) |
| `gms_realtime_values(building, tag_names)` | 最新SCADA值,用于列出的已知标签。纯Oracle值查找 — 首先通过`gms_list_points`解析`tag_names`,它不按device_id/category/keyword搜索 |
| `gms_history_values(building, start_time, end_time, tag_names, to_file)` | 历史值系列,用于列出的已知标签,限制在1天窗口内,每个标签有最大/最小/最新摘要。 与`gms_realtime_values`相同的标签名称合约。 如果`to_file=true`,则将系列写入导出目录下的一个CSV(所有标签组合)而不是嵌入,并且响应仅保留每个标签的摘要加上文件信息(包括`download_url`,如果启用`[export] serve_downloads`) |
### API(外部HTTP)
服务在`config.toml`下配置为`[api.services]`。工具使用服务名称别名;`base_url`和`api_key`在服务器端注入,从不暴露给模型。调用`api_list_services()`首先查看可用服务。
| 工具 | 描述 |
|------|-------------|
| `api_list_services()` | 列出配置的API服务名称 |
| `api_request(service, method, path, query, json_body)` | 对命名服务发出HTTP请求;返回`{status, body}` |
| `push_notify(service, title, content, image_path, push_to_list)` | 发送Push+通知;填充模板的`$_title` / `$_content`(内容可以是内联HTML)。`image_path`嵌入图像文件为内联base64 — 服务器编码,因此您从不粘贴base64本身 |
`token`/`api_key`从服务的`config.toml`块读取,从不暴露给模型。对于其TLS证书未公开信任的内部服务,在其服务块中设置`verify = false`以跳过证书验证。
### 自定义 / 实用
| 工具 | 描述 |
|------|-------------|
| `echo(message)` | 返回消息不变(连接测试) |
| `system_info()` | 返回Python版本,平台,时间戳 |
| `calculate(expression)` | 安全数学表达式求值器 |
| `format_data(data, input_format, output_format)` | JSON ↔ 纯文本转换 |
## 部署
### Docker(推荐用于服务器环境)
```bash
# 1. 复制并编辑配置
cp config.toml.example config.toml
# 编辑config.toml — 设置allowed_paths和数据库连接
# 2. 构建并启动
docker compose up -d
# 3. 检查状态 / 日志
docker compose ps
docker compose logs -f
```
容器以只读方式挂载`./config.toml`到`/config/config.toml`,并在命名卷`mcp-data`中保留数据。要将日志写入文件,请在`docker-compose.yml`中设置`MCP_LOG_FILE=/data/mcp_server.log`。
### systemd(Linux裸金属 / 虚拟机)
```bash
# 以root用户运行一次 — 创建服务用户,安装到/opt/mcp-server,
# 复制配置模板到/etc/mcp/config.toml,并启用服务。
sudo bash deploy/install-systemd.sh
# 编辑配置后启动
sudo nano /etc/mcp/config.toml
sudo systemctl start mcp-server
sudo systemctl status mcp-server
journalctl -u mcp-server -f # 实时日志
```
### 离线 / 空气隔离(Windows)
对于没有互联网访问的目标机器,在联网机器上打包依赖项,并在目标机器上从捆绑包安装。两台机器必须运行**相同的操作系统和Python版本**(轮子是平台特定的)。
```powershell
# 1. 在联网开发机器上(最新主分支上的代码):
git pull
.\scripts\pack_offline.ps1 # 生成 ..\mcp-server-offline.zip
# 2. 将zip复制到离线目标,解压,然后:
.\scripts\install_offline.ps1 # 创建.venv,安装所有轮子
copy config.toml.example config.toml # 然后编辑config.toml
```
`pack_offline.ps1`直接从`pyproject.toml`读取依赖项,因此您稍后添加的任何包都会自动捆绑 — 无需编辑脚本。`install_offline.ps1`将venv指向实时`src/`树,因此之后普通的`git pull`(或解压更新的捆绑包)更新服务器而无需重新安装 — 除非`pyproject.toml`依赖项已更改,否则运行`pack_offline.ps1`以刷新捆绑包。
### 日志记录
日志记录通过环境变量配置(不是config.toml):
| 变量 | 默认值 | 描述 |
|----------|---------|-------------|
| `MCP_LOG_LEVEL` | `INFO` | `DEBUG` / `INFO` / `WARNING` / `ERROR` |
| `MCP_LOG_FILE` | _(无)_ | 如果设置,日志也会写入此文件 |
日志始终输出到**stderr**,以保持stdout干净用于stdio传输。写入操作(`write_file`,`delete_file`,`db_execute`,`db_execute_script`)在INFO级别记录以进行审计。
## 添加新工具
**将工具添加到现有类别** — 打开`src/mcp_server/tools/`中的文件并在`register()`内添加:
```python
@mcp.tool()
def my_new_tool(param: str) -> str:
"""描述此工具的作用。"""
return ...
```
**添加新工具类别** — 创建新模块:
```python
# src/mcp_server/tools/my_category.py
from mcp.server.fastmcp import FastMCP
from mcp_server.utils.errors import ToolError
def register(mcp: FastMCP) -> None:
@mcp.tool()
def my_tool(param: str) -> str:
"""工具描述。"""
...
```
然后在`src/mcp_server/server.py`中添加两行:
```python
from mcp_server.tools import my_category
my_category.register(mcp)
```
## 添加API
外部REST API是配置驱动的 — 普通情况下不需要代码。
**1. 将服务添加到`config.toml`**(API密钥/承载令牌示例):
```toml
[api.services.weather]
base_url = "https://api.openweathermap.org/data/2.5"
api_key = "your-key-here"
auth_header = "Authorization" # 或 "X-API-Key"
auth_prefix = "Bearer " # 使用""表示X-API-Key样式
```
对于公开的、无密钥的API,只需设置`base_url`。模型永远只看到服务名称(`weather`)— 密钥保留在服务器上,就像数据库别名一样。
**2. 调用**通过通用工具:
```
api_request(service="weather", path="/weather", query={"q": "Taipei", "units": "metric"})
```
**3. (可选)添加类型化便捷包装器**在`src/mcp_server/tools/api.py`中,当您想要更清晰、自文档的工具(例如`get_weather(city)`)。
### 要给我什么来连接新API
当您想帮助添加一个时,以下内容就足够了(大多数副本直接来自API文档):
1. **服务名称** — 您想要的别名(例如`weather`,`twse`)。
2. **base_url** — API的根URL。
3. **身份验证** — 头名称(`Authorization` vs `X-API-Key`),前缀(`Bearer`或空),以及密钥(占位符就足够了;请在`config.toml`中放置实际密钥)。
4. **端点** — 方法 + 路径(例如`GET /weather`)和查询/正文参数。
5. **(可选)示例响应** — JSON片段,以便我可以构建一个类型化的包装器,仅显示您关心的字段。
项目1-3足以通过`api_request`调用API。添加4-5,我可以编写一个专用的便捷工具,具有清晰的文档字符串。