Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion src/enrichmcp/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Provides the EnrichMCP class for creating MCP applications.
"""

import functools
import inspect
import warnings
from collections.abc import Callable
Expand Down Expand Up @@ -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,
Expand Down
25 changes: 25 additions & 0 deletions tests/test_tool_logging.py
Original file line number Diff line number Diff line change
@@ -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]