Skip to content

[BUG] MCP Authorization is not working #1269

@Akrog

Description

@Akrog

Bug Description

I'm running the server from master and it seem like lightspeed-stack is not sending the Authorization header to llama-stack.

This is my MCP configuration in lightspeed-stack.yaml:

mcp_servers:
  - name: rhos-osp-tools
    url: http://127.0.0.1:8901/openstack/
    authorization_headers:
      Authorization: "${env.PWD}/lightspeed-stack/mcp-auth-token"
  - name: rhos-ocp-tools
    url: http://127.0.0.1:8901/openshift/
    authorization_headers:
      Authorization: "${env.PWD}/lightspeed-stack/mcp-auth-token"

In llama-stack I have:

  tool_runtime:
  - config: {} # Enable the RAG tool
    provider_id: rag-runtime
    provider_type: inline::rag-runtime
  - provider_id: model-context-protocol
    provider_type: remote::model-context-protocol
    config: {}

On the client I see this:

$ curl -s -X POST http://localhost:8080/v1/query  -H "Content-Type: application/json"  -d '{"query": "How do I launch an instance in OpenStack?"}'
{"detail":{"response":"Internal server error","cause":"An unexpected error occurred while processing the request."}}

On lighspeed-stack I see:

[18:46:28] INFO     HTTP Request: POST http://localhost:8321/v1/responses "HTTP/1.1 401 Unauthorized"                               _client.py:1740
INFO:     127.0.0.1:53008 - "POST /v1/query HTTP/1.1" 500 Internal Server Error

On the llama-stack side we can see the MCP call to list the tools fails with:

         AuthenticationRequiredError: Client error '401 Unauthorized' for url 'http://127.0.0.1:8901/openstack/'
         For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
INFO     2026-03-04 18:46:28,298 uvicorn.access:480 uncategorized: ::1:55450 - "POST /v1/responses HTTP/1.1" 401

I don't know what was different the other day, but it was working.

My debugging

If we debug what llama-stack is going to send to the MCP server we can see there are no headers:

> /home/geguileo/work/lightspeed/lightspeed-stack/.venv/lib64/python3.13/site-packages/llama_stack/providers/utils/tools/mcp.py(143)get_session()
-> import pdb; pdb.set_trace()
(Pdb) l
138                 if key in self._sessions:
139                     session, _, _ = self._sessions[key]
140                     return session
141
142                 # Create new session
143  ->             import pdb; pdb.set_trace()
144                 session, client_ctx, session_ctx = await self._create_session(endpoint, headers)
145                 self._sessions[key] = (session, client_ctx, session_ctx)
146                 logger.debug(f"Created new MCP session for {endpoint} (key: {key[:32]}...)")
147                 return session
148
(Pdb) pp headers
{}

On the lighspeed stack side we can see in src/app/endpoints/query.py in method retrieve_response we have:

303         response = await client.responses.create(
304             **responses_params.model_dump(exclude_none=True)
305         )

But if we check what the dump returns we can see that the authorization is missing:

(Pdb) pp responses_params.model_dump(exclude_none=True)
 < blah blah blah >

 'tools': [{'max_num_results': 10,
            'type': 'file_search',
            'vector_store_ids': ['rhoso_18.0']},
           {'require_approval': 'never',
            'server_label': 'rhos-osp-tools',
            'server_url': 'http://127.0.0.1:8901/openstack/',
            'type': 'mcp'},
           {'require_approval': 'never',
            'server_label': 'rhos-ocp-tools',
            'server_url': 'http://127.0.0.1:8901/openshift/',
            'type': 'mcp'}]}

We can see in llama-stack's src/llama_stack_api/openai_responses.py the caseu: authorization field is explicitly excluded from the dump:

@json_schema_type
class OpenAIResponseInputToolMCP(BaseModel):
    authorization: str | None = Field(default=None, exclude=True)

Potential fix

I tried to forcefully include the authorization in the dump, and it worked.

In file src/utils/types.py I modify the ResponsesApiParams class:

class ResponsesApiParams(BaseModel):
    def model_dump(self, *args, **kwargs):
        result = super().model_dump(*args, **kwargs)
        if self.tools:
            for i, tool in enumerate(self.tools):
                if getattr(tool, "authorization", None):
                    result["tools"][i]["authorization"] = tool.authorization
        return result

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions