Error executing tool xyz: 'Context' object has no attribute 'notify' - is there a way of sending a message from server to client?
Description
I am trying to send a message (notification) from mcp server to mcp client. I use Streamable HTTP as transport. My usecase basically is to be able to interact with an application inside a webapp. There I can't run MCP server for obvious reason so I thought I would run there an MCP client connected to my MCP server. I will then have another client (Claude Desktop or in my case fast-agent implementation) from which I will instruct the LLM to do stuff, use my tools from my MCP server and also interact with the webclient. So I thought the webclient would register itself upon loading and then I want to send it some instruction, message in general, from the fast-agent client.
All chatbots recommended me something like storing the Context of the webclient when it's registering and then call context.notify({data}) when I want to send the message to it. That doesn't work, there is no method notify on the context object. So I tried using context.info(data). That sort of work, but the message was sent to the fast-agent client, the one that should act as sender, not receiver.
So my question is simple. Is there a way how to send a message from one client to another?
Example Code
# mcp_server.py
import httpx
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
# from mcp.server.fastmcp import FastMCP, Context
from fastmcp import Context, FastMCP
# Create the MCP server instance
mcp = FastMCP("Document MCP Server")
mcp_app = mcp.streamable_http_app()
# Create a FastAPI app and mount the MCP server
app = FastAPI(lifespan=mcp_app.router.lifespan_context)
# In-memory map: doc_id -> Context
doc_clients = {}
DOC_SERVICE_URL = "http://localhost:9009"
@mcp.tool()
async def create_doc(context: Context) -> str:
async with httpx.AsyncClient() as client:
resp = await client.post(f"{DOC_SERVICE_URL}/doc/create")
resp.raise_for_status()
doc_id = resp.json()["doc_id"]
# doc_clients[doc_id] = context
return doc_id
@mcp.tool()
async def register_viewer(doc_id: str, context: Context) -> str:
doc_clients[doc_id] = context
print(f"[REGISTER] Viewer registered for doc_id={doc_id}")
return f"Viewer registered for document {doc_id}"
@mcp.tool()
async def add_line(context: Context, doc_id: str, new_line: str) -> str:
if doc_id in doc_clients:
print(f"Notifying client for doc_id={doc_id}: {new_line}")
await doc_clients[doc_id].info({
"method":"document.updated",
"params":{"line": new_line}
})
return "Line added."
# Mount the MCP serverβs SSE handler
# app.mount("/", mcp.sse_app())
app.mount("/", mcp_app)
Version Information
The server is started using
uvicorn mcp_server_streamable:app --port 8008
Additional Context
No response
Related to https://github.com/jlowin/fastmcp/discussions/429#discussion-8314598
Can at least someone tell me, what's the difference between these two lines? from mcp.server.fastmcp import FastMCP, Context from fastmcp import Context, FastMCP
I assume the first one is importing from the official MCP library (https://github.com/modelcontextprotocol/python-sdk) while the second one is actually from this repo, is that correct? And what is the difference in the implementation, I understood that FastMCP 1.0 was basically identical but the version 2.0+ should be only here, on this repo? Or am I completely wrong here? π
You're right about the imports; they come from two different libraries.
@jlowin might be able to answer your second question.
And what is the difference in the implementation, I understood that FastMCP 1.0 was basically identical but the version 2.0+ should be only here, on this repo? Or am I completely wrong here? π
on this, I'd read this