- Notifications
You must be signed in to change notification settings - Fork 2.9k
Description
Is your feature request related to a problem? Please describe.
I have an MCP server that sits in front of my API to allow LLMs to interact with it. My API requires an authorization header.
I have hacked a way to do this in my fork, but essentially the MCP client is able to pass through headers. Only need to use this for the /sse request. Currently, the handlers extract the arguments they need in the decorator. We could instead add a field to the base Request class called raw_request or headers if we just need that and then ensure this is added to the request object before passing it to the handler.
Describe the solution you'd like
# src/mcp/types.pyclassRequest(BaseModel, Generic[RequestParamsT, MethodT]): """Base class for JSON-RPC requests."""method: MethodTparams: RequestParamsTheaders: dict[str, Any] |None=None# <<<<<<<<model_config=ConfigDict(extra="allow") ---------------------# src/mcp/server/fastmcp/server.pydefcall_tool(self): defdecorator( func: Callable[ ..., Awaitable[ Sequence[ types.TextContent|types.ImageContent|types.EmbeddedResource ] ], ], ): logger.debug("Registering handler for CallToolRequest") asyncdefhandler(req: types.CallToolRequest): try: results=awaitfunc(req) # <<<<<<<<<returntypes.ServerResult( types.CallToolResult(content=list(results), isError=False) ) exceptExceptionase: returntypes.ServerResult( types.CallToolResult( content=[types.TextContent(type="text", text=str(e))], isError=True, ) ) self.request_handlers[types.CallToolRequest] =handlerreturnfuncreturndecorator-----------------------------# src/mcp/server/fastmcp/server.pyasyncdefrun_sse_async(self, middleware: list[type] = []) ->None: """Run the server using SSE transport."""fromstarlette.applicationsimportStarlettefromstarlette.routingimportMount, Routesse=SseServerTransport("/messages/") asyncdefhandle_sse(request): asyncwithsse.connect_sse( request.scope, request.receive, request._send ) asstreams: awaitself._mcp_server.run( streams[0], streams[1], self._mcp_server.create_initialization_options(), raw_request=request, # <<<<<<<<<<<<<< ) starlette_app=Starlette( debug=self.settings.debug, routes=[ Route("/sse", endpoint=handle_sse), Mount("/messages/", app=sse.handle_post_message), ], ) config=uvicorn.Config( starlette_app, host=self.settings.host, port=self.settings.port, log_level=self.settings.log_level.lower(), ) server=uvicorn.Server(config) awaitserver.serve() ---------------------------# src/mcp/server/lowlevel/server.pyasyncdefrun( self, read_stream: MemoryObjectReceiveStream[types.JSONRPCMessage|Exception], write_stream: MemoryObjectSendStream[types.JSONRPCMessage], initialization_options: InitializationOptions, raw_request: Any|None=None, # <<<<<<<<<<<<<<<<<<<<<<<<<# When False, exceptions are returned as messages to the client.# When True, exceptions are raised, which will cause the server to shut down# but also make tracing exceptions much easier during testing and when using# in-process servers.raise_exceptions: bool=False, ): withwarnings.catch_warnings(record=True) asw: asyncwithServerSession( read_stream, write_stream, initialization_options ) assession: asyncformessageinsession.incoming_messages: logger.debug(f"Received message: {message}") matchmessage: case ( RequestResponder( request=types.ClientRequest(root=req) ) asresponder ): withresponder: ifraw_requestisnotNone: req.headers=raw_request.headers# <<<<<<<<<<<<<<<<awaitself._handle_request( message, req, session, raise_exceptions ) casetypes.ClientNotification(root=notify): awaitself._handle_notification(notify) forwarninginw: logger.info( f"Warning: {warning.category.__name__}: {warning.message}" )and then use this like:
# already supported on clienttransport=awaitexit_stack.enter_async_context( sse_client(url, headers={"authorization": "..."}) )# on servermcp_server=FastMCP("example", transport="sse") asyncdefhandle_call_tool( self: FastMCP, req: types.CallToolRequest# <<<<<<<<<<<<<<<<<< ) ->Sequence[types.TextContent|types.ImageContent|types.EmbeddedResource]: headers={} if"authorization"inreq.headers: headers={"Authorization": req.headers["authorization"]} # ...http client call to api or if MCP is served from the app itself, check the keyI know auth is a part of the 2025 H1 roadmap so this may be usurped already in terms of how things will be supported. This goes beyond auth headers though since it could be useful to have access to the raw request in total instead within the tool execution context.