Skip to content
Merged
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
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,25 +63,22 @@ If you use this template for your own repo, also see [When using this template](

Tools are implemented in [src/mcp_template_py/api/tools.py](src/mcp_template_py/api/tools.py) as methods on the `Tools` class, and registered in [src/mcp_template_py/api/mcp_builder.py](src/mcp_template_py/api/mcp_builder.py):

**1. Define request/response models in [src/mcp_template_py/api/models.py](src/mcp_template_py/api/models.py):**
**1. Define the response model in [src/mcp_template_py/api/models.py](src/mcp_template_py/api/models.py):**

```python
from pydantic import BaseModel, Field

class HelloRequest(BaseModel):
name: str = Field(..., description="The name of the user making the request.")

class HelloResponse(BaseModel):
result: str = Field(..., description="The greeting message.")
```

**2. Implement the tool in [src/mcp_template_py/api/tools.py](src/mcp_template_py/api/tools.py):**
**2. Implement the tool in [src/mcp_template_py/api/tools.py](src/mcp_template_py/api/tools.py) with flat arguments:**

```python
class Tools:
async def hello(self, request: HelloRequest) -> HelloResponse:
async def hello(self, name: str) -> HelloResponse:
"""Say hello to the user."""
return HelloResponse(result=f"Hello, {request.name}!")
return HelloResponse(result=f"Hello, {name}!")
```

**3. Register the tool in [src/mcp_template_py/api/mcp_builder.py](src/mcp_template_py/api/mcp_builder.py):**
Expand All @@ -93,8 +90,12 @@ mcp.add_tool(tools.hello)

**Key points:**
- Tools are async methods on the `Tools` class
- Docstrings become tool descriptions for MCP clients
- Use Pydantic models for type-safe input validation and output schemas
- **Arguments are flat parameters** (e.g. `name: str, age: int`), not a single wrapper
Pydantic model. MCP clients see each argument as its own field, not a nested `request`
object
- **Return a Pydantic model** so the tool has an explicit, typed output schema
- Docstrings become tool descriptions for MCP clients; use `Annotated[..., Field(description=...)]`
on a parameter if you want per-argument descriptions in the schema
- Tools are registered via `mcp.add_tool()` in the MCP builder
- Use `get_bearer_token()` from `mcp_template_py.auth` to access the client's Bearer token

Expand Down
11 changes: 5 additions & 6 deletions src/mcp_template_py/api/models.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
"""
Defines request and response schemas for MCP tools as Pydantic models.
Defines response schemas for MCP tools as Pydantic models.

Tool arguments are passed as flat parameters on the tool method (so MCP clients
see individual fields), not as a single Pydantic model. Responses stay as
Pydantic models so the MCP output schema is explicit and typed.
"""

from pydantic import BaseModel, Field

__all__ = [
"HelloRequest",
"HelloResponse",
]


class HelloRequest(BaseModel):
name: str = Field(..., description="The name of the user making the request.")


class HelloResponse(BaseModel):
result: str = Field(..., description="The greeting message.")
8 changes: 4 additions & 4 deletions src/mcp_template_py/api/tools.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import structlog

from mcp_template_py.api.models import HelloRequest, HelloResponse
from mcp_template_py.api.models import HelloResponse


class Tools:
def __init__(self):
self._logger: structlog.BoundLogger = structlog.get_logger()

async def hello(self, request: HelloRequest) -> HelloResponse:
async def hello(self, name: str) -> HelloResponse:
"""Say hello to the user."""
self._logger.info("hello tool called", name=request.name)
return HelloResponse(result=f"Hello, {request.name}!")
self._logger.info("hello tool called", name=name)
return HelloResponse(result=f"Hello, {name}!")
Loading