mctxdocs
Configuration

Server Logs

Real-time log streaming architecture on mctx. How logs are captured, WebSocket streaming, console output, and no retention policy.

mctx provides real-time log streaming so developers can debug and monitor their MCP servers. Logs stream live via WebSocket with zero configuration.

Architecture

Real-time logging uses two components:

ComponentPurposeTechnology
Tail WorkerCaptures console outputDispatch namespace tail consumer
LogStream Durable ObjectMaintains WebSocket connectionsCloudflare Durable Object

Log Flow

  1. Tenant worker calls console.log() during request handling
  2. Tail worker receives log event from dispatch namespace
  3. Tail worker extracts tenant slug from X-Tenant-Slug header
  4. Tail worker POSTs batched logs to LogStream DO for that slug
  5. LogStream DO broadcasts to all connected WebSocket clients
  6. Dashboard modal displays logs in real-time

Header Injection

The dispatch worker adds X-Tenant-Slug header when forwarding to tenant workers:

// Dispatch worker
const tenantRequest = new Request(request, {
  headers: {
    ...request.headers,
    "X-Tenant-Slug": slug,
  },
});

This allows the tail worker to associate logs with the correct server.

What Gets Logged

Any console.* output from your server code:

MethodLevelUse Case
console.log()InfoGeneral operational events
console.info()InfoInformational messages
console.warn()WarnWarnings and potential issues
console.error()ErrorErrors and failures
console.debug()DebugDetailed debugging info

Example

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    console.log(`[INFO] ${request.method} ${request.url}`);

    try {
      const result = await processRequest(request, env);
      console.log(`[INFO] Request processed successfully`);
      return result;
    } catch (error) {
      console.error(`[ERROR] Request failed: ${error.message}`);
      throw error;
    }
  },
};

Log Format

Logs are captured with metadata:

interface LogEntry {
  timestamp: number; // Unix timestamp in milliseconds
  level: "log" | "info" | "warn" | "error" | "debug";
  message: string; // The logged message
}

Dashboard Display

Logs appear in the dashboard modal with:

  • Timestamp - Local time (HH:MM:SS.mmm)
  • Level badge - Color-coded (info/warn/error)
  • Message - Full log message

Auto-Scroll

The log viewer auto-scrolls to the latest message. Scrolling up pauses auto-scroll until you scroll to the bottom again.

Accessing Logs

Via Dashboard

  1. Navigate to server detail page (/dev/servers/[id])
  2. Click View Logs button in Server Information section
  3. Modal opens showing real-time log stream
  4. Trigger requests to see logs appear

Requirement: Server must be in Active status. Logs are not available for inactive servers.

WebSocket Connection

The dashboard connects via WebSocket:

wss://workers.mctx.ai/log-stream/{slug}

Authentication: Requires valid session cookie from authenticated user who owns the server.

Connection Lifecycle

  1. Connect - Open WebSocket connection to LogStream DO
  2. Subscribe - LogStream DO registers client for that server slug
  3. Stream - Receive log entries as they arrive
  4. Disconnect - Close modal, WebSocket disconnects

No Retention

Critical: Logs are not stored. They are ephemeral and only visible while streaming.

Implications:

  • Cannot view past logs after closing modal
  • Cannot search historical logs
  • Cannot export logs
  • No log retention policy

For persistent logging: Integrate with external logging service (Datadog, New Relic, Logtail, etc.) by sending logs from your server code.

Log Streaming Best Practices

Use Structured Prefixes

// Consistent prefix format
console.log(`[INFO] Tool called: ${toolName}`);
console.log(`[DEBUG] Parameters: ${JSON.stringify(params)}`);
console.log(`[WARN] Rate limit approaching: ${remaining} requests left`);
console.log(`[ERROR] API call failed: ${error.message}`);

Log Important Events

// Request lifecycle
console.log(`[INFO] ${request.method} request received`);
console.log(`[INFO] Tool: ${toolName}, Params: ${JSON.stringify(params)}`);
console.log(`[INFO] External API call: ${apiUrl}`);
console.log(`[INFO] Response sent: ${response.status}`);

// Errors
console.error(`[ERROR] Failed to parse request: ${error.message}`);
console.error(
  `[ERROR] API returned ${response.status}: ${await response.text()}`,
);

Avoid Logging Secrets

// ❌ Bad - Exposes secret
console.log(`Using API key: ${env.API_KEY}`);

// ✅ Good - Safe reference
console.log(`Using API key: ${env.API_KEY.slice(0, 4)}...`);

Add Timing Information

const start = Date.now();
const result = await callExternalApi(query, env);
const duration = Date.now() - start;
console.log(`[INFO] API call completed in ${duration}ms`);

Log Level Guidelines

LevelWhen to Use
DEBUGDetailed information for troubleshooting
INFONormal operational events
WARNSomething unexpected but not an error
ERRORError conditions that should be investigated

Troubleshooting with Logs

Request Not Reaching Server

Symptoms: No logs appear when request should be processed

Check:

  1. Is the log modal open? (Logs only stream while connected)
  2. Is request going to correct version endpoint?
  3. Is authentication working? (Check for auth errors in browser console)

Solution: Verify endpoint URL and ensure OAuth token is valid.

Request Processing Errors

Symptoms: Error logs appearing

Check:

  1. Error message content
  2. Stack trace (if logged)
  3. Which tool or operation failed

Example:

try {
  const data = await fetchData(url, env.API_KEY);
} catch (error) {
  console.error(`[ERROR] Fetch failed: ${error.message}`);
  console.error(`[ERROR] URL: ${url}`);
  console.error(`[ERROR] Stack: ${error.stack}`);
  throw error;
}

Slow Responses

Symptoms: Requests take too long

Check: Add timing logs to identify bottlenecks

console.log(`[INFO] Request started`);
const t1 = Date.now();

const data = await fetchExternalApi(query);
console.log(`[INFO] External API: ${Date.now() - t1}ms`);

const result = await processData(data);
console.log(`[INFO] Processing: ${Date.now() - t1}ms total`);

Missing Environment Variables

Symptoms: Code fails when accessing env.*

Check: Log environment variable presence (not values!)

console.log(`[DEBUG] API_KEY present: ${!!env.API_KEY}`);
console.log(`[DEBUG] DATABASE_URL present: ${!!env.DATABASE_URL}`);

if (!env.API_KEY) {
  console.error(`[ERROR] API_KEY not configured`);
  throw new Error("Server misconfigured");
}

Log Volume Considerations

CPU Time Limit

Each request has a 10,000ms (10 second) CPU time limit. Excessive logging can contribute to this limit.

Impact of logging:

  • console.log() is fast but not free
  • Logging large objects or long strings is more expensive
  • Hundreds of log calls per request can impact CPU time

Best Practice: Log important events, not every variable assignment.

Batching

The tail worker batches logs before POSTing to LogStream DO:

  • Batch size: Up to 100 log entries
  • Batch interval: 1 second maximum
  • Purpose: Reduce number of DO requests

Limitations

LimitValueNotes
Log retention0No retention, real-time only
Maximum message size10 KBPer log entry
Maximum concurrent viewers10Per server
WebSocket connection timeout5 minutesIdle connections are closed

Exceeding limits:

  • Messages over 10 KB are truncated
  • 11th viewer cannot connect until slot opens
  • Idle connections (no activity for 5 min) are closed

Security

Authentication

Log access requires:

  1. Valid session cookie (signed JWT)
  2. User owns the server (developer relationship)
  3. Server is in active status

Unauthorized access: Returns 401 or 403 error.

Isolation

Logs are isolated per server slug. You cannot access logs from servers you don't own.

Encryption

WebSocket connections use TLS (WSS protocol).

Comparison with CloudFlare Logs

Featuremctx LogsCloudflare Logs
Real-time streaming✅ Yes✅ Yes (via wrangler tail)
Retention❌ No❌ No (Logpush for retention)
Dashboard integration✅ Yes❌ No
Authentication✅ Automatic⚠️ Requires CF token
Filtering❌ No✅ Yes

mctx logs are built for quick debugging in the dashboard. For advanced logging needs, consider Cloudflare Logpush or external services.

External Logging Integration

For persistent logs, send from your server code to external services:

Example: HTTP Logging Endpoint

async function logToExternal(message: string, level: string, env: Env) {
  await fetch("https://logs.example.com/ingest", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${env.LOGGING_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      timestamp: Date.now(),
      level,
      message,
      server: "my-mcp-server",
    }),
  });
}

// Use in your handler
console.log(`[INFO] Processing request`); // Still shows in mctx
await logToExternal("Processing request", "info", env); // Persisted externally
  • Datadog - Full observability platform
  • New Relic - Application performance monitoring
  • Logtail - Simple log aggregation
  • Axiom - High-volume log analytics
  • Better Stack - Developer-focused logging

See Also


See something wrong? Report it or suggest an improvement — your feedback helps make these docs better.