-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Expand file tree
/
Copy pathexceptions.py
More file actions
125 lines (95 loc) · 4.42 KB
/
exceptions.py
File metadata and controls
125 lines (95 loc) · 4.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
from __future__ import annotations
from typing import Any, cast
from mcp.types import INVALID_REQUEST, URL_ELICITATION_REQUIRED, ElicitRequestURLParams, ErrorData, JSONRPCError
class MCPError(Exception):
"""Exception type raised when an error arrives over an MCP connection."""
error: ErrorData
def __init__(self, code: int, message: str, data: Any = None):
super().__init__(code, message, data)
if data is not None:
self.error = ErrorData(code=code, message=message, data=data)
else:
self.error = ErrorData(code=code, message=message)
@property
def code(self) -> int:
return self.error.code
@property
def message(self) -> str:
return self.error.message
@property
def data(self) -> Any:
return self.error.data # pragma: no cover
@classmethod
def from_jsonrpc_error(cls, error: JSONRPCError) -> MCPError:
return cls.from_error_data(error.error)
@classmethod
def from_error_data(cls, error: ErrorData) -> MCPError:
return cls(code=error.code, message=error.message, data=error.data)
def __str__(self) -> str:
return self.message
class NoBackChannelError(MCPError):
"""Raised when sending a server-initiated request over a transport that cannot deliver it.
Stateless HTTP and JSON-response-mode HTTP have no channel for the server to
push requests (sampling, elicitation, roots/list) to the client. This is
raised by `DispatchContext.send_request` when `transport.can_send_request`
is ``False``, and serializes to an ``INVALID_REQUEST`` error response.
"""
def __init__(self, method: str):
super().__init__(
code=INVALID_REQUEST,
message=(
f"Cannot send {method!r}: this transport context has no back-channel for server-initiated requests."
),
)
self.method = method
class StatelessModeNotSupported(RuntimeError):
"""Raised when attempting to use a method that is not supported in stateless mode.
Server-to-client requests (sampling, elicitation, list_roots) are not
supported in stateless HTTP mode because there is no persistent connection
for bidirectional communication.
"""
def __init__(self, method: str):
super().__init__(
f"Cannot use {method} in stateless HTTP mode. "
"Stateless mode does not support server-to-client requests. "
"Use stateful mode (stateless_http=False) to enable this feature."
)
self.method = method
class UrlElicitationRequiredError(MCPError):
"""Specialized error for when a tool requires URL mode elicitation(s) before proceeding.
Servers can raise this error from tool handlers to indicate that the client
must complete one or more URL elicitations before the request can be processed.
Example:
```python
raise UrlElicitationRequiredError([
ElicitRequestURLParams(
message="Authorization required for your files",
url="https://example.com/oauth/authorize",
elicitation_id="auth-001"
)
])
```
"""
def __init__(self, elicitations: list[ElicitRequestURLParams], message: str | None = None):
"""Initialize UrlElicitationRequiredError."""
if message is None:
message = f"URL elicitation{'s' if len(elicitations) > 1 else ''} required"
self._elicitations = elicitations
super().__init__(
code=URL_ELICITATION_REQUIRED,
message=message,
data={"elicitations": [e.model_dump(by_alias=True, exclude_none=True) for e in elicitations]},
)
@property
def elicitations(self) -> list[ElicitRequestURLParams]:
"""The list of URL elicitations required before the request can proceed."""
return self._elicitations
@classmethod
def from_error(cls, error: ErrorData) -> UrlElicitationRequiredError:
"""Reconstruct from an ErrorData received over the wire."""
if error.code != URL_ELICITATION_REQUIRED:
raise ValueError(f"Expected error code {URL_ELICITATION_REQUIRED}, got {error.code}")
data = cast(dict[str, Any], error.data or {})
raw_elicitations = cast(list[dict[str, Any]], data.get("elicitations", []))
elicitations = [ElicitRequestURLParams.model_validate(e) for e in raw_elicitations]
return cls(elicitations, error.message)