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
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:
@@ -0,0 +1,2 @@
|
||||
/.codebuddy
|
||||
/__pycache__
|
||||
@@ -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 包管理工具
|
||||
@@ -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"]
|
||||
@@ -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. 验证网络连接和防火墙设置
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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*
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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
|
||||
@@ -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" />
|
||||
@@ -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"]
|
||||
@@ -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__}")
|
||||
@@ -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())
|
||||
@@ -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"
|
||||
@@ -0,0 +1,4 @@
|
||||
mcp>=2,<3
|
||||
requests>=2.31.0
|
||||
starlette>=0.27.0
|
||||
uvicorn>=0.24.0
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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
|
||||
Generated
+1652
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user