sales-mcp-ui-poc
Click on "Deploy 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., "@sales-mcp-ui-pocShow me the sales dashboard and the Contoso expansion opportunity."
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.
Sales companion MCP Apps PoC
An Azure Functions Remote MCP TypeScript application that renders one bundled MCP Apps workspace for this flow:
sales dashboard -> opportunity detail -> editable quote preview -> explicitly confirmed SIMULATED quote save
The PoC never writes to CRM. Dashboard and opportunity reads use deterministic synthetic data by default or a server-configured read-only MCP server. The dashboard includes responsive, accessible pipeline bar and stage-mix donut charts alongside the detailed table. Quote drafts and simulated saves are persisted in Azure Blob Storage.
Prerequisites
Node.js 22
Azure Functions Core Tools 4.0.7030 or later
Azurite for local Blob/queue storage
Azure Developer CLI (
azd) for the parent-managed deployment flowMicrosoft 365 Agents Toolkit 6.12.0 or later for sideloading
Related MCP server: ticket-triage-mcp
Setup, build, and test
Set-Location C:\Flutter\sales-mcp-ui-poc
npm install
npm testnpm test builds the single-file UI, compiles the server and tests, and runs the Node built-in test entry point. To build without tests:
npm run build:app
npm run buildLocal run
Start Azurite first. The checked-in local.settings.json uses UseDevelopmentStorage=true, contains no credentials, and defaults to synthetic data.
Set-Location C:\Flutter\sales-mcp-ui-poc
npm startMCP endpoint:
http://localhost:7071/runtime/webhooks/mcpThe Functions MCP extension requires the existing host.json configuration, including webhookAuthorizationLevel: Anonymous. In Azure, App Service Authentication/Easy Auth still requires an authenticated Entra token before the request reaches the Functions host.
Configuration
Setting | Values/default | Purpose |
|
| Selects dashboard/opportunity source. Invalid values fail explicitly. |
| Empty by default | Server-side Streamable HTTP MCP endpoint used only when |
|
| Blob container for drafts and simulated saved quotes. |
|
| Clearly labelled scope used only when a trusted authenticated principal ID is unavailable. |
|
| Azurite connection string. Do not commit real credentials. |
| Set by Bicep in Azure | Existing storage account Blob endpoint. |
| Set by Bicep in Azure | Existing user-assigned managed identity client ID. |
Data modes
Synthetic mode returns stable IDs:
opp-contoso-expansionopp-fabrikam-pilotopp-northwind-renewal
Every response is labelled Synthetic demo data and Simulation - not CRM.
MCP mode uses the official @modelcontextprotocol/sdk Client with StreamableHTTPClientTransport. The upstream allowlist is fixed to:
get_sales_dashboardwith no argumentsget_opportunitywith{ "opportunityId": "..." }
The upstream must return normalized integer-cent and basis-point fields matching src/functions/salesData.ts, through structuredContent or JSON text. Connection, tool, schema, and parsing errors are returned explicitly; there is no synthetic fallback.
Example local override:
$env:DATA_MODE = "mcp"
$env:CRM_MCP_URI = "http://localhost:3001/mcp"
npm startPublic MCP surface
Tool | Behavior |
| Read-only dashboard. |
| Read-only opportunity and fixed quote catalog. |
| Creates or revises an identity-scoped Blob-backed draft. |
| Requires the exact |
| Reads a saved simulated quote. |
All tools point to ui://sales/workspace.html, served as text/html;profile=mcp-app. Read tools advertise readOnlyHint. Tool responses include text plus structuredContent.
Quote persistence and identity
Money is stored as integer cents and discounts as integer basis points. A preview creates server revision 1; editing requires the exact current revision and increments it using Blob ETag optimistic concurrency. Save uses a deterministic quote ID derived from the draft and conditional Blob creation. Repeating the same revision and idempotency key returns the original saved record.
Blob paths are scoped by a SHA-256 hash of a trusted principal ID when the Functions trigger exposes one. Anonymous/local hosts often do not expose a principal, so the PoC uses the labelled POC_IDENTITY_FALLBACK. Production is blocked until a Microsoft 365 authenticated call proves that the expected Entra principal reaches InvocationContext.triggerMetadata and produces per-user isolation.
The existing Bicep storage account and user-assigned managed identity are reused. The existing RBAC module grants Blob Data Owner and Queue Data Contributor as required by the template. Bicep declares the sales-quotes container; no new infrastructure stack is introduced.
Azure deployment flow
The parent workflow owns validation and deployment. This project is prepared for:
azd env new <environment-name>
azd env set PRE_AUTHORIZED_CLIENT_IDS <approved-client-id-list>
azd env set VNET_ENABLED false
azd provision
azd deployThe deployed endpoint is:
https://func-api-3hzb2nt3ps5ki.azurewebsites.net/runtime/webhooks/mcpTo switch the deployed app to a read-only CRM MCP source, set DATA_MODE=mcp and CRM_MCP_URI as server-side Function App settings through the approved deployment/configuration process. Do not expose CRM_MCP_URI to the app UI.
Microsoft 365 package
appPackage contains a declarative agent and a pinned RemoteMCPServer plugin with only the five PoC tools. The deployed package uses Entra application d22d3dce-f56f-4a9a-bf08-5d02412cf2a0 and an Enterprise token-store Entra SSO configuration created by Agents Toolkit. The generated Application ID URI is api://auth-0e8e3566-ac5e-4e00-b19e-7731a1d65d55/d22d3dce-f56f-4a9a-bf08-5d02412cf2a0.
The package passed all 61 Agents Toolkit validation rules and was installed in the test tenant (Title ID T_2064f322-b040-883c-9e43-c0da44867a24). The chart-enabled UI bundle is deployed to the existing Function App; the M365 package did not require a manifest change. One interactive user consent/invocation in an authenticated M365 Copilot session remains required to prove inline rendering and per-user identity. Replace publisher URLs/contact values before broader distribution.
Copilot Cowork skills
cowork/ contains a Unified App Manifest, MCP tool descriptions, and three portable Agent Skills inspired by the L2Q Cowork package structure:
pipeline-review-pack— reconciled pipeline analysis plus optional Excel workbook, PowerPoint narrative, and executive email draft.opportunity-research-brief— grounded Word meeting brief and optional email draft without invented contacts or activity.simulated-quote-review— server-calculated quote preview, optional Word review, and explicitly confirmed simulated save.
The package uses the same five MCP tools as the M365 declarative agent. Replace the endpoint and OAuth token-store reference in cowork/manifest.json before upload; never place credentials in the skill files. Build it with:
Set-Location C:\Flutter\sales-mcp-ui-poc
.\cowork\scripts\package.ps1Cowork skills are instructions that orchestrate connector tools; they are not an authorization boundary. Server-side validation, ownership checks, the exact CONFIRM_SIMULATED_SAVE token, current revision, and idempotency key remain mandatory. Cowork MCP Apps may render the same ui://sales/workspace.html resource, but host capability and confirmation behavior must be verified in the target tenant.
Architecture artifacts
Draw.io review record — final artifact only; intermediate passes were not retained
The diagrams distinguish Entra token issuance, Easy Auth enforcement, Function managed identity, host-mediated widget rendering, optional upstream CRM MCP access, and the unproven per-user identity-isolation production gate.
Security caveats
This is a PoC, not a CRM write integration.
save_demo_quoteis state-changing only within the simulation Blob container.The remote CRM tool contract is read-only and hardcoded.
The MCP URI is server-side configuration only.
Easy Auth and the existing Entra template must remain enabled for deployed use.
M365-to-Functions identity propagation is a production gate, not an assumption.
Synthetic data must not be represented as real customer or pipeline data.
No real credentials belong in
local.settings.json, manifests, or source.
Cleanup
Azure resources and the installed M365 app are intentionally preserved for user verification. Local dependency and runtime cache directories are removed after final validation; restore dependencies with npm ci. Do not run azd down until the user explicitly approves deletion.
This server cannot be deployed
Maintenance
Related MCP Connectors
Read-only MCP access to sessions, funnels, campaigns, errors, live visitors, and anomalies.
Read-only MCP for Copart and IAA/IAAI search, history, filters, locations, shipping, and usage.
Read-only MCP access to performix.app's public capability guides and book corpus.
Query your org's data in natural language — read-only MCP access to SQL, NoSQL, files & warehouses.
Related MCP Servers
- FlicenseNot gradedqualityBmaintenanceProvides read-only access to Salesforce orgs via MCP, enabling SOQL queries, sObject descriptions, and object listing using OAuth 2.0 Client Credentials.-
- AlicenseBqualityCmaintenanceEnables support agents to look up customer account details and billing or incident information through two read-only MCP tools for triage workflows.2MIT
- AlicenseBqualityCmaintenanceEnables read-only access to Epicor Kinetic ERP data, including sales orders, customers, parts, and shipments, through secure MCP tools.4MIT
- FlicenseNot gradedqualityBmaintenanceEnables secure, read-only querying of only predefined logical database views through MCP tools and a REST API, using validated SQL and semantic view metadata.-