Microsoft 365 Copilot Β· FastMCP Β· Salesforce CRM Β· Streamable HTTP Β· Azure Container Apps
An enterprise-grade Model Context Protocol server that gives fintech sales professionals a complete Lead-to-Quote workflow without ever leaving the flow of work. It powers two surfaces:
- A Microsoft 365 Copilot Declarative Agent β chat against your Salesforce pipeline with rich, branded HTML widgets, built with the M365 Agents Toolkit (VS Code) and the RemoteMCP plugin.
- A Copilot Cowork plugin β five co-work skills (research briefs, follow-up automation, pitch decks, meeting scheduling, pipeline review packs) that turn Salesforce signals into finished email, Word, PowerPoint and Excel artifacts. See
cowork/.
| Sales Dashboard | Pipeline Summary |
|---|---|
![]() |
![]() |
| Account 360 (with M365 signals) | Opportunities (Kanban + table) |
|---|---|
![]() |
![]() |
| Leads | Dark mode |
|---|---|
![]() |
![]() |
Widgets carry the Microsoft 365 Copilot look β a light surface, the signature rainbow accent bar, Segoe UI typography β and adapt automatically to light, dark and high-contrast themes.
| Capability | Details |
|---|---|
| Salesforce CRM | Leads, Accounts, Contacts, Opportunities, Quotes, Products, Activities |
| Authentication | Client Credentials, OAuth Bearer pass-through, Entra SSO (switchable via env) |
| UI Widgets | Copilot-branded HTML+Skybridge widgets bound to _meta.structuredContent, with light/dark/contrast theming |
| M365 Integration | Agenda/email helpers for Teams, Outlook via M365 Tooling Servers |
| Cowork plugin | Five co-work skills that orchestrate Salesforce data with Cowork's built-in email/Word/PowerPoint/Excel |
| Market Intelligence | Designed to pair with external market-intelligence MCP tools for company, macro and market context |
| Compliance | MiFID II / GDPR / FCA compliance flags and deal desk approval tracking |
| Transport | Streamable HTTP (/mcp) or stdio |
| Cloud | One-command deploy to Azure Container Apps (scripts/deploy-l2q.ps1) |
Microsoft 365 Copilot
β
βββ Declarative Agent βββΊ RemoteMCP plugin
β β
βββ Cowork plugin βββΊ remoteMcpServer connector
β
βΌ
Sales Assistant MCP Server (FastMCP / Streamable HTTP)
hosted on Azure Container Apps
β
βββββββββββββββββββββββββββΌββββββββββββββββββββββββββ
βΌ βΌ βΌ
Salesforce REST API M365 Tooling Servers Market-intelligence MCP
(SOQL) Teams Β· Outlook Β· Calendar (external, in agent)
Leads / Accounts / (referenced, not proxied) company Β· macro Β· headlines
Contacts / Opps /
Quotes / Products
Each tool returns:
contentβ Narrative text with prompt suggestions so the agent can continue a natural conversation._meta.structuredContentβ JSON data consumed by the HTML+Skybridge widgets atui://widgets/<name>.html.
Most tools are read-only. Create/update tools (create_record, update_record) are write operations and require explicit confirmation (confirm=true).
- Python 3.11+
- Salesforce Connected App with Client Credentials flow enabled
- (Optional) Entra ID app registration for SSO
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -e .[dev]Copy-Item env\l2q.example.env env\l2q.envEdit env/l2q.env:
# Auth mode: client_credentials | oauth_bearer | entra_sso
SF_AUTH_MODE=client_credentials
SF_INSTANCE_URL=https://your-org.my.salesforce.com
SF_CLIENT_ID=3MVG9...
SF_CLIENT_SECRET=...
SF_API_VERSION=v59.0
# Only needed for entra_sso mode
AAD_APP_CLIENT_ID=
AAD_APP_TENANT_ID=scripts\run-l2q.ps1
# OR
python -m l2q.cli --transport http --host 0.0.0.0 --port 8080The server listens at http://localhost:8080/mcp.
- Open MCP Inspector β New HTTP Connection β
http://localhost:8080/mcp - Add header
Authorization: Bearer <token>(for oauth_bearer / entra_sso modes) - Call
get_sales_dashboardto see your morning briefing
The server ships with an idempotent deployment script that builds the image in Azure Container Registry (cloud build) and rolls it out to Azure Container Apps.
az login
.\scripts\deploy-l2q.ps1
# Optional: .\scripts\deploy-l2q.ps1 -Location eastus -ImageTag v1.2.0It provisions / updates (all idempotent, safe to re-run):
| Resource | Name | Notes |
|---|---|---|
| Resource Group | rg-l2q-dev |
|
| Container Registry | acrl2qdev |
Basic SKU |
| Container Apps Env | cae-l2q-dev |
auto-creates Log Analytics |
| Container App | ca-l2q-dev |
scale-to-zero, 0β3 replicas, 0.5 vCPU / 1 GiB |
On success it prints the live endpoints:
MCP endpoint : https://<app>.<region>.azurecontainerapps.io/mcp
Health check : https://<app>.<region>.azurecontainerapps.io/health
Set the Salesforce credentials on the running app:
az containerapp update --name ca-l2q-dev --resource-group rg-l2q-dev `
--set-env-vars SF_AUTH_MODE=client_credentials `
SF_INSTANCE_URL=https://your-org.my.salesforce.com `
SF_CLIENT_ID=... SF_CLIENT_SECRET=...$base = "https://<app>.<region>.azurecontainerapps.io"
# Health
Invoke-RestMethod "$base/health" # -> {"status":"ok"}
# MCP handshake + tool list
$h = @{ "Content-Type"="application/json"; "Accept"="application/json, text/event-stream" }
Invoke-WebRequest "$base/mcp" -Method Post -Headers $h `
-Body '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'A healthy server responds to initialize with serverInfo.name = "l2q" and
serves the full Salesforce tool catalog (58 tools).
| Tool | Description |
|---|---|
search_leads |
Search by name, company, status or industry |
get_lead |
Full lead detail |
get_recent_leads |
All open leads (most recent first) |
get_leads_by_status |
Filter by status (New / Working / Qualified) |
| Tool | Description |
|---|---|
search_accounts |
Search financial institution accounts |
get_account |
Full account detail |
get_account_contacts |
Contacts at an account |
get_account_opportunities |
Opportunities for an account |
| Tool | Description |
|---|---|
search_contacts |
Search by name, account or title |
get_contact |
Full contact profile |
get_contact_activity_history |
Call / email / meeting history |
| Tool | Description |
|---|---|
search_opportunities |
Filter by stage, value, account |
get_opportunity |
Full opportunity detail |
get_open_opportunities |
Open pipeline opportunities |
get_opportunities_by_stage |
Deals at a specific stage |
get_pipeline_summary |
Pipeline by stage with totals |
| Tool | Description |
|---|---|
search_quotes |
Find quotes by name or status |
get_quote |
Full quote with pricing |
get_quote_pdf_package |
Retrieve generated quote PDF artifacts/download URLs for email workflows |
get_quote_products |
Line items for a quote |
get_opportunity_quotes |
All quotes on an opportunity |
| Tool | Description |
|---|---|
search_products |
Search the product catalog |
get_product |
Product details and list price |
| Tool | Description |
|---|---|
get_activity_timeline |
Timeline for account/opp/contact |
get_open_tasks |
Open tasks sorted by priority |
| Tool | Description |
|---|---|
draft_create_record |
Build a pre-create draft form for account/contact/case/opportunity/task/quote/lead/event |
create_record |
Create the record in Salesforce after explicit confirmation |
get_record_for_update |
Load a single record as an editable update draft |
update_record |
Apply updates in Salesforce after explicit confirmation |
get_task / get_event / get_case |
Retrieve single objects for update flows |
| Tool | Description |
|---|---|
search_reference_records |
Search accounts/contacts/opportunities/leads/cases for searchable picker dropdowns |
| Tool | Description |
|---|---|
run_salesforce_report |
Run a Salesforce report and render tabular results in widget UI |
| Tool | Description |
|---|---|
get_compliance_flags |
MiFID II / GDPR / FCA flags |
get_deal_desk_status |
Approval process status |
| Tool | Description |
|---|---|
prepare_meeting_agenda |
Teams meeting agenda from Salesforce data |
prepare_email_summary |
Professional email/proposal draft |
get_sales_dashboard |
Morning briefing: pipeline + tasks + leads |
capture_lead_from_email |
Build a lead draft from Outlook email context |
Teams / Outlook / Calendar: Use the M365 Tooling Servers directly from your Copilot Declarative Agent for scheduling meetings and sending emails.
HTML+Skybridge widgets are registered as MCP resources at ui://widgets/<name>.html:
| Widget | Purpose |
|---|---|
dashboard.html |
Sales dashboard β pipeline + tasks + leads |
leads.html |
Lead list with status filter |
opportunities.html |
Kanban + table view |
quotes.html |
Quote cards + line items |
accounts.html |
Account cards + product catalog |
contacts.html |
Contact cards + detail profile |
pipeline.html |
Funnel + stacked bar chart |
activities.html |
Timeline + agenda/email viewer |
Each widget reads structuredContent from the MCP _meta via the Skybridge postMessage protocol.
All widgets now render in compact mode by default (for inline chat iframes) and include a top-right Maximize/Restore button so the user can expand to full-height working mode as needed.
Create/update forms now support search-and-pick lookup flows for relationship IDs (AccountId, ContactId, WhoId, WhatId, OpportunityId, and opportunity PrimaryContactId) to avoid manual ID typing.
- Meeting-driven create:
-
I want to create an opportunity for the meeting with Jim that I just had- Agent flow: runsearch_reference_recordsto find the account/contact β openopportunity_form.htmlwith prefilled data and candidate lookups. - Blank create:
-
Create a new opportunity- Agent flow: rundraft_create_record object_type=opportunityβ use in-form pickers (Search/Pick) viasearch_reference_recordsto choose account/contact before create.
The prompts below are written for a Copilot Declarative Agent that can:
- read emails, Teams meeting summaries, and Teams messages,
- use this L2Q MCP server,
- use an external market-intelligence MCP for company/market context.
When a prompt references βthis deal/account/leadβ, expected resolution flow is:
- Resolve entity from the current artifact context (email/thread/meeting),
- If ambiguous, show candidates and ask user to pick,
- Continue with deeper analysis/drafting.
| Title | Prompt | Context source & expected flow |
|---|---|---|
| Lead Intake Snapshot | From recent client emails, Teams messages, and meeting summaries, produce a lead intake summary with qualification signals, urgency, and recommended next action. | Artifact-led: starts from current conversation artifacts; asks for entity confirmation only if multiple companies are referenced. |
| Qualification Decision | For lead [lead name or ID], assess qualify/nurture/disqualify and explain why using communication evidence plus external company and market context. |
Entity-led: if no ID is provided, resolve by name and ask user to confirm best match. |
| Opportunity Readiness Check | For opportunity [name or ID], assess stage readiness, missing required data, stakeholder gaps, and what must be completed before stage progression. |
Entity-led: if unspecified, shortlist active opportunities and let user choose one. |
| Quote Readiness Review | For opportunity [name or ID], evaluate quote readiness using recent client interactions and external market/company signals, then list blockers and next steps. |
Entity-led: requires selected opportunity, then combines internal communication context + external intelligence. |
| Close Plan & Commit Confidence | For opportunity [name or ID], create a close plan with milestones, owners, confidence drivers, and top slippage risks. |
Entity-led: one selected opportunity; returns plan and confidence narrative. |
| Weekly L2Q Report | Generate my weekly lifecycle report: phase distribution, phase movement, stalled deals, top risks, and next-week priorities. | Portfolio-led: runs across user scope; asks for region/team filter only if needed. |
| Title | Prompt | Context source & expected flow |
|---|---|---|
| At-Risk Deal Rescue | Identify the most at-risk open opportunity in my portfolio and produce a two-week rescue plan using communication signals and external market/company context. | Portfolio-first then drill-down: proposes top at-risk deal, confirms, then generates rescue actions. |
| Executive Forecast Narrative | Build this weekβs executive forecast narrative combining pipeline movement, communication evidence, and external market developments. | Portfolio-led synthesis for leadership reporting; no single-entity selection required. |
| Smart Meeting Prep Pack | For account [name], create a meeting prep pack with objective, talking points, likely objections, and market-aware responses based on latest interactions. |
Account-led: resolves account, then pulls artifact context + external updates. |
| Post-Meeting Auto-Orchestration | From this meeting summary, generate follow-up actions, owner assignments, timeline updates, and a polished client follow-up message aligned to deal strategy. | Artifact-led: starts from current meeting summary; if multiple linked deals exist, asks user to pick target deal. |
| Renewal Risk & Expansion Plan | For account [name], create a renewal risk assessment and expansion plan using relationship signals, engagement patterns, and external outlook. |
Account-led with multi-deal roll-up where applicable; returns 30/60/90 plan. |
| Board-Ready Pipeline Pack | Build a board-ready pipeline package for [portfolio/team/region] with confidence rationale, concentration risk, and recommended management interventions. |
Portfolio-led: asks scope if not provided, then composes pack narrative and priorities. |
Use the preview script to render all widgets in a browser without running MCP tools:
scripts\run-widget-preview.ps1Then open http://127.0.0.1:8090/ and select any widget page.
You can also choose a custom host/port:
scripts\run-widget-preview.ps1 -HostName 127.0.0.1 -Port 8091Set SF_AUTH_MODE in your env file:
| Mode | How it works |
|---|---|
client_credentials |
Server acquires a Salesforce token using SF_CLIENT_ID + SF_CLIENT_SECRET (Connected App) |
oauth_bearer |
Caller passes a Salesforce OAuth token in Authorization: Bearer β¦ header β server forwards it |
entra_sso |
Caller passes a Microsoft Entra ID JWT β server validates it then forwards to Salesforce |
docker build -t l2q-mcp .
docker run -p 8080:8080 \
-e SF_AUTH_MODE=client_credentials \
-e SF_INSTANCE_URL=https://your-org.my.salesforce.com \
-e SF_CLIENT_ID=... \
-e SF_CLIENT_SECRET=... \
l2q-mcpThe server exposes a /health endpoint at GET /health that returns {"status": "ok"} β suitable for container orchestration liveness/readiness probes.
pip install -e .
python -m pytest tests/ -v- Create a new Declarative Agent in VS Code using M365 Agents Toolkit
- Add the RemoteMCP plugin with endpoint
https://<your-server>/mcp - Add the M365 Tooling Servers for Teams, Outlook and Calendar
- (Optional) Add a market-intelligence MCP plugin to the same agent for external company/market context
- Set
Authorization: Bearer <Entra token>in the RemoteMCP plugin config
The cowork/ folder contains a Copilot Cowork plugin that
reuses this same MCP server as a remoteMcpServer connector. Where the
Declarative Agent is the chat surface for fast lookups, the Cowork plugin is
the co-work surface for deep work β it produces finished email, Word,
PowerPoint and Excel artifacts. It ships five skills across two fintech personas:
| Skill | Persona | Built-in surfaces |
|---|---|---|
account-research-brief |
Account executive | Word, Email |
deal-followup-automation |
Account executive | Email, Word |
pitch-deck-builder |
Account executive | PowerPoint |
client-meeting-scheduler |
Account executive | Calendar, Email |
pipeline-review-pack |
Sales manager | Excel, PowerPoint, Email |
Package it with cowork/scripts/package.ps1 and
sideload sales-assistant-cowork.zip. See cowork/README.md
for the full story, connector auth, and packaging steps.
Sales Assistant is a Microsoft 365 Copilot extensibility sample for fintech B2B sales β built for professionals managing large, complex deals with financial-institution clients in a highly regulated environment (MiFID II / GDPR / FCA). It demonstrates an MCP server powering both a Declarative Agent and a Copilot Cowork plugin over live Salesforce data.






