Skip to content

feat(acp): register client-provided MCP servers as agent tools#4705

Merged
teknium1 merged 5 commits into
mainfrom
feat/acp-mcp-server-registration
Apr 3, 2026
Merged

feat(acp): register client-provided MCP servers as agent tools#4705
teknium1 merged 5 commits into
mainfrom
feat/acp-mcp-server-registration

Conversation

@teknium1

@teknium1 teknium1 commented Apr 3, 2026

Copy link
Copy Markdown
Contributor

Salvage of #4496 (by @Jckwind / TheProxyCompany) with fixes and tests.

What it does

ACP clients pass MCP server definitions (stdio and HTTP/SSE) in session/new, load_session, resume_session, and fork_session. Previously these were silently dropped. This wires them into the MCP registration pipeline so client-provided tools are discovered and available before the first prompt.

Changes from original PR

  • Bug fix: tool name sanitization — extracted sanitize_mcp_name_component() that replaces all non-[A-Za-z0-9_] characters, fixing server names like ai.exa/exa that crash Anthropic's API
  • Bug fix: tool results in ACP eventsstep_callback now includes tool results so tool_call_update events have populated rawOutput
  • Refactor — extracted register_mcp_servers() public API from discover_mcp_tools()

Fixes added during salvage

  • Gateway backward compattool_names in agent:step hook events stays list[str] (new tools field carries enriched dicts)
  • Summary logging for register_mcp_servers() so ACP callers get visibility
  • 43 new tests — unit tests for sanitization + register API, ACP integration tests, E2E tests for full MCP registration → prompt → tool event flow, gateway compat tests

Test results

296 passed (ACP + MCP suites)
7497 passed (full suite, 14 pre-existing failures unrelated)

Closes #4496

Jckwind and others added 5 commits April 1, 2026 16:35
ACP clients pass MCP server definitions in session/new, load_session,
resume_session, and fork_session. Previously these were accepted but
silently ignored — the agent never connected to them.

This wires the mcp_servers parameter into the existing MCP registration
pipeline (tools/mcp_tool.py) so client-provided servers are connected,
their tools discovered, and the agent's tool surface refreshed before
the first prompt.

Changes:

tools/mcp_tool.py:
- Extract sanitize_mcp_name_component() to replace all non-[A-Za-z0-9_]
  characters (fixes crash when server names contain / or other chars
  that violate provider tool-name validation rules)
- Use it in _convert_mcp_schema, _sync_mcp_toolsets, _build_utility_schemas
- Extract register_mcp_servers(servers: dict) as a public API that takes
  an explicit {name: config} map. discover_mcp_tools() becomes a thin
  wrapper that loads config.yaml and calls register_mcp_servers()

acp_adapter/server.py:
- Add _register_session_mcp_servers() which converts ACP McpServerStdio /
  McpServerHttp / McpServerSse objects to Hermes MCP config dicts,
  registers them via asyncio.to_thread (avoids blocking the ACP event
  loop), then rebuilds agent.tools, valid_tool_names, and invalidates
  the cached system prompt
- Call it from new_session, load_session, resume_session, fork_session

Tested with Eden (theproxycompany.com) as ACP client — 5 MCP servers
(HTTP + stdio) registered successfully, 110 tools available to the agent.
…ate events

The step_callback previously only forwarded tool names as strings,
so build_tool_complete received result=None and ACP tool_call_update
events had empty content/rawOutput. Now prev_tools carries dicts with
both name and result by pairing each tool_call with its matching
tool-role message via tool_call_id.
The PR changed prev_tools from list[str] to list[dict] with name/result
keys.  The gateway's _step_callback_sync passed this directly to hooks
as 'tool_names', breaking user-authored hooks that call
', '.join(tool_names).

Now:
- 'tool_names' always contains strings (backward-compatible)
- 'tools' carries the enriched dicts for hooks that want results

Also adds summary logging to register_mcp_servers() and comprehensive
tests for all three PR changes:
- sanitize_mcp_name_component edge cases
- register_mcp_servers public API
- _register_session_mcp_servers ACP integration
- step_callback result forwarding
- gateway normalization backward compat
Tests the full ACP flow:
- new_session with mcpServers → config conversion → register_mcp_servers
- prompt → tool_progress_callback → ToolCallStart events
- step_callback with results → ToolCallUpdate with rawOutput
- toolCallId pairing between start and completion events
- server names with slashes/dots sanitized correctly
- all session lifecycle methods (load/resume/fork) register MCP
@teknium1 teknium1 merged commit 3659e1f into main Apr 3, 2026
5 of 6 checks passed
@github-actions

github-actions Bot commented Apr 3, 2026

Copy link
Copy Markdown
Contributor

⚠️ Supply Chain Risk Detected

This PR contains patterns commonly associated with supply chain attacks. This does not mean the PR is malicious — but these patterns require careful human review before merging.

⚠️ WARNING: exec() or eval() usage

Dynamic code execution can hide malicious behavior, especially when combined with base64 or network fetches.

Matches (first 20):

27182:+Sandbox.create() + Sandbox.exec() calls.  This eliminates the need for
27316:+    Uses Modal's Sandbox.create() for container lifecycle and Sandbox.exec()
30941:+Fast peer card retrieval (no LLM). Returns a curated list of key facts about the user.

⚠️ WARNING: Install hook files modified

These files can execute code during package installation or interpreter startup.

Files:

hermes_cli/memory_setup.py
hermes_cli/setup.py
tests/hermes_cli/test_setup.py

Automated scan triggered by supply-chain-audit. If this is a false positive, a maintainer can approve after manual review.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants