Dart MCP Server

  • src
#!/usr/bin/env node import { config } from 'dotenv'; import { resolve } from 'path'; // Load environment variables from /Users/speed/dart-tools/.env config({ path: '/Users/speed/dart-tools/.env' }); import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js'; import { spawn } from 'child_process'; class DartServer { constructor() { this.server = new Server({ name: 'dart-server', version: '0.1.0', }, { capabilities: { tools: {}, }, timeout: 120000, // Increase timeout to 2 minutes }); this.setupToolHandlers(); // Error handling this.server.onerror = (error) => console.error('[MCP Error]', error); process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); // Check Python environment on startup this.checkPythonEnvironment().catch(console.error); } async checkPythonEnvironment() { const command = `import sys import os import traceback try: print("[Debug] Python version:", sys.version, file=sys.stderr) print("[Debug] Python executable:", sys.executable, file=sys.stderr) print("[Debug] PYTHONPATH:", os.environ.get('PYTHONPATH'), file=sys.stderr) print("[Debug] Current directory:", os.getcwd(), file=sys.stderr) print("[Debug] DART_TOKEN:", os.environ.get('DART_TOKEN'), file=sys.stderr) import dart print("[Debug] dart package version:", getattr(dart, '__version__', 'unknown'), file=sys.stderr) from dart import Dart client = Dart() print("[Debug] Successfully created Dart client", file=sys.stderr) except Exception as e: print(f"[Debug] Environment check failed: {str(e)}", file=sys.stderr) traceback.print_exc(file=sys.stderr) sys.exit(1)`; try { // Use pyenv Python const pythonPath = '/Users/speed/.pyenv/shims/python'; console.error('[Debug] Running environment check with:', pythonPath); console.error('[Debug] Environment check command:', command); // Create a clean environment without virtual env variables const env = { ...process.env }; delete env.VIRTUAL_ENV; delete env.CONDA_PREFIX; delete env.CONDA_DEFAULT_ENV; delete env.CONDA_PYTHON_EXE; const childProcess = spawn(pythonPath, ['-c', command], { env: { ...env, PYTHONUNBUFFERED: '1', PYTHONPATH: process.env.PYTHONPATH || process.cwd(), DART_TOKEN: process.env.DART_TOKEN, }, stdio: ['pipe', 'pipe', 'pipe'], }); let output = ''; let errorOutput = ''; childProcess.stdout?.on('data', (data) => { const str = data.toString(); console.error('[Debug] Environment check stdout:', str); output += str; }); childProcess.stderr?.on('data', (data) => { const str = data.toString(); console.error('[Debug] Environment check stderr:', str); errorOutput += str; }); await new Promise((resolve, reject) => { childProcess.on('error', (error) => { console.error('[Debug] Environment check error:', error); reject(new Error(`Failed to start Python process: ${error.message}`)); }); childProcess.on('close', (code) => { console.error(`[Debug] Environment check exited with code ${code}`); if (code === 0) { resolve(output.trim()); } else { reject(new Error(errorOutput || `Environment check failed with exit code ${code}`)); } }); }); console.error('[Debug] Python environment check passed'); } catch (error) { console.error('[Debug] Python environment check failed:', error); throw error; } } async runDartCommand(args) { return new Promise((resolve, reject) => { // Use pyenv Python const pythonPath = '/Users/speed/.pyenv/shims/python'; console.error('[Debug] Running Python command with:', pythonPath); const command = `import sys import os import traceback import json from dart import Dart, Operation, OperationKind, OperationModelKind, TaskCreate, TaskUpdate, TransactionKind, TaskSourceType, SpaceCreate from dart.generated.types import UNSET from dart.dart import _Session, UserBundle, _make_duid from dart.generated.models.icon_kind import IconKind from dart.generated.models.sprint_mode import SprintMode from dart.generated.models.validation_error_response import ValidationErrorResponse def initialize(): print("[Debug] Starting Python execution", file=sys.stderr) print("[Debug] Current directory:", os.getcwd(), file=sys.stderr) print("[Debug] PYTHONPATH:", os.environ.get('PYTHONPATH'), file=sys.stderr) print("[Debug] DART_TOKEN:", os.environ.get('DART_TOKEN'), file=sys.stderr) session = _Session() print("[Debug] Session created", file=sys.stderr) bundle = UserBundle(session) print("[Debug] UserBundle created", file=sys.stderr) dartboard_duid = bundle.default_dartboard["duid"] print(f"[Debug] Got dartboard DUID: {dartboard_duid}", file=sys.stderr) client = Dart() print("[Debug] Dart client created", file=sys.stderr) return client, bundle, dartboard_duid def run_command(client, bundle, dartboard_duid): ${args} def main(): client, bundle, dartboard_duid = initialize() run_command(client, bundle, dartboard_duid) try: main() except Exception as e: print(f"Error: {str(e)}", file=sys.stderr) traceback.print_exc(file=sys.stderr) sys.exit(1)`; console.error('[Debug] Python command:', command); // Create a clean environment without virtual env variables const env = { ...process.env }; delete env.VIRTUAL_ENV; delete env.CONDA_PREFIX; delete env.CONDA_DEFAULT_ENV; delete env.CONDA_PYTHON_EXE; const childProcess = spawn(pythonPath, ['-c', command], { env: { ...env, PYTHONUNBUFFERED: '1', PYTHONPATH: process.env.PYTHONPATH || process.cwd(), DART_TOKEN: process.env.DART_TOKEN, }, stdio: ['pipe', 'pipe', 'pipe'], }); let output = ''; let errorOutput = ''; childProcess.stdout?.on('data', (data) => { const str = data.toString(); console.error('[Debug] Python stdout:', str); output += str; }); childProcess.stderr?.on('data', (data) => { const str = data.toString(); console.error('[Debug] Python stderr:', str); errorOutput += str; }); childProcess.on('error', (error) => { console.error('[Debug] Python process error:', error); reject(new Error(`Failed to start Python process: ${error.message}`)); }); // Add timeout const timeout = setTimeout(() => { console.error('[Debug] Python command timed out'); childProcess.kill(); reject(new Error('Command timed out')); }, 30000); // 30 second timeout childProcess.on('close', (code) => { clearTimeout(timeout); console.error(`[Debug] Python process exited with code ${code}`); if (code === 0) { resolve(output.trim()); } else { reject(new Error(errorOutput || `Command failed with exit code ${code}`)); } }); }); } setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => { console.error('[Debug] Handling listTools request'); const tools = [ { name: 'get_default_status', description: 'Get the default status DUIDs', inputSchema: { type: 'object', properties: {}, required: [], }, }, { name: 'get_default_space', description: 'Get the default space DUID', inputSchema: { type: 'object', properties: {}, required: [], }, }, { name: 'create_task', description: 'Create a new Dart task', inputSchema: { type: 'object', properties: { title: { type: 'string', description: 'Title of the task', }, description: { type: 'string', description: 'Description of the task', }, priority: { type: 'string', description: 'Priority of the task', enum: ['Low', 'Medium', 'High', 'Critical'], }, tags: { type: 'array', items: { type: 'string', }, description: 'Tags for the task', }, size: { type: 'number', description: 'Size/complexity of the task (1-5)', minimum: 1, maximum: 5, }, assignee_duids: { type: 'array', items: { type: 'string', }, description: 'List of assignee DUIDs', }, subscriber_duids: { type: 'array', items: { type: 'string', }, description: 'List of subscriber DUIDs', } }, required: ['title', 'description'], }, }, { name: 'update_task', description: 'Update an existing task', inputSchema: { type: 'object', properties: { duid: { type: 'string', description: 'DUID of the task to update', }, status_duid: { type: 'string', description: 'New status DUID', }, title: { type: 'string', description: 'New title for the task', }, description: { type: 'string', description: 'New description for the task', }, priority: { type: 'string', description: 'New priority for the task', enum: ['Low', 'Medium', 'High', 'Critical'], } }, required: ['duid'], }, }, { name: 'get_dartboards', description: 'Get available dartboards', inputSchema: { type: 'object', properties: { space_duid: { type: 'string', description: 'Space DUID to get dartboards from', } }, required: ['space_duid'], }, }, { name: 'get_folders', description: 'Get available folders', inputSchema: { type: 'object', properties: { space_duid: { type: 'string', description: 'Space DUID to get folders from', } }, required: ['space_duid'], }, }, { name: 'create_folder', description: 'Create a new folder in a space', inputSchema: { type: 'object', properties: { space_duid: { type: 'string', description: 'Space DUID to create the folder in', }, title: { type: 'string', description: 'Title of the folder', }, description: { type: 'string', description: 'Description of the folder', }, kind: { type: 'string', description: 'Kind of folder', enum: ['Default', 'Reports', 'Other'], default: 'Default' } }, required: ['space_duid', 'title'], }, }, { name: 'create_doc', description: 'Create a new document or report', inputSchema: { type: 'object', properties: { folder_duid: { type: 'string', description: 'Folder DUID to create the document in', }, title: { type: 'string', description: 'Title of the document', }, text: { type: 'string', description: 'Content of the document', }, text_markdown: { type: 'string', description: 'Markdown content of the document', }, report_kind: { type: 'string', description: 'Kind of report (if creating a report)', enum: ['Changelog', 'Standup'], }, editor_duids: { type: 'array', items: { type: 'string', }, description: 'List of editor DUIDs', }, subscriber_duids: { type: 'array', items: { type: 'string', }, description: 'List of subscriber DUIDs', } }, required: ['folder_duid', 'title'], }, }, { name: 'create_space', description: 'Create a new space', inputSchema: { type: 'object', properties: { title: { type: 'string', description: 'Title of the space' }, description: { type: 'string', description: 'Description of the space' }, abrev: { type: 'string', description: 'Short abbreviation for the space' }, accessible_by_team: { type: 'boolean', description: 'Whether the space is accessible by the whole team', default: true }, accessible_by_user_duids: { type: 'array', items: { type: 'string' }, description: 'List of user DUIDs who can access the space' }, icon_kind: { type: 'string', enum: ['None', 'Icon', 'Emoji'], description: 'Kind of icon to use', default: 'None' }, icon_name_or_emoji: { type: 'string', description: 'Icon name or emoji character' }, color_hex: { type: 'string', description: 'Color in hex format (e.g. #FF0000)' }, sprint_mode: { type: 'string', enum: ['None', 'ANBA'], description: 'Sprint mode for the space', default: 'None' }, sprint_replicate_on_rollover: { type: 'boolean', description: 'Whether to replicate sprints on rollover', default: false }, sprint_name_fmt: { type: 'string', description: 'Sprint name format' } }, required: ['title'] } }, { name: 'delete_space', description: 'Delete a space and all its contents', inputSchema: { type: 'object', properties: { space_duid: { type: 'string', description: 'DUID of the space to delete' } }, required: ['space_duid'] } } ]; console.error('[Debug] Sending tools response:', JSON.stringify(tools, null, 2)); return { tools }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; console.error('[Debug] Tool request:', name, JSON.stringify(args, null, 2)); switch (name) { case 'create_task': { console.error('[Debug] Handling create_task request'); const pythonCode = ` # Parse optional parameters from JSON tags_json = '''${JSON.stringify(args.tags || [])}''' assignee_duids_json = '''${JSON.stringify(args.assignee_duids || [])}''' subscriber_duids_json = '''${JSON.stringify(args.subscriber_duids || [])}''' # Convert JSON strings to Python objects tags = json.loads(tags_json) if tags_json.strip() != '[]' else [] assignee_duids = json.loads(assignee_duids_json) if assignee_duids_json.strip() != '[]' else [] subscriber_duids = json.loads(subscriber_duids_json) if subscriber_duids_json.strip() != '[]' else [] print("[Debug] Creating task object", file=sys.stderr) # Create the task task = TaskCreate( source_type=TaskSourceType.CLI, duid=_make_duid(), # Generate a new DUID for the task dartboard_duid=dartboard_duid, # Set the dartboard DUID separately title='''${args.title.replace(/'/g, "\\'")}''', description='''${args.description.replace(/'/g, "\\'")}''', priority="${args.priority || 'Medium'}", size=${args.size || 1} ) # Add optional parameters if they exist if tags: print("[Debug] Adding tags:", tags, file=sys.stderr) task.tag_duids = tags if assignee_duids: print("[Debug] Adding assignees:", assignee_duids, file=sys.stderr) task.assignee_duids = assignee_duids if subscriber_duids: print("[Debug] Adding subscribers:", subscriber_duids, file=sys.stderr) task.subscriber_duids = subscriber_duids print("[Debug] Task object created:", task, file=sys.stderr) print("[Debug] Creating operation", file=sys.stderr) task_op = Operation( model=OperationModelKind.TASK, kind=OperationKind.CREATE, data=task ) print("[Debug] Operation created:", task_op, file=sys.stderr) print("[Debug] Executing transaction", file=sys.stderr) response = client.transact([task_op], TransactionKind.TASK_CREATE) print("[Debug] Transaction completed", file=sys.stderr) if response.results and response.results[0].success: task = response.results[0].models.tasks[0] print(f"Task created successfully with DUID: {task.duid}") print(f"[Debug] Task DUID: {task.duid}", file=sys.stderr) else: print("[Debug] Task creation failed", file=sys.stderr) if response.results: print(f"[Debug] Result: {response.results[0]}", file=sys.stderr) sys.exit(1)`; // Add proper indentation to the Python code const command = pythonCode.split('\n').map(line => line.length > 0 ? ' ' + line : line).join('\n'); console.error('[Debug] Running Python command for task creation'); const output = await this.runDartCommand(command); console.error('[Debug] Task creation output:', output); const response = { content: [{ type: 'text', text: output, }], }; console.error('[Debug] Sending task creation response:', JSON.stringify(response, null, 2)); return response; } case 'update_task': { console.error('[Debug] Handling update_task request'); const command = `print("[Debug] Starting task update", file=sys.stderr) try: # Get initialized client and bundle client, bundle, dartboard_duid = initialize() print("[Debug] Got client and bundle", file=sys.stderr) print("[Debug] Creating task update object", file=sys.stderr) task_update = TaskUpdate( duid="${args.duid}", source_type=UNSET ) if ${args.status_duid !== undefined ? 'True' : 'False'}: task_update.status_duid = "${args.status_duid}" if ${args.title !== undefined ? 'True' : 'False'}: task_update.title = "${args.title}" if ${args.description !== undefined ? 'True' : 'False'}: task_update.description = "${args.description}" if ${args.priority !== undefined ? 'True' : 'False'}: task_update.priority = "${args.priority}" print("[Debug] Task update object created:", task_update, file=sys.stderr) print("[Debug] Creating operation", file=sys.stderr) task_update_op = Operation( model=OperationModelKind.TASK, kind=OperationKind.UPDATE, data=task_update ) print("[Debug] Operation created:", task_update_op, file=sys.stderr) print("[Debug] Executing transaction", file=sys.stderr) response = client.transact([task_update_op], TransactionKind.TASK_UPDATE) print("[Debug] Transaction completed", file=sys.stderr) if response.results and response.results[0].success: print(f"Task updated successfully") print(f"[Debug] Task update successful", file=sys.stderr) else: print("[Debug] Task update failed", file=sys.stderr) if response.results: print(f"[Debug] Result: {response.results[0]}", file=sys.stderr) print("Failed to update task", file=sys.stderr) sys.exit(1) except Exception as e: print(f"[Debug] Task update error: {str(e)}", file=sys.stderr) print("[Debug] Error type:", type(e), file=sys.stderr) traceback.print_exc(file=sys.stderr) sys.exit(1)`; console.error('[Debug] Running Python command for task update'); const output = await this.runDartCommand(command); console.error('[Debug] Task update output:', output); const response = { content: [{ type: 'text', text: output, }], }; console.error('[Debug] Sending task update response:', JSON.stringify(response, null, 2)); return response; } case 'get_default_status': { console.error('[Debug] Handling get_default_status request'); const command = `print("[Debug] Getting default statuses", file=sys.stderr) try: # Get initialized client and bundle client, bundle, dartboard_duid = initialize() print("[Debug] Got bundle", file=sys.stderr) statuses = bundle.default_statuses print("[Debug] Got statuses:", statuses, file=sys.stderr) if not statuses: print("No statuses found") sys.exit(1) # Map status kinds to their DUIDs status_map = {} for status in statuses: if status["kind"] == "Unstarted": status_map["todo"] = status["duid"] elif status["kind"] == "Started": status_map["doing"] = status["duid"] elif status["kind"] == "Finished": status_map["done"] = status["duid"] print(f"Status DUIDs:") print(f"Todo: {status_map.get('todo', 'N/A')}") print(f"Doing: {status_map.get('doing', 'N/A')}") print(f"Done: {status_map.get('done', 'N/A')}") except Exception as e: print(f"[Debug] Error getting default statuses: {str(e)}", file=sys.stderr) print("[Debug] Error type:", type(e), file=sys.stderr) traceback.print_exc(file=sys.stderr) sys.exit(1)`; console.error('[Debug] Running Python command for getting default statuses'); const output = await this.runDartCommand(command); console.error('[Debug] Get default statuses output:', output); const response = { content: [{ type: 'text', text: output, }], }; return response; } case 'get_default_space': { console.error('[Debug] Handling get_default_space request'); // Note: Using raw string with proper indentation for the run_command function const pythonCode = ` # Get default space print("[Debug] Getting default space", file=sys.stderr) try: spaces = bundle.spaces print("[Debug] Got spaces:", spaces, file=sys.stderr) if not spaces: print("No spaces found") sys.exit(1) default_space = spaces[0] print(f"Default space DUID: {default_space['duid']}") except Exception as e: print(f"[Debug] Error getting space: {str(e)}", file=sys.stderr) sys.exit(1)`; // Add proper indentation to the Python code const command = pythonCode.split('\n').map(line => line.length > 0 ? ' ' + line : line).join('\n'); console.error('[Debug] Running Python command for getting default space'); const output = await this.runDartCommand(command); console.error('[Debug] Get default space output:', output); const response = { content: [{ type: 'text', text: output, }], }; return response; } case 'get_dartboards': { console.error('[Debug] Handling get_dartboards request'); const pythonCode = ` # Get dartboards for the space print("[Debug] Getting dartboards", file=sys.stderr) try: # Get dartboards from the bundle dartboards = bundle.dartboards print("[Debug] Got dartboards:", dartboards, file=sys.stderr) if not dartboards: print("No dartboards found") sys.exit(1) # Print dartboard titles print("Available dartboards:") for d in dartboards: print(f"- {d['title']} (DUID: {d['duid']})") except Exception as e: print(f"[Debug] Error getting dartboards: {str(e)}", file=sys.stderr) sys.exit(1)`; // Add proper indentation to the Python code const command = pythonCode.split('\n').map(line => line.length > 0 ? ' ' + line : line).join('\n'); console.error('[Debug] Running Python command for getting dartboards'); const output = await this.runDartCommand(command); console.error('[Debug] Get dartboards output:', output); const response = { content: [{ type: 'text', text: output, }], }; return response; } case 'get_folders': { console.error('[Debug] Handling get_folders request'); const pythonCode = ` # Get folders for the space print("[Debug] Getting folders", file=sys.stderr) try: # Get folders using the get_folders function from dart module space_duid = "${args.space_duid}" print(f"[Debug] Getting folders for space: {space_duid}", file=sys.stderr) from dart import get_folders folders = get_folders(space_duid, include_special=True) # Include all folders print("[Debug] Got folders:", folders, file=sys.stderr) # Print folder titles or indicate no folders found if not folders: print("No folders found in this space") else: print("Available folders:") for folder in folders: print(f"- {folder.title} (DUID: {folder.duid}, Kind: {folder.kind})") except Exception as e: print(f"[Debug] Error getting folders: {str(e)}", file=sys.stderr) traceback.print_exc(file=sys.stderr) sys.exit(1)`; // Add proper indentation to the Python code const command = pythonCode.split('\n').map(line => line.length > 0 ? ' ' + line : line).join('\n'); console.error('[Debug] Running Python command for getting folders'); const output = await this.runDartCommand(command); console.error('[Debug] Get folders output:', output); const response = { content: [{ type: 'text', text: output, }], }; return response; } case 'create_folder': { console.error('[Debug] Handling create_folder request'); const pythonCode = ` # Create a new folder print("[Debug] Creating folder", file=sys.stderr) try: from dart import Operation, OperationKind, OperationModelKind, FolderCreate, TransactionKind from dart.generated.models.folder_kind import FolderKind from dart.generated.models.validation_error_response import ValidationErrorResponse from dart.dart import _make_duid space_duid = "${args.space_duid}" title = '''${args.title.replace(/'/g, "\\'")}''' description = '''${args.description ? args.description.replace(/'/g, "\\'") : ''}''' kind_str = "${args.kind || 'Default'}" print(f"[Debug] Creating folder in space: {space_duid}", file=sys.stderr) print(f"[Debug] Folder title: {title}", file=sys.stderr) print(f"[Debug] Folder kind: {kind_str}", file=sys.stderr) # Create the folder object with required fields folder = FolderCreate( duid=_make_duid(), space_duid=space_duid, order="0", # Default order at the top title=title, description=description if description else "", # Use empty string instead of None kind=FolderKind(kind_str) # Convert string to enum ) print("[Debug] Folder object created:", folder, file=sys.stderr) # Create the operation folder_op = Operation( model=OperationModelKind.FOLDER, kind=OperationKind.CREATE, data=folder ) print("[Debug] Operation created:", folder_op, file=sys.stderr) # Execute the transaction print("[Debug] Executing transaction", file=sys.stderr) try: response = client.transact([folder_op], TransactionKind.FOLDER_CREATE) print("[Debug] Transaction completed", file=sys.stderr) print("[Debug] Response type:", type(response), file=sys.stderr) print("[Debug] Response:", response, file=sys.stderr) if isinstance(response, ValidationErrorResponse): print("[Debug] Validation error:", response.items.additional_properties, file=sys.stderr) print(f"Validation error: {response.items.additional_properties}") sys.exit(1) if response.results and response.results[0].success: folder = response.results[0].models.folders[0] print(f"Folder created successfully") print(f"Title: {folder.title}") print(f"DUID: {folder.duid}") print(f"[Debug] Folder DUID: {folder.duid}", file=sys.stderr) else: print("[Debug] Folder creation failed", file=sys.stderr) if response.results: print(f"[Debug] Result: {response.results[0]}", file=sys.stderr) sys.exit(1) except Exception as e: print(f"[Debug] Transaction error: {str(e)}", file=sys.stderr) print("[Debug] Error type:", type(e), file=sys.stderr) traceback.print_exc(file=sys.stderr) sys.exit(1) except Exception as e: print(f"[Debug] Error creating folder: {str(e)}", file=sys.stderr) print("[Debug] Error type:", type(e), file=sys.stderr) traceback.print_exc(file=sys.stderr) sys.exit(1)`; // Add proper indentation to the Python code const command = pythonCode.split('\n').map(line => line.length > 0 ? ' ' + line : line).join('\n'); console.error('[Debug] Running Python command for folder creation'); const output = await this.runDartCommand(command); console.error('[Debug] Folder creation output:', output); const response = { content: [{ type: 'text', text: output, }], }; return response; } case 'create_doc': { console.error('[Debug] Handling create_doc request'); // Validate required fields if (!args.folder_duid) { throw new McpError(ErrorCode.INVALID_PARAMS, "folder_duid is required"); } if (!args.title) { throw new McpError(ErrorCode.INVALID_PARAMS, "title is required"); } const pythonCode = ` # Create a new document print("[Debug] Creating document", file=sys.stderr) try: from dart import Operation, OperationKind, OperationModelKind, DocCreate, TransactionKind, DocSourceType from dart.generated.models.report_kind import ReportKind from dart.generated.models.validation_error_response import ValidationErrorResponse from dart.dart import _make_duid folder_duid = "${args.folder_duid}" title = '''${args.title.replace(/'/g, "\\'")}''' # title is validated above text = '''${args.text ? args.text.replace(/'/g, "\\'") : ''}''' text_markdown = '''${args.text_markdown ? args.text_markdown.replace(/'/g, "\\'") : ''}''' report_kind_str = "${args.report_kind || ''}" print(f"[Debug] Creating document in folder: {folder_duid}", file=sys.stderr) print(f"[Debug] Document title: {title}", file=sys.stderr) if report_kind_str: print(f"[Debug] Report kind: {report_kind_str}", file=sys.stderr) # Create the document object doc = DocCreate( duid=_make_duid(), folder_duid=folder_duid, title=title, source_type=DocSourceType.APPLICATION, order="0" # Add order field like in create_folder ) # Add optional fields if text: doc.text = text if text_markdown: doc.text_markdown = text_markdown if report_kind_str: doc.report_kind = ReportKind(report_kind_str) # Add editor and subscriber DUIDs if provided editor_duids_json = '''${JSON.stringify(args.editor_duids || [])}''' subscriber_duids_json = '''${JSON.stringify(args.subscriber_duids || [])}''' editor_duids = json.loads(editor_duids_json) subscriber_duids = json.loads(subscriber_duids_json) if editor_duids: doc.editor_duids = editor_duids if subscriber_duids: doc.subscriber_duids = subscriber_duids print("[Debug] Document object created:", doc, file=sys.stderr) # Create the operation doc_op = Operation( model=OperationModelKind.DOC, kind=OperationKind.CREATE, data=doc ) print("[Debug] Operation created:", doc_op, file=sys.stderr) # Execute the transaction print("[Debug] Executing transaction", file=sys.stderr) response = client.transact([doc_op], TransactionKind.DOC_CREATE) print("[Debug] Transaction completed", file=sys.stderr) print("[Debug] Response type:", type(response), file=sys.stderr) print("[Debug] Response:", response, file=sys.stderr) if isinstance(response, ValidationErrorResponse): print("[Debug] Validation error:", response.items.additional_properties, file=sys.stderr) print(f"Validation error: {response.items.additional_properties}") sys.exit(1) if response.results and response.results[0].success: doc = response.results[0].models.docs[0] print(f"Document created successfully") print(f"Title: {doc.title}") print(f"DUID: {doc.duid}") if hasattr(doc, 'report_kind') and doc.report_kind: print(f"Report Kind: {doc.report_kind}") print(f"[Debug] Document DUID: {doc.duid}", file=sys.stderr) else: print("[Debug] Document creation failed", file=sys.stderr) if response.results: print(f"[Debug] Result: {response.results[0]}", file=sys.stderr) sys.exit(1) except Exception as e: print(f"[Debug] Error creating document: {str(e)}", file=sys.stderr) print("[Debug] Error type:", type(e), file=sys.stderr) traceback.print_exc(file=sys.stderr) sys.exit(1)`; // Add proper indentation to the Python code const command = pythonCode.split('\n').map(line => line.length > 0 ? ' ' + line : line).join('\n'); console.error('[Debug] Running Python command for document creation'); const output = await this.runDartCommand(command); console.error('[Debug] Document creation output:', output); const response = { content: [{ type: 'text', text: output, }], }; return response; } case 'create_space': { console.error('[Debug] Handling create_space request'); const pythonCode = ` import sys import json from dart import Dart, Operation, OperationKind, OperationModelKind, SpaceCreate, TransactionKind from dart.dart import _Session, UserBundle, _make_duid from dart.generated.models.icon_kind import IconKind print("[Debug] Starting Python execution", file=sys.stderr) # Parse arguments from JSON args_json = '''${JSON.stringify(args)}''' args = json.loads(args_json) print(f"[Debug] Parsed args: {args}", file=sys.stderr) # Initialize session and client session = _Session() print("[Debug] Session created", file=sys.stderr) bundle = UserBundle(session) print("[Debug] UserBundle created", file=sys.stderr) client = Dart() print("[Debug] Dart client created", file=sys.stderr) # Get the user's DUID user_duid = bundle.user["duid"] print(f"[Debug] User DUID: {user_duid}", file=sys.stderr) # Generate a unique DUID for the space space_duid = _make_duid() print(f"[Debug] Generated space DUID: {space_duid}", file=sys.stderr) # Create the space object with required fields space = SpaceCreate( duid=space_duid, order="0", title=args.get("title"), description=args.get("description", ""), drafter_duid=user_duid, # Set drafter_duid explicitly accessible_by_team=args.get("accessible_by_team", True), accessible_by_user_duids=args.get("accessible_by_user_duids", []), icon_kind=IconKind(args.get("icon_kind", "None")), icon_name_or_emoji=args.get("icon_name_or_emoji", "") ) print(f"[Debug] Created space object: {space}", file=sys.stderr) # Create the operation space_op = Operation( model=OperationModelKind.SPACE, kind=OperationKind.CREATE, data=space # Pass the SpaceCreate object ) print(f"[Debug] Created operation: {space_op}", file=sys.stderr) # Execute the transaction result = client.transact([space_op], TransactionKind.SPACE_CREATE) print(f"[Debug] Transaction completed", file=sys.stderr) if result.results and result.results[0].success: space = result.results[0].models.spaces[0] print(f"Space created successfully") print(f"Title: {space.title}") print(f"DUID: {space.duid}") print(f"[Debug] Space DUID: {space.duid}", file=sys.stderr) else: print("[Debug] Space creation failed", file=sys.stderr) if result.results: print(f"[Debug] Result: {result.results[0]}", file=sys.stderr) sys.exit(1)`; // Add proper indentation to the Python code const command = pythonCode.split('\n').map(line => { if (line.trim().length === 0) return line; return ' ' + line; }).join('\n'); console.error('[Debug] Running Python command for space creation'); const output = await this.runDartCommand(command); console.error('[Debug] Space creation output:', output); const response = { content: [{ type: 'text', text: output, }], }; return response; } case 'delete_space': { console.error('[Debug] Handling delete_space request'); const pythonCode = ` # Delete space print("[Debug] Starting space deletion", file=sys.stderr) try: # Parse space_duid from args space_duid = "${args.space_duid}" print(f"[Debug] Deleting space with DUID: {space_duid}", file=sys.stderr) # Create the delete operation delete_op = Operation( model=OperationModelKind.SPACE, kind=OperationKind.DELETE, data={"duid": space_duid} # For delete operations, we just need the DUID ) print("[Debug] Created delete operation", file=sys.stderr) # Execute the transaction print("[Debug] Executing transaction", file=sys.stderr) response = client.transact([delete_op], TransactionKind.SPACE_DELETE) print("[Debug] Transaction completed", file=sys.stderr) if response.results and response.results[0].success: print(f"Space {space_duid} deleted successfully") print(f"[Debug] Space deletion successful", file=sys.stderr) else: print("[Debug] Space deletion failed", file=sys.stderr) if response.results: print(f"[Debug] Result: {response.results[0]}", file=sys.stderr) sys.exit(1) except Exception as e: print(f"[Debug] Error deleting space: {str(e)}", file=sys.stderr) print("[Debug] Error type:", type(e), file=sys.stderr) traceback.print_exc(file=sys.stderr) sys.exit(1)`; // Add proper indentation to the Python code const command = pythonCode.split('\n').map(line => { if (line.trim().length === 0) return line; return ' ' + line; }).join('\n'); console.error('[Debug] Running Python command for space deletion'); const output = await this.runDartCommand(command); console.error('[Debug] Space deletion output:', output); const response = { content: [{ type: 'text', text: output, }], }; return response; } default: throw new McpError(ErrorCode.UNKNOWN_TOOL, `Unknown tool: ${name}`); } } catch (error) { console.error('[Debug] Tool error:', error); throw new McpError( error.code ?? ErrorCode.INTERNAL_ERROR, error.message || 'Internal server error', error.data ); } }); } async start() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Dart MCP server running on stdio'); } } const server = new DartServer(); server.start().catch(console.error);