Skip to content

Commit 051ee3b

Browse files
jeremyederclaude
andauthored
fix: align MCP client with public API session contract (#35)
The MCP client was sending field names that don't match the public API's CreateSessionRequest DTO, causing session creation to fail with validation errors. Also removes parameters the public API doesn't support. - client.py: initialPrompt → task, llmConfig.model → model - client.py: remove interactive/timeout params (not in public API) - client.py: transform repos from bare strings to {url: str} objects - client.py: fix clone_session to use same corrected field names - client.py: update SESSION_TEMPLATES to use task/model fields - server.py: remove interactive/timeout from tool schema and dispatch Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d510040 commit 051ee3b

File tree

2 files changed

+40
-34
lines changed

2 files changed

+40
-34
lines changed

src/mcp_acp/client.py

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,50 @@
44
a simplified REST API for managing AgenticSessions.
55
"""
66

7+
import json
78
import os
89
import re
910
from datetime import datetime, timedelta
1011
from typing import Any
1112

1213
import httpx
1314

14-
from mcp_acp.settings import load_clusters_config, load_settings
15+
from mcp_acp.settings import _acpctl_config_path, load_clusters_config, load_settings
1516
from utils.pylogger import get_python_logger
1617

1718
logger = get_python_logger()
1819

1920
LABEL_VALUE_PATTERN = re.compile(r"^[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?$")
2021

22+
23+
def _read_acpctl_token() -> str | None:
24+
"""Read token from the acpctl CLI config file as a last-resort fallback.
25+
26+
Used when clusters.yaml exists but a cluster entry has no token.
27+
"""
28+
try:
29+
data = json.loads(_acpctl_config_path().read_text())
30+
return data.get("access_token") or None
31+
except (FileNotFoundError, json.JSONDecodeError, OSError):
32+
return None
33+
34+
2135
SESSION_TEMPLATES: dict[str, dict[str, Any]] = {
2236
"triage": {
23-
"workflow": "triage",
24-
"llmConfig": {"model": "claude-sonnet-4", "temperature": 0.7},
37+
"task": "Triage: investigate and classify the issue.",
38+
"model": "claude-sonnet-4",
2539
},
2640
"bugfix": {
27-
"workflow": "bugfix",
28-
"llmConfig": {"model": "claude-sonnet-4", "temperature": 0.3},
41+
"task": "Bugfix: diagnose and fix the reported bug.",
42+
"model": "claude-sonnet-4",
2943
},
3044
"feature": {
31-
"workflow": "feature-development",
32-
"llmConfig": {"model": "claude-sonnet-4", "temperature": 0.5},
45+
"task": "Feature development: implement the requested feature.",
46+
"model": "claude-sonnet-4",
3347
},
3448
"exploration": {
35-
"workflow": "codebase-exploration",
36-
"llmConfig": {"model": "claude-sonnet-4", "temperature": 0.8},
49+
"task": "Codebase exploration: explore and document the codebase.",
50+
"model": "claude-sonnet-4",
3751
},
3852
}
3953

@@ -97,12 +111,20 @@ def _get_cluster_config(self, cluster_name: str | None = None) -> dict[str, Any]
97111
}
98112

99113
def _get_token(self, cluster_config: dict[str, Any]) -> str:
100-
"""Get authentication token for a cluster."""
101-
token = cluster_config.get("token") or os.getenv("ACP_TOKEN")
114+
"""Get authentication token for a cluster.
115+
116+
Resolution order:
117+
1. Per-cluster token in clusters.yaml
118+
2. ACP_TOKEN environment variable
119+
3. acpctl CLI config (~/.config/ambient/config.json)
120+
"""
121+
token = cluster_config.get("token") or os.getenv("ACP_TOKEN") or _read_acpctl_token()
102122

103123
if not token:
104124
raise ValueError(
105-
"No authentication token available. Set 'token' in clusters.yaml or ACP_TOKEN environment variable."
125+
"No authentication token available. "
126+
"Run 'acpctl login --token <token> --url <url>', "
127+
"set 'token' in clusters.yaml, or set ACP_TOKEN environment variable."
106128
)
107129

108130
return token
@@ -338,26 +360,22 @@ async def create_session(
338360
initial_prompt: str,
339361
display_name: str | None = None,
340362
repos: list[str] | None = None,
341-
interactive: bool = False,
342363
model: str = "claude-sonnet-4",
343-
timeout: int = 900,
344364
dry_run: bool = False,
345365
) -> dict[str, Any]:
346366
"""Create an AgenticSession with a custom prompt."""
347367
self._validate_input(project, "project")
348368

349369
session_data: dict[str, Any] = {
350-
"initialPrompt": initial_prompt,
351-
"interactive": interactive,
352-
"llmConfig": {"model": model},
353-
"timeout": timeout,
370+
"task": initial_prompt,
371+
"model": model,
354372
}
355373

356374
if display_name:
357375
session_data["displayName"] = display_name
358376

359377
if repos:
360-
session_data["repos"] = repos
378+
session_data["repos"] = [{"url": r} for r in repos]
361379

362380
if dry_run:
363381
return {
@@ -401,7 +419,7 @@ async def create_session_from_template(
401419
}
402420

403421
if repos:
404-
session_data["repos"] = repos
422+
session_data["repos"] = [{"url": r} for r in repos]
405423

406424
if dry_run:
407425
return {
@@ -524,14 +542,10 @@ async def clone_session(
524542
source = await self._request("GET", f"/v1/sessions/{source_session}", project)
525543

526544
clone_data: dict[str, Any] = {
527-
"displayName": new_display_name,
528-
"initialPrompt": source.get("initialPrompt", ""),
529-
"interactive": source.get("interactive", False),
530-
"timeout": source.get("timeout", 900),
545+
"task": source.get("task", ""),
546+
"model": source.get("model", "claude-sonnet-4"),
531547
}
532548

533-
if source.get("llmConfig"):
534-
clone_data["llmConfig"] = source["llmConfig"]
535549
if source.get("repos"):
536550
clone_data["repos"] = source["repos"]
537551

src/mcp_acp/server.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,7 @@ async def list_tools() -> list[Tool]:
111111
},
112112
"display_name": {"type": "string", "description": "Human-readable display name"},
113113
"repos": {"type": "array", "items": {"type": "string"}, "description": "Repository URLs to clone"},
114-
"interactive": {
115-
"type": "boolean",
116-
"description": "Create an interactive session",
117-
"default": False,
118-
},
119114
"model": {"type": "string", "description": "LLM model to use", "default": "claude-sonnet-4"},
120-
"timeout": {"type": "integer", "description": "Timeout in seconds", "default": 900, "minimum": 60},
121115
"dry_run": _DRY_RUN,
122116
},
123117
"required": ["initial_prompt"],
@@ -491,9 +485,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
491485
initial_prompt=arguments["initial_prompt"],
492486
display_name=arguments.get("display_name"),
493487
repos=arguments.get("repos"),
494-
interactive=arguments.get("interactive", False),
495488
model=arguments.get("model", "claude-sonnet-4"),
496-
timeout=arguments.get("timeout", 900),
497489
dry_run=arguments.get("dry_run", False),
498490
)
499491
text = format_session_created(result)

0 commit comments

Comments
 (0)