Skip to content

MCP server does not support typewriter style streaming return codes #228

@sunbeibei-hub

Description

@sunbeibei-hub

MCP server does not support typewriter style streaming return codes

I want to encapsulate the streaming return of a model, but the MCP tool directly returns all at once, not word by word

server code:

import requests
import json
from fastapi import FastAPI, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from typing import Optional
import asyncio
import aiohttp
import io
from fastapi_mcp import FastApiMCP

# 创建FastAPI应用
app = FastAPI(title="AI Chat API", description="AI聊天接口,支持流式和非流式返回", version="1.0.0")


# 创建 MCP 服务器
mcp = FastApiMCP(
    app,
    name="My API",                        # MCP 名称
    describe_all_responses=True,          # 展示所有响应模型
    describe_full_response_schema=True    # 展示完整 JSON 模式
)

# 挂载 MCP
mcp.mount_sse()

# 请求模型
class ChatRequest(BaseModel):
    msg: str
    sessionId: Optional[str] = ""
    stream: str = "false"  # "true" 或 "false"
    channel: str = "test__1111"

# 响应模型
class ChatResponse(BaseModel):
    success: bool
    data: str
    message: str

# 原始API配置
API_URL = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
HEADERS = {
    'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
    'Content-Type': 'application/json'
}

@app.post("/chat", response_model=ChatResponse, operation_id='chat')
async def chat(request: ChatRequest):
    """
    AI聊天接口
    
    - **msg**: 聊天消息内容
    - **sessionId**: 会话ID(可选)
    - **stream**: 是否流式返回,"true"为流式,"false"为非流式
    - **channel**: 渠道标识
    """
    try:
        # 构建请求数据
        payload = {
            "msg": request.msg,
            "sessionId": request.sessionId,
            "stream": request.stream,
            "channel": request.channel
        }
        
        if request.stream == "true":
            # 流式返回
            return StreamingResponse(
                stream_chat_response(payload),
                media_type="text/event-stream"
            )
        else:
            # 非流式返回
            async with aiohttp.ClientSession() as session:
                async with session.post(
                    API_URL, 
                    headers=HEADERS, 
                    json=payload
                ) as response:
                    if response.status == 200:
                        data = await response.text()
                        return ChatResponse(
                            success=True,
                            data=data,
                            message="请求成功"
                        )
                    else:
                        raise HTTPException(
                            status_code=response.status,
                            detail=f"API请求失败: {response.status}"
                        )
    
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")

async def stream_chat_response(payload: dict):
    """
    流式返回聊天响应
    """
    try:
        async with aiohttp.ClientSession() as session:
            async with session.post(
                API_URL, 
                headers=HEADERS, 
                json=payload
            ) as response:
                if response.status == 200:
                    async for chunk in response.content.iter_chunked(1024):
                        if chunk:
                            yield chunk.decode('utf-8', errors='ignore')
                else:
                    yield f"错误: API请求失败,状态码: {response.status}"
    except Exception as e:
        yield f"错误: {str(e)}"



# 刷新 MCP 服务器
mcp.setup_server()


def print_routes():
    print("=== Registered routes ===")
    for route in app.routes:
        try:
            methods = getattr(route, "methods", None)
            path = getattr(route, "path", None)
            name = getattr(route, "name", None)
            print(f"{methods} {path}  (name={name})")
        except Exception:
            pass
    print("=========================")

print_routes()
if __name__ == "__main__":
    import uvicorn
    uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)

client code:

mcp_client.py

import asyncio
import json
from mcp import ClientSession
from mcp.client.sse import sse_client

SSE_URL = "http://127.0.0.1:8000/sse" # 与服务端路由一致

async def main():
# 1) 建立 SSE 传输的连接(sse_client 返回一对 streams:read, write)
print("连接到 MCP SSE 服务器:", SSE_URL)
async with sse_client(url=SSE_URL) as streams:
# streams 通常是 (read_stream, write_stream) 或能被 ClientSession 解包的流对
async with ClientSession(*streams) as session:
# 2) 初始化 MCP 会话(发送 initialize 等)
await session.initialize()
print("初始化完成")

        # 3) 列出可用工具
        tools = await session.list_tools()
        print("发现工具(示例前三项):")
        try:
            # tools 的格式可能是一个列表或包含 tools 属性,按情况打印
            if hasattr(tools, "tools"):
                tools_list = tools.tools
            else:
                tools_list = tools
            for t in tools_list[:10]:
                # 每个 t 的具体结构可能不同,尝试提取 name / id / description
                name = t.get("name") if isinstance(t, dict) else getattr(t, "name", str(t))
                desc = t.get("description") if isinstance(t, dict) else getattr(t, "description", "")
                print(f" - {name}: {desc}")
        except Exception:
            print("tools raw:", tools)

        # 4) 调用 chat 工具
        params = {
            "msg": "北京到南京的火车票",
            "sessionId": "",
            "stream": "true",
            "channel": "test__1111"
        }
        print("调用 chat 工具,params =", params)
        # call_tool 接口:传入工具名(或 operation_id)和参数 dict
        result = await session.call_tool("chat", params)
        # print("工具返回(raw):", result)

        # 5) 尝试从返回对象中提取常见字段做友好打印
        try:
            # SDK 不同版本返回结构差异,这里尽可能地去兼容几种常见结构
            if hasattr(result, "content"):
                # 常见:result.content 是一个列表,各项可能有 text / structured content
                print("result.content:")
                for item in result.content:
                    # item 可能是对象、字典或简单字符串
                    if hasattr(item, "text"):
                        print(" -", item.text)
                    elif isinstance(item, dict) and "text" in item:
                        print(" -", item["text"])
                    else:
                        print(" -", item)
            elif isinstance(result, dict):
                print(json.dumps(result, ensure_ascii=False, indent=2))
            else:
                print("未识别结构,raw:", result)
        except Exception as e:
            print("解析返回时出错:", e)

if name == "main":
asyncio.run(main())

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions