Skip to content

feat: dev and invoke support for MCP and A2A protocols#554

Merged
tejaskash merged 27 commits intoaws:mainfrom
jesseturner21:protocol-dev-invoke
Mar 18, 2026
Merged

feat: dev and invoke support for MCP and A2A protocols#554
tejaskash merged 27 commits intoaws:mainfrom
jesseturner21:protocol-dev-invoke

Conversation

@tejaskash
Copy link
Contributor

@tejaskash tejaskash commented Mar 18, 2026

Summary

Makes agentcore dev and agentcore invoke protocol-aware for MCP and A2A agents. Previously both commands only understood HTTP — the dev server always ran uvicorn main:app --reload and invoke always POSTed to /invocations.

Dev server (agentcore dev)

  • MCP: Runs python main.py directly (no ASGI app to wrap with uvicorn). Server listens on port 8000. TUI auto-lists available tools on startup. Users type tool_name {"arg": "value"} to call tools.
  • A2A: Runs python main.py directly. Server listens on port 9000. Chat UX identical to HTTP — protocol difference is invisible to the user.
  • HTTP: Unchanged (uvicorn with hot-reload).

Deployed invoke (agentcore invoke)

  • MCP: Supports list-tools and call-tool via JSON-RPC over InvokeAgentRuntime SDK. TUI shows tool list and accepts tool calls.
  • A2A: Supports chat-style invoke via JSON-RPC message/send. Parses task artifacts to extract response text.
  • HTTP: Unchanged.

Non-interactive CLI

  • agentcore dev --invoke "message" — HTTP/A2A
  • agentcore dev --invoke list-tools — MCP
  • agentcore dev --invoke call-tool --tool <name> --input '{"arg": "value"}' — MCP
  • agentcore invoke --invoke "message" — deployed HTTP/A2A
  • agentcore invoke --invoke list-tools — deployed MCP
  • agentcore invoke --invoke call-tool --tool <name> --input '{"arg": "value"}' — deployed MCP

Key implementation details

  • DevConfig gains a protocol field read from the agent spec
  • Shared helpers in utils.ts: sleep, isConnectionError, getEndpointUrl, formatMcpToolList, parseJsonRpcResponse
  • mcpInitSession for lightweight MCP session init (2 requests vs 4 for full listTools)
  • MCP template updated to read PORT from environment
  • A2A uses kind: 'text' for message parts per A2A spec

Test plan

  • Unit tests pass (npm test — 2242 tests)
  • Create MCP project → agentcore dev → list tools → call tool
  • Create A2A+Strands project → agentcore dev → send prompt → get response
  • Non-interactive: agentcore dev --invoke list-tools --agent myMcpAgent
  • Non-interactive: agentcore dev --invoke call-tool --tool add_numbers --input '{"a":1,"b":2}'
  • Non-interactive: agentcore dev --invoke "hello" --agent myA2aAgent
  • Deployed invoke: agentcore invoke with MCP agent → list/call tools
  • Deployed invoke: agentcore invoke with A2A agent → chat works
  • A2A+Anthropic: verify API key from .env is present in dev server env

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
@tejaskash tejaskash requested a review from a team March 18, 2026 17:08
@github-actions github-actions bot added the size/xl PR size: XL label Mar 18, 2026
- 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
@github-actions github-actions bot removed the size/xl PR size: XL label Mar 18, 2026
@github-actions github-actions bot added the size/xl PR size: XL label Mar 18, 2026
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'
@github-actions github-actions bot removed the size/xl PR size: XL label Mar 18, 2026
@github-actions github-actions bot added the size/xl PR size: XL label Mar 18, 2026
Not needed for MCP to work — only mcp package is required.
@github-actions github-actions bot added size/xl PR size: XL and removed size/xl PR size: XL labels Mar 18, 2026
Copy link
Contributor

@aidandaly24 aidandaly24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
@github-actions github-actions bot removed the size/xl PR size: XL label Mar 18, 2026
@github-actions github-actions bot added the size/xl PR size: XL label Mar 18, 2026
Copy link
Contributor

@aidandaly24 aidandaly24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the updates, approved.

@tejaskash tejaskash merged commit c2c646c into aws:main Mar 18, 2026
16 of 17 checks passed
@tejaskash tejaskash deleted the protocol-dev-invoke branch March 18, 2026 21:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl PR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants