<< All versions

Skill v1.0.1

Automated scan100/100
cyanheads/mcp-ts-core/api-config

+4 new

──Details
PublishedAugust 9, 2026 at 09:58 PM
Content Hashsha256:1adfddd51b57f509...
Git SHA7fc128dbb56a
Bump Typepatch
Compare with v1.0.0
──Files
Files (1 file, 15.4 KB)
SKILL.md15.4 KBactive
SKILL.md · 265 lines · 15.4 KB

version: "1.0.1" name: api-config description: > 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. metadata: author: cyanheads version: "1.9" audience: external type: reference


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/title/websiteUrl/description/icons options 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; createApp({ description }) wins when set
PACKAGE_NAMEpkg.namepackage.json nameRarely needed
PACKAGE_VERSIONpkg.versionpackage.json versionRarely needed

SDK identity fields (API-only, no env var equivalent — passed to createApp() / createWorkerHandler(), forwarded to initialize and /.well-known/mcp.json):

OptionTypeNotes
titlestring?Human-readable display name shown in client listings
websiteUrlstring?Canonical homepage / repository URL
descriptionstring?One-line description; wins over MCP_SERVER_DESCRIPTION when set
iconsImplementation['icons']?Array of icon objects: `{ src, mimeType?, sizes?: string[], theme?: 'light'\'dark' }`
NODE_ENVenvironmentdevelopmentAliases: dev→development, prod→production, test→testing
MCP_LOG_LEVELlogLeveldebugAliases: warn→warning, err→error, fatal/silent→emerg, trace→debug, information→info
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_BODY_BYTESmcpHttpMaxBodyBytes1048576 (1 MiB)Max inbound JSON-RPC request body; oversized requests get 413 before per-request allocation. Does not cap upstream data staged into a canvas or response sizes. 0 disables (defer to runtime/proxy).
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_ORIGINSmcpAllowedOrigins—Comma-separated list; omit to allow all
MCP_SERVER_RESOURCE_IDENTIFIERmcpServerResourceIdentifier—RFC 8707 resource indicator URL
MCP_PUBLIC_URLmcpPublicUrl—Public-facing origin for reverse proxies (Cloudflare Tunnel, nginx, ALB) so emitted URLs carry the correct scheme
MCP_HEARTBEAT_INTERVAL_MSmcpHeartbeatIntervalMs0 (disabled)Heartbeat ping interval; 0 disables
MCP_HEARTBEAT_MISS_THRESHOLDmcpHeartbeatMissThreshold3Missed heartbeats before session is considered stale
MCP_GC_PRESSURE_INTERVAL_MSmcpGcPressureIntervalMs0 (disabled)Bun-only opt-in forced GC loop for HTTP deployments with heap growth

Auth

Env VarAppConfig fieldDefaultNotes
MCP_AUTH_MODEmcpAuthModenonenone \jwt \oauth
MCP_AUTH_SECRET_KEYmcpAuthSecretKey—Required for jwt mode; min 32 chars
MCP_AUTH_DISABLE_SCOPE_CHECKSmcpAuthDisableScopeChecksfalseWhen true, bypasses both withRequiredScopes (declared auth: [...]) and checkScopes (runtime/tenant scopes). Token validation (sig/aud/iss/exp) intact. Logs a WARNING at startup. See api-auth skill.
OAUTH_ISSUER_URLoauthIssuerUrl—Required for oauth mode
OAUTH_AUDIENCEoauthAudience—Required for oauth mode
OAUTH_JWKS_URIoauthJwksUri—Override 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
MCP_JWT_EXPECTED_ISSUERmcpJwtExpectedIssuer—Optional issuer validation for JWT mode
MCP_JWT_EXPECTED_AUDIENCEmcpJwtExpectedAudience—Optional audience validation for JWT mode
DEV_MCP_CLIENT_IDdevMcpClientId—Dev-only: override client ID
DEV_MCP_SCOPESdevMcpScopes—Dev-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

Canvas (DataCanvas primitive — Tier 3, optional peer dep @duckdb/node-api)

Env VarAppConfig fieldDefaultNotes
CANVAS_PROVIDER_TYPEcanvas.providerTypenonenone \duckdb. Set to duckdb to enable core.canvas. Fails closed on Cloudflare Workers (DuckDB has no V8-isolate build).
CANVAS_DEFAULT_MEMORY_LIMIT_MBcanvas.defaultMemoryLimitMb1024Per-canvas DuckDB memory_limit PRAGMA value, in MB.
CANVAS_EXPORT_PATHcanvas.exportRootPath./.canvas-exportsSandbox root for path-targeted exports. Absolute paths and .. traversal are rejected.
CANVAS_TEMP_PATHcanvas.tempRootPath<os.tmpdir()>/mcp-canvasScratch root: DuckDB's temp_directory for queries that spill past memory_limit, plus the transient files behind stream exports and the spillover round-trip. Never resolves to the process cwd — DuckDB's own cwd-relative .tmp default fails on a non-root or read-only container rootfs.
CANVAS_MAX_CANVASES_PER_TENANTcanvas.maxCanvasesPerTenant100Active canvas cap per tenant; throws RateLimited when exceeded.
CANVAS_TTL_MScanvas.ttlMs86400000Sliding TTL (24 h). Every operation extends the expiry.
CANVAS_ABSOLUTE_CAP_MScanvas.absoluteCapMs604800000Absolute cap from creation (7 d). Sliding window clamps to this.
CANVAS_SWEEPER_INTERVAL_MScanvas.sweeperIntervalMs60000Background sweep interval. Set to 0 to disable.
CANVAS_DEFAULT_ROW_LIMITcanvas.defaultRowLimit10000Default cap on rows materialized into a query response.
CANVAS_SCHEMA_SNIFF_ROWScanvas.schemaSniffRows100Rows to materialize for schema inference when schema is omitted.

Platform support: Linux/macOS/Windows × x64 supported, Linux/macOS arm64 supported. Windows arm64 unsupported (DuckDB upstream). See api-canvas skill for the full DataCanvas reference.

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_KEYopenrouterApiKey—Optional; 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_TEMPERATUREllmDefaultTemperature—Float
LLM_DEFAULT_TOP_PllmDefaultTopP—Float
LLM_DEFAULT_MAX_TOKENSllmDefaultMaxTokens—Integer
LLM_DEFAULT_TOP_KllmDefaultTopK—Integer
LLM_DEFAULT_MIN_PllmDefaultMinP—Float

Telemetry

Env VarAppConfig fieldDefaultNotes
OTEL_ENABLEDopenTelemetry.enabledfalseEnable OpenTelemetry export
OTEL_SERVICE_NAMEopenTelemetry.serviceNamecreateApp name → package.json nameSeeded from createApp({ name }) when unset; an env value wins
OTEL_SERVICE_VERSIONopenTelemetry.serviceVersionpackage.json version
OTEL_EXPORTER_OTLP_TRACES_ENDPOINTopenTelemetry.tracesEndpoint—OTLP traces endpoint URL
OTEL_EXPORTER_OTLP_METRICS_ENDPOINTopenTelemetry.metricsEndpoint—OTLP 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/memory→in-memory, persistent→storage
TASK_STORE_TENANT_IDtasks.tenantIdsystem-tasksTenant ID for task state storage
TASK_STORE_DEFAULT_TTL_MStasks.defaultTtlMs—TTL 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.apiKey—Provider API key
SPEECH_TTS_BASE_URLspeech.tts.baseUrl—Override provider base URL
SPEECH_TTS_DEFAULT_VOICE_IDspeech.tts.defaultVoiceId—Default voice identifier
SPEECH_TTS_DEFAULT_MODEL_IDspeech.tts.defaultModelId—Default model identifier
SPEECH_TTS_TIMEOUTspeech.tts.timeout—Request 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.apiKey—Provider API key
SPEECH_STT_BASE_URLspeech.stt.baseUrl—Override provider base URL
SPEECH_STT_DEFAULT_MODEL_IDspeech.stt.defaultModelId—Default model identifier
SPEECH_STT_TIMEOUTspeech.stt.timeout—Request 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.

ts
// 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),
verboseLogging: z.stringbool().default(false).describe('Enable verbose logging'),
});
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',
verboseLogging: 'MY_VERBOSE_LOGGING',
});
return _config;
}

Env booleans — use `z.stringbool()`, never `z.coerce.boolean()`. z.coerce.boolean() runs Boolean(value), so "false", "0", and "no" all coerce to true — the flag becomes impossible to disable through the environment except by omitting it entirely. z.stringbool() parses true/false/1/0/yes/no/on/off (case-insensitive) and rejects anything else, so MY_VERBOSE_LOGGING=false actually disables and a typo fails loudly at startup instead of silently coercing. Empty string and unset both fall through to .default().

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.

← v1.0.0All versionsv1.0.2 →