PrefectHQ/fastmcp · error · TypeError

Cannot specify both a name as first argument and as keyword

Error message

Cannot specify both a name as first argument and as keyword argument. Use either @tool('{name_or_fn}') or @tool(name='{name}'), not both.

What it means

@tool accepts a tool name either as the first positional string argument or via the name keyword, but not both. Passing both is ambiguous, so FastMCP raises a TypeError explaining the two supported forms.

Source

Thrown at fastmcp_slim/fastmcp/server/providers/local_provider/decorators/tools.py:329

                meta=meta,
                task=task,
                timeout=timeout,
                auth=auth,
                enabled=enabled,
                run_in_thread=run_in_thread,
            )
            target = fn.__func__ if hasattr(fn, "__func__") else fn
            target.__fastmcp__ = metadata  # type: ignore[attr-defined]  # ty:ignore[unresolved-attribute]
            self.add_tool(fn)
            return fn

        if inspect.isroutine(name_or_fn):
            return decorate_and_register(name_or_fn, name)

        elif isinstance(name_or_fn, str):
            # Case 3: @tool("custom_name") - name passed as first argument
            if name is not None:
                raise TypeError(
                    "Cannot specify both a name as first argument and as keyword argument. "
                    f"Use either @tool('{name_or_fn}') or @tool(name='{name}'), not both."
                )
            tool_name = name_or_fn
        elif name_or_fn is None:
            # Case 4: @tool() or @tool(name="something") - use keyword name
            tool_name = name
        else:
            raise TypeError(
                f"First argument to @tool must be a function, string, or None, got {type(name_or_fn)}"
            )

        # Return partial for cases where we need to wait for the function
        return partial(
            self.tool,
            name=tool_name,
            version=version,
            title=title,

View on GitHub (pinned to 1f02114297)

Solutions

  1. Keep only one: either @tool('name') or @tool(name='name').
  2. If the two names differ, decide which one is intended and remove the other; the description-style first argument does not exist — only name is positional.

Example fix

// before
@tool('my_tool', name='my_tool')
def my_tool(x: int) -> str:
    ...

// after
@tool('my_tool')
def my_tool(x: int) -> str:
    ...
Defensive patterns

Strategy: validation

Validate before calling

def check_tool_args(*args, **kwargs):
    positional_names = [a for a in args if isinstance(a, str)]
    if positional_names and kwargs.get('name') is not None:
        raise TypeError('pass the tool name either positionally or via name=, not both')

check_tool_args('my_tool', name='my_tool')  # would raise

Type guard

def name_conflict(first_arg, name) -> bool:
    return isinstance(first_arg, str) and name is not None

Try / catch

try:
    register_tools()
except TypeError as e:
    if 'both a name as first argument and as keyword' in str(e):
        logging.error('remove the duplicate tool name argument')
    raise

Prevention

When it happens

Trigger: Calling @tool('my_tool', name='my_tool') or @tool('custom', name='other') — a string as the first argument together with a non-None name keyword.

Common situations: Refactoring from @tool(name='x') to @tool('x') (or vice versa) and leaving both in place; IDE auto-completes adding a duplicate name argument; copy-paste from two examples.

Related errors


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/f5a748db4ca2c9fa. Report an issue: GitHub.