Commit b68101b
feat(microsoft-teams): new MCP — channel & chat messaging, meetings, triggers (#444)
* feat(microsoft-teams): new MCP integrating with Microsoft Teams
Adds a Microsoft Teams MCP that lets deco agents interact with Teams
via the Microsoft Graph API. Authentication uses the deco runtime's
native OAuth integration (Authorization Code flow with delegated
permissions), so users sign in via the "Connect to Microsoft" button
in deco Studio.
Tools:
- Channel: TEAMS_LIST_TEAMS, TEAMS_LIST_CHANNELS, TEAMS_SEND_MESSAGE
(with optional subject), TEAMS_REPLY_TO_MESSAGE.
- Chat (1-on-1 and group): TEAMS_LIST_CHATS (enriches oneOnOne chats
with the other member's name), TEAMS_GET_CHAT_MEMBERS,
TEAMS_LIST_CHAT_MESSAGES, TEAMS_SEND_CHAT_MESSAGE, TEAMS_FIND_USER,
TEAMS_CREATE_PRIVATE_CHAT, TEAMS_CREATE_GROUP_CHAT.
Trigger:
- teams.message.received fired when a new message lands in a
subscribed channel via Microsoft Graph change notifications.
- Webhook routes at /teams/notifications/:connectionId handle the
Graph validation handshake and notification dispatch, with
auto-renewal of subscriptions near expiry.
Infra:
- KV store (file-backed JSON) for trigger storage and webhook-time
credential lookup.
- Standalone test scripts in scripts/ for CLI testing.
Adds 'microsoft-teams' to the root workspaces list.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(microsoft-teams): chat tools, channel reads, subscriptions, diagnostics + hardening
Expands the Microsoft Teams MCP and cleans up the codebase.
New capabilities:
- Channel reads: LIST_CHANNEL_MESSAGES (optional include_replies via
$expand), LIST_MESSAGE_REPLIES; message mutations EDIT_CHANNEL_MESSAGE
and REACT_TO_CHANNEL_MESSAGE.
- Private chat: LIST_CHATS (resolves the other member's name for 1-on-1),
GET_CHAT_MEMBERS, SEND_CHAT_MESSAGE (with quote-reply via messageReference),
LIST_CHAT_MESSAGES, FIND_USER, CREATE_PRIVATE_CHAT, CREATE_GROUP_CHAT,
EDIT_CHAT_MESSAGE, REACT_TO_CHAT_MESSAGE.
- Webhook subscription lifecycle (agent-driven): SUBSCRIBE_TO_CHANNEL,
LIST_SUBSCRIPTIONS, REFRESH_SUBSCRIPTIONS, UNSUBSCRIBE_FROM_CHANNEL.
- Diagnostics: GET_RECENT_EVENTS, CLEAR_RECENT_EVENTS for inspecting the
trigger pipeline end-to-end.
Hardening:
- Structured logger with per-request trace_id and measure() timings.
- Event deduplication for redelivered Graph notifications.
- Centralized error formatting (errors.ts) with machine codes and
actionable hints surfaced on every tool response.
- Reactions send the Unicode emoji Graph expects; chat web_url is built
client-side when Graph omits it.
Cleanup:
- Switch to the deco runtime's native OAuth (PKCE) — removed the custom
/auth routes, config-cache, and standalone debug scripts.
- Bump @decocms/runtime to ^1.6.0 to match the OAuth config contract.
- Remove dead helpers (buildAuthorizeUrl, getUserProfile, delete-message
tools) and run oxfmt across the package.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(microsoft-teams): meeting management, user search, richer descriptions
Adds calendar/meeting tools and improves user lookup.
Meetings (Graph Calendar API, requires Calendars.ReadWrite delegated scope):
- CREATE_MEETING (with Teams join link), LIST_MEETINGS, GET_MEETING,
UPDATE_MEETING, RESCHEDULE_MEETING, DELETE_MEETING, CANCEL_MEETING,
and invitation responses ACCEPT_MEETING, DECLINE_MEETING,
TENTATIVELY_ACCEPT_MEETING.
- Meeting output now includes the full `description` (HTML body stripped
to plain text) alongside Graph's truncated `preview`.
Users:
- New SEARCH_USERS_BY_NAME (Graph $search) to resolve a person by name when
the email is unknown.
- Renamed FIND_USER → GET_USER_BY_EMAIL for clarity (exact-email lookup vs
name search).
Docs:
- Update app.json to reflect delegated OAuth, the current toolset, and the
Microsoft 365 work/school account requirement.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(microsoft-teams): make MCP Cloudflare Workers-ready
Migrates the Teams MCP to run on Cloudflare Workers, using the github and
google-gmail MCPs as the reference pattern.
Runtime / entry:
- Replace the Bun serve() server with an `export default { fetch }` Worker
entrypoint; the runtime is built lazily as a singleton.
- Webhook routes are handled in the fetch handler before falling through to
runtime.fetch(), with env.TEAMS_KV threaded into the KV store per request.
Storage (Workers isolates are ephemeral):
- Rewrite lib/kv.ts as a Cloudflare KV adapter (same get/set/delete/keys
interface, TTL via expirationTtl); drop the Bun.file disk store.
- Move dedup to KV (async) instead of an in-memory Map.
Trigger delivery correctness on Workers:
- Stop using the runtime's fire-and-forget triggers.notify() in the webhook
path — it delivers via a floating promise the isolate cancels after the
response. Add an awaitable deliverToMesh() (reads trigger credentials from
KV and POSTs to the callback) and register the background work with
ctx.waitUntil() so it completes. Mirrors the github webhook pattern.
Config / tooling:
- Add wrangler.toml (nodejs_compat, TEAMS_KV namespace, custom domain).
- Switch package.json scripts to wrangler dev/deploy; add wrangler and
@cloudflare/workers-types; tsconfig picks up workers-types.
- Add a dedicated deploy-microsoft-teams.yml workflow (same shape as
deploy-github.yml) — wrangler deploy on push to microsoft-teams/**.
- env.ts gains the TEAMS_KV binding and MICROSOFT_* secret types.
- Point app.json connection URL and webhook defaults at the
microsoft-teams-mcp.decocms.com Worker domain; fix the broken icon URL.
- Add README.md; ignore .wrangler/ and .dev.vars.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(microsoft-teams): address review findings
- security: generate webhook clientState with crypto.randomUUID() instead
of Math.random() (it is the only verification for unauthenticated Graph
notifications).
- graph-client: handle empty-body success responses (202/204) — action
endpoints like cancel/accept/decline/setReaction return 202 with no body,
which previously threw on response.json() and reported false failures.
- graph-client: listJoinedTeams now calls /me/joinedTeams (the user's teams)
instead of /teams (org-wide, needs admin/app permissions).
- meetings: reject partial proposed_start/proposed_end in decline and
tentative responses with a clear validation error instead of silently
dropping the proposed time; extract a shared createRespondTool factory for
accept/decline/tentative to remove duplication.
- main: do not coerce a missing refresh_token to "" on code exchange — leave
it undefined so an invalid empty token isn't persisted and future refreshes
aren't broken.
- logger: serialize log lines defensively (safeStringify) so circular refs,
throwing toJSON, or bigint never crash a handler or mask the original error.
- chore: drop the orphaned `publish` script (deco-cli was removed in the
Workers migration; registry publish is handled by CI).
- wrangler.toml: set the real TEAMS_KV namespace id.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>1 parent 616c2cf commit b68101b
30 files changed
Lines changed: 4984 additions & 0 deletions
File tree
- .github/workflows
- microsoft-teams
- server
- lib
- tools
- types
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
0 commit comments