Skip to content

Add scrum-master scenario: Scrum Master autopilot using Node.js SDK - #334

Open
keshav Keshari (keshavk-msft) wants to merge 7 commits into
microsoft:mainfrom
keshavk-msft:scenario/scrum-master
Open

Add scrum-master scenario: Scrum Master autopilot using Node.js SDK#334
keshav Keshari (keshavk-msft) wants to merge 7 commits into
microsoft:mainfrom
keshavk-msft:scenario/scrum-master

Conversation

@keshavk-msft

Copy link
Copy Markdown

Add scrum-master scenario: Scrum Master autopilot using Node.js SDK

What this adds

A scenario extension under scenarios/scrum-master/ — an autonomous Scrum Master built on the Microsoft Agent 365 SDK + OpenAI Agents SDK that runs a scrum team's ceremonies end-to-end: daily standups, board reconciliation, blocker chase, mid-sprint risk warnings, grounded Q&A, and the sprint close report. Complements the existing Chief-of-Staff scenario (#333) by covering the delivery-team side of leader / squad workflows.

Capabilities

Seven flows, each a handler in src/handlers/:

  • Standup — proactive Adaptive Card DMs → summary card to configured channel
  • Reconcile — deterministic phrase classifier → safe auto-transitions on Jira, ambiguous ones go through an SM approval card
  • Chase — blocker → helper matched from SMA_HelperRoster → MCP calendar tool books an unblock meeting on the agent's mailbox
  • Warn — sprint risk alert when progress vs points-to-do thresholds trip
  • Answer — grounded Q&A over live Jira with per-user rolling history (follow-ups like "provide more details" work)
  • Mid-sprint RAG — T-2 red/amber/green table to channel
  • Sprint close — management-ready markdown report on sprint end

Full per-flow mermaid sequence diagrams: scenarios/scrum-master/docs/design.md.

Design principles

Verbatim from docs/design.md:

  1. Deterministic-first. LLM is gated to only the two paths that genuinely need language understanding (free-text Q&A, MCP calendar tool loop).
  2. Card actions never double-fire. Every submit acks in <200 ms and defers heavy work via setImmediate.
  3. Graceful degradation, never a crash. Process-level safety nets keep scheduler alive on connector failures.
  4. One path to Graph. Delegated device-code + MSAL cache — no application-permission client secret required.
  5. Durable state in SharePoint. Seven SMA_* lists survive restarts.
  6. Jira is the source of truth for issue state. Every LLM reply is grounded through a Jira tool call.

What's included

scenarios/scrum-master/
├─ README.md                    — Setup, quick-start (mock), full-setup (live), first-proof, troubleshooting, deploy-to-Azure
├─ docs/
│  ├─ design.md                 — 12 sections: principles, sequence diagrams, auth model, extension points
│  └─ sharepoint-schema.md      — Full column reference for every SMA_* list
├─ src/                         — Handlers, cards, services, cron, mock, utils
├─ scripts/
│  ├─ setup-sharepoint.ts       — Idempotent list provisioning via delegated Graph
│  ├─ seed-team.ts              — SMA_TeamMembers from team.sample.json
│  ├─ seed-helper-roster.ts     — SMA_HelperRoster
│  ├─ seed-jira-sample.ts       — Optional Jira story/sub-task/sprint seed
│  ├─ team.sample.json          — 4 placeholder personas (Alice/Bob/Charlie/Dana)
│  └─ sprint.sample.json        — 2 stories + 5 sub-tasks topology
├─ manifest/                    — Teams app + agentic-user templates (placeholder GUIDs)
├─ azure-functions/             — Sibling package: nightly + mid-sprint timers
├─ package.json                 — Scripts: dev, build, test-tool, setup:sharepoint, seed, seed:helpers, seed:jira
├─ tsconfig.json
├─ .env.template                — Every env var with WHY comments
└─ .gitignore

Runs offline

JIRA_MODE=mock (the default) drives the sample against a mutable in-memory Jira sprint in src/mock/jira-mock.ts. Contributors can clone, npm install, npm run dev, npm run test-tool and try /standup in the Agents Playground without an Atlassian account, without a SharePoint site, and without a Teams tenant.

Live mode (JIRA_MODE=live) requires:

  • Atlassian Cloud (free tier is fine)
  • SharePoint site the developer can write to (delegated Graph, no admin consent)
  • Azure OpenAI or OpenAI API key

Auth model — delegated only

All Microsoft Graph calls use MSAL device-code + delegated scopes (Sites.ReadWrite.All, Sites.Manage.All, Files.ReadWrite.All). Onboarding is a single interactive sign-in. No application-permission client credentials, no admin consent, no Teams policies to provision.

Verification

  • Set up on fresh M365 dev tenant + fresh Jira Cloud trial per README.
  • All 7 flows exercised end-to-end.
  • Secret scan clean — no tenant IDs, no client secrets, no PATs, no persona bleed. All placeholders are 00000000-…, <your-org>.atlassian.net, alice@contoso.com etc.
  • All source has MIT copyright headers.
  • TypeScript compiles clean; no lint errors.
  • CLA signed.

Placement — why scenarios/

Placed at the repo root under scenarios/scrum-master/ rather than under a language tier (e.g. nodejs/openai/) because a scrum-master workflow doesn't map cleanly to a single SDK — it composes Agent 365 SDK, OpenAI Agents SDK, MCP Calendar, and multiple external services. This mirrors how the Chief-of-Staff sample (#333) is placed. Happy to relocate to nodejs/openai/scenarios/scrum-master/ if reviewers prefer a language-tier scoping.

Not included / future work

  • Multi-team support. Sample is single-team by design; the roadmap for SMA_Teams list + TeamId column + per-team Jira credentials in Key Vault is documented in docs/design.md.
  • Production Key Vault-backed MSAL cache. Local dev uses a file cache (.mstoken-cache.json, gitignored). Production swap-in is called out in docs/design.md.
  • E2E test suite under tests/e2e/. Recommend follow-up PR once initial review lands.

Commits

  1. d33aecc — initial import of the scenario code (57 files)
  2. feb98d9 — Jira sample-data seed script + SharePoint schema doc
  3. 1c5a4af — README rewrite (intro-features-prereqs-setup structure)
  4. 62b0cad — observability + docs polish (logger, http-logger, startup banner, process safety nets, design.md, TOC, troubleshooting, deploy-to-Azure)

Related

Scenario extension on top of the OpenAI + Agent 365 base sample: an
autonomous Scrum Master that runs standups, reconciles the Jira board,
chases blockers via MCP Calendar, warns on sprint risk, answers grounded
Q&A, and posts a management-ready sprint close report.

Runs out of the box with JIRA_MODE=mock (no Atlassian account required).
Live mode uses Jira Cloud REST + delegated Microsoft Graph
(Sites.ReadWrite.All + Sites.Manage.All) via device-code flow.

* nodejs / Microsoft Agent 365 SDK + OpenAI Agents SDK + MCP tools
* Adaptive Card DMs, per-user rolling context, deterministic classifier
* Sibling azure-functions/ package hosts the nightly/weekly timers
Enables reviewers to try live mode (JIRA_MODE=live) without hand-crafting
issues:

* src/scripts/seed-jira-sample.ts — creates 2 stories + 5 sub-tasks and a
  future sprint on a pre-existing Jira Scrum project. Idempotent by
  summary. Reuses accountIds from team.sample.json so a single edit
  propagates through the whole seed.
* src/scripts/sprint.sample.json — topology (edit to customise).
* docs/sharepoint-schema.md — human-readable reference for every
  SMA_* list column, matching LIST_SCHEMAS in services/sharepoint.ts.
* package.json — new npm script: seed:jira
Consolidates a single top-level H1 (was two) and reorders content for a
first-time reader: intro paragraph -> features (7 MVPs) -> architecture
diagram -> prerequisites -> quick start in mock mode -> full setup in
live mode -> capability try-it table -> reference sections.

Adds:
* Mermaid architecture diagram
* Quick start (mock mode, no external deps) as the fast path
* Full configuration reference table with defaults and required-for
* Try-each-capability table with trigger + expected + handler link
* Link to docs/sharepoint-schema.md

Drops:
* Duplicated base-sample content (identity, install, typing, multi-message)
  now referenced via a link at the top
* Redundant POC-flavoured framing
Additive-only improvements ported from patterns in the Chief-of-Staff sample
(PR microsoft#333). Zero behaviour change to existing
handlers.

* src/util/logger.ts (new) — level-based logger with LOG_LEVEL, timestamped
  scope tags, and automatic secret redaction. Available for future use;
  existing console.log calls left in place.
* src/util/httpLogger.ts (new) — global axios interceptor for outbound
  Jira / Graph / MCP tracing. Off by default, gated behind LOG_HTTP=true.
* src/startup-check.ts (new) — one-shot boot banner printing every relevant
  env var with [MISSING] markers so misconfig surfaces before the first
  handler runs.
* src/index.ts — wire installHttpLogging + printStartupBanner immediately
  after configDotenv(); add unhandledRejection and uncaughtException
  handlers so a stray connector 502 no longer tears down the scheduler.
* docs/design.md — replace stub with full design doc: 12 numbered sections
  including per-flow mermaid sequence diagrams, determinism boundary,
  concurrency guarantees, and extension points.
* README.md — trim architecture / reconcile rules / warn thresholds /
  calendar path (moved to design.md); add TOC, First live proof smoke
  test, Troubleshooting matrix, and Deploy to Azure sections.
* .env.template — document LOG_LEVEL and LOG_HTTP toggles.
Copilot AI review requested due to automatic review settings July 24, 2026 09:04
@keshavk-msft
keshav Keshari (keshavk-msft) requested a review from a team as a code owner July 24, 2026 09:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Scrum Master autonomous scenario under scenarios/scrum-master/, built on the Microsoft Agent 365 SDK + OpenAI Agents SDK, with integrations for Jira (live + mock), SharePoint Lists state, proactive Teams messaging via Adaptive Cards, and optional Azure Functions timers to drive scheduled ceremonies.

Changes:

  • Introduces the Scrum Master scenario agent (message routing, slash commands, standup/reconcile/chase/warn/sprint-summary flows) plus supporting services (Jira, SharePoint, Graph delegated auth, MCP Calendar).
  • Adds provisioning + seed scripts for SharePoint lists, helper roster, and optional Jira sample data.
  • Adds scenario packaging/config (ToolingManifest, env template, manifests, Azure Functions sibling project, build tooling).

Reviewed changes

Copilot reviewed 59 out of 63 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
scenarios/scrum-master/tsconfig.json TypeScript compiler configuration for the scenario package.
scenarios/scrum-master/ToolingManifest.json Declares MCP servers (Mail/Calendar) used by the scenario.
scenarios/scrum-master/src/util/logger.ts Scenario-level log helper with redaction/truncation.
scenarios/scrum-master/src/util/httpLogger.ts Optional global axios request/response tracing.
scenarios/scrum-master/src/token-cache.ts In-memory token cache + token resolver for observability export.
scenarios/scrum-master/src/startup-check.ts Startup banner summarizing key env configuration.
scenarios/scrum-master/src/services/team-roster.ts SharePoint-backed team roster with cached ConversationReferences.
scenarios/scrum-master/src/services/sharepoint.ts Graph CRUD wrapper for SharePoint lists + report upload helper.
scenarios/scrum-master/src/services/session-store.ts In-memory standup session tracking for fan-in/cutoffs.
scenarios/scrum-master/src/services/proactive.ts Proactive messaging helper (continueConversation wrapper).
scenarios/scrum-master/src/services/jira.ts Jira client abstraction (live REST + mock implementation).
scenarios/scrum-master/src/services/jira-tool.ts OpenAI Agents function-tools for grounded Jira Q&A.
scenarios/scrum-master/src/services/issue-labels.ts Helpers to map Jira keys to “Task-N” labels and clean titles.
scenarios/scrum-master/src/services/helperMatcher.ts Keyword-based helper matching for blocker chase flow.
scenarios/scrum-master/src/services/graph.ts Delegated Graph auth via MSAL device-code + file token cache.
scenarios/scrum-master/src/services/calendar.ts Calendar ops via MCP CalendarTools using a scenario-specific agent.
scenarios/scrum-master/src/scripts/team.sample.json Sample team roster seed data (placeholder identities).
scenarios/scrum-master/src/scripts/sprint.sample.json Sample sprint topology for optional Jira seeding.
scenarios/scrum-master/src/scripts/setup-sharepoint.ts Idempotent SharePoint list + library provisioning script.
scenarios/scrum-master/src/scripts/seed-team.ts Seeds TeamMembers list (optionally patches current user AAD id).
scenarios/scrum-master/src/scripts/seed-jira-sample.ts Optional live Jira sample data seeding (stories/subtasks/sprint).
scenarios/scrum-master/src/scripts/seed-helper-roster.ts Seeds HelperRoster list rows used by blocker chase.
scenarios/scrum-master/src/openai-config.ts OpenAI/Azure OpenAI client configuration wiring for agents SDK.
scenarios/scrum-master/src/mock/jira-mock.ts In-memory mutable mock sprint for offline demo mode.
scenarios/scrum-master/src/index.ts Express host + internal endpoints + scheduler kickoff.
scenarios/scrum-master/src/handlers/warn.ts Nightly sprint risk detection + SM DM.
scenarios/scrum-master/src/handlers/sprint-summary.ts T-2 mid-sprint markdown summary report builder + delivery.
scenarios/scrum-master/src/handlers/reconcile.ts Deterministic status classifier + safe Jira transitions + SM approval card.
scenarios/scrum-master/src/handlers/config.ts /config channel capture of channel ConversationReference.
scenarios/scrum-master/src/handlers/commands.ts Slash-command router for scenario commands.
scenarios/scrum-master/src/handlers/answer.ts Grounded Jira Q&A agent with per-user rolling history.
scenarios/scrum-master/src/cron/local-scheduler.ts In-process node-cron scheduler for dev mode.
scenarios/scrum-master/src/config.ts Central env-var parsing for scenario configuration.
scenarios/scrum-master/src/client.ts OpenAI Agents + MCP tool registration + observability scope wrapper.
scenarios/scrum-master/src/cards/transition-confirm.card.ts Adaptive Card for SM approval of non-safe Jira transitions.
scenarios/scrum-master/src/cards/standup-summary.card.ts Adaptive Card for consolidated standup summary posting.
scenarios/scrum-master/src/cards/standup-request.card.ts Adaptive Card DM for standup collection + “submitted” confirmation.
scenarios/scrum-master/src/cards/meeting-propose.card.ts Adaptive Card for SM meeting-slot selection and booking.
scenarios/scrum-master/src/cards/blocker-escalation.card.ts Adaptive Card DM escalating a blocker to the SM.
scenarios/scrum-master/src/agent.ts AgentApplication wiring: message routing, card submits, notifications, token preload.
scenarios/scrum-master/package.json Scenario package metadata, dependencies, and scripts.
scenarios/scrum-master/manifest/manifest.json Teams manifest template for the scenario.
scenarios/scrum-master/manifest/agenticUserTemplateManifest.json Agentic user template manifest for the scenario.
scenarios/scrum-master/docs/sharepoint-schema.md Human-readable SharePoint list schema reference.
scenarios/scrum-master/azure-functions/tsconfig.json TS config for the Azure Functions sibling project.
scenarios/scrum-master/azure-functions/src/index.ts Azure Functions timers that POST to agent internal endpoints.
scenarios/scrum-master/azure-functions/README.md Azure Functions setup/run/deploy documentation.
scenarios/scrum-master/azure-functions/package.json Azure Functions package metadata and deps.
scenarios/scrum-master/azure-functions/local.settings.sample.json Local settings template for Functions (callback URL + token).
scenarios/scrum-master/azure-functions/host.json Azure Functions host configuration.
scenarios/scrum-master/azure-functions/.gitignore Ignores dist, node_modules, and local.settings.json secrets.
scenarios/scrum-master/.gitignore Ignores build output, env files, MSAL cache, and logs.
scenarios/scrum-master/.env.template Comprehensive env template covering OpenAI, Agent 365, Jira, SharePoint, scheduling.
Comments suppressed due to low confidence (2)

scenarios/scrum-master/src/util/httpLogger.ts:50

  • The response log line includes res.config.url verbatim, which may contain query strings. Strip ?… / #… before logging.
    scenarios/scrum-master/src/util/httpLogger.ts:60
  • The error log line includes cfg?.url verbatim, which may contain query strings. Strip ?… / #… before logging.

Comment thread scenarios/scrum-master/src/config.ts
Comment thread scenarios/scrum-master/src/index.ts Outdated
Comment thread scenarios/scrum-master/src/index.ts
Comment thread scenarios/scrum-master/src/token-cache.ts Outdated
Comment thread scenarios/scrum-master/src/client.ts
Comment thread scenarios/scrum-master/src/client.ts
Comment thread scenarios/scrum-master/src/client.ts
Comment thread scenarios/scrum-master/src/util/httpLogger.ts
Applies 7 of the 8 review comments; the 8th (client.ts:140 MCP transport
close-in-finally) is deferred because it changes runtime behaviour on a
codepath inherited from the base sample.

* config.ts — default JIRA_MODE to 'mock' (was 'live') so a fresh clone
  runs offline in mock mode as the README documents, instead of throwing
  on missing Jira credentials.
* index.ts — remove duplicate unhandledRejection/uncaughtException
  handlers (kept the more-detailed pair at top of file).
* index.ts — emit a one-shot boot warning when INTERNAL_TRIGGER_TOKEN is
  empty so production deployments notice open /api/internal/* endpoints.
* token-cache.ts — replace 'All rights reserved' header with the
  repo-standard MIT license header.
* client.ts — rename observability service from 'TypeScript Claude
  Sample Agent' to 'Scrum Master Sample Agent'.
* client.ts — fix fallback agentId/agentName (was
  'typescript-compliance-agent' / 'TypeScript Compliance Agent').
* util/httpLogger.ts — add safePath() helper and strip query strings +
  URL fragments from request/response/error log lines so credentials in
  ?token=… never leak.
* AGENT-CODE-WALKTHROUGH.md — sync doc snippet to new agentId strings.
Copilot AI review requested due to automatic review settings July 24, 2026 09:17
@keshavk-msft

Copy link
Copy Markdown
Author

Thanks copilot-pull-request-reviewer for the thorough pass. Pushed ca578fa addressing 7 of the 8 comments:

# File Fix
1 src/config.ts:36 getJiraMode() now defaults to mock so a fresh clone with no .env runs offline (matches README).
2 src/index.ts:170 Removed the duplicate unhandledRejection/uncaughtException handlers (kept the more-detailed pair at the top of the file).
3 src/index.ts:79 One-shot boot warning when INTERNAL_TRIGGER_TOKEN is empty so open /api/internal/* endpoints don't slip into production silently.
4 src/token-cache.ts:1 Replaced "All rights reserved" header with the standard MIT header.
5 src/client.ts:45 Observability service name → Scrum Master Sample Agent.
6 src/client.ts:158 Fallback agentId / agentNamescrum-master-sample-agent / Scrum Master Sample Agent.
8 src/util/httpLogger.ts Added safePath() helper and stripped ?… / #… from request, response, and error log lines. Credentials in ?token=… no longer leak into logs.

Deferred: comment #7 (src/client.ts:140server.close() in finally)

Valid observation and the same behaviour we already worked around in services/calendar.ts (removed the close() there and added a session-lost retry). Keeping client.ts unchanged in this PR because:

  1. invokeAgent() in client.ts is an inherited codepath from the base OpenAI sample-agent and isn't on any hot path in the Scrum Master flows — our Q&A goes through handlers/answer.ts, which builds its own Agent per turn. So the flaky-tool-reuse risk is theoretical for this scenario.
  2. Aligning client.ts with the calendar.ts pattern touches MCP transport lifecycle, which is a runtime-behaviour change I'd rather land as its own PR (with a smoke test) than fold into this initial contribution.

Happy to open a follow-up if reviewers prefer.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 59 out of 63 changed files in this pull request and generated 8 comments.

Comments suppressed due to low confidence (4)

scenarios/scrum-master/src/services/sharepoint.ts:127

  • findByField() has the same issue as findByTitle(): URL-encoding the value inside the OData literal can break matches (e.g., spaces become %20). Encode the entire $filter expression and keep escapeODataString limited to OData single-quote escaping.
    scenarios/scrum-master/src/client.ts:139
  • invokeAgent() always closes MCP servers in finally. In this same scenario, calendar.ts documents that closing Streamable-HTTP MCP transports can break subsequent calls with "Session not found" because McpToolRegistrationService can reuse an already-closed transport. Consider not closing MCP servers here (or only closing on process shutdown) to avoid intermittent MCP failures across turns.
      await this.closeServers();

scenarios/scrum-master/azure-functions/README.md:24

  • This second link to ../openai/sample-agent is also incorrect for this scenario (the agent is ..). Updating it avoids sending readers to a non-existent path.
- The agent already running somewhere reachable — either a dev tunnel URL, App Service, or Container Apps endpoint that exposes `/api/internal/*` from [`../openai/sample-agent`](../openai/sample-agent).

scenarios/scrum-master/src/index.ts:94

  • When INTERNAL_TRIGGER_TOKEN is empty, /api/internal/* endpoints become unauthenticated even if the server is bound to 0.0.0.0 (non-development). Logging a warning is easy to miss in deployment logs; it's safer to reject requests unless you're explicitly in development mode.

Comment on lines +107 to +111
const graph = getGraphClient();
const res = await graph
.api(`/sites/${siteId}/lists/${listName(key)}/items?$expand=fields&$filter=fields/Title eq '${escapeODataString(title)}'`)
.header('Prefer', 'HonorNonIndexedQueriesWarningMayFailRandomly')
.get();
// inside our filter value as URL syntax. Our IDs use `#` as a separator (e.g.
// `<sprintId>#<yyyy-mm-dd>`), which without encoding gets treated as a URL fragment
// and truncates the filter — silently returning zero rows.
return encodeURIComponent(s.replace(/'/g, "''"));
Comment on lines +119 to +124
export function getWarnConfig(): WarnConfig {
return {
todoPct: Number(optional('WARN_TODO_PCT', '0.40')),
sprintProgressPct: Number(optional('WARN_SPRINT_PROGRESS_PCT', '0.50')),
};
}
scopes: getObservabilityAuthenticationScope()
});

console.log(`Preloaded Observability token for agentId=${agentId}, tenantId=${tenantId} token=${aauToken?.token?.substring(0, 10)}...`);
Comment on lines +236 to +238
const response = await client.invokeAgentWithScope(
`You have received the following email. Please follow any instructions in it. ${emailContent}`
);
},
"devDependencies": {
"@microsoft/m365agentsplayground": "^0.2.18",
"@types/express": "^4.17.21",
@@ -0,0 +1,121 @@
# Scrum Master Assistant — Azure Functions

Two timer-triggered functions that drive the SMA scheduled ceremonies. The agent process (in [`../openai/sample-agent`](../openai/sample-agent)) owns all state, tokens, and Adaptive Card logic — these functions only *nudge* it via HTTP.
Comment on lines +28 to +31
/**
* Simple custom in-memory token cache with expiration handling
* In production, use a more robust caching solution like Redis
*/
Copilot AI review requested due to automatic review settings July 27, 2026 06:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 59 out of 63 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (2)

scenarios/scrum-master/src/agent.ts:238

  • The prompt Please follow any instructions in it makes the agent treat email content as executable instructions, which is a classic prompt-injection vector. Even with general safety guidance elsewhere, it’s better to frame email content as untrusted data and ask for summarization + a drafted response.
      // Then process the email
      const response = await client.invokeAgentWithScope(
        `You have received the following email. Please follow any instructions in it. ${emailContent}`
      );

scenarios/scrum-master/src/client.ts:199

  • closeServers() closes every MCP server after each agent invocation. This conflicts with the calendar MCP guidance in services/calendar.ts / README troubleshooting: streamable HTTP MCP sessions are intended to be long-lived, and closing can lead to -32001 Session not found on subsequent turns when the registration service reuses a closed transport.
  private async closeServers(): Promise<void> {
    if (this.agent.mcpServers && this.agent.mcpServers.length > 0) {
      for (const server of this.agent.mcpServers) {
        await server.close();
      }
    }
  }

Comment on lines +78 to +100
const internalToken = getInternalTriggerToken();
if (!internalToken) {
// Loud one-shot warning so production deployments don't accidentally ship
// open internal endpoints. Dev convention still allows an empty token so a
// solo developer can curl the endpoints without setting a secret.
console.warn(
'[security] INTERNAL_TRIGGER_TOKEN is empty — /api/internal/* is UNAUTHENTICATED. ' +
'Set INTERNAL_TRIGGER_TOKEN in .env before deploying anywhere non-local.',
);
}
function requireInternalToken(req: express.Request, res: Response): boolean {
const provided = req.get('x-internal-token') ?? '';
if (!internalToken) {
// Convention: empty token in .env => endpoint is open (dev-only). The one-shot
// warning above logs this condition; each request stays quiet.
return true;
}
if (provided !== internalToken) {
res.status(401).json({ error: 'invalid internal token' });
return false;
}
return true;
}
Comment on lines +25 to +33
const adapter = agentApplication.adapter as CloudAdapter;
const botAppId = getBotAppId();
try {
// Cast: stored references always come from `activity.getConversationReference()`
// which returns a fully-populated ConversationReference, but our storage layer
// deserializes as `Partial<>`. Adapter throws at runtime on truly-incomplete refs.
await adapter.continueConversation(botAppId, reference as ConversationReference, async (context) => {
await logic(context);
});
type: 'Input.Toggle',
id: `approve_${t.issueKey}`,
title: 'Approve this change',
value: 'true',
Copilot AI review requested due to automatic review settings July 27, 2026 06:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 59 out of 63 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (6)

scenarios/scrum-master/src/agent.ts:191

  • Logging even a token prefix can leak credentials via console logs (often shipped to centralized log stores). Avoid printing any part of the observability token; log only metadata like agentId/tenantId.
      console.log(`Preloaded Observability token for agentId=${agentId}, tenantId=${tenantId} token=${aauToken?.token?.substring(0, 10)}...`);

scenarios/scrum-master/src/agent.ts:238

  • This prompt tells the model to "follow any instructions" inside an email, which is a direct prompt-injection vector (email content is untrusted). Ask the model to summarize/extract action items instead, without executing instructions from the email body.
      const response = await client.invokeAgentWithScope(
        `You have received the following email. Please follow any instructions in it. ${emailContent}`
      );

scenarios/scrum-master/src/client.ts:140

  • This closes MCP server sessions after every run(). In this same scenario (see calendar.ts), closing Streamable-HTTP MCP transports can break subsequent tool calls with "Session not found" because McpToolRegistrationService may reuse the underlying transport across agents. Consider keeping MCP sessions long-lived here as well.
    } finally {
      await this.closeServers();
    }

scenarios/scrum-master/src/index.ts:94

  • If INTERNAL_TRIGGER_TOKEN is empty, /api/internal/* becomes unauthenticated for all environments (including production misconfig). It would be safer to only allow the "open" behavior in development and fail closed otherwise.
    scenarios/scrum-master/azure-functions/README.md:3
  • This README links to ../openai/sample-agent, but the agent process for this scenario is the parent scenarios/scrum-master package (and its README). The current link is broken/misleading in this repo layout.
Two timer-triggered functions that drive the SMA scheduled ceremonies. The agent process (in [`../openai/sample-agent`](../openai/sample-agent)) owns all state, tokens, and Adaptive Card logic — these functions only *nudge* it via HTTP.

scenarios/scrum-master/azure-functions/README.md:24

  • Same as above: this link points at ../openai/sample-agent, but the internal endpoints live in the parent scenario package. Update the reference so readers can find the right code/docs.
- The agent already running somewhere reachable — either a dev tunnel URL, App Service, or Container Apps endpoint that exposes `/api/internal/*` from [`../openai/sample-agent`](../openai/sample-agent).

Comment on lines +169 to +170
scope.recordInputTokens(45);
scope.recordOutputTokens(78);
Comment on lines +53 to +55
console.log('[OpenAI Config] Using Azure OpenAI');
console.log(`[OpenAI Config] Endpoint: ${process.env.AZURE_OPENAI_ENDPOINT}`);
console.log(`[OpenAI Config] Deployment: ${process.env.AZURE_OPENAI_DEPLOYMENT}`);
The MCP calendar tool loop occasionally succeeds at creating the event but
the LLM returns a garbled response (e.g. webLink="onlineMeetingUrl" as a
literal string, or id="errorEvent verification failed..." from a mis-formed
rule-microsoft#6 error path). The existing !out.webLink check misses this because
the values are truthy strings that pass Zod validation.

* services/calendar.ts — after the existing empty-webLink guard, check that
  webLink is actually a URL (^https?://) and that id doesn't start with
  "error". On mismatch, throw a distinct CALENDAR_VERIFY_FAILED: error
  with an actionable "verify in Outlook" message.
* handlers/chase.ts — catch the CALENDAR_VERIFY_FAILED: prefix separately;
  mark the blocker as booked (event was almost certainly created) and DM
  the SM a warning that asks them to verify in Outlook, instead of the
  scary "couldn't book the meeting" error.

Tested in the POC with real MCP calendar output that reproduced the shape
described.
Copilot AI review requested due to automatic review settings July 29, 2026 08:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 59 out of 63 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (7)

scenarios/scrum-master/src/agent.ts:191

  • This log line prints a prefix of the exchanged observability token. Even partial token material can be used for correlation/attacks and shouldn’t be emitted to logs.
      console.log(`Preloaded Observability token for agentId=${agentId}, tenantId=${tenantId} token=${aauToken?.token?.substring(0, 10)}...`);

scenarios/scrum-master/src/agent.ts:238

  • The email-notification flow explicitly instructs the model to “follow any instructions” found in the email content. That creates a prompt-injection path where untrusted email content can drive unintended tool use or actions. The agent should treat email as untrusted data: summarize it, extract requests, and ask for user confirmation before acting.
      // Then process the email
      const response = await client.invokeAgentWithScope(
        `You have received the following email. Please follow any instructions in it. ${emailContent}`
      );

scenarios/scrum-master/src/token-cache.ts:37

  • The comments describe “expiration handling” and “store a token with expiration”, but the implementation is a plain Map with no TTL/expiry logic. This is misleading for readers and for anyone reusing the sample code.
    scenarios/scrum-master/src/client.ts:199
  • This closes all MCP server transports after every agent invocation. In this scenario, calendar.ts documents that closing a Streamable-HTTP MCP transport can break subsequent tool calls with “Session not found” when McpToolRegistrationService reuses the closed transport across turns. To avoid intermittent failures, consider not closing MCP servers here (or ensure each agent gets a non-shared transport instance).
  private async closeServers(): Promise<void> {
    if (this.agent.mcpServers && this.agent.mcpServers.length > 0) {
      for (const server of this.agent.mcpServers) {
        await server.close();
      }
    }
  }

scenarios/scrum-master/src/handlers/report.ts:268

  • The action-items table hardcodes the target sprint as "Sprint 2". That will be incorrect for most real sprint names; use a neutral label like "Next sprint" (or derive it from sprint metadata if available).
    scenarios/scrum-master/src/handlers/chase.ts:17
  • The header comment describes the blocker-chase flow as using direct Graph calls (/me/findMeetingTimes, POST /events). The implementation now drives mcp_CalendarTools via services/calendar.ts, so this comment is outdated/misleading.
    scenarios/scrum-master/src/handlers/report.ts:260
  • The action-items table hardcodes the target sprint as "Sprint 2". That will be incorrect for most real sprint names (and even for mock data) and can confuse readers; consider using a neutral label like "Next sprint".

This issue also appears on line 268 of the same file.

Comment on lines +78 to +100
const internalToken = getInternalTriggerToken();
if (!internalToken) {
// Loud one-shot warning so production deployments don't accidentally ship
// open internal endpoints. Dev convention still allows an empty token so a
// solo developer can curl the endpoints without setting a secret.
console.warn(
'[security] INTERNAL_TRIGGER_TOKEN is empty — /api/internal/* is UNAUTHENTICATED. ' +
'Set INTERNAL_TRIGGER_TOKEN in .env before deploying anywhere non-local.',
);
}
function requireInternalToken(req: express.Request, res: Response): boolean {
const provided = req.get('x-internal-token') ?? '';
if (!internalToken) {
// Convention: empty token in .env => endpoint is open (dev-only). The one-shot
// warning above logs this condition; each request stays quiet.
return true;
}
if (provided !== internalToken) {
res.status(401).json({ error: 'invalid internal token' });
return false;
}
return true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants