Motivation
adcp/types/core.py:48 — the Pydantic validator validate_agent_uri calls v.rstrip("/") on the entire URI before the AgentConfig is constructed. The comment says "Remove trailing slash for consistency."
This breaks the CLI (and any caller of AgentConfig) against any MCP server whose streamable_http endpoint is mounted at a slash-terminated path. FastMCP — the most common Python MCP server framework — mounts at /mcp/ by default; FastAPI/Starlette Mount(path="/mcp", app=mcp_app) similarly serves only /mcp/ and returns 404 to /mcp.
Repro (reproducible against any FastMCP-served agent)
# Server: any FastMCP agent with default mount, e.g. the FastAPI mount pattern
# app.mount("/mcp", mcp_streamable_http_app)
adcp http://my-agent.example.com:8000/mcp/ --auth <token> list_tools
Expected: connects, lists tools.
Actual:
Failed to connect to MCP agent using streamable_http transport.
Tried URLs: http://my-agent.example.com:8000/mcp ← trailing slash dropped
FAILED
Error: Failed to list tools: Failed to connect: Session terminated
Server logs confirm POST /mcp HTTP/1.1" 404.
Cause
adcp/types/core.py:38-48:
@classmethod
def validate_agent_uri(cls, v: str) -> str:
...
# Remove trailing slash for consistency
return v.rstrip("/")
Subsequent code paths consume the stripped form:
protocols/mcp.py:354-359 — builds urls_to_try from self.agent_config.agent_uri (already stripped).
protocols/mcp.py:386 — passes that URL into streamablehttp_client(...).
The urls_to_try fallback list adds <base>/mcp when the URI doesn't already end with /mcp (after rstrip), but never re-adds the slash form that the user originally provided.
Why this matters
- The user's explicit input is being normalized away — they wrote
/mcp/, the library decided /mcp was "more consistent." That's information loss in a place where path semantics matter.
- The MCP spec (2024-11-05) is silent on whether servers must accept trailing-slash variants. In practice many do not — including FastMCP itself, which is what
adcp.serve() uses internally. So this client cannot reliably connect to servers built with the project's own server-side helpers.
- The workaround (use
fastmcp.client.Client + StreamableHttpTransport directly) defeats the purpose of having a CLI / SDK abstraction for the protocol.
Proposed fix
Two changes, ~10 LOC + a unit test:
1. src/adcp/types/core.py — preserve the user's URI
- # Remove trailing slash for consistency
- return v.rstrip("/")
+ return v
(If "consistency" is genuinely desired for non-MCP-path URLs — e.g., https://agent.example.com/ vs. https://agent.example.com — the right place for that normalization is when those URIs are used as a base for joining other paths, not in the validator that captures user intent.)
2. src/adcp/protocols/mcp.py — make fallbacks cover both forms
- urls_to_try = [self.agent_config.agent_uri]
-
- if not self.agent_config.agent_uri.rstrip("/").endswith("/mcp"):
- base_uri = self.agent_config.agent_uri.rstrip("/")
- urls_to_try.append(f"{base_uri}/mcp")
+ uri = self.agent_config.agent_uri
+ urls_to_try = [uri]
+ # MCP servers vary on whether they mount at "/mcp" or "/mcp/".
+ # Try the alternate form as a fallback regardless of which the user supplied.
+ if uri.endswith("/"):
+ urls_to_try.append(uri.rstrip("/"))
+ elif uri.rstrip("/").endswith("/mcp"):
+ urls_to_try.append(f"{uri}/")
+ else:
+ base = uri.rstrip("/")
+ urls_to_try.extend([f"{base}/mcp", f"{base}/mcp/"])
3. Test
Add to the existing AgentConfig validator tests:
def test_agent_uri_preserves_trailing_slash():
cfg = AgentConfig(id="x", agent_uri="https://example.com/mcp/", protocol="mcp")
assert cfg.agent_uri == "https://example.com/mcp/"
Add to the mcp.py connect-fallback tests a parametrized case for slash-terminated input.
Workaround until merged
from fastmcp.client import Client
from fastmcp.client.transports import StreamableHttpTransport
t = StreamableHttpTransport(
url="http://my-agent.example.com:8000/mcp/",
headers={"x-adcp-auth": "<token>"},
)
async with Client(t) as c:
print(await c.list_tools())
Environment
- adcp 4.4.0 (PyPI)
- Spec target: 3.0.5
- Server: FastMCP via
app.mount("/mcp", mcp_streamable_http_app) (Anthropic Python MCP SDK pattern)
- Client: macOS,
uvx adcp
Motivation
adcp/types/core.py:48— the Pydantic validatorvalidate_agent_uricallsv.rstrip("/")on the entire URI before theAgentConfigis constructed. The comment says "Remove trailing slash for consistency."This breaks the CLI (and any caller of
AgentConfig) against any MCP server whose streamable_http endpoint is mounted at a slash-terminated path. FastMCP — the most common Python MCP server framework — mounts at/mcp/by default; FastAPI/StarletteMount(path="/mcp", app=mcp_app)similarly serves only/mcp/and returns 404 to/mcp.Repro (reproducible against any FastMCP-served agent)
Expected: connects, lists tools.
Actual:
Server logs confirm
POST /mcp HTTP/1.1" 404.Cause
adcp/types/core.py:38-48:Subsequent code paths consume the stripped form:
protocols/mcp.py:354-359— buildsurls_to_tryfromself.agent_config.agent_uri(already stripped).protocols/mcp.py:386— passes that URL intostreamablehttp_client(...).The
urls_to_tryfallback list adds<base>/mcpwhen the URI doesn't already end with/mcp(after rstrip), but never re-adds the slash form that the user originally provided.Why this matters
/mcp/, the library decided/mcpwas "more consistent." That's information loss in a place where path semantics matter.adcp.serve()uses internally. So this client cannot reliably connect to servers built with the project's own server-side helpers.fastmcp.client.Client + StreamableHttpTransportdirectly) defeats the purpose of having a CLI / SDK abstraction for the protocol.Proposed fix
Two changes, ~10 LOC + a unit test:
1.
src/adcp/types/core.py— preserve the user's URI(If "consistency" is genuinely desired for non-MCP-path URLs — e.g.,
https://agent.example.com/vs.https://agent.example.com— the right place for that normalization is when those URIs are used as a base for joining other paths, not in the validator that captures user intent.)2.
src/adcp/protocols/mcp.py— make fallbacks cover both forms3. Test
Add to the existing
AgentConfigvalidator tests:Add to the
mcp.pyconnect-fallback tests a parametrized case for slash-terminated input.Workaround until merged
Environment
app.mount("/mcp", mcp_streamable_http_app)(Anthropic Python MCP SDK pattern)uvx adcp