把你的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数据库。后面有空了我再统一再来改一下吧。

posted @ 2026-02-02 18:22  程序员老奥  阅读(9)  评论(0)    收藏  举报