Agent 365
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Agent 365show me the agent registry"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Agent 365 — MCP App for Microsoft 365 Copilot
The first authenticated MCP App sample — calls Microsoft Graph API via the On-Behalf-Of (OBO) flow and renders interactive Fluent UI widgets inside M365 Copilot Chat.
⚠️ This is a reference implementation / experiment — intended to demonstrate patterns and best practices for building authenticated MCP Apps with rich UI. Not intended for production use as-is.
Surface the full Microsoft Agent 365 governance experience inside M365 Copilot Chat: browse the agent registry, visualize the agent landscape, monitor risky agents, and take admin actions — all through natural language with rich interactive widgets.
What This Agent Can Do
Rich UI Tools (render interactive widgets in chat)
Tool | Widget | Description |
| Agent Registry | Full inventory of all AI agents with search, filters, and detail drawer |
| Agent Map | Circle-packing bubble visualization grouped by publisher type |
| Risky Agents | Agents with active identity protection risk signals |
Admin Action Tools (callable from widgets)
Tool | Description |
| Block a compromised agent, preventing organization-wide use |
| Restore a previously blocked agent to active status |
| Transfer ownership of an agent to a different user |
| Search organization directory to find new agent owners |
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ M365 Copilot Chat │
│ │
│ User: "Show me the agent registry" │
│ │
│ ┌─────────────────────┐ │
│ │ Declarative Agent │ (declarativeAgent.json) │
│ │ "Agent 365" │ │
│ └────────┬────────────┘ │
│ │ invokes tool via MCP plugin │
│ ┌────────▼────────────┐ │
│ │ MCP Plugin │ (agent365-plugin.json) │
│ │ OAuthPluginVault │ → triggers OAuth sign-in (first use) │
│ └────────┬────────────┘ │
└───────────┼──────────────────────────────────────────────────────┘
│ HTTPS + Bearer Token
│ (via devtunnel in dev)
┌───────────▼──────────────────────────────────────────────────────┐
│ MCP Server (Express + StreamableHTTP) localhost:3001 │
│ │
│ 1. Extract Bearer token from Authorization header │
│ 2. OBO exchange → Graph token (via MSAL) │
│ 3. Call Graph API (beta endpoints) │
│ 4. Return structuredContent + text │
│ 5. Copilot renders widget from registered UI resource │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────┐ ┌──────────────────────────────────────┐
│ Entra ID (Azure AD)│ │ Microsoft Graph API (beta) │
│ OBO Token Exchange │────▶│ /copilot/admin/catalog/packages │
│ MSAL Node │ │ /identityProtection/riskyServiceP.. │
└─────────────────────┘ │ /users │
└──────────────────────────────────────┘What Makes This Unique
Full OAuth + OBO authentication — demonstrates the complete token exchange flow from Copilot → your app → Microsoft Graph
Real Microsoft Graph API calls — delegated user-context calls, not mock data
Interactive admin actions — block/unblock/reassign agents directly from widgets
Mock data fallback — toggle
USE_MOCK_DATA=truefor demos without Graph accessProduction-ready patterns — error handling, caching, accessibility, keyboard navigation
Prerequisites
Node.js 18+
Dev Tunnels CLI (
devtunnel)M365 tenant with Copilot Chat access (M365 E5 or M365 Copilot license)
Global Admin or Privileged Role Admin to grant API consent
Test user with Global Reader / Security Reader / Copilot Admin role
💡 ATK CLI is not installed globally — the provisioning command uses
npxto download it automatically.
Setup Guide
Step 1 — Clone & Install
git clone https://github.com/Ramakrishnan24689/agent365-mcpapp.git
cd agent365-mcpapp
npm installStep 2 — Entra ID App Registration
Open Azure Portal → Entra ID → App registrations → + New registration.
2.1 — Register
Field | Value |
Name |
|
Supported account types | Single tenant |
Redirect URI — Platform | Web |
Redirect URI — URL |
|
Click Register. Note down Application (client) ID and Directory (tenant) ID from the Overview page.
2.2 — API Permissions
Go to API permissions → + Add a permission → Microsoft Graph → Delegated permissions.
Add these 5 permissions:
Permission | Admin Consent | Purpose |
| No | Sign-in |
| Yes | User search for reassignment |
| Yes | Read agent catalog |
| Yes | Block/unblock/reassign agents |
| Yes | Risky agent signals |
Then click ✓ Grant admin consent for <your-tenant>. All 5 should show green ✅.
💡 Search "CopilotPackages" in the permission picker to find them.
2.3 — Expose an API
Go to Expose an API:
Click Add next to Application ID URI → accept default
api://<your-client-id>→ SaveClick + Add a scope:
Scope name:
access_as_userWho can consent: Admins and users
Fill display name/description fields → Add scope
Click + Add a client application:
Client ID:
ab3be6b7-baf2-4ad0-ae4c-e0209abb4820(this is M365 Copilot)Check
access_as_user→ Add application
2.4 — Client Secret
Go to Certificates & secrets → + New client secret → Add → copy the Value immediately (shown only once).
2.5 — Set Token Version to v2 ⚠️
Go to Manifest tab → find "accessTokenAcceptedVersion" → change null to 2 → Save.
Without this, OBO fails with
AADSTS50013. This is the most common setup mistake.
Step 3 — Configure Environment
cp .env.sample .env
cp env/.env.local.user.sample env/.env.local.user.env (server runtime):
PORT=3001
USE_MOCK_DATA=false
ENTRA_CLIENT_ID=<your-client-id>
ENTRA_CLIENT_SECRET=<your-client-secret>
ENTRA_TENANT_ID=<your-tenant-id>env/.env.local.user (ATK provisioning — same values):
AGENT365_MCP_CLIENT_ID=<your-client-id>
AGENT365_MCP_CLIENT_SECRET=<your-client-secret>env/.env.local (update tunnel URL after Step 5):
MCP_SERVER_URL=https://<tunnel-id>-3001.inc1.devtunnels.ms
MCP_SERVER_DOMAIN=<tunnel-id>-3001.inc1.devtunnels.ms
ENTRA_TENANT_ID=<your-tenant-id>Step 4 — Build
npm run buildStep 5 — Start Dev Tunnel
In a separate terminal (keep running):
devtunnel create agent365-mcp --allow-anonymous
devtunnel port create agent365-mcp --port-number 3001
devtunnel host agent365-mcpCopy the tunnel URL from output and update env/.env.local with it.
Step 6 — Start MCP Server
In another terminal (keep running):
npm run serveStep 7 — Provision to M365 Copilot
npx -y --package @microsoft/m365agentstoolkit-cli atk provision --env localRe-provisioning? Clear
AGENT365_MCP_AUTH_IDinenv/.env.localfirst if you changed tunnel URL or client ID.
Step 8 — Test
Open the URL from provision output:
https://m365.cloud.microsoft/chat/?titleId=<your-title-id>Try: "Show me the agent registry" or "Show the agent map"
Troubleshooting
Symptom | Fix |
| Set |
No sign-in prompt | Clear |
403 from Graph | Grant admin consent + assign admin role to test user |
"We couldn't find this agent" | Re-provision (tunnel URL may have changed) |
| Tenant needs M365 Copilot license |
Project Structure
agent365-mcpapp/
├── appPackage/ # Declarative Agent manifest
│ ├── manifest.json # Teams app manifest (v1.26)
│ ├── declarativeAgent.json # Agent config with conversation starters
│ ├── agent365-plugin.json # MCP plugin — tools, auth, runtime URL
│ ├── instruction.txt # Agent behavioral instructions
│ └── color.png / outline.png # App icons
├── env/ # ATK environment files
│ ├── .env.local # Non-secret config (committed)
│ ├── .env.local.user # Secrets (gitignored)
│ └── .env.local.user.sample # Template for secrets
├── src/
│ ├── tools/ # MCP tool handlers
│ │ ├── show-registry.ts # Agent registry tool
│ │ ├── show-agent-map.ts # Agent map tool
│ │ ├── show-risky.ts # Risky agents tool
│ │ ├── block-agent.ts # Block action
│ │ ├── unblock-agent.ts # Unblock action
│ │ └── reassign-agent.ts # Reassign action
│ ├── graph/ # Microsoft Graph API clients
│ │ ├── client.ts # Graph fetch abstraction + mock switch
│ │ ├── packages.ts # /copilot/admin/catalog/packages
│ │ ├── risk.ts # /identityProtection/riskyServicePrincipals
│ │ └── users.ts # /users (search)
│ ├── mock/ # Mock data for demo/testing
│ │ ├── packages.ts # 50 sample agents
│ │ ├── risk.ts # Sample risk signals
│ │ └── users.ts # Sample users
│ ├── widgets/ # React + Fluent UI widget source
│ │ ├── agent-registry/ # Registry table with filters & detail drawer
│ │ ├── agent-map/ # D3 circle-packing visualization
│ │ ├── risky-agents/ # Risk signal cards
│ │ └── shared/ # Theme, providers, shared components
│ └── types.ts # Shared TypeScript types
├── ui/ # Vite HTML entry points for widgets
├── dist/ui/ # Built single-file HTML widgets (generated)
├── auth.ts # MSAL OBO token exchange
├── server.ts # MCP server — tool + resource registration
├── main.ts # Express entry point
├── build-ui.mjs # Widget build script (Vite)
├── m365agents.yml # ATK provisioning lifecycle
├── .env.sample # Server env template
├── package.json
├── tsconfig.json # Client TypeScript config
├── tsconfig.server.json # Server TypeScript config
└── vite.config.ts # Vite config for widget buildsAuthentication Deep-Dive
OBO Flow Summary
User → Copilot → [token: api://<client-id>/access_as_user] → MCP Server → [MSAL OBO] → Graph token → Graph APICopilot obtains a token scoped to your app — it cannot call Graph directly. Your server exchanges it via OBO for a Graph-scoped token representing the same user.
The .default Scope
The server requests https://graph.microsoft.com/.default (see auth.ts). This means permissions are controlled entirely by the app registration's configured API permissions — not by per-call scope strings. Add/remove permissions in Entra, grant admin consent, and the OBO token automatically reflects the change.
Environment Variables
Variable | Purpose |
| App identity for MSAL OBO exchange |
| Proves app identity to Entra ID |
| Directs auth to your tenant |
| Same as above — used by ATK provisioning |
| OAuth registration ID (created by ATK — clear to re-register) |
ENTRA_CLIENT_IDandAGENT365_MCP_CLIENT_IDare the same app. Two vars exist because ATK and MSAL consume them independently.
Mock Data Mode
Set USE_MOCK_DATA=true in .env to run without Graph API access. The server will return realistic sample data (50 agents, risk signals, users) — perfect for UI development or demos.
Widget Lifecycle in Copilot
When Copilot renders an MCP App widget, it mounts the widget iframe in multiple render slots against a single tools/call response. This means your widget's ontoolresult callback (see McpAppProvider.tsx) fires independently in each iframe instance. Widgets must be idempotent — they receive the same structuredContent payload each time and should render identically regardless of which slot they occupy. If you see your widget "mount 4 times" during debugging, this is expected behavior, not a bug. Action tools invoked via callServerTool() from any slot will trigger a fresh tools/call to the server.
Development
# Start in dev mode (watch server + widgets)
npm run dev
# Build widgets only
node build-ui.mjs
# Inspect MCP server with MCP Inspector
npm run inspector
# Type-check without build
tsc --noEmitTroubleshooting
Issue | Cause | Fix |
AADSTS500011 — resource principal not found | Wrong client ID in | Ensure |
No sign-in prompt in Copilot | Stale OAuth registration | Clear |
Widget not rendering | Tool returns error or no | Check server logs for Graph API errors |
"We couldn't find this agent" | Stale M365 title ID | Re-provision to get a fresh |
Auth token MISSING on server | Copilot not sending bearer token | Verify OAuth is registered correctly (re-provision with empty AUTH_ID) |
Graph API Endpoints Used
Endpoint | Permission | Purpose |
|
| List all registered agents |
|
| Get agent detail (instructions, capabilities) |
|
| Block an agent |
|
| Unblock an agent |
|
| Reassign agent ownership |
|
| Risk signals for service principals |
|
| Search users for reassignment |
Deploying to Azure (Production)
For production, replace the dev tunnel with an Azure-hosted endpoint:
Component | Recommended Service | Notes |
MCP Server | Azure Container Apps or App Service | Scales to zero, built-in HTTPS |
Secrets | Azure Key Vault | Referenced via App Settings |
Identity | Managed Identity | No credentials needed to access Key Vault |
Steps:
Deploy the Express server to Container Apps (or App Service)
Store
ENTRA_CLIENT_SECRETin Key Vault; reference it via@Microsoft.KeyVault(SecretUri=...)Set remaining env vars (
ENTRA_CLIENT_ID,ENTRA_TENANT_ID,PORT) as App SettingsUpdate
MCP_SERVER_URL/MCP_SERVER_DOMAINinenv/.env.localto the Azure URLRe-provision with ATK:
atk provision --env local
No code changes required — the server reads process.env identically whether values come from .env or Azure App Settings.
Related Resources
License
This server cannot be installed
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Ramakrishnan24689/agent365-mcpapp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server