A Meeting App for Incident Bridges, Scoped With RSC Permissions
Build a Teams meeting app that opens an in-meeting dialog to capture incident timeline entries, scoped with resource-specific consent so it sees only the meetings it's added to.
- #microsoft-teams
- #ai
- #meeting-apps
- #rsc
- #incident-response
Live incident bridges generate a timeline as they happen — “rolled back the api-gateway deploy at 14:12,” “confirmed customer impact in EU-West at 14:18” — and almost all of it evaporates because nobody can write it down while also being on the call. A Teams meeting app fixes that: a button in the meeting opens a small dialog, a responder types the entry, and it lands in your incident record stamped with who said it and when. The interesting engineering isn’t the dialog; it’s scoping the app tightly enough that a security team will actually approve it for a bridge that might discuss sensitive systems. That scoping is what resource-specific consent (RSC) gives you, and it’s the heart of this post.
Meeting surfaces, and which one to use
A meeting app can show up on several surfaces, and choosing the right one matters for usability under pressure:
- In-meeting dialog (TaskModule) — a modal that pops over the meeting for a quick, focused input. Perfect for “log a timeline entry” because it’s fast and gets out of the way.
- Meeting side panel — a persistent panel alongside the meeting for ongoing context, like the live timeline so far.
- Meeting stage — shared content all participants see; overkill for note capture.
You declare these in the manifest’s configurableTabs with the right scopes and context values:
"configurableTabs": [
{
"configurationUrl": "https://app.example.com/config",
"scopes": ["groupChat"],
"context": ["meetingSidePanel", "meetingChatTab"]
}
]
For our use case the workhorse is the in-meeting dialog, triggered from a side-panel button or a bot action, because a responder mid-call wants to type one line and dismiss, not navigate a panel.
RSC: ask for the meeting, not the tenant
This is the part that earns the security team’s signature. The naive way to read meeting context is to grant the app broad application permissions — OnlineMeetings.Read.All, ChatMessage.Read.All — which let it read every meeting and chat in the tenant. For an incident bridge, that’s a wildly over-scoped ask, and rightly hard to approve.
Resource-specific consent flips it: the app requests permissions at the resource scope, and those permissions only apply to the specific chat or meeting the app is added to. You declare them in the manifest’s authorization.permissions.resourceSpecific:
"authorization": {
"permissions": {
"resourceSpecific": [
{ "name": "OnlineMeeting.ReadBasic.Chat", "type": "Application" },
{ "name": "ChatMessage.Read.Chat", "type": "Application" }
]
}
}
Now the app can read basic meeting info and messages only for the meetings it’s installed into. The blast radius is one bridge, not the whole tenant. That’s the difference between “approved” and “escalated to a six-week security review.” Confirm the exact RSC permission names against the current Microsoft docs when you implement — the permission catalog is precisely the kind of detail worth checking against the source rather than trusting from memory, since names and scopes do change.
The discipline to apply here is least privilege: for every permission, ask “does capturing a timeline entry actually need this?” OnlineMeeting.ReadBasic.Chat to get the meeting id, yes. Anything that lets you read full meeting transcripts when all you’re doing is logging notes — drop it.
The in-meeting dialog flow
The dialog itself is an Adaptive Card form opened as a TaskModule. A side-panel button or bot action triggers it, the responder fills it in, and the submit handler writes to your incident store with the meeting id as the correlation key:
// open the in-meeting dialog
const taskModule = {
type: "continue",
value: {
card: timelineEntryCard(), // Adaptive Card: note text, optional severity
title: "Log timeline entry",
height: "small",
},
};
// handle the submit
async function onTaskSubmit(context) {
const { note, severity } = context.activity.value.data;
const meetingId = await getMeetingId(context); // from Teams JS SDK / context
const userId = context.activity.from.aadObjectId;
await incidents.appendTimeline(meetingId, {
note, severity, by: userId, at: new Date().toISOString(),
});
return { task: { type: "message", value: "Logged." } };
}
The two things every entry carries — the meeting id and the acting user — are what make the resulting timeline attributable. A postmortem doesn’t just need “rolled back at 14:12”; it needs to know who reported that and tie it to the right incident. Stamping both at capture time is what turns scattered notes into an evidentiary record.
Lifecycle: apps get added, removed, and meetings end
The detail most meeting-app implementations forget is lifecycle. Your app can be added or removed mid-meeting, and meetings end. Handle all three:
- On add, initialize the timeline binding for that meeting.
- On remove, stop accepting entries from that instance.
- On meeting end, flush any in-flight entries and refuse new ones — you don’t want a timeline entry logged against a call that ended twenty minutes ago.
Teams surfaces meeting lifecycle events (start/end) you can subscribe to; wire your “stop accepting entries” logic to the end event so the record closes cleanly with the bridge.
Drafting it with AI, verifying the permissions
I’ll draft the manifest and the dialog handler with an AI assistant, but the RSC block is the part I verify by hand, because an over-scoped permission the model added “to be safe” is exactly what would sink the security review:
Prompt: “Write the Teams app manifest for a meeting app with an in-meeting dialog that logs incident timeline entries, using resource-specific consent at meeting/chat scope (ReadBasic.Chat, ChatMessage.Read.Chat) rather than tenant-wide permissions. Include the TaskModule submit handler that stamps each entry with the meeting id and acting user.”
Output (excerpt): A manifest with the
meetingSidePanel/meetingChatTabcontext, aresourceSpecificauthorization block with the two scoped permissions, and a submit handler appending{ note, by, at }keyed by meeting id.
The model gets the structure right. What I audit is that every permission in the RSC block is genuinely needed and scoped to the chat, not the tenant — least privilege is a judgment call the human keeps.
The result
A meeting app that’s fast to use during a bridge, tightly scoped via RSC so it only ever sees the meetings it’s added to, attributable in its timeline records, and clean about its own lifecycle. That combination is what gets it approved and makes it genuinely useful when the call is live. For the related meeting and RSC patterns, the Microsoft Teams category collects the surrounding guides, and the prompts library has a meeting-app-with-RSC prompt you can adapt so you start from a least-privilege design instead of paring one back after a security team flags it.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.