# Directory Structure
```
├── .gitignore
├── assets
│ ├── demo-claude.gif
│ └── demo-cline.gif
├── Dockerfile
├── LICENSE
├── package.json
├── README.md
├── smithery.yaml
├── src
│ ├── handlers.ts
│ ├── index.ts
│ ├── tools.ts
│ └── types.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | node_modules/
2 | build/
3 | backup/
4 | *.log
5 | .env*
6 | .DS_Store
7 | .package-lock.json
8 |
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # MCP Server for ArangoDB
2 |
3 | [](https://smithery.ai/server/@ravenwits/mcp-server-arangodb)
4 |
5 | A Model Context Protocol server for ArangoDB
6 |
7 | This is a TypeScript-based MCP server that provides database interaction capabilities through ArangoDB. It implements core database operations and allows seamless integration with ArangoDB through MCP tools. You can use it wih Claude app and also extension for VSCode that works with mcp like Cline!
8 |
9 | ## Features
10 |
11 | ### Tools
12 |
13 | - `arango_query` - Execute AQL queries
14 |
15 | - Takes an AQL query string as required parameter
16 | - Optionally accepts bind variables for parameterized queries
17 | - Returns query results as JSON
18 |
19 | - `arango_insert` - Insert documents into collections
20 |
21 | - Takes collection name and document object as required parameters
22 | - Automatically generates document key if not provided
23 | - Returns the created document metadata
24 |
25 | - `arango_update` - Update existing documents
26 |
27 | - Takes collection name, document key, and update object as required parameters
28 | - Returns the updated document metadata
29 |
30 | - `arango_remove` - Remove documents from collections
31 |
32 | - Takes collection name and document key as required parameters
33 | - Returns the removed document metadata
34 |
35 | - `arango_backup` - Backup all collections to JSON files
36 |
37 | - Takes output directory path as required parameter
38 | - Creates JSON files for each collection with current data
39 | - Useful for data backup and migration purposes
40 |
41 | - `arango_list_collections` - List all collections in the database
42 |
43 | - Returns array of collection information including names, IDs, and types
44 |
45 | - `arango_create_collection` - Create a new collection in the database
46 | - Takes collection name as required parameter
47 | - Optionally specify collection type (document or edge collection)
48 | - Configure waitForSync behavior for write operations
49 | - Returns collection information including name, type, and status
50 |
51 | ## Installation
52 |
53 | ### Installing via NPM
54 |
55 | To install `arango-server` globally via NPM, run the following command:
56 |
57 | ```bash
58 | npm install -g arango-server
59 | ```
60 |
61 | ### Running via NPX
62 |
63 | To run `arango-server` directly without installation, use the following command:
64 |
65 | ```bash
66 | npx arango-server
67 | ```
68 |
69 | ### Configuring for VSCode Agent
70 |
71 | To use `arango-server` with the VSCode Copilot agent, you must have at least **VSCode 1.99.0 installed** and follow these steps:
72 |
73 | 1. **Create or edit the MCP configuration file**:
74 |
75 | - **Workspace-specific configuration**: Create or edit the `.vscode/mcp.json` file in your workspace.
76 | - **User-specific configuration**: Optionally, specify the server in the [setting(mcp)](vscode://settings/mcp) VS Code [user settings](https://code.visualstudio.com/docs/getstarted/personalize-vscode#_configure-settings) to enable the MCP server across all workspaces.
77 |
78 | _Tip: You can refer [here](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) to the MCP configuration documentation of VSCode for more details on how to set up the configuration file._
79 |
80 | 2. **Add the following configuration**:
81 |
82 | ```json
83 | {
84 | "servers": {
85 | "arango-mcp": {
86 | "type": "stdio",
87 | "command": "npx",
88 | "args": ["arango-server"],
89 | "env": {
90 | "ARANGO_URL": "http://localhost:8529",
91 | "ARANGO_DB": "v20",
92 | "ARANGO_USERNAME": "app",
93 | "ARANGO_PASSWORD": "75Sab@MYa3Dj8Fc"
94 | }
95 | }
96 | }
97 | }
98 | ```
99 |
100 | 3. **Start the MCP server**:
101 |
102 | - Open the Command Palette in VSCode (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac).
103 | - Run the command `MCP: Start Server` and select `arango-mcp` from the list.
104 |
105 | 4. **Verify the server**:
106 | - Open the Chat view in VSCode and switch to Agent mode.
107 | - Use the `Tools` button to verify that the `arango-server` tools are available.
108 |
109 | ### Installing via Smithery
110 |
111 | To install ArangoDB for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@ravenwits/mcp-server-arangodb):
112 |
113 | ```bash
114 | npx -y @smithery/cli install @ravenwits/mcp-server-arangodb --client claude
115 | ```
116 |
117 | #### To use with Claude Desktop
118 |
119 | Go to: `Settings > Developer > Edit Config` or
120 |
121 | - MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
122 | - Windows: `%APPDATA%/Claude/claude_desktop_config.json`
123 |
124 | #### To use with Cline VSCode Extension
125 |
126 | Go to: `Cline Extension > MCP Servers > Edit Configuration` or
127 |
128 | - MacOS: `~/Library/Application Support/Code/User/globalStorage/cline.cline/config.json`
129 | - Windows: `%APPDATA%/Code/User/globalStorage/cline.cline/config.json`
130 |
131 | Add the following configuration to the `mcpServers` section:
132 |
133 | ```json
134 | {
135 | "mcpServers": {
136 | "arango": {
137 | "command": "node",
138 | "args": ["/path/to/arango-server/build/index.js"],
139 | "env": {
140 | "ARANGO_URL": "your_database_url",
141 | "ARANGO_DB": "your_database_name",
142 | "ARANGO_USERNAME": "your_username",
143 | "ARANGO_PASSWORD": "your_password"
144 | }
145 | }
146 | }
147 | }
148 | ```
149 |
150 | ### Environment Variables
151 |
152 | The server requires the following environment variables:
153 |
154 | - `ARANGO_URL` - ArangoDB server URL (note: 8529 is the default port for ArangoDB for local development)
155 | - `ARANGO_DB` - Database name
156 | - `ARANGO_USERNAME` - Database user
157 | - `ARANGO_PASSWORD` - Database password
158 |
159 | ## Usage
160 |
161 | You can pretty much provide any meaningful prompt and Claude will try to execute the appropriate function.
162 |
163 | Some example propmts:
164 |
165 | - "List all collections in the database"
166 | - "Query all users"
167 | - "Insert a new document with name 'John Doe' and email "<[email protected]>' to the 'users' collection"
168 | - "Update the document with key '123456' or name 'Jane Doe' to change the age to 48"
169 | - "Create a new collection named 'products'"
170 |
171 | ### Usage with Claude App
172 |
173 | 
174 |
175 | ### Uasge with Cline VSCode extension
176 |
177 | 
178 |
179 | Query all users:
180 |
181 | ```typescript
182 | {
183 | "query": "FOR user IN users RETURN user"
184 | }
185 | ```
186 |
187 | Insert a new document:
188 |
189 | ```typescript
190 | {
191 | "collection": "users",
192 | "document": {
193 | "name": "John Doe",
194 | "email": "[email protected]"
195 | }
196 | }
197 | ```
198 |
199 | Update a document:
200 |
201 | ```typescript
202 | {
203 | "collection": "users",
204 | "key": "123456",
205 | "update": {
206 | "name": "Jane Doe"
207 | }
208 | }
209 | ```
210 |
211 | Remove a document:
212 |
213 | ```typescript
214 | {
215 | "collection": "users",
216 | "key": "123456"
217 | }
218 | ```
219 |
220 | List all collections:
221 |
222 | ```typescript
223 | {
224 | } // No parameters required
225 | ```
226 |
227 | Backup database collections:
228 |
229 | ```typescript
230 | {
231 | "outputDir": "./backup" // Specify an absolute output directory path for the backup files (optional)
232 | "collection": "users" // Specify a collection name to backup (optional) If no collection name is provided, all collections will be backed up
233 | "docLimit": 1000 // Specify the maximum number of documents to backup per collection (optional), if not provided, all documents will be backed up (not having a limit might cause timeout for large collections)
234 | }
235 | ```
236 |
237 | Create a new collection:
238 |
239 | ```typescript
240 | {
241 | "name": "products",
242 | "type": "document", // "document" or "edge" (optional, defaults to "document")
243 | "waitForSync": false // Optional, defaults to false
244 | }
245 | ```
246 |
247 | Note: The server is database-structure agnostic and can work with any collection names or structures as long as they follow ArangoDB's document and edge collection models.
248 |
249 | ## Disclaimer
250 |
251 | ### For Development Use Only
252 |
253 | This tool is designed for local development environments only. While technically it could connect to a production database, this would create significant security risks and is explicitly discouraged. We use it exclusively with our development databases to maintain separation of concerns and protect production data.
254 |
255 | ## Development
256 |
257 | 1. Clone the repository
258 | 2. Install dependencies:
259 |
260 | ```bash
261 | npm run build
262 | ```
263 |
264 | 3. For development with auto-rebuild:
265 |
266 | ```bash
267 | npm run watch
268 | ```
269 |
270 | ### Debugging
271 |
272 | Since MCP servers communicate over stdio, debugging can be challenging. recommended debugging can be done by using [MCP Inspector](https://github.com/modelcontextprotocol/inspector) for development:
273 |
274 | ```bash
275 | npm run inspector
276 | ```
277 |
278 | The Inspector will provide a URL to access debugging tools in your browser.
279 |
280 | ## License
281 |
282 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
283 |
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "Node16",
5 | "moduleResolution": "Node16",
6 | "outDir": "./build",
7 | "rootDir": "./src",
8 | "strict": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true
12 | },
13 | "include": ["src/**/*"],
14 | "exclude": ["node_modules"]
15 | }
16 |
```
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { CollectionType } from 'arangojs/collection';
2 |
3 | // Type definitions for request arguments
4 | export interface BackupArgs {
5 | outputDir: string;
6 | collection?: string;
7 | docLimit?: number;
8 | }
9 |
10 | export interface QueryArgs {
11 | query: string;
12 | bindVars?: Record<string, unknown>;
13 | }
14 |
15 | export interface CollectionDocumentArgs {
16 | collection: string;
17 | document: Record<string, unknown>;
18 | }
19 |
20 | export interface CollectionKeyArgs {
21 | collection: string;
22 | key: string;
23 | }
24 |
25 | export interface UpdateDocumentArgs extends CollectionKeyArgs {
26 | update: Record<string, unknown>;
27 | }
28 |
29 | export interface CreateCollectionArgs {
30 | name: string;
31 | type?: 'document' | 'edge'; // Changed from CollectionType to string literals
32 | waitForSync?: boolean;
33 | }
34 |
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
2 |
3 | startCommand:
4 | type: stdio
5 | configSchema:
6 | # JSON Schema defining the configuration options for the MCP.
7 | type: object
8 | required:
9 | - arangoUrl
10 | - arangoDb
11 | - arangoUsername
12 | - arangoPassword
13 | properties:
14 | arangoUrl:
15 | type: string
16 | description: The URL of the ArangoDB server.
17 | arangoDb:
18 | type: string
19 | description: The name of the database to connect to.
20 | arangoUsername:
21 | type: string
22 | description: The username for database authentication.
23 | arangoPassword:
24 | type: string
25 | description: The password for database authentication.
26 | commandFunction:
27 | # A function that produces the CLI command to start the MCP on stdio.
28 | |-
29 | (config) => ({command: 'node', args: ['build/index.js'], env: {ARANGO_URL: config.arangoUrl, ARANGO_DB: config.arangoDatabase, ARANGO_USERNAME: config.arangoUsername, ARANGO_PASSWORD: config.arangoPassword}})
30 |
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
2 | # Use a Node.js image
3 | FROM node:22.12-alpine AS builder
4 |
5 | # Create app directory
6 | WORKDIR /app
7 |
8 | # Install app dependencies
9 | COPY package.json package-lock.json ./
10 | RUN npm install --ignore-scripts
11 |
12 | # Copy source code and build the project
13 | COPY . .
14 | RUN npm run build
15 |
16 | # Use a smaller base image for the final build
17 | FROM node:22-alpine AS release
18 |
19 | # Create app directory
20 | WORKDIR /app
21 |
22 | # Copy the built files and node_modules from the builder stage
23 | COPY --from=builder /app/build /app/build
24 | COPY --from=builder /app/package.json /app/package.json
25 | COPY --from=builder /app/package-lock.json /app/package-lock.json
26 | COPY --from=builder /app/node_modules /app/node_modules
27 |
28 | # Expose the default ArangoDB port
29 | EXPOSE 8529
30 |
31 | # Set environment variables (these should be overridden at runtime)
32 | ENV ARANGO_URL=http://localhost:8529
33 | ENV ARANGO_DB=your_database_name
34 | ENV ARANGO_USERNAME=your_username
35 | ENV ARANGO_PASSWORD=your_password
36 |
37 | # Command to run the server
38 | CMD ["node", "build/index.js"]
39 |
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "arango-server",
3 | "version": "0.4.4",
4 | "description": "A Model Context Protocol Server for ArangoDB",
5 | "type": "module",
6 | "bin": {
7 | "arango-server": "./build/index.js"
8 | },
9 | "files": [
10 | "build"
11 | ],
12 | "scripts": {
13 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
14 | "prepare": "npm run build",
15 | "watch": "tsc --watch",
16 | "inspector": "npx @modelcontextprotocol/inspector build/index.js",
17 | "dev": "tsc --watch",
18 | "start": "node build/index.js",
19 | "test": "jest",
20 | "lint": "eslint . --ext .ts",
21 | "format": "prettier --write \"src/**/*.ts\""
22 | },
23 | "keywords": [
24 | "arango",
25 | "arangodb",
26 | "mcp",
27 | "model-context-protocol"
28 | ],
29 | "repository": {
30 | "type": "git",
31 | "url": "git+https://github.com/ravenwits/mcp-server-arangodb.git"
32 | },
33 | "homepage": "https://github.com/ravenwits/mcp-server-arangodb#readme",
34 | "author": "Alp Sarıyer <[email protected]>",
35 | "license": "MIT",
36 | "dependencies": {
37 | "@modelcontextprotocol/sdk": "0.6.0",
38 | "arangojs": "^9.2.0"
39 | },
40 | "devDependencies": {
41 | "@types/node": "^20.11.24",
42 | "typescript": "^5.3.3"
43 | }
44 | }
45 |
```
--------------------------------------------------------------------------------
/src/tools.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Tool } from '@modelcontextprotocol/sdk/types.js';
2 | import { CollectionType } from 'arangojs/collection';
3 |
4 | export function createToolDefinitions(): Tool[] {
5 | return [
6 | {
7 | name: API_TOOLS.QUERY as string,
8 | description: 'Execute an AQL query',
9 | inputSchema: {
10 | type: 'object',
11 | properties: {
12 | query: {
13 | type: 'string',
14 | description: 'AQL query string',
15 | },
16 | bindVars: {
17 | type: 'object',
18 | description: 'Query bind variables',
19 | additionalProperties: { type: 'object' },
20 | },
21 | },
22 | required: ['query'],
23 | },
24 | },
25 | {
26 | name: API_TOOLS.INSERT as string,
27 | description: 'Insert a document into a collection',
28 | inputSchema: {
29 | type: 'object',
30 | properties: {
31 | collection: {
32 | type: 'string',
33 | description: 'Collection name',
34 | },
35 | document: {
36 | type: 'object',
37 | description: 'Document to insert',
38 | additionalProperties: { type: 'object' },
39 | },
40 | },
41 | required: ['collection', 'document'],
42 | },
43 | },
44 | {
45 | name: API_TOOLS.UPDATE as string,
46 | description: 'Update a document in a collection',
47 | inputSchema: {
48 | type: 'object',
49 | properties: {
50 | collection: {
51 | type: 'string',
52 | description: 'Collection name',
53 | },
54 | key: {
55 | type: 'string',
56 | description: 'Document key',
57 | },
58 | update: {
59 | type: 'object',
60 | description: 'Update object',
61 | additionalProperties: { type: 'object' },
62 | },
63 | },
64 | required: ['collection', 'key', 'update'],
65 | },
66 | },
67 | {
68 | name: API_TOOLS.REMOVE as string,
69 | description: 'Remove a document from a collection',
70 | inputSchema: {
71 | type: 'object',
72 | properties: {
73 | collection: {
74 | type: 'string',
75 | description: 'Collection name',
76 | },
77 | key: {
78 | type: 'string',
79 | description: 'Document key',
80 | },
81 | },
82 | required: ['collection', 'key'],
83 | },
84 | },
85 | {
86 | name: API_TOOLS.BACKUP as string,
87 | description: 'Backup collections to JSON files.',
88 | inputSchema: {
89 | type: 'object',
90 | properties: {
91 | outputDir: {
92 | type: 'string',
93 | description: 'An absolute directory path to store backup files',
94 | default: './backup',
95 | optional: true,
96 | },
97 | collection: {
98 | type: 'string',
99 | description: 'Collection name to backup. If not provided, backs up all collections.',
100 | optional: true,
101 | },
102 | docLimit: {
103 | type: 'integer',
104 | description: 'Limit the number of documents to backup. If not provided, backs up all documents.',
105 | optional: true,
106 | },
107 | },
108 | required: ['outputDir'],
109 | },
110 | },
111 | {
112 | name: API_TOOLS.COLLECTIONS as string,
113 | description: 'List all collections in the database',
114 | inputSchema: {
115 | type: 'object',
116 | properties: {},
117 | },
118 | },
119 | {
120 | name: API_TOOLS.CREATE_COLLECTION as string,
121 | description: 'Create a new collection in the database',
122 | inputSchema: {
123 | type: 'object',
124 | properties: {
125 | name: {
126 | type: 'string',
127 | description: 'Name of the collection to create',
128 | },
129 | type: {
130 | type: 'string',
131 | description: 'Type of collection to create ("document" or "edge")',
132 | default: 'document',
133 | enum: ['document', 'edge'],
134 | },
135 | waitForSync: {
136 | type: 'boolean',
137 | description: 'If true, wait for data to be synchronized to disk before returning',
138 | default: false,
139 | },
140 | },
141 | required: ['name'],
142 | },
143 | },
144 | ];
145 | }
146 |
147 | export enum API_TOOLS {
148 | QUERY = 'arango_query',
149 | INSERT = 'arango_insert',
150 | UPDATE = 'arango_update',
151 | REMOVE = 'arango_remove',
152 | BACKUP = 'arango_backup',
153 | COLLECTIONS = 'arango_list_collections',
154 | CREATE_COLLECTION = 'arango_create_collection',
155 | }
156 |
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4 | import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5 | import { Database } from 'arangojs';
6 | import { readFileSync } from 'fs';
7 | import { dirname, join } from 'path';
8 | import { fileURLToPath } from 'url';
9 | import { createToolDefinitions } from './tools.js';
10 | import { ToolHandlers } from './handlers.js';
11 |
12 | // Get package version from package.json
13 | const __dirname = dirname(fileURLToPath(import.meta.url));
14 | const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
15 | const MAX_RECONNECTION_ATTEMPTS = 3;
16 | const RECONNECTION_DELAY = 1000; // 1 second
17 |
18 | // Get connection details from environment variables
19 | const ARANGO_URL = process.env.ARANGO_URL || 'http://localhost:8529';
20 | const ARANGO_DB = process.env.ARANGO_DB || '_system';
21 | const ARANGO_USERNAME = process.env.ARANGO_USERNAME;
22 | const ARANGO_PASSWORD = process.env.ARANGO_PASSWORD;
23 | const TOOLS = createToolDefinitions();
24 |
25 | if (!ARANGO_USERNAME || !ARANGO_PASSWORD) {
26 | throw new Error('ARANGO_USERNAME and ARANGO_PASSWORD environment variables are required');
27 | }
28 |
29 | class ArangoServer {
30 | private server: Server;
31 | private db!: Database; // Using definite assignment assertion
32 | private isConnected: boolean = false;
33 | private reconnectionAttempts: number = 0;
34 | private toolHandlers: ToolHandlers;
35 |
36 | constructor() {
37 | this.initializeDatabase();
38 |
39 | // Initialize MCP server
40 | this.server = new Server(
41 | {
42 | name: 'arango-server',
43 | version: packageJson.version,
44 | },
45 | {
46 | capabilities: {
47 | tools: {},
48 | },
49 | },
50 | );
51 |
52 | // Initialize tool handlers
53 | this.toolHandlers = new ToolHandlers(this.db, TOOLS, this.ensureConnection.bind(this));
54 |
55 | // Set up request handlers
56 | this.server.setRequestHandler(ListToolsRequestSchema, () => this.toolHandlers.handleListTools());
57 | this.server.setRequestHandler(CallToolRequestSchema, (request) => this.toolHandlers.handleCallTool(request));
58 |
59 | // Error handling
60 | this.server.onerror = (error) => console.error('[MCP Error]', error);
61 | process.on('SIGINT', async () => {
62 | await this.server.close();
63 | process.exit(0);
64 | });
65 | }
66 |
67 | private async initializeDatabase() {
68 | try {
69 | this.db = new Database([ARANGO_URL]);
70 | this.db.useBasicAuth(ARANGO_USERNAME, ARANGO_PASSWORD);
71 | this.db = this.db.database(ARANGO_DB);
72 |
73 | // Test connection
74 | await this.checkConnection();
75 | this.isConnected = true;
76 | this.reconnectionAttempts = 0;
77 | console.info('Successfully connected to ArangoDB');
78 | } catch (error) {
79 | console.error('Failed to initialize database:', error instanceof Error ? error.message : 'Unknown error');
80 | await this.handleConnectionError();
81 | }
82 | }
83 |
84 | private async checkConnection(): Promise<void> {
85 | try {
86 | await this.db.version();
87 | } catch (error) {
88 | this.isConnected = false;
89 | throw error;
90 | }
91 | }
92 |
93 | private async handleConnectionError(): Promise<void> {
94 | if (this.reconnectionAttempts >= MAX_RECONNECTION_ATTEMPTS) {
95 | throw new Error(`Failed to connect after ${MAX_RECONNECTION_ATTEMPTS} attempts`);
96 | }
97 |
98 | this.reconnectionAttempts++;
99 | console.error(`Attempting to reconnect (${this.reconnectionAttempts}/${MAX_RECONNECTION_ATTEMPTS})...`);
100 |
101 | await new Promise((resolve) => setTimeout(resolve, RECONNECTION_DELAY));
102 | await this.initializeDatabase();
103 | }
104 |
105 | private async ensureConnection(): Promise<void> {
106 | if (!this.isConnected) {
107 | await this.handleConnectionError();
108 | }
109 | }
110 |
111 | async run() {
112 | const transport = new StdioServerTransport();
113 | await this.server.connect(transport);
114 | console.error('ArangoDB MCP server running on stdio');
115 | }
116 | }
117 |
118 | const server = new ArangoServer();
119 | server.run().catch(console.error);
120 |
```
--------------------------------------------------------------------------------
/src/handlers.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ErrorCode, McpError, Request, Tool } from '@modelcontextprotocol/sdk/types.js';
2 | import { Database } from 'arangojs';
3 | import { CollectionStatus, CollectionType, CreateCollectionOptions } from 'arangojs/collection';
4 | import { promises as fs } from 'fs';
5 | import { join, resolve } from 'path';
6 | import { API_TOOLS } from './tools.js';
7 | import { BackupArgs, CollectionDocumentArgs, CollectionKeyArgs, CreateCollectionArgs, QueryArgs, UpdateDocumentArgs } from './types.js';
8 |
9 | const PARALLEL_BACKUP_CHUNKS = 5;
10 |
11 | export class ToolHandlers {
12 | constructor(private db: Database, private tools: Tool[], private ensureConnection: () => Promise<void>) {}
13 |
14 | async handleListTools() {
15 | return {
16 | tools: this.tools,
17 | };
18 | }
19 |
20 | async handleCallTool(request: Request) {
21 | try {
22 | await this.ensureConnection();
23 |
24 | switch (request.params?.name) {
25 | case API_TOOLS.QUERY: {
26 | const args = request.params.arguments as QueryArgs;
27 | try {
28 | const cursor = await this.db.query(args.query, args.bindVars || {});
29 | const result = await cursor.all();
30 | return {
31 | content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
32 | };
33 | } catch (error) {
34 | throw new McpError(ErrorCode.InvalidRequest, `Query execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
35 | }
36 | }
37 |
38 | case API_TOOLS.INSERT: {
39 | const args = request.params.arguments as CollectionDocumentArgs;
40 | try {
41 | const coll = this.db.collection(args.collection);
42 | const result = await coll.save(args.document);
43 | return {
44 | content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
45 | };
46 | } catch (error) {
47 | throw new McpError(ErrorCode.InvalidRequest, `Insert operation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
48 | }
49 | }
50 |
51 | case API_TOOLS.UPDATE: {
52 | const args = request.params.arguments as UpdateDocumentArgs;
53 | try {
54 | const coll = this.db.collection(args.collection);
55 | const result = await coll.update(args.key, args.update);
56 | return {
57 | content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
58 | };
59 | } catch (error) {
60 | throw new McpError(ErrorCode.InvalidRequest, `Update operation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
61 | }
62 | }
63 |
64 | case API_TOOLS.REMOVE: {
65 | const args = request.params.arguments as CollectionKeyArgs;
66 | try {
67 | const coll = this.db.collection(args.collection);
68 | const result = await coll.remove(args.key);
69 | return {
70 | content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
71 | };
72 | } catch (error) {
73 | throw new McpError(ErrorCode.InvalidRequest, `Remove operation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
74 | }
75 | }
76 |
77 | case API_TOOLS.COLLECTIONS: {
78 | try {
79 | const collections = await this.db.listCollections();
80 | return {
81 | content: [{ type: 'text', text: JSON.stringify(collections, null, 2) }],
82 | };
83 | } catch (error) {
84 | throw new McpError(ErrorCode.InternalError, `Failed to list collections: ${error instanceof Error ? error.message : 'Unknown error'}`);
85 | }
86 | }
87 |
88 | case API_TOOLS.CREATE_COLLECTION: {
89 | const args = request.params.arguments as CreateCollectionArgs;
90 | try {
91 | const options: CreateCollectionOptions & { type?: CollectionType } = {
92 | waitForSync: args.waitForSync || false,
93 | };
94 |
95 | // Map string type to CollectionType enum
96 | if (args.type === 'edge') {
97 | options.type = CollectionType.EDGE_COLLECTION;
98 | } else {
99 | // Default to document collection
100 | options.type = CollectionType.DOCUMENT_COLLECTION;
101 | }
102 |
103 | const collection = await this.db.createCollection(
104 | args.name,
105 | options as CreateCollectionOptions & {
106 | type: typeof options.type extends CollectionType.EDGE_COLLECTION ? CollectionType.EDGE_COLLECTION : CollectionType.DOCUMENT_COLLECTION;
107 | },
108 | );
109 |
110 | // Return a simplified response without circular references
111 | const properties = await collection.properties();
112 | const response = {
113 | name: collection.name,
114 | indexes: collection.indexes(),
115 | type: CollectionType[properties.type],
116 | status: CollectionStatus[properties.status],
117 | };
118 |
119 | return {
120 | content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
121 | };
122 | } catch (error) {
123 | throw new McpError(ErrorCode.InvalidRequest, `Failed to create collection: ${error instanceof Error ? error.message : 'Unknown error'}`);
124 | }
125 | }
126 |
127 | case API_TOOLS.BACKUP: {
128 | const args = request.params.arguments as BackupArgs;
129 | const outputDir = resolve(args.outputDir);
130 | const collection = args.collection;
131 | const docLimit = args.docLimit;
132 |
133 | try {
134 | await fs.mkdir(outputDir, { recursive: true, mode: 0o755 });
135 | } catch (error) {
136 | throw new McpError(ErrorCode.InternalError, `Failed to create backup directory: ${error instanceof Error ? error.message : 'Unknown error'}`);
137 | }
138 |
139 | try {
140 | const results = [];
141 | async function backupCollection(db: Database, outputDir: string, collection?: string, docLimit?: number) {
142 | try {
143 | const cursor = await db.query({
144 | query: docLimit ? 'FOR doc IN @@collection LIMIT @limit RETURN doc' : 'FOR doc IN @@collection RETURN doc',
145 | bindVars: {
146 | '@collection': collection,
147 | ...(docLimit && { limit: docLimit }),
148 | },
149 | });
150 | const data = await cursor.all();
151 | const filePath = join(outputDir, `${collection}.json`);
152 | await fs.writeFile(filePath, JSON.stringify(data, null, 2));
153 | return {
154 | collection,
155 | status: 'success',
156 | count: data.length,
157 | outputFile: filePath,
158 | };
159 | } catch (error) {
160 | return {
161 | collection,
162 | status: 'error',
163 | error: error instanceof Error ? error.message : 'Unknown error',
164 | };
165 | }
166 | }
167 |
168 | if (collection) {
169 | // Backup single collection
170 | console.info(`Backing up collection: ${collection}`);
171 | results.push(await backupCollection(this.db, outputDir, collection, docLimit));
172 | } else {
173 | // Backup all collections in parallel chunks
174 | const collections = await this.db.listCollections();
175 | console.info(`Found ${collections.length} collections to backup.`);
176 |
177 | // Process collections in chunks
178 | for (let i = 0; i < collections.length; i += PARALLEL_BACKUP_CHUNKS) {
179 | const chunk = collections.slice(i, i + PARALLEL_BACKUP_CHUNKS);
180 | const backupPromises = chunk.map((collection) => {
181 | console.info(`Backing up collection: ${collection.name}`);
182 | return backupCollection(this.db, outputDir, collection.name, docLimit);
183 | });
184 |
185 | // Wait for the current chunk to complete before processing the next
186 | const chunkResults = await Promise.all(backupPromises);
187 | results.push(...chunkResults);
188 | }
189 | }
190 |
191 | return {
192 | content: [
193 | {
194 | type: 'text',
195 | text: JSON.stringify(
196 | {
197 | status: 'completed',
198 | outputDirectory: outputDir,
199 | results,
200 | },
201 | null,
202 | 2,
203 | ),
204 | },
205 | ],
206 | };
207 | } catch (error) {
208 | throw new McpError(ErrorCode.InternalError, `Backup failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
209 | }
210 | }
211 |
212 | default:
213 | throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params?.name}`);
214 | }
215 | } catch (error: unknown) {
216 | if (error instanceof McpError) throw error;
217 |
218 | // Check if it's a connection error
219 | if (error instanceof Error && error.message.includes('connect')) {
220 | throw new McpError(ErrorCode.InternalError, `Database connection lost: ${error.message}`);
221 | }
222 |
223 | throw new McpError(ErrorCode.InternalError, `Database error: ${error instanceof Error ? error.message : 'Unknown error'}`);
224 | }
225 | }
226 | }
227 |
```