@idea-base/mcp-server 1.0.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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +131 -0
  3. package/package.json +51 -0
  4. package/src/index.js +814 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 IDEAManagement
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # @idea-base/mcp-server
2
+
3
+ MCP (Model Context Protocol) server for [IDEA Base](https://idea-base.us) — AI-powered project management. Manage projects, tasks, and time tracking directly from Claude Code, Cursor, or any MCP-compatible AI tool.
4
+
5
+ ## Quick Setup
6
+
7
+ ### 1. Get your API key
8
+
9
+ Sign in to [IDEA Base](https://app.idea-base.us), go to **Settings > API Keys**, and create a key.
10
+
11
+ ### 2. Add to your MCP config
12
+
13
+ **Claude Code** (`~/.claude/claude_desktop_config.json`):
14
+
15
+ ```json
16
+ {
17
+ "mcpServers": {
18
+ "idea-base": {
19
+ "command": "npx",
20
+ "args": ["-y", "@idea-base/mcp-server"],
21
+ "env": {
22
+ "IDEA_BASE_API_KEY": "ib_your_api_key_here"
23
+ }
24
+ }
25
+ }
26
+ }
27
+ ```
28
+
29
+ **Cursor** (`.cursor/mcp.json` in your project):
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "idea-base": {
35
+ "command": "npx",
36
+ "args": ["-y", "@idea-base/mcp-server"],
37
+ "env": {
38
+ "IDEA_BASE_API_KEY": "ib_your_api_key_here"
39
+ }
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ Or via Claude Code CLI:
46
+ ```bash
47
+ claude mcp add idea-base -- npx -y @idea-base/mcp-server \
48
+ --env IDEA_BASE_API_KEY=ib_your_api_key_here
49
+ ```
50
+
51
+ ### 3. Start using it
52
+
53
+ Ask Claude to manage your projects:
54
+
55
+ - *"List my projects"*
56
+ - *"Create a task in project 1: Implement login page"*
57
+ - *"Log 2 hours on task 42 — built the auth flow"*
58
+ - *"What tasks are in progress?"*
59
+
60
+ ## Available Tools
61
+
62
+ ### Projects
63
+
64
+ | Tool | Description |
65
+ |------|-------------|
66
+ | `list_projects` | List all projects with task counts and progress |
67
+ | `get_project` | Get project details and statistics |
68
+ | `create_project` | Create a new project or sub-project |
69
+ | `update_project` | Update project name, description, or status |
70
+
71
+ ### Tasks
72
+
73
+ | Tool | Description |
74
+ |------|-------------|
75
+ | `list_tasks` | List tasks for a project (filter by status) |
76
+ | `get_task` | Get task details, acceptance criteria, and time entries |
77
+ | `create_task` | Create a task with title, description, estimate |
78
+ | `update_task` | Update task details |
79
+ | `update_task_status` | Change task status (todo/in_progress/done) |
80
+ | `search_tasks` | Search tasks across all projects |
81
+ | `quick_log` | Create + complete + log time in one step |
82
+
83
+ ### Time Tracking
84
+
85
+ | Tool | Description |
86
+ |------|-------------|
87
+ | `log_time` | Log time against a task with notes |
88
+ | `start_working` | Mark yourself as actively working on a task |
89
+ | `stop_working` | Stop active work on a task |
90
+
91
+ ### Products
92
+
93
+ | Tool | Description |
94
+ |------|-------------|
95
+ | `list_products` | List products (top-level containers) |
96
+ | `get_product` | Get product details with linked projects |
97
+ | `create_product` | Create a new product |
98
+ | `link_project_to_product` | Link a project to a product |
99
+
100
+ ## Environment Variables
101
+
102
+ | Variable | Required | Description |
103
+ |----------|----------|-------------|
104
+ | `IDEA_BASE_API_KEY` | Yes | Your API key from Settings > API Keys |
105
+ | `IDEA_BASE_API_URL` | No | Custom API URL (default: `https://app.idea-base.us/api`) |
106
+
107
+ ## Real-time Notifications
108
+
109
+ When you use the MCP server to update tasks or log time, changes are broadcast to all connected users. Team members viewing the project in their browser see live toast notifications — when Claude updates a task, everyone sees it immediately.
110
+
111
+ ## Security
112
+
113
+ - All data access is scoped to your account via API key
114
+ - API keys support read/write permissions
115
+ - No data is stored locally — all operations go through the IDEA Base API
116
+ - Cross-account access is blocked server-side
117
+ - Rate limited per API key
118
+
119
+ ## Development
120
+
121
+ ```bash
122
+ # Run the server directly
123
+ IDEA_BASE_API_KEY=your_key npm start
124
+
125
+ # Watch mode
126
+ IDEA_BASE_API_KEY=your_key npm run dev
127
+ ```
128
+
129
+ ## License
130
+
131
+ MIT - [IDEA Management LLC](https://ideamngmt.com)
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@idea-base/mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP Server for IDEA Base - AI-powered project management. Integrates with Claude Code, Cursor, and Windsurf.",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "idea-base-mcp": "./src/index.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node src/index.js",
12
+ "dev": "node --watch src/index.js"
13
+ },
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.0.0"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "claude",
20
+ "claude-code",
21
+ "cursor",
22
+ "windsurf",
23
+ "project-management",
24
+ "task-management",
25
+ "time-tracking",
26
+ "idea-base",
27
+ "ai"
28
+ ],
29
+ "author": "IDEA Management LLC <support@idea-base.us>",
30
+ "license": "MIT",
31
+ "homepage": "https://idea-base.us",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/IDEAManagement/idea-base-mcp.git"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/IDEAManagement/idea-base-mcp/issues"
38
+ },
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public",
44
+ "provenance": true
45
+ },
46
+ "files": [
47
+ "src/",
48
+ "README.md",
49
+ "LICENSE"
50
+ ]
51
+ }
package/src/index.js ADDED
@@ -0,0 +1,814 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * IDEA Base MCP Server
5
+ *
6
+ * Provides Claude Code with tools to interact with IDEA Base project management.
7
+ * Tools include: list_projects, list_tasks, get_task, create_task, update_task,
8
+ * update_task_status, log_time, and listen for real-time events.
9
+ */
10
+
11
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
12
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
13
+ import {
14
+ CallToolRequestSchema,
15
+ ListToolsRequestSchema,
16
+ } from '@modelcontextprotocol/sdk/types.js';
17
+
18
+ // Configuration from environment
19
+ const API_BASE_URL = process.env.IDEA_BASE_API_URL || 'https://app.idea-base.us/api';
20
+ const API_KEY = process.env.IDEA_BASE_API_KEY;
21
+
22
+ if (!API_KEY) {
23
+ console.error('Error: IDEA_BASE_API_KEY environment variable is required');
24
+ process.exit(1);
25
+ }
26
+
27
+ // API helper function
28
+ async function apiRequest(endpoint, options = {}) {
29
+ const url = `${API_BASE_URL}${endpoint}`;
30
+ const response = await fetch(url, {
31
+ ...options,
32
+ headers: {
33
+ 'Authorization': `Bearer ${API_KEY}`,
34
+ 'Content-Type': 'application/json',
35
+ ...options.headers,
36
+ },
37
+ });
38
+
39
+ if (!response.ok) {
40
+ const error = await response.json().catch(() => ({ error: response.statusText }));
41
+ throw new Error(error.error || `API error: ${response.status}`);
42
+ }
43
+
44
+ return response.json();
45
+ }
46
+
47
+ // Define available tools
48
+ const tools = [
49
+ {
50
+ name: 'list_projects',
51
+ description: 'List all projects accessible to the authenticated user. Returns project names, IDs, status, and task counts.',
52
+ inputSchema: {
53
+ type: 'object',
54
+ properties: {
55
+ parent_id: {
56
+ type: 'number',
57
+ description: 'Filter by parent project ID to get sub-projects. Omit for top-level projects.',
58
+ },
59
+ status: {
60
+ type: 'string',
61
+ enum: ['active', 'completed', 'archived'],
62
+ description: 'Filter by project status. Defaults to all.',
63
+ },
64
+ },
65
+ },
66
+ },
67
+ {
68
+ name: 'create_project',
69
+ description: 'Create a new project. Returns the created project with its ID.',
70
+ inputSchema: {
71
+ type: 'object',
72
+ properties: {
73
+ name: {
74
+ type: 'string',
75
+ description: 'Name of the project',
76
+ },
77
+ description: {
78
+ type: 'string',
79
+ description: 'Description of the project',
80
+ },
81
+ parent_project_id: {
82
+ type: 'number',
83
+ description: 'Parent project ID if this is a sub-project',
84
+ },
85
+ },
86
+ required: ['name'],
87
+ },
88
+ },
89
+ {
90
+ name: 'update_project',
91
+ description: 'Update project details like name, description, or status.',
92
+ inputSchema: {
93
+ type: 'object',
94
+ properties: {
95
+ project_id: {
96
+ type: 'number',
97
+ description: 'The ID of the project to update',
98
+ },
99
+ name: {
100
+ type: 'string',
101
+ description: 'New name for the project',
102
+ },
103
+ description: {
104
+ type: 'string',
105
+ description: 'New description',
106
+ },
107
+ status: {
108
+ type: 'string',
109
+ enum: ['active', 'completed', 'archived'],
110
+ description: 'New status for the project',
111
+ },
112
+ },
113
+ required: ['project_id'],
114
+ },
115
+ },
116
+ {
117
+ name: 'get_project',
118
+ description: 'Get details of a specific project including name, description, status, and task statistics.',
119
+ inputSchema: {
120
+ type: 'object',
121
+ properties: {
122
+ project_id: {
123
+ type: 'number',
124
+ description: 'The ID of the project to retrieve',
125
+ },
126
+ },
127
+ required: ['project_id'],
128
+ },
129
+ },
130
+ {
131
+ name: 'list_tasks',
132
+ description: 'List all tasks for a specific project. Returns task titles, status, estimates, and time logged.',
133
+ inputSchema: {
134
+ type: 'object',
135
+ properties: {
136
+ project_id: {
137
+ type: 'number',
138
+ description: 'The ID of the project whose tasks to list',
139
+ },
140
+ status: {
141
+ type: 'string',
142
+ enum: ['todo', 'in_progress', 'done'],
143
+ description: 'Filter by task status. Omit for all tasks.',
144
+ },
145
+ },
146
+ required: ['project_id'],
147
+ },
148
+ },
149
+ {
150
+ name: 'get_task',
151
+ description: 'Get detailed information about a specific task including description, acceptance criteria, time entries, and status.',
152
+ inputSchema: {
153
+ type: 'object',
154
+ properties: {
155
+ task_id: {
156
+ type: 'number',
157
+ description: 'The ID of the task to retrieve',
158
+ },
159
+ },
160
+ required: ['task_id'],
161
+ },
162
+ },
163
+ {
164
+ name: 'create_task',
165
+ description: 'Create a new task in a project. Returns the created task with its ID.',
166
+ inputSchema: {
167
+ type: 'object',
168
+ properties: {
169
+ project_id: {
170
+ type: 'number',
171
+ description: 'The ID of the project to add the task to',
172
+ },
173
+ title: {
174
+ type: 'string',
175
+ description: 'Title of the task',
176
+ },
177
+ description: {
178
+ type: 'string',
179
+ description: 'Detailed description of the task',
180
+ },
181
+ acceptance_criteria: {
182
+ type: 'string',
183
+ description: 'Criteria for task completion',
184
+ },
185
+ estimated_minutes: {
186
+ type: 'number',
187
+ description: 'Estimated time to complete in minutes',
188
+ },
189
+ priority: {
190
+ type: 'number',
191
+ description: 'Priority level (0-5, higher is more important)',
192
+ },
193
+ },
194
+ required: ['project_id', 'title'],
195
+ },
196
+ },
197
+ {
198
+ name: 'update_task',
199
+ description: 'Update task details like title, description, estimates, or acceptance criteria.',
200
+ inputSchema: {
201
+ type: 'object',
202
+ properties: {
203
+ task_id: {
204
+ type: 'number',
205
+ description: 'The ID of the task to update',
206
+ },
207
+ title: {
208
+ type: 'string',
209
+ description: 'New title for the task',
210
+ },
211
+ description: {
212
+ type: 'string',
213
+ description: 'New description',
214
+ },
215
+ acceptance_criteria: {
216
+ type: 'string',
217
+ description: 'New acceptance criteria',
218
+ },
219
+ estimated_minutes: {
220
+ type: 'number',
221
+ description: 'New time estimate in minutes',
222
+ },
223
+ priority: {
224
+ type: 'number',
225
+ description: 'New priority level',
226
+ },
227
+ },
228
+ required: ['task_id'],
229
+ },
230
+ },
231
+ {
232
+ name: 'update_task_status',
233
+ description: 'Change the status of a task (todo, in_progress, done). This triggers a real-time notification to users viewing the project.',
234
+ inputSchema: {
235
+ type: 'object',
236
+ properties: {
237
+ task_id: {
238
+ type: 'number',
239
+ description: 'The ID of the task to update',
240
+ },
241
+ status: {
242
+ type: 'string',
243
+ enum: ['todo', 'in_progress', 'done'],
244
+ description: 'New status for the task',
245
+ },
246
+ },
247
+ required: ['task_id', 'status'],
248
+ },
249
+ },
250
+ {
251
+ name: 'log_time',
252
+ description: 'Log time spent on a task. This triggers a real-time notification showing time was logged.',
253
+ inputSchema: {
254
+ type: 'object',
255
+ properties: {
256
+ task_id: {
257
+ type: 'number',
258
+ description: 'The ID of the task to log time against',
259
+ },
260
+ minutes: {
261
+ type: 'number',
262
+ description: 'Number of minutes to log',
263
+ },
264
+ notes: {
265
+ type: 'string',
266
+ description: 'Notes about what was done during this time',
267
+ },
268
+ date: {
269
+ type: 'string',
270
+ description: 'Date for the time entry (YYYY-MM-DD). Defaults to today.',
271
+ },
272
+ },
273
+ required: ['task_id', 'minutes'],
274
+ },
275
+ },
276
+ {
277
+ name: 'search_tasks',
278
+ description: 'Search for tasks across all projects by title or description.',
279
+ inputSchema: {
280
+ type: 'object',
281
+ properties: {
282
+ query: {
283
+ type: 'string',
284
+ description: 'Search query to match against task titles and descriptions',
285
+ },
286
+ status: {
287
+ type: 'string',
288
+ enum: ['todo', 'in_progress', 'done'],
289
+ description: 'Filter results by status',
290
+ },
291
+ },
292
+ required: ['query'],
293
+ },
294
+ },
295
+ {
296
+ name: 'quick_log',
297
+ description: 'Quick workflow for logging completed work: creates a task, marks it done, and logs time in one call. Ideal for recording work that has already been completed.',
298
+ inputSchema: {
299
+ type: 'object',
300
+ properties: {
301
+ project_id: {
302
+ type: 'number',
303
+ description: 'The ID of the project to add the task to',
304
+ },
305
+ title: {
306
+ type: 'string',
307
+ description: 'Title of the completed task',
308
+ },
309
+ minutes: {
310
+ type: 'number',
311
+ description: 'Number of minutes spent on this work',
312
+ },
313
+ description: {
314
+ type: 'string',
315
+ description: 'Detailed description of the task (optional)',
316
+ },
317
+ notes: {
318
+ type: 'string',
319
+ description: 'Notes about what was done during this time (optional, defaults to description if not provided)',
320
+ },
321
+ acceptance_criteria: {
322
+ type: 'string',
323
+ description: 'Criteria for task completion (optional)',
324
+ },
325
+ },
326
+ required: ['project_id', 'title', 'minutes'],
327
+ },
328
+ },
329
+ {
330
+ name: 'start_working',
331
+ description: 'Mark that you are actively working on a task. This shows other team members that the task is being worked on and by whom.',
332
+ inputSchema: {
333
+ type: 'object',
334
+ properties: {
335
+ task_id: {
336
+ type: 'number',
337
+ description: 'The ID of the task to start working on',
338
+ },
339
+ },
340
+ required: ['task_id'],
341
+ },
342
+ },
343
+ {
344
+ name: 'stop_working',
345
+ description: 'Mark that you have stopped working on a task. Use this when taking a break or switching to another task.',
346
+ inputSchema: {
347
+ type: 'object',
348
+ properties: {
349
+ task_id: {
350
+ type: 'number',
351
+ description: 'The ID of the task to stop working on',
352
+ },
353
+ },
354
+ required: ['task_id'],
355
+ },
356
+ },
357
+ // Products tools
358
+ {
359
+ name: 'list_products',
360
+ description: 'List all products accessible to the authenticated user. Products are top-level containers for organizing related projects.',
361
+ inputSchema: {
362
+ type: 'object',
363
+ properties: {
364
+ status: {
365
+ type: 'string',
366
+ enum: ['active', 'archived'],
367
+ description: 'Filter by product status. Defaults to all.',
368
+ },
369
+ },
370
+ },
371
+ },
372
+ {
373
+ name: 'get_product',
374
+ description: 'Get details of a specific product including linked projects, team members, and statistics.',
375
+ inputSchema: {
376
+ type: 'object',
377
+ properties: {
378
+ product_id: {
379
+ type: 'number',
380
+ description: 'The ID of the product to retrieve',
381
+ },
382
+ },
383
+ required: ['product_id'],
384
+ },
385
+ },
386
+ {
387
+ name: 'create_product',
388
+ description: 'Create a new product. Products are top-level containers that can have multiple projects linked to them.',
389
+ inputSchema: {
390
+ type: 'object',
391
+ properties: {
392
+ name: {
393
+ type: 'string',
394
+ description: 'Name of the product',
395
+ },
396
+ description: {
397
+ type: 'string',
398
+ description: 'Description of the product',
399
+ },
400
+ github_repo_url: {
401
+ type: 'string',
402
+ description: 'GitHub repository URL for the product',
403
+ },
404
+ },
405
+ required: ['name'],
406
+ },
407
+ },
408
+ {
409
+ name: 'link_project_to_product',
410
+ description: 'Link an existing project to a product.',
411
+ inputSchema: {
412
+ type: 'object',
413
+ properties: {
414
+ product_id: {
415
+ type: 'number',
416
+ description: 'The ID of the product',
417
+ },
418
+ project_id: {
419
+ type: 'number',
420
+ description: 'The ID of the project to link',
421
+ },
422
+ is_primary: {
423
+ type: 'boolean',
424
+ description: 'Whether this is the primary project for the product',
425
+ },
426
+ },
427
+ required: ['product_id', 'project_id'],
428
+ },
429
+ },
430
+ ];
431
+
432
+ // Tool implementation handlers
433
+ const toolHandlers = {
434
+ async list_projects({ parent_id, status }) {
435
+ let endpoint = '/projects';
436
+ const params = new URLSearchParams();
437
+ if (parent_id) params.set('parent_id', parent_id);
438
+ if (status) params.set('status', status);
439
+ if (params.toString()) endpoint += `?${params}`;
440
+
441
+ const projects = await apiRequest(endpoint);
442
+ return {
443
+ content: [
444
+ {
445
+ type: 'text',
446
+ text: JSON.stringify(projects, null, 2),
447
+ },
448
+ ],
449
+ };
450
+ },
451
+
452
+ async create_project({ name, description, parent_project_id }) {
453
+ const project = await apiRequest('/projects', {
454
+ method: 'POST',
455
+ body: JSON.stringify({
456
+ name,
457
+ description,
458
+ parent_project_id,
459
+ }),
460
+ });
461
+ return {
462
+ content: [
463
+ {
464
+ type: 'text',
465
+ text: JSON.stringify(project, null, 2),
466
+ },
467
+ ],
468
+ };
469
+ },
470
+
471
+ async update_project({ project_id, name, description, status }) {
472
+ const updates = {};
473
+ if (name !== undefined) updates.name = name;
474
+ if (description !== undefined) updates.description = description;
475
+ if (status !== undefined) updates.status = status;
476
+
477
+ const project = await apiRequest(`/projects/${project_id}`, {
478
+ method: 'PUT',
479
+ body: JSON.stringify(updates),
480
+ });
481
+ return {
482
+ content: [
483
+ {
484
+ type: 'text',
485
+ text: JSON.stringify(project, null, 2),
486
+ },
487
+ ],
488
+ };
489
+ },
490
+
491
+ async get_project({ project_id }) {
492
+ const project = await apiRequest(`/projects/${project_id}`);
493
+ return {
494
+ content: [
495
+ {
496
+ type: 'text',
497
+ text: JSON.stringify(project, null, 2),
498
+ },
499
+ ],
500
+ };
501
+ },
502
+
503
+ async list_tasks({ project_id, status }) {
504
+ let endpoint = `/projects/${project_id}/tasks`;
505
+ if (status) endpoint += `?status=${status}`;
506
+
507
+ const tasks = await apiRequest(endpoint);
508
+ return {
509
+ content: [
510
+ {
511
+ type: 'text',
512
+ text: JSON.stringify(tasks, null, 2),
513
+ },
514
+ ],
515
+ };
516
+ },
517
+
518
+ async get_task({ task_id }) {
519
+ const task = await apiRequest(`/tasks/${task_id}`);
520
+ return {
521
+ content: [
522
+ {
523
+ type: 'text',
524
+ text: JSON.stringify(task, null, 2),
525
+ },
526
+ ],
527
+ };
528
+ },
529
+
530
+ async create_task({ project_id, title, description, acceptance_criteria, estimated_minutes, priority }) {
531
+ const task = await apiRequest(`/projects/${project_id}/tasks`, {
532
+ method: 'POST',
533
+ body: JSON.stringify({
534
+ title,
535
+ description,
536
+ acceptance_criteria,
537
+ estimated_minutes,
538
+ priority,
539
+ }),
540
+ });
541
+ return {
542
+ content: [
543
+ {
544
+ type: 'text',
545
+ text: JSON.stringify(task, null, 2),
546
+ },
547
+ ],
548
+ };
549
+ },
550
+
551
+ async update_task({ task_id, title, description, acceptance_criteria, estimated_minutes, priority }) {
552
+ const updates = {};
553
+ if (title !== undefined) updates.title = title;
554
+ if (description !== undefined) updates.description = description;
555
+ if (acceptance_criteria !== undefined) updates.acceptance_criteria = acceptance_criteria;
556
+ if (estimated_minutes !== undefined) updates.estimated_minutes = estimated_minutes;
557
+ if (priority !== undefined) updates.priority = priority;
558
+
559
+ const task = await apiRequest(`/tasks/${task_id}`, {
560
+ method: 'PUT',
561
+ body: JSON.stringify(updates),
562
+ });
563
+ return {
564
+ content: [
565
+ {
566
+ type: 'text',
567
+ text: JSON.stringify(task, null, 2),
568
+ },
569
+ ],
570
+ };
571
+ },
572
+
573
+ async update_task_status({ task_id, status }) {
574
+ const task = await apiRequest(`/tasks/${task_id}/status`, {
575
+ method: 'PUT',
576
+ body: JSON.stringify({ status }),
577
+ });
578
+ return {
579
+ content: [
580
+ {
581
+ type: 'text',
582
+ text: `Task ${task_id} status updated to "${status}". This change has been broadcast to all connected users.`,
583
+ },
584
+ ],
585
+ };
586
+ },
587
+
588
+ async log_time({ task_id, minutes, notes, date }) {
589
+ const entry = await apiRequest(`/tasks/${task_id}/time`, {
590
+ method: 'POST',
591
+ body: JSON.stringify({
592
+ minutes,
593
+ notes,
594
+ date: date || new Date().toISOString().split('T')[0],
595
+ }),
596
+ });
597
+ return {
598
+ content: [
599
+ {
600
+ type: 'text',
601
+ text: `Logged ${minutes} minutes to task ${task_id}. ${notes ? `Notes: ${notes}` : ''}`,
602
+ },
603
+ ],
604
+ };
605
+ },
606
+
607
+ async search_tasks({ query, status }) {
608
+ let endpoint = `/tasks/search?q=${encodeURIComponent(query)}`;
609
+ if (status) endpoint += `&status=${status}`;
610
+
611
+ const tasks = await apiRequest(endpoint);
612
+ return {
613
+ content: [
614
+ {
615
+ type: 'text',
616
+ text: JSON.stringify(tasks, null, 2),
617
+ },
618
+ ],
619
+ };
620
+ },
621
+
622
+ async quick_log({ project_id, title, minutes, description, notes, acceptance_criteria }) {
623
+ // Step 1: Create the task
624
+ const task = await apiRequest(`/projects/${project_id}/tasks`, {
625
+ method: 'POST',
626
+ body: JSON.stringify({
627
+ title,
628
+ description,
629
+ acceptance_criteria,
630
+ estimated_minutes: minutes, // Use actual time as estimate since it's completed
631
+ }),
632
+ });
633
+
634
+ const taskId = task.id;
635
+
636
+ // Step 2: Mark task as done
637
+ await apiRequest(`/tasks/${taskId}/status`, {
638
+ method: 'PUT',
639
+ body: JSON.stringify({ status: 'done' }),
640
+ });
641
+
642
+ // Step 3: Log the time
643
+ const timeNotes = notes || description || `Completed: ${title}`;
644
+ await apiRequest(`/tasks/${taskId}/time`, {
645
+ method: 'POST',
646
+ body: JSON.stringify({
647
+ minutes,
648
+ notes: timeNotes,
649
+ date: new Date().toISOString().split('T')[0],
650
+ }),
651
+ });
652
+
653
+ return {
654
+ content: [
655
+ {
656
+ type: 'text',
657
+ text: `Created and completed task #${taskId}: "${title}"\n` +
658
+ `Logged ${minutes} minutes to project ${project_id}\n` +
659
+ `Notes: ${timeNotes}`,
660
+ },
661
+ ],
662
+ };
663
+ },
664
+
665
+ async start_working({ task_id }) {
666
+ await apiRequest(`/tasks/${task_id}/assignments`, {
667
+ method: 'POST',
668
+ body: JSON.stringify({ is_active: true }),
669
+ });
670
+
671
+ return {
672
+ content: [
673
+ {
674
+ type: 'text',
675
+ text: `Started working on task #${task_id}. Other team members can now see you are actively working on this task.`,
676
+ },
677
+ ],
678
+ };
679
+ },
680
+
681
+ async stop_working({ task_id }) {
682
+ await apiRequest(`/tasks/${task_id}/assignments?stop_only=true`, {
683
+ method: 'DELETE',
684
+ });
685
+
686
+ return {
687
+ content: [
688
+ {
689
+ type: 'text',
690
+ text: `Stopped working on task #${task_id}. You are still assigned to the task but no longer marked as actively working.`,
691
+ },
692
+ ],
693
+ };
694
+ },
695
+
696
+ // Products handlers
697
+ async list_products({ status }) {
698
+ let endpoint = '/products';
699
+ if (status) endpoint += `?status=${status}`;
700
+
701
+ const products = await apiRequest(endpoint);
702
+ return {
703
+ content: [
704
+ {
705
+ type: 'text',
706
+ text: JSON.stringify(products, null, 2),
707
+ },
708
+ ],
709
+ };
710
+ },
711
+
712
+ async get_product({ product_id }) {
713
+ const product = await apiRequest(`/products/${product_id}`);
714
+ return {
715
+ content: [
716
+ {
717
+ type: 'text',
718
+ text: JSON.stringify(product, null, 2),
719
+ },
720
+ ],
721
+ };
722
+ },
723
+
724
+ async create_product({ name, description, github_repo_url }) {
725
+ const product = await apiRequest('/products', {
726
+ method: 'POST',
727
+ body: JSON.stringify({
728
+ name,
729
+ description,
730
+ github_repo_url,
731
+ }),
732
+ });
733
+ return {
734
+ content: [
735
+ {
736
+ type: 'text',
737
+ text: JSON.stringify(product, null, 2),
738
+ },
739
+ ],
740
+ };
741
+ },
742
+
743
+ async link_project_to_product({ product_id, project_id, is_primary }) {
744
+ const result = await apiRequest(`/products/${product_id}/projects`, {
745
+ method: 'POST',
746
+ body: JSON.stringify({
747
+ project_id,
748
+ is_primary: is_primary || false,
749
+ }),
750
+ });
751
+ return {
752
+ content: [
753
+ {
754
+ type: 'text',
755
+ text: `Project ${project_id} linked to product ${product_id}. ${is_primary ? '(Primary project)' : ''}`,
756
+ },
757
+ ],
758
+ };
759
+ },
760
+ };
761
+
762
+ // Create and configure the MCP server
763
+ const server = new Server(
764
+ {
765
+ name: 'idea-base',
766
+ version: '1.0.0',
767
+ },
768
+ {
769
+ capabilities: {
770
+ tools: {},
771
+ },
772
+ }
773
+ );
774
+
775
+ // Handle list tools request
776
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
777
+ return { tools };
778
+ });
779
+
780
+ // Handle tool calls
781
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
782
+ const { name, arguments: args } = request.params;
783
+
784
+ const handler = toolHandlers[name];
785
+ if (!handler) {
786
+ throw new Error(`Unknown tool: ${name}`);
787
+ }
788
+
789
+ try {
790
+ return await handler(args);
791
+ } catch (error) {
792
+ return {
793
+ content: [
794
+ {
795
+ type: 'text',
796
+ text: `Error: ${error.message}`,
797
+ },
798
+ ],
799
+ isError: true,
800
+ };
801
+ }
802
+ });
803
+
804
+ // Start the server
805
+ async function main() {
806
+ const transport = new StdioServerTransport();
807
+ await server.connect(transport);
808
+ console.error('IDEA Base MCP Server running on stdio');
809
+ }
810
+
811
+ main().catch((error) => {
812
+ console.error('Fatal error:', error);
813
+ process.exit(1);
814
+ });