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:
typescriptconst 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:
typescriptconst 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:
typescriptconst 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
- 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.
- 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.
- Missing environment variables: MCP servers often need tokens or connection strings. Failing to pass required environment variables causes silent startup failures.
Best Practices
- Start with a single server integration and add more servers incrementally as you validate each integration.
- Use read-only credentials for data servers and add explicit confirmation flows for tools that modify state.
- 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
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 })
);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 });
});