Ten enterprise LOB systems, one M365 Copilot agent, interactive React widgets with side-by-side support
| Subtitle | A multi-LOB MCP Apps platform for M365 Copilot β 10 enterprise systems, 225 tools, 6 React widgets in one agent |
| Author | Vineet Kaul, PM Architect β Agentic AI, Microsoft |
| Date | April 2026 |
| Stack | Python Β· FastMCP 1.26 Β· React 19 Β· Fluent UI v9 Β· Vite Β· @modelcontextprotocol/ext-apps Β· Docker Β· Dev Tunnels Β· M365 Agents Toolkit Β· Azure Container Apps |
Tags: mcp mcp-apps copilot python react fluent-ui m365 salesforce servicenow sap hubspot flight-tracker docusign sap-successfactors workday coupa jira agentic-ai declarative-agent azure-container-apps bicep
TL;DR β Ten Python MCP servers (Salesforce CRM Β· ServiceNow ITSM Β· SAP S/4HANA Β· HubSpot Marketing Β· Flight Tracker Β· DocuSign eSignature Β· SAP SuccessFactors HR Β· Workday HCM Β· Coupa Procurement Β· Jira Projects) with React + Fluent UI widgets, running behind a single ASGI gateway (port 8080) or via Docker Compose, orchestrated by one M365 Copilot declarative agent with 225 tools across 10 runtimes. All new LOBs (and Flight/DocuSign) ship with mock mode β fully functional without credentials. Deploy to Azure Container Apps with one Bicep file.
- Architecture
- LOBs at a Glance
- Project Structure
- Prerequisites
- Setup
- Running
- Deploy to Azure
- Mock Mode
- Static Analysis β check_meta.py
- Manifest Generation β regen_manifests.py
- Skills System
- Tools Reference
- Widget Architecture
- Logs & Monitoring
- Telemetry β App Insights
- Critical Troubleshooting
- References
π New to this? See SETUP.md for a beginner-friendly step-by-step credential guide.
| Generic MCP Server | MCP App (what we built) | |
|---|---|---|
| Returns | Raw JSON β LLM summarizes as text | structuredContent β renders a visual widget |
| UI | None β text in chat | Interactive tables with inline Create, Edit, Delete |
| Requires | Just tools | Tools + _meta.ui.resourceUri + widget HTML resource |
| Example | "You have 5 leads: John Smith at Acme..." | Live sortable table with β edit buttons |
Our MCP servers are MCP Apps β they return structured data that M365 Copilot renders as interactive widgets directly in the chat.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β M365 Copilot β
β β
β "Show leads" "Open incidents" "POs" "Flights" "Sign" "HR" β
β βββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββ β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Enterprise LOB Copilot (Declarative Agent) β β
β β 225 tools Β· 10 runtimes β β
β ββββ¬βββ¬βββ¬βββ¬βββ¬βββ¬βββ¬βββ¬βββ¬βββββββββββββββββββββββ β
ββββββββββββββΌβββΌβββΌβββΌβββΌβββΌβββΌβββΌβββΌβββββββββββββββββββββββββββββββ
β β β β β β β β β
βΌ βΌ βΌ βΌ βΌ βΌ βΌ βΌ βΌ βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ASGI Gateway :8080 (all 10 LOBs in-process) β
β /sf /sn /sap /hs /ft /ds /saphr /workday /coupa /jira β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Two deployment modes:
- Gateway β all 10 LOBs in one Python process on port 8080 (dev tunnel / Azure Container Apps)
- Docker Compose β 10 individual services on ports 3000β3009 (optional per-LOB isolation)
| # | LOB | Port | Tools | Widget | Mock Mode |
|---|---|---|---|---|---|
| 1 | ποΈ Salesforce CRM | 3000 | 31 | β React + Fluent UI | β |
| 2 | π« ServiceNow ITSM | 3001 | 24 | β React + Fluent UI | β |
| 3 | π¦ SAP S/4HANA | 3002 | 14 | β React + Fluent UI | sandbox default |
| 4 | π§‘ HubSpot Marketing | 3003 | 29 | β React + Fluent UI | β |
| 5 | 3004 | 5 | β React + Fluent UI | β auto if no creds | |
| 6 | βοΈ DocuSign eSignature | 3005 | 9 | β React + Fluent UI | β auto if no creds |
| 7 | π€ SAP SuccessFactors HR | 3006 | 20 | β¬ pending | β auto if no creds |
| 8 | πΌ Workday HCM | 3007 | 46 | β¬ pending | β auto if no creds |
| 9 | π Coupa Procurement | 3008 | 21 | β¬ pending | β always (COUPA_MOCK=true) |
| 10 | π Jira Projects | 3009 | 26 | β¬ pending | β auto if no creds |
| Gateway | 8080 | 225 |
lob-mcp-apps/
βββ README.md
βββ .gitignore
βββ check_meta.py β static analysis: tool/manifest alignment
βββ regen_manifests.py β regenerate mcp-tools.json + ai-plugin.json
β
βββ shared-mcp-lib/ # Shared auth/HTTP/logging/telemetry/settings helpers
β βββ shared_mcp/
β βββ auth.py # get_bearer_token(ctx) β OAuth Bearer extraction
β βββ http.py # create_async_client() β httpx factory
β βββ logger.py # get_logger() β structlog wrapper
β βββ settings.py # Dataclass loaders for saphr/workday/coupa/jira
β βββ telemetry.py # track_tool() decorator + wrap_specs() β App Insights
β
βββ sf-mcp-app/ # Salesforce CRM (port 3000)
β βββ sf_crm_mcp/
β β βββ salesforce_server.py # 31 tools β Leads, Opps, Accounts, Contacts,
β β β # Cases, Tasks, Pipeline, Campaigns, Approvals
β β βββ salesforce.py # OAuth2 + REST client (query/create/update/delete)
β β βββ web/widget.html # React + SLDS widget
β βββ .env.example
β
βββ snow-mcp-app/ # ServiceNow ITSM (port 3001)
β βββ servicenow_mcp/
β β βββ servicenow_server.py # 24 tools β Incidents, Requests, Change Requests,
β β β # Problems, Approvals, Service Catalog, KB
β β βββ web/widget.html
β βββ .env.example
β
βββ sap-mcp-app/ # SAP S/4HANA (port 3002)
β βββ sap_s4hana_mcp/
β β βββ sap_server.py # 14 tools β POs, Business Partners, Materials
β β βββ sap_client.py
β β βββ web/widget.html
β βββ .env.example
β
βββ hubspot-mcp-app/ # HubSpot Marketing (port 3003)
β βββ hubspot_mcp/
β β βββ hubspot_server.py # 29 tools β Emails, Lists, Contacts, Campaigns
β β βββ hubspot_client.py
β β βββ web/widget.html
β βββ .env.example
β
βββ flight-mcp-app/ # Flight Tracker (port 3004) β mock-ready
β βββ flight_mcp/
β β βββ flight_server.py # 5 tools β departures, arrivals, state, track
β β βββ web/widget.html
β βββ .env.example
β
βββ docusign-mcp-app/ # DocuSign eSignature (port 3005) β mock-ready
β βββ docusign_mcp/
β β βββ docusign_server.py # 9 tools β envelopes, templates, send, void, sign
β β βββ web/widget.html
β βββ .env.example
β
βββ saphr-mcp-app/ # SAP SuccessFactors HR (port 3006) β mock-ready
β βββ saphr_mcp/
β β βββ saphr_server.py # 20 tools β employees, leave, time, performance
β βββ .env.example
β
βββ workday-mcp-app/ # Workday HCM (port 3007) β mock-ready
β βββ workday_mcp/
β β βββ workday_server.py # 46 tools β workers, skills, learning, org
β βββ .env.example
β
βββ coupa-mcp-app/ # Coupa Procurement (port 3008) β mock-ready
β βββ coupa_mcp/
β β βββ coupa_server.py # 21 tools β requisitions, POs, suppliers, invoices
β βββ .env.example
β
βββ jira-mcp-app/ # Jira Projects (port 3009) β mock-ready
β βββ jira_mcp/
β β βββ jira_server.py # 26 tools β issues, sprints, boards, projects
β βββ .env.example
β
βββ gateway/ # ASGI gateway β all 10 LOBs on port 8080
β βββ app.py # Starlette mounts at /sf /sn /sap /hs /ft /ds
β β # /saphr /workday /coupa /jira
β βββ Dockerfile # Build context must be project root
β
βββ deploy/ # Azure Container Apps IaC
β βββ main.bicep # ACR + Log Analytics + CAE + managed identity
β β # + gtc-gateway container app
β βββ deploy.sh # Provision β az acr build β az containerapp update
β βββ parameters.example.bicepparam
β
βββ widgets/ # React widget source (6 LOBs)
β βββ src/{lob}/ # index.html, main.tsx, App.tsx, types.ts
β βββ src/shared/ # McpBridge, FluentWrapper, Toast, ErrorBoundary
β βββ build.mjs # Vite single-file build β ../web/widget.html
β
βββ lob-agent/ # Enterprise LOB Copilot declarative agent
β βββ appPackage/
β β βββ declarativeAgent.json
β β βββ manifest.json
β β βββ ai-plugin.json # 225 functions, 10 runtimes
β β βββ mcp-tools.json # Tool schemas with _meta + widget URIs
β β βββ instruction.txt
β βββ skills/ # Per-LOB scenario prompts
β
βββ docker-compose.yml # All 10 LOBs (ports 3000β3009) + gateway profile
βββ deploy/SetSail.ps1 # One-command startup (Docker + tunnel)
| Requirement | Notes |
|---|---|
| Docker Desktop | Latest β recommended for the gateway or individual LOBs |
| Python β₯ 3.11 | Only if running without Docker |
| Node.js β₯ 18 | For widget builds (cd widgets && npm run build) |
| M365 Agents Toolkit | VS Code extension or teamsapp CLI |
| Dev Tunnels CLI | devtunnel β or use Azure Container Apps (no tunnel needed) |
| M365 Developer Tenant | Copilot license + sideloading enabled |
LOB credentials (all optional β servers fall back to mock mode without them):
| LOB | Free tier available? |
|---|---|
| Salesforce | Developer Edition org at developer.salesforce.com |
| ServiceNow | Developer instance at developer.servicenow.com |
| SAP S/4HANA | Free sandbox API key at api.sap.com |
| HubSpot | Free CRM account + Private App token |
| Flight Tracker | Free account at opensky-network.org (mock works without) |
| DocuSign | Developer sandbox at developers.docusign.com (mock works without) |
| SAP SuccessFactors | SAP BTP trial (mock works without) |
| Workday | Workday tenant (mock works without) |
| Coupa | Coupa sandbox (always mock by default) |
| Jira | Atlassian Cloud free tier (mock works without) |
git clone https://github.com/kaul-vineet/LOB-MCPAppsSample.git
cd lob-mcp-appsCopy .env.example β .env for each LOB and fill in credentials (leave blank for mock mode):
cp sf-mcp-app/.env.example sf-mcp-app/.env
cp snow-mcp-app/.env.example snow-mcp-app/.env
cp sap-mcp-app/.env.example sap-mcp-app/.env
cp hubspot-mcp-app/.env.example hubspot-mcp-app/.env
cp flight-mcp-app/.env.example flight-mcp-app/.env
cp docusign-mcp-app/.env.example docusign-mcp-app/.env
cp saphr-mcp-app/.env.example saphr-mcp-app/.env
cp workday-mcp-app/.env.example workday-mcp-app/.env
cp coupa-mcp-app/.env.example coupa-mcp-app/.env
cp jira-mcp-app/.env.example jira-mcp-app/.env| Variable | How to get |
|---|---|
SF_INSTANCE_URL |
Your org URL β e.g. https://myorg.salesforce.com |
SF_CLIENT_ID, SF_CLIENT_SECRET |
Setup β App Manager β New Connected App β OAuth2 client_credentials |
| Variable | How to get |
|---|---|
SERVICENOW_INSTANCE |
Instance hostname β e.g. dev12345 |
SERVICENOW_CLIENT_ID/SECRET |
Developer instance β OAuth 2.0 app (AUTH_MODE=oauth) |
SERVICENOW_USERNAME/PASSWORD |
Basic auth fallback (AUTH_MODE=basic) |
| Variable | How to get |
|---|---|
SAP_API_KEY |
Free from api.sap.com β Copy API Key. Default SAP_MODE=sandbox |
| Variable | How to get |
|---|---|
HUBSPOT_ACCESS_TOKEN |
Settings β Integrations β Private Apps. Scopes: crm.objects.contacts.read/write |
| Variable | How to get |
|---|---|
OPENSKY_CLIENT_ID/SECRET |
opensky-network.org free account β leave blank for mock |
| Variable | How to get |
|---|---|
DOCUSIGN_INTEGRATION_KEY |
DocuSign Developer β Apps β JWT Grant |
DOCUSIGN_USER_ID, DOCUSIGN_ACCOUNT_ID |
DocuSign developer account |
DOCUSIGN_RSA_PRIVATE_KEY |
base64 -w0 rsa_key.pem β base64-encoded PEM |
| Variable | How to get |
|---|---|
SAP_SF_ODATA_URL, SAP_SF_TOKEN_URL |
SAP BTP β SuccessFactors API |
SAP_SF_COMPANY_ID, SAP_SF_CLIENT_ID |
SAP BTP service instance credentials |
| Variable | How to get |
|---|---|
WORKDAY_BASE_URL, WORKDAY_TENANT |
Workday tenant URL |
| Variable | How to get |
|---|---|
COUPA_INSTANCE_URL |
https://your-org.coupahost.com β set COUPA_MOCK=false for live |
| Variable | How to get |
|---|---|
JIRA_BASE_URL |
https://your-org.atlassian.net |
JIRA_PROJECT_KEY |
e.g. GTC |
devtunnel user login -d
devtunnel create gtc-tunnel --allow-anonymous
devtunnel port create gtc-tunnel -p 8080 # Gateway (recommended β single port)
# Or individual LOB ports if using docker-compose without gateway:
devtunnel port create gtc-tunnel -p 3000 # Salesforce
devtunnel port create gtc-tunnel -p 3001 # ServiceNow
# ... repeat for 3002β3009Update lob-agent/appPackage/ai-plugin.json runtime URLs with your tunnel hostname, or run python regen_manifests.py after setting MCP_GATEWAY_URL.
cd lob-agent
teamsapp auth login m365
teamsapp provision --env devOr via ATK sidebar in VS Code: Lifecycle β Provision.
All 10 LOBs in one process on port 8080. This is the mode used with Azure Container Apps and the standard dev tunnel setup.
# Docker (build context = project root)
docker compose --profile gateway up gateway
# Or run locally
pip install -e ./shared-mcp-lib -e ./gateway # plus all 10 LOB packages
python -m gateway
# Tunnel
devtunnel host gtc-tunnel --allow-anonymousRoutes: /sf/mcp /sn/mcp /sap/mcp /hs/mcp /ft/mcp /ds/mcp /saphr/mcp /workday/mcp /coupa/mcp /jira/mcp
Individual containers per LOB (ports 3000β3009):
docker compose up -d # all 10 LOBs
docker compose up salesforce servicenow sap # specific LOBs only
devtunnel host gtc-tunnel --allow-anonymous
docker compose ps
docker compose logs -f
docker compose down# Install shared lib first
pip install -e ./shared-mcp-lib
# Then each LOB
pip install -e ./sf-mcp-app && python -m sf_crm_mcp # :3000
pip install -e ./snow-mcp-app && python -m servicenow_mcp # :3001
pip install -e ./sap-mcp-app && python -m sap_s4hana_mcp # :3002
pip install -e ./hubspot-mcp-app && python -m hubspot_mcp # :3003
pip install -e ./flight-mcp-app && python -m flight_mcp # :3004
pip install -e ./docusign-mcp-app && python -m docusign_mcp # :3005
pip install -e ./saphr-mcp-app && python -m saphr_mcp # :3006
pip install -e ./workday-mcp-app && python -m workday_mcp # :3007
pip install -e ./coupa-mcp-app && python -m coupa_mcp # :3008
pip install -e ./jira-mcp-app && python -m jira_mcp # :3009Deploy the gateway to Azure Container Apps β no dev tunnel needed, stable Azure FQDN.
az login
az account set -s <subscription-id>
az bicep installcp deploy/parameters.example.bicepparam deploy/parameters.bicepparam
# Edit parameters.bicepparam with real credentials (this file is .gitignored)
export RESOURCE_GROUP=gtc-rg
export LOCATION=eastus
export ACR_NAME=gtcregistry # must be globally unique
bash deploy/deploy.shThe script:
- Creates the resource group
- Deploys
deploy/main.bicepβ ACR, Log Analytics, Container Apps environment, managed identity, and thegtc-gatewaycontainer app (external ingress, port 8080) - Builds all 11 images in ACR using
az acr build(remote build β no Docker daemon needed locally) - Updates the container app with the new images
export MCP_GATEWAY_URL=https://<your-gateway-fqdn>
python regen_manifests.pyThen re-provision the Teams agent with the new URLs.
| Resource | SKU / Notes |
|---|---|
| Azure Container Registry | Basic |
| Log Analytics Workspace | PerGB2018, 30-day retention |
| Container Apps Environment | Consumption plan |
| User-Assigned Managed Identity | AcrPull on the registry |
gtc-gateway Container App |
1β3 replicas, external ingress, 1 vCPU / 2 GiB |
All six original LOBs (Flight, DocuSign) plus all four new LOBs (SAP HR, Workday, Coupa, Jira) support mock mode β realistic demo data with no real credentials needed.
| LOB | Mock triggers | Mock data |
|---|---|---|
| Flight Tracker | OPENSKY_CLIENT_ID absent |
5 GTC-themed flights (DubaiβLondon, etc.) with live-style positions |
| DocuSign | Credentials absent | 5 mock envelopes (NDA, MSA, Cargo contractβ¦), 4 templates |
| SAP SuccessFactors HR | SAP_SF_ODATA_URL absent |
5 mock employees, leave balances, org chart |
| Workday | WORKDAY_BASE_URL absent |
Mock workers, skills, learning completions |
| Coupa | COUPA_MOCK=true (default) |
Mock requisitions, POs, supplier list |
| Jira | JIRA_BASE_URL absent |
Mock issues, sprint board, project list |
Mock mode activates automatically β the agent and widget see identical response shapes as with live credentials.
Validates tool definitions stay in sync across all three sources of truth:
python check_meta.py # exits 1 if drift detected
python check_meta.py --verbose # full per-tool report| Check | What it verifies |
|---|---|
_meta presence |
Every tool in mcp-tools.json has _meta.ui.resourceUri |
| Plugin registration | Every manifest tool appears in ai-plugin.json |
| Server drift | Tools in server.py match mcp-tools.json |
The four new LOBs (saphr, workday, coupa, jira) use programmatic tool registration (
mcp.tool(...)()loop pattern). Check 3 will warn "no@mcp.tooldecorators found" for these β this is expected. Checks 1 and 2 still pass and verify all 225 tools are correctly manifested.
Regenerates mcp-tools.json and ai-plugin.json by importing all 10 server modules and reading their live tool registrations.
# Local dev (devtunnel URL)
python regen_manifests.py
# After Azure deployment β point at your Container Apps FQDN
MCP_GATEWAY_URL=https://<your-gateway-fqdn> python regen_manifests.pyRun after adding or renaming tools, then re-provision the agent.
lob-agent/skills/ documents per-LOB scenario prompts used as conversation starters:
| File | LOB | Example starter |
|---|---|---|
crm-pipeline.md |
Salesforce | "Show me the latest leads. I want to review qualified ones." |
incident-triage.md |
ServiceNow | "Show me open incidents sorted by priority." |
procurement-check.md |
SAP | "Show me recent purchase orders." |
campaign-performance.md |
HubSpot | "Which campaigns have the best open rates?" |
flight-tracker.md |
Flight | "Show me departures from Heathrow today." |
docusign-envelopes.md |
DocuSign | "Show me envelopes awaiting signature." |
cross-lob-operations.md |
All LOBs | "Give me an end-of-day operations check." |
| Tool | Description |
|---|---|
sf__get_leads |
Latest 5 leads |
sf__create_lead |
Create lead |
sf__update_lead |
Update lead |
sf__convert_lead |
Convert lead β Account + Contact + Opportunity |
sf__get_opportunities |
Latest 5 opportunities |
sf__create_opportunity |
Create opportunity |
sf__update_opportunity |
Update opportunity |
sf__get_accounts |
Latest 5 accounts |
sf__create_account |
Create account |
sf__update_account |
Update account |
sf__get_contacts |
Latest 5 contacts |
sf__create_contact |
Create contact |
sf__update_contact |
Update contact |
sf__get_cases |
Latest 5 cases |
sf__create_case |
Create case |
sf__update_case |
Update case status/resolution |
sf__get_tasks |
Latest 5 tasks/activities |
sf__create_task |
Create task |
sf__get_pipeline_dashboard |
Opportunity pipeline by stage (count + amount) |
sf__get_campaigns |
Get campaigns; pass campaign_id for edit form |
sf__create_campaign |
Create campaign |
sf__update_campaign |
Update campaign |
sf__get_pending_approvals |
Pending ProcessInstance workitems |
sf__get_products |
Product catalog |
sf__get_price_books |
Price books |
sf__get_quotes |
Recent quotes |
sf__create_quote |
Create quote from opportunity |
sf__get_contracts |
Active contracts |
sf__get_activities_timeline |
Activity history for a record |
Full list: see
sf_crm_mcp/salesforce_server.pyβTOOL_SPECS
| Tool | Description |
|---|---|
sn__get_incidents |
Latest incidents |
sn__create_incident |
Create incident |
sn__update_incident |
Update incident |
sn__resolve_incident |
Resolve/close incident |
sn__get_requests |
Latest service requests |
sn__create_request |
Create request |
sn__update_request |
Update request |
sn__get_request_items |
Items for a request |
sn__update_request_item |
Update request item |
sn__get_change_requests |
Latest change requests |
sn__create_change_request |
Create change request |
sn__get_problems |
Get problems; pass number for edit form |
sn__create_problem |
Create problem record |
sn__update_problem |
Update problem record |
sn__get_hr_cases |
Get HR cases; pass sys_id for edit form |
sn__create_hr_case |
Create HR case |
sn__update_hr_case |
Update HR case |
sn__get_pending_approvals |
Pending approvals queue |
sn__get_service_catalog_items |
Active catalog items |
sn__add_work_note |
Add work note to a record |
sn__get_knowledge_articles |
Knowledge base search |
sn__create_incident_form |
Open incident creation form widget |
sn__create_request_form |
Open request creation form widget |
sn__approve_request |
Approve a pending approval |
sn__reject_request |
Reject a pending approval |
sn__get_user_profile |
User profile lookup |
sn__get_cmdb_ci |
Configuration item from CMDB |
sn__get_sla_details |
SLA metrics for an incident |
sn__update_change_request |
Update change request |
Full list: see
servicenow_mcp/servicenow_server.pyβTOOL_SPECS
| Tool | Description |
|---|---|
sap__get_purchase_orders |
Latest purchase orders |
sap__get_business_partners |
Business partners / suppliers |
sap__get_materials |
Materials master data |
sap__create_purchase_order |
Create PO (mock ID in sandbox) |
sap__update_purchase_order |
Update PO |
sap__get_material_details |
Material detail by ID |
sap__get_sales_orders |
Sales orders list |
sap__get_invoices |
Accounts payable invoices |
sap__get_goods_receipts |
Goods receipts for a PO |
sap__get_cost_centers |
Cost center master data |
sap__get_gl_accounts |
G/L account master data |
sap__get_profit_centers |
Profit center list |
sap__approve_purchase_order |
Approve / reject a PO workflow |
sap__get_stock_overview |
Material stock levels |
| Tool | Description |
|---|---|
hs__get_emails |
Marketing emails with metrics |
hs__get_lists |
Contact lists / segments |
hs__get_list_contacts |
Contacts in a list |
hs__add_to_list |
Add contact to list |
hs__remove_from_list |
Remove contact from list |
hs__update_list |
Edit list name |
hs__create_contact |
Create contact |
hs__get_contacts |
Recent contacts |
hs__update_contact |
Update contact |
hs__get_deals |
Recent deals |
hs__create_deal |
Create deal |
hs__get_companies |
Recent companies |
hs__create_company |
Create company |
hs__get_campaigns |
Marketing campaigns |
hs__get_campaign_metrics |
Opens, clicks, conversions per campaign |
hs__clone_email |
Clone a marketing email |
hs__send_test_email |
Send test email to an address |
hs__get_forms |
Landing page forms |
hs__get_form_submissions |
Form submission entries |
hs__get_workflows |
Active automation workflows |
hs__enroll_in_workflow |
Enroll a contact in a workflow |
hs__get_tickets |
Support tickets |
hs__create_ticket |
Create support ticket |
hs__update_ticket |
Update ticket status/owner |
hs__get_owners |
HubSpot users / owners |
hs__get_pipelines |
Deal pipelines |
hs__update_deal |
Update deal stage/amount |
hs__delete_contact |
Delete a contact |
Full list: see
hubspot_mcp/hubspot_server.pyβTOOL_SPECS
| Tool | Description |
|---|---|
ft__get_airport_departures |
Departures from ICAO airport |
ft__get_airport_arrivals |
Arrivals at ICAO airport |
ft__get_aircraft_state |
Live position/speed/altitude |
ft__get_flights_by_aircraft |
Flight history for aircraft |
ft__get_aircraft_track |
Waypoints for a flight |
| Tool | Description |
|---|---|
ds__get_envelopes |
List envelopes with status filters |
ds__get_envelope_details |
Recipient and signing progress |
ds__get_templates |
Available signing templates |
ds__send_envelope |
Send envelope from template |
ds__void_envelope |
Void / cancel an envelope |
ds__resend_envelope |
Resend to pending signers |
ds__get_signing_url |
Generate embedded signing URL |
ds__download_document |
Download signed document |
ds__send_envelope_form |
Open send-envelope form widget |
Employee self-service: profile, leave balances, time-off requests, org chart, performance goals, learning, benefits, and more.
Worker context, skills, required learning, org chart, absence management, compensation, and more.
Requisitions, purchase orders, suppliers, invoices, budgets, approval workflows, and more.
Issues (CRUD), sprints, boards, projects, epics, comments, attachments, transitions, and more.
React 19 + Fluent UI v9 widgets compiled into single-file HTML via Vite + vite-plugin-singlefile. Six of the ten LOBs have live widgets; four (SAP HR, Workday, Coupa, Jira) are in progress.
| Widget | Design System | Key Visual Elements |
|---|---|---|
| Salesforce | Lightning SLDS | Blue header, pill status badges, compact CRUD forms |
| SAP | Fiori | Shell bar, '72' font, Object Page layout |
| HubSpot | Canvas | Metric cards, percentage bars, coral/teal palette, 3-level drill-down |
| ServiceNow | Now Design | Teal shell, P1βP4 priority colors |
| Flight Tracker | Fluent v9 | Dark sky theme, live status badges, altitude/speed metrics |
| DocuSign | DocuSign brand | Envelope status cards, signer progress |
| Feature | Implementation |
|---|---|
| MCP Apps protocol | @modelcontextprotocol/ext-apps β handshake + tool-result delivery |
| Side-by-side mode | app.requestDisplayMode('fullscreen') |
| Auto theming | Light/dark via host context |
| Skeleton loading | CSS shimmer while data loads |
| Error boundaries | Crash recovery UI with "Try Again" |
| CRUD operations | Create/read/update across all 6 live LOBs |
| Toast notifications | Success/error after save |
cd widgets
npm install
npm run build # all 6 widgets
npm run build:sf # specific LOBOutput: widgets/dist/{lob}.html β copied to {lob}-mcp-app/{pkg}/web/widget.html.
# Gateway mode
docker compose --profile gateway logs -f gateway
# Individual LOBs
docker compose logs -f salesforce # :3000
docker compose logs -f servicenow # :3001
docker compose logs -f sap # :3002
docker compose logs -f hubspot # :3003
docker compose logs -f flight # :3004
docker compose logs -f docusign # :3005
docker compose logs -f saphr # :3006
docker compose logs -f workday # :3007
docker compose logs -f coupa # :3008
docker compose logs -f jira # :3009For structured telemetry β tool latencies, success rates, and record counts by LOB β see the Telemetry β App Insights section below.
Every tool call is automatically instrumented via shared_mcp/telemetry.py. Each invocation fires a dependency event to Azure Application Insights β without blocking the tool response.
| Field | Description |
|---|---|
name |
Tool name (e.g. sf__get_leads) |
target |
LOB label (e.g. salesforce-crm) |
duration |
Wall-clock time in milliseconds |
success |
true / false |
resultType |
Value of structuredContent.type (e.g. leads, incidents) |
recordCount |
Value of structuredContent.total |
error |
Error message if the tool threw or returned an error |
cloud_RoleName |
Deployment name (env var) β useful in multi-agent environments |
cloud_RoleInstance |
Derived from the tool name prefix (sf, sn, sap, hs, ft, ds, saphr, wday, coupa, jira) |
Step 1 β Get your App Insights connection string
Azure Portal β Application Insights resource β Overview β Connection String (copy the full string).
Step 2 β Add to each .env file (or gateway environment)
APPINSIGHTS_CONNECTION_STRING=InstrumentationKey=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;IngestionEndpoint=https://eastus.in.applicationinsights.azure.com/
APPINSIGHTS_ROLE_NAME=lob-mcpLeave APPINSIGHTS_CONNECTION_STRING blank (or omit it) to disable telemetry entirely β zero overhead, no HTTP calls made.
Step 3 β In docker-compose.yml gateway block (already wired)
environment:
GATEWAY_PORT: "8080"
APPINSIGHTS_CONNECTION_STRING: "" # paste connection string to activate
APPINSIGHTS_ROLE_NAME: "lob-mcp"Transactions view β App Insights β Transaction Search β filter by dependency
KQL β all tool calls in the last hour
dependencies
| where timestamp > ago(1h)
| where type == "MCP Tool"
| project timestamp, name, target, duration, success, resultCode
| order by timestamp descKQL β slowest tools
dependencies
| where type == "MCP Tool"
| summarize avg(duration), percentile(duration, 95), count() by name
| order by avg_duration descKQL β failure rate by LOB
dependencies
| where type == "MCP Tool"
| summarize total=count(), failures=countif(success == false) by target
| extend failure_pct = round(100.0 * failures / total, 1)
| order by failure_pct descKQL β record volume trend (e.g. leads returned)
dependencies
| where type == "MCP Tool" and name startswith "sf__"
| extend recordCount = toint(customDimensions["resultCount"])
| summarize total_records=sum(recordCount) by bin(timestamp, 1h)
| render timechartWith cloud_RoleInstance set per LOB prefix, App Insights Application Map automatically draws one node per LOB showing call rates and failure percentages. Navigate to Application Insights β Application Map.
If the same MCP servers are shared across multiple agents, set a distinct APPINSIGHTS_ROLE_NAME per deployment (e.g. lob-mcp-prod, lob-mcp-uat). Query by cloud_RoleName to isolate traffic per agent.
-
MCP Apps handshake β Widget must send
ui/initializeβ wait for host response β sendui/notifications/initialized. Only then will the host deliverui/notifications/tool-result. -
--allow-anonymouson the tunnel β Required on bothdevtunnel createanddevtunnel host. Without it, M365 Copilot's backend can't reach your MCP endpoints. -
_metaplacement β_meta.ui.resourceUrimust be on the tool definition inmcp-tools.json, not insideinputSchema. -
Manifest drift β Run
python check_meta.pyafter adding tools. Runpython regen_manifests.pyto regenerate manifests. -
callToolmethod β Widget-to-host calls must usemethod: 'tools/call'witharguments(notui/callToolwithargs). -
Custom App Upload Enabled β ATK sidebar β Accounts. Both "Custom App Upload Enabled β" and "Copilot Access Enabled β" must show.
-
Gateway URL β After Azure deployment, update
MCP_GATEWAY_URLand re-runregen_manifests.py, then re-provision the agent. -
New LOBs returning mock data β Expected when credentials aren't configured. Set the relevant env vars and restart to use live APIs.
- Copy an app folder (e.g.
flight-mcp-app/) and rename the Python package - Replace the API client with your LOB's auth + REST calls
- Keep
@mcp.tool(meta={"ui": {"resourceUri": WIDGET_URI}})andtypes.CallToolResultwithstructuredContentreturns - Add the new app to
gateway/app.py(Mount) andgateway/Dockerfile(COPY + pip install) - Add a service to
docker-compose.yml - Add a Bicep
containerAppresource (or updatedeploy/main.bicep) - Run
python regen_manifests.pyβpython check_meta.py - Re-provision the agent