HTTP Request Access in Hyperia Servers
Added in v2.2.11 – seamlessly reach Starlette Request objects from inside tools, resources, prompts, or any helper buried five calls deep.
When a Hyperia server is exposed via Streamable HTTP (or legacy SSE), each MCP request rides an HTTP envelope. Sometimes you need headers, cookies, query params, or the client’s IP to tailor behaviour—rate limiting, per‑tenant routing, feature flags, etc.
Hyperia exposes a dependency helper so you never have to pass Request
objects by hand.
1 get_http_request()
Helper
get_http_request()
Helperfrom hyperia.server.dependencies import get_http_request # starlette.req proxy
from starlette.requests import Request
@mcp.tool()
async def whois():
req: Request = get_http_request() # 👍 safe anywhere in call‑stack
return {
"ua": req.headers.get("user-agent", "?"),
"ip": req.client.host if req.client else "?",
"path": req.url.path,
}
Key Points
Works only in an active HTTP context. If you call it from STDIO / in‑memory runs you’ll get
RuntimeError("No active HTTP request")
.Returns the Starlette
Request
(FastAPI is Starlette under the hood).Zero coupling—keep function signatures clean; no phantom
request
params exposed to LLMs.
2 Typical Use‑Cases
2.1 Auth Header Introspection
@mcp.tool()
async def auth_info():
req = get_http_request()
auth = req.headers.get("authorization", "")
scheme, _, token = auth.partition(" ")
return {"scheme": scheme.title(), "token_present": bool(token)}
2.2 Tenant Resolution via Custom Header
async def get_tenant_id() -> str:
req = get_http_request()
return req.headers.get("X-Tenant", "public")
@mcp.resource("data://tenant/{key}")
async def per_tenant_data(key: str) -> dict:
tenant = await get_tenant_id()
return db.fetch(tenant, key) # pseudo code
2.3 Query Param Parsing
@mcp.tool()
async def search_default(page_size: int = 20):
req = get_http_request()
size = int(req.query_params.get("page_size", page_size))
return backend.search(limit=size)
3 Advanced Patterns
3.1 Store Request on Context for Down‑Stream Helpers
from hyperia import Context
@mcp.tool()
async def analyse(ctx: Context):
req = get_http_request()
ctx.session_state["request_id"] = req.headers.get("X-Request-ID", ctx.request_id)
...
3.2 Custom Middleware Injection
Need CORS, tracing, or IP‑whitelisting?
from starlette.middleware.base import BaseHTTPMiddleware
class TraceMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
request.state.trace_id = request.headers.get("X-Trace") or uuid4().hex
response = await call_next(request)
response.headers["X-Trace"] = request.state.trace_id
return response
mcp = Hyperia("API", http_middleware=[TraceMiddleware])
Inside any tool you can then get_http_request()
and read request.state.trace_id
.
3.3 Ensuring Availability for Mounted Servers
When you mount servers under a Starlette/FastAPI parent, the parent’s middleware executes first. get_http_request()
still works because the dependency walks the ASGI scope chain.
4 Security & Edge‑Cases
Called from STDIO / in‑memory transport
Raises RuntimeError
Behind reverse proxy
You may need X‑Forwarded‑For
logic to get client IP; implement Starlette’s TrustedHostMiddleware
or similar
Large file uploads
Use await req.body()
or req.stream()
inside the tool—mind memory limits
HTTP/2 push trailers
Available via req.headers.raw
but uncommon
5 Unit‑Testing Helpers
from starlette.testclient import TestClient as StarClient
from hyperia import Hyperia
srv = Hyperia("T")
@srv.tool() async def echo_ip():
return get_http_request().client.host
client = StarClient(srv.http_app())
assert client.post("/mcp/tools/call", json={...}).json()["text"] == "127.0.0.1"
6 Migration Notes (≤ v2.2.10)
Pre‑2.2.11 you had to call ctx.get_http_request()
from within a tool. That helper is deprecated; switch to the standalone get_http_request()
function for clarity and for use in non‑tool helpers.
7 Recap Checklist
Import once:
from hyperia.server.dependencies import get_http_request
.Call it inside HTTP‑backed requests only.
Treat returned object as Starlette
Request
.Use for headers, cookies, query‑params, file uploads, client IP, etc.
For STDIO/in‑memory modes, guard with
if transport.is_http
or let RuntimeError propagate.
Last updated