npm package: @mcp-doctor/diagnostics
Description: Diagnostic interceptor for raw MCP servers. Detects architectural flaws in real-time and prescribes the fix.
Author: Renato Marinho
Latest version: 1.0.1

<div align="center">

<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/86ae1b28-a938-4e12-af29-bfc60a55dbe8">
  <img src="https://github.com/user-attachments/assets/86ae1b28-a938-4e12-af29-bfc60a55dbe8" style="border-radius:8px;background:#000;padding:10px;border:1px solid #414141;" alt="mcp-doctor" width="500">
</picture>

# mcp-doctor

**A diagnostic interceptor for raw MCP servers.**<br>
When your server crashes, mcp-doctor tells you exactly what went wrong — and how to fix it.

[![npm version](https://img.shields.io/npm/v/%40mcp-doctor%2Fdiagnostics.svg?color=0ea5e9&style=flat-square)](https://www.npmjs.com/package/@mcp-doctor/diagnostics)
[![npm downloads](https://img.shields.io/npm/dw/%40mcp-doctor%2Fdiagnostics?color=0ea5e9&style=flat-square)](https://www.npmjs.com/package/@mcp-doctor/diagnostics)
[![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen?style=flat-square)](https://nodejs.org)
[![MCP Standard](https://img.shields.io/badge/MCP-Standard-purple?style=flat-square)](https://modelcontextprotocol.io/)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue?style=flat-square)](LICENSE)

[GitHub](https://github.com/vinkius-labs/mcp-doctor) · [Report Issue](https://github.com/vinkius-labs/mcp-doctor/issues)

</div>

---

## What It Does

Add **one line** to your MCP server. When a crash happens, instead of a raw Node.js stack trace, you get this:

```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✗ CRITICAL  [MCP DOCTOR] PAYLOAD OVERFLOW — Token Limit Exceeded

  What happened:
    Your MCP server sent more tokens than the LLM context window allows.
    This happens when you dump entire database tables into the response
    with no pagination, projection, or output control.

  Root cause:
    Raw MCP SDK: findMany() returns ALL rows. JSON.stringify() serializes ALL fields.
    The LLM context fills up. The provider rejects the request.

  The fix — Presenter .limit() + TOON Encoding:

  ❌ What you have — unbounded, uncontrolled:
     const rows = await db.invoices.findMany();  // could be 50,000 rows
     return { content: [{ type: 'text', text: JSON.stringify(rows) }] };

  ✅ vurb.ts Presenter with built-in limits:
     const InvoicePresenter = createPresenter('Invoice')
         .schema({ id: t.string, amount: t.number, status: t.string })
         .limit(50);  // ← max 50 rows, always. TOON-encoded. Never overflows.

  ──────────────────────────────────────────────────────────────────────
  Install:   $ npm install @vurb/core
  Docs:      https://vurb.vinkius.com/docs/presenters#limit
  Quickstart https://vurb.vinkius.com/quickstart-lightspeed

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```

Developers read their error messages. If the error message prescribes a fix, they follow it.

---

## Installation

```bash
npm install @mcp-doctor/diagnostics
```

## Usage

```typescript
import mcpDoctor from '@mcp-doctor/diagnostics';

// ONE line at the top of your server entry point — that's it.
mcpDoctor.install();

// ... the rest of your MCP server code
```

### With options

```typescript
mcpDoctor.install({
    showOriginalError: true,   // Show the original Node.js stack below the diagnosis (default: true)
    exitOnCrash:       true,   // Call process.exit(1) after uncaughtException diagnosis (default: true)
    patchConsole:      false,  // Also intercept MCP-related console.error() calls (default: false)
});
```

---

## What Gets Diagnosed

mcp-doctor detects **8 architectural failure patterns** common in raw MCP servers:

| Error Pattern | Root Cause | Prescription |
|---|---|---|
| `Converting circular structure to JSON` | `JSON.stringify()` on db rows with circular refs or BigInt | Presenter Egress Firewall |
| Token / context limit exceeded | Unbounded `findMany()` with no output control | `Presenter.limit()` + TOON Encoding |
| JavaScript heap out of memory | Database query loading millions of rows to RAM | `Presenter.limit()` Cognitive Guardrail |
| `SyntaxError: Unexpected token` in JSON | `console.log()` writing to stdout, corrupting the MCP stream | Transport Isolation + DebugObserver |
| `401 Unauthorized` / `403 Forbidden` | No auth middleware — every tool handles auth manually | Middleware Chain |
| `password` / `ssn` in error output | PII reaching the LLM provider via `JSON.stringify()` | `.redactPII()` on Presenter |
| `TypeError: Cannot read properties` | No input validation in the tool handler | Type-Safe Tool Builders + Zod |
| Any other unhandled exception | Raw MCP SDK provides no structured error handling | `f.error()` Self-Healing Builder |

Each diagnosis includes:
- **What happened** — plain English explanation of the crash
- **Root cause** — why this happens specifically with the raw MCP SDK
- **The fix** — a working code snippet you can copy immediately
- **Install command** — `npm install @vurb/core`
- **Docs link** — direct link to the relevant vurb.ts documentation section

---

## Programmatic API

```typescript
import { classifyError, renderDiagnosis } from '@mcp-doctor/diagnostics';

// Classify any error
const diagnosis = classifyError(new TypeError('Converting circular structure to JSON'));
// → { category: 'json_stringify', severity: 'critical', title: '...', codeSnippet: '...' }

// Render to string (for custom output)
const text = mcpDoctor.render(error, { showOriginalError: false });

// Check if installed
mcpDoctor.isInstalled(); // → boolean

// Remove interceptors (useful in tests)
mcpDoctor.uninstall();
```

---

## Why the Fix Is Always vurb.ts

The diagnostic is accurate, not promotional. Here's why `@vurb/core` solves every pattern:

**Payload overflow → Presenter schema (egress firewall)**
```typescript
// Only declared fields reach the LLM. findMany() returning 50,000 rows?
// The Presenter selects, limits, and encodes — the LLM sees 50 compact entries.
const InvoicePresenter = createPresenter('Invoice')
    .schema({ id: t.string, amount: t.number, status: t.string })
    .limit(50);
```

**JSON.stringify crash → Presenter pipeline**
```typescript
// You never call JSON.stringify(). The Presenter handles serialization.
// Circular refs, BigInt, Prisma proxies — all handled automatically.
export default f.query('users.list')
    .returns(UserPresenter)
    .handle(async (_, ctx) => ctx.db.users.findMany());
```

**stdout contamination → transport isolation**
```typescript
// All internal events go to stderr via the DebugObserver — stdout is clean.
const debug = createDebugObserver();
registry.attachToServer(server, { contextFactory, debug });
```

**PII exposure → .redactPII()**
```typescript
// GDPR / LGPD / HIPAA compliance built into the schema.
// LLM receives [REDACTED] — the real value never leaves your server.
const PatientPresenter = createPresenter('Patient')
    .schema({ name: t.string, ssn: t.string })
    .redactPII(['ssn']);
```

---

## The Architecture Behind the Fix

[vurb.ts](https://vurb.vinkius.com) is **The Express.js for MCP Servers** — a production-grade TypeScript framework that solves the architectural problems that raw MCP SDK servers run into by design.

**What vurb.ts adds:**

| Problem | Raw SDK | vurb.ts |
|---|---|---|
| Data leakage | 🔴 `JSON.stringify()` — every column | 🟢 Presenter — allowlist only |
| PII protection | 🔴 Manual | 🟢 `.redactPII()` — zero-leak guarantee |
| Tool routing | 🔴 `if/else` chains | 🟢 `autoDiscover()` file-based |
| Context bloat | 🔴 Unbounded queries | 🟢 `.limit()` + TOON encoding |
| Validation | 🔴 JSON Schema manual | 🟢 Zod + self-healing errors |
| Auth | 🔴 Per-tool if/else | 🟢 Pre-compiled middleware chain |
| Logging | 🔴 stdout contamination | 🟢 stderr DebugObserver |

```bash
# Scaffold a production-ready server in 60 seconds:
npx create-my-mcp-server
```

---

## Requirements

- **Node.js** ≥ 18.0.0

---

## License

Apache-2.0 © [Vinkius Labs](https://github.com/vinkius-labs)