Skip to content

LitServe MCP Bug: tools/call Always FailsΒ #560

@ductai199x

Description

@ductai199x

πŸ› Bug

MCP tools/call requests consistently fail with endpoint_handler() missing 1 required positional argument: 'request' while tools/list works perfectly. This makes MCP integration completely unusable.

Environment

  • LitServe Version: 0.2.12
  • MCP Version: 1.9.4
  • Python Version: 3.12.3
  • Operating System: Linux 5.15.167.4-microsoft-standard-WSL2 (WSL2)
  • Platform: x86_64

Bug Details

What Works βœ…

  • tools/list requests work perfectly
  • Direct REST API calls to /predict work fine
  • Tool discovery and schema generation work correctly

What's Broken ❌

  • All tools/call requests fail with identical error
  • Affects any LitAPI with MCP enabled
  • 100% failure rate across all tested configurations

Error Message

LitServer._register_api_endpoints.<locals>.endpoint_handler() missing 1 required positional argument: 'request'

Root Cause Analysis

The bug is in /litserve/mcp.py at line 461:

# This line fails:
return await _call_handler(handler, **arguments)

The issue:

  1. handler is the FastAPI endpoint_handler function which expects a request parameter
  2. _call_handler tries to bind MCP arguments as kwargs to this function
  3. FastAPI endpoint handlers require a positional request object, not kwargs
  4. The binding fails because no request argument is provided

The FastAPI endpoint signature is:

async def endpoint_handler(request: request_type) -> response_type:
    return await handler.handle_request(request, request_type)

Minimal Reproduction Case

1. Create Simple LitAPI with MCP

# minimal_mcp_repro.py
import litserve as ls
from litserve.mcp import MCP
from pydantic import BaseModel

class SimpleMCPAPI(ls.LitAPI):
    def setup(self, device):
        pass
    
    def decode_request(self, request):
        if isinstance(request, dict):
            return request
        return request.dict() if hasattr(request, 'dict') else {"message": str(request)}
    
    def predict(self, inputs):
        return {"response": f"Received: {inputs.get('message', 'unknown')}"}
    
    def encode_response(self, output):
        return output

if __name__ == "__main__":
    api = SimpleMCPAPI(
        mcp=MCP(
            name="simple_test",
            description="Simple test API",
            input_schema={
                "type": "object",
                "properties": {
                    "message": {"type": "string"}
                },
                "required": ["message"]
            }
        )
    )
    
    server = ls.LitServer(api)
    server.run(port=8000)

2. Test MCP Endpoints

# test_mcp_bug.py
import requests
import json

def test_mcp_bug():
    headers = {
        "Content-Type": "application/json",
        "Accept": "application/json, text/event-stream"
    }
    
    # This works βœ…
    tools_list = {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "tools/list",
        "params": {}
    }
    
    # This fails ❌  
    tools_call = {
        "jsonrpc": "2.0",
        "id": 2,
        "method": "tools/call",
        "params": {
            "name": "simple_test",
            "arguments": {"message": "test"}
        }
    }
    
    print("Testing tools/list...")
    response = requests.post("http://localhost:8000/mcp/", json=tools_list, headers=headers)
    print(f"Status: {response.status_code}")
    if response.status_code == 200:
        print("βœ… tools/list works")
    else:
        print(f"❌ tools/list failed: {response.text}")
    
    print("\nTesting tools/call...")
    response = requests.post("http://localhost:8000/mcp/", json=tools_call, headers=headers)
    print(f"Status: {response.status_code}")
    if response.status_code == 200:
        result = response.json()
        if result.get('result', {}).get('isError'):
            print(f"❌ tools/call failed: {result['result']['content'][0]['text']}")
        else:
            print("βœ… tools/call works")
    else:
        print(f"❌ tools/call HTTP error: {response.text}")

if __name__ == "__main__":
    test_mcp_bug()

3. Run Reproduction

# Terminal 1: Start server
python minimal_mcp_repro.py

# Terminal 2: Test bug
python test_mcp_bug.py

Expected Output:

Testing tools/list...
Status: 200
βœ… tools/list works

Testing tools/call...
Status: 200
❌ tools/call failed: LitServer._register_api_endpoints.<locals>.endpoint_handler() missing 1 required positional argument: 'request'

Comprehensive Testing Evidence

We tested 11 different configurations:

  • 4 different header combinations (standard, event-stream only, with session, explicit streaming)
  • 3 different payload complexities
  • 5 different endpoint paths
  • All streaming formats and HTTP configurations

Additional Notes

  • The bug appears to be in the design of how MCP integration calls FastAPI endpoints
  • tools/list works because it doesn't go through the same code path
  • Direct REST API calls work fine, proving the LitAPI implementation is correct
  • The error is consistent across all Python versions, operating systems, and configurations tested

Metadata

Metadata

Assignees

Labels

bugSomething isn't workinghelp wantedExtra attention is neededmcp

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions