MCPcopy Index your code
hub / github.com/modelcontextprotocol/python-sdk / _on_request

Method _on_request

src/mcp/server/runner.py:217–292  ·  view source on GitHub ↗
(
        self,
        dctx: DispatchContext[TransportContext],
        method: str,
        params: Mapping[str, Any] | None,
    )

Source from the content-addressed store, hash-verified

215 return self._on_notify
216
217 async def _on_request(
218 self,
219 dctx: DispatchContext[TransportContext],
220 method: str,
221 params: Mapping[str, Any] | None,
222 ) -> dict[str, Any]:
223 meta = _extract_meta(params)
224 version = self.connection.protocol_version
225 ctx = self._make_context(dctx, method, params, meta, version)
226 is_spec_method = method in _methods.SPEC_CLIENT_METHODS
227
228 async def _inner(ctx: ServerRequestContext[LifespanT, Any]) -> HandlerResult:
229 # Read method/params off `ctx` so a middleware that rewrote them via
230 # `call_next(replace(ctx, ...))` reaches lookup and the handler.
231 method, params = ctx.method, ctx.params
232 # Pinned compat: spec methods are surface-validated before lookup,
233 # so malformed params are INVALID_PARAMS even with no handler
234 # registered. Custom methods miss the monolith map and fall through
235 # to `entry.params_type` exactly as before.
236 if method in _methods.SPEC_CLIENT_METHODS:
237 try:
238 _methods.validate_client_request(method, version, params)
239 except KeyError:
240 raise MCPError(code=METHOD_NOT_FOUND, message="Method not found", data=method) from None
241 # TODO(L29): the 2026-07-28 spec drops the handshake; this branch and
242 # the gate become a per-version legacy path then. Initialize runs inline
243 # (read loop parked), so awaiting the peer anywhere on this path deadlocks.
244 if method == "initialize":
245 return self._handle_initialize(params)
246 # Methods without a handler are METHOD_NOT_FOUND regardless of
247 # initialization state: JSON-RPC 2.0 reserves -32601 for "not
248 # available on this server", and clients probing a server before
249 # the handshake key off that code. The init gate below therefore
250 # only ever applies to methods the server actually serves.
251 entry = self.server.get_request_handler(method)
252 if entry is None:
253 raise MCPError(code=METHOD_NOT_FOUND, message="Method not found", data=method)
254 if not self.connection.initialize_accepted and method not in _INIT_EXEMPT:
255 # Pinned compat: the same error shape the union validation produced.
256 raise MCPError(code=INVALID_PARAMS, message="Invalid request parameters", data="")
257 # Absent params validate as {} (required fields still reject), so
258 # the handler receives the model with its defaults, never None.
259 typed_params = entry.params_type.model_validate({} if params is None else params, by_name=False)
260 result = await entry.handler(ctx, typed_params)
261 if isinstance(result, ErrorData):
262 # Raise inside the chain so middleware observes the failure.
263 raise MCPError.from_error_data(result)
264 return result
265
266 call = self._compose_server_middleware(_inner)
267 result = _dump_result(await call(ctx))
268 # TODO(L56): reject resultType values outside {"complete", "input_required"} unless the
269 # corresponding extension is in this request's _meta clientCapabilities.extensions; the
270 # explicit MUST-reject is client-side (basic/index.mdx ResultType), this enforces it proactively.
271 if is_spec_method:
272 try:
273 result = _methods.serialize_server_result(method, version, result)
274 except KeyError:

Callers 1

_dispatch_requestMethod · 0.45

Calls 7

_make_contextMethod · 0.95
_negotiate_initializeMethod · 0.95
MCPErrorClass · 0.90
_extract_metaFunction · 0.85
_dump_resultFunction · 0.85
callFunction · 0.50

Tested by

no test coverage detected