镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

[Bug]: REST tenant routes are mounted before path_prefix, so tenant requests from the SDK client get 404 #1376

Description

@Revinand

What happened?

With a non-empty path_prefix, create_rest_routes serves the tenant routes in front of the prefix, at /{tenant}{path_prefix}/.... The SDK's own REST client sends the tenant after the prefix, at {path_prefix}/{tenant}/..., so a client whose AgentInterface sets tenant gets a 404 on every call.

In src/a2a/server/routes/rest_routes.py, the tenant routes come from Mount(path='/{tenant}', routes=base_route_objects), and base_route_objects already include path_prefix. On the client side, RestTransport._get_path returns /{tenant}{base_path}, which is appended to the interface URL. The HTTP bindings in a2a.proto also put the tenant right after the interface URL (post: "/{tenant}/message:send"). The hello_world_agent sample serves REST with path_prefix='/a2a/rest' and advertises an interface URL ending in /a2a/rest, so the prefix is meant to be part of the interface URL.

This changed in #892. Before it, the REST adapter built /{tenant}{path} and the FastAPI app added rpc_url in front, so the prefix came first. Without a prefix both layouts are the same, so only servers that set path_prefix are affected.

To reproduce, pip install "a2a-sdk[http-server]==1.2.3" and run:

import asyncio
from unittest.mock import AsyncMock

import httpx
from starlette.applications import Starlette

from a2a.client import ClientConfig, ClientFactory
from a2a.server.request_handlers import RequestHandler
from a2a.server.routes import create_rest_routes
from a2a.types import AgentCard, AgentInterface, ListTasksRequest, ListTasksResponse

handler = AsyncMock(spec=RequestHandler)
handler.on_list_tasks.return_value = ListTasksResponse()
app = Starlette(routes=create_rest_routes(handler, path_prefix="/a2a/rest"))

card = AgentCard(supported_interfaces=[AgentInterface(
    url="http://test/a2a/rest", protocol_binding="HTTP+JSON",
    protocol_version="1.0", tenant="acme")])


async def main():
    async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as http:
        for path in ("/a2a/rest/acme/tasks", "/acme/a2a/rest/tasks"):
            r = await http.get(path, headers={"A2A-Version": "1.0"})
            print(path, r.status_code)
        client = ClientFactory(ClientConfig(httpx_client=http, supported_protocol_bindings=["HTTP+JSON"])).create(card)
        try:
            await client.list_tasks(ListTasksRequest())
            print("list_tasks: ok")
        except Exception as e:
            print(f"list_tasks: {type(e).__name__}: {e}")


asyncio.run(main())

I expected /a2a/rest/acme/tasks to return 200 and list_tasks to succeed.

I have a fix with a test and will open a PR.

Relevant log output

/a2a/rest/acme/tasks 404
/acme/a2a/rest/tasks 200
list_tasks: MethodNotFoundError: Resource not found: http://test/a2a/rest/acme/tasks

Code of Conduct

  • I agree to follow this project's Code of Conduct

Activity

  1. added
    component: serverIssues related to frameworks for agent execution, HTTP/event handling, database persistence logic.
    on Oct 27, 2026
  2. added theissue type on Oct 27, 2026
  3. rohityan commented on Oct 27, 2026

    @rohityan

    Hi @Revinand, thanks for reporting this!

    I was able to reproduce the issue. You identified the exact root cause, in create_rest_routes, nesting base_route_objects under Mount(path='/{tenant}', ...) registers endpoints at /{tenant}{path_prefix}/... instead of {path_prefix}/{tenant}/.... Because RestTransport._get_path appends the tenant segment after the interface URL prefix, Starlette fails to match incoming client requests and returns 404.

    Your fix in PR #1377 looks correct and resolves the mismatch cleanly. Assigning this issue to you while the PR is under review!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

component: serverIssues related to frameworks for agent execution, HTTP/event handling, database persistence logic.status:awaiting response

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions