Introduction

Understanding MCP architecture is valuable, but seeing it applied to real scenarios makes the patterns concrete. This lesson walks through four real-world integration examples: a database query assistant, a documentation chatbot, a DevOps assistant, and a multi-server composition for complex workflows.

Key Concepts

  • Database Query Assistant: An LLM application connected to a PostgreSQL MCP server that translates natural language questions into SQL queries and returns formatted results.
  • Documentation Chatbot: An LLM application connected to a filesystem MCP server that reads documentation files and answers questions about them.
  • DevOps Assistant: An LLM application connected to multiple MCP servers (GitHub, Docker, Kubernetes) that automates operational tasks.
  • Multi-Server Composition: Combining capabilities from multiple specialized servers to handle workflows that span different domains.

Real World Context

A startup builds an internal assistant that helps engineers across three domains: querying their PostgreSQL analytics database using natural language, searching their documentation stored in a Git repository, and managing their Kubernetes deployments. Instead of building three separate tools, they build one MCP-powered assistant that connects to three servers and lets the LLM orchestrate between them.

Deep Dive

A database query assistant connects to a PostgreSQL MCP server that exposes tools like query (execute SQL) and list-tables (show schema).

Here is how to set up a database query assistant:

typescript
const dbClient = new Client({ name: 'db-assistant', version: '1.0.0' });
await dbClient.connect(
  new StdioClientTransport({
    command: 'npx',
    args: ['-y', '@modelcontextprotocol/server-postgres', 'postgresql://localhost/analytics']
  })
);

const { tools } = await dbClient.listTools();
// tools: [{ name: 'query', description: 'Execute SQL query', ... }]

// The LLM receives the tools and can call them
const messages = [
  { role: 'system', content: 'You are a data analyst. Use the query tool to answer questions about our database.' },
  { role: 'user', content: 'How many users signed up last week?' }
];

const answer = await agenticLoop(messages, tools, async (name, args) => {
  return dbClient.callTool({ name, arguments: args });
});

The LLM translates the natural language question into a SQL query, calls the query tool, and summarizes the results.

A documentation chatbot uses a filesystem MCP server to read and search documentation files.

Here is the documentation chatbot setup:

typescript
const fsClient = new Client({ name: 'docs-bot', version: '1.0.0' });
await fsClient.connect(
  new StdioClientTransport({
    command: 'npx',
    args: ['-y', '@modelcontextprotocol/server-filesystem', '/path/to/docs']
  })
);

// The filesystem server exposes resources for each file
const { resources } = await fsClient.listResources();
// resources: [{ uri: 'file:///path/to/docs/guide.md', name: 'guide.md' }, ...]

// Read a specific resource to provide context
const guide = await fsClient.readResource({ uri: 'file:///path/to/docs/guide.md' });

const messages = [
  { role: 'system', content: 'You are a documentation assistant. Use the available tools to read files and answer questions.' },
  { role: 'user', content: 'How do I configure authentication?' }
];

The LLM uses the filesystem tools to search for relevant files and reads their content to answer questions.

A DevOps assistant composes multiple servers for operational workflows. This is where multi-server architecture truly shines.

Here is a DevOps assistant connecting to three servers:

typescript
const servers = [
  { name: 'github', command: 'npx', args: ['-y', '@modelcontextprotocol/server-github'], env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN } },
  { name: 'docker', command: 'node', args: ['docker-mcp-server.js'] },
  { name: 'k8s', command: 'node', args: ['k8s-mcp-server.js'] }
];

const { allTools, toolRouter } = await initializeMultiServer(servers);

// Combined tool list includes GitHub, Docker, and K8s tools
// e.g., github__create_issue, docker__list_containers, k8s__get_pods

const messages = [
  { role: 'system', content: 'You are a DevOps assistant with access to GitHub, Docker, and Kubernetes.' },
  { role: 'user', content: 'Check if the API pod is running, and if not, create a GitHub issue.' }
];

// The LLM will:
// 1. Call k8s__get_pods to check pod status
// 2. If the API pod is down, call github__create_issue with details
const answer = await agenticLoop(messages, allTools, async (name, args) => {
  const client = toolRouter.get(name);
  return client.callTool({ name: name.split('__')[1], arguments: args });
});

The LLM naturally chains tools across servers: checking Kubernetes status, then creating a GitHub issue based on the findings. The multi-server setup enables workflows that span multiple systems.

Common Pitfalls

  1. Exposing production databases without guardrails: A database MCP server with write access can be dangerous. Use read-only connections or add confirmation steps for write operations.
  2. Too many tools overwhelming the LLM: Exposing 50+ tools from multiple servers can confuse the LLM. Consider filtering tools based on the current task or conversation context.
  3. Missing environment variables: MCP servers often need tokens or connection strings. Failing to pass required environment variables causes silent startup failures.

Best Practices

  1. Start with a single server integration and add more servers incrementally as you validate each integration.
  2. Use read-only credentials for data servers and add explicit confirmation flows for tools that modify state.
  3. Document each server's requirements (environment variables, network access) in your configuration file.

Summary

  • A database query assistant translates natural language to SQL using a PostgreSQL MCP server.
  • A documentation chatbot uses a filesystem MCP server to read and search files.
  • A DevOps assistant composes GitHub, Docker, and Kubernetes servers for cross-system workflows.
  • Multi-server composition enables workflows that span multiple domains, with the LLM orchestrating between tools.
  • Start simple with one server and add more as needed, always considering security and tool count management.

Code Examples

typescript
const dbClient = new Client({ name: 'db-assistant', version: '1.0.0' });
await dbClient.connect(
  new StdioClientTransport({
    command: 'npx',
    args: ['-y', '@modelcontextprotocol/server-postgres', 'postgresql://localhost/analytics']
  })
);

const { tools } = await dbClient.listTools();
const messages = [
  { role: 'system', content: 'You are a data analyst. Use the query tool to answer questions.' },
  { role: 'user', content: 'How many users signed up last week?' }
];
const answer = await agenticLoop(messages, tools, (name, args) =>
  dbClient.callTool({ name, arguments: args })
);
typescript
const servers = [
  { name: 'github', command: 'npx', args: ['-y', '@modelcontextprotocol/server-github'], env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN } },
  { name: 'docker', command: 'node', args: ['docker-mcp-server.js'] },
  { name: 'k8s', command: 'node', args: ['k8s-mcp-server.js'] }
];

const { allTools, toolRouter } = await initializeMultiServer(servers);

const answer = await agenticLoop(messages, allTools, async (name, args) => {
  const client = toolRouter.get(name);
  return client.callTool({ name: name.split('__')[1], arguments: args });
});
✓ Completed