暂存mcp重写

This commit is contained in:
2025-11-25 13:57:54 +08:00
parent cf7febb3a3
commit 4a695f3ca6
16 changed files with 491 additions and 1545 deletions

3
.gitignore vendored
View File

@@ -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

View File

@@ -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="..." />` 条目,否则会导致编译失败。

View File

@@ -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="..." />` 条目,否则会导致编译失败。

View File

@@ -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()

View 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()

View File

@@ -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服务器已停止。")

View File

@@ -1 +0,0 @@
86696

View File

@@ -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()

View File

@@ -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()

View File

@@ -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❌ 仍存在问题,需要进一步调试")

View File

@@ -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服务器仍存在问题需要进一步调试。")

View File

@@ -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)

View File

@@ -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 知识库了!

View File

@@ -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服务器

View File

@@ -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
View File

@@ -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"
}