(ctx: ServerRequestContext[LifespanT, Any])
| 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)) |
nothing calls this directly
no test coverage detected