python開(kāi)發(fā)Streamable?HTTP?MCP應(yīng)用小結(jié)
一、概述
使用python開(kāi)發(fā),最好的框架是fastmcp,github連接:https://github.com/jlowin/fastmcp
2025 年 5 月 9 日,fastmcp發(fā)布v2.3.0版本,正式支持Streamable HTTP
終于等到官方支持了!
注意:2.3.0版本有bug,目前最新版本已經(jīng)修復(fù)了
升級(jí)到最新版本
pip install --upgrade fastmcp
關(guān)于Streamable HTTP的介紹,請(qǐng)參考鏈接:http://www.dbjr.com.cn/javascript/340794xx1.htm
這里就不再重復(fù)了
二、Streamable HTTP MCP應(yīng)用官方demo
server.py
from fastmcp import FastMCP mcp = FastMCP("Demo ??") @mcp.tool() def add(a: int, b: int) -> int: """Add two numbers""" return a + b if __name__ == "__main__": mcp.run(transport="streamable-http", host="0.0.0.0", port=8000, path="/mcp")
通過(guò)以上8行代碼,就簡(jiǎn)單實(shí)現(xiàn)了Streamable HTTP MCP應(yīng)用。
獲取服務(wù)器公網(wǎng)ip
通過(guò)一個(gè)在實(shí)際生產(chǎn)環(huán)境中,使用的功能,來(lái)演示一個(gè)Streamable HTTP MCP應(yīng)用
server.py
import json import requests from fastmcp import FastMCP mcp = FastMCP("Demo ??") @mcp.tool() def get_public_ip_address() -> str: """ 獲取服務(wù)器公網(wǎng) IP 地址 返回: str: 當(dāng)前網(wǎng)絡(luò)的公網(wǎng) IP 地址 """ try: response = requests.get("http://ip-api.com/json") response.raise_for_status() # 檢查 HTTP 請(qǐng)求是否成功 content = json.loads(response.text) return content.get("query", "Unknown IP") # 提供默認(rèn)值以防字段缺失 except requests.RequestException as e: print(f"請(qǐng)求錯(cuò)誤: {e}") return "Request Failed" except json.JSONDecodeError as e: print(f"JSON 解碼錯(cuò)誤: {e}") return "Invalid Response" if __name__ == "__main__": # mcp.run() mcp.run(transport="streamable-http", host="0.0.0.0", port=9000, path="/mcp")
運(yùn)行代碼
python server.py
輸出:
[05/12/25 10:03:55] INFO Starting server "public_ip_address"... server.py:202
INFO: Started server process [43312]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:9000 (Press CTRL+C to quit)
請(qǐng)確保Cherry Studio版本是最新的,因?yàn)樾掳姹?,增加了Streamable HTTP支持
添加mcp服務(wù)器
名稱:public_ip_address_mcp
類型:Streamable HTTP
url:http://localhost:9000/mcp
添加成功后,點(diǎn)擊工具,可以看到工具方法
創(chuàng)建
添加
選擇助手
選擇
提問(wèn)公網(wǎng)ip,效果如下:
驗(yàn)證一下公網(wǎng)ip是否正確,打開(kāi)網(wǎng)頁(yè)
結(jié)果是正確的,沒(méi)問(wèn)題。
三、SSE轉(zhuǎn)換為Streamable HTTP代碼改造
在前面的文章中,寫的mysql應(yīng)用是SSE模式,鏈接:http://www.dbjr.com.cn/server/321891t37.htm
把代碼拷貝過(guò)來(lái),只需要修改2行代碼,就可以無(wú)縫轉(zhuǎn)換為Streamable HTTP
將以下2行
mcp = FastMCP("operateMysql", host="0.0.0.0", port=9000) mcp.run(transport="sse")
修改為:
mcp = FastMCP("operateMysql") mcp.run(transport="streamable-http", host="0.0.0.0", port=9000, path="/mcp")
mysql_mcp_server_pro應(yīng)用,完整代碼如下:
server.py
from fastmcp import FastMCP from mysql.connector import connect, Error import os mcp = FastMCP("operateMysql") def get_db_config(): """從環(huán)境變量獲取數(shù)據(jù)庫(kù)配置信息 返回: dict: 包含數(shù)據(jù)庫(kù)連接所需的配置信息 - host: 數(shù)據(jù)庫(kù)主機(jī)地址 - port: 數(shù)據(jù)庫(kù)端口 - user: 數(shù)據(jù)庫(kù)用戶名 - password: 數(shù)據(jù)庫(kù)密碼 - database: 數(shù)據(jù)庫(kù)名稱 異常: ValueError: 當(dāng)必需的配置信息缺失時(shí)拋出 """ config = { "host": os.getenv("MYSQL_HOST", "localhost"), "port": int(os.getenv("MYSQL_PORT", "3306")), "user": os.getenv("MYSQL_USER"), "password": os.getenv("MYSQL_PASSWORD"), "database": os.getenv("MYSQL_DATABASE"), } print(config) if not all( [ config["host"], config["port"], config["user"], config["password"], config["database"], ] ): raise ValueError("缺少必需的數(shù)據(jù)庫(kù)配置") return config @mcp.tool() def execute_sql(query: str) -> list: """執(zhí)行SQL查詢語(yǔ)句 參數(shù): query (str): 要執(zhí)行的SQL語(yǔ)句,支持多條語(yǔ)句以分號(hào)分隔 返回: list: 包含查詢結(jié)果的TextContent列表 - 對(duì)于SELECT查詢:返回CSV格式的結(jié)果,包含列名和數(shù)據(jù) - 對(duì)于SHOW TABLES:返回?cái)?shù)據(jù)庫(kù)中的所有表名 - 對(duì)于其他查詢:返回執(zhí)行狀態(tài)和影響行數(shù) - 多條語(yǔ)句的結(jié)果以"---"分隔 異常: Error: 當(dāng)數(shù)據(jù)庫(kù)連接或查詢執(zhí)行失敗時(shí)拋出 """ config = get_db_config() try: with connect(**config) as conn: with conn.cursor() as cursor: statements = [stmt.strip() for stmt in query.split(";") if stmt.strip()] results = [] for statement in statements: try: cursor.execute(statement) # 檢查語(yǔ)句是否返回了結(jié)果集 (SELECT, SHOW, EXPLAIN, etc.) if cursor.description: columns = [desc[0] for desc in cursor.description] rows = cursor.fetchall() # 將每一行的數(shù)據(jù)轉(zhuǎn)換為字符串,特殊處理None值 formatted_rows = [] for row in rows: formatted_row = [ "NULL" if value is None else str(value) for value in row ] formatted_rows.append(",".join(formatted_row)) # 將列名和數(shù)據(jù)合并為CSV格式 results.append( "\n".join([",".join(columns)] + formatted_rows) ) # 如果語(yǔ)句沒(méi)有返回結(jié)果集 (INSERT, UPDATE, DELETE, etc.) else: conn.commit() # 只有在非查詢語(yǔ)句時(shí)才提交 results.append(f"查詢執(zhí)行成功。影響行數(shù): {cursor.rowcount}") except Error as stmt_error: # 單條語(yǔ)句執(zhí)行出錯(cuò)時(shí),記錄錯(cuò)誤并繼續(xù)執(zhí)行 results.append( f"執(zhí)行語(yǔ)句 '{statement}' 出錯(cuò): {str(stmt_error)}" ) # 可以在這里選擇是否繼續(xù)執(zhí)行后續(xù)語(yǔ)句,目前是繼續(xù) return ["\n---\n".join(results)] except Error as e: print(f"執(zhí)行SQL '{query}' 時(shí)出錯(cuò): {e}") return [f"執(zhí)行查詢時(shí)出錯(cuò): {str(e)}"] @mcp.tool() def get_table_name(text: str) -> list: """根據(jù)表的中文注釋搜索數(shù)據(jù)庫(kù)中的表名 參數(shù): text (str): 要搜索的表中文注釋關(guān)鍵詞 返回: list: 包含查詢結(jié)果的TextContent列表 - 返回匹配的表名、數(shù)據(jù)庫(kù)名和表注釋信息 - 結(jié)果以CSV格式返回,包含列名和數(shù)據(jù) """ config = get_db_config() sql = "SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_COMMENT " sql += f"FROM information_schema.TABLES WHERE TABLE_SCHEMA = '{config['database']}' AND TABLE_COMMENT LIKE '%{text}%';" return execute_sql(sql) @mcp.tool() def get_table_desc(text: str) -> list: """獲取指定表的字段結(jié)構(gòu)信息 參數(shù): text (str): 要查詢的表名,多個(gè)表名以逗號(hào)分隔 返回: list: 包含查詢結(jié)果的列表 - 返回表的字段名、字段注釋等信息 - 結(jié)果按表名和字段順序排序 - 結(jié)果以CSV格式返回,包含列名和數(shù)據(jù) """ config = get_db_config() # 將輸入的表名按逗號(hào)分割成列表 table_names = [name.strip() for name in text.split(",")] # 構(gòu)建IN條件 table_condition = "','".join(table_names) sql = "SELECT TABLE_NAME, COLUMN_NAME, COLUMN_COMMENT " sql += ( f"FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '{config['database']}' " ) sql += f"AND TABLE_NAME IN ('{table_condition}') ORDER BY TABLE_NAME, ORDINAL_POSITION;" return execute_sql(sql) @mcp.tool() def get_lock_tables() -> list: """ 獲取當(dāng)前mysql服務(wù)器InnoDB 的行級(jí)鎖 返回: list: 包含查詢結(jié)果的TextContent列表 """ sql = """SELECT p2.`HOST` AS 被阻塞方host, p2.`USER` AS 被阻塞方用戶, r.trx_id AS 被阻塞方事務(wù)id, r.trx_mysql_thread_id AS 被阻塞方線程號(hào), TIMESTAMPDIFF(SECOND, r.trx_wait_started, CURRENT_TIMESTAMP) AS 等待時(shí)間, r.trx_query AS 被阻塞的查詢, l.OBJECT_NAME AS 阻塞方鎖住的表, m.LOCK_MODE AS 被阻塞方的鎖模式, m.LOCK_TYPE AS '被阻塞方的鎖類型(表鎖還是行鎖)', m.INDEX_NAME AS 被阻塞方鎖住的索引, m.OBJECT_SCHEMA AS 被阻塞方鎖對(duì)象的數(shù)據(jù)庫(kù)名, m.OBJECT_NAME AS 被阻塞方鎖對(duì)象的表名, m.LOCK_DATA AS 被阻塞方事務(wù)鎖定記錄的主鍵值, p.`HOST` AS 阻塞方主機(jī), p.`USER` AS 阻塞方用戶, b.trx_id AS 阻塞方事務(wù)id, b.trx_mysql_thread_id AS 阻塞方線程號(hào), b.trx_query AS 阻塞方查詢, l.LOCK_MODE AS 阻塞方的鎖模式, l.LOCK_TYPE AS '阻塞方的鎖類型(表鎖還是行鎖)', l.INDEX_NAME AS 阻塞方鎖住的索引, l.OBJECT_SCHEMA AS 阻塞方鎖對(duì)象的數(shù)據(jù)庫(kù)名, l.OBJECT_NAME AS 阻塞方鎖對(duì)象的表名, l.LOCK_DATA AS 阻塞方事務(wù)鎖定記錄的主鍵值, IF(p.COMMAND = 'Sleep', CONCAT(p.TIME, ' 秒'), 0) AS 阻塞方事務(wù)空閑的時(shí)間 FROM performance_schema.data_lock_waits w INNER JOIN performance_schema.data_locks l ON w.BLOCKING_ENGINE_LOCK_ID = l.ENGINE_LOCK_ID INNER JOIN performance_schema.data_locks m ON w.REQUESTING_ENGINE_LOCK_ID = m.ENGINE_LOCK_ID INNER JOIN information_schema.INNODB_TRX b ON b.trx_id = w.BLOCKING_ENGINE_TRANSACTION_ID INNER JOIN information_schema.INNODB_TRX r ON r.trx_id = w.REQUESTING_ENGINE_TRANSACTION_ID INNER JOIN information_schema.PROCESSLIST p ON p.ID = b.trx_mysql_thread_id INNER JOIN information_schema.PROCESSLIST p2 ON p2.ID = r.trx_mysql_thread_id ORDER BY 等待時(shí)間 DESC;""" return execute_sql(sql) if __name__ == "__main__": mcp.run(transport="streamable-http", host="0.0.0.0", port=9000, path="/mcp")
修改Dockerfile,升級(jí)fastmcp
FROM python:3.13.3-alpine3.21 ADD . /app RUN pip3 install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple && \ pip3 install mysql-connector-python fastmcp -i https://pypi.tuna.tsinghua.edu.cn/simple && \ pip3 install --upgrade fastmcp WORKDIR /app EXPOSE 9000 ENTRYPOINT ["python3","/app/server.py"]
編譯鏡像
docker build -t mysql_mcp_server_pro:v1 .
使用docker-compose啟動(dòng)
docker-compose.yaml
services: mysql_mcp_server_pro: image: mysql_mcp_server_pro:v1 container_name: mysql_mcp_server_pro ports: - "9090:9000" environment: MYSQL_HOST: "192.168.20.128" MYSQL_PORT: "3306" MYSQL_USER: "root" MYSQL_PASSWORD: "abcd@1234" MYSQL_DATABASE: "test" TZ: Asia/Shanghai restart: always
注意修改mysql相關(guān)環(huán)境變量
運(yùn)行
docker-compose up -d
Cherry Studio測(cè)試
添加MCP服務(wù)器
添加智能體mysql8
提示詞和以前一樣
使用中文回復(fù)。 當(dāng)用戶提問(wèn)中涉及學(xué)生、教師、成績(jī)、班級(jí)、課程等實(shí)體時(shí),需要使用 MySQL MCP 進(jìn)行數(shù)據(jù)查詢和操作,表結(jié)構(gòu)說(shuō)明如下: # 學(xué)生管理系統(tǒng)數(shù)據(jù)庫(kù)表結(jié)構(gòu)說(shuō)明 ## 1. 教師表 (teachers) | 字段名 | 類型 | 描述 | 約束 | 示例 | |--------|------|------|------|------| | id | varchar | 教師ID | 主鍵 | "T001" | | name | varchar | 教師姓名 | 必填 | "張建國(guó)" | | gender | enum | 性別 | "男"或"女" | "男" | | subject | varchar | 教授科目 | 必填 | "數(shù)學(xué)" | | title | varchar | 職稱 | 必填 | "教授" | | phone | varchar | 聯(lián)系電話 | 必填 | "13812345678" | | office | varchar | 辦公室位置 | 必填 | "博學(xué)樓301" | | wechat | varchar | 微信(可選) | 可選 | "lily_teacher" | | isHeadTeacher | enum | 是否為班主任,"true"或"false" | 可選 | true | ## 2. 班級(jí)表 (classes) | 字段名 | 類型 | 描述 | 約束 | 示例 | |--------|------|------|------|------| | id | varchar | 班級(jí)ID | 主鍵 | "202301" | | className | varchar | 班級(jí)名稱 | 必填 | "2023級(jí)計(jì)算機(jī)1班" | | grade | int | 年級(jí) | 必填 | 2023 | | headTeacherId | varchar | 班主任ID | 外鍵(teachers.id) | "T003" | | classroom | varchar | 教室位置 | 必填 | "1號(hào)樓302" | | studentCount | int | 學(xué)生人數(shù) | 必填 | 35 | | remark | varchar | 備注信息 | 可選 | "市級(jí)優(yōu)秀班集體" | ## 3. 課程表 (courses) | 字段名 | 類型 | 描述 | 約束 | 示例 | |--------|------|------|------|------| | id | varchar | 課程ID | 主鍵 | "C001" | | courseName | varchar | 課程名稱 | 必填 | "高等數(shù)學(xué)" | | credit | int | 學(xué)分 | 必填 | 4 | | teacherId | varchar | 授課教師ID | 外鍵(teachers.id) | "T001" | | semester | varchar | 學(xué)期 | 格式"YYYY-N" | "2023-1" | | type | enum | 課程類型 | "必修"或"選修" | "必修" | | prerequisite | varchar | 先修課程ID | 可選,外鍵(courses.id) | "C003" | ## 4. 學(xué)生表 (students) | 字段名 | 類型 | 描述 | 約束 | 示例 | |--------|------|------|------|------| | id | varchar | 學(xué)號(hào) | 主鍵 | "S20230101" | | name | varchar | 學(xué)生姓名 | 必填 | "王強(qiáng)" | | gender | enum | 性別 | "男"或"女" | "男" | | birthDate | date | 出生日期 | 必填 | date("2005-01-15") | | enrollmentDate | date | 入學(xué)日期 | 必填 | date("2023-8-1") | | classId | varchar | 班級(jí)ID | 外鍵(classes.id) | "202301" | | phone | varchar | 聯(lián)系電話 | 必填 | "13812345678" | | email | varchar | 電子郵箱 | 必填 | "20230101@school.edu.cn" | | emergencyContact | varchar | 緊急聯(lián)系人電話 | 必填 | "13876543210" | | address | varchar | 家庭住址 | 必填 | "北京市海淀區(qū)中關(guān)村大街1棟101室" | | height | int | 身高(cm) | 必填 | 175 | | weight | int | 體重(kg) | 必填 | 65 | | healthStatus | enum | 健康狀況 | 必填,"良好"或"一般?"或"較差" | "良好" | ## 5. 成績(jī)表 (scores) | 字段名 | 類型 | 描述 | 約束 | 示例 | |--------|------|------|------|------| | id | varchar | 成績(jī)記錄ID | 主鍵 | "S20230101C001" | | studentId | varchar | 學(xué)生ID | 外鍵(students.id) | "S20230101" | | courseId | varchar | 課程ID | 外鍵(courses.id) | "C001" | | score | int | 綜合成績(jī) | 0-100 | 85 | | examDate | date | 考試日期 | 必填 | date("2024-5-20") | | usualScore | int | 平時(shí)成績(jī) | 0-100 | 90 | | finalScore | int | 期末成績(jī) | 0-100 | 80 | ### 補(bǔ)考成績(jī)記錄說(shuō)明 補(bǔ)考記錄在_id后添加"_M"后綴,如"S20230101C001_M" ## 表關(guān)系說(shuō)明 1. **一對(duì)多關(guān)系**: - 一個(gè)班級(jí)(classes)對(duì)應(yīng)多個(gè)學(xué)生(students) - 一個(gè)教師(teachers)可以教授多門課程(courses) - 一個(gè)學(xué)生(students)有多條成績(jī)記錄(scores) 2. **外鍵約束**: - students.classId → classes.id - courses.teacherId → teachers.id - scores.studentId → students.id - scores.courseId → courses.id - classes.headTeacherId → teachers.id
mysql表結(jié)構(gòu),參考文章:http://www.dbjr.com.cn/database/3415585jf.htm
將智能體,添加助手
打開(kāi)助手,選擇MCP
提一個(gè)問(wèn)題,李華的老師是誰(shuí)
效果如下:
到此這篇關(guān)于python開(kāi)發(fā)Streamable HTTP MCP應(yīng)用的文章就介紹到這了,更多相關(guān)python開(kāi)發(fā)Streamable HTTP MCP應(yīng)用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于OpenCV實(shí)現(xiàn)小型的圖像數(shù)據(jù)庫(kù)檢索功能
下面就使用VLAD表示圖像,實(shí)現(xiàn)一個(gè)小型的圖像數(shù)據(jù)庫(kù)的檢索程序。下面實(shí)現(xiàn)需要的功能模塊,分步驟給大家介紹的非常詳細(xì),對(duì)OpenCV圖像數(shù)據(jù)庫(kù)檢索功能感興趣的朋友跟隨小編一起看看吧2021-12-12如何使用Python實(shí)現(xiàn)一個(gè)簡(jiǎn)易的ORM模型
ORM(Object Relational Mapping)是一種程序設(shè)計(jì)技術(shù),用于實(shí)現(xiàn)面向?qū)ο缶幊陶Z(yǔ)言里不同類型系統(tǒng)的數(shù)據(jù)之間的轉(zhuǎn)換。本文將介紹如何使用Python實(shí)現(xiàn)一個(gè)簡(jiǎn)易的ORM2021-05-05PyQt 5 設(shè)置Logo圖標(biāo)和Title標(biāo)題的操作
這篇文章主要介紹了PyQt 5 設(shè)置Logo圖標(biāo)和Title標(biāo)題的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-03-03Python小白學(xué)習(xí)爬蟲(chóng)常用請(qǐng)求報(bào)頭
在本篇文章里小編給大家整理了關(guān)于Python小白學(xué)習(xí)爬蟲(chóng)常用請(qǐng)求報(bào)頭的相關(guān)知識(shí)點(diǎn),需要的朋友們可以學(xué)習(xí)下。2020-06-06freeswitch開(kāi)源通信 python模塊介紹
freeswitch支持多種語(yǔ)言的業(yè)務(wù)開(kāi)發(fā),包括C/C++,java,python,js,lua,Golang等等。freeswitch在使用python做業(yè)務(wù)開(kāi)發(fā)時(shí),有倆種接入方式,一種是ESL接口,另一種是mod_python模塊。本文主要介紹的是fs內(nèi)部的mod_python語(yǔ)言支持模塊,需要的朋友可以參考下面文章內(nèi)容2021-09-09將本地Python項(xiàng)目打包成docker鏡像上傳到服務(wù)器并在docker中運(yùn)行
Docker是一個(gè)開(kāi)源項(xiàng)目,為開(kāi)發(fā)人員和系統(tǒng)管理員提供了一個(gè)開(kāi)放平臺(tái),可以將應(yīng)用程序構(gòu)建、打包為一個(gè)輕量級(jí)容器,并在任何地方運(yùn)行,這篇文章主要給大家介紹了關(guān)于將本地Python項(xiàng)目打包成docker鏡像上傳到服務(wù)器并在docker中運(yùn)行的相關(guān)資料,需要的朋友可以參考下2023-12-12上帝為你開(kāi)了一扇窗之Tkinter常用函數(shù)詳解
構(gòu)思了很長(zhǎng)一段時(shí)間,總感覺(jué)不夠有趣,于是打算出一個(gè)完整的系列,讓大家一起感受python的樂(lè)趣.這個(gè)系列著重以系統(tǒng)庫(kù)中的tkinter為中心來(lái)圍繞進(jìn)行編寫.因此我們的第一步是導(dǎo)入模塊, 第一節(jié)就來(lái)為大家建立一個(gè)窗口 ,需要的朋友可以參考下2021-06-06