@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.
- package/LICENSE +21 -0
- package/README.md +131 -0
- package/package.json +51 -0
- 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
|
+
});
|