Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"description": "Model Context Protocol server for Directus projects.",
"contributors": [
"Rijk van Zanten <[email protected]>",
"Bryant Gillespie"
"Bryant Gillespie",
"Mustafa Öztürk"
],
"license": "MIT",
"keywords": [
Expand All @@ -27,7 +28,6 @@
"lint": "eslint .",
"lint:fix": "eslint --fix ."
},

"dependencies": {
"@directus/sdk": "19.1.0",
"@modelcontextprotocol/sdk": "1.10.2",
Expand Down
19 changes: 9 additions & 10 deletions src/directus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import type {
AuthenticationClient,
DirectusClient,
RestClient,
} from '@directus/sdk';
import type { Config } from './config.js';
import type { Schema } from './types/schema.js';
} from "@directus/sdk";
import type { Config } from "./config.js";
import type { Schema } from "./types/schema.js";
import {
authentication,
createDirectus as createSdk,
rest,
} from '@directus/sdk';
} from "@directus/sdk";

export type Directus = DirectusClient<Schema> &
RestClient<Schema> &
Expand All @@ -30,7 +30,7 @@ export const createDirectus = (config: Config) =>
*/
export async function authenticateDirectus(directus: Directus, config: Config) {
if (!directus || !config) {
throw new Error('Directus or config is not defined');
throw new Error("Directus or config is not defined");
}

// Token-based authentication
Expand All @@ -44,22 +44,21 @@ export async function authenticateDirectus(directus: Directus, config: Config) {
try {
await directus.login(
config.DIRECTUS_USER_EMAIL,
config.DIRECTUS_USER_PASSWORD,
config.DIRECTUS_USER_PASSWORD
);

return;
}
catch (error) {
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
throw new Error(
`Failed to authenticate with credentials: ${errorMessage}`,
`Failed to authenticate with credentials: ${errorMessage}`
);
}
}

// No valid authentication method
throw new Error(
'No valid authentication method provided (requires either DIRECTUS_TOKEN or both DIRECTUS_USER_EMAIL and DIRECTUS_USER_PASSWORD)',
"No valid authentication method provided (requires either DIRECTUS_TOKEN or both DIRECTUS_USER_EMAIL and DIRECTUS_USER_PASSWORD)"
);
}
54 changes: 25 additions & 29 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
#!/usr/bin/env node
import type { CallToolRequest } from '@modelcontextprotocol/sdk/types.js';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import type { CallToolRequest } from "@modelcontextprotocol/sdk/types.js";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
GetPromptRequestSchema,
ListPromptsRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { createConfig } from './config.js';
import { authenticateDirectus, createDirectus } from './directus.js';
import { getAvailablePrompts, handleGetPrompt } from './prompts/handlers.js';
import { fetchPrompts } from './prompts/index.js';
import { getTools } from './tools/index.js';
import { fetchSchema } from './utils/fetch-schema.js';
import { toMpcTools } from './utils/to-mpc-tools.js';
} from "@modelcontextprotocol/sdk/types.js";
import { createConfig } from "./config.js";
import { authenticateDirectus, createDirectus } from "./directus.js";
import { getAvailablePrompts, handleGetPrompt } from "./prompts/handlers.js";
import { fetchPrompts } from "./prompts/index.js";
import { getTools } from "./tools/index.js";
import { fetchSchema } from "./utils/fetch-schema.js";
import { toMpcTools } from "./utils/to-mpc-tools.js";

async function main() {
const config = createConfig();
Expand All @@ -26,16 +26,16 @@ async function main() {

const server = new Server(
{
name: 'Directus MCP Server',
version: '0.0.1',
name: "Directus MCP Server",
version: "0.0.1",
},
{
capabilities: {
tools: {},
resources: {},
prompts: {},
},
},
}
);

// Manage prompts
Expand All @@ -50,12 +50,7 @@ async function main() {
const promptName = request.params.name;
const args = request.params.arguments || {};

return await handleGetPrompt(
directus,
config,
promptName,
args,
);
return await handleGetPrompt(directus, config, promptName, args);
});

// Manage tool requests
Expand All @@ -75,24 +70,26 @@ async function main() {
// Proceed with execution if permission check passes
const { inputSchema, handler } = tool;
const args = inputSchema.parse(request.params.arguments);
return await handler(directus, args, { schema, baseUrl: config.DIRECTUS_URL });
}
catch (error) {
console.error('Error executing tool:', error);
return await handler(directus, args, {
schema,
baseUrl: config.DIRECTUS_URL,
});
} catch (error) {
console.error("Error executing tool:", error);
const errorMessage =
error instanceof Error ? error.message : JSON.stringify(error);

return {
content: [
{
type: 'text',
type: "text",
text: errorMessage,
},
],
isError: true,
};
}
},
}
);

// Return the pre-filtered list for listing purposes
Expand All @@ -107,8 +104,7 @@ async function main() {

try {
await main();
}
catch (error) {
console.error('Fatal error in main():', error);
} catch (error) {
console.error("Fatal error in main():", error);
process.exit(1);
}
67 changes: 67 additions & 0 deletions src/tools/collections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { createCollection, updateCollection } from "@directus/sdk";
import * as z from "zod";
import { defineTool } from "../utils/define.js";
import {
formatErrorResponse,
formatSuccessResponse,
} from "../utils/response.js";

export const createCollectionTool = defineTool("create-collection", {
description: `Create a new collection in Directus. The 'collection' and 'schema' properties are required. You can also provide an array of 'fields' to be created during the creation of the collection.`,
inputSchema: z.object({
collection: z.string().describe("Unique name of the collection."),
schema: z
.union([z.object({}), z.null()])
.describe("Schema definition for the collection, or null for a folder."),
fields: z
.array(
z.object({
field: z.string(),
type: z.string(),
interface: z.string(),
})
)
.optional()
.describe("Fields to be created in the collection."),
meta: z
.record(z.string(), z.unknown())
.optional()
.describe("Optional meta properties for the collection."),
}),
handler: async (directus, input) => {
try {
const { collection, schema, fields, meta } = input;
const collectionObject: any = {
collection,
schema,
};
if (fields) collectionObject.fields = fields;
if (meta) Object.assign(collectionObject, meta);
const result = await directus.request(createCollection(collectionObject));
return formatSuccessResponse(result);
} catch (error) {
return formatErrorResponse(error);
}
},
});

export const updateCollectionTool = defineTool("update-collection", {
description: `Update the metadata for an existing collection. Only the 'meta' values of the collection object can be updated. Updating the collection name is not supported at this time.`,
inputSchema: z.object({
collection: z.string().describe("Unique identifier of the collection."),
meta: z
.record(z.string(), z.unknown())
.describe("Metadata of the collection."),
}),
handler: async (directus, input) => {
try {
const { collection, meta } = input;
const result = await directus.request(
updateCollection(collection, { meta })
);
return formatSuccessResponse(result);
} catch (error) {
return formatErrorResponse(error);
}
},
});
58 changes: 28 additions & 30 deletions src/tools/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,27 @@ import {
readFields,
readFieldsByCollection,
updateField,
} from '@directus/sdk';
import * as z from 'zod';
} from "@directus/sdk";
import * as z from "zod";
import {
CreateFieldDataSchema,
UpdateFieldDataSchema,
} from '../types/fields.js';
import { defineTool } from '../utils/define.js';
} from "../types/fields.js";
import { defineTool } from "../utils/define.js";
import {
formatErrorResponse,
formatSuccessResponse,
} from '../utils/response.js';
} from "../utils/response.js";

export const readFieldsTool = defineTool('read-fields', {
export const readFieldsTool = defineTool("read-fields", {
description:
'Retrieve the field definitions for all collections or a specific collection. Note: This is lots of data and should be used sparingly. Use only if you cannot find the field information you need and you absolutely need to have the raw field definition.',
"Retrieve the field definitions for all collections or a specific collection. Note: This is lots of data and should be used sparingly. Use only if you cannot find the field information you need and you absolutely need to have the raw field definition.",
inputSchema: z.object({
collection: z
.string()
.optional()
.describe(
'Optional: The name (ID) of the collection to retrieve fields for. If omitted, fields for all collections are returned.',
"Optional: The name (ID) of the collection to retrieve fields for. If omitted, fields for all collections are returned."
),
}),
handler: async (directus, { collection }) => {
Expand All @@ -33,72 +33,70 @@ export const readFieldsTool = defineTool('read-fields', {
? await directus.request(readFieldsByCollection(collection))
: await directus.request(readFields());
return formatSuccessResponse(fields);
}
catch (error) {
} catch (error) {
return formatErrorResponse(error);
}
},
});

export const readFieldTool = defineTool('read-field', {
export const readFieldTool = defineTool("read-field", {
description:
'Retrieve the definition of a specific field within a collection.',
"Retrieve the definition of a specific field within a collection.",
inputSchema: z.object({
collection: z
.string()
.describe('The name (ID) of the collection the field belongs to.'),
field: z.string().describe('The name (ID) of the field to retrieve.'),
.describe("The name (ID) of the collection the field belongs to."),
field: z.string().describe("The name (ID) of the field to retrieve."),
}),
handler: async (directus, { collection, field }) => {
try {
const fieldData = await directus.request(readField(collection, field));
return formatSuccessResponse(fieldData);
}
catch (error) {
} catch (error) {
return formatErrorResponse(error);
}
},
});

export const createFieldTool = defineTool('create-field', {
description: 'Create a new field in a specified collection.',
export const createFieldTool = defineTool("create-field", {
description: "Create a new field in a specified collection.",

inputSchema: z.object({
collection: z
.string()
.describe('The name (ID) of the collection to add the field to.'),
.describe("The name (ID) of the collection to add the field to."),
data: CreateFieldDataSchema.describe(
'The data for the new field (field name, type, optional schema/meta).',
"The data for the new field (field name, type, optional schema/meta)."
),
}),
handler: async (directus, { collection, data }) => {
try {
const result = await directus.request(createField(collection, data));
return formatSuccessResponse(result);
}
catch (error) {
} catch (error) {
return formatErrorResponse(error);
}
},
});

export const updateFieldTool = defineTool('update-field', {
description: 'Update an existing field in a specified collection.',
export const updateFieldTool = defineTool("update-field", {
description: "Update an existing field in a specified collection.",
inputSchema: z.object({
collection: z
.string()
.describe('The name (ID) of the collection containing the field.'),
field: z.string().describe('The name (ID) of the field to update.'),
.describe("The name (ID) of the collection containing the field."),
field: z.string().describe("The name (ID) of the field to update."),
data: UpdateFieldDataSchema.describe(
'The partial data to update the field with (type, schema, meta).',
"The partial data to update the field with (type, schema, meta)."
),
}),
handler: async (directus, { collection, field, data }) => {
try {
const result = await directus.request(updateField(collection, field, data));
const result = await directus.request(
updateField(collection, field, data)
);
return formatSuccessResponse(result);
}
catch (error) {
} catch (error) {
return formatErrorResponse(error);
}
},
Expand Down
Loading