Skip to content

Commit 413657e

Browse files
saqadriandrew-lastmile
authored andcommitted
Fix async_tool workflow name binding (lastmile-ai#568)
* Fix async_tool workflow name binding * Run formatter
1 parent d3cbaca commit 413657e

File tree

5 files changed

+312
-98
lines changed

5 files changed

+312
-98
lines changed

src/mcp_agent/cli/commands/install.py

Lines changed: 73 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,18 @@
4949
print_success,
5050
)
5151

52-
app = typer.Typer(help="Install MCP server to client applications", no_args_is_help=False)
52+
app = typer.Typer(
53+
help="Install MCP server to client applications", no_args_is_help=False
54+
)
5355

5456

5557
def _get_claude_desktop_config_path() -> Path:
5658
"""Get the Claude Desktop config path based on platform."""
5759
if platform.system() == "Darwin": # macOS
58-
return Path.home() / "Library/Application Support/Claude/claude_desktop_config.json"
60+
return (
61+
Path.home()
62+
/ "Library/Application Support/Claude/claude_desktop_config.json"
63+
)
5964
elif platform.system() == "Windows":
6065
return Path.home() / "AppData/Roaming/Claude/claude_desktop_config.json"
6166
else: # Linux
@@ -83,7 +88,9 @@ def _get_claude_desktop_config_path() -> Path:
8388
}
8489

8590

86-
def _merge_mcp_json(existing: dict, server_name: str, server_config: dict, format_type: str = "mcp") -> dict:
91+
def _merge_mcp_json(
92+
existing: dict, server_name: str, server_config: dict, format_type: str = "mcp"
93+
) -> dict:
8794
"""
8895
Merge a server configuration into existing MCP JSON.
8996
@@ -111,7 +118,9 @@ def _merge_mcp_json(existing: dict, server_name: str, server_config: dict, forma
111118
servers = dict(existing["mcp"].get("servers") or {})
112119
else:
113120
for k, v in existing.items():
114-
if isinstance(v, dict) and ("url" in v or "transport" in v or "command" in v or "type" in v):
121+
if isinstance(v, dict) and (
122+
"url" in v or "transport" in v or "command" in v or "type" in v
123+
):
115124
servers[k] = v
116125

117126
servers[server_name] = server_config
@@ -141,7 +150,9 @@ def walk(obj):
141150
walk(v)
142151
elif isinstance(obj, list):
143152
for i, v in enumerate(obj):
144-
if isinstance(v, str) and v.lower().startswith("authorization: bearer "):
153+
if isinstance(v, str) and v.lower().startswith(
154+
"authorization: bearer "
155+
):
145156
obj[i] = "Authorization: Bearer ***"
146157
else:
147158
walk(v)
@@ -158,7 +169,9 @@ def _write_json(path: Path, data: dict) -> None:
158169
if path.exists() and os.name == "posix":
159170
original_mode = os.stat(path).st_mode & 0o777
160171

161-
tmp_fd, tmp_name = tempfile.mkstemp(dir=str(path.parent), prefix=path.name, suffix=".tmp")
172+
tmp_fd, tmp_name = tempfile.mkstemp(
173+
dir=str(path.parent), prefix=path.name, suffix=".tmp"
174+
)
162175
try:
163176
with os.fdopen(tmp_fd, "w", encoding="utf-8") as f:
164177
f.write(json.dumps(data, indent=2))
@@ -203,7 +216,13 @@ def _server_hostname(server_url: str, app_name: Optional[str] = None) -> str:
203216
return hostname or "mcp-server"
204217

205218

206-
def _build_server_config(server_url: str, transport: str = "http", for_claude_desktop: bool = False, for_vscode: bool = False, api_key: str = None) -> dict:
219+
def _build_server_config(
220+
server_url: str,
221+
transport: str = "http",
222+
for_claude_desktop: bool = False,
223+
for_vscode: bool = False,
224+
api_key: str = None,
225+
) -> dict:
207226
"""Build server configuration dictionary with auth header.
208227
209228
For Claude Desktop, wraps HTTP/SSE servers with mcp-remote stdio wrapper with actual API key.
@@ -228,39 +247,39 @@ def _build_server_config(server_url: str, transport: str = "http", for_claude_de
228247
"mcp-remote",
229248
server_url,
230249
"--header",
231-
f"Authorization: Bearer {api_key}"
232-
]
250+
f"Authorization: Bearer {api_key}",
251+
],
233252
}
234253
elif for_vscode:
235254
# VSCode uses "type" instead of "transport"
236255
return {
237256
"type": transport,
238257
"url": server_url,
239-
"headers": {
240-
"Authorization": f"Bearer {api_key}"
241-
}
258+
"headers": {"Authorization": f"Bearer {api_key}"},
242259
}
243260
else:
244261
# Direct HTTP/SSE connection for Cursor with embedded API key
245262
return {
246263
"url": server_url,
247264
"transport": transport,
248-
"headers": {
249-
"Authorization": f"Bearer {api_key}"
250-
}
265+
"headers": {"Authorization": f"Bearer {api_key}"},
251266
}
252267

253268

254269
@app.callback(invoke_without_command=True)
255270
def install(
256-
server_identifier: str = typer.Argument(
257-
..., help="Server URL to install"
258-
),
271+
server_identifier: str = typer.Argument(..., help="Server URL to install"),
259272
client: str = typer.Option(
260-
..., "--client", "-c", help="Client to install to: vscode|claude_code|cursor|claude_desktop|chatgpt"
273+
...,
274+
"--client",
275+
"-c",
276+
help="Client to install to: vscode|claude_code|cursor|claude_desktop|chatgpt",
261277
),
262278
name: Optional[str] = typer.Option(
263-
None, "--name", "-n", help="Server name in client config (auto-generated if not provided)"
279+
None,
280+
"--name",
281+
"-n",
282+
help="Server name in client config (auto-generated if not provided)",
264283
),
265284
dry_run: bool = typer.Option(
266285
False, "--dry-run", help="Show what would be installed without writing files"
@@ -310,15 +329,16 @@ def install(
310329
f"Unsupported client: {client}. Supported clients: vscode, claude_code, cursor, claude_desktop, chatgpt"
311330
)
312331

313-
314332
effective_api_key = api_key or settings.API_KEY or load_api_key_credentials()
315333
if not effective_api_key:
316334
raise CLIError(
317335
"Must be logged in to install. Run 'mcp-agent login', set MCP_API_KEY environment variable, or specify --api-key option."
318336
)
319337

320338
server_url = server_identifier
321-
if not server_identifier.startswith("http://") and not server_identifier.startswith("https://"):
339+
if not server_identifier.startswith("http://") and not server_identifier.startswith(
340+
"https://"
341+
):
322342
raise CLIError(
323343
f"Server identifier must be a URL starting with http:// or https://. Got: {server_identifier}"
324344
)
@@ -329,7 +349,9 @@ def install(
329349

330350
console.print("\n[bold cyan]Installing MCP Server[/bold cyan]\n")
331351
print_info(f"Server URL: {server_url}")
332-
print_info(f"Client: {CLIENT_CONFIGS.get(client_lc, {}).get('description', client_lc)}")
352+
print_info(
353+
f"Client: {CLIENT_CONFIGS.get(client_lc, {}).get('description', client_lc)}"
354+
)
333355

334356
mcp_client = MCPAppClient(
335357
api_url=api_url or DEFAULT_API_BASE_URL, api_key=effective_api_key
@@ -349,9 +371,9 @@ def install(
349371
if not app_info:
350372
app_info = run_async(mcp_client.get_app(server_url=server_url))
351373

352-
has_unauth_access = (
353-
app_info.unauthenticatedAccess is True or
354-
(app_info.appServerInfo and app_info.appServerInfo.unauthenticatedAccess is True)
374+
has_unauth_access = app_info.unauthenticatedAccess is True or (
375+
app_info.appServerInfo
376+
and app_info.appServerInfo.unauthenticatedAccess is True
355377
)
356378

357379
if not has_unauth_access:
@@ -379,7 +401,9 @@ def install(
379401
raise
380402
except Exception as e:
381403
print_info(f"Warning: Could not verify unauthenticated access: {e}")
382-
print_info("Proceeding with installation, but ChatGPT may not be able to connect.")
404+
print_info(
405+
"Proceeding with installation, but ChatGPT may not be able to connect."
406+
)
383407

384408
console.print(
385409
Panel(
@@ -405,19 +429,28 @@ def install(
405429
if client_lc == "claude_code":
406430
if dry_run:
407431
console.print("\n[bold yellow]DRY RUN - Would run:[/bold yellow]")
408-
console.print(f"claude mcp add {server_name} {server_url} -t {transport} -H 'Authorization: Bearer <api-key>' -s user")
432+
console.print(
433+
f"claude mcp add {server_name} {server_url} -t {transport} -H 'Authorization: Bearer <api-key>' -s user"
434+
)
409435
return
410436

411437
try:
412438
cmd = [
413-
"claude", "mcp", "add",
439+
"claude",
440+
"mcp",
441+
"add",
414442
server_name,
415443
server_url,
416-
"-t", transport,
417-
"-H", f"Authorization: Bearer {effective_api_key}",
418-
"-s", "user"
444+
"-t",
445+
transport,
446+
"-H",
447+
f"Authorization: Bearer {effective_api_key}",
448+
"-s",
449+
"user",
419450
]
420-
result = subprocess.run(cmd, capture_output=True, text=True, check=True, timeout=30)
451+
result = subprocess.run(
452+
cmd, capture_output=True, text=True, check=True, timeout=30
453+
)
421454
print_success(f"Server '{server_name}' installed to Claude Code")
422455
console.print(result.stdout)
423456
return
@@ -455,14 +488,16 @@ def install(
455488
f"Server '{server_name}' already exists in {config_path}. Use --force to overwrite."
456489
)
457490
except json.JSONDecodeError as e:
458-
raise CLIError(f"Failed to parse existing config at {config_path}: {e}") from e
491+
raise CLIError(
492+
f"Failed to parse existing config at {config_path}: {e}"
493+
) from e
459494

460495
server_config = _build_server_config(
461496
server_url,
462497
transport,
463498
for_claude_desktop=is_claude_desktop,
464499
for_vscode=is_vscode,
465-
api_key=effective_api_key
500+
api_key=effective_api_key,
466501
)
467502

468503
if is_claude_desktop or is_cursor:
@@ -472,7 +507,9 @@ def install(
472507
else:
473508
format_type = "mcp"
474509

475-
merged_config = _merge_mcp_json(existing_config, server_name, server_config, format_type)
510+
merged_config = _merge_mcp_json(
511+
existing_config, server_name, server_config, format_type
512+
)
476513

477514
if dry_run:
478515
console.print("\n[bold]Would write to:[/bold]", config_path)

src/mcp_agent/cli/main.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,9 @@ def main(
188188
)(login)
189189

190190
# Register install command as top-level
191-
app.add_typer(install_cmd.app, name="install", help="Install MCP server to client applications")
191+
app.add_typer(
192+
install_cmd.app, name="install", help="Install MCP server to client applications"
193+
)
192194

193195

194196
def run() -> None:

src/mcp_agent/server/app_server.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2295,10 +2295,15 @@ async def _adapter(**kw):
22952295

22962296
if run_tool_name not in registered:
22972297
# Build a wrapper mirroring original function params (excluding app_ctx/ctx)
2298-
async def _async_wrapper(**kwargs):
2299-
ctx: MCPContext = kwargs.pop("__context__")
2300-
# Start workflow and return workflow_id/run_id (do not wait)
2301-
return await _workflow_run(ctx, wname_local, kwargs)
2298+
def _make_async_wrapper(bound_wname: str):
2299+
async def _async_wrapper(**kwargs):
2300+
ctx: MCPContext = kwargs.pop("__context__")
2301+
# Start workflow and return workflow_id/run_id (do not wait)
2302+
return await _workflow_run(ctx, bound_wname, kwargs)
2303+
2304+
return _async_wrapper
2305+
2306+
_async_wrapper = _make_async_wrapper(wname_local)
23022307

23032308
# Mirror original signature and annotations similar to sync path
23042309
ann = dict(getattr(fn, "__annotations__", {}))

0 commit comments

Comments
 (0)