{"record":{"id":"60760a4f62d871db","repo":"PrefectHQ/fastmcp","slug":"a-completion-handler-returned-a-str-return-a-list","errorCode":null,"errorMessage":"A completion handler returned a str; return a list of strings (for example, [value]) or a Completion instead.","messagePattern":"A completion handler returned a str; return a list of strings \\(for example, \\[value\\]\\) or a Completion instead\\.","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/completions.py","lineNumber":69,"sourceCode":"MAX_COMPLETION_VALUES = 100\n\n\ndef normalize_completion(result: CompletionValues) -> mcp_types.Completion:\n    \"\"\"Coerce a handler's return value into a wire ``Completion``.\n\n    A returned ``str`` is rejected: it is almost always a mistake (the value\n    would iterate into one-character candidates), so it raises rather than\n    silently producing surprising output.\n\n    The MCP contract caps a completion at 100 values, so a longer result is\n    truncated to the first 100 with ``has_more`` set — a handler that returns\n    thousands of matches emits a conforming response rather than an oversized\n    one that strict clients reject.\n    \"\"\"\n    if result is None:\n        return mcp_types.Completion(values=[])\n    if isinstance(result, str):\n        raise TypeError(\n            \"A completion handler returned a str; return a list of strings \"\n            \"(for example, [value]) or a Completion instead.\"\n        )\n    if isinstance(result, mcp_types.Completion):\n        completion = result\n    else:\n        completion = mcp_types.Completion(values=list(result))\n\n    if len(completion.values) > MAX_COMPLETION_VALUES:\n        total = (\n            completion.total if completion.total is not None else len(completion.values)\n        )\n        return mcp_types.Completion(\n            values=completion.values[:MAX_COMPLETION_VALUES],\n            total=total,\n            has_more=True,\n        )\n    return completion","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/completions.py#L51-L87","documentation":"A @mcp.completion handler returned a bare str, which normalize_completion deliberately rejects. A string satisfies Sequence[str] but would iterate into one-character 'candidates' on the wire, so FastMCP raises TypeError instead of silently producing garbage. Valid returns are mcp_types.Completion, list[str]/tuple[str], or None.","triggerScenarios":"Registering a completion handler via @mcp.completion whose return statement is `return value` (a single string) rather than a list, then receiving a completion/complete request that reaches the handler.","commonSituations":"Handlers that fetch a single best-match value and return it directly; authors misreading the return type and assuming a str is a valid single-candidate shorthand; refactors that changed a list return into a scalar return.","solutions":["Wrap the string in a list: return [value].","Return None if there are no candidates.","Return mcp_types.Completion(values=[...]) if you need total/has_more pagination hints.","Annotate the handler's return type as CompletionValues (str is excluded) so type checkers catch this."],"exampleFix":"// before\n@mcp.completion\nasync def complete(ref, argument, context):\n    return \"my-value\"\n// after\n@mcp.completion\nasync def complete(ref, argument, context):\n    return [\"my-value\"]","handlingStrategy":"type-guard","validationCode":"def check_completion_result(r):\n    if r is None or isinstance(r, (mcp_types.Completion, list, tuple)):\n        return r\n    if isinstance(r, str):\n        return [r]\n    raise TypeError(f\"invalid completion return: {type(r).__name__}\")","typeGuard":"def is_valid_completion(r) -> bool:\n    return r is None or isinstance(r, mcp_types.Completion) or (\n        isinstance(r, (list, tuple)) and all(isinstance(v, str) for v in r)\n    )","tryCatchPattern":"try:\n    result = await handler(ref, arg, ctx)\nexcept TypeError as e:\n    logging.error(\"completion handler returned wrong type: %s\", e)\n    result = None  # empty completion","preventionTips":["Annotate completion handlers with the CompletionValues return type so type checkers reject bare str.","Always return a list, even for a single candidate: [value].","Add a unit test that invokes each completion handler and asserts is_valid_completion on the result."],"tags":["python","completions","typeerror","return-type"],"backgroundTag":"wrong-return-type","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}