-
Notifications
You must be signed in to change notification settings - Fork 464
fix(#840): Add user input for A2A agent testing and fix tool visibility #2001
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR addresses issue #840 by adding user input functionality for A2A agent testing and fixing tool visibility issues. The changes enable users to test A2A agents with custom queries through a modal dialog, and ensure that A2A agent tools appear in the Global Tools tab by defaulting their visibility to "public".
Key changes:
- Added modal-based testing interface with customizable query input for A2A agents
- Fixed A2A tool visibility by defaulting to "public" when agent visibility is unset
- Added comprehensive error handling for tool creation during agent registration
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
mcpgateway/admin.py |
Parses request body to extract user-provided query for A2A agent testing |
mcpgateway/templates/admin.html |
Adds A2A test modal with query textarea and result display |
mcpgateway/static/admin.js |
Implements modal handling, form submission, and cleanup for A2A testing |
mcpgateway/services/tool_service.py |
Defaults tool visibility to "public" when agent visibility is unset |
mcpgateway/services/a2a_service.py |
Wraps tool creation in try-catch to prevent agent registration failure |
scripts/demo_a2a_agent.py |
Provides standalone demo A2A agent with calculator and weather tools |
tests/unit/mcpgateway/test_issue_840_a2a_agent.py |
Comprehensive unit tests covering both reported issues |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
84bbae4 to
10ad748
Compare
10ad748 to
826d958
Compare
- Add user input field for A2A agent testing: The test button now opens a modal with a query input field, allowing users to send custom queries like `calc: 7*8` or `weather: Dallas` instead of a hardcoded message - Fix A2A agent tools visibility: Tools created for A2A agents now default to `public` visibility when the agent's visibility is not set, ensuring they appear in the Global Tools tab - Fix transaction handling: Commit agent BEFORE attempting tool creation to prevent agent loss if ToolService.register_tool calls db.rollback() - Add error handling for tool creation during agent registration - Add demo A2A agent script for testing - Add unit tests for both issues - Add integration tests using official a2a-sdk (23 tests) Closes #840 Signed-off-by: Mihai Criveti <[email protected]>
826d958 to
adbb4bc
Compare
Signed-off-by: Mihai Criveti <[email protected]>
Signed-off-by: Mihai Criveti <[email protected]>
Signed-off-by: Mihai Criveti <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR #2001: fix(#840): Add user input for A2A agent testing and fix tool visibility
Overview
This PR addresses Issue #840 with the following improvements:
- User Input for A2A Agent Testing - The admin UI test button now opens a modal with a query input field
- Tool Visibility Fix - A2A agent tools now default to
publicvisibility when agent visibility is unset - Transaction Handling Fix - Agent is committed before tool creation to prevent data loss on rollback
- Cache Invalidation Resilience - Cache failures don't fail the request when agent is already committed
- Custom Agent Query Format Fix - Custom (non-JSONRPC) agents now receive flat query strings instead of JSONRPC message objects
- Integration Test Marker - Integration tests require
--with-integrationflag (opt-in) - Documentation Update - Added "Local Testing" section to A2A docs
Files Changed
| File | Changes |
|---|---|
mcpgateway/admin.py |
Added request body parsing to extract user query for A2A test endpoint |
mcpgateway/services/a2a_service.py |
Fixed transaction handling: commit agent before tool creation; wrapped cache invalidation in try/except |
mcpgateway/services/tool_service.py |
Default tool visibility to public when agent visibility is None; Fixed custom agent query format (pass flat strings, not JSONRPC message objects) |
mcpgateway/static/admin.js |
Added A2A test modal with query textarea, open/close/submit handlers |
mcpgateway/templates/admin.html |
Added A2A test modal HTML structure |
pyproject.toml |
Added a2a-sdk[http-server] as dev dependency |
scripts/demo_a2a_agent.py |
New demo A2A agent with calculator and weather tools |
tests/unit/mcpgateway/test_issue_840_a2a_agent.py |
Unit tests for the feature (10 tests, including custom agent query format tests) |
tests/integration/test_a2a_sdk_integration.py |
Integration tests using official A2A SDK (23 tests); Added pytestmark = pytest.mark.integration |
tests/unit/mcpgateway/services/test_a2a_service.py |
Updated commit count expectation (2 → 3) |
docs/docs/using/agents/a2a.md |
Added "Local Testing" section with demo agent, SDK samples, and Admin UI instructions |
Key Implementation Details
1. User Query Extraction (admin.py)
# Parse request body to get user-provided query
default_message = "Hello from MCP Gateway Admin UI test!"
try:
body = await request.json()
# Use 'or' to also handle empty string queries
user_query = (body.get("query") if body else None) or default_message
except Exception:
user_query = default_message2. Transaction Handling Fix (a2a_service.py)
Problem: ToolService.register_tool() calls db.rollback() on error, which would undo a pending (flushed but uncommitted) agent.
Solution: Commit agent FIRST before attempting tool creation:
db.add(new_agent)
# Commit agent FIRST to ensure it persists even if tool creation fails
db.commit()
db.refresh(new_agent)
# Cache invalidation wrapped in try/except
try:
a2a_stats_cache.invalidate()
# ... other invalidations
except Exception as cache_error:
logger.warning(f"Cache invalidation failed after agent commit: {cache_error}")
# Tool creation in separate try/except
try:
tool_db = await tool_service.create_tool_from_a2a_agent(...)
except Exception as tool_error:
logger.warning(f"Failed to create tool for A2A agent: {tool_error}")
db.refresh(new_agent) # Agent still persists3. Tool Visibility Default (tool_service.py)
# Default to "public" visibility if agent visibility is not set
# This ensures A2A tools are visible in the Global Tools Tab
tool_visibility = agent.visibility or "public"4. Frontend Modal (admin.js)
- Opens modal with textarea for custom query input
- Sends query to
/admin/a2a/{agent_id}/testendpoint - Displays response with proper XSS escaping via
escapeHtml() - Cleanup handlers for proper modal lifecycle management
5. Custom Agent Query Format Fix (tool_service.py)
Problem: Custom (non-JSONRPC) agents were receiving queries in JSONRPC message format, causing HTTP 422 errors:
{"parameters": {"message": {"messageId": "...", "role": "user", "parts": [{"type": "text", "text": "..."}]}}}Solution: Check agent type BEFORE formatting query. Custom agents receive flat parameters:
# Build request data based on agent type
if agent.agent_type in ["generic", "jsonrpc"] or agent.endpoint_url.endswith("/"):
# JSONRPC agents: Convert flat query to nested message structure
params = {"message": {"messageId": ..., "parts": [...]}}
request_data = {"jsonrpc": "2.0", "method": method, "params": params, "id": 1}
else:
# Custom agents: Pass parameters directly without JSONRPC message conversion
params = parameters if isinstance(parameters, dict) else {}
request_data = {"interaction_type": ..., "parameters": params, "protocol_version": ...}6. Integration Test Marker (test_a2a_sdk_integration.py)
# Mark all tests in this module as integration tests
# These tests require --with-integration flag to run
pytestmark = pytest.mark.integrationBehavior:
- Without
--with-integration: 23 tests skipped - With
--with-integration: 23 tests run
7. Documentation Update (docs/docs/using/agents/a2a.md)
Added "Local Testing" section (lines 209-308) covering:
- Demo A2A Agent setup and usage
- A2A SDK HelloWorld sample integration
- Admin UI query input modal
- Testing via MCP Tools
Security Review
| Check | Status |
|---|---|
| XSS Protection | ✅ All output escaped with escapeHtml() |
| SQL Injection | ✅ No database queries with user input |
| Command Injection | ✅ No system calls with user input |
| Authentication | ✅ Auth dependencies unchanged |
| Information Disclosure | ✅ Errors logged internally only |
Demo Script Security
- Calculator uses safe AST evaluation (no
eval()) - JWT_SECRET reads from
JWT_SECRET_KEYenvironment variable with fallback - Binds to 0.0.0.0 (acceptable for demo purposes)
Testing
Test Coverage
| Test File | Tests | Type | Flag Required |
|---|---|---|---|
tests/unit/mcpgateway/test_issue_840_a2a_agent.py |
10 | Unit | No |
tests/integration/test_a2a_sdk_integration.py |
23 | Integration | --with-integration |
tests/unit/mcpgateway/services/test_a2a_service.py |
23 | Unit | No |
| Total | 56 | Mixed |
Unit Tests (test_issue_840_a2a_agent.py)
-
TestIssue840UserInputForA2AAgentTest(3 tests)- User query extraction and invocation
- Admin endpoint signature verification
- Custom agent parameter format
-
TestIssue840A2AToolsNotListedInGlobalTools(4 tests)- Tool creation from A2A agent
- Tool listing includes A2A tools
- Tag filtering for A2A tools
- Integration type preservation
-
TestIssue840ToolInvocationRouting(1 test)- A2A tool routes to agent service
-
TestIssue840CustomAgentQueryFormat(2 tests)- Custom agents receive string query (not JSONRPC object)
- JSONRPC agents receive nested message structure
Integration Tests (test_a2a_sdk_integration.py)
-
A2A SDK Server Tests: Proper agent using official
a2a-sdk- Agent card endpoint verification
- Message/send JSON-RPC protocol
- Streaming response handling
-
Protocol Compliance Tests:
- Agent card has required A2A fields
- message/send returns Task or Message per spec
-
User Query Feature Tests:
- Query extraction from request body
- Default message when body empty/None/empty string
-
Transaction Handling Tests:
- Verifies agent committed before tool creation
- Agent survives tool creation failure
-
Tool Visibility Tests:
- Defaults to public when agent visibility is None
- Respects explicit visibility when set
Running Tests
# Unit tests only (33 tests)
uv run pytest tests/unit/mcpgateway/test_issue_840_a2a_agent.py \
tests/unit/mcpgateway/services/test_a2a_service.py -v
# Integration tests (23 tests, requires flag)
uv run pytest tests/integration/test_a2a_sdk_integration.py -v --with-integration
# All PR-related tests
uv run pytest tests/unit/mcpgateway/test_issue_840_a2a_agent.py \
tests/integration/test_a2a_sdk_integration.py \
tests/unit/mcpgateway/services/test_a2a_service.py \
-v --with-integrationManual Testing
Demo A2A Agent
# Terminal 1: Start ContextForge
make dev
# Terminal 2: Start demo agent (auto-registers with ContextForge)
uv run python scripts/demo_a2a_agent.py
# Test queries via Admin UI or API:
# - "calc: 7*8" → "56"
# - "calc: 100/4+25" → "50.0"
# - "weather: Dallas" → "The weather in Dallas is sunny, 25C"Admin UI Testing
- Go to
http://localhost:8000/admin - Click "A2A Agents" tab
- Find "Demo Calculator Agent" and click Test
- Enter query like
calc: 15*4+10in the modal - Click Run Test to see response
MCP Tool Invocation
# Get token
export TOKEN=$(python -m mcpgateway.utils.create_jwt_token \
--username [email protected] --exp 60 --secret my-test-key)
# Invoke via MCP tools/call
curl -X POST "http://localhost:8000/rpc" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "a2a_demo-calculator-agent",
"arguments": {"query": "calc: 99+1"}
},
"id": 1
}'A2A SDK HelloWorld Sample
# Start the official A2A sample
git clone https://github.com/google/a2a-samples.git
cd a2a-samples/samples/python/agents/helloworld
uv run python __main__.py
# Register with ContextForge
curl -X POST http://localhost:8000/a2a \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"agent": {
"name": "Hello World Agent",
"endpoint_url": "http://localhost:9999/",
"agent_type": "jsonrpc"
},
"visibility": "public"
}'Review Feedback Addressed
| Finding | Resolution |
|---|---|
| XSS in response display | Already uses escapeHtml() |
| Transaction handling bug | Fixed: commit agent before tool creation |
| Cache invalidation failure misleads callers | Fixed: wrapped in try/except |
| Unused imports (F401) | Fixed: removed with ruff |
| Empty query handling | Fixed: empty string uses default |
| Demo hardcodes JWT_SECRET | Fixed: reads from env var with fallback |
| Integration tests lack marker | Fixed: added pytestmark = pytest.mark.integration |
| MCP tool invocation HTTP 422 for custom agents | Fixed: custom agents receive flat query strings |
Summary
Fixes #840
calc: 7*8orweather: Dallasinstead of a hardcoded messagepublicvisibility when the agent's visibility is not set, ensuring they appear in the Global Tools tabChanges
mcpgateway/admin.pymcpgateway/templates/admin.htmlmcpgateway/static/admin.jsmcpgateway/services/tool_service.pypublicwhen agent visibility is unsetmcpgateway/services/a2a_service.pyscripts/demo_a2a_agent.pytests/unit/mcpgateway/test_issue_840_a2a_agent.pyTest plan
weather: Dallas) and verify agent receives the queryuv run python scripts/demo_a2a_agent.pyuv run pytest tests/unit/mcpgateway/test_issue_840_a2a_agent.py -v🤖 Generated with Claude Code