Skip to content

Added support for raw request injection in RequestContext. #380

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

ylassoued
Copy link

@ylassoued ylassoued commented Mar 27, 2025

closes #195
Added support for injecting the raw request into the RequestContext.

Motivation and Context

In real-life applications, authentication may rely on request headers. In the case where the MCP server needs to act on behalf of the client to retrieve resources from a remote server or DB, thus requiring to authenticate on behalf of the client, getting the request headers is essential.
With the solution implemented in this PR, you may access the raw request from any end point as follows:

mcp = FastMCP()

@mcp.resource(uri="resource://hello")
async def get_greeting() -> str:
    ctx: Context[ServerSession, object] = mcp.get_context()
    raw_request: Request = ctx.request_context.request # Starlette Request object
    headers = raw_request.headers
    # do something with the headers

How Has This Been Tested?

Breaking Changes

The only breaking change is in typing. As a matter of fact the following generic classes have now an additional type argument RequestT (raw request type):

  • class RequestContext(Generic[SessionT, LifespanContextT, RequestT])
  • class Server(Generic[LifespanResultT, RequestT])

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • [x ] Documentation update

Checklist

  • I have read the MCP Documentation
  • [ x] My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

@ylassoued ylassoued marked this pull request as ready for review March 27, 2025 10:07
@ylassoued
Copy link
Author

@dsp-ant, I am not sure how the reviewing process works here. I was not able to add reviewers to this PR. Will somebody eventually be assigned as reviewer to this PR?

@georg-ort
Copy link

georg-ort commented Apr 2, 2025

pls merge this it's really essential to develop professional remote mcp servers!

@ylassoued
Copy link
Author

pls merge this it's really essential to develop professional remote mcp servers!

Thank you @georg-ort! I am hoping that someone will review it and approve it so that I may merge the PR.

@georg-ort
Copy link

Well I have to thank you @ylassoued! I'm currently using your fork in order to progress since I do need authentication context in my tool calls or our product wouldn't work.

@ylassoued
Copy link
Author

Oh brilliant. Thank you @georg-ort for supporting this. I am glad somebody if finding it useful :-).

@emilioflc
Copy link

Thank you so much for this, @ylassoued! This looks incredibly useful across a variety of contexts. I actually ran into the exact same situation where I needed authentication context in my tool calls and came across your PR. Really appreciate you putting this together. Fingers crossed it gets reviewed and merged soon!

@ylassoued
Copy link
Author

Thank you very much @emilioflc. My pleasure :-).

@null-crafter
Copy link

is this good to merge? this pull request is very helpful to me.

@bjb2010
Copy link

bjb2010 commented Apr 8, 2025

@dsp-ant Please take some time to check whether this can be merged?

@albertocalderari
Copy link

Hey, we sort of would like to get this too as we ended up implementing the same thing

@rem-lag
Copy link

rem-lag commented Apr 8, 2025

I'd just spent some time poking around trying to figure out if this could be done with the sdk as is so I'm happy to see there's already a PR for it, I hope it can be merged in because this functionality would make my life a lot easier!

Thanks for putting the work in @ylassoued

@wbeard
Copy link

wbeard commented Apr 8, 2025

Thank you for the contribution! I'm using your branch locally and see the headers I need. Would love to see this merged.

@samirbajaj
Copy link

Can we please get a review on this PR? I would love to see it merged.

Thank you.

@281743556
Copy link

When are you planning to launch? We urgently need this feature

@kelvin1804
Copy link

Please merge. This feature is essential for all remote MCPs
Thanks for your contribution @ylassoued

@JorgeRuizITCL
Copy link

Seems good, even implements generics for requests different than Starlette.

@wenxuwan
Copy link

wenxuwan commented Apr 21, 2025

hi @ylassoued, This pr looks like it injects the header of the first /sse request for all subsequent /messages requests, but if the user needs the header of subsequent requests what do they need to do, such as tools/call and tools/list requests, is there any way for the user to get the header information? I want to carry tracer information in the header of each subsequent /messages related http request and get the related tracer information in tools.

@wenxuwan
Copy link

hi @ylassoued, This pr looks like it injects the header of the first /sse request for all subsequent /messages requests, but if the user needs the header of subsequent requests what do they need to do, such as tools/call and tools/list requests, is there any way for the user to get the header information? I want to carry tracer information in the header of each subsequent /messages related http request and get the related tracer information in tools.

I looked at the client-related code and found that there is now no parameter allowing the user to carry a header in subsequent requests 😭

@toughnoah
Copy link

Please merge!!! It is really useful for tools authorization!!

@samirbajaj
Copy link

hi @ylassoued, This pr looks like it injects the header of the first /sse request for all subsequent /messages requests, but if the user needs the header of subsequent requests what do they need to do, such as tools/call and tools/list requests, is there any way for the user to get the header information? I want to carry tracer information in the header of each subsequent /messages related http request and get the related tracer information in tools.

I looked at the client-related code and found that there is now no parameter allowing the user to carry a header in subsequent requests 😭

I imagined that I'd associate a header value with the client_id that I can access on subsequent requests, but I can't seem to extract the client_id either ... #373

@wenxuwan
Copy link

hi @ylassoued, This pr looks like it injects the header of the first /sse request for all subsequent /messages requests, but if the user needs the header of subsequent requests what do they need to do, such as tools/call and tools/list requests, is there any way for the user to get the header information? I want to carry tracer information in the header of each subsequent /messages related http request and get the related tracer information in tools.

I looked at the client-related code and found that there is now no parameter allowing the user to carry a header in subsequent requests 😭

I imagined that I'd associate a header value with the client_id that I can access on subsequent requests, but I can't seem to extract the client_id either ... #373

All subsequent requests (e.g. tools/call and tools/list) are a separate http request, which should allow the developer to customise the header of each request, but the Python client doesn't have this ability right now ☹️

@ylassoued
Copy link
Author

Hi @wenxuwan, @samirbajaj, apologies for the delay in getting back to you. I have to admit, I have not fully understood your concern with carrying the request headers for all subsequent /messages requests. I assumed that when you initialised a client with headers, then it would inject the headers in every request. Am I wrong in assuming so?

@wenxuwan
Copy link

Hi @wenxuwan, @samirbajaj, apologies for the delay in getting back to you. I have to admit, I have not fully understood your concern with carrying the request headers for all subsequent /messages requests. I assumed that when you initialised a client with headers, then it would inject the headers in every request. Am I wrong in assuming so?

hi @ylassoued ,thanks for the reply, what I mean is that apart from the header information in the initialisation phase, I would like to be able to inject some other custom header information, such as the tracer information, in subsequent requests (the trace_id and span_id should be different for each subsequent http request)

@ylassoued
Copy link
Author

I would like to be able to inject some other custom header information, such as the tracer information, in subsequent requests (the trace_id and span_id should be different for each subsequent http request)

Thanks @wenxuwan! Any chance you could write some sample code to illustrate the use case? For example, by writing how ideally the user can make use of the header in the subsequent calls.

@quitrk
Copy link

quitrk commented Apr 23, 2025

I think the basic idea is that an MCP client could send different data (be it headers / different header values or whatever else) for the requests it submits for your tools / resources / prompts. Those headers could contain information unique to each request, like a trace_id as @wenxuwan mentioned. These could in turn be used on the MCP server side for observability (e.g use Grafana, CloudWatch etc to monitor your applications and data processes).

Unless I'm missing something, the solution in this PR doesn't account for that fact, and reuses the same initial request that was submitted when an MCP client connected to the /sse endpoint, which is also a bit misleading since you're basically caching it in the request_context, which is supposed to pertain to the actual current request.

@wenxuwan
Copy link

wenxuwan commented Apr 23, 2025

I would like to be able to inject some other custom header information, such as the tracer information, in subsequent requests (the trace_id and span_id should be different for each subsequent http request)

Thanks @wenxuwan! Any chance you could write some sample code to illustrate the use case? For example, by writing how ideally the user can make use of the header in the subsequent calls.

I would like to be able to inject some other custom header information, such as the tracer information, in subsequent requests (the trace_id and span_id should be different for each subsequent http request)

Thanks @wenxuwan! Any chance you could write some sample code to illustrate the use case? For example, by writing how ideally the user can make use of the header in the subsequent calls.

hi @ylassoued , It's not easy to write a demo because the current python sdk doesn't have this capability, but as @quitrk said, each http request should have its own request_context, and currently your implementation is that all subsequent requests share the request_context of the /sse request, which may solve some of the problems (like the This may solve some of the problems (e.g., authentication of tools), but it's not really right. We should carry the authentication information in the request_context of subsequent requests instead of sharing the request_context of the /sse request.

@ylassoued
Copy link
Author

I think the basic idea is that an MCP client could send different data (be it headers / different header values or whatever else) for the requests it submits for your tools / resources / prompts. Those headers could contain information unique to each request, like a trace_id as @wenxuwan mentioned. These could in turn be used on the MCP server side for observability (e.g use Grafana, CloudWatch etc to monitor your applications and data processes).

Unless I'm missing something, the solution in this PR doesn't account for that fact, and reuses the same initial request that was submitted when an MCP client connected to the /sse endpoint, which is also a bit misleading since you're basically caching it in the request_context, which is supposed to pertain to the actual current request.

OK, I see what you mean. Yes, I confirm that in this implementation, it is the initial request that is injected in the context. This was sufficient to solve the authentication issue as you mentioned it already. But I do understand your concern, and I believe now that this implementation is misleading. Instead of injecting the request, I should have simply injected the session-wide initial request headers. But, as you suggested, it would be cleaner to inject the current request for each call instead of injecting the initial one. I'll have to dig into the code again to figure this out... Thanks @wenxuwan, @samirbajaj, and @quitrk!

@wenxuwan
Copy link

I think the basic idea is that an MCP client could send different data (be it headers / different header values or whatever else) for the requests it submits for your tools / resources / prompts. Those headers could contain information unique to each request, like a trace_id as @wenxuwan mentioned. These could in turn be used on the MCP server side for observability (e.g use Grafana, CloudWatch etc to monitor your applications and data processes).
Unless I'm missing something, the solution in this PR doesn't account for that fact, and reuses the same initial request that was submitted when an MCP client connected to the /sse endpoint, which is also a bit misleading since you're basically caching it in the request_context, which is supposed to pertain to the actual current request.

OK, I see what you mean. Yes, I confirm that in this implementation, it is the initial request that is injected in the context. This was sufficient to solve the authentication issue as you mentioned it already. But I do understand your concern, and I believe now that this implementation is misleading. Instead of injecting the request, I should have simply injected the session-wide initial request headers. But, as you suggested, it would be cleaner to inject the current request for each call instead of injecting the initial one. I'll have to dig into the code again to figure this out... Thanks @wenxuwan, @samirbajaj, and @quitrk!

hi @ylassoued ,Currently, all subsequent requests except /sse are handled inside mcp.server.sse.SseServerTransport.handle_post_message, which transforms the request and sends it via writer.send(message) to the mcp.shared.session.BaseSession._receive_loop. This is where the problem occurs, handle_post_message converts the http request to a message while processing the request, resulting in the header information of the http request being lost 😣. Therefore, we can try to stuff the request_context into the message at the time of conversion and make sure that subsequent conversions will carry the request_context.

@ronald0samuel
Copy link

ronald0samuel commented Apr 29, 2025

How are we supposed to pass in the correct headers from the mcp client

@jlowin
Copy link
Contributor

jlowin commented May 2, 2025

This is super useful -- since I'm sure the low-level server is in flux with things like Streamable HTTP coming into focus, I've added support for accessing the current Starlette request in the standalone FastMCP repo, without modifying the low-level MCP server. Would welcome thoughts for enhancement there: jlowin/fastmcp#302

@ihrpr ihrpr added this to the r-05-25 milestone May 13, 2025
@ihrpr
Copy link
Contributor

ihrpr commented May 13, 2025

related #216

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Pass entire request object to handlers; add raw request to MCP base Request