Copilot Studio Sample Agent - Node.js
This sample demonstrates how to integrate a Microsoft Copilot Studio agent with the Microsoft Agent 365 SDK. It enables enterprise developers to bridge low-code Copilot Studio agents into Agent 365 managed environments with full feature parity.
Why use this integration?
| Copilot Studio | Agent 365 SDK | Together |
|---|---|---|
| Low-code agent building | Enterprise identity (Entra ID) | Best of both worlds |
| Visual flow designer | Microsoft 365 notifications | Low-code agents with enterprise features |
| Built-in connectors | OpenTelemetry observability | Full compliance and monitoring |
| Quick prototyping | Secure Graph access (MCP) | Production-ready deployment |
Demonstrates
This sample demonstrates:
- Copilot Studio Integration: Forward messages to your low-code Copilot Studio agent
- Notifications: Handle email notifications from Agent 365 and return responses
- Observability: End-to-end tracing with OpenTelemetry spans for Copilot Studio calls
- Hosting Patterns: Hosting with Microsoft 365 Agents SDK
This sample uses the @microsoft/agents-copilotstudio-client package and the Microsoft Agent 365 SDK for Node.js.
Prerequisites
- Node.js 18.x or higher
- Microsoft Agent 365 SDK
- Access to Microsoft Copilot Studio (Frontier preview program)
- A published Copilot Studio agent with Web channel enabled
- Azure/Microsoft 365 tenant with administrative permissions
Working with User Identity
On every incoming message, the A365 platform populates activity.from with basic user
information — always available with no API calls or token acquisition:
| Field | Description |
|---|---|
activity.from.id |
Channel-specific user ID (e.g., 29:1AbcXyz... in Teams) |
activity.from.name |
Display name as known to the channel |
activity.from.aadObjectId |
Azure AD Object ID — use this to call Microsoft Graph |
The sample logs these fields at the start of every message turn.
Copilot Studio Setup
Before running this sample, you need a Copilot Studio agent:
Go to Copilot Studio
Create a new agent (or use an existing one)
Publish your agent
Go to Settings > Advanced > Metadata and copy:
- Environment ID
- Schema Name (agent identifier)
Or copy the Direct Connect URL from the agent's channel settings.
Required Setup Steps
This sample requires additional configuration beyond the standard Agent 365 setup:
1. Add CopilotStudio.Copilots.Invoke API Permission
The CopilotStudio.Copilots.Invoke scope must be added to your Microsoft Entra ID app registration and blueprint:
- Go to Azure Portal → Microsoft Entra ID → App registrations
- Select your agent's app registration
- Go to API permissions → Add a permission
- Select APIs my organization uses → search for Power Platform API or Dataverse
- Add the
CopilotStudio.Copilots.Invokedelegated permission - Grant admin consent for the permission
- Update your blueprint to include this scope in the allowed permissions
2. Grant User Access to the Copilot Studio Agent
Users must have access to chat with your Copilot Studio agent:
Option A: Organization-wide access (used in this sample)
- Open your agent in Copilot Studio
- Click … (three dots) → Share
- Select the option to share with everyone in your organization
Option B: Security group access
- Create a security group in Microsoft Entra ID
- Add users who need access to the group
- Share the agent with that security group in Copilot Studio
Note: Individual users cannot be granted access directly—you must use security groups or organization-wide sharing. Authentication must be configured with Microsoft Entra ID and "Require users to sign in" enabled.
For more details, see Share agents with other users.
3. Microsoft 365 Copilot License Requirement
A Microsoft 365 Copilot license is required to publish agents in Copilot Studio. Ensure your tenant has the appropriate licensing before attempting to publish your agent.
4. Agentic Authentication with Power Platform Audience
This sample uses the agentic authentication flow to request a token with the Power Platform audience (https://api.powerplatform.com/.default). This token is required to access and invoke Copilot Studio agents.
// Acquire token for Copilot Studio API
const tokenResult = await authorization.exchangeToken(turnContext, authHandlerName, {
scopes: ['https://api.powerplatform.com/.default']
});
Configuration
Copy .env.example to .env and configure:
# Option 1: Direct Connect URL (recommended)
directConnectUrl=https://your-copilot.api.powerplatform.com/...
# Option 2: Environment ID + Agent Identifier
environmentId=your-environment-id
agentIdentifier=your-agent-schema-name
# Agent 365 Service Connection
connections__service_connection__settings__clientId=<<BLUEPRINT_CLIENT_ID>>
connections__service_connection__settings__clientSecret=<<BLUEPRINT_CLIENT_SECRET>>
connections__service_connection__settings__tenantId=<<TENANT_ID>>
Getting the Connection String
- In Copilot Studio, open your published agent
- Go to Settings > Channels > Custom website
- Copy the connection details (Environment ID and Schema Name)
- Alternatively, use the Direct Connect URL if available
Observability export
src/index.ts imports src/otel.ts first. This sample uses
@microsoft/agents-a365-observability@1.0.0; with
Agent365ExporterOptions.useS2SEndpoint = true, exports post to
/observabilityService/tenants/{tenant}/otlp/agents/{agent}/traces?api-version=1.
Leave ENABLE_A365_OBSERVABILITY_PER_REQUEST_EXPORT unset or false because the
1.0.0 per-request mode still reads runWithExportToken, not the configured
app-only resolver.
@opentelemetry/core is an explicit dependency because the 1.0.0 exporter imports it
without declaring it; relying on incidental dependency hoisting can fail at startup.
When ENABLE_A365_OBSERVABILITY_EXPORTER=true, set AGENT365_OBS_TENANT_ID,
AGENT365_OBS_AGENT_ID, AGENT365_OBS_BLUEPRINT_CLIENT_ID, and
AGENT365_OBS_BLUEPRINT_CLIENT_SECRET from the template. AGENT365_OBS_AGENT_ID
must be the actual agent instance client ID. The sample-local resolver uses
blueprint credentials plus fmi_path to acquire T1, then the agent identity's
client_credentials grant for OBS. It fails closed on delegated scp tokens,
identity, tenant, audience, role, expiry, or response-shape mismatches. Business
MCP, Graph, Power Platform, and OBO calls remain separate.
Single-instance limitation: this resolver exports for one statically configured
agent instance and tenant (AGENT365_OBS_*). It needs its own copy of the
blueprint secret and has no managed-identity option. Multi-instance or multi-tenant
deployments should reuse the hosting connection per agent/tenant instead of sharing
this static provider.
Sovereign clouds are not supported by this sample provider because the authority is
hard-coded to login.microsoftonline.com. For AI Teammates, complete the
Agent365.Observability.OtelWrite application-role step printed by
a365 setup all --aiteammate; AI Teammate S2S without it has not been validated.
How to run this sample
Install dependencies
npm installConfigure environment
cp .env.example .env # Edit .env with your Copilot Studio and Agent 365 settingsRun in development mode
npm run devBuild for production
npm run build npm start
The agent will start listening on http://localhost:3978/api/messages.
Sending Multiple Messages in Teams
Agent365 agents can send multiple discrete messages in response to a single user prompt in Teams. This is achieved by calling sendActivity multiple times within a single turn.
Important: Streaming responses are not supported for agentic identities in Teams. The SDK detects agentic identity and buffers the stream into a single message. Use
sendActivitydirectly to send immediate, discrete messages to the user.
The sample demonstrates this in handleAgentMessageActivity (agent.ts):
// Message 1: immediate ack — reaches the user right away
await turnContext.sendActivity('Got it — working on it…');
// ... LLM processing (forwarded to Copilot Studio) ...
// Message 2: the Copilot Studio response
await turnContext.sendActivity(response);
Each sendActivity call produces a separate Teams message. You can call it as many times as needed to send progress updates, partial results, or a final answer.
Typing Indicators
The agent sends typing indicators in a loop every ~4 seconds to keep the ... animation alive while Copilot Studio processes the request:
let typingInterval: ReturnType<typeof setInterval> | undefined;
const startTypingLoop = () => {
typingInterval = setInterval(async () => {
await turnContext.sendActivity({ type: 'typing' } as Activity);
}, 4000);
};
const stopTypingLoop = () => { clearInterval(typingInterval); };
startTypingLoop();
try {
// ... LLM processing ...
} finally {
stopTypingLoop();
}
Note: Typing indicators are only visible in 1:1 chats and small group chats — not in channels.
Testing
Local Testing with Playground
npm run test-tool
This launches the Microsoft 365 Agents Playground for local testing.
Troubleshooting
Authentication errors
Error: 401 Unauthorized when connecting to Copilot Studio
Solution:
- Verify that
CopilotStudio.Copilots.Invokepermission is added to your Entra ID app registration - Ensure the permission is granted admin consent
- Check that your agent ID and environment ID are correct
- Verify you have shared access to your Copilot to other users in your organization
Extending this sample
Adding multi-turn conversation support
The current implementation is stateless. To maintain conversation context:
// Store conversation ID in state
state.setValue('conversation.conversationId', activity.conversation.id);
// Retrieve for subsequent messages
const conversationId = state.getValue<string>('conversation.conversationId');
Adding Teams notification support
// In agent.ts, add to handleAgentNotificationActivity:
case NotificationType.TeamsNotification:
await this.handleTeamsNotification(context, state, agentNotificationActivity);
break;
Adding streaming responses
// In client.ts, modify to stream responses:
for await (const activity of client.sendActivityStreaming(userActivity)) {
if (activity.type === ActivityTypes.Typing) {
await context.sendActivity(new Activity(ActivityTypes.Typing));
} else if (activity.type === ActivityTypes.Message) {
await context.sendActivity(activity);
}
}
Architecture
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ Agent 365 SDK │ │ This Sample │ │ Copilot Studio │
│ (Notifications)│─────>│ (MyAgent) │─────>│ (Your Low-code │
└─────────────────┘ └──────────────────┘ │ Agent) │
│ │ └─────────────────────┘
│ Email notification │ │
│───────────────────────>│ Forward message │
│ │─────────────────────────>│
│ │ │
│ │ Agent response │
│ │<─────────────────────────│
│ Email response │ │
│<───────────────────────│ │
Support
For issues, questions, or feedback:
- Issues: Please file issues in the GitHub Issues section
- Documentation: See the Microsoft Agents 365 Developer documentation
- Copilot Studio: See Copilot Studio documentation
- Security: For security issues, please see SECURITY.md
Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.
Additional Resources
- Microsoft Agent 365 SDK - Node.js repository
- Microsoft 365 Agents SDK - Node.js repository
- @microsoft/agents-copilotstudio-client package
- Copilot Studio documentation
- Node.js API documentation
Trademarks
Microsoft, Windows, Microsoft Azure and/or other Microsoft products and services referenced in the documentation may be either trademarks or registered trademarks of Microsoft in the United States and/or other countries. The licenses for this project do not grant you rights to use any Microsoft names, logos, or trademarks. Microsoft's general trademark guidelines can be found at http://go.microsoft.com/fwlink/?LinkID=254653.
License
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License - see the LICENSE file for details.