mcpbeat Sign in

Agents SDK Typescript Agent Skill

> Use when any code imports @microsoft/agents-hosting, @microsoft/agents-hosting-express, or related Agents SDK packages, or when the user is building, configuring, or asking questions about a Microsoft 365 Agents SDK agent in TypeScript. Trigger on questions about environment variables, connection configuration, AgentApplication patterns, OAuth sign-in flows, storage backends, cards, streaming, or local testing with the Agents Playground — even if no code exists yet and the user is planning or asking how to get started.

5k tokens
context cost
the whole folder, loaded on every use
3
files
instructions only
0
copies elsewhere
how many repositories repackaged it
1022
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/microsoft/Agents --skill agents-sdk-typescript

What comes with it

2 182 bytes besides the instruction
references/cards.md
references/streaming.md

The instruction itself

26 sections, as written by the author

Overview

The Microsoft 365 Agents SDK builds multichannel agents for Teams, Copilot Studio, and web chat.

| Package | Purpose |

|---|---|

| @microsoft/agents-hosting | Core: AgentApplication, CloudAdapter, TurnContext, TurnState, storage |

| @microsoft/agents-hosting-express | startServer() convenience wrapper |

| @microsoft/agents-hosting-storage-blob | Azure Blob Storage backend |

| @microsoft/agents-hosting-storage-cosmos | CosmosDB backend |

| @microsoft/agents-hosting-dialogs | Dialog system |

Requires Node 18+. Use node --env-file .env (Node 20+) to load environment variables.

Azure Resources Required

Microsoft Entra App Registration

  • clientId — Application (client) ID
  • clientSecret — Certificates & secrets
  • tenantId — Directory (tenant) ID

Azure Bot Resource

  • Messaging endpoint: https://<your-host>/api/messages
  • Microsoft App ID must match clientId

Local dev: Use Bot Framework Emulator. No Azure Bot needed until deployment. Leave clientId blank to skip auth validation.

Environment Variables

Uses connections__ and connectionsMap__ prefixes. Double underscores (__) separate path segments; .settings. is stripped automatically.

Single connection (most common):

connections__serviceConnection__settings__clientId=<your-app-id>
connections__serviceConnection__settings__clientSecret=<your-secret>
connections__serviceConnection__settings__tenantId=<your-tenant-id>
connectionsMap__0__connection=serviceConnection
connectionsMap__0__serviceUrl=*

How connectionsMap works:

Each entry maps a serviceUrl pattern to a named connection. The first matching entry wins.

  • serviceUrl=* — matches any service URL (use as the default/fallback)
  • serviceUrl is treated as a regex for all other values

connectionsMap can be omitted when there is only one connection — the SDK defaults it to serviceUrl=*.

Multiple connections (e.g. different identities for different channels):

connections__mainConn__settings__clientId=<app-id-1>
connections__mainConn__settings__clientSecret=<secret-1>
connections__mainConn__settings__tenantId=<tenant-id>

connections__teamsConn__settings__clientId=<app-id-2>
connections__teamsConn__settings__clientSecret=<secret-2>
connections__teamsConn__settings__tenantId=<tenant-id>

connectionsMap__0__connection=teamsConn
connectionsMap__0__serviceUrl=https://smba.trafficmanager.net/.*
connectionsMap__1__connection=mainConn
connectionsMap__1__serviceUrl=*

Optional audience field on a map entry restricts matching to activities whose JWT aud claim equals that value:

connectionsMap__0__connection=teamsConn
connectionsMap__0__serviceUrl=*
connectionsMap__0__audience=<teams-app-id>

Available connection settings fields:

clientId, clientSecret, tenantId, authority, certPemFile, certKeyFile, sendX5C, connectionName, scope

Legacy format — backwards compatibility only

> Never use the legacy format for new agents. It exists solely for backwards compatibility with older BotFramework-based bots. Always use the modern connections__ format above.

clientId=<your-app-id>
clientSecret=<your-secret>
tenantId=<your-tenant-id>

Both startServer() and loadAuthConfigFromEnv() auto-detect the format. Leave clientId blank locally to skip auth.

OAuth authorization handler variables

These control the user sign-in flow configured via authorization: { [id]: { ... } }. The id is the key used in the authorization options (e.g. graph).

Uses the prefix AgentApplication__UserAuthorization__Handlers__<id>__Settings__<property>. Case-insensitive.

AzureBot handler (default — user OAuth flow via Azure Bot Service):

AgentApplication__UserAuthorization__Handlers__graph__Settings__azureBotOAuthConnectionName=GraphOAuthConnection
AgentApplication__UserAuthorization__Handlers__graph__Settings__title=Sign in with Microsoft
AgentApplication__UserAuthorization__Handlers__graph__Settings__text=Please sign in to continue
AgentApplication__UserAuthorization__Handlers__graph__Settings__invalidSignInRetryMax=3
AgentApplication__UserAuthorization__Handlers__graph__Settings__enableSso=false

# OBO (on-behalf-of) — auto-exchange on routes using exchangeToken()
AgentApplication__UserAuthorization__Handlers__graph__Settings__oboConnectionName=OBOConnection
AgentApplication__UserAuthorization__Handlers__graph__Settings__oboScopes=https://graph.microsoft.com/.default,Mail.Read

# Custom error messages
AgentApplication__UserAuthorization__Handlers__graph__Settings__invalidSignInRetryMessage=That code was invalid, please try again.
AgentApplication__UserAuthorization__Handlers__graph__Settings__invalidSignInRetryMessageFormat=Please enter the 6-digit code from the sign-in card.
AgentApplication__UserAuthorization__Handlers__graph__Settings__invalidSignInRetryMaxExceededMessage=Too many failed attempts. Please try again later.

AgenticUserAuthorization handler (agent-to-agent, no user prompt):

AgentApplication__UserAuthorization__Handlers__myHandler__Settings__type=AgenticUserAuthorization
AgentApplication__UserAuthorization__Handlers__myHandler__Settings__scopes=https://graph.microsoft.com/.default
AgentApplication__UserAuthorization__Handlers__myHandler__Settings__altBlueprintConnectionName=altConn
Legacy format (deprecated)

> Do not use the legacy format for new agents. It exists solely for backwards compatibility. The SDK emits deprecation warnings when legacy variables are detected.

| Legacy variable | Modern equivalent |

|---|---|

| graph_connectionName | ...Handlers__graph__Settings__azureBotOAuthConnectionName |

| graph_connectionTitle | ...Handlers__graph__Settings__title |

| graph_connectionText | ...Handlers__graph__Settings__text |

| graph_maxAttempts | ...Handlers__graph__Settings__invalidSignInRetryMax |

| graph_enableSso | ...Handlers__graph__Settings__enableSso |

| graph_obo_connection | ...Handlers__graph__Settings__oboConnectionName |

| graph_obo_scopes | ...Handlers__graph__Settings__oboScopes |

| graph_messages_invalidCode | ...Handlers__graph__Settings__invalidSignInRetryMessage |

| graph_messages_invalidCodeFormat | ...Handlers__graph__Settings__invalidSignInRetryMessageFormat |

| graph_messages_maxAttemptsExceeded | ...Handlers__graph__Settings__invalidSignInRetryMaxExceededMessage |

| myHandler_type=agentic | ...Handlers__myHandler__Settings__type=AgenticUserAuthorization |

| myHandler_scopes | ...Handlers__myHandler__Settings__scopes |

| myHandler_altBlueprintConnectionName | ...Handlers__myHandler__Settings__altBlueprintConnectionName |

Priority when both formats are present: runtime options (code) > modern env vars > legacy env vars.

Quick Start

import { startServer } from '@microsoft/agents-hosting-express'
import { AgentApplication, MemoryStorage, TurnContext, TurnState } from '@microsoft/agents-hosting'

class MyAgent extends AgentApplication<TurnState> {
  constructor() {
    super({ storage: new MemoryStorage() })

    this.onConversationUpdate('membersAdded', async (ctx: TurnContext) => {
      await ctx.sendActivity('Hello! Send me a message.')
    })

    this.onActivity('message', async (ctx: TurnContext, state: TurnState) => {
      let counter: number = state.getValue('conversation.counter') || 0
      await ctx.sendActivity(`[${counter++}] You said: ${ctx.activity.text}`)
      state.setValue('conversation.counter', counter)
    })
  }
}

startServer(new MyAgent())

Run: node --env-file .env dist/index.js

Server Setup

Option A: startServer() (preferred)

startServer(agent) creates an Express app with:

  • express.json() + authorizeJWT() middleware
  • POST /api/messages route
  • Listens on PORT env var (default 3978)
  • Returns the Express instance — add extra routes to the return value
import { startServer } from '@microsoft/agents-hosting-express'

const server = startServer(agent)
server.get('/health', (_req, res) => res.json({ ok: true }))

Option B: Manual Express

import express, { Response } from 'express'
import { Request, CloudAdapter, authorizeJWT, loadAuthConfigFromEnv } from '@microsoft/agents-hosting'

const authConfig = loadAuthConfigFromEnv()
const adapter = new CloudAdapter(authConfig)
const app = express()

app.use(express.json())
app.use(authorizeJWT(authConfig))

app.post('/api/messages', async (req: Request, res: Response) => {
  await adapter.process(req, res, async (context) => await agent.run(context))
})

app.listen(process.env.PORT || 3978)

Proactive Messaging

Save a reference during a turn, then use adapter.continueConversation from any route. req.user comes from authorizeJWT middleware.

import { ConversationReference } from '@microsoft/agents-activity'

// During a turn — save the reference
const ref = ctx.activity.getConversationReference()
conversationReferences[ref.conversation.id] = ref

// In a proactive route
app.get('/api/notify', async (req, res) => {
  for (const ref of Object.values(conversationReferences)) {
    await adapter.continueConversation(req.user!, ref, async (ctx) => {
      await ctx.sendActivity('Proactive message')
    })
  }
  res.json({ ok: true })
})

Validating Your Configuration

1. Validate bot credentials (clientId / clientSecret / tenantId)

This tests that your Entra app registration credentials are correct and can authenticate with the Bot Framework. A successful response includes access_token; an error response includes error and error_description.

curl -s -X POST \
  "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" \
  -d "grant_type=client_credentials\
&client_id=$clientId\
&client_secret=$clientSecret\
&scope=https://api.botframework.com/.default" \
  | jq '{token_type, expires_in, error, error_description}'

Common errors:

  • AADSTS700016clientId not found in tenant (wrong ID or wrong tenant)
  • AADSTS7000215 — invalid clientSecret (expired or incorrect)
  • AADSTS90002tenantId not found

2. Validate the agent is running and reachable

curl -s -o /dev/null -w "%{http_code}" \
  -X POST http://localhost:3978/api/messages \
  -H "Content-Type: application/json" \
  -d '{}'
  • 401 — agent is running; JWT auth rejected the empty request (expected — means auth is working)
  • 000 or connection refused — agent is not running or wrong port
  • 200 — agent is running with auth disabled (local dev with blank clientId)

3. Validate an OAuth connection name

OAuth connection names (set via AgentApplication__UserAuthorization__Handlers__graph__Settings__azureBotOAuthConnectionName) can only be tested end-to-end through a real sign-in flow. Use the Azure portal:

Azure Portal → Your Bot Resource → Settings → OAuth Connection Settings → [your connection] → Test Connection

This confirms the connection name matches, the OAuth app has the right scopes, and the redirect URI is configured correctly.

Local Testing with Agents Playground

The Agents Playground lets you test your agent locally without deploying to Azure or configuring a Bot resource. It acts as a mock connector service and channel client.

Install:

npm install -g agentsplayground

Include these scripts when creating a new agent's package.json. The test script starts the agent and playground together in parallel — no separate terminals needed:

"scripts": {
  "prebuild": "npm ci",
  "build": "tsc --build",
  "prestart": "npm run build",
  "prestart:anon": "npm run build",
  "start:anon": "node ./dist/index.js",
  "start": "node --env-file .env ./dist/index.js",
  "test-tool": "agentsplayground -c emulator",
  "test": "npm-run-all -p -r start:anon test-tool"
}
  • npm start — builds and runs with .env credentials
  • npm test — builds, starts the agent without auth (start:anon), and launches the playground in parallel. Use this for quick local dev without needing an .env file.
  • -c emulator — uses the emulator channel (no auth required). Change to msteams, webchat, etc. as needed.
  • Requires npm-run-all as a dev dependency: npm install -D npm-run-all

With authentication (for testing OAuth/sign-in flows):

agentsplayground -c msteams \
  --client-id <your-app-id> \
  --client-secret <your-secret> \
  --tenant-id <your-tenant-id>

Channel options (-c): msteams, webchat, directline, emulator, agents

AgentApplication Patterns

Routing

this.onMessage('/cmd', handler)              // exact command
this.onActivity('message', handler)          // all messages
this.onConversationUpdate('membersAdded', handler)
this.onActivity('invoke', handler)
this.onActivity('message', fallback, [], RouteRank.Last) // fallback

TurnState — dot-notation keys auto-scoped to conversation/user/temp:

const count = state.getValue('conversation.counter') || 0
state.setValue('conversation.counter', count + 1)
state.deleteValue('conversation.counter')

Storage backends

| Backend | Use case |

|---|---|

| MemoryStorage | Local dev only — not persistent |

| BlobsStorage | Azure Blob — production |

| CosmosDbPartitionedStorage | CosmosDB — production |

Authorization (User Token Flow)

class MyAgent extends AgentApplication<TurnState> {
  constructor() {
    super({
      storage: new MemoryStorage(),
      authorization: {
        graph: {
          name: 'GraphOAuthConnection', // OAuth connection name in Azure Bot resource
          title: 'Sign in with Microsoft',
          text: 'Please sign in to continue',
        }
      }
    })

    this.onSignInSuccess(async (ctx, state) => {
      const { token } = await this.authorization.getToken(ctx, 'graph')
      // use token to call external APIs
      await ctx.sendActivity('Signed in!')
    })

    this.onSignInFailure(async (ctx, state, authId, err) => {
      await ctx.sendActivity(`Sign-in failed: ${err}`)
    })

    // Protect a route — SDK sends the OAuth card automatically if not signed in
    this.onActivity('message', async (ctx, state) => {
      const { token } = await this.authorization.getToken(ctx, 'graph')
      // token is guaranteed here — route won't run until user is signed in
    }, ['graph'])

    // Sign out from all providers
    this.onMessage('/logout', async (ctx, state) => {
      await this.authorization.signOut(ctx, state)
      await ctx.sendActivity('Signed out.')
    })
  }
}

name can also be provided via environment variable AgentApplication__UserAuthorization__Handlers__graph__Settings__azureBotOAuthConnectionName (where graph is the handler key) and omitted from code.

OBO (on-behalf-of) — exchange user token for a downstream service token:

const { token } = await this.authorization.exchangeToken(ctx, 'graph', {
  scopes: ['https://graph.microsoft.com/.default']
})
// use token to call Graph or other downstream APIs

Agentic auth (agent-to-agent, no user prompt):

authorization: { agentic: { type: 'AgenticUserAuthorization' } }
// env: AgentApplication__UserAuthorization__Handlers__agentic__Settings__type=AgenticUserAuthorization
// env: AgentApplication__UserAuthorization__Handlers__agentic__Settings__scopes=https://graph.microsoft.com/.default

Cards

Read references/cards.md when working with Adaptive Cards, Hero Cards, Thumbnail Cards, or other card types.

Streaming

Read references/streaming.md when implementing streaming responses.

Common Mistakes

1. Wrong field name on ConversationReference

// WRONG
const ref: ConversationReference = { bot: { id: appId } }

// CORRECT
const ref: ConversationReference = { agent: { id: appId } }

2. JwtPayload.aud is string | string[]

// WRONG
const appId = payload.aud

// CORRECT
const appId = Array.isArray(payload.aud) ? payload.aud[0] : payload.aud

3. Adapter callbacks swallow exceptions

// WRONG — error is lost
await adapter.continueConversation(identity, ref, async (ctx) => {
  throw new Error('something failed') // swallowed
})

// CORRECT — capture and rethrow
let capturedError: Error | undefined
await adapter.continueConversation(identity, ref, async (ctx) => {
  try {
    await doWork(ctx)
  } catch (err) {
    capturedError = err as Error
  }
})
if (capturedError) throw capturedError

4. startServer() returns Express — add routes to the return value

// WRONG — routes never registered
const app = express()
startServer(agent)
app.get('/health', handler)

// CORRECT
const server = startServer(agent)
server.get('/health', handler)

Wrong method names — activity() and message() don't exist

AgentApplication uses on-prefixed method names. Common wrong guesses:

// ❌ wrong — these methods don't exist
app.activity('message', handler)
app.message(handler)

// ✅ correct
app.onActivity('message', handler)
app.onMessage('/help', handler)
app.onConversationUpdate('membersAdded', handler)

Contributing

If you hit a problem this skill couldn't solve, found a workaround, or noticed something wrong or outdated, that's valuable — please help improve this skill for everyone.

Draft a suggested issue title and body based on the conversation, then ask the user to open it at: https://github.com/microsoft/agents/issues/new

A good issue includes:

  • What the user was trying to do
  • What went wrong (errors, unexpected behavior)
  • What worked — including any workaround found during this conversation
  • Relevant code or config snippets

Other skills for the same job

different authors, same section of the catalogue
MCP Builder
by anthropics
vendor ×13

Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).

30k tokens scripts
Changelog Generator
by frostant
×9

Automatically creates user-facing changelogs from git commits by analyzing commit history, categorizing changes, and transforming technical commits into clear, customer-friendly release notes. Turns hours of manual changelog writing into minutes of automated generation.

774 tokens
Finishing A Development Branch
by ZhanlinCui
×7

Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup

1k tokens
MCP Builder
by JayZeeDesign
×7

Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).

37k tokens scripts
Vercel React Native Skills
by vercel-labs
vendor ×6

React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.

39k tokens
Vercel React Best Practices
by ratacat
×5

React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.

34k tokens
Next Best Practices
by vercel-labs
vendor ×4

Next.js best practices - file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling

20k tokens
Using Git Worktrees
by ZhanlinCui
×4

Use when starting feature work that needs isolation from current workspace or before executing implementation plans - creates isolated git worktrees with smart directory selection and safety verification

1k tokens

How to use it

Copy the folder

Take microsoft/agents-sdk-typescript from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.

Install what it needs

The instructions reference npm. Without those the skill loads but fails at the first command.