Skip to main content
Providers are dependency-injected singletons (or scoped singletons) that your apps, tools, adapters, and plugins can use — e.g., config, DB pools, Redis clients, KMS, HTTP clients. They’re declared with @Provider() and registered at server or app scope. Resolution is hierarchical: tool → app → server.

Define a provider

import { Provider, ProviderScope } from '@frontmcp/sdk';

@Provider({
  name: 'DbProvider',
  description: 'Postgres connection pool',
  scope: ProviderScope.GLOBAL, // GLOBAL | CONTEXT
})
export class DbProvider {
  /* create pool, expose query() etc. */
}

Scopes

  • GLOBAL (default): one instance per process/worker. Ideal for clients, pools, caches.
  • CONTEXT: one instance per request. Use for per-request state, tracing, or user-specific data.
Legacy scopes SESSION and REQUEST are deprecated and automatically normalized to CONTEXT.

Register providers

Server-level providers (available to all apps):
@FrontMcp({
  info: { name: 'Suite', version: '1.0.0' },
  apps: [BillingApp, AnalyticsApp],
  providers: [DbProvider, CacheProvider],
})
export default class Server {}
App-level providers (override or add on top of server-level):
@App({
  name: 'Billing',
  providers: [BillingConfigProvider],
})
export default class BillingApp {}
You can register class, value, or factory providers. Factories are useful for async initialization or composing other providers.

Using providers from tools/plugins

FrontMCP resolves providers for your executors and hooks. Keep your tool logic pure; read side-effects (DB, queues, secrets) via providers.
  • Prefer GLOBAL for shared clients.
  • Use CONTEXT for request-scoped state or user-bound data.
Provider injection/consumption follows your runtime’s DI rules. In general: register providers at the minimal scope and let the framework resolve them for tools and hooks at execution time.

FrontMcpContext

Every HTTP request creates a FrontMcpContext that flows through the entire execution chain via AsyncLocalStorage. Access it via the FRONTMCP_CONTEXT token or the context getter.

In Tools/Resources/Prompts

import { Tool, FRONTMCP_CONTEXT } from '@frontmcp/sdk';
import { z } from 'zod';

@Tool({ name: 'my-tool', inputSchema: { query: z.string() } })
class MyTool {
  async execute({ query }) {
    // Via getter (recommended)
    const ctx = this.context;

    // Via DI
    const ctx2 = this.get(FRONTMCP_CONTEXT);

    console.log(ctx.requestId, ctx.traceContext.traceId);
    return `Processed: ${query}`;
  }
}

In CONTEXT-Scoped Providers

CONTEXT-scoped providers can access the current context via factory injection:
import { ProviderScope, FRONTMCP_CONTEXT, FrontMcpContext } from '@frontmcp/sdk';

// Factory provider pattern
const requestLoggerProvider = {
  provide: 'RequestLogger',
  scope: ProviderScope.CONTEXT,
  factory: (ctx: FrontMcpContext) => ({
    log: (msg: string) => console.log(`[${ctx.requestId}] ${msg}`),
  }),
  inject: [FRONTMCP_CONTEXT],
};

FrontMcpContext API

PropertyTypeDescription
requestIdstringUnique request identifier (UUID v4)
traceContextTraceContextW3C Trace Context (traceId, parentId, traceFlags)
sessionIdstringMCP session identifier
authInfoPartial<AuthInfo>Authentication information
scopeIdstringCurrent scope identifier
timestampnumberRequest start timestamp
metadataRequestMetadataHeaders, user-agent, client IP
transportTransportAccessor | undefinedTransport for elicit requests
MethodDescription
mark(name)Record timing mark
elapsed(from?, to?)Get elapsed time between marks
set(key, value)Store context-scoped data
get(key)Retrieve context-scoped data
getLogger(parent)Get child logger with context
fetch(input, init?)Context-aware fetch with auto-injection

Context-Aware Fetch

Use ctx.fetch() to automatically inject headers into outgoing requests:
const ctx = this.context;

// Auto-injects: Authorization, traceparent, x-request-id, custom headers
const response = await ctx.fetch('https://api.example.com/data');

Transport Access (Elicit)

Access the transport for interactive prompts:
const ctx = this.context;

if (ctx.transport?.supportsElicit) {
  const result = await ctx.transport.elicit('Please confirm', schema);
  console.log(result.action, result.content);
}
See Request Context for the complete guide including distributed tracing and migration from legacy APIs.

CONTEXT-Scoped Providers

CONTEXT-scoped providers are created fresh for each request. They’re ideal for per-request state, user-specific data, or request-scoped caching.

Accessing Session ID

The session ID is available directly from the context:
import { Provider, ProviderScope, FRONTMCP_CONTEXT, FrontMcpContext } from '@frontmcp/sdk';

// Factory provider that uses session ID
const sessionCacheProvider = {
  provide: 'SessionCache',
  scope: ProviderScope.CONTEXT,
  factory: (ctx: FrontMcpContext, redis: RedisProvider) => {
    const cacheKey = `cache:${ctx.sessionId}`;
    return {
      async get(key: string) {
        return redis.get(`${cacheKey}:${key}`);
      },
      async set(key: string, value: unknown) {
        return redis.set(`${cacheKey}:${key}`, value);
      },
    };
  },
  inject: [FRONTMCP_CONTEXT, RedisProvider],
};

Example: Context-Scoped Redis Provider

import { Provider, ProviderScope, FRONTMCP_CONTEXT, FrontMcpContext } from '@frontmcp/sdk';

const sessionRedisProvider = {
  provide: 'SessionRedis',
  scope: ProviderScope.CONTEXT,
  factory: (ctx: FrontMcpContext, redis: RedisProvider) => {
    const safeSid = ctx.sessionId.replace(/[:\s]/g, '_');
    const prefix = `:session:${safeSid}:`;

    return {
      async setValue<T>(key: string, value: T, ttl?: number) {
        await redis.setValue(`${prefix}${key}`, value, ttl);
      },
      async getValue<T>(key: string): Promise<T | undefined> {
        return redis.getValue(`${prefix}${key}`);
      },
    };
  },
  inject: [FRONTMCP_CONTEXT, RedisProvider],
};

Multi-Pod Deployment Considerations

CONTEXT-scoped providers are per-request - each request builds its own provider instances. They are NOT shared across requests or pods.
When running behind a load balancer:
  • Each request builds its own provider instances
  • Transport state (MCP protocol) is shared via Redis (if configured)
  • For cross-pod session data, use Redis or another distributed store
// Good: Use Redis for data that must persist across requests
const crossRequestDataProvider = {
  provide: 'CrossRequestData',
  scope: ProviderScope.CONTEXT,
  factory: (ctx: FrontMcpContext, redis: RedisProvider) => ({
    async setUserPreference(key: string, value: any) {
      // Data is stored in Redis, accessible from any request/pod
      await redis.setValue(`pref:${ctx.sessionId}:${key}`, value);
    },
    async getUserPreference(key: string) {
      return redis.getValue(`pref:${ctx.sessionId}:${key}`);
    },
  }),
  inject: [FRONTMCP_CONTEXT, RedisProvider],
};

Session Metadata

Access additional session metadata (protocol, platform type) via the context:
@Tool({ name: 'session-info', inputSchema: {} })
class SessionInfoTool {
  async execute() {
    const ctx = this.context;
    const metadata = ctx.sessionMetadata;

    return {
      sessionId: ctx.sessionId,
      protocol: metadata?.protocol,      // 'streamable-http', 'sse', etc.
      platformType: metadata?.platform,  // 'openai', 'claude', etc.
    };
  }
}