feat: dev and invoke support for MCP and A2A protocols#554
Merged
feat: dev and invoke support for MCP and A2A protocols#554
Conversation
Make `agentcore dev` and `agentcore invoke` protocol-aware so MCP and
A2A agents can be locally run and tested the same way HTTP agents are
today.
Dev server: MCP/A2A agents run `python main.py` directly instead of
`uvicorn --reload`, since these templates call serve functions inside
__main__ rather than exposing a module-level ASGI app.
Invoke: A2A uses JSON-RPC message/send to POST /. MCP uses JSON-RPC
initialize + tools/list + tools/call to POST /mcp. Both protocols
include retry logic for server startup race conditions.
TUI: MCP shows available tools on server ready, accepts tool calls as
`tool_name {"args"}` in the chat input. A2A uses the same chat UX as
HTTP. Protocol and endpoint URL shown in header.
CLI: New --tool and --input flags for MCP tool calls via --invoke.
Deployed invoke for non-HTTP protocols deferred with clear message.
MCP template updated to read PORT from env var for dev server
port assignment.
Three fixes from manual testing: 1. MCP "Missing session ID" error: listMcpTools now returns the session ID from the initialize handshake. The TUI and CLI both pass it to subsequent callMcpTool calls. 2. MCP tool list UX: moved tool list from scrollable conversation area into the persistent header so tools are always visible. Conversation area now only shows tool call results. 3. A2A venv setup: non-HTTP protocols checked for the python binary as a proxy for "deps installed", but python always exists once the venv is created. Now checks for uv.lock to verify deps were actually synced.
Extract ServerError, ConnectionError, SSELogger, and InvokeStreamingOptions into invoke-types.ts. Both invoke.ts and invoke-a2a.ts now import from invoke-types instead of each other, breaking the circular import that caused "Cannot access before initialization" in the esbuild bundle.
The isMcp variable was declared in the help text section but used earlier in the height calculation, causing "Cannot access before initialization" at runtime in the esbuild bundle.
The uv.lock check wasn't reliable since the lock file can exist from a previous sync before protocol-specific deps were added. Now always runs uv sync when venv exists for MCP/A2A agents. uv sync is fast (~100ms) when deps are already installed.
- Tool list in header shows at most 5 tools with overflow indicator
- Typing "list" shows all tools in the scrollable conversation area
- Input field shows placeholder "tool_name {"arg": "value"}" for MCP
- Fix lint errors in test files and DevScreen
Instead of "No deployed agents found", now shows "Invoke for deployed MCP/A2A agents is not yet supported. Use agentcore dev for local testing."
InvokeAgentRuntime supports all protocols. Remove the guards that were blocking MCP and A2A agents from appearing in deployed invoke.
- Add fetchA2AAgentCard() to fetch /.well-known/agent.json from A2A agents - DevScreen: show A2A agent card (name, description, skills) in header - InvokeScreen: show protocol in header, include protocol in agent list - useInvokeFlow: pass protocol through to InvokeConfig - Remove all deployed invoke guards for non-HTTP protocols
- A2A agents always run on port 9000 (serve_a2a default). If port 9000 is in use, show an error instead of silently using another port. - Add required messageId (UUID) to A2A message/send JSON-RPC requests, fixing "Field required" validation error from a2a-sdk. - Revert A2A template PORT changes (not needed, port is fixed).
- Use message/stream (SSE) instead of message/send for streaming - Parse artifact-update events for text content as it streams - Parse status-update events for status message text - Handle both kind:'text' and type:'text' part formats - Extract text from full Task results (artifacts + status.message) - Add "Send a message..." placeholder for A2A input in dev and invoke
Status-update events duplicate the same text as artifact-update events. Only yield text from artifact-update events to avoid showing the response twice.
Display non-terminal status states from status-update SSE events as [state] indicators. Terminal states (completed, canceled) are skipped since artifact content covers them.
A2A servers stream incremental text via status-update SSE events with message parts, then send a final artifact-update with the complete text. Previously we discarded status-update text and only yielded the artifact-update, causing long responses to appear all at once. Now extractSSEEventText yields text from status-update message parts for real incremental streaming. When status-update text has been streamed, the duplicate artifact-update is skipped. Also adds A2A task status display (Working.../Completed...) in the TUI, container server readiness polling, and lint fixes.
When MCP tools are fetched on server ready, display them in the conversation area with usage instructions. Also fixes stale closure bug where typing "list" showed old tool data by using a ref for fresh values after async fetch.
Show a clear message that deployed invoke for MCP and A2A agents is not yet supported, directing users to "agentcore dev" for local testing. Applies to both the TUI invoke screen and the non-interactive CLI invoke command.
AgentCore Runtime handles protocol translation, so deployed invoke works the same for all protocols via InvokeAgentRuntimeCommand. Remove the incorrect unsupported-protocol guards.
Show a hint explaining MCP input format when the conversation is empty, and set protocol-appropriate placeholder text on the input.
Add MCP support to deployed invoke (both TUI and CLI):
- New mcpListTools/mcpCallTool functions send JSON-RPC payloads
through InvokeAgentRuntime with mcpSessionId/mcpProtocolVersion
- TUI: auto-fetches tools on agent selection, shows tool list hint
in conversation, parses "tool_name {args}" input for tool calls
- CLI: "agentcore invoke list-tools" lists tools,
"agentcore invoke call-tool --tool name --input '{...}'" calls tools
Deployed runtimes may need >30s to cold-start. Add retry logic (3 attempts with 2s delay) to mcpListTools and mcpCallTool so they survive the "initialization time exceeded" error.
- Match MCP template to starter toolkit (mcp.run with streamable-http) - Use bedrock-agentcore-starter-toolkit dependency for deployment - Disable OTEL instrumentation for MCP agents (incompatible with mcp.run) - Fix accept header for MCP SDK calls (application/json, text/event-stream) - Tolerate JSON-RPC errors from stateless MCP initialize - Use fixed port 8000 for MCP local dev server
- Make protocol field optional in CLI schema (backward compat with existing projects) - Use fixed port 9000 for A2A and 8000 for MCP in --logs and --invoke paths - Add a2a-sdk[all] to Strands A2A template dependencies
Deployed A2A agents require JSON-RPC 2.0 message/send format, not the
HTTP {"prompt": "..."} payload. Add invokeA2ARuntime() that wraps user
text in the proper A2A wire format and parses artifacts from the
response. Wire it into both the non-interactive CLI and TUI invoke paths.
- Use shared helpers (sleep, isConnectionError, getEndpointUrl, formatMcpToolList, parseJsonRpcResponse) from utils.ts to eliminate duplication across 10+ files - Fix A2A message parts to use `kind: 'text'` per A2A spec (was `type: 'text'`) - Add mcpInitSession for lightweight MCP session init (saves 2 requests vs full listTools) - Fix double tool-list display on MCP "list" command in useInvokeFlow - Wrap fetchAgentCard/fetchMcpTools in useCallback with refs to fix stale closures - Fix ensurePythonVenv for non-HTTP: check python binary instead of running uv sync every time - Replace fake async generator with singleValueStream helper - Fix isConnectionError to use exact match for 'fetch failed'
Not needed for MCP to work — only mcp package is required.
aidandaly24
reviewed
Mar 18, 2026
Contributor
aidandaly24
left a comment
There was a problem hiding this comment.
Let me know what you think of these issues. Would understand if you would want to tackle some of these in a follow up PR.
1. Move parseJsonRpcResponse to src/lib/utils/json-rpc.ts to avoid
cross-layer dependency (aws/ importing from operations/dev/)
2. parseJsonRpcResponse now throws on unparseable input instead of
silently returning {} — prevents silent failures on HTML error pages
3. Wrap MCP/A2A invoke paths in action.ts with try-catch returning
{ success: false, error } per project convention
4. Add user-friendly error for malformed --input JSON in dev command
5. Add tests for parseA2AResponse (7 cases covering kind/type parts,
errors, history fallback, non-JSON)
6. Add tests for shared utils (getEndpointUrl, formatMcpToolList,
isConnectionError, sleep) and parseJsonRpcResponse (9 cases)
aidandaly24
approved these changes
Mar 18, 2026
Contributor
aidandaly24
left a comment
There was a problem hiding this comment.
Thanks for making the updates, approved.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes
agentcore devandagentcore invokeprotocol-aware for MCP and A2A agents. Previously both commands only understood HTTP — the dev server always ranuvicorn main:app --reloadand invoke always POSTed to/invocations.Dev server (
agentcore dev)python main.pydirectly (no ASGI app to wrap with uvicorn). Server listens on port 8000. TUI auto-lists available tools on startup. Users typetool_name {"arg": "value"}to call tools.python main.pydirectly. Server listens on port 9000. Chat UX identical to HTTP — protocol difference is invisible to the user.Deployed invoke (
agentcore invoke)list-toolsandcall-toolvia JSON-RPC over InvokeAgentRuntime SDK. TUI shows tool list and accepts tool calls.message/send. Parses task artifacts to extract response text.Non-interactive CLI
agentcore dev --invoke "message"— HTTP/A2Aagentcore dev --invoke list-tools— MCPagentcore dev --invoke call-tool --tool <name> --input '{"arg": "value"}'— MCPagentcore invoke --invoke "message"— deployed HTTP/A2Aagentcore invoke --invoke list-tools— deployed MCPagentcore invoke --invoke call-tool --tool <name> --input '{"arg": "value"}'— deployed MCPKey implementation details
DevConfiggains aprotocolfield read from the agent specutils.ts:sleep,isConnectionError,getEndpointUrl,formatMcpToolList,parseJsonRpcResponsemcpInitSessionfor lightweight MCP session init (2 requests vs 4 for full listTools)PORTfrom environmentkind: 'text'for message parts per A2A specTest plan
npm test— 2242 tests)agentcore dev→ list tools → call toolagentcore dev→ send prompt → get responseagentcore dev --invoke list-tools --agent myMcpAgentagentcore dev --invoke call-tool --tool add_numbers --input '{"a":1,"b":2}'agentcore dev --invoke "hello" --agent myA2aAgentagentcore invokewith MCP agent → list/call toolsagentcore invokewith A2A agent → chat works.envis present in dev server env