@microsoft/agents-hosting
v1.9.1
Published
Microsoft 365 Agents SDK for JavaScript
Keywords
Readme
@microsoft/agents-hosting
Overview
The @microsoft/agents-hosting package provides the necessary tools and components to create and host Microsoft Agents. This package includes a compatible API to migrate a bot using botbuilder from the BotFramework SDK.
Installation
To install the package:
npm install @microsoft/agents-hostingHosting integration APIs
To make hosting an agent independent of any single web framework, this package
exposes framework-agnostic primitives that the
@microsoft/agents-hosting-express and
@microsoft/agents-hosting-fastify packages build on:
createCloudAdapter(agent, authConfig, options?)— returns{ adapter, headerPropagation }for processing incoming activities. It preserves anAgentApplication's existing adapter;authConfigis used only when creating an adapter. Useoptions.configurationContextto scope preloaded configuration to this host. Use this from any web framework.CloudAdapterResult— return type ofcreateCloudAdapter.createAgentResponseHandler(adapter, agent, conversationState)— framework-agnostic handler(req, res, params) => Promise<void>for the authenticated SDK-specific Activity callback route.AgentResponseHandler,AgentResponseHandlerParams,AGENT_RESPONSE_ROUTE_PATH— supporting types and the canonical route path.WebResponse,NextFunction,WebRequestParamsCarrier— minimal structural interfaces (no Express/Fastify imports) used by the cross-framework helpers above.
Most consumers should keep using startServer/createAgentRequestHandler from the
Express or Fastify packages; reach for these APIs when adapting another framework.
This Activity callback flow is used for SDK-specific Activity-protocol delegation.
The Activity callback handler authenticates requests once through the supplied
CloudAdapter. That boundary validates the token for any configured host connection;
the handler then verifies that the caller application matches the delegated agent
recorded for that conversation. Existing route-level authorizeJWT middleware is
redundant but remains compatible. On configured or production hosts, missing,
invalid, expired, or wrong-audience tokens return 401. An authenticated caller
that does not match the delegated agent, or missing, malformed, or pre-upgrade
delegated state, returns 403.
Anonymous callbacks are supported only for unconfigured development hosts
outside production and emit a registration warning because peer ownership cannot
be verified. Pre-upgrade conversations must be restarted.
Pluggable configuration sources
Existing environment variables and direct runtime options continue to work
without changes. Additional asynchronous sources can be loaded into an
immutable host-scoped context and then consumed synchronously by auth
configuration, CloudAdapterOptions, outbound host validation, and
AgentApplication user authorization:
import {
AttachmentDownloader,
CloudAdapter,
createConfigurationContext,
createOutboundHostValidator,
ConfigurationSource,
} from '@microsoft/agents-hosting'
const source: ConfigurationSource = {
name: 'central-configuration',
async load () {
return {
format: 'document',
value: {
cloudAdapterOptions: {
validateServiceUrl: true
},
outboundHostValidator: {
enabled: true,
hosts: ['api.contoso.com']
}
}
}
}
}
const configurationContext = await createConfigurationContext([{
source,
mode: 'overrideEnvironment'
}])
const adapter = new CloudAdapter(
undefined,
undefined,
undefined,
{ configurationContext }
)
// Reuse the same policy for code-level outbound consumers such as attachment
// downloaders.
const outboundPolicy = createOutboundHostValidator({ configurationContext })
const downloader = new AttachmentDownloader('inputFiles', outboundPolicy)For an explicitly selected JSON file, use the built-in file source:
import {
createConfigurationContext,
createJsonFileConfigurationSource
} from '@microsoft/agents-hosting'
const configurationContext = await createConfigurationContext([{
source: createJsonFileConfigurationSource('config.DEVELOPMENT.json'),
mode: 'overrideEnvironment'
}])The helper loads strict JSON using the hierarchical document shape. It does not discover files, select environment-specific names, parse command-line options, or choose precedence. The application owns those policies and supplies the source mode. Missing files, malformed JSON, and non-object document roots fail loading without including file contents in diagnostics.
Context creation is atomic: if a source fails, no partial values are applied.
Create separate contexts for independently configured agents in one process.
Pass the same context to each agent's CloudAdapter and
AgentApplication. Existing applications may instead call
preloadConfigurationSources once before constructing consumers; that API is
the compatibility wrapper over the default process context.
Load the default context before constructing any configuration consumer. Default-context loading is atomic and retryable after failure, but a successful load can occur only once and cannot run concurrently. Constructing a consumer first consumes an empty default snapshot and prevents later preload. To adopt updated or rotated values, create a new immutable context and reconstruct its consumers; existing contexts and consumers are not mutated.
Every source must select an explicit mode:
fallback: below Bot Framework compatibility variables and supported modern__environment configuration.overrideEnvironment: above environment configuration and below direct runtime options.enforce: above direct runtime options.
Sources in the same mode are applied in registration order, with later values
winning. enforce can override values supplied directly by application code
and should be reserved for deliberate central policy enforcement.
Within the environment band, a modern Connections__* registry replaces flat
or prefixed Bot Framework authentication variables; the two representations
are not merged property by property. When no modern registry exists, the
legacy flat or prefixed variables remain supported. When a modern environment
registry has no ConnectionsMap__* entries, the environment auth loader
preserves backward compatibility by creating a wildcard/default route for the
first connection; connection insertion order determines the default only in
that case. External sources may omit connectionsMap when they define exactly
one connection, which is treated as the wildcard/default. External sources
that define multiple connections must provide an explicit wildcard/default
route.
Supported canonical path families are:
connections.<id>.settings.<property>connectionsMap.<index>.serviceUrlconnectionsMap.<index>.audienceconnectionsMap.<index>.connectioncloudAdapterOptions.<property>outboundHostValidator.<property>agentApplication.userAuthorization.handlers.<id>.settings.<property>
Sources return either { format: 'canonical', values } for canonical string
paths or { format: 'document', value } for hierarchical typed JSON.
Existing sources that return a bare canonical record remain supported for
backward compatibility; new sources should use the explicit tagged form.
Canonical paths use current property names only, except for the retained
WIDAssertionFile compatibility spelling (prefer federatedTokenFile).
Schema-shaped __
environment variables are also a permanent first-class input and bind to the
same hierarchy. Bot Framework-era MicrosoftApp*, prefixed connection, and
<handlerId>_* aliases remain supported through an isolated compatibility
adapter, but are not valid external-source paths. Invalid paths or values fail
loading without including the supplied value in diagnostics.
The provider-neutral hierarchical document shape is defined by
agents-configuration.schema.json.
Example Usage based on the AgentApplication object
import { AgentApplication, MemoryStorage, TurnContext, TurnState } from '@microsoft/agents-hosting'
const echo = new AgentApplication<TurnState>({ storage: new MemoryStorage() })
echo.onConversationUpdate('membersAdded', async (context: TurnContext) => {
await context.sendActivity('Welcome to the Echo sample, send a message to see the echo feature in action.')
})
echo.onActivity('message', async (context: TurnContext, state: TurnState) => {
let counter: number = state.getValue('conversation.counter') || 0
await context.sendActivity(`[${counter++}]You said: ${context.activity.text}`)
state.setValue('conversation.counter', counter)
})Storage providers
Public storage options accept StorageProvider, which is Storage | StorageV2.
Providers can implement either contract. Custom V2 providers extend StorageV2,
so hosting can identify the structured contract with instanceof.
Hosting adapts a legacy Storage provider for default upsert operations. The
adapter cannot support create-only, replace, or version conditions.
Built-in V1 and V2 providers have separately named classes, avoiding overloaded
JavaScript methods while preserving every legacy constructor and return type:
const legacyStorage = new MemoryStorage()
const storageV2 = new MemoryStorageV2()
const fileStorageV2 = new FileStorageV2('./data')V2 calls return keyed operation results. Read values are at
results[key].value; inspect status and version for operation outcomes.
Example Usage based on bot framework Activity Handler
Create an Echo bot using the ActivityHandler
// myHandler.ts
import { ActivityHandler, MessageFactory } from '@microsoft/agents-hosting'
export class MyHandler extends ActivityHandler {
constructor () {
super()
this.onMessage(async (context, next) => {
const replyText = `Agent: ${context.activity.text}`
await context.sendActivity(MessageFactory.text(replyText))
await next()
})
}
}Host the bot with express
// index.ts
import express, { Response } from 'express'
import { Request, CloudAdapter, authorizeJWT, AuthConfiguration, loadAuthConfigFromEnv } from '@microsoft/agents-hosting'
import { EchoBot } from './myHandler'
const authConfig: AuthConfiguration = loadAuthConfigFromEnv()
const adapter = new CloudAdapter(authConfig)
const myHandler = new MyHandler()
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 myHandler.run(context))
})
Inbound token issuer validation
Inbound JWT signature, audience, lifetime, and tenant-claim consistency checks are always applied. Issuer allow-list validation is opt-in because AgentID blueprint identities can use runtime-selected tenant issuers that are not known when the host configuration is created.
Enable issuer validation only when every accepted issuer is represented by the
connection's configured tenant or issuers list:
validateIssuer=true
# Latest connection configuration format
Connections__serviceConnection__Settings__ValidateIssuer=trueAccepted boolean values are true, false, 1, and 0 (case-insensitive,
with surrounding whitespace ignored). Any other non-empty value causes
configuration loading to fail with a structured error that identifies the
configuration source and accepted values.
Outbound request host validation
OutboundHostValidator provides an opt-in allowlist for server-side requests made
to activity service URLs and attachment URLs. Enforcement is disabled by default.
It can be configured with environment variables:
OutboundHostValidator__Enabled=true
OutboundHostValidator__IncludeDefaultMicrosoftHosts=true
OutboundHostValidator__Hosts=contoso.com,fabrikam.comIndexed host variables such as OutboundHostValidator__Hosts__0=contoso.com are
also supported. A host entry matches both the exact host and its subdomains, and
is normalized (scheme/port/path stripped; a leading *. is accepted and ignored).
When enforcement is enabled, CloudAdapter rejects inbound activities whose
serviceUrl host is not allowlisted, and it also rejects serviceurl claim
mismatches (equivalent to CloudAdapterOptions.validateServiceUrl=true).
For explicit configuration, reuse the same immutable policy in the adapter and attachment downloaders:
import {
AgentApplication,
AttachmentDownloader,
CloudAdapter,
OutboundHostValidator
} from '@microsoft/agents-hosting'
const outboundHostValidator = new OutboundHostValidator({
enabled: true,
hosts: ['contoso.com']
})
const adapter = new CloudAdapter(undefined, undefined, undefined, undefined, outboundHostValidator)
const agent = new AgentApplication({
adapter,
fileDownloaders: [new AttachmentDownloader('inputFiles', outboundHostValidator)]
})The validator checks the URL supplied to the downloader. Redirects retain native
fetch behavior.
