@vibetasks/mcp-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs/promises';
4
+ import path from 'path';
5
+ import os from 'os';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ async function install() {
12
+ try {
13
+ // Determine Claude Code config location
14
+ const configHome = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
15
+ const claudeConfigDir = path.join(configHome, 'claude-code');
16
+ const claudeConfigPath = path.join(claudeConfigDir, 'config.json');
17
+
18
+ // Get absolute path to MCP server
19
+ const mcpServerPath = path.resolve(__dirname, '..', 'dist', 'index.js');
20
+
21
+ // Read Supabase credentials from web app .env
22
+ const webEnvPath = path.resolve(__dirname, '..', '..', '..', 'apps', 'web', '.env.local');
23
+ let supabaseUrl = 'https://cbkkztbcoitrfcleghfd.supabase.co';
24
+ let supabaseKey = '';
25
+
26
+ try {
27
+ const envContent = await fs.readFile(webEnvPath, 'utf-8');
28
+ const urlMatch = envContent.match(/NEXT_PUBLIC_SUPABASE_URL=(.+)/);
29
+ const keyMatch = envContent.match(/NEXT_PUBLIC_SUPABASE_ANON_KEY=(.+)/);
30
+
31
+ if (urlMatch) supabaseUrl = urlMatch[1].trim();
32
+ if (keyMatch) supabaseKey = keyMatch[1].trim();
33
+ } catch (error) {
34
+ console.warn('āš ļø Could not read .env.local, using defaults');
35
+ }
36
+
37
+ // Read existing config or create new
38
+ let config = {};
39
+ try {
40
+ const existingConfig = await fs.readFile(claudeConfigPath, 'utf-8');
41
+ config = JSON.parse(existingConfig);
42
+ } catch (error) {
43
+ // Config doesn't exist, will create new
44
+ await fs.mkdir(claudeConfigDir, { recursive: true });
45
+ }
46
+
47
+ // Add TaskFlow MCP server
48
+ if (!config.mcpServers) config.mcpServers = {};
49
+
50
+ config.mcpServers.taskflow = {
51
+ command: 'node',
52
+ args: [mcpServerPath],
53
+ env: {
54
+ TASKFLOW_SUPABASE_URL: supabaseUrl,
55
+ TASKFLOW_SUPABASE_KEY: supabaseKey
56
+ }
57
+ };
58
+
59
+ // Add hooks
60
+ if (!config.hooks) config.hooks = {};
61
+
62
+ config.hooks.SessionStart = config.hooks.SessionStart || [];
63
+ config.hooks.Stop = config.hooks.Stop || [];
64
+
65
+ // Add SessionStart hook if not already present
66
+ const sessionStartExists = config.hooks.SessionStart.some(
67
+ h => h.command === 'node' && h.args?.[0] === mcpServerPath
68
+ );
69
+
70
+ if (!sessionStartExists) {
71
+ config.hooks.SessionStart.push({
72
+ type: 'command',
73
+ command: 'node',
74
+ args: [mcpServerPath],
75
+ env: {
76
+ CLAUDE_HOOK_TYPE: 'SessionStart',
77
+ TASKFLOW_SUPABASE_URL: supabaseUrl
78
+ }
79
+ });
80
+ }
81
+
82
+ // Add SessionEnd (Stop) hook if not already present
83
+ const sessionEndExists = config.hooks.Stop.some(
84
+ h => h.command === 'node' && h.args?.[0] === mcpServerPath
85
+ );
86
+
87
+ if (!sessionEndExists) {
88
+ config.hooks.Stop.push({
89
+ type: 'command',
90
+ command: 'node',
91
+ args: [mcpServerPath],
92
+ env: {
93
+ CLAUDE_HOOK_TYPE: 'SessionEnd',
94
+ TASKFLOW_SUPABASE_URL: supabaseUrl
95
+ }
96
+ });
97
+ }
98
+
99
+ // Write config
100
+ await fs.writeFile(claudeConfigPath, JSON.stringify(config, null, 2), 'utf-8');
101
+
102
+ console.log('āœ“ TaskFlow MCP server installed successfully!');
103
+ console.log('\nConfiguration written to:', claudeConfigPath);
104
+ console.log('\nMCP Server path:', mcpServerPath);
105
+ console.log('\nšŸ“ Next steps:');
106
+ console.log('1. Restart Claude Code');
107
+ console.log('2. Type "@" in chat to see TaskFlow tools');
108
+ console.log('3. Tools available: create_task, get_tasks, complete_task, etc.');
109
+ console.log('\nšŸ’” Hooks installed:');
110
+ console.log('- SessionStart: Loads your tasks automatically');
111
+ console.log('- SessionEnd: Auto-logs completed work as tasks');
112
+
113
+ } catch (error) {
114
+ console.error('āŒ Installation failed:', error.message);
115
+ process.exit(1);
116
+ }
117
+ }
118
+
119
+ install();
@@ -0,0 +1,104 @@
1
+ #!/bin/bash
2
+
3
+ # TaskFlow MCP Server Installation Script
4
+
5
+ set -e
6
+
7
+ echo "šŸš€ Installing TaskFlow MCP Server..."
8
+
9
+ # Get the directory where this script is located
10
+ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
11
+ PROJECT_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )"
12
+
13
+ # Build the server
14
+ echo "šŸ“¦ Building MCP server..."
15
+ cd "$PROJECT_ROOT"
16
+ npm run build
17
+
18
+ # Get the absolute path to dist/index.js
19
+ DIST_PATH="$PROJECT_ROOT/dist/index.js"
20
+
21
+ # Check if Claude Code config directory exists
22
+ CLAUDE_CONFIG_DIR="$HOME/.config/claude-code"
23
+
24
+ if [ ! -d "$CLAUDE_CONFIG_DIR" ]; then
25
+ echo "āŒ Claude Code config directory not found at $CLAUDE_CONFIG_DIR"
26
+ echo " Make sure Claude Code is installed."
27
+ exit 1
28
+ fi
29
+
30
+ # Create or update config.json
31
+ CONFIG_FILE="$CLAUDE_CONFIG_DIR/config.json"
32
+
33
+ echo "āš™ļø Configuring Claude Code..."
34
+
35
+ # Read existing config or create new
36
+ if [ -f "$CONFIG_FILE" ]; then
37
+ # Backup existing config
38
+ cp "$CONFIG_FILE" "$CONFIG_FILE.backup"
39
+ echo " Backed up existing config to config.json.backup"
40
+ fi
41
+
42
+ # Read Supabase key from environment or prompt
43
+ if [ -z "$TASKFLOW_SUPABASE_KEY" ]; then
44
+ echo ""
45
+ echo "šŸ“ Please enter your Supabase anon key:"
46
+ echo " (Find it at: https://supabase.com/dashboard/project/_/settings/api)"
47
+ read -r SUPABASE_KEY
48
+ else
49
+ SUPABASE_KEY="$TASKFLOW_SUPABASE_KEY"
50
+ fi
51
+
52
+ # Create config with proper paths
53
+ cat > "$CONFIG_FILE" <<EOF
54
+ {
55
+ "mcpServers": {
56
+ "taskflow": {
57
+ "command": "node",
58
+ "args": [
59
+ "$DIST_PATH"
60
+ ],
61
+ "env": {
62
+ "TASKFLOW_SUPABASE_URL": "https://cbkkztbcoitrfcleghfd.supabase.co",
63
+ "TASKFLOW_SUPABASE_KEY": "$SUPABASE_KEY"
64
+ }
65
+ }
66
+ },
67
+ "hooks": {
68
+ "SessionStart": [
69
+ {
70
+ "type": "command",
71
+ "command": "node",
72
+ "args": ["$DIST_PATH"],
73
+ "env": {
74
+ "CLAUDE_HOOK_TYPE": "SessionStart",
75
+ "TASKFLOW_SUPABASE_URL": "https://cbkkztbcoitrfcleghfd.supabase.co"
76
+ }
77
+ }
78
+ ],
79
+ "Stop": [
80
+ {
81
+ "type": "command",
82
+ "command": "node",
83
+ "args": ["$DIST_PATH"],
84
+ "env": {
85
+ "CLAUDE_HOOK_TYPE": "SessionEnd",
86
+ "TASKFLOW_SUPABASE_URL": "https://cbkkztbcoitrfcleghfd.supabase.co"
87
+ }
88
+ }
89
+ ]
90
+ }
91
+ }
92
+ EOF
93
+
94
+ echo ""
95
+ echo "āœ… TaskFlow MCP Server installed successfully!"
96
+ echo ""
97
+ echo "šŸ“ Configuration written to: $CONFIG_FILE"
98
+ echo "šŸ“ Server location: $DIST_PATH"
99
+ echo ""
100
+ echo "šŸ”„ Next steps:"
101
+ echo " 1. Restart Claude Code"
102
+ echo " 2. Type '@' in a chat to see TaskFlow tools"
103
+ echo " 3. Your tasks will load automatically on session start"
104
+ echo ""
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs/promises';
4
+ import path from 'path';
5
+ import os from 'os';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ async function uninstall() {
12
+ console.log('šŸ—‘ļø Uninstalling TaskFlow MCP integration...\n');
13
+
14
+ let removedCount = 0;
15
+
16
+ try {
17
+ // 1. Remove from Claude Code config
18
+ const configHome = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
19
+ const claudeConfigDir = path.join(configHome, 'claude-code');
20
+ const claudeConfigPath = path.join(claudeConfigDir, 'config.json');
21
+
22
+ try {
23
+ const configData = await fs.readFile(claudeConfigPath, 'utf-8');
24
+ const config = JSON.parse(configData);
25
+
26
+ // Remove TaskFlow MCP server
27
+ if (config.mcpServers?.taskflow) {
28
+ delete config.mcpServers.taskflow;
29
+ console.log('āœ“ Removed TaskFlow MCP server from Claude Code config');
30
+ removedCount++;
31
+ }
32
+
33
+ // Remove SessionStart hook
34
+ if (config.hooks?.SessionStart) {
35
+ const mcpServerPath = path.resolve(__dirname, '..', 'dist', 'index.js');
36
+ config.hooks.SessionStart = config.hooks.SessionStart.filter(
37
+ h => !(h.command === 'node' && h.args?.[0] === mcpServerPath)
38
+ );
39
+ if (config.hooks.SessionStart.length === 0) {
40
+ delete config.hooks.SessionStart;
41
+ }
42
+ console.log('āœ“ Removed SessionStart hook');
43
+ removedCount++;
44
+ }
45
+
46
+ // Remove Stop/SessionEnd hook
47
+ if (config.hooks?.Stop) {
48
+ const mcpServerPath = path.resolve(__dirname, '..', 'dist', 'index.js');
49
+ config.hooks.Stop = config.hooks.Stop.filter(
50
+ h => !(h.command === 'node' && h.args?.[0] === mcpServerPath)
51
+ );
52
+ if (config.hooks.Stop.length === 0) {
53
+ delete config.hooks.Stop;
54
+ }
55
+ console.log('āœ“ Removed SessionEnd hook');
56
+ removedCount++;
57
+ }
58
+
59
+ // Clean up empty objects
60
+ if (config.mcpServers && Object.keys(config.mcpServers).length === 0) {
61
+ delete config.mcpServers;
62
+ }
63
+ if (config.hooks && Object.keys(config.hooks).length === 0) {
64
+ delete config.hooks;
65
+ }
66
+
67
+ await fs.writeFile(claudeConfigPath, JSON.stringify(config, null, 2), 'utf-8');
68
+ } catch (error) {
69
+ if (error.code !== 'ENOENT') {
70
+ console.warn('āš ļø Could not update Claude Code config:', error.message);
71
+ }
72
+ }
73
+
74
+ // 2. Remove CLI config directory
75
+ const taskflowConfigDir = path.join(configHome, 'taskflow');
76
+ try {
77
+ await fs.rm(taskflowConfigDir, { recursive: true, force: true });
78
+ console.log('āœ“ Removed CLI config directory');
79
+ removedCount++;
80
+ } catch (error) {
81
+ if (error.code !== 'ENOENT') {
82
+ console.warn('āš ļø Could not remove CLI config:', error.message);
83
+ }
84
+ }
85
+
86
+ // 3. Note about npm unlink (can't do automatically)
87
+ console.log('\nšŸ“ Manual steps remaining:');
88
+ console.log(' 1. Unlink CLI globally:');
89
+ console.log(' cd packages/cli && npm unlink -g');
90
+ console.log(' 2. Remove git hooks (if installed):');
91
+ console.log(' rm .git/hooks/post-commit');
92
+ console.log(' 3. Clear keychain credentials (macOS/Windows):');
93
+ console.log(' - macOS: Open Keychain Access → search "taskflow"');
94
+ console.log(' - Windows: Control Panel → Credential Manager → search "taskflow"');
95
+
96
+ console.log(`\nāœ… Removed ${removedCount} configuration items`);
97
+ console.log('\nšŸ’” To complete uninstallation:');
98
+ console.log(' - Restart Claude Code to apply config changes');
99
+ console.log(' - Run the manual steps above');
100
+
101
+ } catch (error) {
102
+ console.error('āŒ Uninstallation failed:', error.message);
103
+ process.exit(1);
104
+ }
105
+ }
106
+
107
+ uninstall();
@@ -0,0 +1,75 @@
1
+ import { AuthManager, TaskOperations } from '@vibetasks/core';
2
+
3
+ /**
4
+ * SessionEnd hook - Auto-log AI session as completed task
5
+ */
6
+ export async function handleSessionEnd() {
7
+ try {
8
+ const authManager = new AuthManager();
9
+
10
+ // Check if authenticated
11
+ const isAuth = await authManager.isAuthenticated();
12
+ if (!isAuth) {
13
+ // Silently skip if not authenticated
14
+ return;
15
+ }
16
+
17
+ // Read session metadata from environment (if provided by Claude Code)
18
+ const metadataJson = process.env.CLAUDE_SESSION_METADATA || '{}';
19
+ const metadata = JSON.parse(metadataJson);
20
+
21
+ // Check if significant work was done
22
+ const filesEdited: string[] = metadata.filesEdited || [];
23
+ const duration: number = metadata.duration || 0;
24
+ const aiActions: string[] = metadata.aiActions || [];
25
+
26
+ // Only create task if significant work happened
27
+ const shouldLog =
28
+ filesEdited.length > 0 && duration >= 60000 && aiActions.length > 0;
29
+
30
+ if (!shouldLog) {
31
+ // Session too short or no significant work
32
+ console.error('TaskFlow: Session too short, not logging');
33
+ return;
34
+ }
35
+
36
+ const taskOps = await TaskOperations.fromAuthManager(authManager);
37
+
38
+ // Create summary
39
+ const durationMinutes = Math.round(duration / 60000);
40
+ const primaryAction = aiActions[0] || 'Code changes';
41
+
42
+ const summary = `# AI Session Completed
43
+
44
+ ## Summary
45
+ ${primaryAction}
46
+
47
+ ## Duration
48
+ ${durationMinutes} minutes
49
+
50
+ ## Files Edited (${filesEdited.length})
51
+ ${filesEdited.slice(0, 15).map((f) => `- ${f}`).join('\n')}
52
+ ${filesEdited.length > 15 ? `\n...and ${filesEdited.length - 15} more files` : ''}
53
+
54
+ ## Actions
55
+ ${aiActions.map((a) => `- ${a}`).join('\n')}
56
+
57
+ ---
58
+ *Generated by TaskFlow MCP Server*
59
+ `.trim();
60
+
61
+ // Create completed task
62
+ await taskOps.createTask({
63
+ title: `AI Session: ${primaryAction}`,
64
+ notes: summary,
65
+ notes_format: 'markdown',
66
+ completed: true,
67
+ priority: 'none',
68
+ });
69
+
70
+ console.error('TaskFlow: Logged AI session as completed task');
71
+ } catch (error: any) {
72
+ // Log error to stderr but don't fail
73
+ console.error('TaskFlow SessionEnd hook error:', error.message);
74
+ }
75
+ }
@@ -0,0 +1,74 @@
1
+ import { AuthManager, TaskOperations } from '@vibetasks/core';
2
+
3
+ /**
4
+ * SessionStart hook - Load today's tasks as context for AI
5
+ */
6
+ export async function handleSessionStart() {
7
+ try {
8
+ const authManager = new AuthManager();
9
+
10
+ // Check if authenticated
11
+ const isAuth = await authManager.isAuthenticated();
12
+ if (!isAuth) {
13
+ // Silently skip if not authenticated
14
+ console.log(JSON.stringify({ additionalContext: '' }));
15
+ return;
16
+ }
17
+
18
+ const taskOps = await TaskOperations.fromAuthManager(authManager);
19
+
20
+ // Load today's tasks
21
+ const todayTasks = await taskOps.getTasks('today');
22
+ const activeTasks = await taskOps.getTasks('all');
23
+
24
+ // Format context for AI
25
+ const context = `# TaskFlow - Your Tasks
26
+
27
+ ## Today's Tasks (${todayTasks.length})
28
+ ${
29
+ todayTasks.length === 0
30
+ ? 'No tasks due today.'
31
+ : todayTasks
32
+ .map((t) => {
33
+ const priority =
34
+ t.priority && t.priority !== 'none' ? ` [${t.priority.toUpperCase()}]` : '';
35
+ const tags = t.tags && t.tags.length > 0 ? ` #${t.tags.map((tag) => tag.name).join(' #')}` : '';
36
+ return `- [ ] ${t.title}${priority}${tags}`;
37
+ })
38
+ .join('\n')
39
+ }
40
+
41
+ ## All Active Tasks (${activeTasks.length})
42
+ ${
43
+ activeTasks.length === 0
44
+ ? 'No active tasks.'
45
+ : activeTasks
46
+ .slice(0, 10)
47
+ .map((t) => {
48
+ const priority =
49
+ t.priority && t.priority !== 'none' ? ` [${t.priority.toUpperCase()}]` : '';
50
+ const dueDate = t.due_date ? ` (Due: ${t.due_date.split('T')[0]})` : '';
51
+ return `- [ ] ${t.title}${priority}${dueDate}`;
52
+ })
53
+ .join('\n')
54
+ }${activeTasks.length > 10 ? `\n...and ${activeTasks.length - 10} more` : ''}
55
+
56
+ ---
57
+ You can manage tasks using these MCP tools:
58
+ - create_task: Add new tasks
59
+ - get_tasks: View tasks by filter
60
+ - complete_task: Mark tasks done
61
+ - search_tasks: Find tasks
62
+ - update_task: Modify tasks
63
+ - delete_task: Remove tasks
64
+ - log_ai_session: Log what we accomplish together
65
+ `.trim();
66
+
67
+ // Return context for Claude Code
68
+ console.log(JSON.stringify({ additionalContext: context }));
69
+ } catch (error: any) {
70
+ // Log error to stderr but don't fail
71
+ console.error('TaskFlow SessionStart hook error:', error.message);
72
+ console.log(JSON.stringify({ additionalContext: '' }));
73
+ }
74
+ }
package/src/index.ts ADDED
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * TaskFlow MCP Server
5
+ * Provides task management integration for Claude Code, Cursor, and other AI coding tools
6
+ */
7
+
8
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
9
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
+ import {
11
+ CallToolRequestSchema,
12
+ ListToolsRequestSchema,
13
+ ListResourcesRequestSchema,
14
+ ReadResourceRequestSchema,
15
+ } from '@modelcontextprotocol/sdk/types.js';
16
+ import { AuthManager, TaskOperations } from '@vibetasks/core';
17
+ import { setupTools } from './tools/index.js';
18
+ import { setupResources } from './resources/index.js';
19
+
20
+ // Check if running as a hook
21
+ const hookType = process.env.CLAUDE_HOOK_TYPE;
22
+
23
+ if (hookType === 'SessionStart') {
24
+ // Handle SessionStart hook
25
+ const { handleSessionStart } = await import('./hooks/session-start.js');
26
+ await handleSessionStart();
27
+ process.exit(0);
28
+ } else if (hookType === 'SessionEnd' || hookType === 'Stop') {
29
+ // Handle SessionEnd hook
30
+ const { handleSessionEnd } = await import('./hooks/session-end.js');
31
+ await handleSessionEnd();
32
+ process.exit(0);
33
+ }
34
+
35
+ // Normal MCP server mode
36
+ async function main() {
37
+ try {
38
+ // Initialize authentication
39
+ const authManager = new AuthManager();
40
+
41
+ // Supabase configuration with production fallback
42
+ const supabaseUrl =
43
+ process.env.TASKFLOW_SUPABASE_URL ||
44
+ (await authManager.getConfig('supabase_url')) ||
45
+ 'https://cbkkztbcoitrfcleghfd.supabase.co';
46
+ const supabaseKey =
47
+ process.env.TASKFLOW_SUPABASE_KEY ||
48
+ (await authManager.getConfig('supabase_key')) ||
49
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNia2t6dGJjb2l0cmZjbGVnaGZkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njc3NTc0MjgsImV4cCI6MjA4MzMzMzQyOH0.G7ILx-nntP0NbxO1gKt5yASb7nt7OmpJ8qtykeGYbQA';
50
+ const accessToken = await authManager.getAccessToken();
51
+
52
+ if (!accessToken) {
53
+ console.error('ERROR: Not authenticated.', { severity: 'error' });
54
+ console.error('Run: taskflow login', { severity: 'error' });
55
+ process.exit(1);
56
+ }
57
+
58
+ if (!accessToken) {
59
+ console.error('ERROR: Not authenticated.', { severity: 'error' });
60
+ console.error('Run: taskflow login', { severity: 'error' });
61
+ process.exit(1);
62
+ }
63
+
64
+ // Create TaskOperations instance
65
+ const taskOps = await TaskOperations.fromAuthManager(authManager);
66
+
67
+ // Create MCP server
68
+ const server = new Server(
69
+ {
70
+ name: 'taskflow-mcp-server',
71
+ version: '1.0.0',
72
+ },
73
+ {
74
+ capabilities: {
75
+ tools: {},
76
+ resources: {},
77
+ },
78
+ }
79
+ );
80
+
81
+ // Setup tools and resources
82
+ const tools = setupTools(taskOps);
83
+ const resources = setupResources(taskOps);
84
+
85
+ // Register tool list handler
86
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
87
+ return {
88
+ tools: tools.map((t) => ({
89
+ name: t.name,
90
+ description: t.description,
91
+ inputSchema: t.inputSchema,
92
+ })),
93
+ };
94
+ });
95
+
96
+ // Register tool call handler
97
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
98
+ const tool = tools.find((t) => t.name === request.params.name);
99
+
100
+ if (!tool) {
101
+ throw new Error(`Unknown tool: ${request.params.name}`);
102
+ }
103
+
104
+ try {
105
+ return await tool.handler(request.params.arguments || {}, taskOps);
106
+ } catch (error: any) {
107
+ return {
108
+ content: [
109
+ {
110
+ type: 'text',
111
+ text: JSON.stringify(
112
+ {
113
+ success: false,
114
+ error: error.message,
115
+ },
116
+ null,
117
+ 2
118
+ ),
119
+ },
120
+ ],
121
+ isError: true,
122
+ };
123
+ }
124
+ });
125
+
126
+ // Register resource list handler
127
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
128
+ return {
129
+ resources: resources.map((r) => ({
130
+ uri: r.uri,
131
+ name: r.name,
132
+ description: r.description,
133
+ mimeType: r.mimeType,
134
+ })),
135
+ };
136
+ });
137
+
138
+ // Register resource read handler
139
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
140
+ const resource = resources.find((r) => r.uri === request.params.uri);
141
+
142
+ if (!resource) {
143
+ throw new Error(`Unknown resource: ${request.params.uri}`);
144
+ }
145
+
146
+ try {
147
+ return await resource.handler(taskOps);
148
+ } catch (error: any) {
149
+ throw new Error(`Failed to read resource: ${error.message}`);
150
+ }
151
+ });
152
+
153
+ // Connect via STDIO
154
+ const transport = new StdioServerTransport();
155
+ await server.connect(transport);
156
+
157
+ // Log to stderr (STDIO requirement)
158
+ console.error('TaskFlow MCP server started', { severity: 'info' });
159
+ } catch (error: any) {
160
+ console.error('Fatal error:', error, { severity: 'error' });
161
+ process.exit(1);
162
+ }
163
+ }
164
+
165
+ main();