On this page3 sections
Transport & Observability
mcp-rune supports both MCP transports from the same server factory — your tools, prompts, and apps work identically whether they run locally over stdio or remotely over HTTP. Production-grade logging, tracing, and error tracking are wired into the framework via vendor-agnostic facades so you can swap adapters without changing call sites.
Dual transport
Use StdioServer for local development (the host process spawns mcp-rune as a child — Claude Desktop, Cursor, VS Code):
import { StdioServer } from '@mcp-rune/mcp-rune/server'
new StdioServer({
accessToken: process.env.ACCESS_TOKEN,
mcp: mcpConfig
}).start()import { StdioServer } from '@mcp-rune/mcp-rune/server'
new StdioServer({
accessToken: process.env.ACCESS_TOKEN,
mcp: mcpConfig
}).start()Use HttpServer for remote, multi-user deployments protected by OAuth:
import { HttpServer } from '@mcp-rune/mcp-rune/server'
new HttpServer({
port: 4100,
oauth,
mcp: mcpConfig
}).start()import { HttpServer } from '@mcp-rune/mcp-rune/server'
new HttpServer({
port: 4100,
oauth,
mcp: mcpConfig
}).start()Both servers accept the same mcp configuration. Tool/prompt/app registries are transport-agnostic; the only reason to branch on transport is when you wire surface-level concerns like rate limiting (HttpServer only) or token sources (StdioServer uses an env var, HttpServer uses the OAuth flow).
See the OAuth 2.0 Discovery guide for the auth story attached to HttpServer.
Observability
Five built-in capabilities cover the production basics:
- Structured logging — Winston with JSON and text formats, daily file rotation when
LOG_FILE_ENABLED=true. At bootstrap the logger honorsLOG_LEVEL,LOG_FORMAT,LOG_FILE_FORMAT,LOG_FILE_ENABLED, andNODE_ENVdirectly (the usual convention for a logging subsystem, soLOG_LEVEL=debug npx tsx server.tsjust works). To bring these under your validated config object instead, spreadframeworkConfigSchema(from@mcp-rune/mcp-rune/core) into your schema and callconfigureLogging({ ...config.logging, production: config.runtime.environment === 'production' })afterloadConfig()— injected values then supersede the env. See Config schema. - Distributed tracing — vendor-agnostic facade with a Langfuse adapter included. Wire a different backend by implementing the tracing interface.
- Error tracking — same facade pattern, with a Sentry adapter included.
- Request ID correlation —
X-Request-IDflows across all services so you can stitch together a single request across logs, traces, and errors. - Embeddings — local
all-MiniLM-L6-v2model for semantic search; no network round-trip and no per-call cost. Optional, used by the analysis layer.
import { logger, tracing, errorTracking } from '@mcp-rune/mcp-rune/runtime'
logger.info('request received', { requestId, model: 'book' })
await tracing.traceToolCall('create_book', input, async () => createBook(input))
errorTracking.captureException(err, { tags: { tool: 'create_model' } })import { logger, tracing, errorTracking } from '@mcp-rune/mcp-rune/runtime'
logger.info('request received', { requestId, model: 'book' })
await tracing.traceToolCall('create_book', input, async () => createBook(input))
errorTracking.captureException(err, { tags: { tool: 'create_model' } })See database-reference.md for the full environment-variable reference covering log level, log format, and database options.
Config schema
loadConfig(schema) (from @mcp-rune/mcp-rune/core) reads env vars through one declarative schema, collects all validation errors at once, masks sensitive values in toString(), and returns a frozen config object. The framework reads no env vars of its own for app/business config — you inject everything via constructors — with one deliberate exception: the logger.
The logger honors the standard logging env vars at bootstrap (so logging works before any config is loaded, and LOG_LEVEL=debug behaves as expected). To fold those vars into your validated config instead, spread frameworkConfigSchema and hand the result to configureLogging() — the injected values then supersede the bootstrap, and there is no silent env fallback for anything you declared:
import { loadConfig, frameworkConfigSchema } from '@mcp-rune/mcp-rune/core'
import { logger } from '@mcp-rune/mcp-rune/runtime'
export const config = loadConfig({
...frameworkConfigSchema, // logging.{level,format,fileFormat,fileEnabled}, runtime.environment
api: { url: { env: 'API_URL', default: 'http://localhost:4001' } }
// ...rest of your app schema
})
logger.configureLogging({
...config.logging,
production: config.runtime.environment === 'production'
})import { loadConfig, frameworkConfigSchema } from '@mcp-rune/mcp-rune/core'
import { logger } from '@mcp-rune/mcp-rune/runtime'
export const config = loadConfig({
...frameworkConfigSchema, // logging.{level,format,fileFormat,fileEnabled}, runtime.environment
api: { url: { env: 'API_URL', default: 'http://localhost:4001' } }
// ...rest of your app schema
})
logger.configureLogging({
...config.logging,
production: config.runtime.environment === 'production'
})