Skip to content

feat(nodejs): Add Google ADK sample agent with Microsoft OpenTelemetry distro#313

Open
Yogeshp-MSFT wants to merge 1 commit into
microsoft:mainfrom
Yogeshp-MSFT:Google-ADK-Sample-(nodejs)
Open

feat(nodejs): Add Google ADK sample agent with Microsoft OpenTelemetry distro#313
Yogeshp-MSFT wants to merge 1 commit into
microsoft:mainfrom
Yogeshp-MSFT:Google-ADK-Sample-(nodejs)

Conversation

@Yogeshp-MSFT

Copy link
Copy Markdown

Summary

Adds a new Node.js sample agent using Google ADK (Agent Development Kit) with Gemini models (Vertex AI / public API), fully integrated with the Microsoft Agent 365 SDK, MCP tooling, notifications, and the @microsoft/opentelemetry distro for observability.

This is the first Google ADK sample in the Node.js samples collection.

What's included

  • Google ADK integration@google/adk v1.1.0 with Runner.runEphemeral(), InMemorySessionService, and MCPToolset (StreamableHTTP transport)
  • MCP tool discovery — Direct gateway call to /agents/v2/{agenticAppId}/mcpServers with OBO token exchange using ToolingConfiguration.mcpPlatformAuthenticationScope
  • Observability@microsoft/opentelemetry v1.0.2 with AgenticTokenCacheInstance.refreshObservabilityToken(), BaggageBuilderUtils.fromTurnContext(), InferenceScope, and A365 exporter response logging
  • NotificationsonAgentNotification("agents:*") handling for email (createEmailResponseActivity), Word comments (WpxComment), and lifecycle events
  • Authauthorization: { agentic: { type: 'agentic' } } in AgentApplication, authorizeJWT(loadAuthConfigFromEnv()) on Express, CloudAdapter(getAuthConfigWithDefaults())
  • Multiple messages + typing — Immediate ack, setInterval typing loop (~4s), final LLM response
  • Personalized instructions — Injects user display name from activity.from.name into agent system prompt per turn
  • Agents Toolkit supportm365agents.yml, m365agents.playground.yml, .vscode/launch.json, env/.env.playground

File structure

nodejs/google-adk/sample-agent/
├── .vscode/
│   ├── extensions.json
│   ├── launch.json
│   └── tasks.json
├── env/
│   ├── .env.playground
│   └── .env.playground.user
├── images/
│   └── .gitkeep
├── instrumentation.ts
├── index.ts
├── hosting.ts
├── agent.ts
├── agentInterface.ts
├── mcpToolRegistrationService.ts
├── .env.example
├── .gitignore
├── Agent-Code-Walkthrough.md
├── README.md
├── ToolingManifest.json
├── m365agents.yml
├── m365agents.playground.yml
├── package.json
└── tsconfig.json

Key design decisions

Decision Rationale
Direct gateway call instead of McpToolServerConfigurationService.listToolServers() SDK expects raw array but gateway returns { mcpServers: [...] } — handles both shapes
MCPToolset({ type: 'StreamableHTTPConnectionParams', header: {...} }) Google ADK uses a discriminated union on connectionParams.type; header (singular) not headers
configDotenv() in instrumentation.ts before @microsoft/opentelemetry import A365_OBSERVABILITY_LOG_LEVEL must be in process.env when the logging module initializes
Agent365Exporter.postWithRetries patch for response body logging Node.js distro v1.0.2 discards HTTP response body; patch logs it like the Python distro
Identity from TurnContext only — no AGENTIC_APP_ID env fallback Matches upstream pattern; A365 platform populates activity.recipient.agenticAppId at runtime

Test plan

  • npx tsc --noEmit — zero errors
  • npm run dev — server starts, health endpoint returns 200
  • Teams message via devtunnel — JWT validation passes, message processed, response delivered
  • MCP tools — gateway 200 OK, mcp_MailTools discovered, SendEmailWithAttachments function call executed by Gemini
  • Observability — InferenceScope spans emitted, BaggageBuilder auto-populated, A365 exporter HTTP 200 with rejectedSpans: 0, all sinks (flashpoint/sentinel/esp) status sent
  • Install/uninstall — welcome/farewell messages on installationUpdate activity
  • Typing indicators — ... animation refreshes during LLM processing

Dependencies

Package Version
@google/adk ^1.1.0
@microsoft/agents-hosting ^1.5.3
@microsoft/agents-activity ^1.5.3
@microsoft/agents-a365-notifications ^1.0.0
@microsoft/agents-a365-tooling ^1.0.0
@microsoft/opentelemetry ^1.0.2
express ^4.21.0
dotenv ^16.4.0

…y distro

Adds a Node.js/TypeScript sample using Google ADK (Agent Development Kit) with Gemini models, integrated with Microsoft Agent 365 SDK for hosting, MCP tooling, notifications, and @microsoft/opentelemetry for observability.
Copilot AI review requested due to automatic review settings May 28, 2026 08:01
@Yogeshp-MSFT Yogeshp-MSFT requested a review from a team as a code owner May 28, 2026 08:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a complete Node.js “Google ADK + Microsoft Agent 365 SDK” sample agent, including hosting, MCP tool registration, and OpenTelemetry instrumentation, along with local/Playground configuration and documentation.

Changes:

  • Introduces a TypeScript/Node.js sample agent app (Express entrypoint, hosting handlers, Google ADK agent implementation).
  • Adds MCP tool-server discovery/registration against the A365 tooling gateway.
  • Adds observability bootstrapping (Microsoft OpenTelemetry distro), VS Code launch/tasks, and setup docs/env templates.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
nodejs/google-adk/sample-agent/package.json Defines runtime + dev dependencies and scripts for building/running the sample.
nodejs/google-adk/sample-agent/tsconfig.json TypeScript compilation settings for producing dist output and typings.
nodejs/google-adk/sample-agent/index.ts Express entrypoint with health endpoints + JWT middleware + adapter routing.
nodejs/google-adk/sample-agent/instrumentation.ts OpenTelemetry initialization and exporter debugging patch.
nodejs/google-adk/sample-agent/agent.ts Google ADK agent runner, MCP tool initialization, and InferenceScope telemetry.
nodejs/google-adk/sample-agent/mcpToolRegistrationService.ts A365 gateway call to list MCP servers and register MCPToolset instances.
nodejs/google-adk/sample-agent/hosting.ts AgentApplication set up, message handling, typing loop, and notifications.
nodejs/google-adk/sample-agent/README.md End-user documentation for running/testing and feature overview.
nodejs/google-adk/sample-agent/.env.example Environment variable template for local/dev/prod scenarios.
nodejs/google-adk/sample-agent/m365agents*.yml Agents Toolkit / Playground configuration files.
nodejs/google-adk/sample-agent/.vscode/* Debug + task automation for running locally / Playground.

import type { TurnContext } from '@microsoft/agents-hosting';

// Use axios directly to call the gateway (same as the SDK uses internally)
import axios from 'axios';
Comment on lines +7 to +10
McpToolServerConfigurationService,
ToolingConfiguration,
resolveTokenScopeForServer,
} from '@microsoft/agents-a365-tooling';
Comment on lines +36 to +40
private configService: McpToolServerConfigurationService;

constructor() {
this.configService = new McpToolServerConfigurationService();
}
Comment on lines +119 to +125
logger.error(`Failed to list MCP tool servers:`);
logger.error(` agenticAppId: '${agenticAppId}'`);
logger.error(` Error: ${err.message}`);
if (err.response) {
logger.error(` HTTP Status: ${err.response.status}`);
logger.error(` Response data: ${JSON.stringify(err.response.data)}`);
}
Comment on lines +31 to +37
proto.postWithRetries = async function (url: string, body: Uint8Array, headers: Record<string, string>) {
const originalFetch = globalThis.fetch;
let attempt = 0;
const self = this;
globalThis.fetch = async (input: any, init?: any) => {
attempt++;
const response: Response = await originalFetch(input, init);
Comment on lines +100 to +107
// Inject display name into agent instruction (personalized per turn — local only, no instance mutation)
const personalizedInstruction = getPersonalizedInstruction(displayName);
const personalizedAgent = new Agent({
name: this.agentName,
model: this.model,
description: this.description,
instruction: personalizedInstruction,
});
Comment on lines +179 to +186
// Build baggage from TurnContext — auto-populates tenant, agent, channel, conversation
const baggageScope = BaggageBuilderUtils.fromTurnContext(
new BaggageBuilder(),
context as any
).build();

return new Promise<string>((resolve, reject) => {
baggageScope.run(async () => {
Comment on lines +201 to +204
scope.dispose();
}
});
});
Comment on lines +254 to +256
const timeoutPromise = new Promise<Agent>((_, reject) =>
setTimeout(() => reject(new Error('MCP tool initialization timed out')), 10_000)
);
authToken: bearerToken || undefined,
});

return await Promise.race([initPromise, timeoutPromise]);
@Yogeshp-MSFT

Copy link
Copy Markdown
Author

Hi @gwharris7 can you review this pr?

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