Skip to content

MCP Server Integration

This guide covers how to integrate Model Context Protocol (MCP) servers with vibe-check to extend agent capabilities with custom tools. You’ll learn how to configure, secure, and use MCP servers in tests and workflows.

MCP servers provide agents with specialized tools for specific domains:

  • Filesystem - Read/write files with path restrictions
  • Database - SQL operations (PostgreSQL, MySQL, etc.)
  • Docker - Container management
  • Git - Version control operations
  • Custom - Build your own tools

Without MCP servers, agents are limited to built-in Claude Code tools (Read, Write, Edit, Bash, etc.). MCP servers expand this toolkit.


MCP servers are configured in the mcpServers field of RunAgentOptions or AgentConfig:

import { vibeTest } from '@dao/vibe-check';
vibeTest('use filesystem MCP', async ({ runAgent, expect }) => {
const result = await runAgent({
prompt: 'List all TypeScript files in src/',
mcpServers: {
filesystem: {
command: 'npx',
args: ['@modelcontextprotocol/server-filesystem', './src'],
allowedTools: ['read_file', 'list_directory']
}
}
});
expect(result).toHaveUsedTool('list_directory');
});

Each MCP server has this structure:

interface MCPServerConfig {
/** Server command (e.g., 'node', 'python', 'npx') */
command: string;
/** Command arguments */
args?: string[];
/** Environment variables */
env?: Record<string, string>;
/** Allowed tool names (whitelist) */
allowedTools?: string[];
}

Provides safe file operations with path restrictions:

vibeTest('filesystem operations', async ({ runAgent, expect }) => {
const result = await runAgent({
prompt: 'Read all package.json files and summarize dependencies',
mcpServers: {
filesystem: {
command: 'npx',
args: [
'@modelcontextprotocol/server-filesystem',
'./src', // Restrict to src/ directory
'./package.json'
],
allowedTools: [
'read_file',
'list_directory'
// Note: 'write_file' not allowed (read-only access)
]
}
}
});
expect(result).toHaveUsedTool('read_file');
expect(result.files.changed().length).toBe(0); // No writes
});

Execute SQL operations on PostgreSQL databases:

import { defineAgent } from '@dao/vibe-check';
const dbAgent = defineAgent({
name: 'database-admin',
mcpServers: {
database: {
command: 'npx',
args: ['@modelcontextprotocol/server-postgres'],
env: {
POSTGRES_URL: process.env.DATABASE_URL // Pass connection string via env
},
allowedTools: [
'query', // Execute SELECT queries
'schema', // Read schema information
'migrate' // Run migrations
]
}
}
});
vibeTest('database migration', async ({ runAgent, expect }) => {
const result = await runAgent({
agent: dbAgent,
prompt: 'Add a created_at column to the users table'
});
expect(result).toHaveUsedTool('migrate');
});

Manage Docker containers and images:

vibeTest('container deployment', async ({ runAgent, expect }) => {
const result = await runAgent({
prompt: 'Build and start the app container',
mcpServers: {
docker: {
command: 'npx',
args: ['@modelcontextprotocol/server-docker'],
allowedTools: [
'build_image',
'start_container',
'list_containers'
]
}
}
});
expect(result).toHaveUsedTool('build_image');
expect(result).toHaveUsedTool('start_container');
});

Version control operations:

vibeTest('git operations', async ({ runAgent, expect }) => {
const result = await runAgent({
prompt: 'Create a feature branch and commit changes',
mcpServers: {
git: {
command: 'npx',
args: ['@modelcontextprotocol/server-git', './'],
allowedTools: [
'branch',
'commit',
'status'
// Note: 'push' not allowed for safety
]
}
}
});
expect(result).toHaveUsedTool('branch');
expect(result).toHaveUsedTool('commit');
});

Use multiple servers for different capabilities:

vibeTest('full-stack deployment', async ({ runAgent }) => {
const result = await runAgent({
prompt: 'Deploy the application',
mcpServers: {
// Database operations
database: {
command: 'npx',
args: ['@mcp/postgres'],
env: { POSTGRES_URL: process.env.DATABASE_URL },
allowedTools: ['migrate', 'query']
},
// Docker operations
docker: {
command: 'npx',
args: ['@mcp/docker'],
allowedTools: ['build_image', 'start_container']
},
// Git operations
git: {
command: 'npx',
args: ['@mcp/git', './'],
allowedTools: ['status', 'commit']
}
}
});
// Agent can use tools from all servers
console.log('Tools used:', result.tools.all().map(t => t.name));
});

Configure MCP servers once for reuse:

import { defineAgent, vibeWorkflow } from '@dao/vibe-check';
const deploymentAgent = defineAgent({
name: 'deployer',
mcpServers: {
docker: {
command: 'npx',
args: ['@mcp/docker'],
allowedTools: ['build_image', 'start_container', 'stop_container']
},
database: {
command: 'npx',
args: ['@mcp/postgres'],
env: { POSTGRES_URL: process.env.DATABASE_URL },
allowedTools: ['migrate', 'query']
}
}
});
vibeWorkflow('deployment pipeline', async (wf) => {
// All stages use the same MCP servers
await wf.stage('run migrations', {
agent: deploymentAgent,
prompt: '/migrate-database'
});
await wf.stage('deploy app', {
agent: deploymentAgent,
prompt: '/deploy-containers'
});
});

Override MCP servers for specific stages:

vibeWorkflow('multi-stage deployment', async (wf) => {
// Stage 1: Database only
await wf.stage('migrate database', {
prompt: '/migrate',
mcpServers: {
database: {
command: 'npx',
args: ['@mcp/postgres'],
env: { POSTGRES_URL: process.env.DATABASE_URL }
}
}
});
// Stage 2: Docker only
await wf.stage('deploy containers', {
prompt: '/deploy',
mcpServers: {
docker: {
command: 'npx',
args: ['@mcp/docker']
}
}
});
// Stage 3: Both servers
await wf.stage('finalize deployment', {
prompt: '/finalize',
mcpServers: {
database: { command: 'npx', args: ['@mcp/postgres'] },
docker: { command: 'npx', args: ['@mcp/docker'] }
}
});
});

Always restrict tools to the minimum necessary:

// ✅ Good: Whitelist only necessary tools
mcpServers: {
filesystem: {
command: 'npx',
args: ['@mcp/filesystem', './src'],
allowedTools: ['read_file', 'list_directory'] // Read-only
}
}
// ❌ Bad: No restriction (all tools allowed)
mcpServers: {
filesystem: {
command: 'npx',
args: ['@mcp/filesystem', './']
// Missing allowedTools - allows delete_file, write_file, etc.
}
}

Never hardcode credentials:

// ✅ Good: Use environment variables
mcpServers: {
database: {
command: 'npx',
args: ['@mcp/postgres'],
env: {
POSTGRES_URL: process.env.DATABASE_URL,
DB_PASSWORD: process.env.DB_PASSWORD
}
}
}
// ❌ Bad: Hardcoded credentials
mcpServers: {
database: {
command: 'npx',
args: ['@mcp/postgres'],
env: {
POSTGRES_URL: 'postgresql://user:password@localhost/db' // Exposed!
}
}
}

Limit access to specific directories:

// ✅ Good: Specific directory
args: ['@mcp/filesystem', './src', './tests']
// ❌ Bad: Unrestricted access
args: ['@mcp/filesystem', '/']

Be careful with destructive operations:

// ✅ Good: Safe tools for production
allowedTools: ['query', 'schema'] // Read-only
// ⚠️ Dangerous: Allow with caution
allowedTools: ['migrate', 'drop_table', 'delete'] // Destructive

Build custom MCP servers for specialized needs:

custom-api-server.ts
import { MCPServer } from '@modelcontextprotocol/sdk';
const server = new MCPServer({
name: 'custom-api',
tools: [
{
name: 'fetch_user',
description: 'Fetch user data from API',
parameters: {
userId: { type: 'string', required: true }
},
handler: async ({ userId }) => {
const response = await fetch(`https://api.example.com/users/${userId}`);
return response.json();
}
}
]
});
server.start();
vibeTest('use custom API', async ({ runAgent, expect }) => {
const result = await runAgent({
prompt: 'Get user data for user ID 123',
mcpServers: {
customApi: {
command: 'node',
args: ['./custom-api-server.js'],
allowedTools: ['fetch_user']
}
}
});
expect(result).toHaveUsedTool('fetch_user');
});

Check that the server command and args are correct:

// Debug server configuration
vibeTest('debug MCP server', async ({ runAgent }) => {
try {
const result = await runAgent({
prompt: 'Test MCP',
mcpServers: {
test: {
command: 'npx',
args: ['@mcp/filesystem', './'],
allowedTools: ['read_file']
}
}
});
} catch (error) {
console.error('Server failed to start:', error);
// Check: Is the package installed?
// Check: Are args correct?
// Check: Does the server work standalone?
}
});

Verify tool names match server’s exposed tools:

// ✅ Good: Correct tool name
allowedTools: ['read_file'] // Matches server tool name
// ❌ Bad: Incorrect tool name
allowedTools: ['readFile'] // Server uses 'read_file', not 'readFile'

Check filesystem paths and environment variables:

// Ensure paths are accessible
mcpServers: {
filesystem: {
command: 'npx',
args: ['@mcp/filesystem', './src'], // Must exist
allowedTools: ['read_file']
}
}

vibeTest('verify MCP tools', async ({ runAgent, expect }) => {
const result = await runAgent({
prompt: 'List files and read package.json',
mcpServers: {
filesystem: {
command: 'npx',
args: ['@mcp/filesystem', './'],
allowedTools: ['list_directory', 'read_file']
}
}
});
// Verify tools were used
expect(result).toHaveUsedTool('list_directory');
expect(result).toHaveUsedTool('read_file');
// Check tool call details
const listCalls = result.tools.filter('list_directory');
expect(listCalls.length).toBeGreaterThan(0);
const readCalls = result.tools.filter('read_file');
expect(readCalls.some(t => t.input?.includes('package.json'))).toBe(true);
});
vibeTest('MCP error handling', async ({ runAgent }) => {
const result = await runAgent({
prompt: 'Read non-existent file',
mcpServers: {
filesystem: {
command: 'npx',
args: ['@mcp/filesystem', './'],
allowedTools: ['read_file']
}
}
});
// Check if error was handled
const readCalls = result.tools.filter('read_file');
const failedReads = readCalls.filter(t => t.result?.includes('ERROR'));
expect(failedReads.length).toBeGreaterThan(0);
});

MCP servers add startup overhead:

// Measure server startup impact
vibeTest('MCP startup performance', async ({ runAgent }) => {
const start = Date.now();
const result = await runAgent({
prompt: 'Quick task',
mcpServers: {
filesystem: { command: 'npx', args: ['@mcp/filesystem', './'] }
}
});
const duration = Date.now() - start;
console.log('Total time (with MCP):', duration, 'ms');
// Server startup typically adds 1-3 seconds
});
// ✅ Good: Define once, reuse
const dbAgent = defineAgent({
name: 'db',
mcpServers: { database: { /* config */ } }
});
vibeTest('test 1', async ({ runAgent }) => {
await runAgent({ agent: dbAgent, prompt: '/task1' });
});
vibeTest('test 2', async ({ runAgent }) => {
await runAgent({ agent: dbAgent, prompt: '/task2' });
});

  1. Whitelist Tools - Use allowedTools to restrict access
  2. Use Environment Variables - Never hardcode secrets
  3. Restrict Paths - Limit filesystem access to specific directories
  4. Test Locally First - Verify MCP servers work standalone before integrating
  5. Handle Errors - Check result.tools for failed tool calls
  6. Reuse Configurations - Use defineAgent for common MCP setups
  7. Document Tool Usage - Comment which tools each task needs
  8. Monitor Performance - Track server startup overhead

ServerPackageToolsUse Case
Filesystem@modelcontextprotocol/server-filesystemread_file, write_file, list_directory, delete_fileFile operations with path restrictions
PostgreSQL@modelcontextprotocol/server-postgresquery, schema, migrateSQL database operations
Docker@modelcontextprotocol/server-dockerbuild_image, start_container, stop_container, list_containersContainer management
Git@modelcontextprotocol/server-gitbranch, commit, status, diff, logVersion control operations

Now that you understand MCP servers, explore:

Or learn more about MCP: