ApprovalWorkflow
An enterprise-grade Model Context Protocol (MCP) server built on C# .NET 8 that surfaces approval workflow management directly inside Microsoft 365 Copilot. Approvers can query, approve, reject, and delegate requests using natural language β with a rich interactive widget rendered inline in the Copilot chat pane.
Ask AI about ApprovalWorkflow
Powered by Claude Β· Grounded in docs
I know everything about ApprovalWorkflow. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
Approval Workflow β MCP App for Microsoft 365 Copilot
An enterprise-grade Model Context Protocol (MCP) server built on C# .NET 8 that surfaces approval workflow management directly inside Microsoft 365 Copilot. Approvers can query, approve, reject, and delegate requests using natural language β with a rich interactive widget rendered inline in the Copilot chat pane.
Architecture
βββββββββββββββββββββββββββββββββββββββββββββββ
β Microsoft 365 Copilot β
β (BizChat / Teams / Copilot Studio) β
ββββββββββββββββββ¬βββββββββββββββββββββββββββββ
β MCP over HTTPS (Streamable HTTP)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββ
β ApprovalWorkflow.McpServer β
β ASP.NET Core 8 Β· Azure App Service β
β β
β βββββββββββββββββββ ββββββββββββββββββββ β
β β MCP Tools β β MCP Resource β β
β β β QueryTool β β β Widget HTML β β
β β β DecisionTool β β (ui://) β β
β ββββββββββ¬βββββββββ ββββββββββββββββββββ β
β β β
β ββββββββββΌβββββββββββββββββββββββββββββ β
β β ApprovalWorkflow.Infrastructure β β
β β β ApprovalService (EF Core) β β
β β β GraphNotificationService β β
β ββββββββββ¬βββββββββββββββββββββββββββββ β
βββββββββββββΌββββββββββββββββββββββββββββββββββ
β
ββββββββββ΄βββββββββββββ
β β
βΌ βΌ
Azure SQL Microsoft Graph
(Approval data) (Teams + Email notifications)
Security: All MCP endpoints are protected by Entra ID (JWT bearer auth). The App Service uses Easy Auth as an additional layer. Secrets are stored exclusively in Azure Key Vault, accessed via the App Service Managed Identity.
Solution Structure
ApprovalWorkflow/
βββ ApprovalWorkflow.sln
βββ src/
β βββ ApprovalWorkflow.McpServer/ # ASP.NET Core 8 β MCP host
β β βββ Tools/
β β β βββ ApprovalQueryTool.cs # GetPendingApprovals, GetApprovalDetails
β β β βββ ApprovalDecisionTool.cs # ApproveRequest, RejectRequest, DelegateRequest
β β βββ Resources/
β β β βββ ApprovalWidgetResource.cs # Serves widget HTML at ui://approval/widget.html
β β βββ wwwroot/widget/index.html # Copilot-rendered approval widget
β β βββ Program.cs
β β βββ appsettings.json
β βββ ApprovalWorkflow.Core/ # Domain models & interfaces
β β βββ Models/
β β β βββ ApprovalRequest.cs
β β β βββ ApprovalDecision.cs
β β β βββ ApprovalStatus.cs
β β βββ Interfaces/
β β βββ IApprovalService.cs
β β βββ INotificationService.cs
β βββ ApprovalWorkflow.Infrastructure/ # EF Core + Microsoft Graph
β βββ Data/AppDbContext.cs
β βββ Services/
β βββ ApprovalService.cs
β βββ GraphNotificationService.cs
βββ frontend/widget/index.html # Source copy of the widget
βββ infra/main.bicep # Azure infrastructure (IaC)
βββ .vscode/
βββ mcp.json # Local MCP client config
βββ launch.json
βββ tasks.json
Prerequisites
| Tool | Version | Notes |
|---|---|---|
| .NET SDK | 8.0+ | Download |
| EF Core CLI | Latest | dotnet tool install --global dotnet-ef |
| Azure CLI | Latest | Download |
| SQL Server | 2019+ | LocalDB or Express for local dev |
| Azure subscription | β | For Key Vault, App Service, Azure SQL |
| Microsoft 365 | E3/E5 + Copilot licence | For Copilot integration |
Entra ID App Registration
A Global Administrator or Application Administrator must complete this section once.
-
Open Entra admin centre β App registrations β New registration
-
Name:
ApprovalWorkflow-MCP -
Supported account types: Single tenant
-
Redirect URI:
https://<your-app>.azurewebsites.net/.auth/login/aad/callback -
Expose an API:
- Set Application ID URI:
api://<client-id> - Add scope:
Approvals.ReadWrite(Admin + user consent)
- Set Application ID URI:
-
API permissions (Application permissions β grant admin consent):
Permission Purpose User.ReadBasic.AllResolve approver email to Entra Object ID Chat.CreateCreate 1:1 Teams chats for notifications ChatMessage.SendSend Adaptive Cards via Teams Mail.SendSend decision email notifications -
Certificates & secrets β New client secret β copy value immediately
-
Store the secret in Key Vault (see Key Vault Setup)
-
Note your Tenant ID, Client ID, and Application ID URI for configuration
Key Vault Setup
# Create Key Vault (or use the Bicep deployment below)
az keyvault create \
--name <vault-name> \
--resource-group <rg> \
--location <region>
# Store secrets β note the double-dash delimiter which maps to ':' in ASP.NET Core config
az keyvault secret set --vault-name <vault-name> \
--name "AzureAd--ClientSecret" --value "<client-secret>"
az keyvault secret set --vault-name <vault-name> \
--name "ConnectionStrings--AzureSql" \
--value "Server=<server>.database.windows.net;Database=ApprovalWorkflow;Authentication=Active Directory Managed Identity;"
az keyvault secret set --vault-name <vault-name> \
--name "ApplicationInsights--ConnectionString" --value "<ai-connection-string>"
Configuration
appsettings.json contains placeholder values. In production all secrets are resolved from Key Vault via Managed Identity. For local development use User Secrets.
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "<your-tenant-id>",
"ClientId": "<your-client-id>",
"ClientSecret": "<from-key-vault>",
"Audience": "api://<your-client-id>"
},
"ConnectionStrings": {
"AzureSql": "<from-key-vault>"
},
"KeyVault": {
"Uri": "https://<vault-name>.vault.azure.net/"
},
"ApprovalWorkflow": {
"AppUrl": "https://<app-service-name>.azurewebsites.net",
"NotificationMailbox": "approvals@yourdomain.com"
},
"ApplicationInsights": {
"ConnectionString": "<from-key-vault>"
}
}
Local Development
1. Clone and restore
git clone <repo-url>
cd ApprovalWorkflow
dotnet restore
2. Set User Secrets
cd src/ApprovalWorkflow.McpServer
dotnet user-secrets set "AzureAd:TenantId" "<your-tenant-id>"
dotnet user-secrets set "AzureAd:ClientId" "<your-client-id>"
dotnet user-secrets set "AzureAd:ClientSecret" "<your-client-secret>"
dotnet user-secrets set "ConnectionStrings:AzureSql" "Server=localhost;Database=ApprovalWorkflow;Trusted_Connection=True;TrustServerCertificate=True;"
dotnet user-secrets set "ApprovalWorkflow:NotificationMailbox" "approvals@yourdomain.com"
dotnet user-secrets set "ApprovalWorkflow:AppUrl" "https://localhost:7001"
3. Apply EF Core migrations
# From solution root
dotnet ef migrations add InitialCreate `
--project src/ApprovalWorkflow.Infrastructure `
--startup-project src/ApprovalWorkflow.McpServer
dotnet ef database update `
--project src/ApprovalWorkflow.Infrastructure `
--startup-project src/ApprovalWorkflow.McpServer
4. Run
dotnet run --project src/ApprovalWorkflow.McpServer
# Listening on https://localhost:7001
5. Verify the MCP handshake
POST https://localhost:7001/mcp
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": { "name": "test", "version": "1.0" }
}
}
Expected: a JSON response listing ApprovalWorkflow.McpServer with tools and resources capabilities.
6. Expose locally for Copilot testing
# Azure Dev Tunnels CLI (recommended β Microsoft-hosted, no auth challenge)
devtunnel user login
devtunnel host -p 7001 --allow-anonymous
Or use the VS Code Ports panel: forward port 7001 and set visibility to Public.
MCP Tools Reference
| Tool | Description |
|---|---|
GetPendingApprovals | Returns paginated pending approvals for the authenticated user, ordered by priority then due date. Optional category and pageSize parameters. |
GetApprovalDetails | Returns full detail and complete decision history for a single request by GUID. |
ApproveRequest | Approves a request with an optional comment. Notifies the requestor via email. |
RejectRequest | Rejects a request with a mandatory reason. Notifies the requestor via email. |
DelegateRequest | Reassigns a request to another user by email. Notifies the delegate via Teams Adaptive Card. |
All tools resolve the caller's identity from the oid JWT claim set by Entra ID. Unauthenticated requests return 401 Unauthorized.
Category Filter Values
The category parameter on GetPendingApprovals is a free-form string. Recommended conventions:
| Value | Description |
|---|---|
PurchaseOrder | Procurement and purchase approvals |
Expense | Expense claim approvals |
LeaveRequest | Annual, personal, or sick leave |
ContractExtension | Contractor or vendor extensions |
ChangeRequest | IT change management requests |
Azure Deployment
Option A β Bicep (recommended)
# Create resource group
az group create --name rg-approval-workflow --location uksouth
# Deploy all infrastructure
az deployment group create \
--resource-group rg-approval-workflow \
--template-file infra/main.bicep \
--parameters appName=approval-workflow \
sqlAdminPassword='<secure-password>'
The Bicep template provisions:
| Resource | SKU / Config |
|---|---|
| App Service Plan | B2 Linux |
| App Service | System-Assigned Managed Identity, Easy Auth (Entra ID), HTTPS only |
| Azure SQL Server | Public network access disabled |
| Azure SQL Database | Serverless Gen5, auto-pauses after 60 min |
| Key Vault | Standard, soft delete enabled, 90-day retention |
| Log Analytics Workspace | PerGB2018 |
| Application Insights | Workspace-based |
Option B β VS Code Azure App Service extension
- Install the Azure App Service extension
Ctrl+Shift+Pβ "Azure App Service: Deploy to Web App"- Select Create new Web App β .NET 8 β Linux
- After deployment, add
KeyVault__Urito App Settings in the Azure portal
Post-deployment checklist
- Grant App Service Managed Identity
get+liston Key Vault secrets - Add all three secrets to Key Vault (ClientSecret, AzureSql connection string, App Insights connection string)
- Configure Easy Auth with your Entra App Registration Tenant ID and Client ID
- Set
ApprovalWorkflow:AppUrlApp Setting to the live App Service hostname - EF migrations run automatically at first startup via
MigrateAsync()
Connecting to Microsoft 365 Copilot
Via Copilot Studio
- Go to copilotstudio.microsoft.com
- Create β New agent β Configure tab
- Scroll to Tools β Add a tool β Model Context Protocol
- Fill in:
- Server name:
ApprovalWorkflow - Server description:
Manages pending approval requests for the authenticated user - Server URL:
https://<your-app>.azurewebsites.net/mcp - Authentication: OAuth 2.0 (configure with your Entra App Registration)
- Server name:
- Publish β Channels β Microsoft Teams and Microsoft 365 Copilot
Via .vscode/mcp.json (local development only)
{
"servers": {
"approval-workflow": {
"type": "http",
"url": "https://localhost:7001/mcp"
}
}
}
Notifications
The GraphNotificationService sends notifications via Microsoft Graph:
| Event | Channel | Recipient |
|---|---|---|
| New request created | Teams Adaptive Card (1:1 chat) | Assigned approver |
| Request approved or rejected | Email (shared mailbox) | Original requestor |
| Request delegated | Teams Adaptive Card (1:1 chat) | Delegate approver |
Notifications are fire-and-forget β failures are logged to Application Insights but do not affect the decision response.
Database Migrations
Migrations apply automatically at startup and are idempotent β safe for Blue/Green or slot deployments. To generate a migration after a model change:
dotnet ef migrations add <MigrationName> `
--project src/ApprovalWorkflow.Infrastructure `
--startup-project src/ApprovalWorkflow.McpServer
Security Considerations
- Never commit secrets β use User Secrets locally and Key Vault in production
- The
/mcpendpoint requires a valid Entra ID bearer token at all times - CORS is restricted to known Copilot origins only
- Azure SQL has public network access disabled in the Bicep template β use Private Endpoint or App Service VNet Integration for production workloads
- Key Vault soft delete is enabled with 90-day retention to protect against accidental deletion
Contributing
- Fork the repository and create a feature branch:
git checkout -b feature/your-feature - Follow the existing namespace and layering conventions (Core β Infrastructure β McpServer)
- All new MCP tools must include
[Description]attributes β these are surfaced directly to Copilot as tool documentation - Test locally against the
.vscode/mcp.jsonconfiguration before raising a PR - Open a pull request against
mainwith a clear description of the change and any configuration impacts
License
This project is licensed under the MIT License. See LICENSE for details.
