> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentfront.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Context Providers

> Dependency injection providers — register services with @Provider and inject via this.get()

**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**.

<Info>
  Provider metadata, records, and DI helpers remain available through `@frontmcp/sdk` as type-only exports, so your existing `@frontmcp/di` imports keep working while bundlers drop unused types. Enums such as `ProviderScope` and `ProviderKind` still expose their runtime values, so you can configure scopes the same way you always have.
</Info>

<Tip>
  **Nx users:** Scaffold with `nx g @frontmcp/nx:provider my-provider --project my-app`. See [Provider Generator](/frontmcp/nx-plugin/generators/provider).
</Tip>

## Define a provider

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
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. A client that opens a session (MCP before 2026-07-28) keeps one instance for the session the server verified; any other caller, such as one with a token under 2026-07-28, gets new instances on every request, never another caller's.

<Info>
  Legacy scopes `SESSION` and `REQUEST` are deprecated and automatically normalized to `CONTEXT`.
</Info>

## Register providers

**Server-level** providers (available to all apps):

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@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):

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@App({
  name: 'Billing',
  providers: [BillingConfigProvider],
})
export default class BillingApp {}
```

<Tip>
  You can register **class**, **value**, or **factory** providers. Factories are useful for async initialization or
  composing other providers.
</Tip>

## File Layout (folder per provider)

Once a provider grows past a single class — adds a spec, a helper, internal types, or config-loading logic — promote it from `src/providers/<slug>.provider.ts` to a folder per provider:

```text theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
src/providers/<provider-slug>/
├── index.ts                            # barrel: re-exports class, factory, public types
├── <provider-slug>.provider.ts         # @Provider class and/or AsyncProvider factory
├── <provider-slug>.provider.spec.ts    # unit tests
├── types.ts                            # (optional) internal types / token interfaces
└── <helper>.ts (+ .spec.ts)            # (optional) per-provider helpers
```

Plus a top-level `src/providers/index.ts` barrel:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
// src/providers/index.ts
export * from './task-store';
export * from './config';
export * from './redis';
```

**Cross-provider imports go through the subfolder barrel** — never reach into another provider's implementation file:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
// ✅ Good — through the subfolder barrel
import { TaskStoreProvider } from '../task-store';

// ❌ Bad — reaches into the implementation file
import { TaskStoreProvider } from '../task-store/task-store.provider';
```

Trivial providers (a `Map`-based cache, a pure DTO with no helpers) can stay flat. As soon as the provider has a spec, a helper, or internal types, fold it into its own directory.

## 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.

<Info>
  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.
</Info>

### Resolution order

A tool, resource or prompt resolves each provider from the **nearest** definition in its own hierarchy: first the plugin that contributed the entry, then its app (including the providers the app's plugins contribute), then the server. The same rule applies to CONTEXT-scoped providers. If two apps on one server each install the same plugin with their own options, each app's entries get the provider built from that app's options. The definition the other app registered on the server never shadows it.

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@App({ id: 'billing', plugins: [ApprovalPlugin.init({ storageInstance: billingStorage })], tools: [RefundTool] })
class BillingApp {}

@App({ id: 'ops', plugins: [ApprovalPlugin.init({ storageInstance: opsStorage })], tools: [DeployTool] })
class OpsApp {}

// Inside RefundTool, `this.approval` writes to billingStorage; inside DeployTool, to opsStorage.
```

***

## 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

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { Tool, FRONTMCP_CONTEXT } from '@frontmcp/sdk';
import { z } from '@frontmcp/sdk';

@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:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { ProviderScope, FRONTMCP_CONTEXT, FrontMcpContext } from '@frontmcp/sdk';

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

### FrontMcpContext API

| Property | Type | Description |
| - | - | - |
| `requestId` | `string` | Unique request identifier (UUID v4) |
| `traceContext` | `TraceContext` | W3C Trace Context (traceId, parentId, traceFlags) |
| `sessionId` | `string` | MCP session identifier |
| `authInfo` | `Partial<AuthInfo>` | Authentication information |
| `scopeId` | `string` | Current scope identifier |
| `timestamp` | `number` | Request start timestamp |
| `metadata` | `RequestMetadata` | Headers, user-agent, client IP |
| `transport` | `TransportAccessor \| undefined` | Transport for elicit requests |

| Method | Description |
| - | - |
| `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:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
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:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
const ctx = this.context;

if (ctx.transport?.supportsElicit) {
  const result = await ctx.transport.elicit('Please confirm', schema);
  console.log(result.action, result.content);
}
```

<Tip>
  See [Request Context](/frontmcp/extensibility/request-context) for the complete guide including distributed tracing and migration from legacy APIs.
</Tip>

***

## 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:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { Provider, ProviderScope, FRONTMCP_CONTEXT, FrontMcpContext } from '@frontmcp/sdk';

// Factory provider that uses session ID
const sessionCacheProvider = {
  provide: 'SessionCache',
  scope: ProviderScope.CONTEXT,
  useFactory: (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

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { Provider, ProviderScope, FRONTMCP_CONTEXT, FrontMcpContext } from '@frontmcp/sdk';

const sessionRedisProvider = {
  provide: 'SessionRedis',
  scope: ProviderScope.CONTEXT,
  useFactory: (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

<Warning>
  CONTEXT-scoped providers are **per-request** - each request builds its own provider instances.
  They are NOT shared across requests or pods.
</Warning>

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

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
// Good: Use Redis for data that must persist across requests
const crossRequestDataProvider = {
  provide: 'CrossRequestData',
  scope: ProviderScope.CONTEXT,
  useFactory: (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:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@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.
    };
  }
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.