把你的MCP Server部署到公网,让阿里云上的应用来访问和使用
一、前言
前前后后的给小落同学加了许多的MCP。
但是这些功能之前一直在我本地的小落同学上跑,部署在阿里云ECS上的小落同学因为买的ECS配置太低(99元一年的2H2G特惠主机)跑不动,这个周末在家没事做,想想是不是干脆用frp让公网上的小落同学也可以把这些MCP也都给支持起来。
所以这个周末的任务就是:把原先一直在我本地电脑上跑的小落同学的MCP Server部署到公网,并让阿里云上的小落同学来访问和使用。

目前小落同学支持的MCP包括:
fill_time_cost: 每天自动填TAPD的项目的工时。weekly_report: 每周自动写周报并发到我的钉钉。weather_query: 天气查询。content_summary: 文档内容总结。smart_home: 智能家居控制(家里的小米灯光、海信电视、格力空调等)。odd_notifier: 消息通知(将指定的消息发给指定的钉钉群、企业微信群、邮箱等等)。odd_bookmark: 书签收藏(将微信公众号、博客园、知乎、CSDN等网站上的文章收藏并保存到本地的markdown文件,支持图片表格等)。odd_translate: 内容、文章翻译功能。odd_swissknife: 获取IP地址,获取地理坐标等。- 其它一些杂七杂八的功能,暂不在此介绍。
既然想了,那不管有没有人用小落同学,咱先给它配上去再说。
二、MCP Server可配置化
1. 新增ODDMCP配置
在小落同学的.env环境变量里新增mcp相关环境变量配置
#########################################
## ODDMCP 服务配置
# 1. 访问 ODDMCP 服务的地址和端口, 以及访问令牌
# 2. ODDMCP服务的Redis配置
#########################################
ODDMCP_SERVER_ADDR = "oddmcp.oddmeta.net"
# ODDMCP_SERVER_ADDR = "localhost"
ODDMCP_SERVER_PORT = 9600
ODDMCP_SERVER_TOKEN = "your_oddmcp_server_token"
ODDMCP_REDIS_ADDR = "47.116.14.194"
# ODDMCP_REDIS_ADDR = "localhost"
ODDMCP_REDIS_PORT = 63579
ODDMCP_REDIS_DB = 0
ODDMCP_REDIS_PASSWORD = ""
2. 同步调整MCP Server和MCP Client中与MCP相关的配置
把原先固定的localhost的地址,改成从环境变量中获取。
1)代码:oddmcp_server.py
def StartOddMCPServerTask():
"""
主函数 - 优化后版本,直接在主线程运行
"""
logger.info("启动MCP服务器...")
load_dotenv()
oddmcp_server_addr = os.environ.get("ODDMCP_SERVER_ADDR", "0.0.0.0")
oddmcp_server_port = os.environ.get("ODDMCP_SERVER_PORT", 9600)
print(f"MCP服务器正在启动 ({oddmcp_server_addr}:{oddmcp_server_port})...")
print("按 Ctrl+C 停止")
try:
# FastMCP (Uvicorn) 会自动处理 Ctrl+C 信号并优雅关闭
server.run(
transport="http",
host=oddmcp_server_addr,
port=int(oddmcp_server_port),
path="/mcp"
)
except KeyboardInterrupt:
# 这一步其实通常不需要,因为 Uvicorn 会捕获,但为了保险可以加上
print("\n服务器已停止")
except Exception as e:
print(f"服务器运行出错: {e}")
import traceback
traceback.print_exc()
2)代码:oddmcp_client.py
class OddmcpClient():
def __init__(self) -> None:
load_dotenv()
oddmcp_server_addr = os.environ.get("ODDMCP_SERVER_ADDR", "localhost") # 读取Key
oddmcp_server_port = os.environ.get("ODDMCP_SERVER_PORT", 9600) # 读取 model
self.uri = f"http://{oddmcp_server_addr}:{oddmcp_server_port}/mcp/"
self.tools = None
self._connected = False
self.client = Client(StreamableHttpTransport(url=self.uri))
3)代码:oddmcp_status_callback.py
def StartOddmcpRedis():
load_dotenv()
oddmcp_redis_addr = os.environ.get("ODDMCP_REDIS_ADDR", "127.0.0.1")
oddmcp_redis_port = os.environ.get("ODDMCP_REDIS_PORT", 6379)
redis_db = os.environ.get("ODDMCP_REDIS_DB", 0)
redis_password = os.environ.get("ODDMCP_REDIS_PASSWORD", "")
logger.info(f"Starting Redis client with address {oddmcp_redis_addr}, port {oddmcp_redis_port}, db {redis_db}")
if redis_password == "":
oddmcp_redis_client = redis.Redis(host=oddmcp_redis_addr, port=oddmcp_redis_port, db=redis_db)
else:
oddmcp_redis_client = redis.Redis(host=oddmcp_redis_addr, port=oddmcp_redis_port, db=redis_db, password=redis_password)
return oddmcp_redis_client
oddmcp_redis_client = StartOddmcpRedis()
4)oddagent
同步的时候发现几个新的MCP Server功能还没同步到小落同学上的oddagent,也顺手改了一下。
{
"tool_name": "content_sumary",
"name": "内容摘要,内容总结,会议总结",
"description": "对会议内容进行摘要总结服务。如:总结会议内容,会议总结,会议内容总结。",
"parameters": [
{"name": "content", "desc": "会议内容", "type": "string", "required": True},
],
"enabled": True
},
{
"tool_name": "odd_notify",
"name": "通知",
"description": "将输入的内容利用odd_notify服务进行通知服务。如:转发一下上面的内容到我的钉钉/企业微信。",
"parameters": [
{"name": "content", "desc": "待转发的内容", "type": "string", "required": True},
{"name": "bot", "desc": "机器人名称", "type": "string", "required": True},
],
"enabled": True
},
{
"tool_name": "odd_translate",
"name": "翻译",
"description": "将输入的内容利用odd_translate服务进行翻译服务。如:将英文翻译成中文,将中文翻译成英文。",
"parameters": [
{"name": "content", "desc": "待翻译的内容", "type": "string", "required": True},
{"name": "method", "desc": "翻译方法。中译英:zh2en,英译中:en2zh,中译日:zh2ja,日译中:ja2zh,中译韩:zh2ko,韩译中:ko2zh", "type": "string", "required": True},
],
"enabled": True
},
{
"tool_name": "odd_swissknife",
"name": "瑞士工具",
"description": "瑞士工具服务。如:获取IP地址,获取地理坐标。",
"parameters": [
{"name": "method", "desc": "处理方法。获取IP地址:get_ip,获取地理坐标:get_geo", "type": "string", "required": True},
],
"enabled": True
},
三、利用frp来做跳转
ODDMCP用了两个端口,一个是MCP Server所绑定的9600端口,另一个是每个在MCP运行过程中的一些实时进展状态回调时所使用的redis。
1. 客户端配置
代码:frpc-https.toml
##################################
# 服务5:oddmcp
##################################
[[proxies]]
name = "oddmcp"
type = "tcp"
localPort = 9600
remotePort = 9600
##################################
# 服务6:oddmcp-redis
##################################
[[proxies]]
name = "oddmcp-redis"
type = "tcp"
localPort = 6379
remotePort = 63579
杀掉并重新启动 frpc
pkill frpc
./frpc -c frpc-https.toml &
2. 服务端口配置
客户端修改并新增了这两个端口,并且重启了frpc之后,先到ECS服务器端查看一下,端口状态是否都正常。
- MCP Server端口
ss -tuln | grep :9600
# 或
netstat -tulnp | grep :9600
- Redis端口
ss -tuln | grep :9600
# 或
netstat -tulnp | grep :9600
如果都有正常绑定了,说明frp已经可以工作了。
需要注意的是:服务绑定地址应该是 0.0.0.0:9600,而不是 127.0.0.1:9600(后者只允许本地访问)。
四、阿里云ECS配置
配置好frp后,还需要让阿里云ECS放行这两个端口。
1. 修改ECS安全组配置
打开浏览器,登录阿里云控制台,进入安全组配置,并在其中新增、放行9600和63579这两个TCP的端口。
阿里云控制台上的功能比较多,不常用的话,可能要找地址找半天。由于忘记功能名字了,搜索也不好搜索,呵呵。
为方便记录,特把安全组的链接地址也贴一下:https://ecs.console.aliyun.com/securityGroup/region/cn-shanghai
2. 放行防火墙
打开xshell,ssh登录上ECS服务器,查看是否放行 9600/tcp
如果是centos/openEuler操作系统:
sudo firewall-cmd --list-all
若未放行,添加规则:
sudo firewall-cmd --add-port=9600/tcp --permanent
sudo firewall-cmd --reload
如果是ubuntu操作系统:
sudo ufw status verbose
# 若启用,需放行端口
sudo ufw allow 9600/tcp
查看Linux操作系统发行版命令:
cat /etc/os-release
五、测试验证
1. 端口开放测试
在命令行里,telnet oddmcp.oddmeta.net 9600,看看能不能连接上。
如果能连接上,说明前面的frp的配置,以及阿里云ECS的配置都已经成功了。
2. 小落同学测试
重启一下小落同学,然后测试一下任意一个mcp的功能:智能体项目给填8小时的工时。然后看一下小落同学的日志输出。
如果看到类似下面这样的日志,说明有问题。
2026-02-02 15:09:52 DEBUG oddagent.py:301 (60024-33232) - 【意图识别】响应:code=0, purpose_options={'1': 'query_weather', '2': 'weekly_report', '3': 'set_iot_status', '4': 'get_iot_status', '5': 'load_iot_list', '6': 'bookmark_it', '7': 'search_web', '8': 'fill_time_cost', '9': 'play_music', '10': 'content_sumary', '11': 'odd_translate', '12': 'odd_notify', '13': 'odd_swissknife'}, user_choice=8, 耗时=0.3831911087036133
2026-02-02 15:09:52 INFO oddagent.py:319 (60024-33232) - 【意图识别】用户选择了工具:fill_time_cost
[DEBUG][2026-02-02 15:09:52,521][streamable_http.py:649] Connecting to StreamableHTTP endpoint: http://oddmcp.oddmeta.net:9443/mcp/
[DEBUG][2026-02-02 15:09:52,523][streamable_http.py:547] Sending client message: root=JSONRPCRequest(method='initialize', params={'protocolVersion': '2025-11-25', 'capabilities': {}, 'clientInfo': {'name': 'mcp', 'version': '0.1.0'}}, jsonrpc='2.0', id=0)
[DEBUG][2026-02-02 15:09:52,524][_trace.py:87] connect_tcp.started host='oddmcp.oddmeta.net' port=9443 local_address=None timeout=5.0 socket_options=None
[DEBUG][2026-02-02 15:09:52,575][_trace.py:87] connect_tcp.failed exception=ConnectError(gaierror(11001, 'getaddrinfo failed'))
[ERROR][2026-02-02 15:09:52,578][oddmcp_client.py:57] 连接MCP服务器失败: Client failed to connect: [Errno 11001] getaddrinfo failed
[ERROR][2026-02-02 15:09:52,579][openai_compatible.py:64] [OddMCP]获取工具失败,mcp为空或tools为空! MCP服务器未启动?
[ERROR][2026-02-02 15:09:52,580][openai_compatible.py:113] [OddMCP]获取工具失败: fill_time_cost
如果正常的话,日志应该是类似这样的。
[INFO][2026-02-02 16:38:13,691][openai_compatible.py:365] [OpenAI]工具调用chunk: ChoiceDelta(content=None, function_call=None, refusal=None, role='assistant', tool_calls=[ChoiceDeltaToolCall(index=0, id='call_01e050c62592430f8653d
5', function=ChoiceDeltaToolCallFunction(arguments='{"project_name', name='fill_time_cost'), type='function')], reasoning_content=None)
[INFO][2026-02-02 16:38:13,691][openai_compatible.py:391] [OpenAI]工具调用名称: fill_time_cost
[INFO][2026-02-02 16:38:13,692][openai_compatible.py:395] [OpenAI]工具参数增加: '{"project_name'
[INFO][2026-02-02 16:38:13,715][openai_compatible.py:365] [OpenAI]工具调用chunk: ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id='', function=ChoiceDeltaToolCallFu
nction(arguments='": "智能体', name=None), type='function')], reasoning_content=None)
[INFO][2026-02-02 16:38:13,715][openai_compatible.py:373] [OpenAI]使用最后一个有效工具调用ID: call_01e050c62592430f8653d5
[INFO][2026-02-02 16:38:13,716][openai_compatible.py:395] [OpenAI]工具参数增加: '": "智能体'
[INFO][2026-02-02 16:38:13,762][openai_compatible.py:365] [OpenAI]工具调用chunk: ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id='', function=ChoiceDeltaToolCallFu
nction(arguments='项目", "hours', name=None), type='function')], reasoning_content=None)
[INFO][2026-02-02 16:38:13,769][openai_compatible.py:373] [OpenAI]使用最后一个有效工具调用ID: call_01e050c62592430f8653d5
[INFO][2026-02-02 16:38:13,769][openai_compatible.py:395] [OpenAI]工具参数增加: '项目", "hours'
[INFO][2026-02-02 16:38:13,772][openai_compatible.py:365] [OpenAI]工具调用chunk: ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id='', function=ChoiceDeltaToolCallFu
nction(arguments='": 10', name=None), type='function')], reasoning_content=None)
[INFO][2026-02-02 16:38:13,772][openai_compatible.py:373] [OpenAI]使用最后一个有效工具调用ID: call_01e050c62592430f8653d5
[INFO][2026-02-02 16:38:13,773][openai_compatible.py:395] [OpenAI]工具参数增加: '": 10'
[INFO][2026-02-02 16:38:13,811][openai_compatible.py:365] [OpenAI]工具调用chunk: ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id='', function=ChoiceDeltaToolCallFu
nction(arguments='}', name=None), type='function')])
[INFO][2026-02-02 16:38:13,811][openai_compatible.py:373] [OpenAI]使用最后一个有效工具调用ID: call_01e050c62592430f8653d5
[INFO][2026-02-02 16:38:13,814][openai_compatible.py:395] [OpenAI]工具参数增加: '}'
六、后话
最后,测试是成功了,但是发现的一个问题是我让位于阿里云ECS上的小落同学去访问一个远端的、部署在我自己电脑上的MCP Server,可能是由于我家里网络速度的原因,亦或可能是其他什么原因,总是会特别慢,导致查询MCP Server上任务的运行状态可能会超时,从而拿不到任务运行过程中的步骤列表及每一个步骤的结果。
查询任务状态超时
[INFO][2026-02-02 16:38:14,335][oddmcp_client.py:142] Result: {"success":true,"description":"工时填写任务已提交,正在处理中...","data":{"task_id":"f3ffd5c4-1d34-4919-9eb1-7900837ca0af","project_name":"智能体项目","task_name":"time_cost","user_id":"2","hours":10,"status":"pending"}}
[INFO][2026-02-02 16:38:14,336][openai_compatible.py:442] [OddMCP]工具返回: CallToolResult(content=[TextContent(type='text', text='{"success":true,"description":"工时填写任务已提交,正在处理中...","data":{"task_id":"f3ffd5c4-1d34-4919-9eb1-7900837ca0af","project_name":"智能体项目","task_name":"time_cost","user_id":"2","hours":10,"status":"pending"}}',annotations=None, meta=None)], structured_content={'success': True, 'description': '工时填写任务已提交,正在处理
中...', 'data': {'task_id': 'f3ffd5c4-1d34-4919-9eb1-7900837ca0af', 'project_name': '智能体项目', 'task_name': 'time_cost', 'user_id': '2', 'hours': 10, 'status': 'pending'}}, meta=None, data={'success': True, 'description': '工时填写任务已提交,正在处理中...', 'data': {'task_id': 'f3ffd5c4-1d34-4919-9eb1-7900837ca0af', 'project_name': '智能体项目', 'task_name': 'time_cost', 'user_id': '2', 'hours': 10, 'status': 'pending'}}, is_error=False)
[INFO][2026-02-02 16:38:14,337][openai_compatible.py:476] [OddMCP]获取到任务ID: f3ffd5c4-1d34-4919-9eb1-7900837ca0af,开始轮询任务状态。
[INFO][2026-02-02 16:38:14,349][oddmcp_status_callback.py:23] Starting Redis client with address 47.116.14.194, port 63579, db 0
[ERROR][2026-02-02 16:38:35,396][openai_compatible.py:509] [OddMCP]轮询任务状态失败: Timeout connecting to server
家里用的中国移动送的1G的宽带,这网络咋就这么垃圾呢???
除了网络的问题外,初步想了一下可能的优化方向:将Redis客户端做成一个singleton的实例,不要每次都去重新连接redis数据库。后面有空了我再统一再来改一下吧。
浙公网安备 33010602011771号