api-config

Reference for core and server configuration in `@cyanheads/mcp-ts-core`. Covers env var tables with defaults, priority order, server-specific Zod schema pattern, and Workers lazy-parsing requirement.

Overview

Configuration has two layers: core config (managed by the framework, env-driven) and server config (your own Zod schema for domain-specific env vars). Never merge them.

Import: AppConfig, config, parseConfig, resetConfig, ConfigSchema from @cyanheads/mcp-ts-core/config.


Core config

Managed by @cyanheads/mcp-ts-core. Validated via Zod from environment variables. Uses a lazy proxy — parsing is deferred until the first property read.

Priority (highest to lowest):

  1. name/version overrides passed to createApp() or createWorkerHandler()
  2. Environment variables
  3. package.json fields

Identity

Env VarAppConfig fieldDefaultNotes
MCP_SERVER_NAMEmcpServerNamepackage.json nameOverrides package name
MCP_SERVER_VERSIONmcpServerVersionpackage.json versionOverrides package version
MCP_SERVER_DESCRIPTIONmcpServerDescriptionpackage.json descriptionOptional
PACKAGE_NAMEpkg.namepackage.json nameRarely needed
PACKAGE_VERSIONpkg.versionpackage.json versionRarely needed
NODE_ENVenvironmentdevelopmentAliases: devdevelopment, prodproduction, testtesting
MCP_LOG_LEVELlogLeveldebugAliases: warnwarning, errerror, fatal/silentemerg, tracedebug, informationinfo
LOGS_DIRlogsPath<project-root>/logsNode.js only; absolute or relative to project root

Transport

Env VarAppConfig fieldDefaultNotes
MCP_TRANSPORT_TYPEmcpTransportTypestdiostdio | http
MCP_HTTP_PORTmcpHttpPort3010Port for HTTP transport
MCP_HTTP_HOSTmcpHttpHost127.0.0.1Bind address
MCP_HTTP_ENDPOINT_PATHmcpHttpEndpointPath/mcpHTTP endpoint path
MCP_HTTP_MAX_PORT_RETRIESmcpHttpMaxPortRetries15Retry count if port is busy
MCP_HTTP_PORT_RETRY_DELAY_MSmcpHttpPortRetryDelayMs50Delay between port retries (ms)
MCP_SESSION_MODEmcpSessionModeautostateless | stateful | auto
MCP_STATEFUL_SESSION_STALE_TIMEOUT_MSmcpStatefulSessionStaleTimeoutMs180000030 min; stale session eviction
MCP_RESPONSE_VERBOSITYmcpResponseVerbositystandardminimal | standard | full
MCP_ALLOWED_ORIGINSmcpAllowedOriginsComma-separated list; omit to allow all
MCP_SERVER_RESOURCE_IDENTIFIERmcpServerResourceIdentifierRFC 8707 resource indicator URL

Auth

Env VarAppConfig fieldDefaultNotes
MCP_AUTH_MODEmcpAuthModenonenone | jwt | oauth
MCP_AUTH_SECRET_KEYmcpAuthSecretKeyRequired for jwt mode; min 32 chars
OAUTH_ISSUER_URLoauthIssuerUrlRequired for oauth mode
OAUTH_AUDIENCEoauthAudienceRequired for oauth mode
OAUTH_JWKS_URIoauthJwksUriOverride JWKS endpoint (otherwise derived from issuer)
OAUTH_JWKS_COOLDOWN_MSoauthJwksCooldownMs3000005 min; min time between JWKS refetches
OAUTH_JWKS_TIMEOUT_MSoauthJwksTimeoutMs5000JWKS fetch timeout (ms)
DEV_MCP_AUTH_BYPASSdevMcpAuthBypassfalseSkip auth in development; blocked in production
DEV_MCP_CLIENT_IDdevMcpClientIdDev-only: override client ID
DEV_MCP_SCOPESdevMcpScopesDev-only: comma-separated scope overrides

OAuth proxy (optional sub-object)

Activated when OAUTH_PROXY_AUTHORIZATION_URL or OAUTH_PROXY_TOKEN_URL is set.

Env VarAppConfig fieldNotes
OAUTH_PROXY_AUTHORIZATION_URLoauthProxy.authorizationUrlProxy authorization endpoint
OAUTH_PROXY_TOKEN_URLoauthProxy.tokenUrlProxy token endpoint
OAUTH_PROXY_REVOCATION_URLoauthProxy.revocationUrlOptional
OAUTH_PROXY_ISSUER_URLoauthProxy.issuerUrlOptional
OAUTH_PROXY_SERVICE_DOCUMENTATION_URLoauthProxy.serviceDocumentationUrlOptional
OAUTH_PROXY_DEFAULT_CLIENT_REDIRECT_URISoauthProxy.defaultClientRedirectUrisComma-separated list

Storage

Env VarAppConfig fieldDefaultNotes
STORAGE_PROVIDER_TYPEstorage.providerTypein-memoryin-memory | filesystem | supabase | cloudflare-r2 | cloudflare-kv | cloudflare-d1; aliases: mem, fs
STORAGE_FILESYSTEM_PATHstorage.filesystemPath./.storageUsed only when providerType is filesystem

Supabase (optional sub-object)

Activated when both SUPABASE_URL and SUPABASE_ANON_KEY are set.

Env VarAppConfig fieldNotes
SUPABASE_URLsupabase.urlRequired to activate
SUPABASE_ANON_KEYsupabase.anonKeyRequired to activate
SUPABASE_SERVICE_ROLE_KEYsupabase.serviceRoleKeyOptional; elevated access

LLM

Env VarAppConfig fieldDefaultNotes
OPENROUTER_API_KEYopenrouterApiKeyOptional; enables LLM provider
OPENROUTER_APP_URLopenrouterAppUrlhttp://localhost:3000Reported to OpenRouter
OPENROUTER_APP_NAMEopenrouterAppNamepackage.json nameReported to OpenRouter
LLM_DEFAULT_MODELllmDefaultModelgoogle/gemini-2.5-flash-preview-05-20OpenRouter model ID
LLM_DEFAULT_TEMPERATUREllmDefaultTemperatureFloat
LLM_DEFAULT_TOP_PllmDefaultTopPFloat
LLM_DEFAULT_MAX_TOKENSllmDefaultMaxTokensInteger
LLM_DEFAULT_TOP_KllmDefaultTopKInteger
LLM_DEFAULT_MIN_PllmDefaultMinPFloat

Telemetry

Env VarAppConfig fieldDefaultNotes
OTEL_ENABLEDopenTelemetry.enabledfalseEnable OpenTelemetry export
OTEL_SERVICE_NAMEopenTelemetry.serviceNamepackage.json name
OTEL_SERVICE_VERSIONopenTelemetry.serviceVersionpackage.json version
OTEL_EXPORTER_OTLP_TRACES_ENDPOINTopenTelemetry.tracesEndpointOTLP traces endpoint URL
OTEL_EXPORTER_OTLP_METRICS_ENDPOINTopenTelemetry.metricsEndpointOTLP metrics endpoint URL
OTEL_TRACES_SAMPLER_ARGopenTelemetry.samplingRatio1.00–1; fraction of traces to export
OTEL_LOG_LEVELopenTelemetry.logLevelINFOOTel SDK internal log level: NONE | ERROR | WARN | INFO | DEBUG | VERBOSE | ALL

Tasks

Env VarAppConfig fieldDefaultNotes
TASK_STORE_TYPEtasks.storeTypein-memoryin-memory | storage; aliases: mem/memoryin-memory, persistentstorage
TASK_STORE_TENANT_IDtasks.tenantIdsystem-tasksTenant ID for task state storage
TASK_STORE_DEFAULT_TTL_MStasks.defaultTtlMsTTL for completed tasks (ms); null = no expiry

Speech (optional sub-object)

Activated when SPEECH_TTS_ENABLED or SPEECH_STT_ENABLED is set.

TTS (Text-to-Speech)

Env VarAppConfig fieldDefaultNotes
SPEECH_TTS_ENABLEDspeech.tts.enabledfalseEnable TTS
SPEECH_TTS_PROVIDERspeech.tts.providerelevenlabsCurrently only elevenlabs
SPEECH_TTS_API_KEYspeech.tts.apiKeyProvider API key
SPEECH_TTS_BASE_URLspeech.tts.baseUrlOverride provider base URL
SPEECH_TTS_DEFAULT_VOICE_IDspeech.tts.defaultVoiceIdDefault voice identifier
SPEECH_TTS_DEFAULT_MODEL_IDspeech.tts.defaultModelIdDefault model identifier
SPEECH_TTS_TIMEOUTspeech.tts.timeoutRequest timeout (ms)

STT (Speech-to-Text)

Env VarAppConfig fieldDefaultNotes
SPEECH_STT_ENABLEDspeech.stt.enabledfalseEnable STT
SPEECH_STT_PROVIDERspeech.stt.provideropenai-whisperCurrently only openai-whisper
SPEECH_STT_API_KEYspeech.stt.apiKeyProvider API key
SPEECH_STT_BASE_URLspeech.stt.baseUrlOverride provider base URL
SPEECH_STT_DEFAULT_MODEL_IDspeech.stt.defaultModelIdDefault model identifier
SPEECH_STT_TIMEOUTspeech.stt.timeoutRequest timeout (ms)

Server config (separate schema)

Define your own Zod schema for domain-specific env vars. Never merge with core's schema.

Use the lazy init/accessor pattern — do not parse process.env at module top-level.

// src/config/server-config.ts
import { z } from '@cyanheads/mcp-ts-core';
import { parseEnvConfig } from '@cyanheads/mcp-ts-core/config';

const ServerConfigSchema = z.object({
  apiKey: z.string().describe('External API key'),
  maxResults: z.coerce.number().default(100),
});

export type ServerConfig = z.infer<typeof ServerConfigSchema>;

let _config: ServerConfig | undefined;

export function getServerConfig(): ServerConfig {
  _config ??= parseEnvConfig(ServerConfigSchema, {
    apiKey: 'MY_API_KEY',
    maxResults: 'MY_MAX_RESULTS',
  });
  return _config;
}

Why parseEnvConfig? It maps Zod schema paths to env var names so validation errors name the actual variable at fault. A missing MY_API_KEY produces:

Server config validation failed:
  - MY_API_KEY (apiKey): Invalid input: expected string, received undefined

Instead of a raw ZodError dump at startup. The framework catches the resulting ConfigurationError and prints a clean banner (full stack behind DEBUG=true).

Direct ServerConfigSchema.parse(...) still works — the framework intercepts raw ZodError thrown from setup() and converts it — but error messages won't know about env var names, so they show the Zod path (apiKey) instead of the variable name (MY_API_KEY).

Workers: Do not parse process.env at module top-level. In Workers, env bindings are injected at request time via injectEnvVars(), after all static imports. Lazy parsing is required.