SSE(Server Sent Events)是一種伺服器到客戶端的串流標準,允許伺服器透過 HTTP 推送即時更新給客戶端。這對於需要即時更新的應用程式特別有用,例如聊天應用、通知或即時資料串流。此外,您的伺服器可以同時被多個客戶端使用,因為它可以部署在雲端等任何地方。
本課程將介紹如何建立與使用 SSE 伺服器。
完成本課程後,您將能夠:
- 建立 SSE 伺服器。
- 使用 Inspector 偵錯 SSE 伺服器。
- 使用 Visual Studio Code 使用 SSE 伺服器。
SSE 是兩種支援的傳輸類型之一。您之前的課程已經看過第一種 stdio 的使用方式。兩者的差異如下:
- SSE 需要您處理兩件事:連線與訊息。
- 由於這是可以部署在任何地方的伺服器,您需要在使用 Inspector 和 Visual Studio Code 等工具時反映這點。這表示您不再是指定如何啟動伺服器,而是指定可建立連線的端點。以下是範例程式碼:
app.get("/sse", async (_: Request, res: Response) => {
const transport = new SSEServerTransport('/messages', res);
transports[transport.sessionId] = transport;
res.on("close", () => {
delete transports[transport.sessionId];
});
await server.connect(transport);
});
app.post("/messages", async (req: Request, res: Response) => {
const sessionId = req.query.sessionId as string;
const transport = transports[sessionId];
if (transport) {
await transport.handlePostMessage(req, res);
} else {
res.status(400).send('No transport found for sessionId');
}
});在上述程式碼中:
/sse被設定為路由。當有請求到此路由時,會建立一個新的傳輸實例,伺服器會透過此傳輸 連線。/messages是處理傳入訊息的路由。
mcp = FastMCP("My App")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
# Mount the SSE server to the existing ASGI server
app = Starlette(
routes=[
Mount('/', app=mcp.sse_app()),
]
)在上述程式碼中,我們:
-
建立一個 ASGI 伺服器實例(特別使用 Starlette),並掛載預設路由
/背後的運作是將
/sse和/messages路由分別設定來處理連線與訊息。其餘應用程式功能,如加入工具等,則與 stdio 伺服器相同。
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddMcpServer()
.WithTools<Tools>();
builder.Services.AddHttpClient();
var app = builder.Build();
app.MapMcp();
```
有兩個方法幫助我們從一般的網頁伺服器轉成支援 SSE 的網頁伺服器:
- `AddMcpServer`,此方法新增相關功能。
- `MapMcp`,此方法新增像是 `/SSE` 和 `/messages` 的路由。Now that we know a little bit more about SSE, let's build an SSE server next.
To create our server, we need to keep two things in mind:
- We need to use a web server to expose endpoints for connection and messages.
- Build our server like we normally do with tools, resources and prompts when we were using stdio.
To create our server, we use the same types as with stdio. However, for the transport, we need to choose SSE.
import { Request, Response } from "express";
import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
const server = new McpServer({
name: "example-server",
version: "1.0.0"
});
const app = express();
const transports: {[sessionId: string]: SSEServerTransport} = {};In the preceding code we've:
- Created a server instance.
- Defined an app using the web framework express.
- Created a transports variable that we will use to store incoming connections.
from starlette.applications import Starlette
from starlette.routing import Mount, Host
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("My App")In the preceding code we've:
- Imported the libraries we're going to need with Starlette (an ASGI framework) being pulled in.
- Created an MCP server instance
mcp.
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddMcpServer();
builder.Services.AddHttpClient();
var app = builder.Build();
// TODO: add routes At this point, we've:
- Created a web app
- Added support for MCP features through
AddMcpServer.
Let's add the needed routes next.
Let's add routes next that handle the connection and incoming messages:
app.get("/sse", async (_: Request, res: Response) => {
const transport = new SSEServerTransport('/messages', res);
transports[transport.sessionId] = transport;
res.on("close", () => {
delete transports[transport.sessionId];
});
await server.connect(transport);
});
app.post("/messages", async (req: Request, res: Response) => {
const sessionId = req.query.sessionId as string;
const transport = transports[sessionId];
if (transport) {
await transport.handlePostMessage(req, res);
} else {
res.status(400).send('No transport found for sessionId');
}
});
app.listen(3001);In the preceding code we've defined:
- An
/sseroute that instantiates a transport of type SSE and ends up callingconnecton the MCP server. - A
/messagesroute that takes care of incoming messages.
app = Starlette(
routes=[
Mount('/', app=mcp.sse_app()),
]
)In the preceding code we've:
- Created an ASGI app instance using the Starlette framework. As part of that we passes
mcp.sse_app()to it's list of routes. That ends up mounting an/sseand/messagesroute on the app instance.
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddMcpServer();
builder.Services.AddHttpClient();
var app = builder.Build();
app.MapMcp();We've added one line of code at the end add.MapMcp() this means we now have routes /SSE and /messages.
Let's add capabilties to the server next.
Now that we've got everything SSE specific defined, let's add server capabilities like tools, prompts and resources.
server.tool("random-joke", "A joke returned by the chuck norris api", {},
async () => {
const response = await fetch("https://api.chucknorris.io/jokes/random");
const data = await response.json();
return {
content: [
{
type: "text",
text: data.value
}
]
};
}
);Here's how you can add a tool for example. This specific tool creates a tool call "random-joke" that calls a Chuck Norris API and returns a JSON response.
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + bNow your server has one tool.
// server-sse.ts
import { Request, Response } from "express";
import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
// Create an MCP server
const server = new McpServer({
name: "example-server",
version: "1.0.0",
});
const app = express();
const transports: { [sessionId: string]: SSEServerTransport } = {};
app.get("/sse", async (_: Request, res: Response) => {
const transport = new SSEServerTransport("/messages", res);
transports[transport.sessionId] = transport;
res.on("close", () => {
delete transports[transport.sessionId];
});
await server.connect(transport);
});
app.post("/messages", async (req: Request, res: Response) => {
const sessionId = req.query.sessionId as string;
const transport = transports[sessionId];
if (transport) {
await transport.handlePostMessage(req, res);
} else {
res.status(400).send("No transport found for sessionId");
}
});
server.tool("random-joke", "A joke returned by the chuck norris api", {}, async () => {
const response = await fetch("https://api.chucknorris.io/jokes/random");
const data = await response.json();
return {
content: [
{
type: "text",
text: data.value,
},
],
};
});
app.listen(3001);from starlette.applications import Starlette
from starlette.routing import Mount, Host
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("My App")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
# Mount the SSE server to the existing ASGI server
app = Starlette(
routes=[
Mount('/', app=mcp.sse_app()),
]
)- Let's create some tools first, for this we will create a file Tools.cs with the following content:
using System.ComponentModel;
using System.Text.Json;
using ModelContextProtocol.Server;
namespace server;
[McpServerToolType]
public sealed class Tools
{
public Tools()
{
}
[McpServerTool, Description("Add two numbers together.")]
public async Task<string> AddNumbers(
[Description("The first number")] int a,
[Description("The second number")] int b)
{
return (a + b).ToString();
}
}Here we've added the following:
- Created a class
Toolswith the decoratorMcpServerToolType. - Defined a tool
AddNumbersby decorating the method withMcpServerTool. We've also provided parameters and an implementation.
- Let's leverage the
Toolsclass we just created:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddMcpServer()
.WithTools<Tools>();
builder.Services.AddHttpClient();
var app = builder.Build();
app.MapMcp();We've added a call to WithTools that specifies Tools as the class containing the tools. That's it, we're ready.
Great, we have a server using SSE, let's take it for a spin next.
Inspector is a great tool that we saw in a previous lesson Creating your first server. Let's see if we can use the Inspector even here:
To run the inspector, you first must have an SSE server running, so let's do that next:
-
Run the server
tsx && node ./build/server-sse.tsuvicorn server:app
Note how we use the executable
uvicornthat's installed when we typedpip install "mcp[cli]". Typingserver:appmeans we're trying to run a fileserver.pyand for it to have a Starlette instance calledapp.dotnet run
This should start the server. To interface with it you need a new terminal.
-
Run the inspector
![NOTE] Run this in a separate terminal window than the server is running in. Also note, you need to adjust the below command to fit the URL where your server runs.
npx @modelcontextprotocol/inspector --cli http://localhost:8000/sse --method tools/list
執行 inspector 在所有執行環境中看起來都相同。請注意,我們不是傳入伺服器的啟動路徑和指令,而是傳入伺服器運行的 URL,並且指定
/sse路由。
在下拉選單中選擇 SSE,並填入伺服器運行的 URL,例如 http:localhost:4321/sse。接著點擊「Connect」按鈕。和之前一樣,選擇列出工具、選擇工具並提供輸入值。您應該會看到如下結果:
太好了,您已經能使用 inspector,接下來讓我們看看如何使用 Visual Studio Code。
嘗試為您的伺服器增加更多功能。參考此頁面來新增一個呼叫 API 的工具。伺服器的樣貌由您決定。祝您玩得愉快 :)
解答 這裡有一個可行的解答範例,包含可運作的程式碼。
本章節的重點如下:
- SSE 是繼 stdio 之後第二種支援的傳輸方式。
- 支援 SSE 需要使用網頁框架管理傳入的連線與訊息。
- 您可以使用 Inspector 和 Visual Studio Code 來使用 SSE 伺服器,就像 stdio 伺服器一樣。請注意 stdio 與 SSE 之間有些微差異。SSE 需要您先啟動伺服器,然後再執行 inspector 工具。使用 inspector 時,也需要指定 URL。
免責聲明:
本文件係使用 AI 翻譯服務 Co-op Translator 進行翻譯。雖然我們致力於確保準確性,但請注意,自動翻譯可能包含錯誤或不準確之處。原始文件的母語版本應視為權威來源。對於重要資訊,建議採用專業人工翻譯。我們不對因使用本翻譯而產生的任何誤解或誤釋負責。
