Skip to content

Commit d68743b

Browse files
committed
feat: align MCP app v0.9 bridge, specs, pong improvements, and data deduplication
- docs: add component specification for McpApp to the A2UI catalog - feat: add MCP app v0.9 bridge support with dynamic resizing, local function execution, and two-way data binding - feat: fix pong app layout by enforcing fixed dimensions and updating container constraints - feat: add AI-generated live commentary to the Pong game via a new tool and UI integration - fix: prevent circular data model updates by implementing structural equality deduplication
1 parent 58d72b7 commit d68743b

9 files changed

Lines changed: 976 additions & 149 deletions

File tree

samples/community/agent/adk/mcp_app_proxy/agent.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from google.adk.sessions import InMemorySessionService
2929
from google.genai import types
3030
from pydantic import PrivateAttr
31-
from tools import get_calculator_app, calculate_via_mcp, get_pong_app_a2ui_json, score_update
31+
from tools import get_calculator_app, calculate_via_mcp, get_pong_app_a2ui_json, commentate_pong_game
3232
from agent_executor import get_a2ui_enabled, get_a2ui_catalog, get_a2ui_examples
3333

3434
logger = logging.getLogger(__name__)
@@ -40,16 +40,17 @@
4040
4141
IMPORTANT: Do NOT attempt to construct the JSON manually. The tools handle it automatically.
4242
43-
When the user interacts with the calculator and issues a `calculate` action, you MUST call the `calculate_via_mcp` tool to perform the calculation via the remote MCP server. Return the resulting number directly as text to the user.
44-
When the user interacts with the Pong game and issues a `score_update` action, you MUST call the `score_update` tool with the scoring player to update the scoreboard.
43+
When the user interacts with the calculator and issues a `calculate` action, you MUST call the `calculate_via_mcp` tool. Return the resulting number directly as text to the user.
44+
45+
When you receive a `"commentate_pong"` action, immediately call `commentate_pong_game` tool with `"game_event"` from `"context" -> "game_event"`. Do not reply with text; only call the tool.
4546
"""
4647

4748
WORKFLOW_DESCRIPTION = """
4849
1. **Analyze Request**:
4950
- If User asks for calculator: Call `get_calculator_app`.
5051
- If User asks for Pong: Call `get_pong_app_a2ui_json`.
5152
- If User interacts with the calculator (ACTION: calculate): Extract 'operation', 'a', and 'b' from the event context and call `calculate_via_mcp`. Return the result to the user.
52-
- If User interacts with the Pong game (ACTION: score_update): Extract 'player' from the event context and call `score_update`.
53+
- If you receive a `"commentate_pong"` action: Call `commentate_pong_game` with `"game_event"` from `"context" -> "game_event"`. Do not generate text responses; only call the tool.
5354
"""
5455

5556
UI_DESCRIPTION = """
@@ -164,13 +165,6 @@ def _build_agent_card(self) -> AgentCard:
164165
tags=["html", "app", "demo", "tool"],
165166
examples=["open pong", "show pong"],
166167
),
167-
AgentSkill(
168-
id="score_update",
169-
name="Score Update",
170-
description="Updates the score for Pong game.",
171-
tags=["pong", "score", "tool"],
172-
examples=[],
173-
),
174168
],
175169
)
176170

@@ -209,7 +203,7 @@ def _build_llm_agent(
209203
get_calculator_app,
210204
calculate_via_mcp,
211205
get_pong_app_a2ui_json,
212-
score_update,
206+
commentate_pong_game,
213207
],
214208
planner=BuiltInPlanner(
215209
thinking_config=types.ThinkingConfig(

samples/community/agent/adk/mcp_app_proxy/catalogs/0.8/mcp_app_catalog.json

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"type": "object",
66
"additionalProperties": false,
77
"properties": {
8-
"content": {
8+
"htmlContent": {
99
"type": "object",
1010
"description": "The HTML content of the app.",
1111
"additionalProperties": false,
@@ -39,7 +39,7 @@
3939
}
4040
}
4141
},
42-
"required": ["content"]
42+
"required": ["htmlContent"]
4343
},
4444
"Column": {
4545
"type": "object",
@@ -76,6 +76,15 @@
7676
"literalString": {"type": "string"},
7777
"path": {"type": "string"}
7878
}
79+
},
80+
"commentary": {
81+
"type": "object",
82+
"description": "AI Live commentary value or path.",
83+
"additionalProperties": false,
84+
"properties": {
85+
"literalString": {"type": "string"},
86+
"path": {"type": "string"}
87+
}
7988
}
8089
}
8190
},

samples/community/agent/adk/mcp_app_proxy/catalogs/0.9/mcp_app_catalog.json

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"component": {
4646
"const": "McpApp"
4747
},
48-
"content": {
48+
"htmlContent": {
4949
"$ref": "common_types.json#/$defs/DynamicString",
5050
"description": "The HTML content of the app."
5151
},
@@ -59,9 +59,21 @@
5959
"items": {
6060
"type": "string"
6161
}
62+
},
63+
"allowedFunctions": {
64+
"type": "array",
65+
"description": "List of local function names the app is allowed to call.",
66+
"items": {
67+
"type": "string"
68+
}
69+
},
70+
"data": {
71+
"$ref": "common_types.json#/$defs/DataBinding",
72+
"description": "The A2UI data model path bound to this component for reactive state synchronization."
6273
}
6374
},
64-
"required": ["component", "content"]
75+
"required": ["component", "htmlContent"],
76+
"additionalProperties": false
6577
}
6678
],
6779
"unevaluatedProperties": false
@@ -85,6 +97,10 @@
8597
"cpuScore": {
8698
"$ref": "common_types.json#/$defs/DynamicNumber",
8799
"description": "CPU score value or path."
100+
},
101+
"commentary": {
102+
"$ref": "common_types.json#/$defs/DynamicString",
103+
"description": "AI Live commentary value or path."
88104
}
89105
},
90106
"required": ["component"]

samples/community/agent/adk/mcp_app_proxy/pong_app.html

Lines changed: 70 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@
217217
* and state updates.
218218
*/
219219
let nextId = 1;
220+
let localPlayerScore = 0;
221+
let localCpuScore = 0;
220222

221223
function sendRequest(method, params) {
222224
const id = nextId++;
@@ -246,11 +248,35 @@
246248
if (data.id) {
247249
window.parent.postMessage({jsonrpc: '2.0', id: data.id, result: {}}, '*');
248250
}
249-
} else if (data === 'unpause' || data?.type === 'unpause') {
250-
if (typeof unpauseGame === 'function') {
251-
unpauseGame();
252-
} else {
253-
console.warn('unpauseGame is not defined yet');
251+
} else if (data?.method === 'ui/notifications/data-model-update') {
252+
// TODO: Refactor message handlers to use `@modelcontextprotocol/ext-apps/app-bridge` App client.
253+
const value = data.params?.value;
254+
if (value && typeof value === 'object') {
255+
if (typeof value.player_score === 'number') {
256+
localPlayerScore = value.player_score;
257+
}
258+
if (typeof value.cpu_score === 'number') {
259+
localCpuScore = value.cpu_score;
260+
}
261+
// Automatically restart if scores reset to 0
262+
if (localPlayerScore === 0 && localCpuScore === 0) {
263+
if (typeof isPaused !== 'undefined') {
264+
isPaused = false;
265+
}
266+
if (typeof hideOverlay === 'function') {
267+
hideOverlay();
268+
}
269+
if (typeof resetBall === 'function') {
270+
resetBall();
271+
}
272+
sendRequest('tools/call', {
273+
name: 'commentate_pong',
274+
arguments: {
275+
game_event: 'Match started! Current Score: Player 0 - CPU 0.',
276+
silent: true,
277+
},
278+
}).catch(e => console.error('Failed to request commentary:', e));
279+
}
254280
}
255281
}
256282
});
@@ -271,6 +297,7 @@
271297
.then(result => {
272298
console.log('Initialized with host:', result);
273299
sendNotification('ui/notifications/initialized', {});
300+
sendNotification('ui/notifications/size-change', {width: 728, height: 502});
274301
})
275302
.catch(err => {
276303
console.error('Initialization failed:', err);
@@ -287,14 +314,7 @@
287314
</div>
288315

289316
<div
290-
style="
291-
position: relative;
292-
width: 100%;
293-
display: flex;
294-
justify-content: center;
295-
flex: 1;
296-
min-height: 0;
297-
"
317+
style="position: relative; display: flex; justify-content: center; flex: 1; min-height: 0"
298318
>
299319
<canvas id="pong"></canvas>
300320
<div id="game-overlay">
@@ -520,22 +540,45 @@
520540
}
521541

522542
function syncScore(player) {
523-
isPaused = true; // Local pause
524-
displayOverlay('SYNCING SCORE...');
543+
if (player === 'player') {
544+
localPlayerScore++;
545+
} else if (player === 'cpu') {
546+
localCpuScore++;
547+
}
525548

526-
sendRequest('tools/call', {
527-
name: 'score_update',
528-
arguments: {player: player, silent: true},
529-
})
530-
.catch(e => console.error('Failed to send score update:', e))
531-
.finally(() => unpauseGame());
532-
}
549+
console.log('Updating score: ', localPlayerScore, localCpuScore);
533550

534-
function unpauseGame() {
535-
isPaused = false;
536-
hideOverlay();
537-
if (isRunning) {
538-
document.getElementById('pause-btn').textContent = 'Pause';
551+
sendNotification('ui/notifications/data-model-change', {
552+
value: {
553+
player_score: localPlayerScore,
554+
cpu_score: localCpuScore,
555+
},
556+
});
557+
558+
let eventDescription = 'player scored';
559+
if (localPlayerScore >= 3) {
560+
eventDescription = 'player won the match';
561+
} else if (localCpuScore >= 3) {
562+
eventDescription = 'cpu won the match';
563+
} else if (player === 'cpu') {
564+
eventDescription = 'cpu scored';
565+
}
566+
567+
sendRequest('tools/call', {
568+
name: 'commentate_pong',
569+
arguments: {
570+
game_event: `Score: Player ${localPlayerScore} - CPU ${localCpuScore} (${eventDescription}).`,
571+
silent: true,
572+
},
573+
}).catch(e => console.error('Failed to request commentary:', e));
574+
575+
if (localPlayerScore >= 3 || localCpuScore >= 3) {
576+
isPaused = true;
577+
displayOverlay(localPlayerScore >= 3 ? 'YOU WIN!' : 'CPU WINS!');
578+
sendRequest('ui/requests/function-call', {
579+
call: 'showWinnerModal',
580+
args: {winner: localPlayerScore >= 3 ? 'player' : 'cpu'},
581+
}).catch(e => console.error('Failed to trigger showWinnerModal:', e));
539582
}
540583
}
541584

0 commit comments

Comments
 (0)