Skip to main content
Glama
Akakinad

GraphRAG TypeScript MCP Tools

by Akakinad

GraphRAG TypeScript MCP Tools

A complete implementation of a GraphRAG MCP server built with TypeScript, Neo4j, and the MCP TypeScript SDK. This project demonstrates how to build production-quality MCP servers that expose graph-backed tools, resources, and advanced features like LLM sampling and completions.

Built as part of the Neo4j GraphAcademy — Building GraphRAG TypeScript MCP tools course.


What is MCP?

The Model Context Protocol (MCP) is an open standard by Anthropic that allows AI agents (Claude, Cursor, VS Code Copilot) to connect to external tools and data sources in a standardized way.


Project Structure

genai-mcp-build-custom-tools-typescript/ ├── server/ │ └── index.ts ← Main MCP server: 4 tools + 1 resource + sampling + completions ├── strawberry/ │ └── index.ts ← First MCP server: simple countLetters tool ├── solutions/ ← Course reference solutions ├── .vscode/ │ └── mcp.json ← VS Code MCP configuration └── README.md


What Was Built

Step 1 — First MCP Server (strawberry/index.ts)

The simplest possible MCP server. One tool, no database, stdio transport.

server.registerTool("countLetters", {
  description: "Count occurrences of a letter in the text",
  inputSchema: {
    text: z.string().describe("The text to search in"),
    search: z.string().describe("The letter to count"),
  },
}, async ({ text, search }) => ({
  content: [{
    type: "text",
    text: String(text.toLowerCase().split(search.toLowerCase()).length - 1),
  }],
}));

Test result: countLetters("strawberry", "r")3

Tested using the MCP Inspector — a browser-based tool for exploring and testing MCP servers.


Step 2 — Neo4j Connection (Module Scope)

Unlike Python's lifespan context manager, TypeScript uses module-scope variables — the driver is created once at the top of the file and shared by all tools directly.

// Created ONCE when file loads — shared by all tools
const driver: Driver = neo4j.driver(
  process.env["NEO4J_URI"] ?? "neo4j://localhost:7687",
  neo4j.auth.basic(
    process.env["NEO4J_USERNAME"] ?? "neo4j",
    process.env["NEO4J_PASSWORD"] ?? "password"
  )
);
const database = process.env["NEO4J_DATABASE"] ?? "neo4j";

Graceful shutdown via SIGINT:

process.on("SIGINT", async () => {
  await driver.close();
  await server.close();
  process.exit(0);
});

Step 3 — Tool 1: graphStatistics

Counts all nodes and relationships in Neo4j.

Result: {"nodes": 28863, "relationships": 332522}


Step 4 — Tool 2: getMoviesByGenre

Searches movies by genre ordered by IMDB rating. Uses console.error() for logging — never console.log() in stdio servers (it corrupts the JSON-RPC channel).

server.registerTool("getMoviesByGenre", {
  description: "Get movies by genre from the Neo4j database",
  inputSchema: {
    genre: z.string().describe("The genre to search for (e.g., Action, Comedy, Drama)"),
    limit: z.number().default(10).describe("Maximum number of movies to return"),
  },
}, async ({ genre, limit }) => {
  const { records } = await driver.executeQuery(query,
    { genre, limit: neo4j.int(limit) },  // neo4j.int() for 64-bit integer compatibility
    { database }
  );
  ...
});

Step 5 — Tool 3: browse_movies_by_genre (Paginated)

Cursor-based pagination using Neo4j's SKIP and LIMIT:

const skip = parseInt(cursor, 10) || 0;
// Cypher: SKIP $skip LIMIT $limit
const nextCursor = movies.length === pageSize ? String(skip + pageSize) : null;

Returns:

{
  "genre": "Action",
  "movies": [...],
  "nextCursor": "2",
  "page": 1,
  "pageSize": 2,
  "hasMore": true,
  "count": 2
}

Step 6 — Resource: movie://{tmdbId}

Exposes full movie details by TMDB ID using ResourceTemplate:

server.registerResource(
  "movie",
  new ResourceTemplate("movie://{tmdbId}", { list: undefined }),
  { description: "Get detailed information about a specific movie", mimeType: "application/json" },
  async (uri, { tmdbId }) => {
    // uri.href = "movie://603"
    // returns: contents array with JSON movie data
  }
);

Examples: movie://603 (The Matrix), movie://13 (Forrest Gump)


Step 7 — Advanced: Sampling (explainMovieData)

Tools that call the LLM during execution to convert raw Neo4j data into natural language:

const result = await server.server.createMessage({
  messages: [{
    role: "user",
    content: {
      type: "text",
      text: `Describe '${movieData.title}' (${movieData.released})...`,
    },
  }],
  maxTokens: 200,
});

Without sampling: {'title': 'Toy Story', 'released': '1995', 'actors': [...]}

With sampling (VS Code Copilot): "Toy Story — A clever, funny animated adventure about Woody, a jealous cowboy doll who feels displaced when Buzz Lightyear becomes the new favorite..."

Note: Requires setting capability on the low-level server:

server.server["_capabilities"] = { ...server.server["_capabilities"], completions: {} };

Step 8 — Advanced: Completions

Real-time autocomplete suggestions for genre parameters — queries Neo4j as the user types:

import { CompleteRequestSchema } from "@modelcontextprotocol/sdk/types.js";

server.server.setRequestHandler(CompleteRequestSchema, async (request) => {
  if (request.params.argument.name === "genre") {
    const { records } = await driver.executeQuery(
      `MATCH (g:Genre)
       WHERE g.name STARTS WITH $prefix
       RETURN g.name AS name
       ORDER BY name ASC LIMIT 10`,
      { prefix: request.params.argument.value },
      { database }
    );
    return { completion: { values: records.map(r => r.get("name")) } };
  }
  return { completion: { values: [] } };
});

Key Differences from Python Version

Concept

Python (FastMCP)

TypeScript (McpServer)

Tool registration

@mcp.tool() decorator

server.registerTool() method

Shared state

Lifespan context manager

Module-scope variables

Driver access

ctx.request_context.lifespan_context.driver

driver (direct)

Logging

await ctx.info()

console.error()

Sampling

ctx.session.create_message()

server.server.createMessage()

Completions

@server.completion()

server.server.setRequestHandler(CompleteRequestSchema)

File structure

Separate files per feature

Everything in one index.ts

Number params

Python int type hints

neo4j.int() wrapper needed

Prompt params

int, str, float

Always z.string(), parse manually


Setup

Prerequisites

Install

git clone https://github.com/Akakinad/genai-mcp-build-custom-tools-typescript
cd genai-mcp-build-custom-tools-typescript
npm install

Configure credentials

cat > server/.env << EOF
NEO4J_URI=bolt://your-sandbox-ip:7687
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=your-password
NEO4J_DATABASE=neo4j
EOF

Verify setup

npx tsx client/test_environment.ts
# Expected: All checks passed!

Running

Test with MCP Inspector (browser UI)

cd server
npx @modelcontextprotocol/inspector npx tsx index.ts

Open the URL shown in terminal → Connect → Tools tab → List Tools → select a tool → Run Tool.

Run server for AI editor use

cd server
npx tsx index.ts

VS Code Configuration (.vscode/mcp.json)

{
  "servers": {
    "movies-ts": {
      "type": "stdio",
      "command": "npx",
      "args": ["tsx", "/absolute/path/to/server/index.ts"]
    }
  }
}

Test in VS Code Copilot

Explain the movie "Toy Story" using the movies-ts MCP tool Search for Action movies using the movies-ts MCP tool Get graph statistics using the movies-ts MCP tool


Course

Learning Path: Generative AI & GraphRAG

Course: Building GraphRAG TypeScript MCP tools


Building GraphRAG TypeScript MCP Tools

Companion repository for the GraphAcademy course Building GraphRAG TypeScript MCP Tools.

Students build an MCP (Model Context Protocol) server that connects to a Neo4j graph database, exposing tools and resources for use with AI assistants.

Getting Started

  1. Copy .env.example to .env and update the values with your Neo4j connection details.

  2. Install dependencies:

npm install
  1. Start the server:

npm start
  1. Inspect the server with the MCP Inspector:

npm run inspect

Solutions

The solutions/ directory contains the completed code for each lesson checkpoint.

-
license - not tested
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • MCP server for AI dialogue using various LLM models via AceDataCloud

  • MCP server for Argo RPG Platform — connects AI assistants to campaign data via OAuth2

  • Self-hosted MCP gateway: turn any API, database or MCP server into AI connectors — no code.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Akakinad/genai-mcp-build-custom-tools-typescript'

If you have feedback or need assistance with the MCP directory API, please join our Discord server