Skip to main content
Glama

Dart MCP Server

by its-dart
index.ts14.2 kB
#!/usr/bin/env node import "dotenv/config"; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { ListToolsRequestSchema, CallToolRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, ResourceTemplate, ListPromptsRequestSchema, GetPromptRequestSchema, Prompt, } from "@modelcontextprotocol/sdk/types.js"; import { readFileSync } from "fs"; import { join, dirname } from "path"; import { fileURLToPath } from "url"; import { ApiError, CommentCreate, CommentService, ConfigService, DartboardService, DocCreate, DocService, DocUpdate, FolderService, TaskCreate, TaskService, TaskUpdate, ViewService, } from "dart-tools"; import { ADD_TASK_COMMENT_TOOL, CREATE_DOC_TOOL, CREATE_TASK_TOOL, DELETE_DOC_TOOL, DELETE_TASK_TOOL, GET_CONFIG_TOOL, GET_DARTBOARD_TOOL, GET_DOC_TOOL, GET_FOLDER_TOOL, GET_TASK_TOOL, GET_VIEW_TOOL, LIST_DOCS_TOOL, LIST_TASK_COMMENTS_TOOL, LIST_TASKS_TOOL, UPDATE_DOC_TOOL, UPDATE_TASK_TOOL, } from "./tools.js"; const ID_REGEX = /^[a-zA-Z0-9]{12}$/; const token = process.env.DART_TOKEN; if (!token) { console.error("DART_TOKEN environment variable is required"); process.exit(1); } const filename = fileURLToPath(import.meta.url); const packageJson = JSON.parse( readFileSync(join(dirname(filename), "..", "package.json"), "utf-8"), ); const getIdValidated = (strMaybe: any, name: string = "ID"): string => { if (typeof strMaybe !== "string" && !(strMaybe instanceof String)) { throw new Error(`${name} must be a string`); } const id = strMaybe.toString(); if (!ID_REGEX.test(id)) { throw new Error(`${name} must be 12 alphanumeric characters`); } return id; }; // Prompts const CREATE_TASK_PROMPT: Prompt = { name: "Create task", description: "Create a new task in Dart", arguments: [ { name: "title", description: "Title of the task", required: true, }, { name: "description", description: "Description of the task", required: false, }, { name: "status", description: "Status of the task", required: false, }, { name: "priority", description: "Priority of the task", required: false, }, { name: "assignee", description: "Email of the assignee", required: false, }, ], }; const CREATE_DOC_PROMPT: Prompt = { name: "Create doc", description: "Create a new document in Dart", arguments: [ { name: "title", description: "Title of the document", required: true, }, { name: "text", description: "Content of the document", required: false, }, { name: "folder", description: "Folder to place the document in", required: false, }, ], }; const SUMMARIZE_TASKS_PROMPT: Prompt = { name: "Summarize tasks", description: "Get a summary of tasks with optional filtering", arguments: [ { name: "status", description: "Filter by status (e.g., 'In Progress', 'Done')", required: false, }, { name: "assignee", description: "Filter by assignee email", required: false, }, ], }; // Resources const CONFIG_PROTOCOL = "dart-config"; const CONFIG_RESOURCE_TEMPLATE: ResourceTemplate = { uriTemplate: `${CONFIG_PROTOCOL}:`, name: "Dart config", description: "Information about the authenticated user associated with the API key, including their role, teams, and settings.", parameters: {}, examples: [`${CONFIG_PROTOCOL}:`], }; const TASK_PROTOCOL = "dart-task:"; const TASK_RESOURCE_TEMPLATE: ResourceTemplate = { uriTemplate: `${TASK_PROTOCOL}///{taskId}`, name: "Dart task", description: "A Dart task with its title, description, status, priority, dates, and more. Use this to fetch detailed information about a specific task.", parameters: { taskId: { type: "string", description: "The unique identifier of the Dart task", }, }, examples: [`${TASK_PROTOCOL}///9q5qtB8n2Qn6`], }; const DOC_PROTOCOL = "dart-doc:"; const DOC_RESOURCE_TEMPLATE: ResourceTemplate = { uriTemplate: `${DOC_PROTOCOL}///{docId}`, name: "Dart doc", description: "A Dart doc with its title, text content, and folder. Use this to fetch detailed information about a specific doc.", parameters: { docId: { type: "string", description: "The unique identifier of the Dart doc", }, }, examples: [`${DOC_PROTOCOL}///9q5qtB8n2Qn6`], }; // Tools const TOOLS = [ // Config GET_CONFIG_TOOL, // Tasks CREATE_TASK_TOOL, LIST_TASKS_TOOL, GET_TASK_TOOL, UPDATE_TASK_TOOL, DELETE_TASK_TOOL, // Docs CREATE_DOC_TOOL, LIST_DOCS_TOOL, GET_DOC_TOOL, UPDATE_DOC_TOOL, DELETE_DOC_TOOL, // Comments ADD_TASK_COMMENT_TOOL, LIST_TASK_COMMENTS_TOOL, // Other GET_DARTBOARD_TOOL, GET_FOLDER_TOOL, GET_VIEW_TOOL, ]; const NO_ARGS_TOOL_NAMES = new Set( TOOLS.filter( (tool) => !tool.inputSchema.properties || Object.keys(tool.inputSchema.properties).length === 0, ).map((tool) => tool.name), ); // Server const server = new Server( { name: "dart-mcp", version: packageJson.version, }, { capabilities: { prompts: {}, resources: {}, tools: {}, }, }, ); server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: [CREATE_TASK_PROMPT, CREATE_DOC_PROMPT, SUMMARIZE_TASKS_PROMPT], })); server.setRequestHandler(GetPromptRequestSchema, async (request) => { const { name, arguments: args } = request.params; if (name === CREATE_TASK_PROMPT.name) { const title = args?.title || "(no title)"; const description = args?.description || ""; const status = args?.status || ""; const priority = args?.priority || ""; const assignee = args?.assignee || ""; return { description: "Create a new task in Dart", messages: [ { role: "user", content: { type: "text", text: `Create a new task in Dart with the following details: Title: ${title} ${description ? `Description: ${description}` : ""} ${status ? `Status: ${status}` : ""} ${priority ? `Priority: ${priority}` : ""} ${assignee ? `Assignee: ${assignee}` : ""}`, }, }, ], }; } if (name === CREATE_DOC_PROMPT.name) { const title = args?.title || "(no title)"; const text = args?.text || ""; const folder = args?.folder || ""; return { description: "Create a new document in Dart", messages: [ { role: "user", content: { type: "text", text: `Create a new document in Dart with the following details: Title: ${title} ${text ? `Content: ${text}` : ""} ${folder ? `Folder: ${folder}` : ""}`, }, }, ], }; } if (name === SUMMARIZE_TASKS_PROMPT.name) { const status = args?.status || ""; const assignee = args?.assignee || ""; return { description: "Get a summary of tasks with optional filtering", messages: [ { role: "user", content: { type: "text", text: `Summarize the tasks in Dart${status ? ` with status "${status}"` : ""}${assignee ? ` assigned to ${assignee}` : ""}. Please include the total count, group by status, and list any high priority items.`, }, }, ], }; } throw new Error(`Unknown prompt: ${name}`); }); server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({ resourceTemplates: [ CONFIG_RESOURCE_TEMPLATE, TASK_RESOURCE_TEMPLATE, DOC_RESOURCE_TEMPLATE, ], })); server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; const url = new URL(uri); const path = url.pathname.replace(/^\//, ""); const { protocol } = url; if (protocol === CONFIG_PROTOCOL) { const config = await ConfigService.getConfig(); return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(config, null, 2), }, ], }; } if (protocol === TASK_PROTOCOL) { const task = await TaskService.getTask(path); return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(task, null, 2), }, ], }; } if (protocol === DOC_PROTOCOL) { const doc = await DocService.getDoc(path); return { contents: [ { uri, mimeType: "application/json", text: JSON.stringify(doc, null, 2), }, ], }; } throw new Error(`Unknown resource: ${uri}`); }); server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS, })); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: argsMaybe } = request.params; let args: Record<string, unknown>; try { if (argsMaybe) { args = argsMaybe; } else { if (!NO_ARGS_TOOL_NAMES.has(name)) { throw new Error("Arguments are required"); } else { args = {}; } } switch (name) { // Config case GET_CONFIG_TOOL.name: { const config = await ConfigService.getConfig(); return { content: [{ type: "text", text: JSON.stringify(config, null, 2) }], }; } // Tasks case CREATE_TASK_TOOL.name: { const taskData = args as TaskCreate; const task = await TaskService.createTask({ item: taskData }); return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }], }; } case LIST_TASKS_TOOL.name: { const tasks = await TaskService.listTasks(args); return { content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }], }; } case GET_TASK_TOOL.name: { const id = getIdValidated(args.id); const task = await TaskService.getTask(id); return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }], }; } case UPDATE_TASK_TOOL.name: { const id = getIdValidated(args.id); const taskData = args as TaskUpdate; const task = await TaskService.updateTask(id, { item: taskData, }); return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }], }; } case DELETE_TASK_TOOL.name: { const id = getIdValidated(args.id); const task = await TaskService.deleteTask(id); return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }], }; } // Docs case CREATE_DOC_TOOL.name: { const docData = args as DocCreate; const doc = await DocService.createDoc({ item: docData, }); return { content: [{ type: "text", text: JSON.stringify(doc, null, 2) }], }; } case LIST_DOCS_TOOL.name: { const docs = await DocService.listDocs(args); return { content: [{ type: "text", text: JSON.stringify(docs, null, 2) }], }; } case GET_DOC_TOOL.name: { const id = getIdValidated(args.id); const doc = await DocService.getDoc(id); return { content: [{ type: "text", text: JSON.stringify(doc, null, 2) }], }; } case UPDATE_DOC_TOOL.name: { const id = getIdValidated(args.id); const docData = args as DocUpdate; const doc = await DocService.updateDoc(id, { item: docData }); return { content: [{ type: "text", text: JSON.stringify(doc, null, 2) }], }; } case DELETE_DOC_TOOL.name: { const id = getIdValidated(args.id); const doc = await DocService.deleteDoc(id); return { content: [{ type: "text", text: JSON.stringify(doc, null, 2) }], }; } // Comments case ADD_TASK_COMMENT_TOOL.name: { const taskId = getIdValidated(args.taskId); const text = args.text; const commentData = { taskId, text } as CommentCreate; const comment = await CommentService.addTaskComment({ item: commentData, }); return { content: [{ type: "text", text: JSON.stringify(comment, null, 2) }], }; } case LIST_TASK_COMMENTS_TOOL.name: { const taskId = getIdValidated(args.taskId, "taskId"); const comments = await CommentService.listComments({ taskId, ...args }); return { content: [{ type: "text", text: JSON.stringify(comments, null, 2) }], }; } // Other tools case GET_DARTBOARD_TOOL.name: { const id = getIdValidated(args.id); const dartboard = await DartboardService.getDartboard(id); return { content: [{ type: "text", text: JSON.stringify(dartboard, null, 2) }], }; } case GET_FOLDER_TOOL.name: { const id = getIdValidated(args.id); const folder = await FolderService.getFolder(id); return { content: [{ type: "text", text: JSON.stringify(folder, null, 2) }], }; } case GET_VIEW_TOOL.name: { const id = getIdValidated(args.id); const view = await ViewService.getView(id); return { content: [{ type: "text", text: JSON.stringify(view, null, 2) }], }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { if (error instanceof ApiError) { throw new Error( `API error: ${error.status} ${JSON.stringify(error.body) || error.message || "(unknown error)"}`, ); } throw error; } }); async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Dart MCP Server running on stdio"); } runServer().catch((error) => { console.error("Unhandled error:", error); process.exit(1); });

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/its-dart/dart-mcp-server'

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