@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 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
- ## Available Tools
196
+ ## The `productive` Tool
196
197
 
197
- ### Projects
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
- ### Tasks
202
- - `productive_list_tasks` - List tasks with optional filters
203
- - `productive_get_task` - Get task details by ID
200
+ ```
201
+ productive(resource, action, ...)
202
+ ```
204
203
 
205
- ### Time Entries
206
- - `productive_list_time_entries` - List time entries with filters
207
- - `productive_get_time_entry` - Get time entry details by ID
208
- - `productive_create_time_entry` - Create a new time entry
209
- - `productive_update_time_entry` - Update an existing time entry
210
- - `productive_delete_time_entry` - Delete a time entry
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
- ### Services
213
- - `productive_list_services` - List services (budget line items)
263
+ ---
214
264
 
215
- ### People
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
- ### Configuration (Local mode only)
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
- ## Get Your Productive.io Credentials
274
+ ### List Projects
227
275
 
228
- 1. Log into [Productive.io](https://productive.io)
229
- 2. Go to **Settings → Integrations → API**
230
- 3. Generate an API token
231
- 4. Note your Organization ID (visible in URL or API settings)
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
- ## Usage Examples
283
+ ```
284
+ You: "Get details for project 12345"
285
+ Claude uses: productive(resource="projects", action="get", id="12345")
286
+ ```
237
287
 
238
- ### First Time Setup (Local Mode)
288
+ ### Create Time Entry
239
289
 
240
290
  ```
241
- You: "Configure my Productive.io credentials"
242
- Claude: "I'll help you set up. Please provide your Organization ID and API Token..."
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
- ### Common Queries
295
+ ### List My Time Entries
246
296
 
247
- - "Show me all active projects in Productive"
248
- - "Create a time entry for 2 hours today on project X"
249
- - "List all tasks assigned to me"
250
- - "What did I work on last week?"
251
- - "Show me the services/budgets for project 12345"
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-cli
260
- cd productive-cli
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":"productive_list_projects","arguments":{}},"id":2}'
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 (shared)
357
- │ ├── handlers.ts # Tool execution (shared)
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-cli)
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-cli/issues)
484
+ - [Issues](https://github.com/studiometa/productive-tools/issues)
@@ -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, _included?: JsonApiResource[]): Record<string, unknown>;
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, _included?: JsonApiResource[]): Record<string, unknown>;
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, included?: JsonApiResource[]): Record<string, unknown>;
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, _included?: JsonApiResource[]): Record<string, unknown>;
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, _included?: JsonApiResource[]): Record<string, unknown>;
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, included?) => T
44
+ * @param formatter - Formatter function (item, options?) => T
35
45
  * @param meta - Pagination metadata
36
- * @param included - Included resources for relationship resolution
46
+ * @param options - MCP format options (compact, included)
37
47
  */
38
- export declare function formatListResponse<T>(data: JsonApiResource[], formatter: (item: JsonApiResource, included?: JsonApiResource[]) => T, meta?: JsonApiMeta, included?: 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
  };
@@ -1 +1 @@
1
- {"version":3,"file":"formatters.d.ts","sourceRoot":"","sources":["../src/formatters.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;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,wBAAgB,eAAe,CAC7B,KAAK,EAAE,eAAe,EACtB,SAAS,CAAC,EAAE,eAAe,EAAE,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,eAAe,EACxB,SAAS,CAAC,EAAE,eAAe,EAAE,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,eAAe,EACrB,QAAQ,CAAC,EAAE,eAAe,EAAE,GAC3B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,eAAe,EACvB,SAAS,CAAC,EAAE,eAAe,EAAE,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,eAAe,EACxB,SAAS,CAAC,EAAE,eAAe,EAAE,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,IAAI,EAAE,eAAe,EAAE,EACvB,SAAS,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,eAAe,EAAE,KAAK,CAAC,EACrE,IAAI,CAAC,EAAE,WAAW,EAClB,QAAQ,CAAC,EAAE,eAAe,EAAE,GAC3B;IAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,mBAAmB,CAAA;CAAE,CAY3C"}
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"}
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAUvD,MAAM,MAAM,UAAU,GAAG,cAAc,CAAC;AAqBxC;;;;;;;GAOG;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,CA0GrB"}
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 { formatPerson as formatPerson$1, formatListResponse as formatListResponse$1, formatService as formatService$1, formatTask as formatTask$1, formatTimeEntry as formatTimeEntry$1, formatProject as formatProject$1, ProductiveApi } from "@studiometa/productive-cli";
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 formatTimeEntry(entry, _included) {
8
- return formatTimeEntry$1(entry, MCP_FORMAT_OPTIONS);
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 formatProject(project, _included) {
11
- return formatProject$1(project, MCP_FORMAT_OPTIONS);
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 formatTask(task, included) {
14
- return formatTask$1(task, { ...MCP_FORMAT_OPTIONS, included });
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, _included) {
17
- return formatPerson$1(person, MCP_FORMAT_OPTIONS);
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, _included) {
20
- return formatService$1(service, MCP_FORMAT_OPTIONS);
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, included) {
23
- const wrappedFormatter = (item, _options) => {
24
- return formatter(item, included);
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
- apiToken: credentials.apiToken,
46
- organizationId: credentials.organizationId
92
+ token: credentials.apiToken,
93
+ "org-id": credentials.organizationId,
94
+ "user-id": credentials.userId
47
95
  });
48
- try {
49
- switch (name) {
50
- case "productive_list_projects": {
51
- const result = await api.getProjects(args);
52
- return jsonResult(formatListResponse(result.data, formatProject, result.meta));
53
- }
54
- case "productive_get_project": {
55
- const result = await api.getProject(args.id);
56
- return jsonResult(formatProject(result.data));
57
- }
58
- case "productive_list_time_entries": {
59
- const result = await api.getTimeEntries(args);
60
- return jsonResult(formatListResponse(result.data, formatTimeEntry, result.meta));
61
- }
62
- case "productive_get_time_entry": {
63
- const result = await api.getTimeEntry(args.id);
64
- return jsonResult(formatTimeEntry(result.data));
65
- }
66
- case "productive_create_time_entry": {
67
- const result = await api.createTimeEntry(
68
- args
69
- );
70
- return jsonResult({
71
- success: true,
72
- ...formatTimeEntry(result.data)
73
- });
74
- }
75
- case "productive_update_time_entry": {
76
- const { id, ...data } = args;
77
- const result = await api.updateTimeEntry(id, data);
78
- return jsonResult({
79
- success: true,
80
- ...formatTimeEntry(result.data)
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
- case "productive_list_services": {
105
- const result = await api.getServices(args);
106
- return jsonResult(formatListResponse(result.data, formatService, result.meta));
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
- case "productive_list_people": {
109
- const result = await api.getPeople(args);
110
- return jsonResult(formatListResponse(result.data, formatPerson, result.meta));
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
- case "productive_get_person": {
113
- const result = await api.getPerson(args.id);
114
- return jsonResult(formatPerson(result.data));
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
- case "productive_get_current_user": {
117
- if (credentials.userId) {
118
- const result = await api.getPerson(credentials.userId);
119
- return jsonResult(formatPerson(result.data));
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 jsonResult({
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
- default:
127
- return errorResult(`Unknown tool: ${name}`);
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
@@ -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":";AA6BA,MAAM,qBAAoC;AAAA,EACxC,wBAAwB;AAAA,EACxB,mBAAmB;AAAA,EACnB,WAAW;AACb;AAKO,SAAS,gBACd,OACA,WACyB;AACzB,SAAOA,kBAAmB,OAAO,kBAAkB;AACrD;AAKO,SAAS,cACd,SACA,WACyB;AACzB,SAAOC,gBAAiB,SAAS,kBAAkB;AACrD;AAMO,SAAS,WACd,MACA,UACyB;AACzB,SAAOC,aAAc,MAAM,EAAE,GAAG,oBAAoB,UAAU;AAChE;AAKO,SAAS,aACd,QACA,WACyB;AACzB,SAAOC,eAAgB,QAAQ,kBAAkB;AACnD;AAKO,SAAS,cACd,SACA,WACyB;AACzB,SAAOC,gBAAiB,SAAS,kBAAkB;AACrD;AAUO,SAAS,mBACd,MACA,WACA,MACA,UAC2C;AAE3C,QAAM,mBAAmB,CAAC,MAAuB,aAA6B;AAC5E,WAAO,UAAU,MAAM,QAAQ;AAAA,EACjC;AAEA,QAAM,SAASC,qBAAsB,MAAM,kBAAkB,MAAM;AAAA,IACjE,GAAG;AAAA,IACH;AAAA,EAAA,CACD;AAED,SAAO;AACT;ACzFA,SAAS,WAAW,MAA2B;AAC7C,SAAO;AAAA,IACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC,EAAA,CAAG;AAAA,EAAA;AAEnE;AAKA,SAAS,YAAY,SAA6B;AAChD,SAAO;AAAA,IACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,UAAU,OAAO,IAAI;AAAA,IACrD,SAAS;AAAA,EAAA;AAEb;AAUA,eAAsB,2BACpB,MACA,MACA,aACqB;AAErB,QAAM,MAAM,IAAI,cAAc;AAAA,IAC5B,UAAU,YAAY;AAAA,IACtB,gBAAgB,YAAY;AAAA,EAAA,CACH;AAE3B,MAAI;AACF,YAAQ,MAAA;AAAA,MACN,KAAK,4BAA4B;AAC/B,cAAM,SAAS,MAAM,IAAI,YAAY,IAA6C;AAClF,eAAO,WAAW,mBAAmB,OAAO,MAAM,eAAe,OAAO,IAAI,CAAC;AAAA,MAC/E;AAAA,MAEA,KAAK,0BAA0B;AAC7B,cAAM,SAAS,MAAM,IAAI,WAAY,KAAwB,EAAE;AAC/D,eAAO,WAAW,cAAc,OAAO,IAAI,CAAC;AAAA,MAC9C;AAAA,MAEA,KAAK,gCAAgC;AACnC,cAAM,SAAS,MAAM,IAAI,eAAe,IAAgD;AACxF,eAAO,WAAW,mBAAmB,OAAO,MAAM,iBAAiB,OAAO,IAAI,CAAC;AAAA,MACjF;AAAA,MAEA,KAAK,6BAA6B;AAChC,cAAM,SAAS,MAAM,IAAI,aAAc,KAAwB,EAAE;AACjE,eAAO,WAAW,gBAAgB,OAAO,IAAI,CAAC;AAAA,MAChD;AAAA,MAEA,KAAK,gCAAgC;AACnC,cAAM,SAAS,MAAM,IAAI;AAAA,UACvB;AAAA,QAAA;AAEF,eAAO,WAAW;AAAA,UAChB,SAAS;AAAA,UACT,GAAG,gBAAgB,OAAO,IAAI;AAAA,QAAA,CAC/B;AAAA,MACH;AAAA,MAEA,KAAK,gCAAgC;AACnC,cAAM,EAAE,IAAI,GAAG,KAAA,IAAS;AACxB,cAAM,SAAS,MAAM,IAAI,gBAAgB,IAAI,IAAiD;AAC9F,eAAO,WAAW;AAAA,UAChB,SAAS;AAAA,UACT,GAAG,gBAAgB,OAAO,IAAI;AAAA,QAAA,CAC/B;AAAA,MACH;AAAA,MAEA,KAAK,gCAAgC;AACnC,cAAM,IAAI,gBAAiB,KAAwB,EAAE;AACrD,eAAO,WAAW,EAAE,SAAS,MAAM,SAAS,sBAAsB;AAAA,MACpE;AAAA,MAEA,KAAK,yBAAyB;AAC5B,cAAM,SAAS,QAA8C,CAAA;AAE7D,eAAO,UAAU,CAAC,WAAW,iBAAiB;AAC9C,cAAM,SAAS,MAAM,IAAI,SAAS,MAAM;AACxC,eAAO,WAAW;AAAA,UAChB,OAAO;AAAA,UACP;AAAA,UACA,OAAO;AAAA,UACP,OAAO;AAAA,QAAA,CACR;AAAA,MACH;AAAA,MAEA,KAAK,uBAAuB;AAC1B,cAAM,SAAS,MAAM,IAAI,QAAS,KAAwB,IAAI;AAAA,UAC5D,SAAS,CAAC,WAAW,iBAAiB;AAAA,QAAA,CACvC;AACD,eAAO,WAAW,WAAW,OAAO,MAAM,OAAO,QAAQ,CAAC;AAAA,MAC5D;AAAA,MAEA,KAAK,4BAA4B;AAC/B,cAAM,SAAS,MAAM,IAAI,YAAY,IAA6C;AAClF,eAAO,WAAW,mBAAmB,OAAO,MAAM,eAAe,OAAO,IAAI,CAAC;AAAA,MAC/E;AAAA,MAEA,KAAK,0BAA0B;AAC7B,cAAM,SAAS,MAAM,IAAI,UAAU,IAA2C;AAC9E,eAAO,WAAW,mBAAmB,OAAO,MAAM,cAAc,OAAO,IAAI,CAAC;AAAA,MAC9E;AAAA,MAEA,KAAK,yBAAyB;AAC5B,cAAM,SAAS,MAAM,IAAI,UAAW,KAAwB,EAAE;AAC9D,eAAO,WAAW,aAAa,OAAO,IAAI,CAAC;AAAA,MAC7C;AAAA,MAEA,KAAK,+BAA+B;AAClC,YAAI,YAAY,QAAQ;AACtB,gBAAM,SAAS,MAAM,IAAI,UAAU,YAAY,MAAM;AACrD,iBAAO,WAAW,aAAa,OAAO,IAAI,CAAC;AAAA,QAC7C;AACA,eAAO,WAAW;AAAA,UAChB,SAAS;AAAA,UACT,gBAAgB,YAAY;AAAA,QAAA,CAC7B;AAAA,MACH;AAAA,MAEA;AACE,eAAO,YAAY,iBAAiB,IAAI,EAAE;AAAA,IAAA;AAAA,EAEhD,SAAS,OAAO;AACd,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,WAAO,YAAY,OAAO;AAAA,EAC5B;AACF;"}
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-3wN4XBZL.js";
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-3wN4XBZL.js";
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-3wN4XBZL.js";
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
- * Tool definitions for Productive.io MCP server
4
- * These are shared between stdio and HTTP transports
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
  /**
@@ -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;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAE,IAAI,EAsMvB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,IAAI,EAsBlC,CAAC"}
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: "productive_list_projects",
4
- description: "List projects from Productive.io with optional filters",
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
- filter: {
9
- type: "object",
10
- description: "Filters to apply",
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
- page: { type: "number", description: "Page number (default: 1)" },
19
- per_page: { type: "number", description: "Items per page (default: 50, max: 200)" }
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
- page: { type: "number", description: "Page number (default: 1)" },
158
- per_page: { type: "number", description: "Items per page (default: 50, max: 200)" }
159
- }
160
- }
161
- },
162
- {
163
- name: "productive_list_people",
164
- description: "List people from the organization",
165
- inputSchema: {
166
- type: "object",
167
- properties: {
168
- filter: {
169
- type: "object",
170
- description: "Filters to apply",
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 (organization ID, API token, and optionally user ID)",
36
+ description: "Configure Productive.io credentials",
204
37
  inputSchema: {
205
38
  type: "object",
206
39
  properties: {
207
- organizationId: { type: "string", description: "Your Productive.io organization ID" },
208
- apiToken: { type: "string", description: "Your Productive.io API token" },
209
- userId: { type: "string", description: "Your Productive.io user ID (optional, for time entries)" }
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 Productive.io configuration (without exposing the API token)",
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;"}
@@ -0,0 +1,5 @@
1
+ const VERSION = "0.5.0";
2
+ export {
3
+ VERSION as V
4
+ };
5
+ //# sourceMappingURL=version-eQNCcjOb.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version-3wN4XBZL.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;"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studiometa/productive-mcp",
3
- "version": "0.4.5",
3
+ "version": "0.5.0",
4
4
  "description": "MCP server for Productive.io API - Model Context Protocol integration for Claude Desktop",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -1,5 +0,0 @@
1
- const VERSION = "0.4.5";
2
- export {
3
- VERSION as V
4
- };
5
- //# sourceMappingURL=version-3wN4XBZL.js.map