diff --git a/.idea/misc.xml b/.idea/misc.xml
index 419adf3..06ede0d 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,5 +3,5 @@
-
+
\ No newline at end of file
diff --git a/.idea/solidstate-tools.iml b/.idea/solidstate-tools.iml
index 8073316..909438d 100644
--- a/.idea/solidstate-tools.iml
+++ b/.idea/solidstate-tools.iml
@@ -2,7 +2,7 @@
-
+
\ No newline at end of file
diff --git a/mcp/lifespan.py b/mcp/lifespan.py
new file mode 100644
index 0000000..d5e75d7
--- /dev/null
+++ b/mcp/lifespan.py
@@ -0,0 +1,50 @@
+import asyncssh
+from contextlib import asynccontextmanager
+from collections.abc import AsyncIterator
+from dataclasses import dataclass
+from starlette.applications import Starlette
+# --- 1. SSH 连接参数 ---
+REMOTE_HOST = '202.121.182.208'
+REMOTE_USER = 'koko125'
+# 确保路径正确,Windows 路径建议使用正斜杠 '/'
+PRIVATE_KEY_PATH = 'D:/tool/tool/id_rsa.txt'
+INITIAL_WORKING_DIRECTORY = f'/cluster/home/{REMOTE_USER}/sandbox'
+
+
+# --- 2. 定义共享上下文 ---
+# 这个数据类将持有所有模块需要共享的资源
+@dataclass
+class SharedAppContext:
+ """在整个服务器生命周期内持有的共享资源"""
+ ssh_connection: asyncssh.SSHClientConnection
+ sandbox_path: str
+
+
+# --- 3. 创建生命周期管理器 ---
+# 这是整个架构的核心,负责在服务器启动时连接SSH,在关闭时断开
+@asynccontextmanager
+async def shared_lifespan(app: Starlette) -> AsyncIterator[SharedAppContext]:
+ """
+ 为整个应用管理共享资源的生命周期。
+ """
+ print("主应用启动,正在建立共享 SSH 连接...")
+ conn = None
+ try:
+ # 建立 SSH 连接
+ conn = await asyncssh.connect(
+ REMOTE_HOST,
+ username=REMOTE_USER,
+ client_keys=[PRIVATE_KEY_PATH]
+ )
+ print(f"SSH 连接到 {REMOTE_HOST} 成功!")
+
+ # 使用 yield 将创建好的共享资源上下文传递给 Starlette 应用
+ # 服务器会在此处暂停并开始处理请求
+ yield {"shared_context": SharedAppContext(ssh_connection=conn, sandbox_path=INITIAL_WORKING_DIRECTORY)}
+
+ finally:
+ # 当服务器关闭时,yield 之后的代码会被执行
+ if conn:
+ conn.close()
+ await conn.wait_closed()
+ print("共享 SSH 连接已关闭。")
diff --git a/mcp/main.py b/mcp/main.py
new file mode 100644
index 0000000..16e67e7
--- /dev/null
+++ b/mcp/main.py
@@ -0,0 +1,28 @@
+# main.py
+import contextlib
+from starlette.applications import Starlette
+from starlette.routing import Mount
+
+from test_tools import create_test_mcp
+from system_tools import create_system_mcp # 如果暂时不用可先不挂
+
+# 先创建 MCP 实例
+test_mcp = create_test_mcp()
+# system_mcp = create_system_mcp()
+
+# 关键:在 Starlette 的 lifespan 中启动 MCP 的 session manager
+@contextlib.asynccontextmanager
+async def lifespan(app: Starlette):
+ async with contextlib.AsyncExitStack() as stack:
+ await stack.enter_async_context(test_mcp.session_manager.run())
+ # await stack.enter_async_context(system_mcp.session_manager.run())
+ yield # 服务器运行期间
+ # 退出时自动清理
+
+app = Starlette(
+ lifespan=lifespan,
+ routes=[
+ Mount("/test", app=test_mcp.streamable_http_app()),
+ # Mount("/system", app=system_mcp.streamable_http_app()),
+ ],
+)
\ No newline at end of file
diff --git a/mcp/system_tools.py b/mcp/system_tools.py
new file mode 100644
index 0000000..d453d4c
--- /dev/null
+++ b/mcp/system_tools.py
@@ -0,0 +1,114 @@
+# system_tools.py
+import os
+import asyncssh
+from mcp.server.fastmcp import FastMCP, Context
+from mcp.server.session import ServerSession
+from lifespan import SharedAppContext # 导入共享上下文的类型定义
+
+
+def create_system_mcp() -> FastMCP:
+ """创建一个包含系统操作工具的 MCP 实例。"""
+
+ system_mcp = FastMCP(
+ name="System Tools",
+ instructions="用于在远程服务器上进行基本文件和目录操作的工具集。",
+ # 将 MCP 实例的 HTTP 路径设置为根,以便挂载在 /system 下 [1]
+ streamable_http_path="/"
+ )
+
+ @system_mcp.tool()
+ async def list_files(ctx: Context[ServerSession, dict], path: str = ".") -> str:
+ """
+ 列出远程服务器安全沙箱路径下的文件和目录。
+ Args:
+ path: 要查看的相对路径,默认为当前沙箱目录。
+ """
+ try:
+ # 从上下文中获取共享的 SSH 连接和沙箱路径 [1]
+ shared_ctx: SharedAppContext = ctx.request_context.lifespan_context["shared_context"]
+
+ conn = shared_ctx.ssh_connection
+ sandbox_path = shared_ctx.sandbox_path
+
+ # 安全检查:防止目录穿越攻击
+ if ".." in path.split('/'):
+ return "错误: 不允许使用 '..' 访问上级目录。"
+
+ # 安全地拼接路径
+ target_path = os.path.join(sandbox_path, path)
+ await ctx.info(f"正在列出安全路径: {target_path}")
+
+ # 使用 conn.run() 执行 ls -l 命令
+ command_to_run = f'ls -la {conn.escape(target_path)}'
+ result = await conn.run(command_to_run, check=True)
+ return result.stdout.strip()
+ except asyncssh.ProcessError as e:
+ error_message = f"命令执行失败: {e.stderr.strip()}"
+ await ctx.error(error_message)
+ return error_message
+ except Exception as e:
+ error_message = f"执行 ls 命令时发生未知错误: {e}"
+ await ctx.error(error_message)
+ return error_message
+
+ @system_mcp.tool()
+ async def read_file(ctx: Context[ServerSession, dict], file_path: str) -> str:
+ """
+ 读取远程服务器安全沙箱内指定文件的内容。
+ Args:
+ file_path: 相对于沙箱目录的文件路径。
+ """
+ try:
+ shared_ctx: SharedAppContext = ctx.request_context.lifespan_context["shared_context"]
+
+ conn = shared_ctx.ssh_connection
+ sandbox_path = shared_ctx.sandbox_path
+ # 安全检查:防止目录穿越
+ if ".." in file_path.split('/'):
+ return "错误: 不允许使用 '..' 访问上级目录。"
+
+ target_path = os.path.join(sandbox_path, file_path)
+ await ctx.info(f"正在读取远程文件: {target_path}")
+
+ async with conn.start_sftp_client() as sftp:
+ async with sftp.open(target_path, 'r') as f:
+ content = await f.read()
+ return content
+ except FileNotFoundError:
+ error_message = f"读取文件失败: 文件 '{file_path}' 不存在。"
+ await ctx.error(error_message)
+ return error_message
+ except Exception as e:
+ await ctx.error(f"读取文件时发生未知错误: {e}")
+ return f"错误: {e}"
+
+ @system_mcp.tool()
+ async def write_file(ctx: Context[ServerSession, dict], file_path: str, content: str) -> str:
+ """
+ 向远程服务器安全沙箱内的文件写入内容。如果文件已存在,则会覆盖它。
+ Args:
+ file_path: 相对于沙箱目录的文件路径。
+ content: 要写入文件的文本内容。
+ """
+ try:
+ shared_ctx: SharedAppContext = ctx.request_context.lifespan_context["shared_context"]
+
+ conn = shared_ctx.ssh_connection
+ sandbox_path = shared_ctx.sandbox_path
+
+ # 安全检查
+ if ".." in file_path.split('/'):
+ return "错误: 不允许使用 '..' 访问上级目录。"
+
+ target_path = os.path.join(sandbox_path, file_path)
+ await ctx.info(f"正在向远程文件写入: {target_path}")
+
+ async with conn.start_sftp_client() as sftp:
+ async with sftp.open(target_path, 'w') as f:
+ await f.write(content)
+ return f"内容已成功写入到沙箱文件 '{file_path}'。"
+ except Exception as e:
+ await ctx.error(f"写入文件时发生未知错误: {e}")
+ return f"错误: {e}"
+
+ return system_mcp
diff --git a/mcp/test_tools.py b/mcp/test_tools.py
new file mode 100644
index 0000000..a5d59f2
--- /dev/null
+++ b/mcp/test_tools.py
@@ -0,0 +1,19 @@
+# test_tools.py
+from mcp.server.fastmcp import FastMCP
+
+
+def create_test_mcp() -> FastMCP:
+ """创建一个只包含最简单工具的 MCP 实例,用于测试连接。"""
+
+ test_mcp = FastMCP(
+ name="Test Tools",
+ instructions="用于测试服务器连接是否通畅。",
+ streamable_http_path="/"
+ )
+
+ @test_mcp.tool()
+ def ping() -> str:
+ """一个简单的工具,用于确认服务器是否响应。"""
+ return "pong"
+
+ return test_mcp
\ No newline at end of file