1. Description

The primary source for a component’s description is its module-level docstring.

# tools/my_tool.py
"""This is the description for 'my_tool'.
It can span multiple lines and will be used by FastMCP.
"""

# ... rest of the tool code
  • This docstring is mandatory. If missing, the build will fail.
  • The function docstring of the exported function is generally ignored if a module docstring is present. It might be used as a fallback if the module docstring is missing, but relying on this is not recommended.

2. Entry function (export)

GolfMCP needs to know which function within your Python file is the main entry point for the component. This is primarily done by assigning the function to a module-level variable named export.

# tools/calculator.py
"""A simple calculator tool."""

async def add_numbers(a: int, b: int) -> int:
    return a + b

async def subtract_numbers(a: int, b: int) -> int:
    return a - b

# 'add_numbers' is the designated tool function
export = add_numbers

If an export variable is not found, GolfMCP build will fail.

3. Function signature requirements

The signature of your exported function must adhere to certain rules:

  • Tools:
    • Parameters must have type hints (e.g., param: str, count: int).
    • The return value must have a type hint (e.g., -> str, -> Dict[str, Any]).
    • Can be async def or def.
  • Resources:
    • If defined as a function, it follows similar rules to tools.
    • Resource functions typically do not take arguments unless they are part of a resource template (where arguments come from URI path parameters).
    • The URI for the resource is defined by a module-level variable resource_uri: str.
    • Example: resource_uri = "data://config"
    • Can also be a simple module-level constant (JSON-serializable).
  • Prompts:
    • The function should return either a str (which becomes a single user message) or a List[Dict].
    • Parameters should have type hints.

Pydantic for Input/Output Schemas (Tools):

For tools, Pydantic BaseModel is highly recommended for defining complex input argument structures and output shapes, enabling clear schemas and validation for FastMCP.

  • Input Parameters & Schema: Tool inputs are always defined by the parameters of your exported tool function. These parameters must have type hints and descriptions.

    • Direct Function Parameters: You define inputs directly in the tool function’s signature.
      # tools/hello.py
      """Hello World tool example."""
      from typing import Annotated
      from pydantic import BaseModel, Field
      
      class Output(BaseModel):
          message: str
      
      async def hello(
          name: Annotated[str, Field(description="The name of the person to greet")] = "World",
          greeting: Annotated[str, Field(description="The greeting phrase to use")] = "Hello"
      ) -> Output:
          # 'name' and 'greeting' are direct input parameters.
          # Golf's parser creates an inputSchema in the manifest based on these.
          # FastMCP uses these type hints for runtime validation.
          return Output(message=f"{greeting}, {name}!")
      export = hello
  • Output Structure & Schema: The structure of your tool’s output should be defined by its return type hint. It is strongly recommended to use a Pydantic BaseModel for this return type to ensure a clear and validated output schema.

    # tools/user_profile_tool.py
    """Tool to fetch user profile information."""
    from pydantic import BaseModel
    from typing import Optional
    
    class Output(BaseModel): # Explicit Pydantic model for the output
        user_id: int
        username: str
        email: Optional[str]
        is_active: bool
    
    async def fetch_user_profile(user_id: int) -> Output:
        # FastMCP uses the 'Output' return type hint for its
        # runtime behavior and schema exposure to clients.
        return Output(
            user_id=user_id, 
            username="example_user", 
            email="user@example.com", 
            is_active=True
        )
    
    export = fetch_user_profile

While simple type hints are sufficient, using Annotated with Pydantic Field provides rich descriptions and validation that significantly improves how AI agents understand and use your tools.

Basic parameter annotations

from typing import Annotated
from pydantic import BaseModel, Field

async def hello(
    name: Annotated[str, Field(description="The name of the person to greet")] = "World",
    greeting: Annotated[str, Field(description="The greeting phrase to use")] = "Hello"
) -> Output:
    """Say hello to the given name."""
    return Output(message=f"{greeting}, {name}!")

Parameter validation

Field annotations support various validation constraints:

from typing import Annotated, Optional
from pydantic import BaseModel, Field

class ChargeOutput(BaseModel):
    success: bool
    charge_id: str
    message: str

async def charge_payment(
    # Numeric constraints
    amount: Annotated[float, Field(
        description="Amount to charge in USD",
        gt=0,        # Must be greater than 0
        le=10000     # Maximum charge limit
    )],
    
    # Pattern validation
    card_token: Annotated[str, Field(
        description="Tokenized payment card identifier",
        pattern=r"^tok_[a-zA-Z0-9]+$"  # Regex validation
    )],
    
    # String length constraints
    description: Annotated[str, Field(
        description="Optional payment description",
        max_length=200,
        min_length=0
    )] = "",
    
    # Optional parameters with constraints
    customer_email: Annotated[Optional[str], Field(
        description="Customer's email address",
        pattern=r"^[\w\.-]+@[\w\.-]+\.\w+$",
        default=None
    )] = None
) -> ChargeOutput:
    """Process a payment charge with validation."""
    # Implementation here
    pass

5. Tool annotations (MCP behavioral hints)

Tool annotations provide behavioral hints about your tools to MCP clients, following the official MCP specification. These hints help clients understand how to interact with your tools safely and efficiently.

Overview

Add an annotations dictionary at the module level to specify behavioral hints:

"""Delete file tool."""

from typing import Annotated
from pydantic import BaseModel, Field

# Tool annotations - MCP behavioral hints
annotations = {
    "readOnlyHint": False,      # Tool modifies state
    "destructiveHint": True,     # Tool may delete/mutate data irreversibly  
    "idempotentHint": False,     # Repeated calls have side effects
    "openWorldHint": False       # Tool operates on local state only
}

class Output(BaseModel):
    success: bool
    message: str

async def delete_file(
    path: Annotated[str, Field(description="Path to file to delete")]
) -> Output:
    """Delete a file from the filesystem."""
    return Output(success=True, message="File deleted")

export = delete_file

Supported annotation types

readOnlyHint
boolean

true → tool cannot change state (pure “GET-style” call)

Note: When true, other hints are ignored by MCP clients.

destructiveHint
boolean

true → tool may delete or irreversibly mutate data

Only meaningful when readOnlyHint is false.

idempotentHint
boolean

true → repeated calls with identical arguments are safe and side-effect-free

Helps MCP clients add caching and deduplication.

openWorldHint
boolean

true → behavior depends on external mutable state (e.g., live web, clock)

Signals lower reproducibility to MCP clients.

Common patterns

Read-only tool:

# Tool that only reads data
annotations = {
    "readOnlyHint": True  # Other hints ignored when this is true
}

Web search tool:

# Tool that searches the internet
annotations = {
    "readOnlyHint": True,        # Doesn't modify local state
    "openWorldHint": True        # Depends on external web services
}

Idempotent tool:

# Tool safe to retry
annotations = {
    "readOnlyHint": False,
    "destructiveHint": False,
    "idempotentHint": True,      # Safe to call multiple times
    "openWorldHint": False
}

File operations tool:

# Tool that modifies files
annotations = {
    "readOnlyHint": False,
    "destructiveHint": True,     # May delete files
    "idempotentHint": False,     # Multiple calls may have different effects
    "openWorldHint": False       # Works on local filesystem
}