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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions