What it takes to wire an MCP-enabled web app into your own intranet's Teams with per-user enterprise identity — told through Ridgeline's build.
Teams chat→Copilot Studio (Claude)→Entra ID OAuth→Ridgeline MCP→Honest-cost engine
Why
Meet your users where they already are
All we assume about the client: they're a Microsoft shop. If your users live in Teams, your agent should meet them there — no new logins, no new tabs.
Ridgeline already had the 2026 architecture: MCP as the operating channel, web UI as review surface, server-enforced write gates.
The missing piece for any intranet: an identity story where every Teams user is themselves — no shared API keys, real attribution on every write.
Industry pattern validated by research: autonomy on reads, human-in-the-loop on writes — enforce it on the server and any client can connect safely.
Architecture · ADR-022
Multi-tenant enterprise identity
One app registration, vendor-ownedMulti-tenant, lives in Digital Rain's Entra. Client orgs onboard with a single admin-consent click + an allowlist entry. No per-client builds.
Identity = (tenant ID, object ID)Immutable Microsoft identifiers. Email is informational only — never used for lookup or authorization. Binding written once, during interactive web sign-in.
Authentication ≠ authorizationEntra proves who you are; Ridgeline roles decide what you may do. A valid token with no promoted user gets nothing.
Config-gatedNo env vars → the whole feature is inert. Enablement is a deployment decision, not a code path.
Every auth path — cookie, service key, API key, Entra JWT — resolves to one internal Principal before any decision. Non-goal, stated in the ADR: no Microsoft Graph access. The agent cannot read Outlook, by design.
Onboarding · Entra
Allowlisting a tenant — two switches, two places
1 · Their Entra: admin consentTheir IT admin opens one URL — login.microsoftonline.com/<tenant>/adminconsent?client_id=… — which "installs" the app (creates its service principal in their directory) and grants the Ridgeline.Access scope tenant-wide. Optional lockdown: Enterprise Applications → "Assignment required" to choose who may use it.
2 · Your env: the trust boundaryAppend their tenant GUID to RIDGELINE_ENTRA_ALLOWED_TENANTS and redeploy. The JWT verifier checks every token's tid claim against this list — this, not consent, is what Ridgeline trusts.
Either switch alone → 401. A perfectly valid Microsoft token from an unlisted tenant is refused; consent without the env entry gets nothing. And nothing happens per-client in your Entra — one multi-tenant registration serves every org.
Onboarding · Gotcha
The allowlist names user tenants, not the app's
A token's tid is the tenant the user signed in from — where the app registration lives is irrelevant at the door.
Our own dogfood list already holds two GUIDs: the registration's home tenant and the separate M365 pilot tenant where Teams + Copilot Studio live — identities from both worlds call the API.
Each client org is then exactly one more GUID — no new registration, no new secrets, no code change.
Entra token→signature + v2 issuer→tid ∈ allowlist?→scope check→(tid, oid) → user → role
What shipped in the repo
~1 day of code, fully tested
"Sign in with Microsoft" on the web app (Supabase azure provider), with the (tid, oid) binding captured in the auth callback — the only place it can ever be written.
Entra JWT verification on the MCP/REST bearer path: JWKS signature, v2 issuer reconstruction, tenant allowlist, scope check — each invariant with its own failing test.
RFC 9728 discovery metadata at /.well-known/oauth-protected-resource for spec-compliant MCP clients.
326/326 tests · migration applied before deploy · verified inert-when-unconfigured in production before any config existed.
Three documents, per review: ADR (decisions) · implementation plan · operations runbooks.
The part nobody warns you about
The Microsoft identity gauntlet
Wall
What it looked like
Fix (now in runbooks)
Personal vs work accounts
"You can't sign in here with a personal account"
Both founder emails were MSAs; created first native member account
#EXT# guest identities
Copilot Studio walls, "isn't in our system"
Native onmicrosoft.com admin for all M365 work
Bare tenants
No Teams, no licenses anywhere
M365 Business Basic trial + license via Graph API
Two-layer licensing
"You cannot publish" with an active trial
Tenant capacity SKU ≠ user license; sign out/in to refresh
Dataverse missing
Infinite spinner
Default environment needs a database; add + wait
HK CDN interference
TLS reset on msocdn.com
DNS switch / hotspot for portal sessions
Every wall is documented with its fix — your rollout inherits the map, not the maze.
The agent
Copilot Studio, wired to Ridgeline MCP
Agent instructions versioned in the repo, ported from the Claude-side skills: never invent data, literal APPLY confirmations, routing groups, one supplier per session.
Connection: OAuth 2.0 (manual) against the Entra app — each Teams user consents once and gets their own token; attribution flows to their own Ridgeline user.
Server-side gates hold regardless of client: preview diffs, confirmation strings, impact-report acknowledgements, the 5% auto-apply envelope.
Default model in Copilot Studio: Claude Sonnet — a Claude agent driving MCP tools, the same pattern that built the system.
The MCP connection: ridgeline.works/api/mcp · OAuth 2.0 manualModel: Claude Sonnet 4.6, selected over the GPT-5 family
Proof
First live call — the honest number, end to end
Journey Water 2D1N · 14 people · HK$1,662.80 — with the engine's reviewable-share disclosures intact.
Shipped
Published — and live in Microsoft Teams
Per-user OAuth: each user connects as themselves before any data flowsThe arrival: Ridgeline's install card inside Teams
Publish required one last boss fight — Copilot Studio's two-layer licensing — now documented in the troubleshooting index so it never costs an hour again.
Money
Cost posture
Now: HK$0M365 Business Basic trial (Teams) + Copilot Studio trial. Recurring billing already off; both expire ~Aug 19.
Founders Hub: US$1,000Azure credit in the same tenant. Copilot Studio messages can bill pay-as-you-go against it after the trial — no per-user licenses for end users.
Only the maker paysThe ~HK$1,500/mo license is for agent authors. Teams end users need none.
Know your trial cliffBoth trials expire on a date — decide PAYG vs maker license before it lands, not after.
Next
The recipe, for any tenant
Your app: an MCP endpoint with bearer auth → add Entra JWT verification + RFC 9728 discovery metadata.
Your Entra: one multi-tenant app registration; the target org's admin clicks one consent URL, their tenant GUID joins the allowlist (rehearsed end-to-end on a cold tenant).
Each user: sign into the web app with Microsoft once → promoted by an admin → add the agent in Teams and consent as themselves.
The agent: Copilot Studio + the MCP connector, published to the org catalog — everything scripted or documented: entra-id-setup.md · copilot-studio-agent-setup.md · ADR-022.
From "they're a Microsoft shop" to a working Teams agent with per-user identity: one weekend, every decision written down.