CRM Account Planning Cowork Agent MCP Server
by cdhamija
README.md
# CRM Account Planning Cowork Agent — MCP Server
One-click Azure deployment for the integration layer described in
[microsoft/ai-agent-runbooks → CRM-Account-Planning-Cowork-Agent → 2.Architecture.md](https://github.com/microsoft/ai-agent-runbooks/blob/main/01-scenarios/CRM-Account-Planning-Cowork-Agent/2.Architecture.md).
The runbook is guidance, not code. The only component in that architecture you actually deploy to
Azure is the **MCP server** — Copilot Cowork, the built-in skills, custom skills, and the
Organization Asset Library are all M365 tenant configuration. This repo builds and deploys that MCP
server, with the tool design, payload shaping, and governance switches the runbook prescribes.
[](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fcdhamija%2Fcrm-account-planning-mcp%2Fmain%2Fazuredeploy.json)
[](https://armviz.io/#/?load=https%3A%2F%2Fraw.githubusercontent.com%2Fcdhamija%2Fcrm-account-planning-mcp%2Fmain%2Fazuredeploy.json)
The button works in **any Azure tenant** — it only needs Contributor on a resource group. With the
default `dataMode = sample` it deploys and runs end to end with no CRM credentials, so you can wire
it into Copilot and validate the whole path before touching Salesforce.
---
## What gets deployed
| Resource | Purpose |
|---|---|
| App Service plan (Linux, B1 by default) | Hosts the server; `alwaysOn` so the first tool call isn't a cold start |
| Web App (Node 20 LTS) | The MCP server, HTTPS-only, TLS 1.2 minimum, FTPS disabled, health probe on `/health` |
| Application Insights + Log Analytics | The MCP server is in the critical path of a sales workflow — monitor it like production |
| System-assigned managed identity | Ready for Key Vault references or downstream Entra-protected APIs |
Source is shipped as a prebuilt zip release asset and pushed to the site by the ARM deployment
itself, so there is no container registry, no build pipeline in your subscription, and no secrets
in the template. The deployment finishes with the code already running.
### Parameters
| Parameter | Default | Notes |
|---|---|---|
| `siteName` | `crm-mcp-<unique>` | Must be globally unique — it becomes the hostname |
| `appServicePlanSku` | `B1` | `B1`, `B2`, `P0v3`, `P1v3` |
| `dataMode` | `sample` | `sample` = built-in demo data, no credentials. `salesforce` = live CRM |
| `salesforceInstanceUrl` / `salesforceClientId` / `salesforceClientSecret` | empty | Required only when `dataMode = salesforce` |
| `mcpApiKey` | empty | Optional bearer token on `/mcp`. **Set it for anything beyond a demo** |
| `allowWrites` | `true` | Set `false` for the read-only pilot fallback the runbook describes |
| `packageUri` | latest release asset | Prebuilt zip to deploy. Point at your own build to ship your tool set |
### After deployment
The deployment outputs `mcpEndpoint` and `healthEndpoint`. Check the server is up:
```bash
curl https://<siteName>.azurewebsites.net/health
# {"status":"ok","dataMode":"sample","writesEnabled":true,"protectedByApiKey":false}
```
Then point any MCP client at `https://<siteName>.azurewebsites.net/mcp`. DNS for a brand-new
hostname can take a minute to propagate.
---
## The tools
Five task-shaped tools, matching the runbook's "do this" table. Each returns a bounded,
field-selected payload — never raw CRM records.
| Tool | Returns |
|---|---|
| `get_account_360` | One account: firmographics, open opportunities, renewal dates, product usage, key contacts, recent activity — assembled server-side |
| `get_pipeline` | Open opportunities for an owner or segment with stage, value, close date, risk |
| `get_opportunity_detail` | One opportunity with custom fields and field history |
| `get_account_relationships` | Parent/child accounts, partners, competitive displacements |
| `update_opportunity_next_step` | A single, narrow write — gated by `allowWrites` |
Design constraints honoured deliberately:
- **5 tools, not 40.** Response quality degrades past ~10, and all tools of a matched plugin are
injected into the prompt.
- **No `run_soql_query`.** Query composition stays server-side so the model never needs CRM schema
knowledge.
- **Bounded payloads.** Row caps, field selection, and truncated free-text fields keep the token
window intact.
- **Write blast radius of one.** The write tool takes exactly one record id and one field.
---
## Connecting it to Copilot
1. Deploy, then confirm `/health`.
2. Register the endpoint `https://<siteName>.azurewebsites.net/mcp` as an MCP plugin — via
Copilot Studio, or admin-deployed org-wide from the Microsoft 365 admin center (the governed
path).
3. Write the plugin description carefully. Past five plugins, selection is semantic matching on the
**plugin description**, not on individual tools.
4. Add your custom skills for account-plan structure and QBR narrative rules, and point the
artifact templates at your Organization Asset Library (`.potx` / `.dotx` / `.xltx`).
Re-run your evaluation set after every deployment of this server: MCP plugins resolve tools
dynamically at runtime, so a server change alters agent behaviour immediately with no release gate.
---
## Identity: read this before production
The target pattern is per-user OAuth — the user authenticates to the MCP server, the server
exchanges for a CRM token scoped to that user, and every tool call runs under it, so CRM record
visibility follows the seller's own permissions and writes are attributable in the CRM audit trail.
The `salesforce` data mode in this repo implements the runbook's documented **fallback**: OAuth 2.0
client credentials against a connected app, i.e. a scoped service identity. Use it to get moving,
and treat it as a deliberate, documented limitation:
- Restrict the plugin audience to a group whose CRM access is uniform.
- Consider `allowWrites = false` until per-user identity is in place.
- Validate what identity your server actually receives in the exact Copilot surface you publish to
before you build on the passthrough assumption.
Move the client secret to Key Vault and reference it from app settings once you are past the pilot.
---
## Local development
```bash
npm install
npm start # http://localhost:3000/mcp
```
Environment variables: `PORT`, `DATA_MODE`, `ALLOW_WRITES`, `MCP_API_KEY`, `MAX_PIPELINE_ROWS`,
`SF_INSTANCE_URL`, `SF_CLIENT_ID`, `SF_CLIENT_SECRET`, `SF_API_VERSION`.
Rebuild the ARM template after editing the Bicep:
```bash
az bicep build --file infra/main.bicep --outfile azuredeploy.json
```
Publishing a new package: push a tag (`v*`) and the `package` workflow builds the zip, attaches it
to the release, and the button picks it up from `releases/latest/download`.
## Licence
MIT. Sample data is fictional. Not an official Microsoft product.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues