Skip to content

Commit 503c14b

Browse files
committed
Minor fixes
1 parent e2fa0d5 commit 503c14b

File tree

7 files changed

+70
-54
lines changed

7 files changed

+70
-54
lines changed

src/mcp/client/session.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,8 @@ async def call_tool(
282282
params=types.CallToolRequestParams(
283283
name=name,
284284
arguments=arguments,
285+
webhooks=webhooks,
285286
),
286-
webhooks=webhooks,
287287
)
288288
),
289289
types.CallToolResult,

src/mcp/server/fastmcp/server.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@
6363
from mcp.types import Resource as MCPResource
6464
from mcp.types import ResourceTemplate as MCPResourceTemplate
6565
from mcp.types import Tool as MCPTool
66-
from mcp.types import Webhook
6766

6867
logger = get_logger(__name__)
6968

@@ -277,15 +276,13 @@ async def call_tool(
277276
self,
278277
name: str,
279278
arguments: dict[str, Any],
280-
webhooks: list[Webhook] | None = None,
281279
) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
282280
"""Call a tool by name with arguments."""
283281
context = self.get_context()
284282
result = await self._tool_manager.call_tool(
285283
name,
286284
arguments,
287285
context=context,
288-
webhooks=webhooks
289286
)
290287
converted_result = _convert_to_content(result)
291288
return converted_result
@@ -787,7 +784,8 @@ def streamable_http_app(self) -> Starlette:
787784
event_store=self._event_store,
788785
json_response=self.settings.json_response,
789786
stateless=self.settings.stateless_http, # Use the stateless setting
790-
webhooks_supported=self.settings.webhooks_supported, # Use the webhooks supported setting
787+
webhooks_supported=self.settings.webhooks_supported,
788+
# Use the webhooks supported setting
791789
)
792790

793791
# Create the ASGI handler

src/mcp/server/fastmcp/tools/tool_manager.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from mcp.server.fastmcp.tools.base import Tool
88
from mcp.server.fastmcp.utilities.logging import get_logger
99
from mcp.shared.context import LifespanContextT
10-
from mcp.types import ToolAnnotations, Webhook
10+
from mcp.types import ToolAnnotations
1111

1212
if TYPE_CHECKING:
1313
from mcp.server.fastmcp.server import Context
@@ -66,16 +66,16 @@ async def call_tool(
6666
name: str,
6767
arguments: dict[str, Any],
6868
context: Context[ServerSessionT, LifespanContextT] | None = None,
69-
webhooks: list[Webhook] | None = None,
7069
) -> Any:
7170
"""Call a tool by name with arguments."""
7271
tool = self.get_tool(name)
7372
if not tool:
7473
raise ToolError(f"Unknown tool: {name}")
7574

76-
if context:
77-
context.has_webhook = (
78-
webhooks is not None and len(webhooks) > 0
79-
)
75+
if context is not None:
76+
try:
77+
context.has_webhook = context.request_context.has_webhook
78+
except Exception:
79+
logger.debug("Request context is not available.")
8080

8181
return await tool.run(arguments, context=context)

src/mcp/server/lowlevel/server.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,9 @@ def decorator(
412412

413413
async def handler(req: types.CallToolRequest):
414414
try:
415-
results = await func(req.params.name, (req.params.arguments or {}), req.webhooks)
415+
if req.params.webhooks is not None and len(req.params.webhooks) > 0:
416+
self.request_context.has_webhook = True
417+
results = await func(req.params.name, (req.params.arguments or {}))
416418
return types.ServerResult(
417419
types.CallToolResult(content=list(results), isError=False)
418420
)

src/mcp/server/streamable_http.py

Lines changed: 55 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -420,27 +420,29 @@ async def _handle_post_request(
420420
request_stream_reader = self._request_streams[request_id][1]
421421

422422
session_message = SessionMessage(message)
423-
if self._is_call_tool_request_with_webhooks(
424-
session_message.message
425-
):
423+
webhooks = self._get_webhooks(session_message.message)
424+
if webhooks is not None:
426425
if self.is_webhooks_supported:
426+
result = {
427+
"content": [
428+
{
429+
"type": "text",
430+
"text": "Response will be forwarded to the webhook.",
431+
}
432+
],
433+
"isError": False,
434+
}
427435
response = self._create_json_response(
428-
JSONRPCMessage(root=JSONRPCResponse(
429-
jsonrpc="2.0",
430-
id=message.root.id,
431-
result={
432-
'content': [{
433-
'type': 'text',
434-
'text': 'Response will be forwarded to the webhook.'
435-
}],
436-
'isError': False
437-
},
438-
)),
436+
JSONRPCMessage(
437+
root=JSONRPCResponse(
438+
jsonrpc="2.0", id=message.root.id, result=result
439+
)
440+
),
439441
HTTPStatus.OK,
440442
)
441443
asyncio.create_task(
442444
self._send_response_to_webhooks(
443-
request_id, session_message, request_stream_reader
445+
request_id, session_message, webhooks, request_stream_reader
444446
)
445447
)
446448
else:
@@ -574,14 +576,13 @@ async def sse_writer():
574576
await writer.send(Exception(err))
575577
return
576578

577-
578579
async def _send_response_to_webhooks(
579580
self,
580581
request_id: str,
581582
session_message: SessionMessage,
583+
webhooks: list[Webhook],
582584
request_stream_reader: MemoryObjectReceiveStream[EventMessage],
583585
):
584-
webhooks: list[Webhook] = [Webhook(**webhook) for webhook in session_message.message.root.webhooks]
585586
writer = self._read_stream_writer
586587
if writer is None:
587588
raise ValueError(
@@ -611,9 +612,7 @@ async def _send_response_to_webhooks(
611612
break
612613
# For notifications and request, keep waiting
613614
else:
614-
logger.debug(
615-
f"received: {event_message.message.root.method}"
616-
)
615+
logger.debug(f"received: {event_message.message.root.method}")
617616

618617
await self._send_message_to_webhooks(webhooks, response_message)
619618
else:
@@ -635,7 +634,6 @@ async def _send_response_to_webhooks(
635634
finally:
636635
await self._clean_up_memory_streams(request_id)
637636

638-
639637
async def _send_message_to_webhooks(
640638
self,
641639
webhooks: list[Webhook],
@@ -646,7 +644,9 @@ async def _send_message_to_webhooks(
646644
# Add authorization headers
647645
if webhook.authentication and webhook.authentication.credentials:
648646
if webhook.authentication.strategy == "bearer":
649-
headers["Authorization"] = f"Bearer {webhook.authentication.credentials}"
647+
headers["Authorization"] = (
648+
f"Bearer {webhook.authentication.credentials}"
649+
)
650650
elif webhook.authentication.strategy == "apiKey":
651651
headers["X-API-Key"] = webhook.authentication.credentials
652652
elif webhook.authentication.strategy == "basic":
@@ -656,32 +656,45 @@ async def _send_message_to_webhooks(
656656
if "username" in creds_dict and "password" in creds_dict:
657657
# Create basic auth header from username and password
658658
import base64
659-
auth_string = f"{creds_dict['username']}:{creds_dict['password']}"
660-
credentials = base64.b64encode(auth_string.encode()).decode()
659+
660+
auth_string = (
661+
f"{creds_dict['username']}:{creds_dict['password']}"
662+
)
663+
credentials = base64.b64encode(
664+
auth_string.encode()
665+
).decode()
661666
headers["Authorization"] = f"Basic {credentials}"
662-
except:
667+
except Exception:
663668
# Not JSON, use as-is
664-
headers["Authorization"] = f"Basic {webhook.authentication.credentials}"
665-
elif webhook.authentication.strategy == "customHeader" and webhook.authentication.credentials:
669+
headers["Authorization"] = (
670+
f"Basic {webhook.authentication.credentials}"
671+
)
672+
elif (
673+
webhook.authentication.strategy == "customHeader"
674+
and webhook.authentication.credentials
675+
):
666676
try:
667677
custom_headers = json.loads(webhook.authentication.credentials)
668678
headers.update(custom_headers)
669-
except:
670-
pass
679+
except Exception as e:
680+
logger.exception(f"Error setting custom headers: {e}")
671681

672682
async with create_mcp_http_client(headers=headers) as client:
673683
try:
674684
if isinstance(message, JSONRPCMessage | JSONRPCError):
675685
await client.post(
676686
webhook.url,
677-
json=message.model_dump_json(by_alias=True, exclude_none=True),
687+
json=message.model_dump_json(
688+
by_alias=True, exclude_none=True
689+
),
678690
)
679691
else:
680692
await client.post(webhook.url, json=message)
681693

682694
except Exception as e:
683-
logger.exception(f"Error sending response to webhook {webhook.url}: {e}")
684-
695+
logger.exception(
696+
f"Error sending response to webhook {webhook.url}: {e}"
697+
)
685698

686699
async def _handle_get_request(self, request: Request, send: Send) -> None:
687700
"""
@@ -803,17 +816,18 @@ async def _handle_delete_request(self, request: Request, send: Send) -> None:
803816
)
804817
await response(request.scope, request.receive, send)
805818

806-
807-
def _is_call_tool_request_with_webhooks(self, message: JSONRPCMessage) -> bool:
808-
"""Check if the request is a call tool request with webhooks."""
809-
return (
819+
def _get_webhooks(self, message: JSONRPCMessage) -> list[Webhook] | None:
820+
"""Return webhooks if the request is a call tool request with webhooks."""
821+
if (
810822
isinstance(message.root, JSONRPCRequest)
811823
and message.root.method == "tools/call"
812-
and hasattr(message.root, "webhooks")
813-
and message.root.webhooks is not None
814-
and len(message.root.webhooks) > 0
815-
)
816-
824+
and message.root.params is not None
825+
and "webhooks" in message.root.params
826+
and message.root.params["webhooks"] is not None
827+
and len(message.root.params["webhooks"]) > 0
828+
):
829+
return [Webhook(**webhook) for webhook in message.root.params["webhooks"]]
830+
return None
817831

818832
async def _terminate_session(self) -> None:
819833
"""Terminate the current session, closing all streams.

src/mcp/shared/context.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ class RequestContext(Generic[SessionT, LifespanContextT]):
1616
meta: RequestParams.Meta | None
1717
session: SessionT
1818
lifespan_context: LifespanContextT
19+
has_webhook: bool = False

src/mcp/types.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ class JSONRPCRequest(Request[dict[str, Any] | None, str]):
122122
id: RequestId
123123
method: str
124124
params: dict[str, Any] | None = None
125+
webhooks: dict[str, Any] | None = None
125126

126127

127128
class JSONRPCNotification(Notification[dict[str, Any] | None, str]):
@@ -806,6 +807,7 @@ class CallToolRequestParams(RequestParams):
806807

807808
name: str
808809
arguments: dict[str, Any] | None = None
810+
webhooks: list[Webhook] | None = None
809811
model_config = ConfigDict(extra="allow")
810812

811813

@@ -814,7 +816,6 @@ class CallToolRequest(Request[CallToolRequestParams, Literal["tools/call"]]):
814816

815817
method: Literal["tools/call"]
816818
params: CallToolRequestParams
817-
webhooks: list[Webhook] | None = None
818819

819820

820821
class CallToolResult(Result):

0 commit comments

Comments
 (0)