Difficulty: Intermediate-Advanced Time: 60 minutes Prerequisites: Completed 01-03, Node.js installed
An agent that integrates with the Model Context Protocol (MCP) to:
- Discover available tools dynamically from MCP servers
- Execute tool calls through standardized protocol
- Access file systems, databases, and APIs via MCP
- Use a standardized context-sharing mechanism
MCP is an open protocol that enables AI applications to securely connect to data sources and tools!
Model Context Protocol is a universal standard for:
- Connecting LLMs to external data and tools
- Providing context to AI models
- Standardizing tool definitions and execution
- Building interoperable AI agents
┌─────────────┐
│ n8n Agent │
└──────┬──────┘
│ MCP Protocol
├──────────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ Server │ │ Server │
│ Files │ │ Database │
└──────────┘ └──────────┘
- Standardization: One protocol for all tools
- Discoverability: Tools advertise capabilities
- Security: Controlled access to resources
- Interoperability: Works across platforms
Provides tools and resources:
{
"tools": [
{
"name": "read_file",
"description": "Read contents of a file",
"inputSchema": { ... }
}
]
}Discovers and calls tools:
// Discover tools
const tools = await mcpClient.listTools();
// Execute tool
const result = await mcpClient.executeTool("read_file", {
path: "/path/to/file"
});npm install @modelcontextprotocol/sdkCreate mcp-server.js:
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import fs from 'fs/promises';
import path from 'path';
// Create MCP server
const server = new Server(
{
name: 'filesystem-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Define tools
server.setRequestHandler('tools/list', async () => {
return {
tools: [
{
name: 'read_file',
description: 'Read the contents of a file',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Path to the file to read'
}
},
required: ['path']
}
},
{
name: 'list_directory',
description: 'List files in a directory',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Directory path'
}
},
required: ['path']
}
},
{
name: 'write_file',
description: 'Write content to a file',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'File path'
},
content: {
type: 'string',
description: 'Content to write'
}
},
required: ['path', 'content']
}
}
]
};
});
// Handle tool execution
server.setRequestHandler('tools/call', async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'read_file': {
const content = await fs.readFile(args.path, 'utf-8');
return {
content: [
{
type: 'text',
text: content
}
]
};
}
case 'list_directory': {
const files = await fs.readdir(args.path);
return {
content: [
{
type: 'text',
text: JSON.stringify(files, null, 2)
}
]
};
}
case 'write_file': {
await fs.writeFile(args.path, args.content, 'utf-8');
return {
content: [
{
type: 'text',
text: `Successfully wrote to ${args.path}`
}
]
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error.message}`
}
],
isError: true
};
}
});
// Start server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('MCP Filesystem Server running on stdio');
}
main().catch(console.error);Make it executable:
chmod +x mcp-server.js# Test the server
node mcp-server.jsCurrently, n8n doesn't have native MCP support, so we'll create a custom integration using HTTP and Code nodes.
User Request
↓
Parse Intent
↓
Discover Tools (MCP)
↓
Agent Selects Tool
↓
Execute via MCP
↓
Format Response
↓
Return to User
Receives requests with tool usage intent.
// MCP Client Implementation
const { spawn } = require('child_process');
class MCPClient {
constructor(serverPath) {
this.server = spawn('node', [serverPath]);
this.requestId = 0;
this.pendingRequests = new Map();
this.server.stdout.on('data', (data) => {
this.handleResponse(data);
});
}
async listTools() {
return this.sendRequest('tools/list');
}
async executeTool(name, args) {
return this.sendRequest('tools/call', {
name,
arguments: args
});
}
sendRequest(method, params = {}) {
return new Promise((resolve, reject) => {
const id = ++this.requestId;
const request = {
jsonrpc: '2.0',
id,
method,
params
};
this.pendingRequests.set(id, { resolve, reject });
this.server.stdin.write(JSON.stringify(request) + '\n');
});
}
handleResponse(data) {
const response = JSON.parse(data.toString());
const pending = this.pendingRequests.get(response.id);
if (pending) {
if (response.error) {
pending.reject(new Error(response.error.message));
} else {
pending.resolve(response.result);
}
this.pendingRequests.delete(response.id);
}
}
}
// Usage in workflow
const client = new MCPClient('./mcp-server.js');
const tools = await client.listTools();
return [{ json: { tools } }];Uses discovered tools to process user requests.
Calls the selected tool via MCP protocol.
Processes tool output and creates user-friendly response.
User: "Show me the contents of README.md"
Flow:
- Agent identifies need to read file
- Calls
read_filetool via MCP - Returns file contents
curl -X POST http://localhost:5678/webhook-test/mcp-agent \
-H "Content-Type: application/json" \
-d '{
"message": "Read the file at ./docs/setup.md"
}'User: "What files are in the src directory?"
curl -X POST http://localhost:5678/webhook-test/mcp-agent \
-H "Content-Type: application/json" \
-d '{
"message": "List all files in ./src"
}'User: "Create a new file called notes.txt with 'Hello World'"
curl -X POST http://localhost:5678/webhook-test/mcp-agent \
-H "Content-Type: application/json" \
-d '{
"message": "Write \"Hello World\" to notes.txt"
}'-
@modelcontextprotocol/server-filesystem
- File operations
- Directory navigation
-
@modelcontextprotocol/server-postgres
- Database queries
- Schema inspection
-
@modelcontextprotocol/server-sqlite
- SQLite operations
-
@modelcontextprotocol/server-git
- Git operations
- Repository management
npm install @modelcontextprotocol/server-filesystem
npm install @modelcontextprotocol/server-postgresimport { Server } from '@modelcontextprotocol/server-filesystem';
const server = new Server('/allowed/base/path');server.setRequestHandler('tools/list', async () => {
return {
tools: [
{
name: 'get_weather',
description: 'Get weather for a location',
inputSchema: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'City name'
}
},
required: ['location']
}
}
]
};
});
server.setRequestHandler('tools/call', async (request) => {
const { name, arguments: args } = request.params;
if (name === 'get_weather') {
const weather = await fetchWeather(args.location);
return {
content: [
{
type: 'text',
text: JSON.stringify(weather)
}
]
};
}
});server.setRequestHandler('tools/list', async () => {
return {
tools: [
{
name: 'query_database',
description: 'Execute SQL query',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'SQL query to execute'
}
},
required: ['query']
}
}
]
};
});// Only allow access to specific directories
const ALLOWED_PATHS = ['/safe/directory'];
function isPathAllowed(requestedPath) {
return ALLOWED_PATHS.some(allowed =>
requestedPath.startsWith(allowed)
);
}function validateInput(args, schema) {
// Validate against JSON schema
if (!validate(args, schema)) {
throw new Error('Invalid input');
}
}const rateLimiter = new Map();
function checkRateLimit(clientId) {
const now = Date.now();
const requests = rateLimiter.get(clientId) || [];
const recent = requests.filter(t => now - t < 60000);
if (recent.length > 100) {
throw new Error('Rate limit exceeded');
}
recent.push(now);
rateLimiter.set(clientId, recent);
}curl -X POST http://localhost:5678/webhook-test/mcp-agent/tools \
-H "Content-Type: application/json"Expected: List of available tools from MCP server.
curl -X POST http://localhost:5678/webhook-test/mcp-agent \
-H "Content-Type: application/json" \
-d '{
"tool": "read_file",
"arguments": {
"path": "./README.md"
}
}'MCP also supports resources (not just tools):
server.setRequestHandler('resources/list', async () => {
return {
resources: [
{
uri: 'file:///path/to/file',
name: 'Configuration',
mimeType: 'application/json'
}
]
};
});MCP servers can provide prompt templates:
server.setRequestHandler('prompts/list', async () => {
return {
prompts: [
{
name: 'analyze_code',
description: 'Analyze code for issues'
}
]
};
});Allow MCP servers to request LLM completions:
server.setRequestHandler('sampling/createMessage', async (request) => {
// Server can request AI completion
const { messages } = request.params;
return await getLLMCompletion(messages);
});server.onerror = (error) => {
console.error('[MCP Error]', error);
};
server.onclose = () => {
console.log('[MCP] Server closed');
};npm install -g @modelcontextprotocol/inspector
mcp-inspector node mcp-server.js- Check Node.js version (18+)
- Verify script permissions
- Check for port conflicts
- Ensure
tools/listhandler is implemented - Verify JSON schema format
- Check server connection
- Validate input arguments
- Check file permissions
- Review error messages
- MCP server starts successfully
- Tools are discovered
- Tool execution works
- Errors are handled gracefully
- Security restrictions enforced
- Agent integrates with tools
- Responses are formatted correctly
Now let's combine everything into a multi-agent system:
05-orchestration: Build coordinated multi-agent workflows
✓ MCP standardizes tool integration ✓ Dynamic tool discovery ✓ Secure, controlled access ✓ Interoperable across platforms ✓ Extensible protocol