# Directory Structure
```
├── .dockerignore
├── .gitignore
├── .npmignore
├── bin.js
├── Dockerfile
├── glama.json
├── launch-claude-with-typesense.sh
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│ └── index.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
```
# Source
src/
# TypeScript config
tsconfig.json
# Tests
test/
*.test.js
*.spec.js
# Development tools
.eslintrc
.prettierrc
.editorconfig
.vscode/
.idea/
# Git
.git/
.gitignore
# CI/CD
.github/
.travis.yml
.gitlab-ci.yml
# Logs
logs/
*.log
npm-debug.log*
# Environment
.env
.env.*
# Misc
node_modules/
coverage/
.DS_Store
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Dependencies
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
# Build output
dist/
build/
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# IDE and editor files
.idea/
.vscode/
*.swp
*.swo
.DS_Store
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Misc
.cache/
coverage/
```
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
```
# Node modules and dependencies
node_modules/
npm-debug.log
# Git and version control
.git/
.gitignore
.github/
# Build artifacts
dist/
# Development and environment files
.vscode/
.idea/
.env
*.env.local
*.env.development
*.env.test
*.env.production
# OS files
.DS_Store
Thumbs.db
# Documentation and markdown
*.md
!README.md
# Debug logs
logs/
*.log
# Other unnecessary files
.npmignore
tsconfig.tsbuildinfo
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
[](https://mseep.ai/app/suhail-ak-s-mcp-typesense-server)
# Typesense MCP Server
---
[](https://badge.fury.io/js/typesense-mcp-server)
[](https://opensource.org/licenses/MIT)
[](https://nodejs.org/)
A [Model Context Protocol (MCP)](https://github.com/modelcontextprotocol/mcp) server implementation that provides AI models with access to [Typesense](https://typesense.org/) search capabilities. This server enables LLMs to discover, search, and analyze data stored in Typesense collections.
## Demo
[](https://www.youtube.com/watch?v=your-video-id)
## Features
### Resources
- List and access collections via `typesense://` URIs
- Each collection has a name, description, and document count
- JSON mime type for schema access
### Tools
- **typesense_query**
- Search for documents in Typesense collections with powerful filtering
- Input: Query text, collection name, search fields, filters, sort options, limit
- Returns matching documents with relevance scores
- **typesense_get_document**
- Retrieve specific documents by ID from collections
- Input: Collection name, document ID
- Returns complete document data
- **typesense_collection_stats**
- Get statistics about a Typesense collection
- Input: Collection name
- Returns collection metadata, document count, and schema information
### Prompts
- **analyze_collection**
- Analyze collection structure and contents
- Input: Collection name
- Output: Insights about schema, data types, and statistics
- **search_suggestions**
- Get suggestions for effective search queries for a collection
- Input: Collection name
- Output: Recommended search strategies based on collection schema
## Installation
### Via npm
```bash
# Global installation
npm install -g typesense-mcp-server
# Local installation
npm install typesense-mcp-server
```
### Via mcp-get
```bash
npx @michaellatman/mcp-get@latest install typesense-mcp-server
```
## Development
Install dependencies:
```bash
npm install
```
Build the server:
```bash
npm run build
```
For development with auto-rebuild:
```bash
npm run watch
```
## Installation for Development
### Using Claude Desktop
To use with Claude Desktop, add the server config:
On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
```json
{
"mcpServers": {
"typesense": {
"command": "node",
"args": [
"~/typesense-mcp-server/dist/index.js",
"--host", "your-typesense-host",
"--port", "8108",
"--protocol", "http",
"--api-key", "your-api-key"
]
},
}
}
```
### Debugging
Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), which is available as a package script:
```bash
npm run inspector
```
The Inspector will provide a URL to access debugging tools in your browser.
## Components
### Resources
The server provides information about Typesense collections:
- **Collection Schemas** (`typesense://collections/<collection>`)
- JSON schema information for each collection
- Includes field names and data types
- Sample documents for understanding data structure
### Tools
The server provides the following tools for interacting with Typesense:
- **typesense_list_collections** - List all available collections with their schemas
- Enables zero-conf discovery and routing
- LLM can enumerate collections at runtime and pick the right one(s) before searching
- Useful when collections vary by environment, tenant, or version
- Returns field definitions for schema inference (searchable, facetable, numeric fields)
- Supports `include_fields` parameter to control detail level
- **typesense_query** - Search for documents in a collection
- Full-text search with customizable parameters
- Supports filtering, sorting, and pagination
- **typesense_get_document** - Retrieve a specific document by ID
- **typesense_collection_stats** - Get detailed statistics about a specific collection
### Resource Templates
The server provides templates for:
- **typesense_search** - Template for constructing Typesense search queries
- **typesense_collection** - Template for viewing Typesense collection details
## Usage with Claude Desktop
To use this server with the Claude Desktop app, add the following configuration to the "mcpServers" section of your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"typesense": {
"command": "npx",
"args": [
"-y",
"typesense-mcp-server",
"--host", "your-typesense-host",
"--port", "8108",
"--protocol", "http",
"--api-key", "your-api-key"
]
}
}
}
```
## Logging
The server logs information to a file located at:
```
/tmp/typesense-mcp.log
```
This log contains detailed information about server operations, requests, and any errors that occur.
## License
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
```
--------------------------------------------------------------------------------
/glama.json:
--------------------------------------------------------------------------------
```json
{
"$schema": "https://glama.ai/mcp/schemas/server.json",
"maintainers": [
"suhail-ak-s"
]
}
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "dist",
"rootDir": "src",
"declaration": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM node:20-slim
WORKDIR /app
# Copy package files
COPY package.json package-lock.json ./
# Install dependencies
RUN npm ci
# Copy source code
COPY tsconfig.json ./
COPY src/ ./src/
COPY bin.js ./
# Build the application
RUN npm run build
# Set environment variables (can be overridden at runtime)
ENV HOST=localhost
ENV PORT=8108
ENV PROTOCOL=http
ENV API_KEY=demo
# Expose port for health checks (though not needed for stdio communication)
EXPOSE 3333
# Run the MCP server
CMD ["node", "dist/index.js", "--host", "${HOST}", "--port", "${PORT}", "--protocol", "${PROTOCOL}", "--api-key", "${API_KEY}"]
```
--------------------------------------------------------------------------------
/bin.js:
--------------------------------------------------------------------------------
```javascript
#!/usr/bin/env node
import { spawn } from 'child_process';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// All arguments after the script name are passed to the server
const args = process.argv.slice(2);
// Start the server process
const serverProcess = spawn('node', [join(__dirname, 'dist', 'index.js'), ...args], {
stdio: 'inherit' // Pipe all stdio to parent process
});
// Handle process events
serverProcess.on('error', (err) => {
console.error('Failed to start server process:', err);
process.exit(1);
});
serverProcess.on('close', (code) => {
process.exit(code);
});
// Handle termination signals
process.on('SIGINT', () => serverProcess.kill('SIGINT'));
process.on('SIGTERM', () => serverProcess.kill('SIGTERM'));
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "typesense-mcp-server",
"version": "1.1.0",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"stdio": "node dist/index.js --api-key ${TYPESENSE_API_KEY:-demo}",
"stdio:dev": "tsx src/index.ts --api-key ${TYPESENSE_API_KEY:-demo}",
"inspector": "npx @modelcontextprotocol/inspector",
"watch": "tsc --watch",
"prepublishOnly": "npm run build"
},
"keywords": [
"typesense",
"search",
"model-context-protocol",
"ai",
"llm",
"mcp",
"claude"
],
"author": "Suhail",
"license": "MIT",
"description": "A Model Context Protocol server that provides access to Typesense search capabilities",
"repository": {
"type": "git",
"url": "git+https://github.com/suhail-ak-s/mcp-typesense-server.git"
},
"engines": {
"node": ">=16"
},
"bin": {
"typesense-mcp-server": "bin.js"
},
"files": [
"dist/",
"bin.js",
"README.md",
"LICENSE"
],
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@types/node": "^22.13.5",
"ts-node": "^10.9.2",
"tsx": "^4.19.3",
"typescript": "^5.7.3"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.6.0",
"typesense": "^2.0.3"
}
}
```
--------------------------------------------------------------------------------
/launch-claude-with-typesense.sh:
--------------------------------------------------------------------------------
```bash
#!/bin/bash
# Default values
TYPESENSE_API_KEY=${TYPESENSE_API_KEY:-demo}
TYPESENSE_HOST=${TYPESENSE_HOST:-localhost}
TYPESENSE_PORT=${TYPESENSE_PORT:-8108}
TYPESENSE_PROTOCOL=${TYPESENSE_PROTOCOL:-http}
VIEW_LOGS=false
# Function to display usage information
show_usage() {
echo "Usage: $0 [options]"
echo "Options:"
echo " --api-key KEY Set Typesense API key (default: demo)"
echo " --host HOST Set Typesense host (default: localhost)"
echo " --port PORT Set Typesense port (default: 8108)"
echo " --protocol PROTO Set Typesense protocol (http/https) (default: http)"
echo " --view-logs View MCP server logs after launching Claude"
echo " --help Display this help message"
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--api-key)
TYPESENSE_API_KEY="$2"
shift 2
;;
--host)
TYPESENSE_HOST="$2"
shift 2
;;
--port)
TYPESENSE_PORT="$2"
shift 2
;;
--protocol)
TYPESENSE_PROTOCOL="$2"
shift 2
;;
--view-logs)
VIEW_LOGS=true
shift
;;
--help)
show_usage
exit 0
;;
*)
echo "Unknown option: $1"
show_usage
exit 1
;;
esac
done
# Clear any existing log file to start fresh
LOG_FILE="/tmp/typesense-mcp.log"
if [ -f "$LOG_FILE" ]; then
rm "$LOG_FILE"
fi
# Export environment variables
export TYPESENSE_API_KEY
export TYPESENSE_HOST
export TYPESENSE_PORT
export TYPESENSE_PROTOCOL
echo "Launching Claude with Typesense settings:"
echo "API Key: $TYPESENSE_API_KEY"
echo "Host: $TYPESENSE_HOST"
echo "Port: $TYPESENSE_PORT"
echo "Protocol: $TYPESENSE_PROTOCOL"
# Launch Claude (adjust the path if needed)
open -a "Claude"
# If --view-logs option was specified, tail the log file
if [ "$VIEW_LOGS" = true ]; then
echo "Waiting for log file to be created..."
# Wait for log file to be created (up to 10 seconds)
for i in {1..20}; do
if [ -f "$LOG_FILE" ]; then
break
fi
sleep 0.5
echo -n "."
done
if [ -f "$LOG_FILE" ]; then
echo -e "\nViewing logs (press Ctrl+C to stop):"
tail -f "$LOG_FILE"
else
echo -e "\nLog file not found at $LOG_FILE after waiting"
echo "The MCP server may not have started yet."
fi
fi
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Typesense MCP Server
* A low-level server implementation using Model Context Protocol
*/
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListResourcesRequestSchema,
ListToolsRequestSchema,
ReadResourceRequestSchema,
GetPromptRequestSchema,
ListPromptsRequestSchema,
ListResourceTemplatesRequestSchema
} from "@modelcontextprotocol/sdk/types.js";
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import * as TypesenseModule from 'typesense';
interface TypesenseCollection {
name: string;
num_documents?: number;
[key: string]: any;
}
const logFile = path.join(os.tmpdir(), 'typesense-mcp.log');
fs.writeFileSync(logFile, `[INFO] ${new Date().toISOString()} - Starting Typesense MCP Server...\n`);
console.log = (...args) => {
const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : arg).join(' ');
fs.appendFileSync(logFile, `[INFO] ${new Date().toISOString()} - ${message}\n`);
};
console.error = (...args) => {
const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : arg).join(' ');
fs.appendFileSync(logFile, `[ERROR] ${new Date().toISOString()} - ${message}\n`);
};
const logger = {
log: (message: string) => {
fs.appendFileSync(logFile, `[INFO] ${new Date().toISOString()} - ${message}\n`);
},
error: (message: string, error?: any) => {
fs.appendFileSync(logFile, `[ERROR] ${new Date().toISOString()} - ${message}\n`);
if (error) {
fs.appendFileSync(logFile, `${error.stack || error}\n`);
}
}
};
type TypesenseConfig = {
host: string;
port: number;
protocol: 'http' | 'https';
apiKey: string;
};
let typesenseConfig: TypesenseConfig;
let typesenseClient: TypesenseModule.Client;
function initTypesenseClient(config: TypesenseConfig): TypesenseModule.Client {
return new TypesenseModule.Client({
nodes: [
{
host: config.host,
port: config.port,
protocol: config.protocol
}
],
apiKey: config.apiKey,
connectionTimeoutSeconds: 5
});
}
function parseArgs(): TypesenseConfig {
const args = process.argv.slice(2);
const config: Partial<TypesenseConfig> = {
host: 'localhost',
port: 8108,
protocol: 'http',
apiKey: ''
};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === '--host' && i + 1 < args.length) {
config.host = args[++i];
} else if (arg === '--port' && i + 1 < args.length) {
config.port = parseInt(args[++i], 10);
} else if (arg === '--protocol' && i + 1 < args.length) {
const protocol = args[++i];
if (protocol === 'http' || protocol === 'https') {
config.protocol = protocol;
}
} else if (arg === '--api-key' && i + 1 < args.length) {
config.apiKey = args[++i];
}
}
if (!config.apiKey) {
throw new Error('Typesense API key is required. Use --api-key argument.');
}
return config as TypesenseConfig;
}
const server = new Server(
{
name: "typesense-mcp-server",
version: "1.0.0"
},
{
capabilities: {
resources: {
read: true,
list: true,
templates: true
},
tools: {
list: true,
call: true
},
prompts: {
list: true,
get: true
}
}
}
);
async function fetchTypesenseCollections(): Promise<TypesenseCollection[]> {
try {
logger.log('Fetching collections from Typesense...');
const collections = await typesenseClient.collections().retrieve();
logger.log(`Found ${collections.length} collections`);
return collections as TypesenseCollection[];
} catch (error) {
logger.error('Error fetching collections from Typesense:', error);
throw error;
}
}
// Set up the resource listing request handler
server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
logger.log('Received list resources request: ' + JSON.stringify(request));
logger.log(`Connecting to Typesense at ${typesenseConfig.protocol}://${typesenseConfig.host}:${typesenseConfig.port}`);
try {
const collections = await fetchTypesenseCollections();
if (collections.length === 0) {
logger.log('No collections found in Typesense');
throw new Error('No collections found in Typesense');
}
const resources = collections.map((collection: TypesenseCollection) => ({
uri: new URL(`typesense://collections/${collection.name}`),
name: collection.name,
description: `Collection with ${collection.num_documents || 0} documents`
}));
logger.log(`Returning ${resources.length} collections as resources`);
return { resources };
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
logger.error('Error handling list resources request:', error);
throw new Error(`Typesense error: ${errorMessage}`);
}
});
/**
* Handler for reading a collection's schema or contents.
* Takes a typesense:// URI and returns the collection info as JSON.
*/
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
logger.log('Received read resource request: ' + JSON.stringify(request));
try {
const url = new URL(request.params.uri);
const collectionName = url.pathname.replace(/^\/collections\//, "");
if (!collectionName) {
throw new Error("Invalid collection URI format. Expected: typesense://collections/{collectionName}");
}
// Get collection schema
const collectionSchema = await typesenseClient.collections(collectionName).retrieve();
// Get a sample document to infer structure
let sampleDocument = null;
try {
const searchResult = await typesenseClient.collections(collectionName).documents().search({
q: '*',
per_page: 1
});
if (searchResult.hits && searchResult.hits.length > 0) {
sampleDocument = searchResult.hits[0].document;
}
} catch (err) {
logger.log(`No sample document found for collection ${collectionName}`);
}
// Build schema information
const schema = {
type: "collection",
name: collectionName,
fields: collectionSchema.fields || [],
sample: sampleDocument
};
return {
contents: [{
uri: request.params.uri,
mimeType: "application/json",
text: JSON.stringify(schema, null, 2)
}]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
logger.error('Error handling read resource request:', error);
throw new Error(`Failed to read collection: ${errorMessage}`);
}
});
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
"name": "typesense_query",
"description": "Search for relevant documents in the TypeSense database based on the user's query.",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query entered by the user."
},
"collection": {
"type": "string",
"description": "The name of the TypeSense collection to search within."
},
"query_by": {
"type": "string",
"description": "Comma-separated fields to search in the collection, e.g., 'title,content'."
},
"filter_by": {
"type": "string",
"description": "Optional filtering criteria, e.g., 'category:Chatbot'."
},
"sort_by": {
"type": "string",
"description": "Sorting criteria, e.g., 'created_at:desc'."
},
"limit": {
"type": "integer",
"description": "The maximum number of results to return.",
"default": 10
}
},
"required": ["query", "collection", "query_by"]
}
},
{
"name": "typesense_get_document",
"description": "Retrieve a specific document by ID from a Typesense collection",
"inputSchema": {
"type": "object",
"properties": {
"collection": {
"type": "string",
"description": "The name of the TypeSense collection"
},
"document_id": {
"type": "string",
"description": "The ID of the document to retrieve"
}
},
"required": ["collection", "document_id"]
}
},
{
"name": "typesense_collection_stats",
"description": "Get statistics about a Typesense collection",
"inputSchema": {
"type": "object",
"properties": {
"collection": {
"type": "string",
"description": "The name of the TypeSense collection"
}
},
"required": ["collection"]
}
},
{
"name": "typesense_list_collections",
"description": "List all available Typesense collections with their schemas. Allows zero-conf discovery and routing - the LLM can enumerate collections at runtime and pick the right one(s) before searching. Useful when collections vary by environment, tenant, or version. Returns field definitions for schema inference.",
"inputSchema": {
"type": "object",
"properties": {
"include_fields": {
"type": "boolean",
"description": "Include detailed field schemas for each collection (default: true)",
"default": true
}
}
}
}
]
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
logger.log('Received call tool request: ' + JSON.stringify(request));
// Ensure TypeSense client is initialized
if (!typesenseClient) {
if (!typesenseConfig) {
throw new Error("TypeSense client is not initialized. Please configure it before querying.");
}
typesenseClient = initTypesenseClient(typesenseConfig);
}
switch (request.params.name) {
case "typesense_query": {
const { query = "", collection = "", query_by = "", filter_by = "", sort_by = "", limit = 10, exclude_fields = [] } = request.params.arguments || {};
// Validate required parameters
if (!query || !collection || !query_by) {
throw new Error("Missing required parameters: 'query', 'collection', or 'query_by'");
}
try {
// Construct TypeSense search query
const searchParams = {
q: query as string,
query_by: query_by as string,
filter_by: filter_by as string,
sort_by: sort_by as string,
per_page: limit as number,
prefix: false,
exclude_fields: ['embedding'],
};
// Execute TypeSense search
const response = await typesenseClient.collections(collection as string).documents().search(searchParams);
return {
content: [{
type: "text",
text: JSON.stringify(response.hits, null, 2)
}]
};
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to query TypeSense collection '${collection}': ${error.message}`);
}
throw new Error(`Failed to query TypeSense collection '${collection}': Unknown error`);
}
}
case "typesense_get_document": {
const { collection = "", document_id = "" } = request.params.arguments || {};
// Validate required parameters
if (!collection || !document_id) {
throw new Error("Missing required parameters: 'collection' or 'document_id'");
}
try {
// Get document by ID
const document = await typesenseClient.collections(collection as string).documents(document_id as string).retrieve();
return {
content: [{
type: "text",
text: JSON.stringify(document, null, 2)
}]
};
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to retrieve document '${document_id}' from collection '${collection}': ${error.message}`);
}
throw new Error(`Failed to retrieve document '${document_id}' from collection '${collection}': Unknown error`);
}
}
case "typesense_collection_stats": {
const { collection = "" } = request.params.arguments || {};
// Validate required parameters
if (!collection) {
throw new Error("Missing required parameter: 'collection'");
}
try {
// Get collection
const collectionData = await typesenseClient.collections(collection as string).retrieve();
return {
content: [{
type: "text",
text: JSON.stringify(collectionData, null, 2)
}]
};
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to get stats for collection '${collection}': ${error.message}`);
}
throw new Error(`Failed to get stats for collection '${collection}': Unknown error`);
}
}
case "typesense_list_collections": {
const { include_fields = true } = request.params.arguments || {};
try {
// Fetch all collections
const collections = await typesenseClient.collections().retrieve();
if (!include_fields) {
// Return just collection names and basic info
const basicInfo = collections.map((col: any) => ({
name: col.name,
num_documents: col.num_documents || 0,
created_at: col.created_at
}));
return {
content: [{
type: "text",
text: JSON.stringify({
collections: basicInfo,
count: basicInfo.length
}, null, 2)
}]
};
}
// Return full collection details with field schemas
const detailedCollections = [];
for (const col of collections) {
try {
const collectionDetail = await typesenseClient.collections(col.name).retrieve();
detailedCollections.push({
name: collectionDetail.name,
num_documents: collectionDetail.num_documents || 0,
created_at: collectionDetail.created_at,
fields: collectionDetail.fields || [],
default_sorting_field: collectionDetail.default_sorting_field
});
} catch (error) {
logger.error(`Error fetching details for collection ${col.name}:`, error);
// Include basic info even if detailed fetch fails
detailedCollections.push({
name: col.name,
num_documents: col.num_documents || 0,
error: 'Failed to fetch detailed schema'
});
}
}
return {
content: [{
type: "text",
text: JSON.stringify({
collections: detailedCollections,
count: detailedCollections.length
}, null, 2)
}]
};
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to list collections: ${error.message}`);
}
throw new Error(`Failed to list collections: Unknown error`);
}
}
default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
});
/**
* Handler that lists available prompts.
* Exposes prompts for analyzing collections.
*/
server.setRequestHandler(ListPromptsRequestSchema, async () => {
return {
prompts: [
{
name: "analyze_collection",
description: "Analyze a Typesense collection structure and contents",
arguments: [
{
name: "collection",
description: "Name of the collection to analyze",
required: true
}
]
},
{
name: "search_suggestions",
description: "Get suggestions for effective search queries for a collection",
arguments: [
{
name: "collection",
description: "Name of the collection to analyze",
required: true
}
]
}
]
};
});
/**
* Handler for collection analysis prompt.
* Returns a prompt that requests analysis of a collection's structure and data.
*/
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
logger.log('Received get prompt request: ' + JSON.stringify(request));
const promptName = request.params.name;
if (!["analyze_collection", "search_suggestions"].includes(promptName)) {
throw new Error(`Unknown prompt: ${promptName}`);
}
const collectionName = request.params.arguments?.collection;
if (!collectionName) {
throw new Error("Collection name is required");
}
try {
// Get collection information
const collection = await typesenseClient.collections(collectionName).retrieve();
// Get a sample of documents to show data distribution
let sampleDocs: any[] = [];
try {
const searchResult = await typesenseClient.collections(collectionName).documents().search({
q: '*',
per_page: 5
});
if (searchResult.hits && searchResult.hits.length > 0) {
sampleDocs = searchResult.hits.map((hit: any) => hit.document);
}
} catch (err) {
logger.log(`No sample documents found for collection ${collectionName}`);
}
if (promptName === "analyze_collection") {
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Please analyze the following Typesense collection:
Collection: ${collectionName}
Schema:
${JSON.stringify(collection, null, 2)}
Document count: ${collection.num_documents || 'unknown'}
Sample documents:
${JSON.stringify(sampleDocs, null, 2)}`
}
},
{
role: "user",
content: {
type: "text",
text: "Provide insights about the collection's structure, data types, and how to effectively search it."
}
}
]
};
}
// If promptName is "search_suggestions"
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Please suggest effective search queries for the following Typesense collection:
Collection: ${collectionName}
Fields:
${JSON.stringify(collection.fields, null, 2)}
Sample documents:
${JSON.stringify(sampleDocs, null, 2)}`
}
},
{
role: "user",
content: {
type: "text",
text: "Based on the collection schema and sample data, suggest effective search queries and parameters that would yield useful results."
}
}
]
};
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to analyze collection ${collectionName}: ${error.message}`);
} else {
throw new Error(`Failed to analyze collection ${collectionName}: Unknown error`);
}
}
});
/**
* Handler for listing templates.
* Exposes templates for constructing Typesense queries.
*/
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
return {
resourceTemplates: [
{
name: "typesense_search",
description: "Template for constructing Typesense search queries",
uriTemplate: "typesense://collections/{collection}/search",
text: `To search Typesense collections, you can use these parameters:
Search parameters:
- q: The query text to search for in the documents
- query_by: Comma-separated list of fields to search against
- filter_by: Filter conditions for refining your search results
- sort_by: Fields to sort the results by
- per_page: Number of results to return per page (default: 10)
- page: Page number of results to return (starts at 1)
Example queries:
1. Basic search for "machine learning" in title and content fields:
{
"q": "machine learning",
"query_by": "title,content"
}
2. Search with filtering by category:
{
"q": "neural networks",
"query_by": "title,content",
"filter_by": "category:AI"
}
3. Search with custom sorting:
{
"q": "database",
"query_by": "title,content",
"sort_by": "published_date:desc"
}
Use these patterns to construct Typesense search queries.`
},
{
name: "typesense_collection",
description: "Template for viewing Typesense collection details",
uriTemplate: "typesense://collections/{collection}",
text: `This template is used to view details about a Typesense collection.
The URI format follows this pattern:
typesense://collections/{collection_name}
For example:
typesense://collections/products
This will return information about the collection including:
- Field definitions
- Number of documents
- Collection-specific settings
- Schema details`
}
]
};
});
/**
* Main function to initialize and run the MCP server
*/
async function main() {
try {
typesenseConfig = parseArgs();
logger.log('Typesense configuration: ' + JSON.stringify(typesenseConfig));
typesenseClient = initTypesenseClient(typesenseConfig);
logger.log('Typesense client initialized');
try {
const health = await typesenseClient.health.retrieve();
logger.log('Typesense connection test successful: ' + JSON.stringify(health));
} catch (error) {
logger.error('Typesense connection test failed:', error);
}
logger.log('Connecting to stdio transport...');
const transport = new StdioServerTransport();
await server.connect(transport);
logger.log('MCP server connected and ready');
} catch (error) {
logger.error('Error running MCP server:', error);
process.exit(1);
}
}
main().catch(err => logger.error('Unhandled error:', err));
```