diff --git a/src/enrichmcp/app.py b/src/enrichmcp/app.py index 1e101ed..b9a7e14 100644 --- a/src/enrichmcp/app.py +++ b/src/enrichmcp/app.py @@ -4,6 +4,7 @@ Provides the EnrichMCP class for creating MCP applications. """ +import functools import inspect import warnings from collections.abc import Callable @@ -364,7 +365,35 @@ def _register_tool_def(self, fn: Callable[..., Any], tool_def: ToolDef) -> Calla desc = self._append_enrichparameter_hints(tool_def.final_description(self), fn) self.resources[tool_def.name] = fn mcp_tool = self.mcp.tool(name=tool_def.name, description=desc) - return mcp_tool(fn) + + registered = mcp_tool(fn) + + sig = inspect.signature(fn) + context_kwarg = None + for name, param in sig.parameters.items(): + try: + if issubclass(param.annotation, EnrichContext): + context_kwarg = name + break + except Exception: + continue + + is_coroutine = inspect.iscoroutinefunction(fn) + + async def wrapper(*args: Any, **kwargs: Any) -> Any: + ctx = kwargs.get(context_kwarg) if context_kwarg else None + if ctx is not None: + bound = sig.bind_partial(*args, **kwargs) + bound.apply_defaults() + params = {k: v for k, v in bound.arguments.items() if k != context_kwarg} + await ctx.info(f"Calling tool {tool_def.name} params={params!r}") + if is_coroutine: + return await registered(*args, **kwargs) + return registered(*args, **kwargs) + + wrapped = functools.wraps(registered)(wrapper) + wrapped.__signature__ = sig + return wrapped def _tool_decorator( self, diff --git a/tests/test_tool_logging.py b/tests/test_tool_logging.py new file mode 100644 index 0000000..b1d2e03 --- /dev/null +++ b/tests/test_tool_logging.py @@ -0,0 +1,25 @@ +from unittest.mock import AsyncMock, Mock + +import pytest + +from enrichmcp import EnrichContext, EnrichMCP + + +@pytest.mark.asyncio +async def test_tool_logs_call_params(): + app = EnrichMCP("Test", description="d") + + @app.retrieve(description="say hi") + async def greet(name: str, ctx: EnrichContext) -> str: + return f"hi {name}" + + mock_ctx = Mock(spec=EnrichContext) + mock_ctx.info = AsyncMock() + + result = await greet("bob", ctx=mock_ctx) + + assert result == "hi bob" + mock_ctx.info.assert_awaited_once() + args, kwargs = mock_ctx.info.call_args + assert "greet" in args[0] + assert "'name': 'bob'" in args[0]