|

MCP-Plugin-Lösungen mit NestJS erstellen

In den vorherigen Beiträgen haben wir die MCP-Server-Architektur und Plugin-Systeme untersucht. Jetzt sehen wir uns an, wie wir die leistungsstarke Dependency Injection und das Modulsystem von NestJS nutzen können, um produktionsreife MCP-Server mit eleganter Plugin-Architektur zu erstellen. NestJS bietet die perfekte Grundlage für MCP-Server, die testbar, wartbar und skalierbar sind.

Warum NestJS für MCP-Server?

NestJS bringt Enterprise-Grade-Muster in die Node.js-Entwicklung und ist damit ideal für MCP-Server:

  • Dependency Injection: Plugin-Abhängigkeiten sauber verwalten ohne manuelle Verdrahtung
  • Modulsystem: MCP-Capabilities in kohärente, wiederverwendbare Module organisieren
  • Decorators: Tools, Resources und Prompts deklarativ definieren
  • Eingebautes Testing: Erstklassige Unterstützung für Unit- und Integrationstests
  • Typsicherheit: Vollständige TypeScript-Unterstützung mit starker Typisierung durchgehend
  • Lifecycle Hooks: Plugin-Initialisierung und Bereinigung elegant verwalten
import { Module, Injectable } from '@nestjs/common'

@Injectable()
class WeatherPlugin {
  @MCPTool({
    name: 'get_weather',
    description: 'Get current weather for a location'
  })
  async getWeather(location: string): Promise<WeatherData> {
    // Implementation
  }
}

@Module({
  providers: [WeatherPlugin],
  exports: [WeatherPlugin]
})
class WeatherModule {}

Projekt-Setup

Beginnen Sie mit einem NestJS-Projekt, das für die MCP-Server-Entwicklung konfiguriert ist:

npm i -g @nestjs/cli
nest new mcp-server
cd mcp-server
npm install @modelcontextprotocol/sdk zod class-validator class-transformer

Konfigurieren Sie TypeScript für Decorators in tsconfig.json:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2021",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true
  }
}

MCP-Decorators für NestJS

Erstellen Sie Decorators, die Methoden als MCP-Capabilities markieren:

import { SetMetadata } from '@nestjs/common'
import { z } from 'zod'

export const MCP_TOOL_METADATA = 'mcp:tool'
export const MCP_RESOURCE_METADATA = 'mcp:resource'
export const MCP_PROMPT_METADATA = 'mcp:prompt'

export interface MCPToolOptions {
  name: string
  description: string
  inputSchema?: z.ZodSchema
}

export interface MCPResourceOptions {
  uri: string
  name: string
  description: string
  mimeType: string
}

export interface MCPPromptOptions {
  name: string
  description: string
  arguments?: Array<{
    name: string
    description: string
    required: boolean
  }>
}

export function MCPTool(options: MCPToolOptions): MethodDecorator {
  return (target, propertyKey, descriptor) => {
    SetMetadata(MCP_TOOL_METADATA, options)(target, propertyKey, descriptor)
    return descriptor
  }
}

export function MCPResource(options: MCPResourceOptions): MethodDecorator {
  return (target, propertyKey, descriptor) => {
    SetMetadata(MCP_RESOURCE_METADATA, options)(target, propertyKey, descriptor)
    return descriptor
  }
}

export function MCPPrompt(options: MCPPromptOptions): MethodDecorator {
  return (target, propertyKey, descriptor) => {
    SetMetadata(MCP_PROMPT_METADATA, options)(target, propertyKey, descriptor)
    return descriptor
  }
}

MCP-Capability-Discovery-Service

Erstellen Sie einen Service, der MCP-Capabilities aus dekorierten Methoden entdeckt:

import { Injectable, OnModuleInit } from '@nestjs/common'
import { DiscoveryService, MetadataScanner, Reflector } from '@nestjs/core'
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'

@Injectable()
export class MCPDiscoveryService implements OnModuleInit {
  private tools: Map<string, ToolDefinition> = new Map()
  private resources: Map<string, ResourceDefinition> = new Map()
  private prompts: Map<string, PromptDefinition> = new Map()

  constructor(
    private readonly discoveryService: DiscoveryService,
    private readonly metadataScanner: MetadataScanner,
    private readonly reflector: Reflector
  ) {}

  async onModuleInit() {
    await this.discoverCapabilities()
  }

  private async discoverCapabilities() {
    const providers = this.discoveryService.getProviders()

    for (const wrapper of providers) {
      const { instance } = wrapper
      if (!instance || !Object.getPrototypeOf(instance)) {
        continue
      }

      this.scanForTools(instance, wrapper)
      this.scanForResources(instance, wrapper)
      this.scanForPrompts(instance, wrapper)
    }
  }

  private scanForTools(instance: any, wrapper: InstanceWrapper) {
    const prototype = Object.getPrototypeOf(instance)
    const methodNames = this.metadataScanner.getAllMethodNames(prototype)

    for (const methodName of methodNames) {
      const metadata = this.reflector.get<MCPToolOptions>(
        MCP_TOOL_METADATA,
        instance[methodName]
      )

      if (metadata) {
        this.tools.set(metadata.name, {
          name: metadata.name,
          description: metadata.description,
          inputSchema: metadata.inputSchema,
          handler: instance[methodName].bind(instance)
        })
      }
    }
  }

  private scanForResources(instance: any, wrapper: InstanceWrapper) {
    const prototype = Object.getPrototypeOf(instance)
    const methodNames = this.metadataScanner.getAllMethodNames(prototype)

    for (const methodName of methodNames) {
      const metadata = this.reflector.get<MCPResourceOptions>(
        MCP_RESOURCE_METADATA,
        instance[methodName]
      )

      if (metadata) {
        this.resources.set(metadata.uri, {
          uri: metadata.uri,
          name: metadata.name,
          description: metadata.description,
          mimeType: metadata.mimeType,
          handler: instance[methodName].bind(instance)
        })
      }
    }
  }

  private scanForPrompts(instance: any, wrapper: InstanceWrapper) {
    const prototype = Object.getPrototypeOf(instance)
    const methodNames = this.metadataScanner.getAllMethodNames(prototype)

    for (const methodName of methodNames) {
      const metadata = this.reflector.get<MCPPromptOptions>(
        MCP_PROMPT_METADATA,
        instance[methodName]
      )

      if (metadata) {
        this.prompts.set(metadata.name, {
          name: metadata.name,
          description: metadata.description,
          arguments: metadata.arguments || [],
          handler: instance[methodName].bind(instance)
        })
      }
    }
  }

  getTools(): ToolDefinition[] {
    return Array.from(this.tools.values())
  }

  getResources(): ResourceDefinition[] {
    return Array.from(this.resources.values())
  }

  getPrompts(): PromptDefinition[] {
    return Array.from(this.prompts.values())
  }

  getTool(name: string): ToolDefinition | undefined {
    return this.tools.get(name)
  }

  getResource(uri: string): ResourceDefinition | undefined {
    return this.resources.get(uri)
  }

  getPrompt(name: string): PromptDefinition | undefined {
    return this.prompts.get(name)
  }
}

Plugin-Module erstellen

Erstellen Sie NestJS-Module, die MCP-Plugin-Funktionalität kapseln:

import { Injectable, Module } from '@nestjs/common'
import { z } from 'zod'

// Datenbank-Plugin
@Injectable()
export class DatabasePlugin {
  constructor(private readonly databaseService: DatabaseService) {}

  @MCPTool({
    name: 'query_database',
    description: 'Execute a SQL query against the database',
    inputSchema: z.object({
      query: z.string().describe('SQL query to execute'),
      params: z.array(z.any()).optional().describe('Query parameters')
    })
  })
  async queryDatabase(params: { query: string; params?: any[] }) {
    const results = await this.databaseService.query(
      params.query,
      params.params || []
    )
    return {
      rows: results,
      count: results.length
    }
  }

  @MCPResource({
    uri: 'db://schema',
    name: 'database_schema',
    description: 'Database schema information',
    mimeType: 'application/json'
  })
  async getDatabaseSchema() {
    const schema = await this.databaseService.getSchema()
    return JSON.stringify(schema, null, 2)
  }

  @MCPPrompt({
    name: 'generate_query',
    description: 'Generate a SQL query based on natural language',
    arguments: [
      {
        name: 'description',
        description: 'Natural language description of the query',
        required: true
      }
    ]
  })
  async generateQueryPrompt(args: { description: string }) {
    return [
      {
        role: 'user',
        content: {
          type: 'text',
          text: `Generate a SQL query for: ${args.description}\n\nUse the database schema from the db://schema resource.`
        }
      }
    ]
  }
}

@Module({
  providers: [DatabasePlugin, DatabaseService],
  exports: [DatabasePlugin]
})
export class DatabasePluginModule {}

Dateisystem-Plugin-Modul

import { Injectable, Module } from '@nestjs/common'
import { z } from 'zod'
import * as fs from 'fs/promises'
import * as path from 'path'

@Injectable()
export class FileSystemPlugin {
  constructor(
    @Inject('FILE_SYSTEM_ROOT') private readonly rootPath: string
  ) {}

  @MCPTool({
    name: 'read_file',
    description: 'Read contents of a file',
    inputSchema: z.object({
      path: z.string().describe('File path relative to root')
    })
  })
  async readFile(params: { path: string }) {
    const fullPath = path.join(this.rootPath, params.path)
    const content = await fs.readFile(fullPath, 'utf-8')
    return {
      content,
      path: params.path
    }
  }

  @MCPTool({
    name: 'write_file',
    description: 'Write contents to a file',
    inputSchema: z.object({
      path: z.string().describe('File path relative to root'),
      content: z.string().describe('File content to write')
    })
  })
  async writeFile(params: { path: string; content: string }) {
    const fullPath = path.join(this.rootPath, params.path)
    await fs.writeFile(fullPath, params.content, 'utf-8')
    return {
      success: true,
      path: params.path
    }
  }

  @MCPTool({
    name: 'list_files',
    description: 'List files in a directory',
    inputSchema: z.object({
      path: z.string().optional().describe('Directory path (defaults to root)')
    })
  })
  async listFiles(params: { path?: string }) {
    const dirPath = params.path
      ? path.join(this.rootPath, params.path)
      : this.rootPath

    const entries = await fs.readdir(dirPath, { withFileTypes: true })

    return {
      files: entries
        .filter(e => e.isFile())
        .map(e => e.name),
      directories: entries
        .filter(e => e.isDirectory())
        .map(e => e.name)
    }
  }

  @MCPResource({
    uri: 'file://directory-tree',
    name: 'directory_tree',
    description: 'Complete directory tree structure',
    mimeType: 'application/json'
  })
  async getDirectoryTree() {
    const tree = await this.buildDirectoryTree(this.rootPath)
    return JSON.stringify(tree, null, 2)
  }

  private async buildDirectoryTree(dir: string): Promise<any> {
    const entries = await fs.readdir(dir, { withFileTypes: true })
    const tree: any = {
      name: path.basename(dir),
      type: 'directory',
      children: []
    }

    for (const entry of entries) {
      const fullPath = path.join(dir, entry.name)
      if (entry.isDirectory()) {
        tree.children.push(await this.buildDirectoryTree(fullPath))
      } else {
        tree.children.push({
          name: entry.name,
          type: 'file'
        })
      }
    }

    return tree
  }
}

@Module({
  providers: [
    FileSystemPlugin,
    {
      provide: 'FILE_SYSTEM_ROOT',
      useValue: process.env.FS_ROOT || './data'
    }
  ],
  exports: [FileSystemPlugin]
})
export class FileSystemPluginModule {}

Wetter-API-Plugin-Modul

import { Injectable, Module, HttpService } from '@nestjs/common'
import { z } from 'zod'

@Injectable()
export class WeatherPlugin {
  constructor(
    private readonly httpService: HttpService,
    @Inject('WEATHER_API_KEY') private readonly apiKey: string
  ) {}

  @MCPTool({
    name: 'get_weather',
    description: 'Get current weather for a location',
    inputSchema: z.object({
      location: z.string().describe('City name or coordinates'),
      units: z.enum(['metric', 'imperial']).optional().describe('Temperature units')
    })
  })
  async getWeather(params: { location: string; units?: string }) {
    const response = await this.httpService.axiosRef.get(
      `https://api.weatherapi.com/v1/current.json`,
      {
        params: {
          key: this.apiKey,
          q: params.location,
          units: params.units || 'metric'
        }
      }
    )

    return {
      location: response.data.location.name,
      temperature: response.data.current.temp_c,
      condition: response.data.current.condition.text,
      humidity: response.data.current.humidity,
      wind_speed: response.data.current.wind_kph
    }
  }

  @MCPTool({
    name: 'get_forecast',
    description: 'Get weather forecast for a location',
    inputSchema: z.object({
      location: z.string().describe('City name or coordinates'),
      days: z.number().min(1).max(7).optional().describe('Number of days')
    })
  })
  async getForecast(params: { location: string; days?: number }) {
    const response = await this.httpService.axiosRef.get(
      `https://api.weatherapi.com/v1/forecast.json`,
      {
        params: {
          key: this.apiKey,
          q: params.location,
          days: params.days || 3
        }
      }
    )

    return {
      location: response.data.location.name,
      forecast: response.data.forecast.forecastday.map((day: any) => ({
        date: day.date,
        max_temp: day.day.maxtemp_c,
        min_temp: day.day.mintemp_c,
        condition: day.day.condition.text,
        chance_of_rain: day.day.daily_chance_of_rain
      }))
    }
  }

  @MCPPrompt({
    name: 'weather_report',
    description: 'Generate a comprehensive weather report',
    arguments: [
      {
        name: 'location',
        description: 'Location for the weather report',
        required: true
      }
    ]
  })
  async weatherReportPrompt(args: { location: string }) {
    return [
      {
        role: 'user',
        content: {
          type: 'text',
          text: `Create a comprehensive weather report for ${args.location}. Use the get_weather and get_forecast tools to gather current conditions and a 3-day forecast. Format the report in a user-friendly way.`
        }
      }
    ]
  }
}

@Module({
  imports: [HttpModule],
  providers: [
    WeatherPlugin,
    {
      provide: 'WEATHER_API_KEY',
      useValue: process.env.WEATHER_API_KEY
    }
  ],
  exports: [WeatherPlugin]
})
export class WeatherPluginModule {}

MCP-Server-Controller

Erstellen Sie einen Controller, der MCP-Protokollanfragen verarbeitet:

import { Controller, Post, Body } from '@nestjs/common'
import { MCPDiscoveryService } from './mcp-discovery.service'

interface MCPRequest {
  jsonrpc: '2.0'
  id: string | number
  method: string
  params?: any
}

interface MCPResponse {
  jsonrpc: '2.0'
  id: string | number
  result?: any
  error?: {
    code: number
    message: string
    data?: any
  }
}

@Controller('mcp')
export class MCPController {
  constructor(private readonly discoveryService: MCPDiscoveryService) {}

  @Post()
  async handleRequest(@Body() request: MCPRequest): Promise<MCPResponse> {
    try {
      switch (request.method) {
        case 'tools/list':
          return this.handleToolsList(request)

        case 'tools/call':
          return await this.handleToolsCall(request)

        case 'resources/list':
          return this.handleResourcesList(request)

        case 'resources/read':
          return await this.handleResourcesRead(request)

        case 'prompts/list':
          return this.handlePromptsList(request)

        case 'prompts/get':
          return await this.handlePromptsGet(request)

        default:
          return {
            jsonrpc: '2.0',
            id: request.id,
            error: {
              code: -32601,
              message: `Method not found: ${request.method}`
            }
          }
      }
    } catch (error) {
      return {
        jsonrpc: '2.0',
        id: request.id,
        error: {
          code: -32000,
          message: error.message,
          data: { stack: error.stack }
        }
      }
    }
  }

  private handleToolsList(request: MCPRequest): MCPResponse {
    const tools = this.discoveryService.getTools().map(tool => ({
      name: tool.name,
      description: tool.description,
      inputSchema: tool.inputSchema
    }))

    return {
      jsonrpc: '2.0',
      id: request.id,
      result: { tools }
    }
  }

  private async handleToolsCall(request: MCPRequest): Promise<MCPResponse> {
    const { name, arguments: args } = request.params

    const tool = this.discoveryService.getTool(name)
    if (!tool) {
      return {
        jsonrpc: '2.0',
        id: request.id,
        error: {
          code: -32602,
          message: `Tool not found: ${name}`
        }
      }
    }

    try {
      const result = await tool.handler(args)

      return {
        jsonrpc: '2.0',
        id: request.id,
        result: {
          content: [
            {
              type: 'text',
              text: JSON.stringify(result, null, 2)
            }
          ]
        }
      }
    } catch (error) {
      return {
        jsonrpc: '2.0',
        id: request.id,
        error: {
          code: -32000,
          message: error.message
        }
      }
    }
  }

  private handleResourcesList(request: MCPRequest): MCPResponse {
    const resources = this.discoveryService.getResources().map(resource => ({
      uri: resource.uri,
      name: resource.name,
      description: resource.description,
      mimeType: resource.mimeType
    }))

    return {
      jsonrpc: '2.0',
      id: request.id,
      result: { resources }
    }
  }

  private async handleResourcesRead(request: MCPRequest): Promise<MCPResponse> {
    const { uri } = request.params

    const resource = this.discoveryService.getResource(uri)
    if (!resource) {
      return {
        jsonrpc: '2.0',
        id: request.id,
        error: {
          code: -32602,
          message: `Resource not found: ${uri}`
        }
      }
    }

    try {
      const content = await resource.handler()

      return {
        jsonrpc: '2.0',
        id: request.id,
        result: {
          contents: [
            {
              uri: resource.uri,
              mimeType: resource.mimeType,
              text: typeof content === 'string' ? content : content.toString()
            }
          ]
        }
      }
    } catch (error) {
      return {
        jsonrpc: '2.0',
        id: request.id,
        error: {
          code: -32000,
          message: error.message
        }
      }
    }
  }

  private handlePromptsList(request: MCPRequest): MCPResponse {
    const prompts = this.discoveryService.getPrompts().map(prompt => ({
      name: prompt.name,
      description: prompt.description,
      arguments: prompt.arguments
    }))

    return {
      jsonrpc: '2.0',
      id: request.id,
      result: { prompts }
    }
  }

  private async handlePromptsGet(request: MCPRequest): Promise<MCPResponse> {
    const { name, arguments: args } = request.params

    const prompt = this.discoveryService.getPrompt(name)
    if (!prompt) {
      return {
        jsonrpc: '2.0',
        id: request.id,
        error: {
          code: -32602,
          message: `Prompt not found: ${name}`
        }
      }
    }

    try {
      const messages = await prompt.handler(args || {})

      return {
        jsonrpc: '2.0',
        id: request.id,
        result: {
          description: prompt.description,
          messages
        }
      }
    } catch (error) {
      return {
        jsonrpc: '2.0',
        id: request.id,
        error: {
          code: -32000,
          message: error.message
        }
      }
    }
  }
}

Haupt-Anwendungsmodul

Verbinden Sie alles im Haupt-Anwendungsmodul:

import { Module } from '@nestjs/common'
import { DiscoveryModule } from '@nestjs/core'
import { MCPDiscoveryService } from './mcp-discovery.service'
import { MCPController } from './mcp.controller'
import { DatabasePluginModule } from './plugins/database/database-plugin.module'
import { FileSystemPluginModule } from './plugins/filesystem/filesystem-plugin.module'
import { WeatherPluginModule } from './plugins/weather/weather-plugin.module'

@Module({
  imports: [
    DiscoveryModule,
    DatabasePluginModule,
    FileSystemPluginModule,
    WeatherPluginModule
  ],
  controllers: [MCPController],
  providers: [MCPDiscoveryService]
})
export class AppModule {}

MCP-Plugins testen

NestJS macht das Testen von Plugins unkompliziert:

import { Test, TestingModule } from '@nestjs/testing'
import { DatabasePlugin } from './database.plugin'
import { DatabaseService } from './database.service'

describe('DatabasePlugin', () => {
  let plugin: DatabasePlugin
  let databaseService: DatabaseService

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        DatabasePlugin,
        {
          provide: DatabaseService,
          useValue: {
            query: jest.fn(),
            getSchema: jest.fn()
          }
        }
      ]
    }).compile()

    plugin = module.get<DatabasePlugin>(DatabasePlugin)
    databaseService = module.get<DatabaseService>(DatabaseService)
  })

  describe('queryDatabase', () => {
    it('should execute query and return results', async () => {
      const mockResults = [{ id: 1, name: 'Test' }]
      jest.spyOn(databaseService, 'query').mockResolvedValue(mockResults)

      const result = await plugin.queryDatabase({
        query: 'SELECT * FROM users',
        params: []
      })

      expect(result).toEqual({
        rows: mockResults,
        count: 1
      })
      expect(databaseService.query).toHaveBeenCalledWith(
        'SELECT * FROM users',
        []
      )
    })
  })

  describe('getDatabaseSchema', () => {
    it('should return schema as JSON string', async () => {
      const mockSchema = { tables: ['users', 'posts'] }
      jest.spyOn(databaseService, 'getSchema').mockResolvedValue(mockSchema)

      const result = await plugin.getDatabaseSchema()

      expect(result).toBe(JSON.stringify(mockSchema, null, 2))
    })
  })
})

Dynamisches Plugin-Laden

Aktivieren Sie dynamisches Plugin-Laden zur Laufzeit:

import { Injectable, OnModuleInit } from '@nestjs/common'
import { ModuleRef } from '@nestjs/core'

@Injectable()
export class DynamicPluginLoader implements OnModuleInit {
  constructor(private readonly moduleRef: ModuleRef) {}

  async onModuleInit() {
    const pluginModules = await this.discoverPluginModules()

    for (const pluginModule of pluginModules) {
      await this.loadPluginModule(pluginModule)
    }
  }

  private async discoverPluginModules(): Promise<string[]> {
    // Discover plugin modules from configuration or file system
    return process.env.MCP_PLUGINS?.split(',') || []
  }

  private async loadPluginModule(modulePath: string) {
    try {
      const module = await import(modulePath)
      // Register module dynamically
      console.log(`Loaded plugin module: ${modulePath}`)
    } catch (error) {
      console.error(`Failed to load plugin module ${modulePath}:`, error)
    }
  }
}

Konfigurationsverwaltung

Verwenden Sie das NestJS-Konfigurationsmodul für Plugin-Einstellungen:

import { Module } from '@nestjs/common'
import { ConfigModule, ConfigService } from '@nestjs/config'

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: '.env'
    }),
    DatabasePluginModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        connectionString: config.get('DATABASE_URL')
      })
    }),
    WeatherPluginModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        apiKey: config.get('WEATHER_API_KEY')
      })
    })
  ]
})
export class AppModule {}

Best Practices für NestJS-MCP-Plugins

Dependency Injection nutzen: Verwenden Sie NestJS DI, um Plugin-Abhängigkeiten sauber zu verwalten. Injizieren Sie Services, Konfiguration und andere Abhängigkeiten, anstatt sie manuell zu erstellen.

Decorators konsistent verwenden: Definieren Sie alle MCP-Capabilities mit Decorators. Dies macht Capabilities auffindbar und hält Ihren Code deklarativ.

Nach Features organisieren: Gruppieren Sie verwandte Tools, Resources und Prompts in kohärente Plugin-Module. Jedes Modul sollte eine eigenständige Capability-Domäne repräsentieren.

Gründlich testen: Verwenden Sie NestJS-Test-Utilities, um umfassende Unit- und Integrationstests für Ihre Plugins zu schreiben.

Fehler elegant behandeln: Verwenden Sie NestJS-Exception-Filter, um Fehler konsistent über alle Plugins hinweg zu behandeln.

Capabilities dokumentieren: Bieten Sie klare Beschreibungen für alle Tools, Resources und Prompts. Gute Beschreibungen helfen KI-Modellen, Ihre Capabilities effektiv zu nutzen.

APIs versionieren: Verwenden Sie NestJS-Versionierung, um Breaking Changes in Ihren MCP-Capabilities zu verwalten.

Performance überwachen: Verwenden Sie NestJS-Interceptors, um Plugin-Performance zu protokollieren und zu überwachen.

Fazit

NestJS bietet eine hervorragende Grundlage für den Aufbau produktionsreifer MCP-Server mit Plugin-Architektur. Die Kombination aus Dependency Injection, Decorators und Modulsystem macht es einfach, wartbare, testbare und skalierbare MCP-Server zu erstellen.

Durch die Nutzung von NestJS-Mustern erhalten Sie Enterprise-Grade-Features wie Dependency Injection, Lifecycle-Management und Test-Unterstützung out of the box. Dies ermöglicht es Ihnen, sich auf den Aufbau leistungsstarker MCP-Capabilities zu konzentrieren, anstatt auf Infrastruktur-Belange.

Beginnen Sie mit einigen einfachen Plugin-Modulen und erweitern Sie dann, wenn Ihre Anforderungen wachsen. Die hier gezeigten Muster skalieren von kleinen Projekten bis zu großen Enterprise-Systemen.

Schreiten Sie weiter voran und genießen Sie jeden Schritt Ihrer Programmierreise.