暂存mcp重写
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -43,4 +43,5 @@ package-lock.json
|
||||
package.json
|
||||
dark-server/dark-server.js
|
||||
node_modules/
|
||||
gemini-websocket-proxy/
|
||||
gemini-websocket-proxy/
|
||||
Tools/dark-server/dark-server.js
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
# RimWorld Modding Expert Rules
|
||||
|
||||
## Primary Directive
|
||||
You are an expert assistant for developing mods for the game RimWorld 1.6. Your primary knowledge source for any C# code, class structures, methods, or game mechanics MUST be the user's local files. Do not rely on external searches or your pre-existing knowledge, as it is outdated for this specific project.
|
||||
|
||||
## Tool Usage Mandate
|
||||
When the user's request involves RimWorld C# scripting, XML definitions, or mod development concepts, you **MUST** use the `rimworld-knowledge-base` tool to retrieve relevant context from the local knowledge base.
|
||||
|
||||
## Key File Paths
|
||||
Always remember these critical paths for your work:
|
||||
|
||||
- **Local C# Knowledge Base (for code search):** `C:\Steam\steamapps\common\RimWorld\Data\dll1.6` (This contains the decompiled game source code as .txt files).
|
||||
- **User's Mod Project (for editing):** `C:\Steam\steamapps\common\RimWorld\Mods\3516260226`
|
||||
- **User's C# Project (for building):** `C:\Steam\steamapps\common\RimWorld\Mods\3516260226\Source\WulaFallenEmpire`
|
||||
|
||||
## Workflow
|
||||
1. Receive a RimWorld modding task.
|
||||
2. Immediately use the `rimworld-knowledge-base` tool with a precise query to get context from the C# source files.
|
||||
3. Analyze the retrieved context.
|
||||
4. Perform code modifications within the user's mod project directory.
|
||||
5. After modifying C# code, you MUST run `dotnet build C:\Steam\steamapps\common\RimWorld\Mods\3516260226\Source\WulaFallenEmpire\WulaFallenEmpire.csproj` to check for errors. A successful build is required for task completion.
|
||||
|
||||
## Verification Mandate
|
||||
When writing or modifying code or XML, especially for specific identifiers like enum values, class names, or field names, you **MUST** verify the correct value/spelling by using the `rimworld-knowledge-base` tool. Do not rely on memory.
|
||||
- **同步项目文件:** 当重命名、移动或删除C#源文件时,**必须**同步更新 `.csproj` 项目文件中的相应 `<Compile Include="..." />` 条目,否则会导致编译失败。
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
trigger: always_on
|
||||
alwaysApply: true
|
||||
---
|
||||
# RimWorld Modding Expert Rules
|
||||
|
||||
## Primary Directive
|
||||
You are an expert assistant for developing mods for the game RimWorld 1.6. Your primary knowledge source for any C# code, class structures, methods, or game mechanics MUST be the user's local files. Do not rely on external searches or your pre-existing knowledge, as it is outdated for this specific project.
|
||||
|
||||
## Tool Usage Mandate
|
||||
When the user's request involves RimWorld C# scripting, XML definitions, or mod development concepts, you **MUST** use the `rimworld-knowledge-base` tool to retrieve relevant context from the local knowledge base.
|
||||
|
||||
## Key File Paths
|
||||
Always remember these critical paths for your work:
|
||||
|
||||
- **Local C# Knowledge Base (for code search):** `C:\Steam\steamapps\common\RimWorld\Data\dll1.6` (This contains the decompiled game source code as .txt files).
|
||||
- **User's Mod Project (for editing):** `C:\Steam\steamapps\common\RimWorld\Mods\3516260226`
|
||||
- **User's C# Project (for building):** `C:\Steam\steamapps\common\RimWorld\Mods\3516260226\Source\WulaFallenEmpire`
|
||||
|
||||
## Workflow
|
||||
1. Receive a RimWorld modding task.
|
||||
2. Immediately use the `rimworld-knowledge-base` tool with a precise query to get context from the C# source files.
|
||||
3. Analyze the retrieved context.
|
||||
4. Perform code modifications within the user's mod project directory.
|
||||
5. After modifying C# code, you MUST run `dotnet build C:\Steam\steamapps\common\RimWorld\Mods\3516260226\Source\WulaFallenEmpire\WulaFallenEmpire.csproj` to check for errors. A successful build is required for task completion.
|
||||
|
||||
## Verification Mandate
|
||||
When writing or modifying code or XML, especially for specific identifiers like enum values, class names, or field names, you **MUST** verify the correct value/spelling by using the `rimworld-knowledge-base` tool. Do not rely on memory.
|
||||
- **同步项目文件:** 当重命名、移动或删除C#源文件时,**必须**同步更新 `.csproj` 项目文件中的相应 `<Compile Include="..." />` 条目,否则会导致编译失败。
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
直接调用MCP服务器的Python接口
|
||||
绕过Qoder IDE,直接使用RimWorld知识库
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
# 添加MCP路径
|
||||
MCP_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
SDK_PATH = os.path.join(MCP_DIR, 'python-sdk', 'src')
|
||||
if SDK_PATH not in sys.path:
|
||||
sys.path.insert(0, SDK_PATH)
|
||||
|
||||
# 导入MCP服务器
|
||||
from mcpserver_stdio import get_context
|
||||
|
||||
class DirectMCPClient:
|
||||
"""直接调用MCP服务器的客户端"""
|
||||
|
||||
def __init__(self):
|
||||
print("🚀 直接MCP客户端已启动")
|
||||
print("📚 RimWorld知识库已加载")
|
||||
|
||||
def query(self, question: str) -> str:
|
||||
"""查询RimWorld知识库"""
|
||||
try:
|
||||
print(f"🔍 正在查询: {question}")
|
||||
result = get_context(question)
|
||||
return result
|
||||
except Exception as e:
|
||||
return f"查询出错: {e}"
|
||||
|
||||
def interactive_mode(self):
|
||||
"""交互模式"""
|
||||
print("\n" + "="*60)
|
||||
print("🎯 RimWorld知识库 - 交互模式")
|
||||
print("输入问题查询知识库,输入 'quit' 或 'exit' 退出")
|
||||
print("="*60)
|
||||
|
||||
while True:
|
||||
try:
|
||||
question = input("\n❓ 请输入您的问题: ").strip()
|
||||
|
||||
if question.lower() in ['quit', 'exit', '退出', 'q']:
|
||||
print("👋 再见!")
|
||||
break
|
||||
|
||||
if not question:
|
||||
print("⚠️ 请输入有效的问题")
|
||||
continue
|
||||
|
||||
print("\n🔄 正在搜索...")
|
||||
result = self.query(question)
|
||||
|
||||
print("\n📖 查询结果:")
|
||||
print("-" * 50)
|
||||
print(result)
|
||||
print("-" * 50)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n👋 用户中断,退出程序")
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"\n❌ 出现错误: {e}")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='直接调用RimWorld MCP知识库')
|
||||
parser.add_argument('--query', '-q', type=str, help='直接查询问题')
|
||||
parser.add_argument('--interactive', '-i', action='store_true', help='进入交互模式')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
client = DirectMCPClient()
|
||||
|
||||
if args.query:
|
||||
# 直接查询模式
|
||||
result = client.query(args.query)
|
||||
print("\n📖 查询结果:")
|
||||
print("="*60)
|
||||
print(result)
|
||||
print("="*60)
|
||||
elif args.interactive:
|
||||
# 交互模式
|
||||
client.interactive_mode()
|
||||
else:
|
||||
# 默认显示帮助
|
||||
print("\n🔧 使用方法:")
|
||||
print("1. 直接查询: python direct_mcp_client.py -q \"ThingDef是什么\"")
|
||||
print("2. 交互模式: python direct_mcp_client.py -i")
|
||||
print("3. 查看帮助: python direct_mcp_client.py -h")
|
||||
|
||||
# 演示查询
|
||||
print("\n🎬 演示查询:")
|
||||
demo_result = client.query("ThingDef")
|
||||
print(demo_result[:500] + "..." if len(demo_result) > 500 else demo_result)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
488
MCP/mcpserver_stdio_complete.py
Normal file
488
MCP/mcpserver_stdio_complete.py
Normal file
@@ -0,0 +1,488 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import glob
|
||||
from http import HTTPStatus
|
||||
from collections import defaultdict
|
||||
from typing import List, Dict, Any, Optional, Tuple
|
||||
import threading
|
||||
|
||||
import dashscope
|
||||
from tenacity import retry, stop_after_attempt, wait_random_exponential
|
||||
from dotenv import load_dotenv
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
# 1. --- 配置与初始化 ---
|
||||
|
||||
# 路径配置
|
||||
MCP_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
LOG_FILE_PATH = os.path.join(MCP_DIR, 'rimworld_rag.log')
|
||||
|
||||
# 设置日志
|
||||
logging.basicConfig(filename=LOG_FILE_PATH, level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
encoding='utf-8')
|
||||
|
||||
# 加载环境变量
|
||||
load_dotenv(os.path.join(MCP_DIR, '.env'))
|
||||
DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY")
|
||||
if not DASHSCOPE_API_KEY:
|
||||
logging.warning("Missing DASHSCOPE_API_KEY. Semantic reranking will be disabled.")
|
||||
else:
|
||||
dashscope.api_key = DASHSCOPE_API_KEY
|
||||
|
||||
# 数据根目录
|
||||
# 根据之前的 list_files 结果调整路径
|
||||
RIMWORLD_DATA_ROOT = os.path.abspath(os.path.join(MCP_DIR, "..", "..", "..", "Data"))
|
||||
RIMWORLD_SOURCE_ROOT = os.path.abspath(os.path.join(MCP_DIR, "..", "..", "..", "dll1.6"))
|
||||
|
||||
logging.info(f"Data Root: {RIMWORLD_DATA_ROOT}")
|
||||
logging.info(f"Source Root: {RIMWORLD_SOURCE_ROOT}")
|
||||
|
||||
# 2. --- 核心索引类 (内存版) ---
|
||||
|
||||
class SymbolIndex:
|
||||
"""
|
||||
内存索引构建器。
|
||||
替代 C# 项目中的 Lucene/MetadataStore。
|
||||
启动时扫描所有文件,建立 Symbol -> FilePath 的映射。
|
||||
"""
|
||||
def __init__(self):
|
||||
self.symbol_map = {} # symbol_id -> file_path
|
||||
self.files_cache = [] # 所有文件路径列表
|
||||
self.is_initialized = False
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def initialize(self):
|
||||
with self._lock:
|
||||
if self.is_initialized: return
|
||||
logging.info("正在构建内存索引...")
|
||||
start_t = time.time()
|
||||
|
||||
# 1. 扫描 C# 源码
|
||||
if os.path.exists(RIMWORLD_SOURCE_ROOT):
|
||||
for root, _, files in os.walk(RIMWORLD_SOURCE_ROOT):
|
||||
for file in files:
|
||||
if file.endswith(('.cs', '.txt')):
|
||||
full_path = os.path.join(root, file)
|
||||
# 假设文件名即类名 (简化逻辑)
|
||||
symbol = os.path.splitext(file)[0]
|
||||
self.symbol_map[symbol] = full_path
|
||||
self.files_cache.append(full_path)
|
||||
else:
|
||||
logging.warning(f"Source root not found: {RIMWORLD_SOURCE_ROOT}")
|
||||
|
||||
# 2. 扫描 XML Defs
|
||||
# 扫描 Data 目录下的所有子目录
|
||||
if os.path.exists(RIMWORLD_DATA_ROOT):
|
||||
for root, _, files in os.walk(RIMWORLD_DATA_ROOT):
|
||||
for file in files:
|
||||
if file.endswith('.xml'):
|
||||
full_path = os.path.join(root, file)
|
||||
self.files_cache.append(full_path)
|
||||
# 快速解析 XML 找 defName
|
||||
try:
|
||||
self._scan_xml_defs(full_path)
|
||||
except Exception as e:
|
||||
logging.warning(f"解析 XML 失败 {full_path}: {e}")
|
||||
else:
|
||||
logging.warning(f"Data root not found: {RIMWORLD_DATA_ROOT}")
|
||||
|
||||
logging.info(f"索引构建完成,耗时 {time.time() - start_t:.2f}s,收录符号 {len(self.symbol_map)} 个")
|
||||
self.is_initialized = True
|
||||
|
||||
def _scan_xml_defs(self, path):
|
||||
content = read_file_content(path)
|
||||
if not content: return
|
||||
|
||||
# 使用正则快速提取 defName
|
||||
def_names = re.findall(r'<defName>(.*?)</defName>', content)
|
||||
for name in def_names:
|
||||
self.symbol_map[f"xml:{name}"] = path
|
||||
|
||||
def search_symbols(self, keyword: str, kind: str = None) -> List[Tuple[str, str]]:
|
||||
"""简单的关键词匹配"""
|
||||
results = []
|
||||
kw_lower = keyword.lower()
|
||||
for sym, path in self.symbol_map.items():
|
||||
if kind == 'csharp' and sym.startswith('xml:'): continue
|
||||
if kind == 'xml' and not sym.startswith('xml:'): continue
|
||||
|
||||
if kw_lower in sym.lower():
|
||||
results.append((sym, path))
|
||||
# 移除硬限制,以便后续排序能找到最佳结果
|
||||
# if len(results) > 200: break
|
||||
return results
|
||||
|
||||
# 全局索引实例
|
||||
global_index = SymbolIndex()
|
||||
|
||||
# 3. --- 辅助工具函数 ---
|
||||
|
||||
def read_file_content(path: str) -> str:
|
||||
"""健壮的文件读取"""
|
||||
encodings = ['utf-8', 'utf-8-sig', 'gbk', 'latin-1']
|
||||
for enc in encodings:
|
||||
try:
|
||||
with open(path, 'r', encoding=enc) as f:
|
||||
return f.read()
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
except Exception:
|
||||
continue
|
||||
return ""
|
||||
|
||||
def extract_xml_fragment(file_path: str, def_name: str) -> str:
|
||||
"""提取 XML 中特定的 Def 块"""
|
||||
content = read_file_content(file_path)
|
||||
try:
|
||||
# 尝试正则匹配整个 Def 块
|
||||
# 假设 Def 格式为 <DefType defName="NAME">...</DefType> 或 <DefType><defName>NAME</defName>...</DefType>
|
||||
|
||||
# 策略 1: 查找 <defName>NAME</defName>,然后向上找最近的 <DefType>
|
||||
pattern = r"<(\w+)\s*(?:Name=\"[^\"]*\")?>\s*<defName>" + re.escape(def_name) + r"</defName>"
|
||||
match = re.search(pattern, content)
|
||||
|
||||
if not match:
|
||||
# 策略 2: 查找 defName="NAME"
|
||||
pattern = r"<(\w+)\s+[^>]*defName=\"" + re.escape(def_name) + r"\""
|
||||
match = re.search(pattern, content)
|
||||
|
||||
if match:
|
||||
tag_name = match.group(1)
|
||||
# 找到开始位置
|
||||
start_pos = match.start()
|
||||
# 寻找对应的结束标签 </Tag>
|
||||
# 这是一个简化的查找,不支持嵌套同名标签,但在 RimWorld Defs 中通常足够
|
||||
end_tag = f"</{tag_name}>"
|
||||
end_pos = content.find(end_tag, start_pos)
|
||||
if end_pos != -1:
|
||||
return content[start_pos:end_pos + len(end_tag)]
|
||||
except Exception as e:
|
||||
logging.error(f"Error extracting XML fragment: {e}")
|
||||
|
||||
# 降级方案:返回文件内容,如果太长则截断
|
||||
lines = content.split('\n')
|
||||
if len(lines) > 100:
|
||||
return "\n".join(lines[:100]) + "\n... (XML too long, truncated)"
|
||||
return content
|
||||
|
||||
def extract_csharp_fragment(file_path: str, symbol: str) -> str:
|
||||
"""提取 C# 类或方法"""
|
||||
content = read_file_content(file_path)
|
||||
# 简化:直接返回整个文件,如果太大则截断
|
||||
lines = content.split('\n')
|
||||
if len(lines) > 500:
|
||||
return "\n".join(lines[:500]) + "\n\n// ... (File too long, truncated)"
|
||||
return content
|
||||
|
||||
# 4. --- 功能实现类 ---
|
||||
|
||||
class RoughSearcher:
|
||||
"""模仿 C# 项目的 RoughSearcher,结合关键词过滤和语义重排序"""
|
||||
|
||||
def __init__(self, config: Dict):
|
||||
self.config = config
|
||||
global_index.initialize()
|
||||
|
||||
def search(self, query: str) -> List[Dict]:
|
||||
# 1. 粗筛:在文件名和 Symbol 中查找
|
||||
candidates = global_index.search_symbols(query, self.config.get('kind'))
|
||||
|
||||
# 如果没有找到直接匹配,尝试更宽松的搜索(如分词)
|
||||
if not candidates:
|
||||
tokens = query.split()
|
||||
if len(tokens) > 1:
|
||||
candidates = global_index.search_symbols(tokens[0], self.config.get('kind'))
|
||||
|
||||
if not candidates:
|
||||
return []
|
||||
|
||||
# 优化:对候选结果进行预排序
|
||||
# 优先级:完全匹配 > 前缀匹配 > 长度更短 > 字母顺序
|
||||
candidates.sort(key=lambda x: (
|
||||
x[0].lower() != query.lower(),
|
||||
not x[0].lower().startswith(query.lower()),
|
||||
len(x[0]),
|
||||
x[0]
|
||||
))
|
||||
|
||||
# 2. 准备重排序文档
|
||||
docs = []
|
||||
valid_candidates = []
|
||||
|
||||
# 增加重排数量到 50,提高召回率
|
||||
for sym, path in candidates[:50]:
|
||||
content = read_file_content(path)
|
||||
# 截取一部分内容用于语义判断
|
||||
snippet = content[:1000]
|
||||
docs.append(f"Title: {sym}\nContent: {snippet}")
|
||||
valid_candidates.append((sym, path))
|
||||
|
||||
# 3. 使用 DashScope Rerank (如果配置了key)
|
||||
ranked_results = []
|
||||
if DASHSCOPE_API_KEY and docs:
|
||||
try:
|
||||
response = dashscope.TextReRank.call(
|
||||
model='gte-rerank',
|
||||
query=query,
|
||||
documents=docs,
|
||||
top_n=self.config.get('max_results', 10)
|
||||
)
|
||||
if response.status_code == HTTPStatus.OK:
|
||||
for res in response.output['results']:
|
||||
idx = res['index']
|
||||
sym, path = valid_candidates[idx]
|
||||
score = res['score']
|
||||
ranked_results.append(self._format_result(sym, path, score))
|
||||
else:
|
||||
logging.error(f"Rerank API error: {response}")
|
||||
# 降级:直接返回
|
||||
ranked_results = [self._format_result(s, p, 0.5) for s, p in valid_candidates]
|
||||
except Exception as e:
|
||||
logging.error(f"Rerank exception: {e}")
|
||||
ranked_results = [self._format_result(s, p, 0.5) for s, p in valid_candidates]
|
||||
else:
|
||||
# 无 Key 模式:按名称长度排序作为简单 heuristic
|
||||
valid_candidates.sort(key=lambda x: len(x[0]))
|
||||
ranked_results = [self._format_result(s, p, 1.0) for s, p in valid_candidates[:self.config.get('max_results', 10)]]
|
||||
|
||||
return ranked_results
|
||||
|
||||
def _format_result(self, symbol, path, score):
|
||||
is_xml = symbol.startswith('xml:')
|
||||
return {
|
||||
"symbolId": symbol,
|
||||
"kind": "xml" if is_xml else "csharp",
|
||||
"symbolKind": "definition" if is_xml else "class",
|
||||
"path": path,
|
||||
"title": symbol,
|
||||
"score": float(score),
|
||||
"preview": "Use get_item to view content"
|
||||
}
|
||||
|
||||
class GraphQuerier:
|
||||
"""
|
||||
模拟 C# 项目的 GraphQuerier。
|
||||
由于没有预计算的 .bin 图数据,这里使用实时解析 (Heuristic) 实现。
|
||||
"""
|
||||
def __init__(self):
|
||||
global_index.initialize()
|
||||
|
||||
def query_uses(self, symbol: str, kind: str = "all") -> List[Dict]:
|
||||
"""查询下游依赖 (Symbol 引用了什么)"""
|
||||
if symbol not in global_index.symbol_map:
|
||||
return []
|
||||
|
||||
file_path = global_index.symbol_map[symbol]
|
||||
content = read_file_content(file_path)
|
||||
edges = []
|
||||
|
||||
if symbol.startswith('xml:'):
|
||||
# XML 解析逻辑
|
||||
# 1. 查找 <ParentName> (Inherits)
|
||||
parents = re.findall(r'ParentName="([^"]+)"', content)
|
||||
parents += re.findall(r'<ParentName>([^<]+)</ParentName>', content)
|
||||
for p in parents:
|
||||
edges.append({"target": f"xml:{p}", "kind": "Inherits"})
|
||||
|
||||
# 2. 查找 <xxxClass> (BindsClass)
|
||||
classes = re.findall(r'<[\w]+Class>([\w\.]+)</[\w]+Class>', content) # 如 <thingClass>
|
||||
classes += re.findall(r'Class="([\w\.]+)"', content) # 如 <li Class="...">
|
||||
for c in classes:
|
||||
# 简单的命名空间推断
|
||||
target_cls = c
|
||||
if '.' not in c:
|
||||
# 尝试推断,通常是 RimWorld 或 Verse
|
||||
if f"RimWorld.{c}" in global_index.symbol_map: target_cls = f"RimWorld.{c}"
|
||||
elif f"Verse.{c}" in global_index.symbol_map: target_cls = f"Verse.{c}"
|
||||
|
||||
edges.append({"target": target_cls, "kind": "XmlBindsClass"})
|
||||
|
||||
# 3. 查找 <defName> 引用 (References)
|
||||
potential_refs = re.findall(r'>([\w]+)</', content)
|
||||
for ref in potential_refs:
|
||||
xml_ref = f"xml:{ref}"
|
||||
if xml_ref in global_index.symbol_map and xml_ref != symbol:
|
||||
edges.append({"target": xml_ref, "kind": "XmlReferences"})
|
||||
|
||||
else:
|
||||
# C# 解析逻辑 (Regex)
|
||||
# 1. 继承 : BaseClass
|
||||
inherits = re.search(r'class\s+\w+\s*:\s*([\w\.]+)', content)
|
||||
if inherits:
|
||||
edges.append({"target": inherits.group(1), "kind": "Inherits"})
|
||||
|
||||
# 2. 字段/方法调用 (References) - 简化版:查找所有大写开头的单词
|
||||
tokens = set(re.findall(r'\b[A-Z]\w+\b', content))
|
||||
for t in tokens:
|
||||
if t in global_index.symbol_map and t != symbol:
|
||||
edges.append({"target": t, "kind": "References"})
|
||||
elif f"RimWorld.{t}" in global_index.symbol_map:
|
||||
edges.append({"target": f"RimWorld.{t}", "kind": "References"})
|
||||
elif f"Verse.{t}" in global_index.symbol_map:
|
||||
edges.append({"target": f"Verse.{t}", "kind": "References"})
|
||||
|
||||
# 过滤 Kind
|
||||
if kind != 'all':
|
||||
filtered = []
|
||||
for e in edges:
|
||||
is_xml_target = e['target'].startswith('xml:')
|
||||
if kind == 'xml' and is_xml_target: filtered.append(e)
|
||||
elif kind == 'csharp' and not is_xml_target: filtered.append(e)
|
||||
return filtered
|
||||
|
||||
return edges
|
||||
|
||||
def query_used_by(self, symbol: str, kind: str = "all") -> List[Dict]:
|
||||
"""
|
||||
查询上游依赖 (谁使用了 Symbol)。
|
||||
这个操作非常昂贵 (全文件扫描),C# 使用了倒排索引。
|
||||
Python 版这里只能用 grep (glob scan) 模拟,速度会慢。
|
||||
"""
|
||||
results = []
|
||||
search_term = symbol.replace('xml:', '')
|
||||
|
||||
# 限制扫描文件数以防超时,优先扫描同类型文件
|
||||
scan_xml = kind in ['all', 'xml']
|
||||
scan_cs = kind in ['all', 'csharp']
|
||||
|
||||
# 遍历所有已知文件进行文本匹配
|
||||
count = 0
|
||||
for file_path in global_index.files_cache:
|
||||
is_xml_file = file_path.endswith('.xml')
|
||||
if is_xml_file and not scan_xml: continue
|
||||
if not is_xml_file and not scan_cs: continue
|
||||
|
||||
content = read_file_content(file_path)
|
||||
if search_term in content:
|
||||
# 找到了引用
|
||||
# 尝试反推该文件的 Symbol ID
|
||||
source_symbol = "Unknown"
|
||||
if is_xml_file:
|
||||
# 尝试找 defName
|
||||
m = re.search(r'<defName>(.*?)</defName>', content)
|
||||
if m: source_symbol = f"xml:{m.group(1)}"
|
||||
else:
|
||||
# C# 文件名即 Symbol
|
||||
fname = os.path.basename(file_path)
|
||||
source_symbol = os.path.splitext(fname)[0]
|
||||
|
||||
if source_symbol != "Unknown" and source_symbol != symbol:
|
||||
results.append({
|
||||
"source": source_symbol,
|
||||
"kind": "References" if not is_xml_file else "XmlReferences",
|
||||
"distance": 1
|
||||
})
|
||||
count += 1
|
||||
if count >= 50: break # 限制数量
|
||||
|
||||
return results
|
||||
|
||||
# 5. --- MCP Server 定义 ---
|
||||
|
||||
mcp = FastMCP(name="rimworld-code-rag")
|
||||
|
||||
@mcp.tool()
|
||||
def rough_search(query: str, kind: str = None, max_results: int = 20) -> Dict[str, Any]:
|
||||
"""
|
||||
粗略搜索工具:使用自然语言查询 RimWorld 代码符号和 XML 定义。
|
||||
|
||||
Args:
|
||||
query: 搜索关键词,如 "weapon gun" 或 "pawn health"
|
||||
kind: 过滤类型,可选 "csharp" (或 "cs") 或 "xml" (或 "def")
|
||||
max_results: 最大返回结果数
|
||||
"""
|
||||
logging.info(f"rough_search: {query} (kind={kind})")
|
||||
searcher = RoughSearcher({"kind": kind, "max_results": max_results})
|
||||
results = searcher.search(query)
|
||||
|
||||
return {
|
||||
"results": results,
|
||||
"totalFound": len(results)
|
||||
}
|
||||
|
||||
@mcp.tool()
|
||||
def get_item(symbol: str, max_lines: int = 0) -> Dict[str, Any]:
|
||||
"""
|
||||
精确检索工具:获取特定符号的完整源代码或 Definition。
|
||||
|
||||
Args:
|
||||
symbol: 符号ID,例如 "RimWorld.Pawn" 或 "xml:Gun_Revolver"
|
||||
max_lines: 最大返回行数,0 表示不限制
|
||||
"""
|
||||
logging.info(f"get_item: {symbol}")
|
||||
global_index.initialize()
|
||||
|
||||
if symbol not in global_index.symbol_map:
|
||||
return {"error": f"未找到符号: {symbol},请先使用 rough_search 确认名称。"}
|
||||
|
||||
file_path = global_index.symbol_map[symbol]
|
||||
|
||||
if symbol.startswith("xml:"):
|
||||
source_code = extract_xml_fragment(file_path, symbol.replace("xml:", ""))
|
||||
else:
|
||||
source_code = extract_csharp_fragment(file_path, symbol)
|
||||
|
||||
# 行数限制
|
||||
lines = source_code.split('\n')
|
||||
if max_lines > 0 and len(lines) > max_lines:
|
||||
source_code = "\n".join(lines[:max_lines]) + f"\n... (剩余 {len(lines)-max_lines} 行已截断)"
|
||||
|
||||
return {
|
||||
"symbolId": symbol,
|
||||
"path": file_path,
|
||||
"language": "xml" if symbol.startswith("xml:") else "csharp",
|
||||
"sourceCode": source_code,
|
||||
"totalLines": len(lines)
|
||||
}
|
||||
|
||||
@mcp.tool()
|
||||
def get_uses(symbol: str, kind: str = "all", max_results: int = 50) -> Dict[str, Any]:
|
||||
"""
|
||||
依赖分析:查找该符号引用了什么(下游依赖)。
|
||||
|
||||
Args:
|
||||
symbol: 符号ID
|
||||
kind: 过滤目标类型 ("csharp", "xml", "all")
|
||||
"""
|
||||
logging.info(f"get_uses: {symbol}")
|
||||
querier = GraphQuerier()
|
||||
edges = querier.query_uses(symbol, kind)
|
||||
|
||||
return {
|
||||
"sourceSymbol": symbol,
|
||||
"edges": edges[:max_results],
|
||||
"total": len(edges)
|
||||
}
|
||||
|
||||
@mcp.tool()
|
||||
def get_used_by(symbol: str, kind: str = "all", max_results: int = 20) -> Dict[str, Any]:
|
||||
"""
|
||||
反向依赖分析:查找谁使用了该符号(上游依赖)。
|
||||
注意:由于没有预计算索引,此操作涉及文件扫描,可能较慢。
|
||||
|
||||
Args:
|
||||
symbol: 符号ID
|
||||
kind: 过滤源类型 ("csharp", "xml", "all")
|
||||
"""
|
||||
logging.info(f"get_used_by: {symbol}")
|
||||
querier = GraphQuerier()
|
||||
edges = querier.query_used_by(symbol, kind)
|
||||
|
||||
return {
|
||||
"targetSymbol": symbol,
|
||||
"edges": edges[:max_results],
|
||||
"total": len(edges)
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.info("RimWorld Code RAG Python Server Starting...")
|
||||
# 预热索引
|
||||
global_index.initialize()
|
||||
mcp.run()
|
||||
@@ -1,271 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import re
|
||||
from http import HTTPStatus
|
||||
import dashscope
|
||||
|
||||
# 1. --- 导入MCP SDK ---
|
||||
MCP_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
SDK_PATH = os.path.join(MCP_DIR, 'python-sdk', 'src')
|
||||
if SDK_PATH not in sys.path:
|
||||
sys.path.insert(0, SDK_PATH)
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# 2. --- 日志和环境配置 ---
|
||||
LOG_FILE_PATH = os.path.join(MCP_DIR, 'mcpserver_hybrid.log')
|
||||
logging.basicConfig(filename=LOG_FILE_PATH, level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
encoding='utf-8')
|
||||
|
||||
# 加载 .env 文件 (API_KEY 和 APP_ID)
|
||||
env_path = os.path.join(MCP_DIR, '.env')
|
||||
load_dotenv(dotenv_path=env_path)
|
||||
|
||||
DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY")
|
||||
DASHSCOPE_APP_ID = os.getenv("DASHSCOPE_APP_ID")
|
||||
|
||||
if not DASHSCOPE_API_KEY or not DASHSCOPE_APP_ID:
|
||||
error_msg = "错误:请确保 MCP/.env 文件中已正确配置 DASHSCOPE_API_KEY 和 DASHSCOPE_APP_ID。"
|
||||
logging.error(error_msg)
|
||||
sys.exit(error_msg)
|
||||
|
||||
# 定义本地知识库路径
|
||||
KNOWLEDGE_BASE_PATHS = [
|
||||
r"C:\Steam\steamapps\common\RimWorld\Data"
|
||||
]
|
||||
|
||||
# 3. --- 本地代码搜索与提取函数 (从旧版移植) ---
|
||||
|
||||
def find_files_with_keyword(base_paths: list[str], keywords: list[str]) -> list[str]:
|
||||
"""在基础路径中递归搜索包含任意一个关键词的文件。"""
|
||||
found_files = set()
|
||||
# 完全匹配,区分大小写
|
||||
for base_path in base_paths:
|
||||
for root, _, files in os.walk(base_path):
|
||||
for file in files:
|
||||
if any(keyword in file for keyword in keywords):
|
||||
found_files.add(os.path.join(root, file))
|
||||
logging.info(f"通过文件名关键词找到 {len(found_files)} 个文件: {found_files}")
|
||||
return list(found_files)
|
||||
|
||||
def extract_csharp_class(lines, start_index):
|
||||
"""从C#代码行中提取完整的类定义。"""
|
||||
class_start_index = -1
|
||||
brace_level_at_class_start = -1
|
||||
# 向上找到 class, struct, or enum 声明
|
||||
for i in range(start_index, -1, -1):
|
||||
line = lines[i]
|
||||
if 'class ' in line or 'struct ' in line or 'enum ' in line:
|
||||
class_start_index = i
|
||||
# 计算声明行的初始大括号层级
|
||||
brace_level_at_class_start = lines[i].count('{')
|
||||
# 如果声明行没有'{', 则从下一行开始计算
|
||||
if '{' not in lines[i]:
|
||||
brace_level_at_class_start = 0
|
||||
# 寻找第一个'{'
|
||||
for j in range(i + 1, len(lines)):
|
||||
if '{' in lines[j]:
|
||||
class_start_index = j
|
||||
brace_level_at_class_start = lines[j].count('{')
|
||||
break
|
||||
break
|
||||
|
||||
if class_start_index == -1: return ""
|
||||
|
||||
brace_count = 0
|
||||
# 从class声明之后的第一行或包含`{`的行开始计数
|
||||
start_line_for_brace_count = class_start_index
|
||||
if '{' in lines[class_start_index]:
|
||||
brace_count = lines[class_start_index].count('{') - lines[class_start_index].count('}')
|
||||
start_line_for_brace_count += 1
|
||||
|
||||
class_end_index = -1
|
||||
for i in range(start_line_for_brace_count, len(lines)):
|
||||
line = lines[i]
|
||||
brace_count += line.count('{')
|
||||
brace_count -= line.count('}')
|
||||
if brace_count <= 0:
|
||||
class_end_index = i
|
||||
break
|
||||
|
||||
if class_end_index != -1:
|
||||
# 实际截取时,从包含 class 声明的那一行开始
|
||||
original_start_index = -1
|
||||
for i in range(start_index, -1, -1):
|
||||
if 'class ' in lines[i] or 'struct ' in lines[i] or 'enum ' in lines[i]:
|
||||
original_start_index = i
|
||||
break
|
||||
if original_start_index != -1:
|
||||
return "\n".join(lines[original_start_index:class_end_index+1])
|
||||
return ""
|
||||
|
||||
def extract_xml_def(lines, start_index):
|
||||
"""从XML行中提取完整的Def块。"""
|
||||
def_start_index = -1
|
||||
def_tag = ""
|
||||
# 向上找到Def的起始标签
|
||||
for i in range(start_index, -1, -1):
|
||||
line = lines[i].strip()
|
||||
match = re.search(r'<([A-Za-z_][A-Za-z0-9_]*Def)\s*.*>', line)
|
||||
if match:
|
||||
def_start_index = i
|
||||
def_tag = match.group(1)
|
||||
break
|
||||
|
||||
if def_start_index == -1: return ""
|
||||
|
||||
# 向下找到匹配的 </DefName>
|
||||
def_end_index = -1
|
||||
for i in range(def_start_index + 1, len(lines)):
|
||||
if f'</{def_tag}>' in lines[i]:
|
||||
def_end_index = i
|
||||
break
|
||||
|
||||
if def_end_index != -1:
|
||||
return "\n".join(lines[def_start_index:def_end_index+1])
|
||||
return ""
|
||||
|
||||
def extract_relevant_code(file_path, keyword):
|
||||
"""从文件中智能提取包含关键词的完整代码块 (C#类 或 XML Def)。"""
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
lines = content.split('\n')
|
||||
|
||||
found_line_index = -1
|
||||
for i, line in enumerate(lines):
|
||||
# 使用更精确的匹配,例如匹配 "class Keyword" 或 "<defName>Keyword</defName>"
|
||||
if re.search(r'\b' + re.escape(keyword) + r'\b', line):
|
||||
found_line_index = i
|
||||
break
|
||||
|
||||
if found_line_index == -1:
|
||||
return ""
|
||||
|
||||
if file_path.endswith(('.cs', '.txt')):
|
||||
return extract_csharp_class(lines, found_line_index)
|
||||
elif file_path.endswith('.xml'):
|
||||
return extract_xml_def(lines, found_line_index)
|
||||
else:
|
||||
return ""
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"提取代码时出错 {file_path}: {e}")
|
||||
return f"# Error reading file: {e}"
|
||||
|
||||
def extract_keywords_from_response(text: str) -> list[str]:
|
||||
"""从LLM的回复中提取所有看起来像C#类或XML Def的关键词。"""
|
||||
# 匹配驼峰命名且以大写字母开头的单词,可能是C#类
|
||||
csharp_pattern = r'\b([A-Z][a-zA-Z0-9_]*)\b'
|
||||
# 匹配XML DefName的通用模式
|
||||
xml_pattern = r'\b([a-zA-Z0-9_]+?Def)\b'
|
||||
|
||||
keywords = set()
|
||||
|
||||
# 先找精确的XML Def
|
||||
for match in re.finditer(xml_pattern, text):
|
||||
keywords.add(match.group(1))
|
||||
|
||||
# 再找可能是C#的类
|
||||
for match in re.finditer(csharp_pattern, text):
|
||||
word = match.group(1)
|
||||
# 过滤掉一些常见非类名词和全大写的缩写
|
||||
if word.upper() != word and len(word) > 2 and not word.endswith('Def'):
|
||||
# 检查是否包含小写字母,以排除全大写的缩写词
|
||||
if any(c.islower() for c in word):
|
||||
keywords.add(word)
|
||||
|
||||
# 从 `// 来自文档X (ClassName 类)` 中提取
|
||||
doc_pattern = r'\(([\w_]+)\s+类\)'
|
||||
for match in re.finditer(doc_pattern, text):
|
||||
keywords.add(match.group(1))
|
||||
|
||||
logging.info(f"从LLM回复中提取的关键词: {list(keywords)}")
|
||||
return list(keywords)
|
||||
|
||||
|
||||
# 4. --- 创建MCP服务器实例 ---
|
||||
mcp = FastMCP(
|
||||
name="rimworld-knowledge-base"
|
||||
)
|
||||
|
||||
# 5. --- 定义核心工具 ---
|
||||
@mcp.tool()
|
||||
def get_context(question: str) -> str:
|
||||
"""
|
||||
接收一个问题,调用云端智能体获取分析,然后用本地文件增强代码的完整性。
|
||||
"""
|
||||
# --- 第一阶段:调用云端智能体 ---
|
||||
logging.info(f"收到问题: {question}")
|
||||
enhanced_prompt = (
|
||||
f"{question}\n\n"
|
||||
f"--- \n"
|
||||
f"请注意:如果回答中包含代码,必须提供完整的类或Def定义,不要省略任何部分。"
|
||||
)
|
||||
|
||||
try:
|
||||
response = dashscope.Application.call(
|
||||
app_id=DASHSCOPE_APP_ID,
|
||||
api_key=DASHSCOPE_API_KEY,
|
||||
prompt=enhanced_prompt
|
||||
)
|
||||
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
error_info = (f'请求失败: request_id={response.request_id}, '
|
||||
f'code={response.status_code}, message={response.message}')
|
||||
logging.error(error_info)
|
||||
return f"Error: 调用AI智能体失败。{error_info}"
|
||||
|
||||
llm_response_text = response.output.text
|
||||
logging.info(f"收到智能体回复: {llm_response_text[:300]}...")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"调用智能体时发生未知异常: {e}", exc_info=True)
|
||||
return f"Error: 调用AI智能体时发生未知异常 - {e}"
|
||||
|
||||
# --- 第二阶段:本地增强 ---
|
||||
logging.info("开始本地增强流程...")
|
||||
keywords = extract_keywords_from_response(llm_response_text)
|
||||
if not keywords:
|
||||
logging.info("未从回复中提取到关键词,直接返回云端结果。")
|
||||
return llm_response_text
|
||||
|
||||
found_code_blocks = []
|
||||
processed_files = set()
|
||||
|
||||
# 优先根据文件名搜索
|
||||
found_files = find_files_with_keyword(KNOWLEDGE_BASE_PATHS, keywords)
|
||||
for file_path in found_files:
|
||||
if file_path in processed_files:
|
||||
continue
|
||||
# 从文件名中找出是哪个关键词匹配的
|
||||
matching_keyword = next((k for k in keywords if k in os.path.basename(file_path)), None)
|
||||
if matching_keyword:
|
||||
logging.info(f"在文件 {file_path} 中为关键词 '{matching_keyword}' 提取代码...")
|
||||
code = extract_relevant_code(file_path, matching_keyword)
|
||||
if code:
|
||||
header = f"\n\n--- 完整代码定义: {matching_keyword} (来自 {os.path.basename(file_path)}) ---\n"
|
||||
found_code_blocks.append(header + code)
|
||||
processed_files.add(file_path)
|
||||
|
||||
# --- 组合最终结果 ---
|
||||
final_response = llm_response_text
|
||||
if found_code_blocks:
|
||||
final_response += "\n\n" + "="*40
|
||||
final_response += "\n本地知识库补充的完整代码定义:\n" + "="*40
|
||||
final_response += "".join(found_code_blocks)
|
||||
|
||||
return final_response
|
||||
|
||||
# 6. --- 启动服务器 ---
|
||||
if __name__ == "__main__":
|
||||
logging.info("启动混合模式MCP服务器...")
|
||||
logging.info(f"将使用 App ID: {DASHSCOPE_APP_ID}")
|
||||
logging.info(f"Python Executable: {sys.executable}")
|
||||
mcp.run()
|
||||
logging.info("混合模式MCP服务器已停止。")
|
||||
@@ -1 +0,0 @@
|
||||
86696
|
||||
@@ -1,105 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
RimWorld知识库命令行工具
|
||||
快速查询工具,无需Qoder IDE
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
|
||||
# 添加MCP路径
|
||||
MCP_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
SDK_PATH = os.path.join(MCP_DIR, 'python-sdk', 'src')
|
||||
if SDK_PATH not in sys.path:
|
||||
sys.path.insert(0, SDK_PATH)
|
||||
|
||||
def quick_query(question: str, format_output: bool = True) -> str:
|
||||
"""快速查询函数"""
|
||||
try:
|
||||
# 动态导入避免启动时的依赖检查
|
||||
from mcpserver_stdio import get_context
|
||||
result = get_context(question)
|
||||
|
||||
if format_output:
|
||||
# 格式化输出
|
||||
lines = result.split('\n')
|
||||
formatted_lines = []
|
||||
current_section = ""
|
||||
|
||||
for line in lines:
|
||||
if line.startswith('--- 结果'):
|
||||
current_section = f"\n🔍 {line}"
|
||||
formatted_lines.append(current_section)
|
||||
elif line.startswith('文件路径:'):
|
||||
formatted_lines.append(f"📄 {line}")
|
||||
elif line.strip() and not line.startswith('---'):
|
||||
formatted_lines.append(line)
|
||||
|
||||
return '\n'.join(formatted_lines)
|
||||
else:
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
return f"❌ 查询失败: {e}"
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='RimWorld知识库命令行查询工具',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
使用示例:
|
||||
%(prog)s "ThingDef是什么"
|
||||
%(prog)s "如何创建新的Pawn" --raw
|
||||
%(prog)s "建筑物定义" --output result.txt
|
||||
%(prog)s --list-examples
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument('question', nargs='?', help='要查询的问题')
|
||||
parser.add_argument('--raw', action='store_true', help='输出原始结果,不格式化')
|
||||
parser.add_argument('--output', '-o', help='将结果保存到文件')
|
||||
parser.add_argument('--list-examples', action='store_true', help='显示查询示例')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.list_examples:
|
||||
print("📚 RimWorld知识库查询示例:")
|
||||
examples = [
|
||||
"ThingDef的定义和用法",
|
||||
"如何创建新的Building",
|
||||
"Pawn类的主要方法",
|
||||
"CompPower的使用方法",
|
||||
"XML中的defName规则",
|
||||
"GenConstruct.CanPlaceBlueprintAt",
|
||||
"Building_Door的开关逻辑"
|
||||
]
|
||||
for i, example in enumerate(examples, 1):
|
||||
print(f" {i}. {example}")
|
||||
return
|
||||
|
||||
if not args.question:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
print(f"🔍 正在查询: {args.question}")
|
||||
|
||||
result = quick_query(args.question, not args.raw)
|
||||
|
||||
if args.output:
|
||||
try:
|
||||
with open(args.output, 'w', encoding='utf-8') as f:
|
||||
f.write(result)
|
||||
print(f"✅ 结果已保存到: {args.output}")
|
||||
except Exception as e:
|
||||
print(f"❌ 保存文件失败: {e}")
|
||||
else:
|
||||
print("\n" + "="*60)
|
||||
print("📖 查询结果:")
|
||||
print("="*60)
|
||||
print(result)
|
||||
print("="*60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,123 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
验证MCP服务器配置和环境
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import json
|
||||
|
||||
def test_mcp_configuration():
|
||||
"""测试MCP配置是否正确"""
|
||||
print("🔍 MCP配置验证工具")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. 检查Python环境
|
||||
print(f"✓ Python解释器: {sys.executable}")
|
||||
print(f"✓ Python版本: {sys.version}")
|
||||
|
||||
# 2. 检查工作目录
|
||||
mcp_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
print(f"✓ MCP目录: {mcp_dir}")
|
||||
|
||||
# 3. 检查MCP SDK
|
||||
sdk_path = os.path.join(mcp_dir, 'python-sdk', 'src')
|
||||
print(f"✓ SDK路径: {sdk_path}")
|
||||
print(f"✓ SDK存在: {os.path.exists(sdk_path)}")
|
||||
|
||||
# 4. 检查必要文件
|
||||
server_script = os.path.join(mcp_dir, 'mcpserver_stdio.py')
|
||||
env_file = os.path.join(mcp_dir, '.env')
|
||||
print(f"✓ 服务器脚本: {os.path.exists(server_script)}")
|
||||
print(f"✓ 环境文件: {os.path.exists(env_file)}")
|
||||
|
||||
# 5. 检查依赖包
|
||||
try:
|
||||
import mcp
|
||||
print("✓ MCP SDK: 已安装")
|
||||
except ImportError as e:
|
||||
print(f"❌ MCP SDK: 未安装 - {e}")
|
||||
|
||||
try:
|
||||
import dashscope
|
||||
print("✓ DashScope: 已安装")
|
||||
except ImportError as e:
|
||||
print(f"❌ DashScope: 未安装 - {e}")
|
||||
|
||||
try:
|
||||
import openai
|
||||
print("✓ OpenAI: 已安装")
|
||||
except ImportError as e:
|
||||
print(f"❌ OpenAI: 未安装 - {e}")
|
||||
|
||||
# 6. 生成正确的配置
|
||||
python_exe = sys.executable.replace("\\", "\\\\")
|
||||
mcp_dir_escaped = mcp_dir.replace("\\", "\\\\")
|
||||
sdk_path_escaped = sdk_path.replace("\\", "\\\\")
|
||||
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"rimworld-knowledge-base": {
|
||||
"command": python_exe,
|
||||
"args": ["mcpserver_stdio.py"],
|
||||
"cwd": mcp_dir_escaped,
|
||||
"disabled": False,
|
||||
"alwaysAllow": [],
|
||||
"env": {
|
||||
"PYTHONPATH": sdk_path_escaped
|
||||
}
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"rimworld-knowledge-base": {
|
||||
"description": "从RimWorld本地知识库(包括C#源码和XML)中检索上下文。",
|
||||
"server_name": "rimworld-knowledge-base",
|
||||
"tool_name": "get_context",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"question": {
|
||||
"type": "string",
|
||||
"description": "关于RimWorld开发的问题,应包含代码或XML中的关键词。"
|
||||
}
|
||||
},
|
||||
"required": ["question"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print("\\n📋 建议的MCP配置:")
|
||||
print("=" * 50)
|
||||
print(json.dumps(config, indent=2, ensure_ascii=False))
|
||||
|
||||
# 7. 测试服务器启动
|
||||
print("\\n🚀 测试服务器启动:")
|
||||
print("=" * 50)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[sys.executable, server_script],
|
||||
cwd=mcp_dir,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
if result.returncode == 0:
|
||||
print("✓ 服务器可以正常启动")
|
||||
else:
|
||||
print(f"❌ 服务器启动失败: {result.stderr}")
|
||||
except subprocess.TimeoutExpired:
|
||||
print("✓ 服务器启动正常(超时保护触发)")
|
||||
except Exception as e:
|
||||
print(f"❌ 服务器测试失败: {e}")
|
||||
|
||||
print("\\n🎯 配置建议:")
|
||||
print("=" * 50)
|
||||
print("1. 复制上面的配置到 Qoder IDE 的 MCP 设置中")
|
||||
print("2. 确保所有依赖包都已安装")
|
||||
print("3. 检查 .env 文件中的 API Key 配置")
|
||||
print("4. 重启 Qoder IDE 并重新连接 MCP 服务器")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_mcp_configuration()
|
||||
149
MCP/test_mcp.py
149
MCP/test_mcp.py
@@ -1,149 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
最终功能测试:验证MCP服务器是否能正常工作
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import time
|
||||
import json
|
||||
|
||||
def test_mcp_server_final():
|
||||
"""最终测试MCP服务器功能"""
|
||||
print("🔥 MCP服务器最终功能测试")
|
||||
print("=" * 50)
|
||||
|
||||
# 获取当前目录
|
||||
mcp_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
script_path = os.path.join(mcp_dir, 'mcpserver_stdio.py')
|
||||
|
||||
try:
|
||||
# 1. 验证SDK安装
|
||||
try:
|
||||
import mcp
|
||||
print("✅ MCP SDK: 已正确安装")
|
||||
except ImportError:
|
||||
print("❌ MCP SDK: 未安装")
|
||||
return False
|
||||
|
||||
# 2. 启动服务器
|
||||
print("🚀 启动MCP服务器...")
|
||||
process = subprocess.Popen(
|
||||
[sys.executable, script_path],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
cwd=mcp_dir
|
||||
)
|
||||
|
||||
# 等待启动
|
||||
time.sleep(2)
|
||||
|
||||
# 3. 初始化测试
|
||||
init_request = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "initialize",
|
||||
"params": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {},
|
||||
"clientInfo": {
|
||||
"name": "final-test-client",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print("📡 发送初始化请求...")
|
||||
process.stdin.write(json.dumps(init_request) + '\n')
|
||||
process.stdin.flush()
|
||||
|
||||
# 读取初始化响应
|
||||
response = process.stdout.readline()
|
||||
if response:
|
||||
response_data = json.loads(response.strip())
|
||||
if "result" in response_data:
|
||||
print("✅ 初始化成功")
|
||||
print(f" 服务器名称: {response_data['result'].get('serverInfo', {}).get('name', 'unknown')}")
|
||||
print(f" 服务器版本: {response_data['result'].get('serverInfo', {}).get('version', 'unknown')}")
|
||||
else:
|
||||
print("❌ 初始化失败")
|
||||
return False
|
||||
else:
|
||||
print("❌ 初始化无响应")
|
||||
return False
|
||||
|
||||
# 4. 工具列表测试
|
||||
tools_request = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "tools/list"
|
||||
}
|
||||
|
||||
print("🔧 请求工具列表...")
|
||||
process.stdin.write(json.dumps(tools_request) + '\n')
|
||||
process.stdin.flush()
|
||||
|
||||
tools_response = process.stdout.readline()
|
||||
if tools_response:
|
||||
tools_data = json.loads(tools_response.strip())
|
||||
if "result" in tools_data and "tools" in tools_data["result"]:
|
||||
tools = tools_data["result"]["tools"]
|
||||
print(f"✅ 发现 {len(tools)} 个工具:")
|
||||
for tool in tools:
|
||||
print(f" - {tool.get('name', 'unknown')}: {tool.get('description', 'no description')}")
|
||||
else:
|
||||
print("❌ 获取工具列表失败")
|
||||
else:
|
||||
print("❌ 工具列表请求无响应")
|
||||
|
||||
print("\n🎯 测试结果:")
|
||||
print("✅ MCP服务器能够正常启动")
|
||||
print("✅ 初始化协议工作正常")
|
||||
print("✅ 工具发现机制正常")
|
||||
print("\n✨ 所有基本功能测试通过!")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 测试过程中出错: {e}")
|
||||
return False
|
||||
|
||||
finally:
|
||||
# 清理进程
|
||||
try:
|
||||
process.terminate()
|
||||
process.wait(timeout=5)
|
||||
except:
|
||||
try:
|
||||
process.kill()
|
||||
except:
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("开始最终测试...")
|
||||
success = test_mcp_server_final()
|
||||
|
||||
if success:
|
||||
print("\n🎉 恭喜!MCP服务器已完全修复并正常工作!")
|
||||
print("\n📋 现在您需要在Qoder IDE中更新配置:")
|
||||
print("1. 打开Qoder IDE设置 → MCP")
|
||||
print("2. 更新配置文件,确保使用正确的绝对路径")
|
||||
print("3. 重启Qoder IDE")
|
||||
print("4. 在Agent模式下测试知识库查询")
|
||||
print("\n建议的配置:")
|
||||
print(json.dumps({
|
||||
"mcpServers": {
|
||||
"rimworld-knowledge-base": {
|
||||
"command": sys.executable,
|
||||
"args": ["mcpserver_stdio.py"],
|
||||
"cwd": os.path.dirname(os.path.abspath(__file__)),
|
||||
"disabled": False,
|
||||
"alwaysAllow": []
|
||||
}
|
||||
}
|
||||
}, indent=2))
|
||||
else:
|
||||
print("\n❌ 仍存在问题,需要进一步调试")
|
||||
@@ -1,150 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
测试MCP服务器超时修复
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import time
|
||||
import json
|
||||
|
||||
def test_mcp_server_timeout_fix():
|
||||
"""测试MCP服务器是否能快速启动并响应"""
|
||||
print("开始测试MCP服务器超时修复...")
|
||||
|
||||
# 获取当前目录
|
||||
mcp_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
script_path = os.path.join(mcp_dir, 'mcpserver_stdio.py')
|
||||
|
||||
try:
|
||||
# 启动MCP服务器进程
|
||||
print("启动MCP服务器...")
|
||||
start_time = time.time()
|
||||
|
||||
process = subprocess.Popen(
|
||||
[sys.executable, script_path],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
cwd=mcp_dir
|
||||
)
|
||||
|
||||
# 等待服务器启动(减少等待时间)
|
||||
time.sleep(2) # 从3秒减少到2秒
|
||||
|
||||
startup_time = time.time() - start_time
|
||||
print(f"服务器启动耗时: {startup_time:.2f}秒")
|
||||
|
||||
# 发送初始化请求
|
||||
init_request = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "initialize",
|
||||
"params": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {},
|
||||
"clientInfo": {
|
||||
"name": "test-client",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print("发送初始化请求...")
|
||||
request_start = time.time()
|
||||
process.stdin.write(json.dumps(init_request) + '\n')
|
||||
process.stdin.flush()
|
||||
|
||||
# 读取响应
|
||||
response_line = process.stdout.readline()
|
||||
init_time = time.time() - request_start
|
||||
|
||||
if response_line:
|
||||
print(f"✅ 初始化成功,耗时: {init_time:.2f}秒")
|
||||
print(f"收到响应: {response_line.strip()}")
|
||||
else:
|
||||
print("❌ 初始化失败:无响应")
|
||||
return False
|
||||
|
||||
# 发送简单的工具调用请求
|
||||
tool_request = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "get_context",
|
||||
"arguments": {
|
||||
"question": "ThingDef" # 简单的测试查询
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print("发送工具调用请求...")
|
||||
tool_start = time.time()
|
||||
process.stdin.write(json.dumps(tool_request) + '\n')
|
||||
process.stdin.flush()
|
||||
|
||||
# 等待响应(减少超时时间)
|
||||
timeout = 20 # 从30秒减少到20秒
|
||||
response_received = False
|
||||
|
||||
while time.time() - tool_start < timeout:
|
||||
if process.poll() is not None:
|
||||
print("服务器进程已退出")
|
||||
break
|
||||
|
||||
response_line = process.stdout.readline()
|
||||
if response_line:
|
||||
tool_time = time.time() - tool_start
|
||||
print(f"✅ 工具调用成功,耗时: {tool_time:.2f}秒")
|
||||
print(f"工具调用响应: {response_line.strip()[:200]}...") # 只显示前200个字符
|
||||
response_received = True
|
||||
break
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
total_time = time.time() - start_time
|
||||
|
||||
if response_received:
|
||||
print(f"✅ 测试成功:MCP服务器能够正常处理请求")
|
||||
print(f"总耗时: {total_time:.2f}秒")
|
||||
|
||||
# 性能评估
|
||||
if total_time < 15:
|
||||
print("🚀 性能优秀:服务器响应速度很快")
|
||||
elif total_time < 25:
|
||||
print("✅ 性能良好:服务器响应速度可接受")
|
||||
else:
|
||||
print("⚠️ 性能一般:服务器响应较慢,可能仍有超时风险")
|
||||
|
||||
else:
|
||||
print("❌ 测试失败:超时未收到响应")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 测试出错: {e}")
|
||||
return False
|
||||
|
||||
finally:
|
||||
# 清理进程
|
||||
try:
|
||||
process.terminate()
|
||||
process.wait(timeout=5)
|
||||
except:
|
||||
try:
|
||||
process.kill()
|
||||
except:
|
||||
pass
|
||||
print("测试完成")
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = test_mcp_server_timeout_fix()
|
||||
if success:
|
||||
print("\n🎉 MCP服务器超时问题已修复!")
|
||||
print("现在可以在Qoder IDE中重新连接MCP服务器了。")
|
||||
else:
|
||||
print("\n❌ MCP服务器仍存在问题,需要进一步调试。")
|
||||
@@ -1,249 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
RimWorld知识库Web API服务器
|
||||
提供HTTP接口,可以通过浏览器或HTTP客户端访问
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
import threading
|
||||
import webbrowser
|
||||
|
||||
# 添加MCP路径
|
||||
MCP_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
SDK_PATH = os.path.join(MCP_DIR, 'python-sdk', 'src')
|
||||
if SDK_PATH not in sys.path:
|
||||
sys.path.insert(0, SDK_PATH)
|
||||
|
||||
class RimWorldAPIHandler(BaseHTTPRequestHandler):
|
||||
"""HTTP请求处理器"""
|
||||
|
||||
def do_GET(self):
|
||||
"""处理GET请求"""
|
||||
parsed_url = urlparse(self.path)
|
||||
|
||||
if parsed_url.path == '/':
|
||||
self.serve_web_interface()
|
||||
elif parsed_url.path == '/query':
|
||||
self.handle_query_get(parsed_url)
|
||||
elif parsed_url.path == '/api/query':
|
||||
self.handle_api_query_get(parsed_url)
|
||||
else:
|
||||
self.send_error(404, "Not Found")
|
||||
|
||||
def do_POST(self):
|
||||
"""处理POST请求"""
|
||||
if self.path == '/api/query':
|
||||
self.handle_api_query_post()
|
||||
else:
|
||||
self.send_error(404, "Not Found")
|
||||
|
||||
def serve_web_interface(self):
|
||||
"""提供Web界面"""
|
||||
html = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>RimWorld 知识库</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
|
||||
.header { text-align: center; margin-bottom: 30px; }
|
||||
.query-box { margin-bottom: 20px; }
|
||||
input[type="text"] { width: 70%; padding: 10px; font-size: 16px; }
|
||||
button { padding: 10px 20px; font-size: 16px; background: #007cba; color: white; border: none; cursor: pointer; }
|
||||
button:hover { background: #005a8b; }
|
||||
.result { margin-top: 20px; padding: 15px; background: #f5f5f5; border-radius: 5px; white-space: pre-wrap; }
|
||||
.loading { color: #666; font-style: italic; }
|
||||
.examples { margin-top: 20px; }
|
||||
.example { cursor: pointer; color: #007cba; margin: 5px 0; }
|
||||
.example:hover { text-decoration: underline; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>🎮 RimWorld 知识库</h1>
|
||||
<p>直接查询RimWorld游戏的C#源码和XML定义</p>
|
||||
</div>
|
||||
|
||||
<div class="query-box">
|
||||
<input type="text" id="queryInput" placeholder="输入您的问题,例如:ThingDef是什么?" onkeypress="handleKeyPress(event)">
|
||||
<button onclick="performQuery()">🔍 查询</button>
|
||||
</div>
|
||||
|
||||
<div class="examples">
|
||||
<h3>💡 查询示例:</h3>
|
||||
<div class="example" onclick="setQuery('ThingDef的定义和用法')">• ThingDef的定义和用法</div>
|
||||
<div class="example" onclick="setQuery('如何创建Building')">• 如何创建Building</div>
|
||||
<div class="example" onclick="setQuery('Pawn类的主要方法')">• Pawn类的主要方法</div>
|
||||
<div class="example" onclick="setQuery('CompPower的使用')">• CompPower的使用</div>
|
||||
</div>
|
||||
|
||||
<div id="result" class="result" style="display: none;"></div>
|
||||
|
||||
<script>
|
||||
async function performQuery() {
|
||||
const input = document.getElementById('queryInput');
|
||||
const result = document.getElementById('result');
|
||||
const query = input.value.trim();
|
||||
|
||||
if (!query) {
|
||||
alert('请输入查询问题');
|
||||
return;
|
||||
}
|
||||
|
||||
result.style.display = 'block';
|
||||
result.textContent = '🔄 正在查询,请稍候...';
|
||||
result.className = 'result loading';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/query', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({question: query})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
result.textContent = data.result;
|
||||
result.className = 'result';
|
||||
} else {
|
||||
result.textContent = '❌ 查询失败: ' + data.error;
|
||||
result.className = 'result';
|
||||
}
|
||||
} catch (error) {
|
||||
result.textContent = '❌ 网络错误: ' + error.message;
|
||||
result.className = 'result';
|
||||
}
|
||||
}
|
||||
|
||||
function setQuery(query) {
|
||||
document.getElementById('queryInput').value = query;
|
||||
}
|
||||
|
||||
function handleKeyPress(event) {
|
||||
if (event.key === 'Enter') {
|
||||
performQuery();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'text/html; charset=utf-8')
|
||||
self.end_headers()
|
||||
self.wfile.write(html.encode('utf-8'))
|
||||
|
||||
def handle_query_get(self, parsed_url):
|
||||
"""处理GET查询请求"""
|
||||
params = parse_qs(parsed_url.query)
|
||||
question = params.get('q', [''])[0]
|
||||
|
||||
if not question:
|
||||
self.send_error(400, "Missing 'q' parameter")
|
||||
return
|
||||
|
||||
try:
|
||||
from mcpserver_stdio import get_context
|
||||
result = get_context(question)
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'text/plain; charset=utf-8')
|
||||
self.end_headers()
|
||||
self.wfile.write(result.encode('utf-8'))
|
||||
except Exception as e:
|
||||
self.send_error(500, f"Query failed: {e}")
|
||||
|
||||
def handle_api_query_get(self, parsed_url):
|
||||
"""处理API GET查询"""
|
||||
params = parse_qs(parsed_url.query)
|
||||
question = params.get('q', [''])[0]
|
||||
|
||||
if not question:
|
||||
response = {"success": False, "error": "Missing 'q' parameter"}
|
||||
else:
|
||||
try:
|
||||
from mcpserver_stdio import get_context
|
||||
result = get_context(question)
|
||||
response = {"success": True, "result": result}
|
||||
except Exception as e:
|
||||
response = {"success": False, "error": str(e)}
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(response, ensure_ascii=False).encode('utf-8'))
|
||||
|
||||
def handle_api_query_post(self):
|
||||
"""处理API POST查询"""
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
post_data = self.rfile.read(content_length)
|
||||
|
||||
try:
|
||||
data = json.loads(post_data.decode('utf-8'))
|
||||
question = data.get('question', '')
|
||||
|
||||
if not question:
|
||||
response = {"success": False, "error": "Missing 'question' field"}
|
||||
else:
|
||||
from mcpserver_stdio import get_context
|
||||
result = get_context(question)
|
||||
response = {"success": True, "result": result}
|
||||
except Exception as e:
|
||||
response = {"success": False, "error": str(e)}
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(response, ensure_ascii=False).encode('utf-8'))
|
||||
|
||||
def log_message(self, format, *args):
|
||||
"""自定义日志输出"""
|
||||
print(f"[{self.address_string()}] {format % args}")
|
||||
|
||||
def start_server(port=8080, open_browser=True):
|
||||
"""启动Web服务器"""
|
||||
server_address = ('', port)
|
||||
httpd = HTTPServer(server_address, RimWorldAPIHandler)
|
||||
|
||||
print(f"🌐 RimWorld知识库Web服务器启动")
|
||||
print(f"📍 服务地址: http://localhost:{port}")
|
||||
print(f"🔍 查询API: http://localhost:{port}/api/query?q=您的问题")
|
||||
print(f"💻 Web界面: http://localhost:{port}")
|
||||
print("按 Ctrl+C 停止服务器")
|
||||
|
||||
if open_browser:
|
||||
# 延迟打开浏览器
|
||||
def open_browser_delayed():
|
||||
import time
|
||||
time.sleep(1)
|
||||
webbrowser.open(f'http://localhost:{port}')
|
||||
|
||||
thread = threading.Thread(target=open_browser_delayed)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\n🛑 服务器已停止")
|
||||
httpd.shutdown()
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='RimWorld知识库Web API服务器')
|
||||
parser.add_argument('--port', '-p', type=int, default=8080, help='服务器端口 (默认: 8080)')
|
||||
parser.add_argument('--no-browser', action='store_true', help='不自动打开浏览器')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
start_server(args.port, not args.no_browser)
|
||||
102
MCP/使用指南.md
102
MCP/使用指南.md
@@ -1,102 +0,0 @@
|
||||
# RimWorld 知识库 - 绕过 Qoder IDE 使用指南
|
||||
|
||||
由于 Qoder IDE 中的 MCP 连接可能存在问题,我们提供了多种直接访问 RimWorld 知识库的方法。
|
||||
|
||||
## 🚀 方法 1:直接 Python 调用
|
||||
|
||||
最简单直接的方法:
|
||||
|
||||
```bash
|
||||
# 直接查询
|
||||
python direct_mcp_client.py -q "ThingDef是什么"
|
||||
|
||||
# 交互模式
|
||||
python direct_mcp_client.py -i
|
||||
|
||||
# 查看帮助
|
||||
python direct_mcp_client.py -h
|
||||
```
|
||||
|
||||
### 优点:
|
||||
- ✅ 最快速,无需额外依赖
|
||||
- ✅ 支持交互模式
|
||||
- ✅ 直接在命令行使用
|
||||
|
||||
## 🛠️ 方法 2:命令行工具
|
||||
|
||||
专业的命令行查询工具:
|
||||
|
||||
```bash
|
||||
# 基本查询
|
||||
python rimworld_query.py "ThingDef的定义"
|
||||
|
||||
# 保存结果到文件
|
||||
python rimworld_query.py "Building类的方法" --output building_info.txt
|
||||
|
||||
# 显示原始结果(不格式化)
|
||||
python rimworld_query.py "Pawn类" --raw
|
||||
|
||||
# 查看示例
|
||||
python rimworld_query.py --list-examples
|
||||
```
|
||||
|
||||
### 优点:
|
||||
- ✅ 结果可保存到文件
|
||||
- ✅ 支持原始输出格式
|
||||
- ✅ 内置查询示例
|
||||
|
||||
## 📝 常用查询示例
|
||||
|
||||
```bash
|
||||
# 查询类定义
|
||||
"ThingDef的定义和用法"
|
||||
"Building类有哪些方法"
|
||||
"Pawn类的构造函数"
|
||||
|
||||
# 查询特定方法
|
||||
"GenConstruct.CanPlaceBlueprintAt 方法"
|
||||
"Building_Door 的开关逻辑"
|
||||
"CompPower 的电力管理"
|
||||
|
||||
# 查询XML相关
|
||||
"XML中的defName规则"
|
||||
"如何定义新的ThingDef"
|
||||
"建筑物的XML结构"
|
||||
```
|
||||
|
||||
## 🔧 故障排除
|
||||
|
||||
### 如果出现导入错误:
|
||||
```bash
|
||||
# 确保在正确的目录
|
||||
cd "C:\Steam\steamapps\common\RimWorld\Mods\3516260226\MCP"
|
||||
|
||||
# 检查 Python 环境
|
||||
python -c "import mcp; print('MCP SDK 正常')"
|
||||
```
|
||||
|
||||
### 如果查询结果为空:
|
||||
- 尝试使用更具体的关键词
|
||||
- 检查关键词拼写
|
||||
- 使用英文类名或方法名
|
||||
|
||||
### 如果 Web 服务器无法启动:
|
||||
- 检查端口是否被占用
|
||||
- 尝试使用不同的端口号
|
||||
- 确保没有其他程序占用该端口
|
||||
|
||||
## 💡 推荐使用场景
|
||||
|
||||
- **快速查询**: 使用方法 1 (direct_mcp_client.py)
|
||||
- **批量处理**: 使用方法 2 (rimworld_query.py)
|
||||
- **团队共享**: 使用方法 3 (web_api_server.py)
|
||||
- **集成开发**: 使用 Web API 接口
|
||||
|
||||
## 🎯 性能优化
|
||||
|
||||
所有方法都已经过优化:
|
||||
- 向量化处理限制在 10 个文件以内
|
||||
- API 调用超时设置为 12-15 秒
|
||||
- 支持本地缓存加速重复查询
|
||||
|
||||
现在您可以完全绕过 Qoder IDE,直接使用 RimWorld 知识库了!
|
||||
207
MCP/使用转发服务指南.md
207
MCP/使用转发服务指南.md
@@ -1,207 +0,0 @@
|
||||
# OpenAI兼容接口到阿里云百炼平台智能体应用转发服务使用指南
|
||||
|
||||
本文档介绍了如何使用Python编写的转发服务,将OpenAI兼容的API请求转换为阿里云百炼平台智能体应用的请求。
|
||||
|
||||
## 工作原理
|
||||
|
||||
该转发服务作为一个代理服务器运行,接收OpenAI兼容的API请求,并将其转换为阿里云百炼平台智能体应用的请求格式,然后将响应转换回OpenAI格式返回给客户端。
|
||||
|
||||
```
|
||||
客户端 -> OpenAI兼容请求 -> 转发服务 -> 阿里云百炼平台 -> 转发服务 -> OpenAI兼容响应 -> 客户端
|
||||
```
|
||||
|
||||
## 配置要求
|
||||
|
||||
### 环境变量设置
|
||||
|
||||
需要设置以下环境变量:
|
||||
|
||||
1. `DASHSCOPE_API_KEY` - 阿里云百炼平台的API Key
|
||||
2. `DASHSCOPE_APP_ID` - 智能体应用的APP ID
|
||||
3. `PROXY_PORT` (可选) - 转发服务监听的端口,默认为8000
|
||||
|
||||
您可以在项目目录下的 [.env](file://c:\Steam\steamapps\common\RimWorld\Mods\3516260226\MCP\.env) 文件中配置这些变量:
|
||||
|
||||
```env
|
||||
DASHSCOPE_API_KEY="sk-xxxxxxxx"
|
||||
DASHSCOPE_APP_ID="app-xxxxxxxx"
|
||||
PROXY_PORT=8000
|
||||
```
|
||||
|
||||
在Windows系统中设置环境变量的示例:
|
||||
```cmd
|
||||
set DASHSCOPE_API_KEY=your_api_key_here
|
||||
set DASHSCOPE_APP_ID=your_app_id_here
|
||||
set PROXY_PORT=8000
|
||||
```
|
||||
|
||||
在Linux/macOS系统中设置环境变量的示例:
|
||||
```bash
|
||||
export DASHSCOPE_API_KEY=your_api_key_here
|
||||
export DASHSCOPE_APP_ID=your_app_id_here
|
||||
export PROXY_PORT=8000
|
||||
```
|
||||
|
||||
## 启动服务
|
||||
|
||||
运行转发服务:
|
||||
|
||||
```bash
|
||||
python openai_to_dashscope_proxy.py
|
||||
```
|
||||
|
||||
服务启动后,将显示以下信息:
|
||||
```
|
||||
OpenAI到阿里云百炼平台转发服务启动,监听端口 8000
|
||||
Base URL: http://localhost:8000/v1
|
||||
模型名称: dashscope-app
|
||||
```
|
||||
|
||||
## 在Kilo Code中配置
|
||||
|
||||
在Kilo Code中按以下步骤配置:
|
||||
|
||||
1. 打开Kilo Code设置面板
|
||||
2. 选择"API Provider"为"OpenAI Compatible"
|
||||
3. 设置Base URL为: `http://localhost:8000/v1` (如果使用默认端口)
|
||||
4. API Key可以任意填写(服务不会验证)
|
||||
5. 模型名称填写: `dashscope-app`
|
||||
|
||||
## 支持的功能
|
||||
|
||||
### 1. 基本文本对话
|
||||
|
||||
发送聊天完成请求:
|
||||
```json
|
||||
{
|
||||
"model": "dashscope-app",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "你好,你是谁?"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 流式输出
|
||||
|
||||
启用流式输出:
|
||||
```json
|
||||
{
|
||||
"model": "dashscope-app",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "讲一个有趣的故事"
|
||||
}
|
||||
],
|
||||
"stream": true
|
||||
}
|
||||
```
|
||||
|
||||
## 技术细节
|
||||
|
||||
### 请求转换
|
||||
|
||||
OpenAI兼容请求格式:
|
||||
```json
|
||||
{
|
||||
"model": "dashscope-app",
|
||||
"messages": [
|
||||
{"role": "user", "content": "提示词"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
转换为阿里云百炼平台请求格式:
|
||||
```json
|
||||
{
|
||||
"input": {
|
||||
"prompt": "提示词"
|
||||
},
|
||||
"parameters": {},
|
||||
"debug": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 响应转换
|
||||
|
||||
阿里云百炼平台响应格式:
|
||||
```json
|
||||
{
|
||||
"output": {
|
||||
"text": "响应内容",
|
||||
"finish_reason": "stop"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
转换为OpenAI兼容响应格式:
|
||||
```json
|
||||
{
|
||||
"id": "chatcmpl-request_id",
|
||||
"object": "chat.completion",
|
||||
"created": 1234567890,
|
||||
"model": "dashscope-app",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "响应内容"
|
||||
},
|
||||
"finish_reason": "stop"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 1. 环境变量未设置
|
||||
|
||||
错误信息:
|
||||
```
|
||||
ValueError: 请设置环境变量 DASHSCOPE_API_KEY
|
||||
```
|
||||
|
||||
解决方案:
|
||||
确保已正确设置环境变量 `DASHSCOPE_API_KEY` 和 `DASHSCOPE_APP_ID`。
|
||||
|
||||
### 2. 网络连接问题
|
||||
|
||||
错误信息:
|
||||
```
|
||||
requests.exceptions.ConnectionError
|
||||
```
|
||||
|
||||
解决方案:
|
||||
检查网络连接,确保可以访问阿里云百炼平台。
|
||||
|
||||
### 3. API Key或APP ID错误
|
||||
|
||||
错误信息:
|
||||
```
|
||||
401 Unauthorized
|
||||
```
|
||||
|
||||
解决方案:
|
||||
检查API Key和APP ID是否正确。
|
||||
|
||||
### 4. 端口被占用
|
||||
|
||||
错误信息:
|
||||
```
|
||||
OSError: [Errno 98] Address already in use
|
||||
```
|
||||
|
||||
解决方案:
|
||||
更改端口号,通过设置 `PROXY_PORT` 环境变量或修改代码中的默认端口。
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **安全性**:在生产环境中,建议将转发服务部署在安全的网络环境中
|
||||
2. **监控**:启用日志记录以便监控服务运行状态
|
||||
3. **错误处理**:确保正确处理各种错误情况
|
||||
4. **性能**:对于高并发场景,考虑使用异步框架如FastAPI替代内置的HTTP服务器
|
||||
@@ -13,20 +13,7 @@
|
||||
"path": "../../../../Data"
|
||||
},
|
||||
{
|
||||
"name": "AlienRace",
|
||||
"path": "../../../../../../../../Users/Kalo/Downloads/AlienRaces/Source/AlienRace/AlienRace"
|
||||
},
|
||||
{
|
||||
"path": "../../../../../../workshop/content/294100/3565275325/Source/SRALib/SRALib"
|
||||
},
|
||||
{
|
||||
"path": "../../../../../../workshop/content/294100/3575567766"
|
||||
},
|
||||
{
|
||||
"path": "../../../../../../../../Users/Kalo/Downloads/MechsuitFramework-main"
|
||||
},
|
||||
{
|
||||
"path": "../../../../../../workshop/content/294100/3226701491/1.6/Assemblies/BM_PowerArmor"
|
||||
"path": "../../../../dll1.6"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
|
||||
11
mod.vdf
11
mod.vdf
@@ -1,11 +0,0 @@
|
||||
|
||||
"workshopitem"
|
||||
{
|
||||
"appid" "294100"
|
||||
"contentfolder" "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\WulaFallenEmpireTest"
|
||||
"previewfile" "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\WulaFallenEmpireTest\\About\\Preview.png"
|
||||
"visibility" "3"
|
||||
"title" "WulaFallenEmpire V2"
|
||||
"changenote" "1.6"
|
||||
"publishedfileid" "3604325124"
|
||||
}
|
||||
Reference in New Issue
Block a user