WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s

This commit is contained in:
yawan
2026-09-22 16:19:06 +08:00
commit f7fc992f57
3929 changed files with 1006607 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
/.codebuddy
/__pycache__
+151
View File
@@ -0,0 +1,151 @@
# 更新日志
所有重要的项目更改都将记录在此文件中。
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
并且本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
## [Unreleased]
### 新增
- 新增 `create_knowledge_from_text` 工具:通过手动 Markdown 文本创建知识条目,调用既有 `/knowledge-bases/{id}/knowledge/manual` 接口,补齐 #323 中"文本"部分。默认 `status="publish"`,创建后即进入解析/索引流程、可被检索;可传 `status="draft"` 仅保存不索引。
- README 工具清单补列既有的 `create_knowledge_from_file`。
## [1.1.1] - 2026-07-30
### 变更
- 官方 PyPI 包名改为 **`tencent-weknora-mcp`**(由 Tencent/WeKnora 仓库 CI 通过 Trusted Publishing 发布)。
原社区包 `weknora-mcp` 非官方维护,请迁移安装命令。
- 项目 URL 指向 [Tencent/WeKnora](https://github.com/Tencent/WeKnora) 官方仓库(`mcp-server/` 目录)。
### 修复
- HTTP 传输恢复 `stateless_http=True`,与迁移前 `StreamableHTTPSessionManager(stateless=True)` 行为一致。
- SSE 传输恢复消息端点 `/sse/messages/`,与迁移前路由一致。
- `WeKnoraClient` 使用线程本地 `requests.Session`,避免 MCP 2.x 在线程池中并发调用时出现 Session 竞态。
- 文件上传(`create_knowledge_from_file`)尊重 `WEKNORA_VERIFY_SSL` 设置。
### 注意
- 工具执行失败时,MCPServer 2.x 返回 `CallToolResult(isError=True)`(`ToolError`),
不再像旧版低层 API 那样以成功响应的文本块返回 `"Error executing …"` 前缀。
仅解析 `content[0].text` 的客户端通常无感;依赖 `isError` 标志的集成方行为会更符合 MCP 规范。
## [1.1.0] - 2026-07-30
### 修复
- 修复 MCP Python SDK 2.x 下服务器启动崩溃(`AttributeError: 'Server' object has no attribute 'list_tools'`)。
SDK 2.0 移除了低层 `Server` 的装饰器 API(`@app.list_tools()` / `@app.call_tool()` / `app.get_capabilities()`),
而通过 `uvx` 拉起已发布包时会解析到最新 2.x,导致连接被关闭。
### 变更
- 将 MCP 服务器实现从低层 `Server` 迁移到高层 `MCPServer` API(mcp 2.x,原 FastMCP 改名)。
- 28 个工具重写为 `@mcp.tool()` 函数签名式:输入参数走类型标注(schema 由框架自动生成),
描述走 docstring,返回纯 Python 值由框架序列化。
- 传输层改用 `run_stdio_async()` / `sse_app()` / `streamable_http_app()`,鉴权仍由 `MCPAuthMiddleware` 包裹。
- `WeKnoraClient` 业务逻辑(REST/SSE 调用、resolve_*、wiki)保持不变。
- 依赖上限调整为 `mcp>=2,<3`(发布包与开发环境一致,避免再次因无上限被解析到未来破坏性版本)。
### 注意
- 本版本要求运行环境 `mcp>=2`。若临时需要旧版 SDK,可在启动命令加 `--with "mcp<2"`,但建议升级到本版本。
## [1.0.1] - 2026-07-28
### 修复
- 将入口脚本(`run_server.py`、`main.py`、`run.py`)的诊断输出改到 stderr,避免破坏 MCP stdio 协议流导致客户端启动失败
- 修复 wheel 漏打包 `upload_paths.py` 导致的 `ModuleNotFoundError`
- 将 `__init__.py` 改为绝对导入,修复 unittest/pytest 收集测试时的包导入错误
### 变更
- PyPI 分发包名统一为 `weknora-mcp`(命令行入口 `weknora-mcp-server` / `weknora-server` 不变)
- 新增 CI workflow(`.github/workflows/mcp-server.yml`),发布 tag 格式为 `mcp-server-v*`
## [1.0.0] - 2024-01-XX
### 新增
- 初始版本发布
- WeKnora MCP Server 核心功能
- 完整的 WeKnora API 集成
- 空间管理工具
- 知识库管理工具
- 知识管理工具
- 模型管理工具
- 会话管理工具
- 聊天功能工具
- 块管理工具
- 多种启动方式支持
- 命令行参数支持
- 环境变量配置
- 完整的包安装支持
- 开发和生产模式
- 详细的文档和安装指南
### 工具列表
- `create_tenant` - 创建新空间
- `list_tenants` - 列出所有空间
- `create_knowledge_base` - 创建知识库
- `list_knowledge_bases` - 列出知识库
- `get_knowledge_base` - 获取知识库详情
- `delete_knowledge_base` - 删除知识库
- `hybrid_search` - 混合搜索
- `create_knowledge_from_url` - 从 URL 创建知识
- `list_knowledge` - 列出知识
- `get_knowledge` - 获取知识详情
- `delete_knowledge` - 删除知识
- `create_model` - 创建模型
- `list_models` - 列出模型
- `get_model` - 获取模型详情
- `create_session` - 创建聊天会话
- `get_session` - 获取会话详情
- `list_sessions` - 列出会话
- `delete_session` - 删除会话
- `chat` - 发送聊天消息
- `list_chunks` - 列出知识块
- `delete_chunk` - 删除知识块
### 文件结构
```
WeKnora/mcp-server/
├── __init__.py # 包初始化文件
├── main.py # 主入口点 (推荐)
├── run.py # 便捷启动脚本
├── run_server.py # 原始启动脚本
├── weknora_mcp_server.py # MCP 服务器实现
├── test_module.py # 模组测试脚本
├── requirements.txt # 依赖列表
├── setup.py # 安装脚本 (传统)
├── pyproject.toml # 现代项目配置
├── MANIFEST.in # 包含文件清单
├── LICENSE # MIT 许可证
├── README.md # 项目说明
├── INSTALL.md # 详细安装指南
└── CHANGELOG.md # 更新日志
```
### 启动方式
1. `python main.py` - 主入口点 (推荐)
2. `python run_server.py` - 原始启动脚本
3. `python run.py` - 便捷启动脚本
4. `python weknora_mcp_server.py` - 直接运行
5. `python -m weknora_mcp_server` - 模块运行
6. `weknora-mcp-server` - 安装后命令行工具
7. `weknora-server` - 安装后命令行工具 (别名)
### 技术特性
- 基于 Model Context Protocol (MCP) 1.0.0+
- 异步 I/O 支持
- 完整的错误处理
- 详细的日志记录
- 环境变量配置
- 命令行参数支持
- 多种安装方式
- 开发和生产模式
- 完整的测试覆盖
### 依赖
- Python 3.10+
- mcp >= 1.0.0
- requests >= 2.31.0
### 兼容性
- 支持 Windows、macOS、Linux
- 支持 Python 3.10-3.12
- 兼容现代 Python 包管理工具
+17
View File
@@ -0,0 +1,17 @@
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN pip install --no-cache-dir -e .
ENV MCP_HOST=0.0.0.0
ENV MCP_PORT=8000
ENV WEKNORA_BASE_URL=http://app:8080/api/v1
# Set MCP_SERVER_AUTH_TOKEN at runtime; HTTP transport refuses to start without it.
EXPOSE 8000
CMD ["weknora-mcp-server", "--transport", "http", "--host", "0.0.0.0", "--port", "8000"]
+424
View File
@@ -0,0 +1,424 @@
# WeKnora MCP Server 使用示例
本文档提供了 WeKnora MCP Server 的详细使用示例。
## 基本使用
### 1. 启动服务器
```bash
# 推荐方式 - 使用主入口点
python main.py
# 检查环境配置
python main.py --check-only
# 启用详细日志
python main.py --verbose
```
### 2. 环境配置示例
```bash
# 设置环境变量
export WEKNORA_BASE_URL="http://localhost:8080/api/v1"
export WEKNORA_API_KEY="your_api_key_here"
# 或者在 .env 文件中设置
echo "WEKNORA_BASE_URL=http://localhost:8080/api/v1" > .env
echo "WEKNORA_API_KEY=your_api_key_here" >> .env
```
## MCP 工具使用示例
以下是各种 MCP 工具的使用示例:
### 空间管理
#### 创建空间
```json
{
"tool": "create_tenant",
"arguments": {
"name": "我的公司",
"description": "公司知识管理系统",
"business": "technology",
"retriever_engines": {
"engines": [
{"retriever_type": "keywords", "retriever_engine_type": "postgres"},
{"retriever_type": "vector", "retriever_engine_type": "postgres"}
]
}
}
}
```
#### 列出所有空间
```json
{
"tool": "list_tenants",
"arguments": {}
}
```
### 知识库管理
#### 创建知识库
```json
{
"tool": "create_knowledge_base",
"arguments": {
"name": "产品文档库",
"description": "产品相关文档和资料",
"embedding_model_id": "text-embedding-ada-002",
"summary_model_id": "gpt-3.5-turbo"
}
}
```
#### 列出知识库
```json
{
"tool": "list_knowledge_bases",
"arguments": {}
}
```
#### 获取知识库详情
```json
{
"tool": "get_knowledge_base",
"arguments": {
"kb_id": "kb_123456"
}
}
```
#### 混合搜索
```json
{
"tool": "hybrid_search",
"arguments": {
"kb_id": "kb_123456",
"query": "如何使用API",
"vector_threshold": 0.7,
"keyword_threshold": 0.5,
"match_count": 10
}
}
```
### 知识管理
#### 从URL创建知识
```json
{
"tool": "create_knowledge_from_url",
"arguments": {
"kb_id": "kb_123456",
"url": "https://docs.example.com/api-guide",
"enable_multimodel": true
}
}
```
#### 从文本创建知识
```json
{
"tool": "create_knowledge_from_text",
"arguments": {
"kb_id": "my-knowledge-base",
"title": "注意力机制摘要",
"content": "# 注意力机制\n\n注意力机制允许模型在处理序列时动态分配权重...",
"status": "publish"
}
}
```
#### 列出知识
```json
{
"tool": "list_knowledge",
"arguments": {
"kb_id": "kb_123456",
"page": 1,
"page_size": 20
}
}
```
#### 获取知识详情
```json
{
"tool": "get_knowledge",
"arguments": {
"knowledge_id": "know_789012"
}
}
```
### 模型管理
#### 创建模型
```json
{
"tool": "create_model",
"arguments": {
"name": "GPT-4 Chat Model",
"type": "KnowledgeQA",
"source": "openai",
"description": "OpenAI GPT-4 模型用于知识问答",
"base_url": "https://api.openai.com/v1",
"api_key": "sk-...",
"is_default": true
}
}
```
#### 列出模型
```json
{
"tool": "list_models",
"arguments": {}
}
```
### 会话管理
#### 创建聊天会话
```json
{
"tool": "create_session",
"arguments": {
"kb_id": "kb_123456",
"max_rounds": 10,
"enable_rewrite": true,
"fallback_response": "抱歉,我无法回答这个问题。",
"summary_model_id": "gpt-3.5-turbo"
}
}
```
#### 获取会话详情
```json
{
"tool": "get_session",
"arguments": {
"session_id": "sess_345678"
}
}
```
#### 列出会话
```json
{
"tool": "list_sessions",
"arguments": {
"page": 1,
"page_size": 10
}
}
```
### 聊天功能
#### 发送聊天消息
```json
{
"tool": "chat",
"arguments": {
"session_id": "sess_345678",
"query": "请介绍一下产品的主要功能"
}
}
```
### 块管理
#### 列出知识块
```json
{
"tool": "list_chunks",
"arguments": {
"knowledge_id": "know_789012",
"page": 1,
"page_size": 50
}
}
```
#### 删除知识块
```json
{
"tool": "delete_chunk",
"arguments": {
"knowledge_id": "know_789012",
"chunk_id": "chunk_456789"
}
}
```
## 完整工作流程示例
### 场景:创建一个完整的知识问答系统
```bash
# 1. 启动服务器
python main.py --verbose
# 2. 在 MCP 客户端中执行以下步骤:
```
#### 步骤 1: 创建空间
```json
{
"tool": "create_tenant",
"arguments": {
"name": "技术文档中心",
"description": "公司技术文档知识管理",
"business": "technology"
}
}
```
#### 步骤 2: 创建知识库
```json
{
"tool": "create_knowledge_base",
"arguments": {
"name": "API文档库",
"description": "所有API相关文档"
}
}
```
#### 步骤 3: 添加知识内容
```json
{
"tool": "create_knowledge_from_url",
"arguments": {
"kb_id": "返回的知识库ID",
"url": "https://docs.company.com/api",
"enable_multimodel": true
}
}
```
#### 步骤 4: 创建聊天会话
```json
{
"tool": "create_session",
"arguments": {
"kb_id": "知识库ID",
"max_rounds": 5,
"enable_rewrite": true
}
}
```
#### 步骤 5: 开始对话
```json
{
"tool": "chat",
"arguments": {
"session_id": "会话ID",
"query": "如何使用用户认证API?"
}
}
```
## 错误处理示例
### 常见错误和解决方案
#### 1. 连接错误
```json
{
"error": "Connection refused",
"solution": "检查 WEKNORA_BASE_URL 是否正确,确认服务正在运行"
}
```
#### 2. 认证错误
```json
{
"error": "Unauthorized",
"solution": "检查 WEKNORA_API_KEY 是否设置正确"
}
```
#### 3. 资源不存在
```json
{
"error": "Knowledge base not found",
"solution": "确认知识库ID是否正确,或先创建知识库"
}
```
## 高级配置示例
### 自定义检索配置
```json
{
"tool": "hybrid_search",
"arguments": {
"kb_id": "kb_123456",
"query": "搜索查询",
"vector_threshold": 0.8,
"keyword_threshold": 0.6,
"match_count": 15
}
}
```
### 自定义会话策略
```json
{
"tool": "create_session",
"arguments": {
"kb_id": "kb_123456",
"max_rounds": 20,
"enable_rewrite": true,
"fallback_response": "根据现有知识,我无法准确回答您的问题。请尝试重新表述或联系技术支持。"
}
}
```
## 性能优化建议
1. **批量操作**: 尽量批量处理知识创建和更新
2. **缓存策略**: 合理设置搜索阈值以平衡准确性和性能
3. **会话管理**: 及时清理不需要的会话以节省资源
4. **监控日志**: 使用 `--verbose` 选项监控性能指标
## 集成示例
### 与 Claude Desktop 集成
在 Claude Desktop 的配置文件中添加:
```json
{
"mcpServers": {
"weknora": {
"command": "python",
"args": ["path/to/main.py"],
"env": {
"WEKNORA_BASE_URL": "http://localhost:8080/api/v1",
"WEKNORA_API_KEY": "your_api_key"
}
}
}
}
```
项目仓库: https://github.com/Tencent/WeKnora/tree/main/mcp-server
### 与其他 MCP 客户端集成
参考各客户端的文档,配置服务器启动命令和环境变量。
## 故障排除
如果遇到问题:
1. 运行 `python main.py --check-only` 检查环境
2. 使用 `python main.py --verbose` 查看详细日志
3. 检查 WeKnora 服务是否正常运行
4. 验证网络连接和防火墙设置
+208
View File
@@ -0,0 +1,208 @@
# WeKnora MCP Server 安装和使用指南
## 快速开始
### 1. 安装依赖
```bash
pip install -r requirements.txt
```
### 2. 设置环境变量
```bash
# Linux/macOS
export WEKNORA_BASE_URL="http://localhost:8080/api/v1"
export WEKNORA_API_KEY="your_api_key_here"
# Windows PowerShell
$env:WEKNORA_BASE_URL="http://localhost:8080/api/v1"
$env:WEKNORA_API_KEY="your_api_key_here"
# Windows CMD
set WEKNORA_BASE_URL=http://localhost:8080/api/v1
set WEKNORA_API_KEY=your_api_key_here
```
### 3. 运行服务器
有多种方式运行服务器:
#### 方式 1: 使用主入口点 (推荐)
```bash
python main.py
```
#### 方式 2: 使用原始启动脚本
```bash
python run_server.py
```
#### 方式 3: 直接运行服务器模块
```bash
python weknora_mcp_server.py
```
#### 方式 4: 作为 Python 模块运行
```bash
python -m weknora_mcp_server
```
## 作为 Python 包安装
### 开发模式安装
```bash
pip install -e .
```
安装后可以使用命令行工具:
```bash
weknora-mcp-server
# 或
weknora-server
```
### 生产模式安装
```bash
pip install .
```
### 构建分发包
```bash
# 构建源码分发包和轮子
python setup.py sdist bdist_wheel
# 或使用 build 工具
pip install build
python -m build
```
## 命令行选项
主入口点 `main.py` 支持以下选项:
```bash
python main.py --help # 显示帮助信息
python main.py --check-only # 仅检查环境配置
python main.py --verbose # 启用详细日志
python main.py --version # 显示版本信息
```
## 环境检查
运行以下命令检查环境配置:
```bash
python main.py --check-only
```
这将显示:
- WeKnora API 基础 URL 配置
- API 密钥设置状态
- 依赖包安装状态
## 故障排除
### 1. 导入错误
如果遇到 `ImportError`,请确保:
- 已安装所有依赖:`pip install -r requirements.txt`
- Python 版本兼容(推荐 3.10+)
- 没有文件名冲突
### 2. 连接错误
如果无法连接到 WeKnora API:
- 检查 `WEKNORA_BASE_URL` 是否正确
- 确认 WeKnora 服务正在运行
- 验证网络连接
### 3. 认证错误
如果遇到认证问题:
- 检查 `WEKNORA_API_KEY` 是否设置
- 确认 API 密钥有效
- 验证权限设置
## 开发模式
### 项目结构
```
WeKnora/mcp-server/
├── __init__.py # 包初始化文件
├── main.py # 主入口点
├── run_server.py # 原始启动脚本
├── weknora_mcp_server.py # MCP 服务器实现
├── requirements.txt # 依赖列表
├── setup.py # 安装脚本
├── pyproject.toml # 项目元数据(PyPI: tencent-weknora-mcp)
├── MANIFEST.in # 包含文件清单
├── LICENSE # 许可证
├── README.md # 项目说明
└── INSTALL.md # 安装指南
```
### 添加新功能
1. 在 `WeKnoraClient` 类中添加新的 API 方法
2. 用 `@mcp.tool()` 装饰器注册一个新工具函数:参数用类型标注(schema 自动生成),描述写在 docstring 里,函数体调用上面新增的客户端方法
3. 更新文档和测试
### 测试
```bash
# 运行基本测试
python check_imports.py
# 测试环境配置
python main.py --check-only
# 测试服务器启动
python main.py --verbose
```
## 部署
### Docker 部署
创建 `Dockerfile`:
```dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
RUN pip install -e .
ENV WEKNORA_BASE_URL=http://localhost:8080/api/v1
EXPOSE 8000
CMD ["weknora-mcp-server"]
```
### 系统服务
创建 systemd 服务文件 `/etc/systemd/system/weknora-mcp.service`:
```ini
[Unit]
Description=WeKnora MCP Server
After=network.target
[Service]
Type=simple
User=weknora
WorkingDirectory=/opt/weknora-mcp
Environment=WEKNORA_BASE_URL=http://localhost:8080/api/v1
Environment=WEKNORA_API_KEY=your_api_key
ExecStart=/usr/local/bin/weknora-mcp-server
Restart=always
[Install]
WantedBy=multi-user.target
```
启用服务:
```bash
sudo systemctl enable weknora-mcp
sudo systemctl start weknora-mcp
```
## 支持
如果遇到问题,请:
1. 查看日志输出
2. 检查环境配置
3. 参考故障排除部分
4. 提交 Issue 到项目仓库: https://github.com/Tencent/WeKnora/issues
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 WeKnora Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+14
View File
@@ -0,0 +1,14 @@
include README.md
include requirements.txt
include LICENSE
include *.py
recursive-include * *.py
recursive-include * *.md
recursive-include * *.txt
recursive-include * *.yml
recursive-include * *.yaml
global-exclude __pycache__
global-exclude *.py[co]
global-exclude .DS_Store
global-exclude *.so
global-exclude .git*
+116
View File
@@ -0,0 +1,116 @@
# 使用 uv 运行 WeKnora MCP 服务器
> 更推荐使用`uv`来运行基于python的MCP服务。
>
> 也可通过 PyPI 安装:`pip install tencent-weknora-mcp`,或使用 `uvx --from tencent-weknora-mcp weknora-mcp-server`(官方包名 `tencent-weknora-mcp`,由 [Tencent/WeKnora](https://github.com/Tencent/WeKnora) 维护)。
## 1. 安装 uv
```bash
# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# 或使用 Homebrew (macOS)
brew install uv
# Windows
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```
## 2. MCP 客户端配置
### Claude Desktop 配置
在 Claude Desktop 设置中添加:
```json
{
"mcpServers": {
"weknora": {
"args": [
"--directory",
"/path/WeKnora/mcp-server",
"run",
"run_server.py"
],
"command": "uv",
"env": {
"WEKNORA_API_KEY": "your_api_key_here",
"WEKNORA_BASE_URL": "http://localhost:8080/api/v1"
}
}
}
}
```
### Cursor 配置
在 Cursor 中,编辑 MCP 配置文件 (通常在 `~/.cursor/mcp-config.json`):
```json
{
"mcpServers": {
"weknora": {
"command": "uv",
"args": [
"--directory",
"/path/WeKnora/mcp-server",
"run",
"run_server.py"
],
"env": {
"WEKNORA_API_KEY": "your_api_key_here",
"WEKNORA_BASE_URL": "http://localhost:8080/api/v1"
}
}
}
}
```
### KiloCode 配置
对于 KiloCode 或其他支持 MCP 的编辑器,配置如下:
```json
{
"mcpServers": {
"weknora": {
"command": "uv",
"args": [
"--directory",
"/path/WeKnora/mcp-server",
"run",
"run_server.py"
],
"env": {
"WEKNORA_API_KEY": "your_api_key_here",
"WEKNORA_BASE_URL": "http://localhost:8080/api/v1"
}
}
}
}
```
### 其他 MCP 客户端
对于一般 MCP 客户端配置:
```json
{
"mcpServers": {
"weknora": {
"command": "uv",
"args": [
"--directory",
"/path/WeKnora/mcp-server",
"run",
"run_server.py"
],
"env": {
"WEKNORA_API_KEY": "your_api_key_here",
"WEKNORA_BASE_URL": "http://localhost:8080/api/v1"
}
}
}
}
```
+278
View File
@@ -0,0 +1,278 @@
# WeKnora MCP Server 可运行模组包 - 项目总结
## 🎉 项目完成状态
✅ **所有测试通过** - 模组已成功打包并可正常运行
## 📁 项目结构
```
WeKnora/mcp-server/
├── 📦 核心文件
│ ├── __init__.py # 包初始化文件
│ ├── weknora_mcp_server.py # MCP 服务器核心实现
│ └── requirements.txt # 项目依赖
│
├── 🚀 启动脚本 (多种方式)
│ ├── main.py # 主入口点 (推荐) ⭐
│ ├── run_server.py # 原始启动脚本
│ └── run.py # 便捷启动脚本
│
├── 📋 配置文件
│ ├── setup.py # 传统安装脚本
│ ├── pyproject.toml # 现代项目配置
│ └── MANIFEST.in # 包含文件清单
│
├── 🧪 测试文件
│ ├── test_module.py # 模组功能测试
│ └── check_imports.py # 手动导入检查
│
├── 📚 文档文件
│ ├── README.md # 项目说明
│ ├── INSTALL.md # 详细安装指南
│ ├── EXAMPLES.md # 使用示例
│ ├── CHANGELOG.md # 更新日志
│ ├── PROJECT_SUMMARY.md # 项目总结 (本文件)
│ └── LICENSE # MIT 许可证
│
└── 📂 其他
├── __pycache__/ # Python 缓存 (自动生成)
├── .codebuddy/ # CodeBuddy 配置
└── .venv/ # 虚拟环境 (可选)
```
## 🚀 启动方式 (7种)
### 1. 主入口点 (推荐) ⭐
```bash
python main.py # 基本启动
python main.py --check-only # 仅检查环境
python main.py --verbose # 详细日志
python main.py --help # 显示帮助
```
### 2. 原始启动脚本
```bash
python run_server.py
```
### 3. 便捷启动脚本
```bash
python run.py
```
### 4. 直接运行服务器
```bash
python weknora_mcp_server.py
```
### 5. 作为模块运行
```bash
python -m weknora_mcp_server
```
### 6. 安装后命令行工具
```bash
pip install -e . # 开发模式安装
weknora-mcp-server # 主命令
weknora-server # 别名命令
```
### 7. 生产环境安装
```bash
pip install . # 生产安装
weknora-mcp-server # 全局命令
```
## 🔧 环境配置
### 必需环境变量
```bash
# Linux/macOS
export WEKNORA_BASE_URL="http://localhost:8080/api/v1"
export WEKNORA_API_KEY="your_api_key_here"
# Windows PowerShell
$env:WEKNORA_BASE_URL="http://localhost:8080/api/v1"
$env:WEKNORA_API_KEY="your_api_key_here"
# Windows CMD
set WEKNORA_BASE_URL=http://localhost:8080/api/v1
set WEKNORA_API_KEY=your_api_key_here
```
## 🛠️ 功能特性
### MCP 工具 (21个)
- **空间管理**: `create_tenant`, `list_tenants`
- **知识库管理**: `create_knowledge_base`, `list_knowledge_bases`, `get_knowledge_base`, `delete_knowledge_base`, `hybrid_search`
- **知识管理**: `create_knowledge_from_url`, `list_knowledge`, `get_knowledge`, `delete_knowledge`
- **模型管理**: `create_model`, `list_models`, `get_model`
- **会话管理**: `create_session`, `get_session`, `list_sessions`, `delete_session`
- **聊天功能**: `chat`
- **块管理**: `list_chunks`, `delete_chunk`
### 技术特性
- ✅ 异步 I/O 支持
- ✅ 完整错误处理
- ✅ 详细日志记录
- ✅ 环境变量配置
- ✅ 命令行参数支持
- ✅ 多种安装方式
- ✅ 开发和生产模式
- ✅ 完整测试覆盖
## 📦 安装方式
### 快速开始
```bash
# 1. 安装依赖
pip install -r requirements.txt
# 2. 设置环境变量
export WEKNORA_BASE_URL="http://localhost:8080/api/v1"
export WEKNORA_API_KEY="your_api_key"
# 3. 启动服务器
python main.py
```
### 开发模式安装
```bash
pip install -e .
weknora-mcp-server
```
### 生产模式安装
```bash
pip install .
weknora-mcp-server
```
### 构建分发包
```bash
# 传统方式
python setup.py sdist bdist_wheel
# 现代方式
pip install build
python -m build
```
## 🧪 测试验证
### 运行完整测试
```bash
python test_module.py
```
### 测试结果
```
WeKnora MCP Server 模组测试
==================================================
✓ 模块导入测试通过
✓ 环境配置测试通过
✓ 客户端创建测试通过
✓ 文件结构测试通过
✓ 入口点测试通过
✓ 包安装测试通过
==================================================
测试结果: 6/6 通过
✓ 所有测试通过!模组可以正常使用。
```
## 🔍 兼容性
### Python 版本
- ✅ Python 3.10+
- ✅ Python 3.11
- ✅ Python 3.12
### 操作系统
- ✅ Windows 10/11
- ✅ macOS 10.15+
- ✅ Linux (Ubuntu, CentOS, etc.)
### 依赖包
- `mcp >= 1.0.0` - Model Context Protocol 核心库
- `requests >= 2.31.0` - HTTP 请求库
## 📖 文档资源
1. **README.md** - 项目概述和快速开始
2. **INSTALL.md** - 详细安装和配置指南
3. **EXAMPLES.md** - 完整使用示例和工作流程
4. **CHANGELOG.md** - 版本更新记录
5. **PROJECT_SUMMARY.md** - 项目总结 (本文件)
## 🎯 使用场景
### 1. 开发环境
```bash
python main.py --verbose
```
### 2. 生产环境
```bash
pip install .
weknora-mcp-server
```
### 3. Docker 部署
```dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install .
CMD ["weknora-mcp-server"]
```
### 4. 系统服务
```ini
[Unit]
Description=WeKnora MCP Server
[Service]
ExecStart=/usr/local/bin/weknora-mcp-server
Environment=WEKNORA_BASE_URL=http://localhost:8080/api/v1
```
## 🔧 故障排除
### 常见问题
1. **导入错误**: 运行 `pip install -r requirements.txt`
2. **连接错误**: 检查 `WEKNORA_BASE_URL` 设置
3. **认证错误**: 验证 `WEKNORA_API_KEY` 配置
4. **环境检查**: 运行 `python main.py --check-only`
### 调试模式
```bash
python main.py --verbose # 详细日志
python test_module.py # 运行测试
```
## 🎉 项目成就
✅ **完整的可运行模组** - 从单个脚本转换为完整的 Python 包
✅ **多种启动方式** - 提供 7 种不同的启动方法
✅ **完善的文档** - 包含安装、使用、示例等完整文档
✅ **全面的测试** - 所有功能都经过测试验证
✅ **现代化配置** - 支持 setup.py 和 pyproject.toml
✅ **跨平台兼容** - 支持 Windows、macOS、Linux
✅ **生产就绪** - 可用于开发和生产环境
## 🚀 下一步
1. **部署到生产环境**
2. **集成到 CI/CD 流程**
3. **发布到 PyPI**
4. **添加更多测试用例**
5. **性能优化和监控**
---
**项目状态**: ✅ 完成并可投入使用
**项目仓库**: https://github.com/Tencent/WeKnora/tree/main/mcp-server
**PyPI 包名**: `tencent-weknora-mcp`
**最后更新**: 2025年10月
**版本**: 1.0.0
+157
View File
@@ -0,0 +1,157 @@
# WeKnora MCP Server
这是一个 Model Context Protocol (MCP) 服务器,提供对 WeKnora 知识管理 API 的访问。
## 快速开始
> 推荐直接参考 [MCP配置说明](./MCP_CONFIG.md),无需进行以下操作。
### 1. 安装依赖
```bash
pip install -r requirements.txt
```
### 2. 配置环境变量
```bash
# Linux/macOS
export WEKNORA_BASE_URL="http://localhost:8080/api/v1"
export WEKNORA_API_KEY="your_api_key_here"
# Windows PowerShell
$env:WEKNORA_BASE_URL="http://localhost:8080/api/v1"
$env:WEKNORA_API_KEY="your_api_key_here"
# Windows CMD
set WEKNORA_BASE_URL=http://localhost:8080/api/v1
set WEKNORA_API_KEY=your_api_key_here
```
### 3. 运行服务器
**推荐方式 - 使用主入口点:**
```bash
python main.py
```
**其他运行方式:**
```bash
# 使用原始启动脚本
python run_server.py
# 使用便捷脚本
python run.py
# 直接运行服务器模块
python weknora_mcp_server.py
# 作为 Python 模块运行
python -m weknora_mcp_server
```
### 4. 命令行选项
```bash
python main.py --help # 显示帮助信息
python main.py --check-only # 仅检查环境配置
python main.py --verbose # 启用详细日志
python main.py --version # 显示版本信息
```
## 安装为 Python 包
### 从 PyPI 安装
```bash
pip install tencent-weknora-mcp
# 或使用 uvx 直接运行(无需预安装)
uvx --from tencent-weknora-mcp weknora-mcp-server
```
> 官方 PyPI 包名为 **`tencent-weknora-mcp`**(Tencent/WeKnora 维护,Trusted Publishing 发布)。
> 旧社区包 `weknora-mcp` 请不要再使用。
> 安装后命令行入口仍为 `weknora-mcp-server` / `weknora-server`。
### 开发模式安装
```bash
pip install -e .
```
安装后可以使用命令行工具:
```bash
weknora-mcp-server
# 或
weknora-server
```
### 生产模式安装
```bash
pip install .
```
### 构建分发包
```bash
# 使用 setuptools
python setup.py sdist bdist_wheel
# 使用现代构建工具
pip install build
python -m build
```
## 测试模组
运行测试脚本验证模组是否正常工作:
```bash
python test_module.py
```
## 功能特性
该 MCP 服务器提供以下工具:
### 空间管理
- `create_tenant` - 创建新空间
- `list_tenants` - 列出所有空间
### 知识库管理
- `create_knowledge_base` - 创建知识库
- `list_knowledge_bases` - 列出知识库
- `get_knowledge_base` - 获取知识库详情
- `delete_knowledge_base` - 删除知识库
- `hybrid_search` - 混合搜索
### 知识管理
- `create_knowledge_from_file` - 从本地文件创建知识
- `create_knowledge_from_url` - 从 URL 创建知识
- `create_knowledge_from_text` - 从文本创建知识
- `list_knowledge` - 列出知识
- `get_knowledge` - 获取知识详情
- `delete_knowledge` - 删除知识
### 模型管理
- `create_model` - 创建模型
- `list_models` - 列出模型
- `get_model` - 获取模型详情
### 会话管理
- `create_session` - 创建聊天会话
- `get_session` - 获取会话详情
- `list_sessions` - 列出会话
- `delete_session` - 删除会话
### 聊天功能
- `chat` - 发送聊天消息
### 块管理
- `list_chunks` - 列出知识块
- `delete_chunk` - 删除知识块
## 故障排除
如果遇到导入错误,请确保:
1. 已安装所有必需的依赖包
2. Python 版本兼容(推荐 3.10+)
3. 没有文件名冲突(避免使用 `mcp.py` 作为文件名)
## 调用效果
<img width="950" height="2063" alt="118d078426f42f3d4983c13386085d7f" src="https://github.com/user-attachments/assets/09111ec8-0489-415c-969d-aa3835778e14" />
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env python3
"""
WeKnora MCP Server Package
A Model Context Protocol server that provides access to the WeKnora knowledge management API.
"""
__version__ = "1.1.1"
__author__ = "WeKnora Team"
__description__ = "WeKnora MCP Server - Model Context Protocol server for WeKnora API"
from weknora_mcp_server import WeKnoraClient, run
__all__ = ["WeKnoraClient", "run"]
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env python3
"""
手动 MCP 导入检查脚本(非 unittest 测试)。
用法: python check_imports.py
"""
try:
import mcp.server.stdio # noqa: F401
print("✓ mcp.server.stdio 导入成功")
except ImportError as e:
print(f"✗ mcp.server.stdio 导入失败: {e}")
try:
# mcp 2.x high-level server API (MCPServer, formerly FastMCP).
from mcp.server import MCPServer
print("✓ MCPServer 从 mcp.server 导入成功")
except ImportError as e:
print(f"✗ MCPServer 导入失败: {e}")
try:
from mcp.server.mcpserver.exceptions import ToolError # noqa: F401
print("✓ ToolError 从 mcp.server.mcpserver.exceptions 导入成功")
except ImportError as e:
print(f"✗ ToolError 导入失败: {e}")
try:
import mcp_types # noqa: F401 # standalone protocol types in mcp 2.x
print("✓ mcp_types 导入成功")
except ImportError as e:
print(f"✗ mcp_types 导入失败: {e}")
# 检查 MCP 包结构
import mcp
print(f"\nMCP 包版本: {getattr(mcp, '__version__', '未知')}")
print(f"MCP 包路径: {mcp.__file__}")
+178
View File
@@ -0,0 +1,178 @@
#!/usr/bin/env python3
"""
WeKnora MCP Server 主入口点
这个文件提供了一个统一的入口点来启动 WeKnora MCP 服务器。
可以通过多种方式运行:
1. python main.py
2. python -m weknora_mcp_server
3. weknora-mcp-server (安装后)
注意:在 stdio 传输下,stdout 是 JSON-RPC 通道,所有诊断/提示信息必须写入
stderr,否则会破坏 MCP 协议流导致客户端判定"启动失败"。本文件所有 print
均通过 stderr 输出。
"""
import argparse
import asyncio
import os
import sys
from pathlib import Path
def setup_environment():
"""设置环境和路径"""
# 确保当前目录在 Python 路径中
current_dir = Path(__file__).parent.absolute()
if str(current_dir) not in sys.path:
sys.path.insert(0, str(current_dir))
def check_dependencies():
"""检查依赖是否已安装"""
try:
import mcp
import requests
return True
except ImportError as e:
print(f"缺少依赖: {e}", file=sys.stderr)
print("请运行: pip install -r requirements.txt", file=sys.stderr)
return False
def check_environment_variables():
"""检查环境变量配置"""
base_url = os.getenv("WEKNORA_BASE_URL")
api_key = os.getenv("WEKNORA_API_KEY")
print("=== WeKnora MCP Server 环境检查 ===", file=sys.stderr)
print(f"Base URL: {base_url or 'http://localhost:8080/api/v1 (默认)'}", file=sys.stderr)
print(f"API Key: {'已设置' if api_key else '未设置 (警告)'}", file=sys.stderr)
if not base_url:
print("提示: 可以设置 WEKNORA_BASE_URL 环境变量", file=sys.stderr)
if not api_key:
print("警告: 建议设置 WEKNORA_API_KEY 环境变量", file=sys.stderr)
print("=" * 40, file=sys.stderr)
return True
def parse_arguments():
"""解析命令行参数"""
parser = argparse.ArgumentParser(
description="WeKnora MCP Server - Model Context Protocol server for WeKnora API",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
python main.py # 使用默认配置启动
python main.py --check-only # 仅检查环境,不启动服务器
python main.py --verbose # 启用详细日志
环境变量:
WEKNORA_BASE_URL WeKnora API 基础 URL (默认: http://localhost:8080/api/v1)
WEKNORA_API_KEY WeKnora API 密钥
MCP_SERVER_AUTH_TOKEN SSE/HTTP 传输必填,客户端通过 Authorization: Bearer 传递
""",
)
parser.add_argument(
"--check-only", action="store_true", help="仅检查环境配置,不启动服务器"
)
parser.add_argument("--verbose", "-v", action="store_true", help="启用详细日志输出")
parser.add_argument(
"--version", action="version", version="WeKnora MCP Server 1.1.1"
)
parser.add_argument(
"--transport",
choices=["stdio", "sse", "http"],
default=os.getenv("MCP_TRANSPORT", "stdio"),
help="Transport type: stdio (default), sse, or http",
)
parser.add_argument(
"--host",
default=os.getenv("MCP_HOST", "127.0.0.1"),
help="Bind host for network transports (default: 127.0.0.1)",
)
parser.add_argument(
"--port",
type=int,
default=int(os.getenv("MCP_PORT", "8000")),
help="Bind port for network transports (default: 8000)",
)
return parser.parse_args()
async def main():
"""主函数"""
args = parse_arguments()
# 设置环境
setup_environment()
# 检查依赖
if not check_dependencies():
sys.exit(1)
# 检查环境变量
check_environment_variables()
# 如果只是检查环境,则退出
if args.check_only:
print("环境检查完成。", file=sys.stderr)
return
# 设置日志级别
if args.verbose:
import logging
logging.basicConfig(level=logging.DEBUG)
print("已启用详细日志模式", file=sys.stderr)
try:
print(f"正在启动 WeKnora MCP Server (transport={args.transport})...", file=sys.stderr)
from weknora_mcp_server import run_stdio, run_sse, run_http
# Select transport mode based on CLI argument or MCP_TRANSPORT env var
# - stdio: Default, used by VS Code Copilot for local integration
# - sse: Server-Sent Events over HTTP, suitable for cloud/remote deployments
# - http: Streamable HTTP sessions (MCP 2025-03-26 spec), compatible with REST clients
if args.transport == "stdio":
# Stdio mode: communication via stdin/stdout pipes (typical for CLI integrations)
await run_stdio()
elif args.transport == "sse":
# SSE mode: HTTP server with Server-Sent Events for bidirectional streaming
await run_sse(args.host, args.port)
elif args.transport == "http":
# HTTP mode: HTTP REST server with request/response model
await run_http(args.host, args.port)
except ImportError as e:
print(f"导入错误: {e}", file=sys.stderr)
print("请确保所有文件都在正确的位置", file=sys.stderr)
sys.exit(1)
except KeyboardInterrupt:
print("\n服务器已停止", file=sys.stderr)
except Exception as e:
print(f"服务器运行错误: {e}", file=sys.stderr)
if args.verbose:
import traceback
traceback.print_exc()
sys.exit(1)
def sync_main():
"""同步版本的主函数,用于 entry_points"""
asyncio.run(main())
if __name__ == "__main__":
asyncio.run(main())
+108
View File
@@ -0,0 +1,108 @@
[build-system]
requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"]
build-backend = "setuptools.build_meta"
[project]
name = "tencent-weknora-mcp"
version = "1.1.1"
description = "WeKnora MCP Server - Model Context Protocol server for WeKnora API"
readme = "README.md"
license = {text = "MIT"}
authors = [
{name = "WeKnora Team", email = "support@weknora.com"}
]
maintainers = [
{name = "WeKnora Team", email = "support@weknora.com"}
]
keywords = ["mcp", "model-context-protocol", "weknora", "knowledge-management", "api-server"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
]
requires-python = ">=3.10"
dependencies = [
"mcp>=2,<3",
"requests>=2.31.0",
"starlette>=0.27.0",
"uvicorn>=0.24.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0",
"pytest-asyncio>=0.21.0",
"black>=23.0",
"flake8>=6.0",
"mypy>=1.0",
]
test = [
"pytest>=7.0",
"pytest-asyncio>=0.21.0",
"pytest-cov>=4.0",
]
[project.urls]
Homepage = "https://github.com/Tencent/WeKnora"
Documentation = "https://docs.weknora.com"
Repository = "https://github.com/Tencent/WeKnora/tree/main/mcp-server"
"Bug Reports" = "https://github.com/Tencent/WeKnora/issues"
Changelog = "https://github.com/Tencent/WeKnora/blob/main/mcp-server/CHANGELOG.md"
[project.scripts]
weknora-mcp-server = "main:sync_main"
weknora-server = "run_server:main"
[tool.setuptools]
py-modules = ["weknora_mcp_server", "upload_paths", "main", "run_server", "run", "test_module"]
include-package-data = true
[tool.setuptools.package-data]
"*" = ["*.md", "*.txt", "*.yml", "*.yaml"]
[tool.black]
line-length = 88
target-version = ['py38']
include = '\.pyi?$'
extend-exclude = '''
/(
# directories
\.eggs
| \.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| build
| dist
)/
'''
[tool.mypy]
python_version = "3.8"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
warn_unreachable = true
strict_equality = true
[tool.pytest.ini_options]
minversion = "7.0"
addopts = "-ra -q --strict-markers --strict-config"
testpaths = ["tests"]
asyncio_mode = "auto"
+4
View File
@@ -0,0 +1,4 @@
mcp>=2,<3
requests>=2.31.0
starlette>=0.27.0
uvicorn>=0.24.0
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env python3
"""
WeKnora MCP Server 便捷启动脚本
这是一个简化的启动脚本,提供最基本的功能。
对于更多选项,请使用 main.py
注意:在 stdio 传输下,stdout 是 JSON-RPC 通道,所有诊断/提示信息必须写入
stderr,否则会破坏 MCP 协议流导致客户端判定"启动失败"。本脚本所有 print
均通过 stderr 输出。
"""
import os
import sys
from pathlib import Path
def main():
"""简单的启动函数"""
# 添加当前目录到 Python 路径
current_dir = Path(__file__).parent.absolute()
if str(current_dir) not in sys.path:
sys.path.insert(0, str(current_dir))
# 检查环境变量
base_url = os.getenv("WEKNORA_BASE_URL", "http://localhost:8080/api/v1")
api_key = os.getenv("WEKNORA_API_KEY", "")
print("WeKnora MCP Server", file=sys.stderr)
print(f"Base URL: {base_url}", file=sys.stderr)
print(f"API Key: {'已设置' if api_key else '未设置'}", file=sys.stderr)
print("-" * 40, file=sys.stderr)
try:
# 导入并运行
from main import sync_main
sync_main()
except ImportError:
print("错误: 无法导入必要模块", file=sys.stderr)
print("请确保运行: pip install -r requirements.txt", file=sys.stderr)
sys.exit(1)
except KeyboardInterrupt:
print("\n服务器已停止", file=sys.stderr)
except Exception as e:
print(f"错误: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""
WeKnora MCP Server 启动脚本
注意:在 stdio 传输下,stdout 是 JSON-RPC 通道,所有诊断/提示信息必须写入
stderr,否则会破坏 MCP 协议流导致客户端判定"启动失败"。本脚本所有 print
均通过 stderr 输出。
"""
import asyncio
import os
import sys
def check_environment():
"""检查环境配置"""
base_url = os.getenv("WEKNORA_BASE_URL")
api_key = os.getenv("WEKNORA_API_KEY")
if not base_url:
print(
"警告: WEKNORA_BASE_URL 环境变量未设置,使用默认值: http://localhost:8080/api/v1",
file=sys.stderr,
)
if not api_key:
print("警告: WEKNORA_API_KEY 环境变量未设置", file=sys.stderr)
print(f"WeKnora Base URL: {base_url or 'http://localhost:8080/api/v1'}", file=sys.stderr)
print(f"API Key: {'已设置' if api_key else '未设置'}", file=sys.stderr)
def main():
"""主函数"""
print("启动 WeKnora MCP Server...", file=sys.stderr)
check_environment()
try:
from weknora_mcp_server import run
asyncio.run(run())
except ImportError as e:
print(f"导入错误: {e}", file=sys.stderr)
print("请确保已安装所有依赖: pip install -r requirements.txt", file=sys.stderr)
sys.exit(1)
except KeyboardInterrupt:
print("\n服务器已停止", file=sys.stderr)
except Exception as e:
print(f"服务器运行错误: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""
WeKnora MCP Server 安装脚本
"""
from setuptools import setup
# 读取 README 文件
def read_readme():
try:
with open("README.md", "r", encoding="utf-8") as f:
return f.read()
except FileNotFoundError:
return "WeKnora MCP Server - Model Context Protocol server for WeKnora API"
# 读取依赖
def read_requirements():
try:
with open("requirements.txt", "r", encoding="utf-8") as f:
return [
line.strip() for line in f if line.strip() and not line.startswith("#")
]
except FileNotFoundError:
return [
"mcp>=2,<3",
"requests>=2.31.0",
"starlette>=0.27.0",
"uvicorn>=0.24.0",
]
setup(
name="tencent-weknora-mcp",
version="1.1.1",
author="WeKnora Team",
author_email="support@weknora.com",
description="WeKnora MCP Server - Model Context Protocol server for WeKnora API",
long_description=read_readme(),
long_description_content_type="text/markdown",
url="https://github.com/Tencent/WeKnora/tree/main/mcp-server",
py_modules=["weknora_mcp_server", "upload_paths", "main", "run_server", "run", "test_module"],
classifiers=[
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
],
python_requires=">=3.10",
install_requires=read_requirements(),
entry_points={
"console_scripts": [
"weknora-mcp-server=main:sync_main",
"weknora-server=run_server:main",
],
},
include_package_data=True,
data_files=[
("", ["README.md", "requirements.txt", "LICENSE"]),
],
keywords="mcp model-context-protocol weknora knowledge-management api-server",
)
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""Tests for the create_knowledge_from_text MCP tool and client method.
unittest discover 会收集本文件中的 TestCase;也可直接运行:
python test_create_knowledge_from_text.py
"""
import unittest
from unittest import mock
import weknora_mcp_server as srv
class CreateKnowledgeFromTextTest(unittest.TestCase):
def test_client_method_exists_and_callable(self):
client = srv.WeKnoraClient("http://localhost:8080/api/v1", "k")
self.assertTrue(callable(getattr(client, "create_knowledge_from_text", None)))
def test_posts_to_manual_endpoint_with_expected_body(self):
client = srv.WeKnoraClient("http://localhost:8080/api/v1", "k")
with mock.patch.object(client, "_request", return_value={"ok": True}) as req:
client.create_knowledge_from_text("kb-1", "T", "C", tag_ids=["t1"])
args, kwargs = req.call_args
self.assertEqual(args[0], "POST")
self.assertEqual(args[1], "/knowledge-bases/kb-1/knowledge/manual")
body = kwargs["json"]
self.assertEqual(body["title"], "T")
self.assertEqual(body["content"], "C")
self.assertEqual(body["tag_ids"], ["t1"])
# status defaults to "publish" so the entry is indexed/searchable.
self.assertEqual(body["status"], "publish")
def test_status_can_be_overridden_to_draft(self):
client = srv.WeKnoraClient("http://localhost:8080/api/v1", "k")
with mock.patch.object(client, "_request", return_value={"ok": True}) as req:
client.create_knowledge_from_text("kb-1", "T", "C", status="draft")
self.assertEqual(req.call_args.kwargs["json"]["status"], "draft")
def test_omits_tag_ids_when_none(self):
client = srv.WeKnoraClient("http://localhost:8080/api/v1", "k")
with mock.patch.object(client, "_request", return_value={"ok": True}) as req:
client.create_knowledge_from_text("kb-1", "T", "C")
body = req.call_args.kwargs["json"]
self.assertNotIn("tag_ids", body)
self.assertEqual(body["status"], "publish")
def test_tool_function_resolves_kb_id_and_forwards_status(self):
with mock.patch.object(
srv.client, "resolve_kb_id", return_value="uuid-1"
) as rid, mock.patch.object(
srv.client, "create_knowledge_from_text", return_value={"ok": True}
) as method:
srv.create_knowledge_from_text("my-kb", "T", "C", status="draft")
rid.assert_called_once_with("my-kb")
method.assert_called_once_with("uuid-1", "T", "C", tag_ids=None, status="draft")
if __name__ == "__main__":
unittest.main()
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env python3
import os
import tempfile
import unittest
from upload_paths import clear_active_transport, resolve_upload_file_path, set_active_transport
class ResolveUploadFilePathTest(unittest.TestCase):
def setUp(self):
self._old_transport = os.environ.get("MCP_TRANSPORT")
self._old_roots = os.environ.get("MCP_ALLOWED_UPLOAD_DIRS")
os.environ.pop("MCP_TRANSPORT", None)
def tearDown(self):
clear_active_transport()
if self._old_transport is None:
os.environ.pop("MCP_TRANSPORT", None)
else:
os.environ["MCP_TRANSPORT"] = self._old_transport
if self._old_roots is None:
os.environ.pop("MCP_ALLOWED_UPLOAD_DIRS", None)
else:
os.environ["MCP_ALLOWED_UPLOAD_DIRS"] = self._old_roots
def test_network_transport_blocks_path_outside_cwd(self):
os.environ["MCP_TRANSPORT"] = "http"
with tempfile.TemporaryDirectory() as tmp:
os.chdir(tmp)
allowed = os.path.join(tmp, "note.txt")
with open(allowed, "w", encoding="utf-8") as handle:
handle.write("ok")
resolved = resolve_upload_file_path("note.txt")
self.assertEqual(resolved, os.path.realpath(allowed))
with self.assertRaises(ValueError):
resolve_upload_file_path("/etc/passwd")
def test_active_transport_without_env_blocks_path_outside_cwd(self):
"""CLI --transport http sets active transport without MCP_TRANSPORT env."""
set_active_transport("http")
with tempfile.TemporaryDirectory() as tmp:
os.chdir(tmp)
allowed = os.path.join(tmp, "note.txt")
with open(allowed, "w", encoding="utf-8") as handle:
handle.write("ok")
resolved = resolve_upload_file_path("note.txt")
self.assertEqual(resolved, os.path.realpath(allowed))
with self.assertRaises(ValueError):
resolve_upload_file_path("/etc/passwd")
def test_explicit_allowed_roots(self):
with tempfile.TemporaryDirectory() as tmp:
os.environ["MCP_ALLOWED_UPLOAD_DIRS"] = tmp
allowed = os.path.join(tmp, "doc.md")
with open(allowed, "w", encoding="utf-8") as handle:
handle.write("hello")
resolved = resolve_upload_file_path(os.path.join(tmp, "doc.md"))
self.assertEqual(resolved, os.path.realpath(allowed))
outside = tempfile.NamedTemporaryFile(delete=False)
try:
outside.write(b"secret")
outside.close()
with self.assertRaises(ValueError):
resolve_upload_file_path(outside.name)
finally:
os.unlink(outside.name)
if __name__ == "__main__":
unittest.main()
+166
View File
@@ -0,0 +1,166 @@
#!/usr/bin/env python3
"""Regression tests for MCP 2.x transport compatibility."""
import asyncio
import os
import subprocess
import sys
import threading
import unittest
from pathlib import Path
MCP_SERVER_DIR = Path(__file__).resolve().parent
class TransportRegressionTest(unittest.TestCase):
def test_http_transport_is_stateless(self):
import weknora_mcp_server as srv
from mcp.server import MCPServer
probe = MCPServer("probe")
probe.streamable_http_app(
host="127.0.0.1", stateless_http=srv.STREAMABLE_HTTP_STATELESS
)
self.assertTrue(probe.session_manager.stateless)
def test_sse_message_path_matches_legacy_mount(self):
import weknora_mcp_server as srv
from mcp.server import MCPServer
from starlette.routing import Mount, Route
probe = MCPServer("probe")
app = probe.sse_app(host="127.0.0.1", message_path=srv.SSE_MESSAGE_PATH)
mount_paths = [
route.path
for route in app.routes
if isinstance(route, (Mount, Route))
]
self.assertIn("/sse", mount_paths)
self.assertIn(
srv.SSE_MESSAGE_PATH.rstrip("/"),
{path.rstrip("/") for path in mount_paths},
)
def test_weknora_client_session_is_thread_local(self):
from weknora_mcp_server import WeKnoraClient
client = WeKnoraClient("http://localhost:8080/api/v1", "test-key")
barrier = threading.Barrier(2)
sessions: dict[str, object] = {}
def worker(name: str) -> None:
barrier.wait()
sessions[name] = client.session
threads = [
threading.Thread(target=worker, args=(name,))
for name in ("a", "b")
]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
self.assertEqual(len(sessions), 2)
self.assertIsNot(sessions["a"], sessions["b"])
class StdioToolsListTest(unittest.TestCase):
def test_tools_list_returns_30_tools(self):
async def _run() -> int:
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
params = StdioServerParameters(
command=sys.executable,
args=[str(MCP_SERVER_DIR / "weknora_mcp_server.py")],
env={
**os.environ,
"WEKNORA_API_KEY": "test-key",
},
)
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
return len(tools.tools)
count = asyncio.run(_run())
self.assertEqual(count, 30)
class HttpStatelessSmokeTest(unittest.TestCase):
def test_initialize_does_not_require_mcp_session_id(self):
import time
port = 19876
env = {
**os.environ,
"MCP_SERVER_AUTH_TOKEN": "test-token",
"WEKNORA_API_KEY": "test-key",
}
proc = subprocess.Popen(
[
sys.executable,
"weknora_mcp_server.py",
"--transport",
"http",
"--host",
"127.0.0.1",
"--port",
str(port),
],
cwd=MCP_SERVER_DIR,
env=env,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
try:
deadline = time.time() + 10
while time.time() < deadline:
probe = subprocess.run(
[
"curl",
"-s",
"-D",
"-",
"-o",
"/dev/null",
"-X",
"POST",
f"http://127.0.0.1:{port}/mcp",
"-H",
"Authorization: Bearer test-token",
"-H",
"Content-Type: application/json",
"-H",
"Accept: application/json, text/event-stream",
"-d",
(
'{"jsonrpc":"2.0","id":1,"method":"initialize",'
'"params":{"protocolVersion":"2025-03-26",'
'"capabilities":{},"clientInfo":{"name":"t","version":"1"}}}'
),
],
capture_output=True,
text=True,
)
if probe.returncode == 0 and "HTTP/" in probe.stdout:
break
time.sleep(0.2)
else:
self.fail("HTTP server did not become ready in time")
headers = probe.stdout.lower()
self.assertIn("200 ok", headers)
self.assertNotIn("mcp-session-id:", headers)
finally:
proc.terminate()
try:
proc.wait(timeout=5)
except subprocess.TimeoutExpired:
proc.kill()
if __name__ == "__main__":
unittest.main()
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""
WeKnora MCP Server 模组测试脚本
测试模组的各种启动方式和功能。unittest discover 会收集本文件中的 TestCase;
也可直接运行: python test_module.py
"""
import os
import subprocess
import sys
import unittest
from pathlib import Path
MCP_SERVER_DIR = Path(__file__).resolve().parent
REQUIRED_FILES = [
"__init__.py",
"main.py",
"run_server.py",
"weknora_mcp_server.py",
"requirements.txt",
"setup.py",
"pyproject.toml",
"README.md",
"INSTALL.md",
"LICENSE",
"MANIFEST.in",
]
class ModuleIntegrationTest(unittest.TestCase):
def test_imports(self):
import mcp # noqa: F401
import requests # noqa: F401
import weknora_mcp_server # noqa: F401
from weknora_mcp_server import WeKnoraClient, run # noqa: F401
import main # noqa: F401
def test_environment_optional_vars(self):
os.getenv("WEKNORA_BASE_URL")
os.getenv("WEKNORA_API_KEY")
def test_client_creation(self):
from weknora_mcp_server import WeKnoraClient
base_url = os.getenv("WEKNORA_BASE_URL", "http://localhost:8080/api/v1")
api_key = os.getenv("WEKNORA_API_KEY", "test_key")
client = WeKnoraClient(base_url, api_key)
self.assertEqual(client.base_url, base_url)
self.assertEqual(client.api_key, api_key)
def test_required_files_exist(self):
missing = [name for name in REQUIRED_FILES if not (MCP_SERVER_DIR / name).exists()]
self.assertEqual(missing, [], f"Missing files: {missing}")
def test_main_help(self):
result = subprocess.run(
[sys.executable, "main.py", "--help"],
capture_output=True,
text=True,
timeout=10,
cwd=MCP_SERVER_DIR,
)
self.assertEqual(result.returncode, 0, result.stderr)
def test_main_check_only(self):
result = subprocess.run(
[sys.executable, "main.py", "--check-only"],
capture_output=True,
text=True,
timeout=10,
cwd=MCP_SERVER_DIR,
)
self.assertEqual(result.returncode, 0, result.stderr)
def test_wiki_tools(self):
import weknora_mcp_server
client = weknora_mcp_server.WeKnoraClient("http://localhost:8080/api/v1", "test")
for method in ["wiki_search", "wiki_read_page", "wiki_index_view"]:
self.assertTrue(hasattr(client, method), f"WeKnoraClient missing: {method}")
self.assertTrue(callable(getattr(client, method)), f"{method} not callable")
def test_pyproject_metadata(self):
text = (MCP_SERVER_DIR / "pyproject.toml").read_text(encoding="utf-8")
self.assertIn("tencent-weknora-mcp", text)
self.assertIn("weknora-mcp-server", text)
if __name__ == "__main__":
unittest.main(verbosity=2)
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env python3
"""Regression: MCP stdio entry scripts must not write diagnostics to stdout."""
import subprocess
import sys
import unittest
from pathlib import Path
MCP_SERVER_DIR = Path(__file__).resolve().parent
class StdioStdoutPurityTest(unittest.TestCase):
def test_main_check_only_keeps_stdout_empty(self):
result = subprocess.run(
[sys.executable, "main.py", "--check-only"],
capture_output=True,
timeout=10,
cwd=MCP_SERVER_DIR,
)
self.assertEqual(result.returncode, 0)
self.assertEqual(
result.stdout,
b"",
f"stdout must stay empty for MCP stdio compatibility, got: {result.stdout!r}",
)
self.assertTrue(
result.stderr,
"environment check output should appear on stderr",
)
def test_run_server_startup_keeps_stdout_empty(self):
proc = subprocess.Popen(
[sys.executable, "run_server.py"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=MCP_SERVER_DIR,
)
try:
stdout, stderr = proc.communicate(timeout=2)
except subprocess.TimeoutExpired:
proc.terminate()
stdout, stderr = proc.communicate(timeout=5)
self.assertEqual(
stdout,
b"",
f"run_server.py must not write to stdout before JSON-RPC, got: {stdout!r}",
)
self.assertTrue(stderr, "startup diagnostics should appear on stderr")
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,34 @@
from weknora_mcp_server import WeKnoraClient
def test_resolve_agent_id_accepts_non_uuid_agent_id(monkeypatch):
client = WeKnoraClient("http://example.test/api/v1", "")
agents = {
"data": [
{
"id": "builtin-wiki-researcher",
"name": "维基问答",
}
]
}
monkeypatch.setattr(client, "_request", lambda *args, **kwargs: agents)
assert (
client.resolve_agent_id("builtin-wiki-researcher")
== "builtin-wiki-researcher"
)
def test_resolve_agent_id_accepts_agent_name_case_insensitively(monkeypatch):
client = WeKnoraClient("http://example.test/api/v1", "")
agents = {
"data": [
{
"id": "builtin-wiki-researcher",
"name": "Wiki Questioner",
}
]
}
monkeypatch.setattr(client, "_request", lambda *args, **kwargs: agents)
assert client.resolve_agent_id("wiki questioner") == "builtin-wiki-researcher"
+86
View File
@@ -0,0 +1,86 @@
from weknora_mcp_server import WeKnoraClient, _normalize_kb_entries
# Shapes mirror GET /knowledge-bases and GET /shared-knowledge-bases wire JSON.
# See internal/handler/knowledgebase.go (buildKBListResponse) and
# internal/handler/organization.go (sharedKBRow / ListSharedKnowledgeBases).
def test_normalize_kb_entries_flattens_shared_rows():
resp = {
"success": True,
"data": [
{
"knowledge_base": {"id": "kb-shared-1", "name": "Shared Docs"},
"share_id": "kbs-1",
"organization_id": "org-1",
"permission": "viewer",
}
],
"total": 1,
}
assert _normalize_kb_entries(resp) == [
{"id": "kb-shared-1", "name": "Shared Docs"}
]
def test_normalize_kb_entries_passes_through_owned_rows():
resp = {
"success": True,
"data": [{"id": "kb-owned-1", "name": "My Docs"}],
}
assert _normalize_kb_entries(resp) == [{"id": "kb-owned-1", "name": "My Docs"}]
def test_resolve_kb_id_accepts_shared_kb_name(monkeypatch):
client = WeKnoraClient("http://example.test/api/v1", "")
def fake_request(method, path, **kwargs):
if path == "/knowledge-bases":
return {"success": True, "data": []}
if path == "/shared-knowledge-bases":
return {
"success": True,
"data": [
{
"knowledge_base": {
"id": "kb-shared-1",
"name": "技术文档库",
},
"share_id": "kbs-1",
}
],
}
raise AssertionError(f"unexpected request: {method} {path}")
monkeypatch.setattr(client, "_request", fake_request)
assert client.resolve_kb_id("技术文档库") == "kb-shared-1"
def test_resolve_kb_id_prefers_owned_over_shared_name(monkeypatch):
client = WeKnoraClient("http://example.test/api/v1", "")
def fake_request(method, path, **kwargs):
if path == "/knowledge-bases":
return {
"success": True,
"data": [{"id": "kb-owned-1", "name": "Same Name"}],
}
if path == "/shared-knowledge-bases":
return {
"success": True,
"data": [
{
"knowledge_base": {
"id": "kb-shared-1",
"name": "Same Name",
},
"share_id": "kbs-1",
}
],
}
raise AssertionError(f"unexpected request: {method} {path}")
monkeypatch.setattr(client, "_request", fake_request)
assert client.resolve_kb_id("same name") == "kb-owned-1"
+66
View File
@@ -0,0 +1,66 @@
"""Local file path validation for MCP upload tools."""
from __future__ import annotations
import os
from typing import List, Optional
_active_transport: Optional[str] = None
def set_active_transport(transport: str) -> None:
"""Record the transport selected at server startup (CLI or run_* entry)."""
global _active_transport
_active_transport = transport.strip().lower()
def clear_active_transport() -> None:
"""Reset startup transport override (for tests)."""
global _active_transport
_active_transport = None
def _current_transport() -> str:
if _active_transport is not None:
return _active_transport
return os.getenv("MCP_TRANSPORT", "stdio").strip().lower()
def _path_within_root(resolved_path: str, root: str) -> bool:
root = os.path.realpath(root)
resolved_path = os.path.realpath(resolved_path)
try:
common = os.path.commonpath([root, resolved_path])
except ValueError:
return False
return common == root
def _allowed_upload_roots() -> List[str]:
"""Return directories local files may be read from for upload tools."""
raw = os.getenv("MCP_ALLOWED_UPLOAD_DIRS", "").strip()
if raw:
return [os.path.realpath(part.strip()) for part in raw.split(",") if part.strip()]
transport = _current_transport()
if transport in ("sse", "http"):
return [os.path.realpath(os.getcwd())]
return []
def resolve_upload_file_path(file_path: str) -> str:
"""Resolve and validate a local file path for create_knowledge_from_file."""
raw = (file_path or "").strip()
if not raw:
raise ValueError("file path is required")
if "\x00" in raw:
raise ValueError("file path contains invalid characters")
resolved = os.path.realpath(raw)
if not os.path.isfile(resolved):
raise ValueError(f"file not found: {file_path}")
roots = _allowed_upload_roots()
if roots and not any(_path_within_root(resolved, root) for root in roots):
raise ValueError("file path is outside allowed upload directories")
return resolved
+1652
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff