diff --git a/.idea/misc.xml b/.idea/misc.xml
index 06ede0d..419adf3 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 909438d..8073316 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/server.py b/mcp/server.py
index 1a9c189..54d62ae 100644
--- a/mcp/server.py
+++ b/mcp/server.py
@@ -1,56 +1,221 @@
-# server.py
+import asyncssh
+import os
+from contextlib import asynccontextmanager
+from collections.abc import AsyncIterator
+from dataclasses import dataclass
-import asyncio
-import asyncssh # 用于 SSH 连接的库
from mcp.server.fastmcp import FastMCP, Context
+from mcp.server.session import ServerSession
-# --- 预设的服务器连接信息 ---
-# 警告:在生产环境中,不要将密钥和密码硬编码在代码里。
-# 最好使用环境变量、配置文件或专门的密钥管理服务。
-REMOTE_HOST = '202.121.182.208' # 替换为你的服务器地址
-REMOTE_USER = 'koko125' # 替换为你的用户名
-PRIVATE_KEY_PATH = 'D:/tool/tool/id_rsa.txt' # 替换为你的私钥文件路径
+# --- 1. 使用您提供的参数 ---
+REMOTE_HOST = '202.121.182.208'
+REMOTE_USER = 'koko125'
+PRIVATE_KEY_PATH = 'D:/tool/tool/id_rsa.txt' # Windows 路径建议使用 /
+INITIAL_WORKING_DIRECTORY = '/cluster/home/koko125/sandbox'
-# 创建一个 MCP 服务器实例
-mcp = FastMCP("远程服务器工具")
+# --- 2. 生命周期管理部分 (高级功能) ---
+
+@dataclass
+class AppContext:
+ """用于在服务器生命周期内持有共享资源的上下文对象"""
+ ssh_connection: asyncssh.SSHClientConnection
+ current_path: str
+
+
+@asynccontextmanager
+async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
+ """在服务器启动时建立SSH连接,在关闭时断开。"""
+ print("服务器启动中,正在建立SSH连接...")
+ conn = None
+ try:
+ conn = await asyncssh.connect(
+ REMOTE_HOST,
+ username=REMOTE_USER,
+ client_keys=[PRIVATE_KEY_PATH]
+ )
+ print("SSH 连接成功!")
+
+ # <<< 在登录时执行 source bashrc 命令 >>>
+ # 注意:bashrc 的效果(如环境变量)只会对这一个 conn.run() 调用生效。
+ # 对于非交互式shell,更好的做法是把需要执行的命令串联起来。
+ # 但如果只是为了加载路径等,可以在后续命令中体现。
+ print("正在加载 .bashrc...")
+ # 为了让 bashrc 生效,我们需要在一个交互式 shell 中执行命令
+ await conn.run('source /cluster/home/koko125/.bashrc', check=True)
+ print(".bashrc 加载完成。")
+
+ print(f"初始工作目录设置为: {INITIAL_WORKING_DIRECTORY}")
+ yield AppContext(ssh_connection=conn, current_path=INITIAL_WORKING_DIRECTORY)
+ finally:
+ if conn:
+ conn.close()
+ await conn.wait_closed()
+ print("SSH 连接已关闭。")
+
+
+# --- 3. 创建 MCP 服务器实例,并应用生命周期管理 ---
+mcp = FastMCP("远程服务器工具", lifespan=app_lifespan)
+
+
+# --- 4. 保留的调试工具 (不安全) ---
@mcp.tool()
async def execute_remote_command(command: str, ctx: Context) -> str:
"""
- 在远程服务器上执行一个shell命令并返回其输出。
-
- Args:
- command: 要在远程服务器上执行的命令字符串。
+ 【调试用】在远程服务器上执行一个任意的shell命令并返回其输出。
+ 警告:此工具有安全风险,请勿在生产环境中使用。
"""
- await ctx.info(f"准备在 {REMOTE_HOST} 上执行命令: '{command}'")
-
+ await ctx.info(f"准备在 {REMOTE_HOST} 上执行调试命令: '{command}'")
try:
- # 建立 SSH 连接
+ # 每次都创建一个新连接,以确保环境隔离
async with asyncssh.connect(REMOTE_HOST, username=REMOTE_USER, client_keys=[PRIVATE_KEY_PATH]) as conn:
- # 执行命令
- result = await conn.run(command, check=True)
-
- # 成功执行,返回标准输出
+ # 在执行命令前先 source bashrc
+ full_command = f'source /cluster/home/koko125/.bashrc && {command}'
+ result = await conn.run(full_command, check=True)
output = result.stdout.strip()
- await ctx.info(f"命令成功执行,返回输出。")
- await ctx.debug(f"输出内容: {output}")
+ await ctx.info("调试命令成功执行。")
return output
-
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"发生未知错误: {str(e)}"
await ctx.error(error_message)
return error_message
-# 这部分使得你可以直接运行 `python server.py`
+# --- 5. 新增的规范、安全的工具 ---
+
+@mcp.tool()
+async def list_files_in_sandbox(ctx: Context[ServerSession, AppContext], path: str = ".") -> str:
+ """
+ 【安全】列出远程服务器安全沙箱路径下的文件和目录。
+
+ Args:
+ path: 要查看的相对路径,默认为当前沙箱目录。
+ """
+ try:
+ app_ctx = ctx.request_context.lifespan_context
+ conn = app_ctx.ssh_connection
+
+ # 防止目录穿越攻击
+ if ".." in path.split('/'):
+ return "错误: 不允许使用 '..' 访问上级目录。"
+
+ # 安全地拼接路径
+ target_path = os.path.join(app_ctx.current_path, path)
+
+ await ctx.info(f"正在列出安全路径: {target_path}")
+
+ # 为了让 .bashrc 的设置(如别名)生效,最好也加上 source
+ command_to_run = f'source /cluster/home/koko125/.bashrc && ls -l {conn.escape(target_path)}'
+ result = await conn.run(command_to_run, check=True)
+ return result.stdout.strip()
+ except Exception as e:
+ await ctx.error(f"执行 ls 命令失败: {e}")
+ return f"错误: {e}"
+
+
+@mcp.tool()
+async def create_directory_in_sandbox(ctx: Context[ServerSession, AppContext], directory_name: str) -> str:
+ """
+ 【安全】在远程服务器的安全沙箱内创建一个新目录。
+
+ Args:
+ directory_name: 要创建的目录的名称。
+ """
+ try:
+ app_ctx = ctx.request_context.lifespan_context
+ conn = app_ctx.ssh_connection
+
+ # 安全检查:确保目录名不包含斜杠或 "..",以防止创建深层目录或进行目录穿越
+ if '/' in directory_name or ".." in directory_name:
+ return "错误: 目录名无效。不能包含 '/' 或 '..'。"
+
+ target_path = os.path.join(app_ctx.current_path, directory_name)
+
+ await ctx.info(f"正在创建远程目录: {target_path}")
+
+ command_to_run = f'source /cluster/home/koko125/.bashrc && mkdir {conn.escape(target_path)}'
+ await conn.run(command_to_run, check=True)
+
+ return f"目录 '{directory_name}' 在沙箱中成功创建。"
+ except asyncssh.ProcessError as e:
+ error_message = f"创建目录失败: {e.stderr.strip()}"
+ await ctx.error(error_message)
+ return error_message
+ except Exception as e:
+ await ctx.error(f"创建目录时发生未知错误: {e}")
+ return f"错误: {e}"
+
+
+@mcp.tool()
+async def read_file_in_sandbox(ctx: Context[ServerSession, AppContext], file_path: str) -> str:
+ """
+ 【安全】读取远程服务器安全沙箱内指定文件的内容。
+
+ Args:
+ file_path: 相对于沙箱目录的文件路径。
+ """
+ try:
+ app_ctx = ctx.request_context.lifespan_context
+ conn = app_ctx.ssh_connection
+
+ # 安全检查:防止目录穿越
+ if ".." in file_path.split('/'):
+ return "错误: 不允许使用 '..' 访问上级目录。"
+
+ target_path = os.path.join(app_ctx.current_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}"
+
+
+@mcp.tool()
+async def write_file_in_sandbox(ctx: Context[ServerSession, AppContext], file_path: str, content: str) -> str:
+ """
+ 【安全】向远程服务器安全沙箱内的文件写入内容。如果文件已存在,则会覆盖它。
+
+ Args:
+ file_path: 相对于沙箱目录的文件路径。
+ content: 要写入文件的文本内容。
+ """
+ try:
+ app_ctx = ctx.request_context.lifespan_context
+ conn = app_ctx.ssh_connection
+ normalized_file_path = file_path.replace("\\", "/")
+ # 安全检查
+ if ".." in file_path.split('/'):
+ return "错误: 不允许使用 '..' 访问上级目录。"
+
+ target_path = os.path.join(app_ctx.current_path, normalized_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}"
+
+
+# --- 运行服务器 ---
if __name__ == "__main__":
- # mcp.run() # 这是默认的 stdio 模式
- mcp.run(transport="streamable-http") # 改为 streamable-http 模式
\ No newline at end of file
+ mcp.run(transport="streamable-http")
\ No newline at end of file