@studiometa/productive-mcp 0.4.5 → 0.5.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 +139 -48
- package/dist/formatters.d.ts +18 -8
- package/dist/formatters.d.ts.map +1 -1
- package/dist/handlers.d.ts +3 -5
- package/dist/handlers.d.ts.map +1 -1
- package/dist/handlers.js +193 -94
- package/dist/handlers.js.map +1 -1
- package/dist/http.js +1 -1
- package/dist/index.js +1 -1
- package/dist/server.js +1 -1
- package/dist/tools.d.ts +3 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +27 -194
- package/dist/tools.js.map +1 -1
- package/dist/version-eQNCcjOb.js +5 -0
- package/dist/{version-3wN4XBZL.js.map → version-eQNCcjOb.js.map} +1 -1
- package/package.json +1 -1
- package/dist/version-3wN4XBZL.js +0 -5
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ MCP (Model Context Protocol) server for [Productive.io](https://productive.io) A
|
|
|
13
13
|
- 🔑 **OAuth 2.0 support** for Claude Desktop custom connectors
|
|
14
14
|
- 🌐 Deploy once, share with your team via Claude Desktop custom connectors
|
|
15
15
|
- 🐳 Docker-ready for easy deployment
|
|
16
|
+
- ⚡ **Token-optimized** - Single tool design minimizes context usage (~180 tokens)
|
|
16
17
|
- 📦 Built on [@studiometa/productive-cli](../productive-cli)
|
|
17
18
|
|
|
18
19
|
## Usage Modes
|
|
@@ -192,63 +193,135 @@ echo -n "12345:pk_abc123xyz:67890" | base64
|
|
|
192
193
|
|
|
193
194
|
---
|
|
194
195
|
|
|
195
|
-
##
|
|
196
|
+
## The `productive` Tool
|
|
196
197
|
|
|
197
|
-
|
|
198
|
-
- `productive_list_projects` - List projects with optional filters
|
|
199
|
-
- `productive_get_project` - Get project details by ID
|
|
198
|
+
The MCP server exposes a single unified tool optimized for minimal token usage:
|
|
200
199
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
200
|
+
```
|
|
201
|
+
productive(resource, action, ...)
|
|
202
|
+
```
|
|
204
203
|
|
|
205
|
-
###
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
204
|
+
### Resources & Actions
|
|
205
|
+
|
|
206
|
+
| Resource | Actions | Description |
|
|
207
|
+
|----------|---------|-------------|
|
|
208
|
+
| `projects` | `list`, `get` | Project management |
|
|
209
|
+
| `time` | `list`, `get`, `create`, `update`, `delete` | Time tracking |
|
|
210
|
+
| `tasks` | `list`, `get` | Task management |
|
|
211
|
+
| `services` | `list` | Budget line items |
|
|
212
|
+
| `people` | `list`, `get`, `me` | Team members |
|
|
213
|
+
|
|
214
|
+
### Parameters
|
|
215
|
+
|
|
216
|
+
| Parameter | Type | Description |
|
|
217
|
+
|-----------|------|-------------|
|
|
218
|
+
| `resource` | string | **Required**. One of: `projects`, `time`, `tasks`, `services`, `people` |
|
|
219
|
+
| `action` | string | **Required**. Action to perform (see table above) |
|
|
220
|
+
| `id` | string | Resource ID (required for `get`, `update`, `delete`) |
|
|
221
|
+
| `filter` | object | Filter criteria for `list` actions |
|
|
222
|
+
| `page` | number | Page number for pagination |
|
|
223
|
+
| `per_page` | number | Items per page (default: 20, max: 200) |
|
|
224
|
+
| `compact` | boolean | Compact output mode (default: true) |
|
|
225
|
+
| `person_id` | string | Person ID (for time entry creation) |
|
|
226
|
+
| `service_id` | string | Service ID (for time entry creation) |
|
|
227
|
+
| `time` | number | Time in minutes (for time entries) |
|
|
228
|
+
| `date` | string | Date in YYYY-MM-DD format |
|
|
229
|
+
| `note` | string | Note/description |
|
|
230
|
+
|
|
231
|
+
### Filter Options
|
|
232
|
+
|
|
233
|
+
#### Projects
|
|
234
|
+
- `company_id` - Filter by company
|
|
235
|
+
- `project_manager_id` - Filter by project manager
|
|
236
|
+
|
|
237
|
+
#### Time Entries
|
|
238
|
+
- `person_id` - Filter by person
|
|
239
|
+
- `project_id` - Filter by project
|
|
240
|
+
- `service_id` - Filter by service
|
|
241
|
+
- `after` - After date (YYYY-MM-DD)
|
|
242
|
+
- `before` - Before date (YYYY-MM-DD)
|
|
243
|
+
|
|
244
|
+
#### Tasks
|
|
245
|
+
- `project_id` - Filter by project
|
|
246
|
+
- `assignee_id` - Filter by assignee
|
|
247
|
+
- `task_list_id` - Filter by task list
|
|
248
|
+
|
|
249
|
+
#### Services
|
|
250
|
+
- `project_id` - Filter by project
|
|
251
|
+
- `deal_id` - Filter by deal
|
|
252
|
+
|
|
253
|
+
#### People
|
|
254
|
+
- `archived` - Include archived (boolean)
|
|
255
|
+
|
|
256
|
+
### Configuration Tools (Local mode only)
|
|
257
|
+
|
|
258
|
+
| Tool | Description |
|
|
259
|
+
|------|-------------|
|
|
260
|
+
| `productive_configure` | Configure credentials (organizationId, apiToken, userId) |
|
|
261
|
+
| `productive_get_config` | View current configuration (token masked) |
|
|
211
262
|
|
|
212
|
-
|
|
213
|
-
- `productive_list_services` - List services (budget line items)
|
|
263
|
+
---
|
|
214
264
|
|
|
215
|
-
|
|
216
|
-
- `productive_list_people` - List people in the organization
|
|
217
|
-
- `productive_get_person` - Get person details by ID
|
|
218
|
-
- `productive_get_current_user` - Get current authenticated user
|
|
265
|
+
## Usage Examples
|
|
219
266
|
|
|
220
|
-
###
|
|
221
|
-
- `productive_configure` - Configure credentials
|
|
222
|
-
- `productive_get_config` - View current configuration
|
|
267
|
+
### First Time Setup (Local Mode)
|
|
223
268
|
|
|
224
|
-
|
|
269
|
+
```
|
|
270
|
+
You: "Configure my Productive.io credentials"
|
|
271
|
+
Claude: "I'll help you set up. Please provide your Organization ID and API Token..."
|
|
272
|
+
```
|
|
225
273
|
|
|
226
|
-
|
|
274
|
+
### List Projects
|
|
227
275
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
5. Note your User ID (click your profile, visible in URL)
|
|
276
|
+
```
|
|
277
|
+
You: "Show me all projects"
|
|
278
|
+
Claude uses: productive(resource="projects", action="list")
|
|
279
|
+
```
|
|
233
280
|
|
|
234
|
-
|
|
281
|
+
### Get Project Details
|
|
235
282
|
|
|
236
|
-
|
|
283
|
+
```
|
|
284
|
+
You: "Get details for project 12345"
|
|
285
|
+
Claude uses: productive(resource="projects", action="get", id="12345")
|
|
286
|
+
```
|
|
237
287
|
|
|
238
|
-
###
|
|
288
|
+
### Create Time Entry
|
|
239
289
|
|
|
240
290
|
```
|
|
241
|
-
You: "
|
|
242
|
-
Claude: "
|
|
291
|
+
You: "Log 2 hours today on service 456"
|
|
292
|
+
Claude uses: productive(resource="time", action="create", person_id="...", service_id="456", time=120, date="2024-01-15")
|
|
243
293
|
```
|
|
244
294
|
|
|
245
|
-
###
|
|
295
|
+
### List My Time Entries
|
|
246
296
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
297
|
+
```
|
|
298
|
+
You: "What did I work on last week?"
|
|
299
|
+
Claude uses: productive(resource="time", action="list", filter={person_id: "...", after: "2024-01-08", before: "2024-01-14"})
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Get Current User
|
|
303
|
+
|
|
304
|
+
```
|
|
305
|
+
You: "Who am I logged in as?"
|
|
306
|
+
Claude uses: productive(resource="people", action="me")
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### List Tasks for a Project
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
You: "Show tasks for project 789"
|
|
313
|
+
Claude uses: productive(resource="tasks", action="list", filter={project_id: "789"})
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Get Your Productive.io Credentials
|
|
319
|
+
|
|
320
|
+
1. Log into [Productive.io](https://productive.io)
|
|
321
|
+
2. Go to **Settings → Integrations → API**
|
|
322
|
+
3. Generate an API token
|
|
323
|
+
4. Note your Organization ID (visible in URL or API settings)
|
|
324
|
+
5. Note your User ID (click your profile, visible in URL)
|
|
252
325
|
|
|
253
326
|
---
|
|
254
327
|
|
|
@@ -256,8 +329,8 @@ Claude: "I'll help you set up. Please provide your Organization ID and API Token
|
|
|
256
329
|
|
|
257
330
|
```bash
|
|
258
331
|
# Clone the repository
|
|
259
|
-
git clone https://github.com/studiometa/productive-
|
|
260
|
-
cd productive-
|
|
332
|
+
git clone https://github.com/studiometa/productive-tools
|
|
333
|
+
cd productive-tools
|
|
261
334
|
|
|
262
335
|
# Install dependencies
|
|
263
336
|
npm install
|
|
@@ -271,6 +344,9 @@ npm run build -w @studiometa/productive-mcp
|
|
|
271
344
|
# Development mode (watch)
|
|
272
345
|
npm run dev -w @studiometa/productive-mcp
|
|
273
346
|
|
|
347
|
+
# Run tests
|
|
348
|
+
npm test -w @studiometa/productive-mcp
|
|
349
|
+
|
|
274
350
|
# Test local server
|
|
275
351
|
node packages/productive-mcp/dist/index.js
|
|
276
352
|
|
|
@@ -290,7 +366,7 @@ TOKEN=$(echo -n "YOUR_ORG_ID:YOUR_API_TOKEN:YOUR_USER_ID" | base64)
|
|
|
290
366
|
# Test health endpoint
|
|
291
367
|
curl http://localhost:3000/health
|
|
292
368
|
|
|
293
|
-
# Test MCP endpoint
|
|
369
|
+
# Test MCP endpoint - list tools
|
|
294
370
|
curl -X POST http://localhost:3000/mcp \
|
|
295
371
|
-H "Authorization: Bearer $TOKEN" \
|
|
296
372
|
-H "Content-Type: application/json" \
|
|
@@ -300,7 +376,13 @@ curl -X POST http://localhost:3000/mcp \
|
|
|
300
376
|
curl -X POST http://localhost:3000/mcp \
|
|
301
377
|
-H "Authorization: Bearer $TOKEN" \
|
|
302
378
|
-H "Content-Type: application/json" \
|
|
303
|
-
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"
|
|
379
|
+
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"productive","arguments":{"resource":"projects","action":"list"}},"id":2}'
|
|
380
|
+
|
|
381
|
+
# Get a specific project
|
|
382
|
+
curl -X POST http://localhost:3000/mcp \
|
|
383
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
384
|
+
-H "Content-Type: application/json" \
|
|
385
|
+
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"productive","arguments":{"resource":"projects","action":"get","id":"12345"}},"id":3}'
|
|
304
386
|
```
|
|
305
387
|
|
|
306
388
|
---
|
|
@@ -353,13 +435,22 @@ productive-mcp/
|
|
|
353
435
|
│ ├── http.ts # HTTP routes and MCP endpoint
|
|
354
436
|
│ ├── oauth.ts # OAuth 2.0 endpoints
|
|
355
437
|
│ ├── crypto.ts # Encryption for stateless OAuth tokens
|
|
356
|
-
│ ├── tools.ts # Tool definitions (
|
|
357
|
-
│ ├── handlers.ts # Tool execution
|
|
438
|
+
│ ├── tools.ts # Tool definitions (single unified tool)
|
|
439
|
+
│ ├── handlers.ts # Tool execution logic
|
|
440
|
+
│ ├── formatters.ts # Response formatting with compact mode
|
|
358
441
|
│ └── auth.ts # Bearer token parsing
|
|
359
442
|
├── Dockerfile
|
|
360
443
|
└── README.md
|
|
361
444
|
```
|
|
362
445
|
|
|
446
|
+
### Token Optimization
|
|
447
|
+
|
|
448
|
+
The server uses a single unified `productive` tool instead of multiple individual tools. This reduces the tool schema from ~1300 tokens to ~180 tokens (86% reduction), which:
|
|
449
|
+
|
|
450
|
+
- Reduces context window usage
|
|
451
|
+
- Minimizes compaction frequency
|
|
452
|
+
- Improves response times
|
|
453
|
+
|
|
363
454
|
### OAuth Flow (Stateless)
|
|
364
455
|
|
|
365
456
|
The OAuth implementation is **stateless** - no database or session storage required:
|
|
@@ -386,8 +477,8 @@ MIT © [Studio Meta](https://www.studiometa.fr)
|
|
|
386
477
|
|
|
387
478
|
## Links
|
|
388
479
|
|
|
389
|
-
- [GitHub Repository](https://github.com/studiometa/productive-
|
|
480
|
+
- [GitHub Repository](https://github.com/studiometa/productive-tools)
|
|
390
481
|
- [Productive.io API Docs](https://developer.productive.io)
|
|
391
482
|
- [MCP Documentation](https://modelcontextprotocol.io)
|
|
392
483
|
- [Claude Desktop Custom Connectors](https://docs.anthropic.com)
|
|
393
|
-
- [Issues](https://github.com/studiometa/productive-
|
|
484
|
+
- [Issues](https://github.com/studiometa/productive-tools/issues)
|
package/dist/formatters.d.ts
CHANGED
|
@@ -3,39 +3,49 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This module re-exports formatters from @studiometa/productive-cli
|
|
5
5
|
* with MCP-specific defaults (no relationship IDs, no timestamps).
|
|
6
|
+
*
|
|
7
|
+
* Supports compact mode to reduce token usage by omitting verbose fields
|
|
8
|
+
* like descriptions and notes from list responses.
|
|
6
9
|
*/
|
|
7
10
|
import { type JsonApiResource, type JsonApiMeta, type FormatOptions, type FormattedPagination } from '@studiometa/productive-cli';
|
|
8
11
|
export type { JsonApiResource, JsonApiMeta, FormatOptions, FormattedPagination };
|
|
12
|
+
/**
|
|
13
|
+
* Extended format options for MCP with compact mode
|
|
14
|
+
*/
|
|
15
|
+
export interface McpFormatOptions {
|
|
16
|
+
compact?: boolean;
|
|
17
|
+
included?: JsonApiResource[];
|
|
18
|
+
}
|
|
9
19
|
/**
|
|
10
20
|
* Format time entry for agent consumption
|
|
11
21
|
*/
|
|
12
|
-
export declare function formatTimeEntry(entry: JsonApiResource,
|
|
22
|
+
export declare function formatTimeEntry(entry: JsonApiResource, options?: McpFormatOptions): Record<string, unknown>;
|
|
13
23
|
/**
|
|
14
24
|
* Format project for agent consumption
|
|
15
25
|
*/
|
|
16
|
-
export declare function formatProject(project: JsonApiResource,
|
|
26
|
+
export declare function formatProject(project: JsonApiResource, options?: McpFormatOptions): Record<string, unknown>;
|
|
17
27
|
/**
|
|
18
28
|
* Format task for agent consumption
|
|
19
29
|
* Tasks use included resources to resolve project/company names
|
|
20
30
|
*/
|
|
21
|
-
export declare function formatTask(task: JsonApiResource,
|
|
31
|
+
export declare function formatTask(task: JsonApiResource, options?: McpFormatOptions): Record<string, unknown>;
|
|
22
32
|
/**
|
|
23
33
|
* Format person for agent consumption
|
|
24
34
|
*/
|
|
25
|
-
export declare function formatPerson(person: JsonApiResource,
|
|
35
|
+
export declare function formatPerson(person: JsonApiResource, options?: McpFormatOptions): Record<string, unknown>;
|
|
26
36
|
/**
|
|
27
37
|
* Format service for agent consumption
|
|
28
38
|
*/
|
|
29
|
-
export declare function formatService(service: JsonApiResource,
|
|
39
|
+
export declare function formatService(service: JsonApiResource, options?: McpFormatOptions): Record<string, unknown>;
|
|
30
40
|
/**
|
|
31
41
|
* Format list response with pagination
|
|
32
42
|
*
|
|
33
43
|
* @param data - Array of JSON:API resources
|
|
34
|
-
* @param formatter - Formatter function (item,
|
|
44
|
+
* @param formatter - Formatter function (item, options?) => T
|
|
35
45
|
* @param meta - Pagination metadata
|
|
36
|
-
* @param
|
|
46
|
+
* @param options - MCP format options (compact, included)
|
|
37
47
|
*/
|
|
38
|
-
export declare function formatListResponse<T>(data: JsonApiResource[], formatter: (item: JsonApiResource,
|
|
48
|
+
export declare function formatListResponse<T>(data: JsonApiResource[], formatter: (item: JsonApiResource, options?: McpFormatOptions) => T, meta?: JsonApiMeta, options?: McpFormatOptions): {
|
|
39
49
|
data: T[];
|
|
40
50
|
meta?: FormattedPagination;
|
|
41
51
|
};
|
package/dist/formatters.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatters.d.ts","sourceRoot":"","sources":["../src/formatters.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"formatters.d.ts","sourceRoot":"","sources":["../src/formatters.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAOL,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACzB,MAAM,4BAA4B,CAAC;AAGpC,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,mBAAmB,EAAE,CAAC;AAcjF;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;CAC9B;AAgBD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,eAAe,EACtB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,eAAe,EACxB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAazB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,eAAe,EACvB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,eAAe,EACxB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,IAAI,EAAE,eAAe,EAAE,EACvB,SAAS,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,gBAAgB,KAAK,CAAC,EACnE,IAAI,CAAC,EAAE,WAAW,EAClB,OAAO,CAAC,EAAE,gBAAgB,GACzB;IAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,mBAAmB,CAAA;CAAE,CAY3C"}
|
package/dist/handlers.d.ts
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tool execution handlers for Productive MCP server
|
|
3
3
|
* These are shared between stdio and HTTP transports
|
|
4
|
+
*
|
|
5
|
+
* Single consolidated tool for minimal token overhead:
|
|
6
|
+
* - productive: resource + action based API
|
|
4
7
|
*/
|
|
5
8
|
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
6
9
|
import type { ProductiveCredentials } from './auth.js';
|
|
7
10
|
export type ToolResult = CallToolResult;
|
|
8
11
|
/**
|
|
9
12
|
* Execute a tool with the given credentials and arguments
|
|
10
|
-
*
|
|
11
|
-
* @param name - Tool name
|
|
12
|
-
* @param args - Tool arguments
|
|
13
|
-
* @param credentials - Productive API credentials
|
|
14
|
-
* @returns Tool execution result
|
|
15
13
|
*/
|
|
16
14
|
export declare function executeToolWithCredentials(name: string, args: Record<string, unknown>, credentials: ProductiveCredentials): Promise<ToolResult>;
|
|
17
15
|
//# sourceMappingURL=handlers.d.ts.map
|
package/dist/handlers.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAWvD,MAAM,MAAM,UAAU,GAAG,cAAc,CAAC;AA0DxC;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,WAAW,EAAE,qBAAqB,GACjC,OAAO,CAAC,UAAU,CAAC,CAoLrB"}
|
package/dist/handlers.js
CHANGED
|
@@ -1,34 +1,71 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { formatProject as formatProject$1, formatListResponse as formatListResponse$1, formatTimeEntry as formatTimeEntry$1, formatTask as formatTask$1, formatService as formatService$1, formatPerson as formatPerson$1, ProductiveApi } from "@studiometa/productive-cli";
|
|
2
2
|
const MCP_FORMAT_OPTIONS = {
|
|
3
3
|
includeRelationshipIds: false,
|
|
4
4
|
includeTimestamps: false,
|
|
5
5
|
stripHtml: true
|
|
6
6
|
};
|
|
7
|
-
function
|
|
8
|
-
|
|
7
|
+
function compactify(obj, fieldsToRemove) {
|
|
8
|
+
const result = { ...obj };
|
|
9
|
+
for (const field of fieldsToRemove) {
|
|
10
|
+
delete result[field];
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
9
13
|
}
|
|
10
|
-
function
|
|
11
|
-
|
|
14
|
+
function formatTimeEntry(entry, options) {
|
|
15
|
+
const result = formatTimeEntry$1(entry, MCP_FORMAT_OPTIONS);
|
|
16
|
+
if (options?.compact) {
|
|
17
|
+
return compactify(result, ["note", "billable_time", "approved"]);
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
12
20
|
}
|
|
13
|
-
function
|
|
14
|
-
|
|
21
|
+
function formatProject(project, options) {
|
|
22
|
+
const result = formatProject$1(project, MCP_FORMAT_OPTIONS);
|
|
23
|
+
if (options?.compact) {
|
|
24
|
+
return compactify(result, ["budget"]);
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
function formatTask(task, options) {
|
|
29
|
+
const result = formatTask$1(task, { ...MCP_FORMAT_OPTIONS, included: options?.included });
|
|
30
|
+
if (options?.compact) {
|
|
31
|
+
return compactify(result, [
|
|
32
|
+
"description",
|
|
33
|
+
"initial_estimate",
|
|
34
|
+
"worked_time",
|
|
35
|
+
"remaining_time",
|
|
36
|
+
"project",
|
|
37
|
+
// Keep project_name but remove nested object
|
|
38
|
+
"company"
|
|
39
|
+
// Keep company name inline if needed
|
|
40
|
+
]);
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
15
43
|
}
|
|
16
|
-
function formatPerson(person,
|
|
17
|
-
|
|
44
|
+
function formatPerson(person, options) {
|
|
45
|
+
const result = formatPerson$1(person, MCP_FORMAT_OPTIONS);
|
|
46
|
+
if (options?.compact) {
|
|
47
|
+
return compactify(result, ["title", "first_name", "last_name"]);
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
18
50
|
}
|
|
19
|
-
function formatService(service,
|
|
20
|
-
|
|
51
|
+
function formatService(service, options) {
|
|
52
|
+
const result = formatService$1(service, MCP_FORMAT_OPTIONS);
|
|
53
|
+
if (options?.compact) {
|
|
54
|
+
return compactify(result, ["budgeted_time", "worked_time"]);
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
21
57
|
}
|
|
22
|
-
function formatListResponse(data, formatter, meta,
|
|
23
|
-
const wrappedFormatter = (item,
|
|
24
|
-
return formatter(item,
|
|
58
|
+
function formatListResponse(data, formatter, meta, options) {
|
|
59
|
+
const wrappedFormatter = (item, _cliOptions) => {
|
|
60
|
+
return formatter(item, options);
|
|
25
61
|
};
|
|
26
62
|
const result = formatListResponse$1(data, wrappedFormatter, meta, {
|
|
27
63
|
...MCP_FORMAT_OPTIONS,
|
|
28
|
-
included
|
|
64
|
+
included: options?.included
|
|
29
65
|
});
|
|
30
66
|
return result;
|
|
31
67
|
}
|
|
68
|
+
const DEFAULT_PER_PAGE = 20;
|
|
32
69
|
function jsonResult(data) {
|
|
33
70
|
return {
|
|
34
71
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
@@ -40,96 +77,158 @@ function errorResult(message) {
|
|
|
40
77
|
isError: true
|
|
41
78
|
};
|
|
42
79
|
}
|
|
80
|
+
function toStringFilter(filter) {
|
|
81
|
+
if (!filter) return void 0;
|
|
82
|
+
const result = {};
|
|
83
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
84
|
+
if (value !== void 0 && value !== null) {
|
|
85
|
+
result[key] = String(value);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
89
|
+
}
|
|
43
90
|
async function executeToolWithCredentials(name, args, credentials) {
|
|
44
91
|
const api = new ProductiveApi({
|
|
45
|
-
|
|
46
|
-
|
|
92
|
+
token: credentials.apiToken,
|
|
93
|
+
"org-id": credentials.organizationId,
|
|
94
|
+
"user-id": credentials.userId
|
|
47
95
|
});
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
case "productive_delete_time_entry": {
|
|
84
|
-
await api.deleteTimeEntry(args.id);
|
|
85
|
-
return jsonResult({ success: true, message: "Time entry deleted" });
|
|
86
|
-
}
|
|
87
|
-
case "productive_list_tasks": {
|
|
88
|
-
const params = args || {};
|
|
89
|
-
params.include = ["project", "project.company"];
|
|
90
|
-
const result = await api.getTasks(params);
|
|
91
|
-
return jsonResult(formatListResponse(
|
|
92
|
-
result.data,
|
|
93
|
-
formatTask,
|
|
94
|
-
result.meta,
|
|
95
|
-
result.included
|
|
96
|
-
));
|
|
97
|
-
}
|
|
98
|
-
case "productive_get_task": {
|
|
99
|
-
const result = await api.getTask(args.id, {
|
|
100
|
-
include: ["project", "project.company"]
|
|
101
|
-
});
|
|
102
|
-
return jsonResult(formatTask(result.data, result.included));
|
|
96
|
+
if (name === "productive") {
|
|
97
|
+
const {
|
|
98
|
+
resource,
|
|
99
|
+
action,
|
|
100
|
+
id,
|
|
101
|
+
filter,
|
|
102
|
+
page,
|
|
103
|
+
per_page,
|
|
104
|
+
compact = true,
|
|
105
|
+
person_id,
|
|
106
|
+
service_id,
|
|
107
|
+
task_id,
|
|
108
|
+
time,
|
|
109
|
+
date,
|
|
110
|
+
note
|
|
111
|
+
} = args;
|
|
112
|
+
const formatOptions = { compact };
|
|
113
|
+
const stringFilter = toStringFilter(filter);
|
|
114
|
+
const perPage = per_page ?? DEFAULT_PER_PAGE;
|
|
115
|
+
try {
|
|
116
|
+
if (resource === "projects") {
|
|
117
|
+
if (action === "get") {
|
|
118
|
+
if (!id) return errorResult("id is required for get action");
|
|
119
|
+
const result = await api.getProject(id);
|
|
120
|
+
return jsonResult(formatProject(result.data, formatOptions));
|
|
121
|
+
}
|
|
122
|
+
if (action === "list") {
|
|
123
|
+
const result = await api.getProjects({ filter: stringFilter, page, perPage });
|
|
124
|
+
return jsonResult(
|
|
125
|
+
formatListResponse(result.data, formatProject, result.meta, formatOptions)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return errorResult(`Invalid action "${action}" for projects. Use: list, get`);
|
|
103
129
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
130
|
+
if (resource === "time") {
|
|
131
|
+
if (action === "get") {
|
|
132
|
+
if (!id) return errorResult("id is required for get action");
|
|
133
|
+
const result = await api.getTimeEntry(id);
|
|
134
|
+
return jsonResult(formatTimeEntry(result.data, formatOptions));
|
|
135
|
+
}
|
|
136
|
+
if (action === "create") {
|
|
137
|
+
if (!person_id || !service_id || !time || !date) {
|
|
138
|
+
return errorResult("person_id, service_id, time, and date are required for create");
|
|
139
|
+
}
|
|
140
|
+
const result = await api.createTimeEntry({
|
|
141
|
+
person_id,
|
|
142
|
+
service_id,
|
|
143
|
+
time,
|
|
144
|
+
date,
|
|
145
|
+
note,
|
|
146
|
+
task_id
|
|
147
|
+
});
|
|
148
|
+
return jsonResult({ success: true, ...formatTimeEntry(result.data, formatOptions) });
|
|
149
|
+
}
|
|
150
|
+
if (action === "update") {
|
|
151
|
+
if (!id) return errorResult("id is required for update action");
|
|
152
|
+
const updateData = {};
|
|
153
|
+
if (time !== void 0) updateData.time = time;
|
|
154
|
+
if (date !== void 0) updateData.date = date;
|
|
155
|
+
if (note !== void 0) updateData.note = note;
|
|
156
|
+
const result = await api.updateTimeEntry(id, updateData);
|
|
157
|
+
return jsonResult({ success: true, ...formatTimeEntry(result.data, formatOptions) });
|
|
158
|
+
}
|
|
159
|
+
if (action === "delete") {
|
|
160
|
+
if (!id) return errorResult("id is required for delete action");
|
|
161
|
+
await api.deleteTimeEntry(id);
|
|
162
|
+
return jsonResult({ success: true, message: "Time entry deleted" });
|
|
163
|
+
}
|
|
164
|
+
if (action === "list") {
|
|
165
|
+
const result = await api.getTimeEntries({ filter: stringFilter, page, perPage });
|
|
166
|
+
return jsonResult(
|
|
167
|
+
formatListResponse(result.data, formatTimeEntry, result.meta, formatOptions)
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
return errorResult(`Invalid action "${action}" for time. Use: list, get, create, update, delete`);
|
|
107
171
|
}
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
172
|
+
if (resource === "tasks") {
|
|
173
|
+
const include = ["project", "project.company"];
|
|
174
|
+
if (action === "get") {
|
|
175
|
+
if (!id) return errorResult("id is required for get action");
|
|
176
|
+
const result = await api.getTask(id, { include });
|
|
177
|
+
return jsonResult(
|
|
178
|
+
formatTask(result.data, { ...formatOptions, included: result.included })
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
if (action === "list") {
|
|
182
|
+
const result = await api.getTasks({ filter: stringFilter, page, perPage, include });
|
|
183
|
+
return jsonResult(
|
|
184
|
+
formatListResponse(result.data, formatTask, result.meta, {
|
|
185
|
+
...formatOptions,
|
|
186
|
+
included: result.included
|
|
187
|
+
})
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
return errorResult(`Invalid action "${action}" for tasks. Use: list, get`);
|
|
111
191
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
192
|
+
if (resource === "services") {
|
|
193
|
+
if (action === "list") {
|
|
194
|
+
const result = await api.getServices({ filter: stringFilter, page, perPage });
|
|
195
|
+
return jsonResult(
|
|
196
|
+
formatListResponse(result.data, formatService, result.meta, formatOptions)
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return errorResult(`Invalid action "${action}" for services. Use: list`);
|
|
115
200
|
}
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
201
|
+
if (resource === "people") {
|
|
202
|
+
if (action === "get") {
|
|
203
|
+
if (!id) return errorResult("id is required for get action");
|
|
204
|
+
const result = await api.getPerson(id);
|
|
205
|
+
return jsonResult(formatPerson(result.data, formatOptions));
|
|
206
|
+
}
|
|
207
|
+
if (action === "me") {
|
|
208
|
+
if (credentials.userId) {
|
|
209
|
+
const result = await api.getPerson(credentials.userId);
|
|
210
|
+
return jsonResult(formatPerson(result.data, formatOptions));
|
|
211
|
+
}
|
|
212
|
+
return jsonResult({
|
|
213
|
+
message: "User ID not configured. Set userId in credentials to use this action.",
|
|
214
|
+
organizationId: credentials.organizationId
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
if (action === "list") {
|
|
218
|
+
const result = await api.getPeople({ filter: stringFilter, page, perPage });
|
|
219
|
+
return jsonResult(
|
|
220
|
+
formatListResponse(result.data, formatPerson, result.meta, formatOptions)
|
|
221
|
+
);
|
|
120
222
|
}
|
|
121
|
-
return
|
|
122
|
-
message: "User ID not configured. Set userId in credentials to use this tool.",
|
|
123
|
-
organizationId: credentials.organizationId
|
|
124
|
-
});
|
|
223
|
+
return errorResult(`Invalid action "${action}" for people. Use: list, get, me`);
|
|
125
224
|
}
|
|
126
|
-
|
|
127
|
-
|
|
225
|
+
return errorResult(`Unknown resource: ${resource}`);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
228
|
+
return errorResult(message);
|
|
128
229
|
}
|
|
129
|
-
} catch (error) {
|
|
130
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
131
|
-
return errorResult(message);
|
|
132
230
|
}
|
|
231
|
+
return errorResult(`Unknown tool: ${name}`);
|
|
133
232
|
}
|
|
134
233
|
export {
|
|
135
234
|
executeToolWithCredentials
|
package/dist/handlers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.js","sources":["../src/formatters.ts","../src/handlers.ts"],"sourcesContent":["/**\n * Response formatters for agent-friendly output\n *\n * This module re-exports formatters from @studiometa/productive-cli\n * with MCP-specific defaults (no relationship IDs, no timestamps).\n */\n\nimport {\n formatTimeEntry as cliFormatTimeEntry,\n formatProject as cliFormatProject,\n formatTask as cliFormatTask,\n formatPerson as cliFormatPerson,\n formatService as cliFormatService,\n formatListResponse as cliFormatListResponse,\n type JsonApiResource,\n type JsonApiMeta,\n type FormatOptions,\n type FormattedPagination,\n} from '@studiometa/productive-cli';\n\n// Re-export types\nexport type { JsonApiResource, JsonApiMeta, FormatOptions, FormattedPagination };\n\n/**\n * MCP-specific format options\n * - No relationship IDs (cleaner output for agents)\n * - No timestamps (reduce noise)\n * - HTML stripping enabled\n */\nconst MCP_FORMAT_OPTIONS: FormatOptions = {\n includeRelationshipIds: false,\n includeTimestamps: false,\n stripHtml: true,\n};\n\n/**\n * Format time entry for agent consumption\n */\nexport function formatTimeEntry(\n entry: JsonApiResource,\n _included?: JsonApiResource[]\n): Record<string, unknown> {\n return cliFormatTimeEntry(entry, MCP_FORMAT_OPTIONS);\n}\n\n/**\n * Format project for agent consumption\n */\nexport function formatProject(\n project: JsonApiResource,\n _included?: JsonApiResource[]\n): Record<string, unknown> {\n return cliFormatProject(project, MCP_FORMAT_OPTIONS);\n}\n\n/**\n * Format task for agent consumption\n * Tasks use included resources to resolve project/company names\n */\nexport function formatTask(\n task: JsonApiResource,\n included?: JsonApiResource[]\n): Record<string, unknown> {\n return cliFormatTask(task, { ...MCP_FORMAT_OPTIONS, included });\n}\n\n/**\n * Format person for agent consumption\n */\nexport function formatPerson(\n person: JsonApiResource,\n _included?: JsonApiResource[]\n): Record<string, unknown> {\n return cliFormatPerson(person, MCP_FORMAT_OPTIONS);\n}\n\n/**\n * Format service for agent consumption\n */\nexport function formatService(\n service: JsonApiResource,\n _included?: JsonApiResource[]\n): Record<string, unknown> {\n return cliFormatService(service, MCP_FORMAT_OPTIONS);\n}\n\n/**\n * Format list response with pagination\n *\n * @param data - Array of JSON:API resources\n * @param formatter - Formatter function (item, included?) => T\n * @param meta - Pagination metadata\n * @param included - Included resources for relationship resolution\n */\nexport function formatListResponse<T>(\n data: JsonApiResource[],\n formatter: (item: JsonApiResource, included?: JsonApiResource[]) => T,\n meta?: JsonApiMeta,\n included?: JsonApiResource[]\n): { data: T[]; meta?: FormattedPagination } {\n // Create a wrapper that converts (item, options?) signature to (item, included?) signature\n const wrappedFormatter = (item: JsonApiResource, _options?: FormatOptions) => {\n return formatter(item, included);\n };\n\n const result = cliFormatListResponse(data, wrappedFormatter, meta, {\n ...MCP_FORMAT_OPTIONS,\n included,\n });\n\n return result as { data: T[]; meta?: FormattedPagination };\n}\n","/**\n * Tool execution handlers for Productive MCP server\n * These are shared between stdio and HTTP transports\n */\n\nimport { ProductiveApi } from '@studiometa/productive-cli';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport type { ProductiveCredentials } from './auth.js';\nimport {\n formatTimeEntry,\n formatTask,\n formatProject,\n formatPerson,\n formatService,\n formatListResponse,\n} from './formatters.js';\n\nexport type ToolResult = CallToolResult;\n\n/**\n * Helper to create a successful JSON response\n */\nfunction jsonResult(data: unknown): ToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],\n };\n}\n\n/**\n * Helper to create an error response\n */\nfunction errorResult(message: string): ToolResult {\n return {\n content: [{ type: 'text', text: `Error: ${message}` }],\n isError: true,\n };\n}\n\n/**\n * Execute a tool with the given credentials and arguments\n *\n * @param name - Tool name\n * @param args - Tool arguments\n * @param credentials - Productive API credentials\n * @returns Tool execution result\n */\nexport async function executeToolWithCredentials(\n name: string,\n args: Record<string, unknown>,\n credentials: ProductiveCredentials\n): Promise<ToolResult> {\n // Initialize API client with provided credentials\n const api = new ProductiveApi({\n apiToken: credentials.apiToken,\n organizationId: credentials.organizationId,\n } as Record<string, string>);\n\n try {\n switch (name) {\n case 'productive_list_projects': {\n const result = await api.getProjects(args as Parameters<typeof api.getProjects>[0]);\n return jsonResult(formatListResponse(result.data, formatProject, result.meta));\n }\n\n case 'productive_get_project': {\n const result = await api.getProject((args as { id: string }).id);\n return jsonResult(formatProject(result.data));\n }\n\n case 'productive_list_time_entries': {\n const result = await api.getTimeEntries(args as Parameters<typeof api.getTimeEntries>[0]);\n return jsonResult(formatListResponse(result.data, formatTimeEntry, result.meta));\n }\n\n case 'productive_get_time_entry': {\n const result = await api.getTimeEntry((args as { id: string }).id);\n return jsonResult(formatTimeEntry(result.data));\n }\n\n case 'productive_create_time_entry': {\n const result = await api.createTimeEntry(\n args as Parameters<typeof api.createTimeEntry>[0]\n );\n return jsonResult({\n success: true,\n ...formatTimeEntry(result.data),\n });\n }\n\n case 'productive_update_time_entry': {\n const { id, ...data } = args as { id: string } & Record<string, unknown>;\n const result = await api.updateTimeEntry(id, data as Parameters<typeof api.updateTimeEntry>[1]);\n return jsonResult({\n success: true,\n ...formatTimeEntry(result.data),\n });\n }\n\n case 'productive_delete_time_entry': {\n await api.deleteTimeEntry((args as { id: string }).id);\n return jsonResult({ success: true, message: 'Time entry deleted' });\n }\n\n case 'productive_list_tasks': {\n const params = args as Parameters<typeof api.getTasks>[0] || {};\n // Always include project and company for context\n params.include = ['project', 'project.company'];\n const result = await api.getTasks(params);\n return jsonResult(formatListResponse(\n result.data,\n formatTask,\n result.meta,\n result.included\n ));\n }\n\n case 'productive_get_task': {\n const result = await api.getTask((args as { id: string }).id, {\n include: ['project', 'project.company'],\n });\n return jsonResult(formatTask(result.data, result.included));\n }\n\n case 'productive_list_services': {\n const result = await api.getServices(args as Parameters<typeof api.getServices>[0]);\n return jsonResult(formatListResponse(result.data, formatService, result.meta));\n }\n\n case 'productive_list_people': {\n const result = await api.getPeople(args as Parameters<typeof api.getPeople>[0]);\n return jsonResult(formatListResponse(result.data, formatPerson, result.meta));\n }\n\n case 'productive_get_person': {\n const result = await api.getPerson((args as { id: string }).id);\n return jsonResult(formatPerson(result.data));\n }\n\n case 'productive_get_current_user': {\n if (credentials.userId) {\n const result = await api.getPerson(credentials.userId);\n return jsonResult(formatPerson(result.data));\n }\n return jsonResult({\n message: 'User ID not configured. Set userId in credentials to use this tool.',\n organizationId: credentials.organizationId,\n });\n }\n\n default:\n return errorResult(`Unknown tool: ${name}`);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return errorResult(message);\n }\n}\n"],"names":["cliFormatTimeEntry","cliFormatProject","cliFormatTask","cliFormatPerson","cliFormatService","cliFormatListResponse"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"handlers.js","sources":["../src/formatters.ts","../src/handlers.ts"],"sourcesContent":["/**\n * Response formatters for agent-friendly output\n *\n * This module re-exports formatters from @studiometa/productive-cli\n * with MCP-specific defaults (no relationship IDs, no timestamps).\n *\n * Supports compact mode to reduce token usage by omitting verbose fields\n * like descriptions and notes from list responses.\n */\n\nimport {\n formatTimeEntry as cliFormatTimeEntry,\n formatProject as cliFormatProject,\n formatTask as cliFormatTask,\n formatPerson as cliFormatPerson,\n formatService as cliFormatService,\n formatListResponse as cliFormatListResponse,\n type JsonApiResource,\n type JsonApiMeta,\n type FormatOptions,\n type FormattedPagination,\n} from '@studiometa/productive-cli';\n\n// Re-export types\nexport type { JsonApiResource, JsonApiMeta, FormatOptions, FormattedPagination };\n\n/**\n * MCP-specific format options\n * - No relationship IDs (cleaner output for agents)\n * - No timestamps (reduce noise)\n * - HTML stripping enabled\n */\nconst MCP_FORMAT_OPTIONS: FormatOptions = {\n includeRelationshipIds: false,\n includeTimestamps: false,\n stripHtml: true,\n};\n\n/**\n * Extended format options for MCP with compact mode\n */\nexport interface McpFormatOptions {\n compact?: boolean;\n included?: JsonApiResource[];\n}\n\n/**\n * Remove verbose fields from an object for compact output\n */\nfunction compactify<T extends Record<string, unknown>>(\n obj: T,\n fieldsToRemove: string[]\n): T {\n const result = { ...obj };\n for (const field of fieldsToRemove) {\n delete result[field];\n }\n return result;\n}\n\n/**\n * Format time entry for agent consumption\n */\nexport function formatTimeEntry(\n entry: JsonApiResource,\n options?: McpFormatOptions\n): Record<string, unknown> {\n const result = cliFormatTimeEntry(entry, MCP_FORMAT_OPTIONS);\n if (options?.compact) {\n return compactify(result, ['note', 'billable_time', 'approved']);\n }\n return result;\n}\n\n/**\n * Format project for agent consumption\n */\nexport function formatProject(\n project: JsonApiResource,\n options?: McpFormatOptions\n): Record<string, unknown> {\n const result = cliFormatProject(project, MCP_FORMAT_OPTIONS);\n if (options?.compact) {\n return compactify(result, ['budget']);\n }\n return result;\n}\n\n/**\n * Format task for agent consumption\n * Tasks use included resources to resolve project/company names\n */\nexport function formatTask(\n task: JsonApiResource,\n options?: McpFormatOptions\n): Record<string, unknown> {\n const result = cliFormatTask(task, { ...MCP_FORMAT_OPTIONS, included: options?.included });\n if (options?.compact) {\n return compactify(result, [\n 'description',\n 'initial_estimate',\n 'worked_time',\n 'remaining_time',\n 'project', // Keep project_name but remove nested object\n 'company', // Keep company name inline if needed\n ]);\n }\n return result;\n}\n\n/**\n * Format person for agent consumption\n */\nexport function formatPerson(\n person: JsonApiResource,\n options?: McpFormatOptions\n): Record<string, unknown> {\n const result = cliFormatPerson(person, MCP_FORMAT_OPTIONS);\n if (options?.compact) {\n return compactify(result, ['title', 'first_name', 'last_name']); // Keep 'name' which combines them\n }\n return result;\n}\n\n/**\n * Format service for agent consumption\n */\nexport function formatService(\n service: JsonApiResource,\n options?: McpFormatOptions\n): Record<string, unknown> {\n const result = cliFormatService(service, MCP_FORMAT_OPTIONS);\n if (options?.compact) {\n return compactify(result, ['budgeted_time', 'worked_time']);\n }\n return result;\n}\n\n/**\n * Format list response with pagination\n *\n * @param data - Array of JSON:API resources\n * @param formatter - Formatter function (item, options?) => T\n * @param meta - Pagination metadata\n * @param options - MCP format options (compact, included)\n */\nexport function formatListResponse<T>(\n data: JsonApiResource[],\n formatter: (item: JsonApiResource, options?: McpFormatOptions) => T,\n meta?: JsonApiMeta,\n options?: McpFormatOptions\n): { data: T[]; meta?: FormattedPagination } {\n // Create a wrapper that passes MCP options to the formatter\n const wrappedFormatter = (item: JsonApiResource, _cliOptions?: FormatOptions) => {\n return formatter(item, options);\n };\n\n const result = cliFormatListResponse(data, wrappedFormatter, meta, {\n ...MCP_FORMAT_OPTIONS,\n included: options?.included,\n });\n\n return result as { data: T[]; meta?: FormattedPagination };\n}\n","/**\n * Tool execution handlers for Productive MCP server\n * These are shared between stdio and HTTP transports\n *\n * Single consolidated tool for minimal token overhead:\n * - productive: resource + action based API\n */\n\nimport { ProductiveApi } from '@studiometa/productive-cli';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport type { ProductiveCredentials } from './auth.js';\nimport {\n formatTimeEntry,\n formatTask,\n formatProject,\n formatPerson,\n formatService,\n formatListResponse,\n type McpFormatOptions,\n} from './formatters.js';\n\nexport type ToolResult = CallToolResult;\n\n/** Default page size for MCP (smaller than CLI to reduce token usage) */\nconst DEFAULT_PER_PAGE = 20;\n\n/**\n * Helper to create a successful JSON response\n */\nfunction jsonResult(data: unknown): ToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],\n };\n}\n\n/**\n * Helper to create an error response\n */\nfunction errorResult(message: string): ToolResult {\n return {\n content: [{ type: 'text', text: `Error: ${message}` }],\n isError: true,\n };\n}\n\n/**\n * Convert unknown filter to string filter for API\n */\nfunction toStringFilter(filter?: Record<string, unknown>): Record<string, string> | undefined {\n if (!filter) return undefined;\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(filter)) {\n if (value !== undefined && value !== null) {\n result[key] = String(value);\n }\n }\n return Object.keys(result).length > 0 ? result : undefined;\n}\n\n/**\n * Args interface for the consolidated tool\n */\ninterface ProductiveArgs {\n resource: 'projects' | 'time' | 'tasks' | 'services' | 'people';\n action: 'list' | 'get' | 'create' | 'update' | 'delete' | 'me';\n id?: string;\n filter?: Record<string, unknown>;\n page?: number;\n per_page?: number;\n compact?: boolean;\n // Time entry fields\n person_id?: string;\n service_id?: string;\n task_id?: string;\n time?: number;\n date?: string;\n note?: string;\n}\n\n/**\n * Execute a tool with the given credentials and arguments\n */\nexport async function executeToolWithCredentials(\n name: string,\n args: Record<string, unknown>,\n credentials: ProductiveCredentials\n): Promise<ToolResult> {\n // Initialize API client with provided credentials\n const api = new ProductiveApi({\n token: credentials.apiToken,\n 'org-id': credentials.organizationId,\n 'user-id': credentials.userId,\n } as Record<string, string>);\n\n // Handle the single consolidated tool\n if (name === 'productive') {\n const {\n resource,\n action,\n id,\n filter,\n page,\n per_page,\n compact = true,\n person_id,\n service_id,\n task_id,\n time,\n date,\n note,\n } = args as unknown as ProductiveArgs;\n\n const formatOptions: McpFormatOptions = { compact };\n const stringFilter = toStringFilter(filter);\n const perPage = per_page ?? DEFAULT_PER_PAGE;\n\n try {\n // ========================================================================\n // Projects\n // ========================================================================\n if (resource === 'projects') {\n if (action === 'get') {\n if (!id) return errorResult('id is required for get action');\n const result = await api.getProject(id);\n return jsonResult(formatProject(result.data, formatOptions));\n }\n if (action === 'list') {\n const result = await api.getProjects({ filter: stringFilter, page, perPage });\n return jsonResult(\n formatListResponse(result.data, formatProject, result.meta, formatOptions)\n );\n }\n return errorResult(`Invalid action \"${action}\" for projects. Use: list, get`);\n }\n\n // ========================================================================\n // Time Entries\n // ========================================================================\n if (resource === 'time') {\n if (action === 'get') {\n if (!id) return errorResult('id is required for get action');\n const result = await api.getTimeEntry(id);\n return jsonResult(formatTimeEntry(result.data, formatOptions));\n }\n\n if (action === 'create') {\n if (!person_id || !service_id || !time || !date) {\n return errorResult('person_id, service_id, time, and date are required for create');\n }\n const result = await api.createTimeEntry({\n person_id,\n service_id,\n time,\n date,\n note,\n task_id,\n });\n return jsonResult({ success: true, ...formatTimeEntry(result.data, formatOptions) });\n }\n\n if (action === 'update') {\n if (!id) return errorResult('id is required for update action');\n const updateData: Parameters<typeof api.updateTimeEntry>[1] = {};\n if (time !== undefined) updateData.time = time;\n if (date !== undefined) updateData.date = date;\n if (note !== undefined) updateData.note = note;\n const result = await api.updateTimeEntry(id, updateData);\n return jsonResult({ success: true, ...formatTimeEntry(result.data, formatOptions) });\n }\n\n if (action === 'delete') {\n if (!id) return errorResult('id is required for delete action');\n await api.deleteTimeEntry(id);\n return jsonResult({ success: true, message: 'Time entry deleted' });\n }\n\n if (action === 'list') {\n const result = await api.getTimeEntries({ filter: stringFilter, page, perPage });\n return jsonResult(\n formatListResponse(result.data, formatTimeEntry, result.meta, formatOptions)\n );\n }\n\n return errorResult(`Invalid action \"${action}\" for time. Use: list, get, create, update, delete`);\n }\n\n // ========================================================================\n // Tasks\n // ========================================================================\n if (resource === 'tasks') {\n const include = ['project', 'project.company'];\n\n if (action === 'get') {\n if (!id) return errorResult('id is required for get action');\n const result = await api.getTask(id, { include });\n return jsonResult(\n formatTask(result.data, { ...formatOptions, included: result.included })\n );\n }\n\n if (action === 'list') {\n const result = await api.getTasks({ filter: stringFilter, page, perPage, include });\n return jsonResult(\n formatListResponse(result.data, formatTask, result.meta, {\n ...formatOptions,\n included: result.included,\n })\n );\n }\n\n return errorResult(`Invalid action \"${action}\" for tasks. Use: list, get`);\n }\n\n // ========================================================================\n // Services\n // ========================================================================\n if (resource === 'services') {\n if (action === 'list') {\n const result = await api.getServices({ filter: stringFilter, page, perPage });\n return jsonResult(\n formatListResponse(result.data, formatService, result.meta, formatOptions)\n );\n }\n\n return errorResult(`Invalid action \"${action}\" for services. Use: list`);\n }\n\n // ========================================================================\n // People\n // ========================================================================\n if (resource === 'people') {\n if (action === 'get') {\n if (!id) return errorResult('id is required for get action');\n const result = await api.getPerson(id);\n return jsonResult(formatPerson(result.data, formatOptions));\n }\n\n if (action === 'me') {\n if (credentials.userId) {\n const result = await api.getPerson(credentials.userId);\n return jsonResult(formatPerson(result.data, formatOptions));\n }\n return jsonResult({\n message: 'User ID not configured. Set userId in credentials to use this action.',\n organizationId: credentials.organizationId,\n });\n }\n\n if (action === 'list') {\n const result = await api.getPeople({ filter: stringFilter, page, perPage });\n return jsonResult(\n formatListResponse(result.data, formatPerson, result.meta, formatOptions)\n );\n }\n\n return errorResult(`Invalid action \"${action}\" for people. Use: list, get, me`);\n }\n\n return errorResult(`Unknown resource: ${resource}`);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return errorResult(message);\n }\n }\n\n return errorResult(`Unknown tool: ${name}`);\n}\n"],"names":["cliFormatTimeEntry","cliFormatProject","cliFormatTask","cliFormatPerson","cliFormatService","cliFormatListResponse"],"mappings":"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"}
|
package/dist/http.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createApp, createRouter, defineEventHandler, getHeader, setResponseHead
|
|
|
2
2
|
import { TOOLS } from "./tools.js";
|
|
3
3
|
import { executeToolWithCredentials } from "./handlers.js";
|
|
4
4
|
import { parseAuthHeader } from "./auth.js";
|
|
5
|
-
import { V as VERSION } from "./version-
|
|
5
|
+
import { V as VERSION } from "./version-eQNCcjOb.js";
|
|
6
6
|
import { oauthMetadataHandler, registerHandler, authorizeGetHandler, authorizePostHandler, tokenHandler } from "./oauth.js";
|
|
7
7
|
function jsonRpcError(code, message, id = null) {
|
|
8
8
|
return {
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import { getAvailableTools, getAvailablePrompts, handlePrompt, handleToolCall } from "./stdio.js";
|
|
6
|
-
import { V as VERSION } from "./version-
|
|
6
|
+
import { V as VERSION } from "./version-eQNCcjOb.js";
|
|
7
7
|
function createStdioServer() {
|
|
8
8
|
const server = new Server(
|
|
9
9
|
{
|
package/dist/server.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createServer } from "node:http";
|
|
3
3
|
import { toNodeListener } from "h3";
|
|
4
4
|
import { createHttpApp } from "./http.js";
|
|
5
|
-
import { V as VERSION } from "./version-
|
|
5
|
+
import { V as VERSION } from "./version-eQNCcjOb.js";
|
|
6
6
|
const DEFAULT_PORT = 3e3;
|
|
7
7
|
const DEFAULT_HOST = "0.0.0.0";
|
|
8
8
|
function startHttpServer(port = DEFAULT_PORT, host = DEFAULT_HOST) {
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Single consolidated tool for Productive.io MCP server
|
|
4
|
+
*
|
|
5
|
+
* Optimized for minimal token overhead (~170 tokens vs ~1300 for individual tools)
|
|
5
6
|
*/
|
|
6
7
|
export declare const TOOLS: Tool[];
|
|
7
8
|
/**
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAE/D
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAE/D;;;;GAIG;AACH,eAAO,MAAM,KAAK,EAAE,IAAI,EAgCvB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,IAAI,EAsBlC,CAAC"}
|
package/dist/tools.js
CHANGED
|
@@ -1,219 +1,52 @@
|
|
|
1
1
|
const TOOLS = [
|
|
2
2
|
{
|
|
3
|
-
name: "
|
|
4
|
-
description: "
|
|
3
|
+
name: "productive",
|
|
4
|
+
description: "Productive.io API. Resources: projects, time, tasks, services, people. Actions: list, get, create/update/delete (time), me (people). Filters: project_id, person_id, service_id, after/before (dates).",
|
|
5
5
|
inputSchema: {
|
|
6
6
|
type: "object",
|
|
7
7
|
properties: {
|
|
8
|
-
|
|
9
|
-
type: "
|
|
10
|
-
|
|
11
|
-
properties: {
|
|
12
|
-
board_id: { type: "string", description: "Filter by board ID" },
|
|
13
|
-
company_id: { type: "string", description: "Filter by company ID" },
|
|
14
|
-
project_manager_id: { type: "string", description: "Filter by project manager ID" },
|
|
15
|
-
workflow_status: { type: "string", description: "Filter by workflow status" }
|
|
16
|
-
}
|
|
8
|
+
resource: {
|
|
9
|
+
type: "string",
|
|
10
|
+
enum: ["projects", "time", "tasks", "services", "people"]
|
|
17
11
|
},
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
name: "productive_get_project",
|
|
25
|
-
description: "Get details for a specific project by ID",
|
|
26
|
-
inputSchema: {
|
|
27
|
-
type: "object",
|
|
28
|
-
properties: {
|
|
29
|
-
id: { type: "string", description: "Project ID" }
|
|
30
|
-
},
|
|
31
|
-
required: ["id"]
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
name: "productive_list_time_entries",
|
|
36
|
-
description: "List time entries with optional filters",
|
|
37
|
-
inputSchema: {
|
|
38
|
-
type: "object",
|
|
39
|
-
properties: {
|
|
40
|
-
filter: {
|
|
41
|
-
type: "object",
|
|
42
|
-
description: "Filters to apply",
|
|
43
|
-
properties: {
|
|
44
|
-
person_id: { type: "string", description: "Filter by person ID" },
|
|
45
|
-
project_id: { type: "string", description: "Filter by project ID" },
|
|
46
|
-
service_id: { type: "string", description: "Filter by service ID" },
|
|
47
|
-
task_id: { type: "string", description: "Filter by task ID" },
|
|
48
|
-
after: { type: "string", description: "After date (YYYY-MM-DD)" },
|
|
49
|
-
before: { type: "string", description: "Before date (YYYY-MM-DD)" }
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
page: { type: "number", description: "Page number (default: 1)" },
|
|
53
|
-
per_page: { type: "number", description: "Items per page (default: 50, max: 200)" }
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
name: "productive_get_time_entry",
|
|
59
|
-
description: "Get details for a specific time entry by ID",
|
|
60
|
-
inputSchema: {
|
|
61
|
-
type: "object",
|
|
62
|
-
properties: {
|
|
63
|
-
id: { type: "string", description: "Time entry ID" }
|
|
64
|
-
},
|
|
65
|
-
required: ["id"]
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
name: "productive_create_time_entry",
|
|
70
|
-
description: "Create a new time entry",
|
|
71
|
-
inputSchema: {
|
|
72
|
-
type: "object",
|
|
73
|
-
properties: {
|
|
74
|
-
person_id: { type: "string", description: "Person ID" },
|
|
75
|
-
service_id: { type: "string", description: "Service ID" },
|
|
76
|
-
time: { type: "number", description: "Time in minutes" },
|
|
77
|
-
date: { type: "string", description: "Date (YYYY-MM-DD)" },
|
|
78
|
-
note: { type: "string", description: "Note/description" },
|
|
79
|
-
task_id: { type: "string", description: "Task ID (optional)" }
|
|
80
|
-
},
|
|
81
|
-
required: ["person_id", "service_id", "time", "date"]
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
name: "productive_update_time_entry",
|
|
86
|
-
description: "Update an existing time entry",
|
|
87
|
-
inputSchema: {
|
|
88
|
-
type: "object",
|
|
89
|
-
properties: {
|
|
90
|
-
id: { type: "string", description: "Time entry ID" },
|
|
91
|
-
time: { type: "number", description: "Time in minutes" },
|
|
92
|
-
date: { type: "string", description: "Date (YYYY-MM-DD)" },
|
|
93
|
-
note: { type: "string", description: "Note/description" },
|
|
94
|
-
service_id: { type: "string", description: "Service ID" },
|
|
95
|
-
task_id: { type: "string", description: "Task ID" }
|
|
96
|
-
},
|
|
97
|
-
required: ["id"]
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
name: "productive_delete_time_entry",
|
|
102
|
-
description: "Delete a time entry",
|
|
103
|
-
inputSchema: {
|
|
104
|
-
type: "object",
|
|
105
|
-
properties: {
|
|
106
|
-
id: { type: "string", description: "Time entry ID" }
|
|
107
|
-
},
|
|
108
|
-
required: ["id"]
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
name: "productive_list_tasks",
|
|
113
|
-
description: "List tasks with optional filters",
|
|
114
|
-
inputSchema: {
|
|
115
|
-
type: "object",
|
|
116
|
-
properties: {
|
|
117
|
-
filter: {
|
|
118
|
-
type: "object",
|
|
119
|
-
description: "Filters to apply",
|
|
120
|
-
properties: {
|
|
121
|
-
project_id: { type: "string", description: "Filter by project ID" },
|
|
122
|
-
assignee_id: { type: "string", description: "Filter by assignee ID" },
|
|
123
|
-
task_list_id: { type: "string", description: "Filter by task list ID" },
|
|
124
|
-
workflow_status_id: { type: "string", description: "Filter by workflow status ID" }
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
page: { type: "number", description: "Page number (default: 1)" },
|
|
128
|
-
per_page: { type: "number", description: "Items per page (default: 50, max: 200)" }
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
name: "productive_get_task",
|
|
134
|
-
description: "Get details for a specific task by ID",
|
|
135
|
-
inputSchema: {
|
|
136
|
-
type: "object",
|
|
137
|
-
properties: {
|
|
138
|
-
id: { type: "string", description: "Task ID" }
|
|
139
|
-
},
|
|
140
|
-
required: ["id"]
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
name: "productive_list_services",
|
|
145
|
-
description: "List services (budget line items) with optional filters",
|
|
146
|
-
inputSchema: {
|
|
147
|
-
type: "object",
|
|
148
|
-
properties: {
|
|
149
|
-
filter: {
|
|
150
|
-
type: "object",
|
|
151
|
-
description: "Filters to apply",
|
|
152
|
-
properties: {
|
|
153
|
-
project_id: { type: "string", description: "Filter by project ID" },
|
|
154
|
-
deal_id: { type: "string", description: "Filter by deal ID" }
|
|
155
|
-
}
|
|
12
|
+
action: {
|
|
13
|
+
type: "string",
|
|
14
|
+
enum: ["list", "get", "create", "update", "delete", "me"]
|
|
156
15
|
},
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
properties: {
|
|
172
|
-
archived: { type: "boolean", description: "Filter by archived status" }
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
page: { type: "number", description: "Page number (default: 1)" },
|
|
176
|
-
per_page: { type: "number", description: "Items per page (default: 50, max: 200)" }
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
name: "productive_get_person",
|
|
182
|
-
description: "Get details for a specific person by ID",
|
|
183
|
-
inputSchema: {
|
|
184
|
-
type: "object",
|
|
185
|
-
properties: {
|
|
186
|
-
id: { type: "string", description: "Person ID" }
|
|
187
|
-
},
|
|
188
|
-
required: ["id"]
|
|
189
|
-
}
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
name: "productive_get_current_user",
|
|
193
|
-
description: "Get the current authenticated user information",
|
|
194
|
-
inputSchema: {
|
|
195
|
-
type: "object",
|
|
196
|
-
properties: {}
|
|
16
|
+
id: { type: "string" },
|
|
17
|
+
filter: { type: "object" },
|
|
18
|
+
page: { type: "number" },
|
|
19
|
+
per_page: { type: "number" },
|
|
20
|
+
compact: { type: "boolean" },
|
|
21
|
+
// Time entry fields
|
|
22
|
+
person_id: { type: "string" },
|
|
23
|
+
service_id: { type: "string" },
|
|
24
|
+
task_id: { type: "string" },
|
|
25
|
+
time: { type: "number" },
|
|
26
|
+
date: { type: "string" },
|
|
27
|
+
note: { type: "string" }
|
|
28
|
+
},
|
|
29
|
+
required: ["resource", "action"]
|
|
197
30
|
}
|
|
198
31
|
}
|
|
199
32
|
];
|
|
200
33
|
const STDIO_ONLY_TOOLS = [
|
|
201
34
|
{
|
|
202
35
|
name: "productive_configure",
|
|
203
|
-
description: "Configure Productive.io credentials
|
|
36
|
+
description: "Configure Productive.io credentials",
|
|
204
37
|
inputSchema: {
|
|
205
38
|
type: "object",
|
|
206
39
|
properties: {
|
|
207
|
-
organizationId: { type: "string"
|
|
208
|
-
apiToken: { type: "string"
|
|
209
|
-
userId: { type: "string"
|
|
40
|
+
organizationId: { type: "string" },
|
|
41
|
+
apiToken: { type: "string" },
|
|
42
|
+
userId: { type: "string" }
|
|
210
43
|
},
|
|
211
44
|
required: ["organizationId", "apiToken"]
|
|
212
45
|
}
|
|
213
46
|
},
|
|
214
47
|
{
|
|
215
48
|
name: "productive_get_config",
|
|
216
|
-
description: "Get current
|
|
49
|
+
description: "Get current configuration",
|
|
217
50
|
inputSchema: {
|
|
218
51
|
type: "object",
|
|
219
52
|
properties: {}
|
package/dist/tools.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.js","sources":["../src/tools.ts"],"sourcesContent":["import type { Tool } from '@modelcontextprotocol/sdk/types.js';\n\n/**\n * Tool definitions for Productive.io MCP server\n * These are shared between stdio and HTTP transports\n */\nexport const TOOLS: Tool[] = [\n {\n name: 'productive_list_projects',\n description: 'List projects from Productive.io with optional filters',\n inputSchema: {\n type: 'object',\n properties: {\n filter: {\n type: 'object',\n description: 'Filters to apply',\n properties: {\n board_id: { type: 'string', description: 'Filter by board ID' },\n company_id: { type: 'string', description: 'Filter by company ID' },\n project_manager_id: { type: 'string', description: 'Filter by project manager ID' },\n workflow_status: { type: 'string', description: 'Filter by workflow status' },\n },\n },\n page: { type: 'number', description: 'Page number (default: 1)' },\n per_page: { type: 'number', description: 'Items per page (default: 50, max: 200)' },\n },\n },\n },\n {\n name: 'productive_get_project',\n description: 'Get details for a specific project by ID',\n inputSchema: {\n type: 'object',\n properties: {\n id: { type: 'string', description: 'Project ID' },\n },\n required: ['id'],\n },\n },\n {\n name: 'productive_list_time_entries',\n description: 'List time entries with optional filters',\n inputSchema: {\n type: 'object',\n properties: {\n filter: {\n type: 'object',\n description: 'Filters to apply',\n properties: {\n person_id: { type: 'string', description: 'Filter by person ID' },\n project_id: { type: 'string', description: 'Filter by project ID' },\n service_id: { type: 'string', description: 'Filter by service ID' },\n task_id: { type: 'string', description: 'Filter by task ID' },\n after: { type: 'string', description: 'After date (YYYY-MM-DD)' },\n before: { type: 'string', description: 'Before date (YYYY-MM-DD)' },\n },\n },\n page: { type: 'number', description: 'Page number (default: 1)' },\n per_page: { type: 'number', description: 'Items per page (default: 50, max: 200)' },\n },\n },\n },\n {\n name: 'productive_get_time_entry',\n description: 'Get details for a specific time entry by ID',\n inputSchema: {\n type: 'object',\n properties: {\n id: { type: 'string', description: 'Time entry ID' },\n },\n required: ['id'],\n },\n },\n {\n name: 'productive_create_time_entry',\n description: 'Create a new time entry',\n inputSchema: {\n type: 'object',\n properties: {\n person_id: { type: 'string', description: 'Person ID' },\n service_id: { type: 'string', description: 'Service ID' },\n time: { type: 'number', description: 'Time in minutes' },\n date: { type: 'string', description: 'Date (YYYY-MM-DD)' },\n note: { type: 'string', description: 'Note/description' },\n task_id: { type: 'string', description: 'Task ID (optional)' },\n },\n required: ['person_id', 'service_id', 'time', 'date'],\n },\n },\n {\n name: 'productive_update_time_entry',\n description: 'Update an existing time entry',\n inputSchema: {\n type: 'object',\n properties: {\n id: { type: 'string', description: 'Time entry ID' },\n time: { type: 'number', description: 'Time in minutes' },\n date: { type: 'string', description: 'Date (YYYY-MM-DD)' },\n note: { type: 'string', description: 'Note/description' },\n service_id: { type: 'string', description: 'Service ID' },\n task_id: { type: 'string', description: 'Task ID' },\n },\n required: ['id'],\n },\n },\n {\n name: 'productive_delete_time_entry',\n description: 'Delete a time entry',\n inputSchema: {\n type: 'object',\n properties: {\n id: { type: 'string', description: 'Time entry ID' },\n },\n required: ['id'],\n },\n },\n {\n name: 'productive_list_tasks',\n description: 'List tasks with optional filters',\n inputSchema: {\n type: 'object',\n properties: {\n filter: {\n type: 'object',\n description: 'Filters to apply',\n properties: {\n project_id: { type: 'string', description: 'Filter by project ID' },\n assignee_id: { type: 'string', description: 'Filter by assignee ID' },\n task_list_id: { type: 'string', description: 'Filter by task list ID' },\n workflow_status_id: { type: 'string', description: 'Filter by workflow status ID' },\n },\n },\n page: { type: 'number', description: 'Page number (default: 1)' },\n per_page: { type: 'number', description: 'Items per page (default: 50, max: 200)' },\n },\n },\n },\n {\n name: 'productive_get_task',\n description: 'Get details for a specific task by ID',\n inputSchema: {\n type: 'object',\n properties: {\n id: { type: 'string', description: 'Task ID' },\n },\n required: ['id'],\n },\n },\n {\n name: 'productive_list_services',\n description: 'List services (budget line items) with optional filters',\n inputSchema: {\n type: 'object',\n properties: {\n filter: {\n type: 'object',\n description: 'Filters to apply',\n properties: {\n project_id: { type: 'string', description: 'Filter by project ID' },\n deal_id: { type: 'string', description: 'Filter by deal ID' },\n },\n },\n page: { type: 'number', description: 'Page number (default: 1)' },\n per_page: { type: 'number', description: 'Items per page (default: 50, max: 200)' },\n },\n },\n },\n {\n name: 'productive_list_people',\n description: 'List people from the organization',\n inputSchema: {\n type: 'object',\n properties: {\n filter: {\n type: 'object',\n description: 'Filters to apply',\n properties: {\n archived: { type: 'boolean', description: 'Filter by archived status' },\n },\n },\n page: { type: 'number', description: 'Page number (default: 1)' },\n per_page: { type: 'number', description: 'Items per page (default: 50, max: 200)' },\n },\n },\n },\n {\n name: 'productive_get_person',\n description: 'Get details for a specific person by ID',\n inputSchema: {\n type: 'object',\n properties: {\n id: { type: 'string', description: 'Person ID' },\n },\n required: ['id'],\n },\n },\n {\n name: 'productive_get_current_user',\n description: 'Get the current authenticated user information',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n];\n\n/**\n * Additional tools only available in stdio mode (local execution)\n * These tools manage persistent configuration\n */\nexport const STDIO_ONLY_TOOLS: Tool[] = [\n {\n name: 'productive_configure',\n description: 'Configure Productive.io credentials (organization ID, API token, and optionally user ID)',\n inputSchema: {\n type: 'object',\n properties: {\n organizationId: { type: 'string', description: 'Your Productive.io organization ID' },\n apiToken: { type: 'string', description: 'Your Productive.io API token' },\n userId: { type: 'string', description: 'Your Productive.io user ID (optional, for time entries)' },\n },\n required: ['organizationId', 'apiToken'],\n },\n },\n {\n name: 'productive_get_config',\n description: 'Get current Productive.io configuration (without exposing the API token)',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n];\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tools.js","sources":["../src/tools.ts"],"sourcesContent":["import type { Tool } from '@modelcontextprotocol/sdk/types.js';\n\n/**\n * Single consolidated tool for Productive.io MCP server\n *\n * Optimized for minimal token overhead (~170 tokens vs ~1300 for individual tools)\n */\nexport const TOOLS: Tool[] = [\n {\n name: 'productive',\n description:\n 'Productive.io API. Resources: projects, time, tasks, services, people. Actions: list, get, create/update/delete (time), me (people). Filters: project_id, person_id, service_id, after/before (dates).',\n inputSchema: {\n type: 'object',\n properties: {\n resource: {\n type: 'string',\n enum: ['projects', 'time', 'tasks', 'services', 'people'],\n },\n action: {\n type: 'string',\n enum: ['list', 'get', 'create', 'update', 'delete', 'me'],\n },\n id: { type: 'string' },\n filter: { type: 'object' },\n page: { type: 'number' },\n per_page: { type: 'number' },\n compact: { type: 'boolean' },\n // Time entry fields\n person_id: { type: 'string' },\n service_id: { type: 'string' },\n task_id: { type: 'string' },\n time: { type: 'number' },\n date: { type: 'string' },\n note: { type: 'string' },\n },\n required: ['resource', 'action'],\n },\n },\n];\n\n/**\n * Additional tools only available in stdio mode (local execution)\n * These tools manage persistent configuration\n */\nexport const STDIO_ONLY_TOOLS: Tool[] = [\n {\n name: 'productive_configure',\n description: 'Configure Productive.io credentials',\n inputSchema: {\n type: 'object',\n properties: {\n organizationId: { type: 'string' },\n apiToken: { type: 'string' },\n userId: { type: 'string' },\n },\n required: ['organizationId', 'apiToken'],\n },\n },\n {\n name: 'productive_get_config',\n description: 'Get current configuration',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n];\n"],"names":[],"mappings":"AAOO,MAAM,QAAgB;AAAA,EAC3B;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,UAAU;AAAA,UACR,MAAM;AAAA,UACN,MAAM,CAAC,YAAY,QAAQ,SAAS,YAAY,QAAQ;AAAA,QAAA;AAAA,QAE1D,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,MAAM,CAAC,QAAQ,OAAO,UAAU,UAAU,UAAU,IAAI;AAAA,QAAA;AAAA,QAE1D,IAAI,EAAE,MAAM,SAAA;AAAA,QACZ,QAAQ,EAAE,MAAM,SAAA;AAAA,QAChB,MAAM,EAAE,MAAM,SAAA;AAAA,QACd,UAAU,EAAE,MAAM,SAAA;AAAA,QAClB,SAAS,EAAE,MAAM,UAAA;AAAA;AAAA,QAEjB,WAAW,EAAE,MAAM,SAAA;AAAA,QACnB,YAAY,EAAE,MAAM,SAAA;AAAA,QACpB,SAAS,EAAE,MAAM,SAAA;AAAA,QACjB,MAAM,EAAE,MAAM,SAAA;AAAA,QACd,MAAM,EAAE,MAAM,SAAA;AAAA,QACd,MAAM,EAAE,MAAM,SAAA;AAAA,MAAS;AAAA,MAEzB,UAAU,CAAC,YAAY,QAAQ;AAAA,IAAA;AAAA,EACjC;AAEJ;AAMO,MAAM,mBAA2B;AAAA,EACtC;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,gBAAgB,EAAE,MAAM,SAAA;AAAA,QACxB,UAAU,EAAE,MAAM,SAAA;AAAA,QAClB,QAAQ,EAAE,MAAM,SAAA;AAAA,MAAS;AAAA,MAE3B,UAAU,CAAC,kBAAkB,UAAU;AAAA,IAAA;AAAA,EACzC;AAAA,EAEF;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY,CAAA;AAAA,IAAC;AAAA,EACf;AAEJ;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version-
|
|
1
|
+
{"version":3,"file":"version-eQNCcjOb.js","sources":["../src/version.ts"],"sourcesContent":["/**\n * Package version - injected from package.json at build time\n */\ndeclare const __VERSION__: string;\nexport const VERSION = __VERSION__;\n"],"names":[],"mappings":"AAIO,MAAM,UAAU;"}
|
package/package.json
CHANGED