mcp-change
This commit is contained in:
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -3,5 +3,5 @@
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.12" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11" project-jdk-type="Python SDK" />
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (test1)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
2
.idea/solidstate-tools.iml
generated
2
.idea/solidstate-tools.iml
generated
@@ -2,7 +2,7 @@
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.11" jdkType="Python SDK" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.12 (test1)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
227
mcp/server.py
227
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 模式
|
||||
mcp.run(transport="streamable-http")
|
||||
Reference in New Issue
Block a user