MCP Server Documentation: OpenAPI, TypeDoc & Interactive Examples

Great documentation is the difference between an MCP server that gets adopted and one that gets ignored. When developers evaluate your ChatGPT app, they spend 90% of their time reading docs before writing a single line of code. Poor documentation means frustrated developers, support tickets, and abandoned integrations.

This guide shows you how to create production-grade documentation for your MCP server using OpenAPI specifications, TypeDoc comments, interactive examples, and API playgrounds. We'll cover automated doc generation, validation testing, and developer onboarding workflows that reduce time-to-first-integration from hours to minutes.

Why MCP Documentation Matters: ChatGPT apps expose complex APIs through the Model Context Protocol. Without clear documentation, developers can't understand your tool schemas, authentication flows, or error handling patterns. OpenAI's app approval team explicitly reviews documentation quality during submission—incomplete docs are a common rejection reason.

What You'll Learn: By the end of this article, you'll have a complete documentation system that auto-generates OpenAPI specs from your code, validates examples against live endpoints, and provides interactive playgrounds for testing. You'll implement TypeDoc annotations that turn comments into searchable reference docs, create runnable code snippets that verify themselves, and build onboarding guides that get developers productive in under 10 minutes.

Let's build documentation that developers love.

OpenAPI Specification for MCP Servers

OpenAPI (formerly Swagger) provides a standardized format for describing RESTful APIs. For MCP servers, this means machine-readable schemas that document every tool, parameter, and response type. Auto-generated from your code, validated against live endpoints, and consumed by interactive API explorers.

Why OpenAPI for MCP: The Model Context Protocol uses JSON-RPC over HTTP, but your MCP server likely exposes REST endpoints for authentication, webhooks, and health checks. OpenAPI documents these endpoints with:

  • Tool schemas: Input parameters, validation rules, required fields
  • Response formats: Success payloads, error codes, example values
  • Authentication flows: OAuth 2.1, API keys, custom JWT patterns
  • Widget templates: Structured content schemas for ChatGPT display

Key Benefits:

  1. Auto-generated client SDKs: Tools like OpenAPI Generator create type-safe clients
  2. Interactive documentation: Swagger UI lets developers test endpoints in-browser
  3. Contract testing: Validate implementations against specs automatically
  4. Version management: Track API changes, deprecations, breaking updates

Here's a production-ready OpenAPI generator for MCP servers:

// openapi-generator.ts - Auto-generate OpenAPI 3.1 spec from MCP tools
import { OpenAPIV3_1 as OpenAPI } from 'openapi-types';
import { MCPServer, Tool } from '@modelcontextprotocol/sdk';
import { z } from 'zod';
import * as fs from 'fs';
import * as yaml from 'js-yaml';

interface MCPOpenAPIConfig {
  server: MCPServer;
  title: string;
  version: string;
  baseUrl: string;
  contactEmail: string;
  license?: { name: string; url: string };
}

export class OpenAPIGenerator {
  private spec: OpenAPI.Document;

  constructor(private config: MCPOpenAPIConfig) {
    this.spec = this.initializeSpec();
  }

  private initializeSpec(): OpenAPI.Document {
    return {
      openapi: '3.1.0',
      info: {
        title: this.config.title,
        version: this.config.version,
        description: 'Auto-generated OpenAPI spec for MCP server',
        contact: { email: this.config.contactEmail },
        license: this.config.license || {
          name: 'MIT',
          url: 'https://opensource.org/licenses/MIT'
        }
      },
      servers: [
        {
          url: this.config.baseUrl,
          description: 'Production MCP server'
        }
      ],
      paths: {},
      components: {
        schemas: {},
        securitySchemes: {
          OAuth2: {
            type: 'oauth2',
            flows: {
              authorizationCode: {
                authorizationUrl: `${this.config.baseUrl}/oauth/authorize`,
                tokenUrl: `${this.config.baseUrl}/oauth/token`,
                scopes: {
                  'read:tools': 'Access MCP tools',
                  'write:data': 'Modify user data'
                }
              }
            }
          }
        }
      }
    };
  }

  public addTool(tool: Tool): void {
    const path = `/tools/${tool.name}`;
    const schema = this.zodToOpenAPI(tool.inputSchema);

    this.spec.paths[path] = {
      post: {
        summary: tool.description,
        tags: [tool.category || 'Tools'],
        requestBody: {
          required: true,
          content: {
            'application/json': {
              schema,
              examples: {
                basic: {
                  summary: 'Basic usage',
                  value: this.generateExample(tool.inputSchema)
                }
              }
            }
          }
        },
        responses: {
          '200': {
            description: 'Successful tool execution',
            content: {
              'application/json': {
                schema: {
                  type: 'object',
                  properties: {
                    content: { type: 'string' },
                    structuredContent: { type: 'object' },
                    _meta: {
                      type: 'object',
                      properties: {
                        displayMode: { enum: ['inline', 'fullscreen', 'pip'] }
                      }
                    }
                  }
                }
              }
            }
          },
          '400': {
            description: 'Invalid input parameters',
            content: {
              'application/json': {
                schema: { $ref: '#/components/schemas/Error' }
              }
            }
          },
          '401': { description: 'Authentication required' },
          '500': { description: 'Internal server error' }
        },
        security: [{ OAuth2: ['read:tools'] }]
      }
    };
  }

  private zodToOpenAPI(schema: z.ZodTypeAny): OpenAPI.SchemaObject {
    // Convert Zod schema to OpenAPI schema
    if (schema instanceof z.ZodObject) {
      const shape = schema.shape;
      const properties: Record<string, OpenAPI.SchemaObject> = {};
      const required: string[] = [];

      for (const [key, value] of Object.entries(shape)) {
        properties[key] = this.zodToOpenAPI(value as z.ZodTypeAny);
        if (!(value instanceof z.ZodOptional)) {
          required.push(key);
        }
      }

      return {
        type: 'object',
        properties,
        required: required.length > 0 ? required : undefined
      };
    }

    if (schema instanceof z.ZodString) {
      return { type: 'string', description: schema.description };
    }

    if (schema instanceof z.ZodNumber) {
      return { type: 'number', description: schema.description };
    }

    if (schema instanceof z.ZodBoolean) {
      return { type: 'boolean', description: schema.description };
    }

    if (schema instanceof z.ZodArray) {
      return {
        type: 'array',
        items: this.zodToOpenAPI(schema.element)
      };
    }

    return { type: 'object' };
  }

  private generateExample(schema: z.ZodTypeAny): any {
    // Generate realistic example values
    if (schema instanceof z.ZodObject) {
      const example: Record<string, any> = {};
      for (const [key, value] of Object.entries(schema.shape)) {
        example[key] = this.generateExample(value as z.ZodTypeAny);
      }
      return example;
    }

    if (schema instanceof z.ZodString) return 'example_value';
    if (schema instanceof z.ZodNumber) return 42;
    if (schema instanceof z.ZodBoolean) return true;
    if (schema instanceof z.ZodArray) return [];

    return null;
  }

  public exportJSON(filepath: string): void {
    fs.writeFileSync(filepath, JSON.stringify(this.spec, null, 2));
    console.log(`✅ OpenAPI spec exported to ${filepath}`);
  }

  public exportYAML(filepath: string): void {
    const yamlStr = yaml.dump(this.spec, { lineWidth: -1 });
    fs.writeFileSync(filepath, yamlStr);
    console.log(`✅ OpenAPI spec exported to ${filepath}`);
  }
}

// Usage example
const generator = new OpenAPIGenerator({
  server: mcpServer,
  title: 'Fitness Studio MCP Server',
  version: '1.0.0',
  baseUrl: 'https://api.fitnessstudio.com',
  contactEmail: 'api@fitnessstudio.com'
});

mcpServer.tools.forEach(tool => generator.addTool(tool));
generator.exportYAML('./openapi.yaml');
generator.exportJSON('./openapi.json');

Integration with MCP Servers: Run this generator during your build process to keep specs synchronized with code changes. Add a npm run docs:generate script that updates OpenAPI files before deployment.

TypeDoc Comments for Auto-Generated Reference

TypeDoc transforms inline JSDoc comments into searchable HTML documentation. For MCP servers, this means every function, type, and configuration option gets documented automatically—no manual wiki updates required.

Why TypeDoc: Traditional documentation goes stale because it's separated from code. Developers update implementations but forget to update docs. TypeDoc solves this by making comments the source of truth. Change a function signature? The docs update automatically.

Best Practices for MCP TypeDoc:

  1. Document every public method: Include @param, @returns, @throws tags
  2. Add code examples: Use @example blocks with runnable snippets
  3. Link to related types: @see tags create cross-references
  4. Explain edge cases: @remarks sections for complex behaviors

Here's production-ready TypeDoc annotation for an MCP tool handler:

// mcp-tool-handler.ts - TypeDoc-annotated MCP tool implementation

/**
 * Booking management tool for fitness studio MCP server.
 *
 * Provides class scheduling, member registration, and waitlist management
 * through ChatGPT conversations. Integrates with Mindbody API for real-time
 * availability and automated confirmations.
 *
 * @remarks
 * This tool uses optimistic locking to prevent double-bookings. When multiple
 * users attempt to book the last spot simultaneously, the first successful
 * transaction wins and subsequent requests receive waitlist offers.
 *
 * @example Basic class booking
 * ```typescript
 * const result = await bookClassTool.execute({
 *   classId: 'spin-2024-01-15-0600',
 *   memberId: 'user-12345',
 *   spotPreference: 'front-row'
 * });
 *
 * console.log(result.content); // "✅ Booked: 6am Spin Class (Front Row)"
 * ```
 *
 * @example Waitlist handling
 * ```typescript
 * const result = await bookClassTool.execute({
 *   classId: 'yoga-2024-01-15-1800',
 *   memberId: 'user-67890'
 * });
 *
 * if (result._meta.waitlisted) {
 *   console.log('Added to waitlist, position:', result._meta.position);
 * }
 * ```
 *
 * @see {@link MindbodyClient} for API integration details
 * @see {@link WaitlistManager} for waitlist logic
 *
 * @public
 */
export class BookClassTool implements MCPTool {
  /**
   * Tool name used by ChatGPT to invoke this functionality.
   * Must match the name in tool registration.
   */
  public readonly name = 'book_class';

  /**
   * Human-readable description shown to users in ChatGPT.
   * Optimized for model selection—clear, concise, action-oriented.
   */
  public readonly description = 'Book fitness classes, manage waitlists, and confirm reservations';

  /**
   * Zod schema defining valid input parameters.
   *
   * @remarks
   * All fields use `.describe()` to generate inline help text shown
   * to users when ChatGPT prompts for missing parameters.
   */
  public readonly inputSchema = z.object({
    classId: z.string().describe('Unique class identifier (e.g., "spin-2024-01-15-0600")'),
    memberId: z.string().describe('Member account ID'),
    spotPreference: z.enum(['front-row', 'back-row', 'any']).optional()
      .describe('Preferred position in class'),
    notifyWaitlist: z.boolean().optional().default(true)
      .describe('Send SMS notification if waitlisted')
  });

  /**
   * Mindbody API client for class availability and booking.
   * Injected via constructor for testability.
   */
  private mindbody: MindbodyClient;

  /**
   * Creates a new class booking tool instance.
   *
   * @param mindbody - Configured Mindbody API client
   * @param config - Tool configuration options
   *
   * @throws {Error} If Mindbody client is not authenticated
   *
   * @example
   * ```typescript
   * const tool = new BookClassTool(
   *   new MindbodyClient({ apiKey: process.env.MINDBODY_KEY }),
   *   { maxWaitlistSize: 20 }
   * );
   * ```
   */
  constructor(
    mindbody: MindbodyClient,
    private config: BookingToolConfig = {}
  ) {
    if (!mindbody.isAuthenticated()) {
      throw new Error('Mindbody client must be authenticated before use');
    }
    this.mindbody = mindbody;
  }

  /**
   * Executes the class booking workflow.
   *
   * Checks availability, attempts booking, handles waitlisting,
   * and sends confirmation notifications.
   *
   * @param input - Validated booking parameters
   * @returns Tool result with booking status and metadata
   *
   * @throws {BookingError} If class doesn't exist or is cancelled
   * @throws {ValidationError} If member account is suspended
   *
   * @remarks
   * This method is idempotent—calling it multiple times with the
   * same classId and memberId won't create duplicate bookings.
   *
   * @example Handling booking failures
   * ```typescript
   * try {
   *   const result = await tool.execute({ classId: 'invalid', memberId: 'user-1' });
   * } catch (error) {
   *   if (error instanceof BookingError) {
   *     console.error('Booking failed:', error.message);
   *   }
   * }
   * ```
   */
  public async execute(
    input: z.infer<typeof this.inputSchema>
  ): Promise<MCPToolResult> {
    // Check class availability
    const classInfo = await this.mindbody.getClass(input.classId);

    if (!classInfo) {
      throw new BookingError(`Class ${input.classId} not found`);
    }

    if (classInfo.spotsAvailable > 0) {
      // Book directly
      const booking = await this.mindbody.bookClass({
        classId: input.classId,
        memberId: input.memberId,
        spotPreference: input.spotPreference
      });

      return {
        content: `✅ Booked: ${classInfo.name} (${booking.spotAssigned})`,
        structuredContent: {
          type: 'booking_confirmation',
          classId: input.classId,
          className: classInfo.name,
          startTime: classInfo.startTime,
          spot: booking.spotAssigned
        },
        _meta: {
          displayMode: 'inline',
          bookingId: booking.id,
          waitlisted: false
        }
      };
    } else {
      // Add to waitlist
      const waitlistEntry = await this.mindbody.addToWaitlist({
        classId: input.classId,
        memberId: input.memberId,
        notifyBySMS: input.notifyWaitlist
      });

      return {
        content: `Class is full. Added to waitlist (position #${waitlistEntry.position})`,
        structuredContent: {
          type: 'waitlist_confirmation',
          classId: input.classId,
          position: waitlistEntry.position,
          estimatedWaitTime: waitlistEntry.estimatedMinutes
        },
        _meta: {
          displayMode: 'inline',
          waitlisted: true,
          position: waitlistEntry.position
        }
      };
    }
  }
}

/**
 * Configuration options for booking tool behavior.
 *
 * @public
 */
export interface BookingToolConfig {
  /**
   * Maximum waitlist size before rejecting new entries.
   * @defaultValue 15
   */
  maxWaitlistSize?: number;

  /**
   * Automatically upgrade waitlisted users when spots open.
   * @defaultValue true
   */
  autoUpgrade?: boolean;
}

Generating HTML Documentation:

npx typedoc --out docs/api src/index.ts \
  --excludePrivate \
  --excludeInternal \
  --categorizeByGroup \
  --gitRevision main

This creates a searchable website at docs/api/index.html with:

  • Searchable index: Find functions by name instantly
  • Type navigation: Browse interfaces, classes, enums
  • Cross-references: Click types to jump to definitions
  • Syntax highlighting: Readable code examples

Interactive Examples with API Playgrounds

Static code snippets are useful, but interactive examples let developers test your MCP server without writing code. API playgrounds provide in-browser editors, live request/response inspection, and one-click execution.

Why Interactive Examples: Developers learn by doing. Reading about an API is one thing—seeing it execute in real-time with editable parameters is transformative. Playgrounds reduce time-to-first-successful-call from hours to seconds.

Key Features:

  1. Pre-populated examples: Common use cases ready to run
  2. Real-time validation: Highlight errors before sending requests
  3. Response inspection: Formatted JSON with syntax highlighting
  4. Shareable links: Save and share working examples

Here's a production-ready API playground for MCP servers:

// api-playground.tsx - Interactive MCP tool testing interface
import React, { useState } from 'react';
import MonacoEditor from '@monaco-editor/react';
import { z } from 'zod';

interface PlaygroundProps {
  toolName: string;
  schema: z.ZodTypeAny;
  endpoint: string;
  examples: Record<string, any>;
}

export const APIPlayground: React.FC<PlaygroundProps> = ({
  toolName,
  schema,
  endpoint,
  examples
}) => {
  const [input, setInput] = useState(JSON.stringify(examples.basic, null, 2));
  const [response, setResponse] = useState<any>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const executeRequest = async () => {
    setLoading(true);
    setError(null);

    try {
      // Validate input against schema
      const parsed = JSON.parse(input);
      const validated = schema.parse(parsed);

      // Execute MCP tool call
      const res = await fetch(endpoint, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${getAuthToken()}`
        },
        body: JSON.stringify({
          tool: toolName,
          input: validated
        })
      });

      if (!res.ok) {
        throw new Error(`HTTP ${res.status}: ${await res.text()}`);
      }

      const data = await res.json();
      setResponse(data);
    } catch (err: any) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="api-playground">
      <div className="playground-header">
        <h3>{toolName}</h3>
        <select onChange={(e) => setInput(JSON.stringify(examples[e.target.value], null, 2))}>
          {Object.keys(examples).map(key => (
            <option key={key} value={key}>{key}</option>
          ))}
        </select>
      </div>

      <div className="playground-split">
        <div className="editor-panel">
          <h4>Request</h4>
          <MonacoEditor
            height="300px"
            language="json"
            value={input}
            onChange={(val) => setInput(val || '')}
            options={{
              minimap: { enabled: false },
              fontSize: 14,
              lineNumbers: 'on',
              scrollBeyondLastLine: false
            }}
          />
          <button onClick={executeRequest} disabled={loading}>
            {loading ? 'Executing...' : 'Run Request'}
          </button>
        </div>

        <div className="response-panel">
          <h4>Response</h4>
          {error && <div className="error">{error}</div>}
          {response && (
            <MonacoEditor
              height="300px"
              language="json"
              value={JSON.stringify(response, null, 2)}
              options={{
                readOnly: true,
                minimap: { enabled: false },
                fontSize: 14
              }}
            />
          )}
        </div>
      </div>
    </div>
  );
};

function getAuthToken(): string {
  return localStorage.getItem('mcp_auth_token') || '';
}

Embedding in Documentation: Add playgrounds to your TypeDoc-generated pages or standalone docs site. Host on the same domain to avoid CORS issues.

Developer Onboarding Guides

Great reference docs explain what your API does. Great onboarding guides explain how to succeed with it. The goal: developers complete their first integration in under 10 minutes.

Onboarding Workflow:

  1. Quick start: 5-minute "Hello World" that proves the concept
  2. Authentication setup: OAuth configuration, API key generation
  3. First tool integration: Copy-paste example that works
  4. Error handling: Common failure modes and fixes
  5. Production checklist: Security, rate limiting, monitoring

Here's a tutorial generator that creates step-by-step guides:

// tutorial-generator.ts - Auto-generate onboarding tutorials
import * as fs from 'fs';
import * as path from 'path';

interface TutorialStep {
  title: string;
  description: string;
  code?: string;
  language?: string;
  expectedOutput?: string;
  tips?: string[];
}

interface Tutorial {
  title: string;
  difficulty: 'beginner' | 'intermediate' | 'advanced';
  estimatedTime: number; // minutes
  prerequisites: string[];
  steps: TutorialStep[];
}

export class TutorialGenerator {
  constructor(private outputDir: string) {
    if (!fs.existsSync(outputDir)) {
      fs.mkdirSync(outputDir, { recursive: true });
    }
  }

  public generate(tutorial: Tutorial): void {
    const markdown = this.buildMarkdown(tutorial);
    const filename = tutorial.title.toLowerCase().replace(/\s+/g, '-') + '.md';
    const filepath = path.join(this.outputDir, filename);

    fs.writeFileSync(filepath, markdown);
    console.log(`✅ Tutorial generated: ${filepath}`);
  }

  private buildMarkdown(tutorial: Tutorial): string {
    let md = `# ${tutorial.title}\n\n`;
    md += `**Difficulty**: ${tutorial.difficulty}\n`;
    md += `**Estimated Time**: ${tutorial.estimatedTime} minutes\n\n`;

    if (tutorial.prerequisites.length > 0) {
      md += `## Prerequisites\n\n`;
      tutorial.prerequisites.forEach(prereq => {
        md += `- ${prereq}\n`;
      });
      md += `\n`;
    }

    tutorial.steps.forEach((step, index) => {
      md += `## Step ${index + 1}: ${step.title}\n\n`;
      md += `${step.description}\n\n`;

      if (step.code) {
        md += `\`\`\`${step.language || 'typescript'}\n`;
        md += `${step.code}\n`;
        md += `\`\`\`\n\n`;
      }

      if (step.expectedOutput) {
        md += `**Expected Output**:\n\n`;
        md += `\`\`\`\n${step.expectedOutput}\n\`\`\`\n\n`;
      }

      if (step.tips && step.tips.length > 0) {
        md += `**Tips**:\n`;
        step.tips.forEach(tip => {
          md += `- ${tip}\n`;
        });
        md += `\n`;
      }
    });

    md += `## Next Steps\n\n`;
    md += `Congratulations! You've completed this tutorial. Explore:\n\n`;
    md += `- API Reference\n`;
    md += `- Interactive Playground\n`;
    md += `- Production Checklist\n`;

    return md;
  }
}

// Usage: Generate "Quick Start" tutorial
const generator = new TutorialGenerator('./tutorials');

generator.generate({
  title: 'Quick Start: Your First MCP Integration',
  difficulty: 'beginner',
  estimatedTime: 10,
  prerequisites: [
    'Node.js 18+ installed',
    'MCP server API key (sign up at makeaihq.com)',
    'Basic TypeScript knowledge'
  ],
  steps: [
    {
      title: 'Install the SDK',
      description: 'Add the MCP SDK to your project:',
      code: 'npm install @modelcontextprotocol/sdk',
      language: 'bash',
      expectedOutput: 'added 1 package, and audited 2 packages in 3s'
    },
    {
      title: 'Initialize the Client',
      description: 'Create an MCP client with your API credentials:',
      code: `import { MCPClient } from '@modelcontextprotocol/sdk';

const client = new MCPClient({
  serverUrl: 'https://api.yourdomain.com/mcp',
  apiKey: process.env.MCP_API_KEY
});

await client.connect();
console.log('Connected to MCP server!');`,
      language: 'typescript',
      tips: [
        'Store API keys in environment variables, never commit them',
        'Use .env files with dotenv library for local development'
      ]
    },
    {
      title: 'Call Your First Tool',
      description: 'Execute a simple tool to verify everything works:',
      code: `const result = await client.callTool('book_class', {
  classId: 'spin-2024-01-15-0600',
  memberId: 'demo-user'
});

console.log(result.content);
// ✅ Booked: 6am Spin Class (Front Row)`,
      language: 'typescript',
      expectedOutput: '✅ Booked: 6am Spin Class (Front Row)',
      tips: [
        'Check tool schemas in API reference for required parameters',
        'Use TypeScript for auto-completion of input types'
      ]
    }
  ]
});

Tutorial Distribution: Host tutorials on your docs site, link from README files, and include in onboarding emails sent to new developers.

Documentation Testing and Validation

Untested documentation is broken documentation. Code examples rot, API endpoints change, and examples stop working. Automated testing catches these issues before developers encounter them.

What to Test:

  1. Code examples: Do they compile and run?
  2. API endpoints: Are they reachable and returning expected shapes?
  3. Schema validity: Does OpenAPI spec match live implementations?
  4. Link integrity: Are cross-references and external links valid?

Here's a documentation validator for MCP servers:

// doc-validator.ts - Automated documentation testing
import * as fs from 'fs';
import * as path from 'path';
import { OpenAPIV3_1 as OpenAPI } from 'openapi-types';
import * as yaml from 'js-yaml';
import Ajv from 'ajv';
import axios from 'axios';

interface ValidationResult {
  passed: boolean;
  errors: string[];
  warnings: string[];
}

export class DocumentationValidator {
  private ajv = new Ajv({ strict: false });

  public async validateOpenAPISpec(specPath: string): Promise<ValidationResult> {
    const result: ValidationResult = { passed: true, errors: [], warnings: [] };

    try {
      const spec = yaml.load(fs.readFileSync(specPath, 'utf8')) as OpenAPI.Document;

      // Validate structure
      if (!spec.openapi || !spec.info || !spec.paths) {
        result.errors.push('Invalid OpenAPI structure');
        result.passed = false;
        return result;
      }

      // Check all endpoints
      for (const [path, methods] of Object.entries(spec.paths)) {
        for (const [method, operation] of Object.entries(methods || {})) {
          if (typeof operation !== 'object') continue;

          // Verify examples exist
          if (operation.requestBody?.content?.['application/json']?.examples) {
            const examples = operation.requestBody.content['application/json'].examples;
            if (Object.keys(examples).length === 0) {
              result.warnings.push(`${method.toUpperCase()} ${path} has no examples`);
            }
          }

          // Validate response schemas
          if (operation.responses) {
            for (const [code, response] of Object.entries(operation.responses)) {
              if (typeof response === 'object' && response.content) {
                const schema = response.content['application/json']?.schema;
                if (!schema) {
                  result.warnings.push(`${method.toUpperCase()} ${path} response ${code} has no schema`);
                }
              }
            }
          }
        }
      }

      console.log(`✅ OpenAPI spec validation: ${result.errors.length} errors, ${result.warnings.length} warnings`);
    } catch (err: any) {
      result.errors.push(`Failed to parse OpenAPI spec: ${err.message}`);
      result.passed = false;
    }

    return result;
  }

  public async testLiveEndpoints(baseUrl: string, spec: OpenAPI.Document): Promise<ValidationResult> {
    const result: ValidationResult = { passed: true, errors: [], warnings: [] };

    for (const [path, methods] of Object.entries(spec.paths)) {
      for (const [method, operation] of Object.entries(methods || {})) {
        if (method === 'get') {
          try {
            const url = `${baseUrl}${path}`;
            const response = await axios.get(url, { timeout: 5000 });

            if (response.status !== 200) {
              result.errors.push(`GET ${path} returned ${response.status}`);
              result.passed = false;
            }
          } catch (err: any) {
            result.errors.push(`GET ${path} failed: ${err.message}`);
            result.passed = false;
          }
        }
      }
    }

    console.log(`✅ Live endpoint testing: ${result.errors.length} failures`);
    return result;
  }
}

// Usage
const validator = new DocumentationValidator();
const specResult = await validator.validateOpenAPISpec('./openapi.yaml');
const liveResult = await validator.testLiveEndpoints('https://api.yourdomain.com', spec);

CI/CD Integration: Run validation on every pull request to catch doc bugs before merge:

# .github/workflows/docs-validation.yml
name: Documentation Validation

on: [pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Validate OpenAPI spec
        run: npm run docs:validate

      - name: Test live endpoints
        run: npm run docs:test-endpoints
        env:
          API_BASE_URL: ${{ secrets.STAGING_API_URL }}

      - name: Check code examples compile
        run: |
          find docs/examples -name "*.ts" -exec tsc --noEmit {} \;

      - name: Validate links
        run: npx markdown-link-check docs/**/*.md

Conclusion: Documentation as Developer Experience

Great MCP documentation isn't an afterthought—it's the first interaction developers have with your ChatGPT app. OpenAPI specs provide machine-readable contracts. TypeDoc comments turn code into searchable references. Interactive playgrounds let developers test without writing code. Onboarding guides compress time-to-first-integration from hours to minutes.

Your Documentation Checklist:

  • ✅ OpenAPI 3.1 spec auto-generated from code
  • ✅ TypeDoc comments on every public method
  • ✅ Interactive API playground with pre-populated examples
  • ✅ "Quick Start" tutorial under 10 minutes
  • ✅ Automated validation in CI/CD pipeline
  • ✅ Link integrity checks on every pull request
  • ✅ Example code verified against live endpoints

Next Steps: Implement the OpenAPI generator first—it provides the foundation for playgrounds and validation. Add TypeDoc comments incrementally as you develop new features. Create one tutorial per major use case. Set up validation in CI/CD to prevent doc rot.

Ready to build ChatGPT apps with production-grade documentation? Start your free trial at MakeAIHQ.com and deploy your first MCP server with auto-generated docs in under 10 minutes. Our AI Conversational Editor creates OpenAPI specs, TypeDoc comments, and interactive examples automatically—no manual writing required.

Join thousands of developers building the future of ChatGPT apps. Your documentation is your developer experience. Make it unforgettable.


Internal Links

  • ChatGPT App Development Guide (Pillar)
  • MCP Server Architecture Patterns
  • MCP Testing Strategies
  • MCP Deployment Best Practices
  • API Design for ChatGPT Apps
  • Developer Experience Optimization
  • OpenAPI Schema Generation
  • Building AI App Templates

External Links


Schema Markup (HowTo):

{
  "@context": "https://schema.org",
  "@type": "HowTo",
  "name": "MCP Server Documentation: OpenAPI, TypeDoc & Interactive Examples",
  "description": "Create comprehensive MCP documentation: OpenAPI specs, TypeDoc comments, interactive examples, API playgrounds, and developer onboarding guides.",
  "estimatedCost": {
    "@type": "MonetaryAmount",
    "currency": "USD",
    "value": "0"
  },
  "totalTime": "PT12M",
  "step": [
    {
      "@type": "HowToStep",
      "name": "Generate OpenAPI Specification",
      "text": "Auto-generate OpenAPI 3.1 spec from MCP tools using zodToOpenAPI conversion and example generation",
      "url": "https://makeaihq.com/guides/cluster/mcp-server-documentation-best-practices#openapi-specification-for-mcp-servers"
    },
    {
      "@type": "HowToStep",
      "name": "Add TypeDoc Comments",
      "text": "Document every public method with JSDoc annotations including @param, @returns, @example, and @see tags",
      "url": "https://makeaihq.com/guides/cluster/mcp-server-documentation-best-practices#typedoc-comments-for-auto-generated-reference"
    },
    {
      "@type": "HowToStep",
      "name": "Build Interactive Playground",
      "text": "Create API playground with Monaco Editor for real-time request testing and response inspection",
      "url": "https://makeaihq.com/guides/cluster/mcp-server-documentation-best-practices#interactive-examples-with-api-playgrounds"
    },
    {
      "@type": "HowToStep",
      "name": "Generate Onboarding Tutorials",
      "text": "Create step-by-step guides with code examples, expected outputs, and troubleshooting tips",
      "url": "https://makeaihq.com/guides/cluster/mcp-server-documentation-best-practices#developer-onboarding-guides"
    },
    {
      "@type": "HowToStep",
      "name": "Validate Documentation",
      "text": "Set up automated testing for OpenAPI schemas, live endpoints, code examples, and link integrity",
      "url": "https://makeaihq.com/guides/cluster/mcp-server-documentation-best-practices#documentation-testing-and-validation"
    }
  ]
}