Building Bridges: My Journey with FalkorDB and the MCP Server
The Problem That Started It All
As a Senior Product Security Engineer at Red Hat, I spend my days thinking about complex systems, threat modeling, and the intricate relationships between different components in our security architecture. But like many engineers, my most interesting work often happens outside office hours—driven by personal curiosity and the desire to solve problems that don't yet have elegant solutions.
The problem was simple yet frustrating: I had built a comprehensive personal knowledge graph in Neo4j containing everything from my professional projects to my friend networks, but I couldn't easily leverage this rich, interconnected data in my conversations with Claude. The data was there, structured and queryable, but accessing it required context switching between tools and manual query writing.
Enter the Model Context Protocol
When Anthropic announced the Model Context Protocol (MCP), I immediately saw the potential. MCP provides a standardized way for AI assistants to connect with external data sources and tools. It's like having universal adapters for your AI—suddenly, the assistant can reach beyond its training data and tap into your personal databases, APIs, and services.
But there was a gap: while the community quickly built MCP servers for popular databases like PostgreSQL and MongoDB, graph databases were underrepresented. FalkorDB had an existing MCP server, but it was broken and unusable in its current state. Rather than wait for fixes, I decided to fork it and build something that actually worked.
Why FalkorDB?
Before diving into the technical details, let me explain why I chose FalkorDB over sticking with Neo4j:
Performance: FalkorDB is built on Redis, which means it's incredibly fast for the kind of queries I typically run on my personal knowledge graph.
Simplicity: Unlike Neo4j's enterprise-focused architecture, FalkorDB gives me a lightweight graph database without the overhead.
OpenCypher Support: I could keep using the same query language I'd learned, making migration painless.
Cost: For personal projects, FalkorDB's resource efficiency allows me to run it anywhere without worrying about licensing costs.
Building the FalkorDB MCP Server
Starting with a Fork
The existing FalkorDB MCP server appeared promising on paper, but upon attempting to use it, I quickly encountered fundamental issues. It was built as an Express server with endpoints I didn't understand, and despite the README promising Docker container support, no container was actually available. The architecture didn't follow MCP patterns properly, making it incompatible with standard MCP clients, such as Claude Desktop.
Rather than starting from scratch, I forked the project and began a systematic rebuild. This gave me a head start on the basic structure while allowing me to fix the fundamental issues that made it unusable.
Architecture Decisions
The rebuilt MCP server needed to be:
- Type-safe: Built in TypeScript with proper type definitions
- Secure: Input validation and sanitization for all queries
- Flexible: Support for all OpenCypher operations that FalkorDB provides
- Reliable: Proper error handling and connection management
Here's the core structure I settled on:
// MCP Server Entry Point (src/index.ts)
const server = new McpServer({
name: "falkordb-mcpserver",
version: "1.0.0"
}, {
capabilities: {
tools: { listChanged: true },
resources: { listChanged: true },
prompts: { listChanged: true },
logging: {}
}
});
// Service Layer Architecture
class FalkorDBService {
private client: FalkorDB | null = null;
private readonly maxRetries = 5;
private retryCount = 0;
private isInitializing = false;
async executeQuery(graphName: string, query: string, params?: Record<string, any>): Promise<GraphReply<any>> {
if (!this.client) {
throw new AppError(CommonErrors.CONNECTION_FAILED, 'FalkorDB client not initialized');
}
const graph = this.client.selectGraph(graphName);
return await graph.query(query, params);
}
}
// MCP Tools Registration (src/mcp/tools.ts)
function registerQueryGraphTool(server: McpServer): void {
server.registerTool("query_graph", {
title: "Query Graph",
description: "Run a OpenCypher query on a graph",
inputSchema: {
graphName: z.string().describe("The name of the graph to query"),
query: z.string().describe("The OpenCypher query to run"),
},
}, async ({graphName, query}) => {
const result = await falkorDBService.executeQuery(graphName, query);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
};
});
}
The Migration Challenge
My first real test case was migrating my existing Neo4j knowledge graph. This graph contained 76 nodes, representing people, projects, locations, and events, along with complex relationships between them. The challenge wasn't just moving data—it was preserving the semantic meaning and ensuring no relationships were lost.
Rather than writing a custom migration script, I leveraged the MCP ecosystem itself. I connected both the Neo4j MCP server and my new FalkorDB MCP server to Claude Desktop, then simply asked Claude to handle the migration. The AI automatically:
- Extracted all nodes and relationships from Neo4j
- Transformed array properties to JSON strings (FalkorDB handles these differently)
- Validated that every relationship had valid source and target nodes
- Verified the final count matched the source database
The beauty of having working MCP servers was that Claude could orchestrate the entire migration process without me writing a single line of migration code.
Real-World Usage
Once the migration was complete, the power became immediately apparent. Instead of writing manual Cypher queries, I could simply ask Claude:
"Who are the close friends in my network who also work in tech?"
Claude would automatically:
- Connect to my FalkorDB instance
- Write and execute the appropriate OpenCypher query
- Analyze the results and provide insights
- Even suggest follow-up queries based on what it found
The knowledge graph became a living, queryable extension of my conversations with AI.
Technical Deep Dive
Service Architecture
The server is built around a clean service layer pattern:
FalkorDB Service (src/services/falkordb.service.ts
):
- Singleton service managing FalkorDB connections using the official
falkordb
npm package - Implements connection retry logic with exponential backoff
- Handles connection pooling and health checks via ping
- Main operations:
executeQuery()
,listGraphs()
,deleteGraph()
Redis Service (src/services/redis.service.ts
):
- Parallel Redis service for key-value operations using the official
redis
package - Enables metadata storage and simple caching alongside graph operations
- Implements SCAN-based key listing for better performance
Logger Service (src/services/logger.service.ts
):
- Dual-channel logging: file-based persistence and MCP client notifications
- Structured JSON logging with contextual metadata
- Graceful fallback when MCP notifications fail
MCP Protocol Implementation
The server exposes three types of MCP interfaces:
Tools (7 total):
query_graph
- Execute OpenCypher queries with parameter bindinglist_graphs
,delete_graph
- Graph lifecycle managementset_key
,get_key
,delete_key
,list_keys
- Redis operations
Resources (1 total):
graph_list
- Markdown-formatted listing of available graphs
Prompts (3 total):
user_setup
- Initialize user nodes and relationshipsmemory_query
- Structured memory retrieval with configurable relationship depthgraph_reorganization
- Automated graph optimization and restructuring
Connection Management
async function executeQuery(graphName: string, query: string, params?: Record<string, any>) {
if (!this.client) {
throw new AppError(CommonErrors.CONNECTION_FAILED, 'Client not initialized');
}
try {
const graph = this.client.selectGraph(graphName);
const result = await graph.query(query, params);
logger.debug('Query executed successfully', {
graphName,
query: query.substring(0, 100) + (query.length > 100 ? '...' : ''),
hasParams: !!params
});
return result;
} catch (error) {
const appError = new AppError(
CommonErrors.OPERATION_FAILED,
`Failed to execute query on graph '${graphName}': ${error.message}`,
true // operational error
);
throw appError;
}
}
Error Handling Framework
I built a comprehensive error handling system that distinguishes between operational errors (expected, recoverable) and programmer errors (bugs, require restart):
export class AppError extends Error {
public readonly name: string;
public readonly isOperational: boolean;
constructor(name: string, description: string, isOperational: boolean = true) {
super(description);
this.name = name;
this.isOperational = isOperational;
Error.captureStackTrace(this, this.constructor);
}
}
// Error handling with process crash protection
export class ErrorHandler {
public crashIfUntrustedError(error: Error): void {
if (!this.isTrustedError(error)) {
logger.errorSync('Crashing process due to untrusted error', error);
process.exit(1);
}
}
public isTrustedError(error: Error): boolean {
return error instanceof AppError && error.isOperational;
}
}
Configuration & Environment Management
The server uses a centralized configuration system with environment variable support:
// src/config/index.ts
export const config = {
server: {
port: process.env.PORT || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
},
falkorDB: {
host: process.env.FALKORDB_HOST || 'localhost',
port: parseInt(process.env.FALKORDB_PORT || '6379'),
username: process.env.FALKORDB_USERNAME || '',
password: process.env.FALKORDB_PASSWORD || '',
},
redis: {
url: process.env.REDIS_URL || 'redis://localhost:6379',
username: process.env.REDIS_USERNAME || '',
password: process.env.REDIS_PASSWORD || '',
},
};
TypeScript & Testing
The entire codebase is written in TypeScript with comprehensive type safety:
- Test coverage using Jest with ts-jest for critical service components
- Service layer testing with mocked dependencies
- Error path testing for resilience validation
- ES modules throughout for modern Node.js compatibility
Deployment & CI/CD
The project includes a complete CI/CD pipeline:
- Multi-version testing (Node.js 18, 20, 22)
- Automated npm publishing on GitHub releases
- Code coverage reporting via Codecov
- ESLint integration with TypeScript-specific rules
The Open Source Release
The FalkorDB MCP Server is ready for use and includes:
- Comprehensive Documentation: Installation guides, API reference, and usage examples
- Test Suite: Jest-based testing for critical service components
- CI/CD Pipeline: Automated testing across Node.js 18, 20, and 22
- TypeScript Definitions: Full type safety for developers
- NPM Package: Published as
falkordb-mcpserver
for easy installation - Environment Configuration: Flexible setup via environment variables
Installation is as simple as:
npx -y falkordb-mcpserver@latest
The project adheres to semantic versioning and incorporates proper error handling, graceful shutdown, and production-ready logging.
Impact and Future Plans
Personal Productivity
The MCP server has transformed how I access and utilize my personal knowledge. Instead of context switching between tools, I have seamless access to my structured memories, project notes, and relationship data directly in my AI conversations.
Community Contributions
This project highlights the importance of developing reliable, focused MCP servers that address specific problems. The Model Context Protocol ecosystem benefits when developers create well-architected tools that bridge the gap between AI assistants and the databases and services we use daily.
Lessons Learned
- AI-Assisted Development: When using AI coding tools like Claude Code, you may need to write the first bit (especially for patterns outside their training corpus) and then ask it to replicate the pattern so you don't have to do all that typing
- Start Small: I began with basic CRUD operations and gradually added complexity
- Test Early: Having a real use case (my personal graph) provided immediate validation
- Documentation Matters: Good docs make the difference between a helpful tool and shelfware
- Community First: Building for open source from day one improved the code quality
What's Next?
The FalkorDB MCP server effectively serves its core purpose, bridging AI assistants with graph databases through a clean and reliable interface. As the MCP ecosystem expands, I hope this project showcases the value of focused, well-architected tools that address specific problems without unnecessary complexity.
Get Involved
If you're interested in graph databases, AI tooling, or the intersection of the two, I'd love to hear your feedback. The project is available on GitHub, and I welcome contributions from collaborators who share a passion for building tools that enhance human-AI interaction.
The future of AI isn't just about developing better models—it's about integrating them more effectively with the tools and data that power our daily work. Sometimes that means building on existing work, sometimes it means starting fresh, and sometimes—like with this project—it means taking something broken and making it work properly.
Katie Mulliken is a Senior Product Security Engineer at Red Hat. She builds security tools, rides motorcycles, and believes that the best technology bridges gaps rather than creating them. Follow her work on GitHub.