From 4a695f3ca6cda8f8df0ee65990c40a4e45989b4f Mon Sep 17 00:00:00 2001 From: "ProjectKoi-Kalo\\Kalo" Date: Tue, 25 Nov 2025 13:57:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9A=82=E5=AD=98mcp=E9=87=8D=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- .lingma/rules/rimworld.md | 29 -- .qoder/rules/rimworld.md | 30 -- MCP/direct_mcp_client.py | 103 ---- MCP/mcpserver_stdio_complete.py | 488 ++++++++++++++++++ MCP/mcpserver_stdio_simple_proxy.py | 271 ---------- MCP/pid.txt | 1 - MCP/rimworld_query.py | 105 ---- MCP/test_config.py | 123 ----- MCP/test_mcp.py | 149 ------ MCP/test_mcp_timeout_fix.py | 150 ------ MCP/web_api_server.py | 249 --------- MCP/使用指南.md | 102 ---- MCP/使用转发服务指南.md | 207 -------- .../3516260226.code-workspace | 15 +- mod.vdf | 11 - 16 files changed, 491 insertions(+), 1545 deletions(-) delete mode 100644 .lingma/rules/rimworld.md delete mode 100644 .qoder/rules/rimworld.md delete mode 100644 MCP/direct_mcp_client.py create mode 100644 MCP/mcpserver_stdio_complete.py delete mode 100644 MCP/mcpserver_stdio_simple_proxy.py delete mode 100644 MCP/pid.txt delete mode 100644 MCP/rimworld_query.py delete mode 100644 MCP/test_config.py delete mode 100644 MCP/test_mcp.py delete mode 100644 MCP/test_mcp_timeout_fix.py delete mode 100644 MCP/web_api_server.py delete mode 100644 MCP/使用指南.md delete mode 100644 MCP/使用转发服务指南.md delete mode 100644 mod.vdf diff --git a/.gitignore b/.gitignore index 121fd9b0..9b3767bc 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,5 @@ package-lock.json package.json dark-server/dark-server.js node_modules/ -gemini-websocket-proxy/ \ No newline at end of file +gemini-websocket-proxy/ +Tools/dark-server/dark-server.js diff --git a/.lingma/rules/rimworld.md b/.lingma/rules/rimworld.md deleted file mode 100644 index b48fcd64..00000000 --- a/.lingma/rules/rimworld.md +++ /dev/null @@ -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` 项目文件中的相应 `` 条目,否则会导致编译失败。 \ No newline at end of file diff --git a/.qoder/rules/rimworld.md b/.qoder/rules/rimworld.md deleted file mode 100644 index 88fa530a..00000000 --- a/.qoder/rules/rimworld.md +++ /dev/null @@ -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` 项目文件中的相应 `` 条目,否则会导致编译失败。 - diff --git a/MCP/direct_mcp_client.py b/MCP/direct_mcp_client.py deleted file mode 100644 index fd559d94..00000000 --- a/MCP/direct_mcp_client.py +++ /dev/null @@ -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() \ No newline at end of file diff --git a/MCP/mcpserver_stdio_complete.py b/MCP/mcpserver_stdio_complete.py new file mode 100644 index 00000000..035c1768 --- /dev/null +++ b/MCP/mcpserver_stdio_complete.py @@ -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'(.*?)', 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 格式为 ...NAME... + + # 策略 1: 查找 NAME,然后向上找最近的 + pattern = r"<(\w+)\s*(?:Name=\"[^\"]*\")?>\s*" + re.escape(def_name) + r"" + 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() + # 寻找对应的结束标签 + # 这是一个简化的查找,不支持嵌套同名标签,但在 RimWorld Defs 中通常足够 + end_tag = f"" + 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. 查找 (Inherits) + parents = re.findall(r'ParentName="([^"]+)"', content) + parents += re.findall(r'([^<]+)', content) + for p in parents: + edges.append({"target": f"xml:{p}", "kind": "Inherits"}) + + # 2. 查找 (BindsClass) + classes = re.findall(r'<[\w]+Class>([\w\.]+)', content) # 如 + classes += re.findall(r'Class="([\w\.]+)"', content) # 如
  • + 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. 查找 引用 (References) + potential_refs = re.findall(r'>([\w]+) 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'(.*?)', 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() \ No newline at end of file diff --git a/MCP/mcpserver_stdio_simple_proxy.py b/MCP/mcpserver_stdio_simple_proxy.py deleted file mode 100644 index 8ea45dd9..00000000 --- a/MCP/mcpserver_stdio_simple_proxy.py +++ /dev/null @@ -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 "" - - # 向下找到匹配的 - def_end_index = -1 - for i in range(def_start_index + 1, len(lines)): - if f'' 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" 或 "Keyword" - 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服务器已停止。") \ No newline at end of file diff --git a/MCP/pid.txt b/MCP/pid.txt deleted file mode 100644 index 32b2ab6a..00000000 --- a/MCP/pid.txt +++ /dev/null @@ -1 +0,0 @@ -86696 \ No newline at end of file diff --git a/MCP/rimworld_query.py b/MCP/rimworld_query.py deleted file mode 100644 index bd491e84..00000000 --- a/MCP/rimworld_query.py +++ /dev/null @@ -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() \ No newline at end of file diff --git a/MCP/test_config.py b/MCP/test_config.py deleted file mode 100644 index 9ace940d..00000000 --- a/MCP/test_config.py +++ /dev/null @@ -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() \ No newline at end of file diff --git a/MCP/test_mcp.py b/MCP/test_mcp.py deleted file mode 100644 index 4cea894a..00000000 --- a/MCP/test_mcp.py +++ /dev/null @@ -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❌ 仍存在问题,需要进一步调试") \ No newline at end of file diff --git a/MCP/test_mcp_timeout_fix.py b/MCP/test_mcp_timeout_fix.py deleted file mode 100644 index 2f0f803a..00000000 --- a/MCP/test_mcp_timeout_fix.py +++ /dev/null @@ -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服务器仍存在问题,需要进一步调试。") \ No newline at end of file diff --git a/MCP/web_api_server.py b/MCP/web_api_server.py deleted file mode 100644 index 5a29b922..00000000 --- a/MCP/web_api_server.py +++ /dev/null @@ -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 = """ - - - - - - RimWorld 知识库 - - - -
    -

    🎮 RimWorld 知识库

    -

    直接查询RimWorld游戏的C#源码和XML定义

    -
    - -
    - - -
    - -
    -

    💡 查询示例:

    -
    • ThingDef的定义和用法
    -
    • 如何创建Building
    -
    • Pawn类的主要方法
    -
    • CompPower的使用
    -
    - - - - - - - """ - - 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) \ No newline at end of file diff --git a/MCP/使用指南.md b/MCP/使用指南.md deleted file mode 100644 index 9fd0dcb5..00000000 --- a/MCP/使用指南.md +++ /dev/null @@ -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 知识库了! \ No newline at end of file diff --git a/MCP/使用转发服务指南.md b/MCP/使用转发服务指南.md deleted file mode 100644 index 8fba0697..00000000 --- a/MCP/使用转发服务指南.md +++ /dev/null @@ -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服务器 \ No newline at end of file diff --git a/Source/WulaFallenEmpire/3516260226.code-workspace b/Source/WulaFallenEmpire/3516260226.code-workspace index 9604a404..a2433624 100644 --- a/Source/WulaFallenEmpire/3516260226.code-workspace +++ b/Source/WulaFallenEmpire/3516260226.code-workspace @@ -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": {} diff --git a/mod.vdf b/mod.vdf deleted file mode 100644 index a6b7699b..00000000 --- a/mod.vdf +++ /dev/null @@ -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" -} \ No newline at end of file