# FastMCP Logging Rules
## ❌ CRITICAL: What NOT to do
**NEVER use these during MCP protocol operations:**
- ❌ `console.log()`
- ❌ `console.debug()`
- ❌ `console.info()`
- ❌ `console.warn()`
- ❌ `console.error()`
- ❌ `process.stdout.write()`
- ❌ `process.stderr.write()`
**Why:** MCP uses stdio transport. Any output to stdout/stderr becomes part of the JSON-RPC protocol and causes parsing errors like:
```
Client error for command Unexpected token 'F', "[FastMCP Da"... is not valid JSON
```
## ✅ CORRECT: What TO do
### 1. Server Startup Logging (Before MCP Protocol)
⚠️ **NEW FINDING (2025-06-17)**: Empirical testing shows that **ANY** `console.*` or direct stdout/stderr output – _even before_ calling `server.start()` – can be captured by the MCP client **and will break the JSON-RPC handshake**. Therefore, the safest rule is:
> **NEVER use `console.*`, `process.stdout.write`, or `process.stderr.write` at any point in an MCP process.**
If you must capture early-startup diagnostics, write them to a temporary log file instead:
```typescript
import { appendFileSync } from "fs";
appendFileSync("startup.log", "Server initializing...\n");
await server.start(); // JSON-RPC starts immediately after process launch
```
All runtime logging (including startup information you want surfaced to the client) should go through `context.log.*` inside tool execution.
### 2. Runtime Logging (During MCP Operations)
**ONLY** use FastMCP's context.log inside tool execution:
```typescript
server.addTool({
name: "my-tool",
execute: async (args, { log }) => {
// ✅ SAFE: Uses FastMCP context.log
log.info("Processing request", { userId: args.userId });
log.debug("Fetching data", { endpoint: "/api/data" });
log.warn("Rate limit approaching", { remaining: 10 });
log.error("Failed to process", { error: "Network timeout" });
return result;
},
});
```
### 3. FastMCP Context.log Signature
```typescript
log.debug(message: string, data?: SerializableValue)
log.info(message: string, data?: SerializableValue)
log.warn(message: string, data?: SerializableValue)
log.error(message: string, data?: SerializableValue)
```
**Separate message from data:**
- ✅ `log.info("User authenticated", { userId: 123, method: "oauth" })`
- ❌ `log.info("User 123 authenticated via oauth")` (harder to parse)
## 🛠️ Implementation Rules
### Server Logger Class
```typescript
export class ServerLogger {
// ✅ ONLY for startup logging (before MCP protocol)
static startup = {
debug: (message: string) => {
if (process.env.DEBUG === "true") {
console.debug(`[Startup] ${message}`);
}
},
info: (message: string) => {
if (process.env.DEBUG === "true") {
console.debug(`[Startup] ${message}`);
}
},
error: (message: string) => {
// Errors can go to stderr during startup only
console.error(`[Startup Error] ${message}`);
},
};
// ❌ NO runtime logging methods - use context.log only
}
```
### Tool Implementation
```typescript
server.addTool({
name: "get-balance",
execute: async (args, { log }) => {
try {
log.info("Fetching wallet balance", {
wallet: args.wallet,
chain: args.chain,
});
const result = await apiClient.getBalance(args.wallet, args.chain);
log.debug("Balance retrieved successfully", {
balance: result.balance,
currency: result.currency,
});
return result;
} catch (error) {
log.error("Failed to fetch balance", {
error: error.message,
wallet: args.wallet,
});
throw error;
}
},
});
```
## 🔍 Debugging Guidelines
### Environment Variables
- `DEBUG=true` in MCP config → Enables startup logging only
- `DATAI_DEBUG_TRUNCATE=true` → Truncate large responses
- `DATAI_LIMIT=1` → Limit API responses for testing
### MCP Logs vs Console
- **Console**: Only startup logs (before MCP protocol)
- **MCP Logs**: Only context.log outputs (during tool execution)
- **No mixing**: Never console.\* during MCP operations
### Error Diagnosis
If you see `Unexpected token` errors in MCP logs:
1. Check for any `console.*` calls after `server.start()`
2. Check for `process.stdout.write()` or `process.stderr.write()`
3. Ensure all runtime logging uses `context.log`
## 📋 Checklist for New Code
Before adding any logging:
- [ ] Is this during server startup? → Use `ServerLogger.startup.*`
- [ ] Is this during tool execution? → Use `context.log.*`
- [ ] Is this runtime server code? → NO logging (or use context.log if available)
- [ ] Does message contain sensitive data? → Put in `data` parameter
- [ ] Is the message human-readable? → Separate from structured data
## 🚫 Common Mistakes
1. **Logging in API client during MCP operations**
```typescript
// ❌ BAD
async function apiCall() {
console.log("Making API call"); // Breaks MCP protocol
}
// ✅ GOOD
async function apiCall(log?: FastMCPLog) {
log?.debug("Making API call", { endpoint: "/api/data" });
}
```
2. **Logging errors outside tool context**
```typescript
// ❌ BAD
server.onError((error) => {
console.error("Server error:", error); // Breaks MCP protocol
});
// ✅ GOOD
// Let FastMCP handle errors internally, or log only during startup
```
3. **Mixed logging approaches**
```typescript
// ❌ BAD
if (DEBUG) console.log("Debug info"); // During MCP operations
// ✅ GOOD
log?.debug("Debug info", { context: "additional data" });
```
## 📝 Summary
**Golden Rule**: After `server.start()` is called, NEVER use `console.*` or write to stdout/stderr directly. Only use FastMCP's `context.log` inside tool execution contexts.
This ensures clean MCP protocol communication and proper log routing to MCP clients.