@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.
- package/README.md +310 -0
- package/config/claude-code.json +38 -0
- package/dist/index.js +565 -0
- package/dist/session-end-6BUUHSB7.js +56 -0
- package/dist/session-start-OIAJ7YIL.js +49 -0
- package/package.json +29 -0
- package/scripts/install.js +119 -0
- package/scripts/install.sh +104 -0
- package/scripts/uninstall.js +107 -0
- package/src/hooks/session-end.ts +75 -0
- package/src/hooks/session-start.ts +74 -0
- package/src/index.ts +165 -0
- package/src/resources/index.ts +115 -0
- package/src/tools/index.ts +414 -0
- package/tsconfig.json +21 -0
|
@@ -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();
|