@studiometa/productive-mcp 0.4.6 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/Dockerfile +7 -0
  2. package/README.md +160 -61
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js.map +1 -1
  5. package/dist/crypto.js +1 -1
  6. package/dist/crypto.js.map +1 -1
  7. package/dist/formatters.d.ts +38 -8
  8. package/dist/formatters.d.ts.map +1 -1
  9. package/dist/handlers/bookings.d.ts +6 -0
  10. package/dist/handlers/bookings.d.ts.map +1 -0
  11. package/dist/handlers/comments.d.ts +6 -0
  12. package/dist/handlers/comments.d.ts.map +1 -0
  13. package/dist/handlers/companies.d.ts +6 -0
  14. package/dist/handlers/companies.d.ts.map +1 -0
  15. package/dist/handlers/deals.d.ts +6 -0
  16. package/dist/handlers/deals.d.ts.map +1 -0
  17. package/dist/handlers/index.d.ts +15 -0
  18. package/dist/handlers/index.d.ts.map +1 -0
  19. package/dist/handlers/people.d.ts +7 -0
  20. package/dist/handlers/people.d.ts.map +1 -0
  21. package/dist/handlers/projects.d.ts +6 -0
  22. package/dist/handlers/projects.d.ts.map +1 -0
  23. package/dist/handlers/services.d.ts +6 -0
  24. package/dist/handlers/services.d.ts.map +1 -0
  25. package/dist/handlers/tasks.d.ts +6 -0
  26. package/dist/handlers/tasks.d.ts.map +1 -0
  27. package/dist/handlers/time.d.ts +6 -0
  28. package/dist/handlers/time.d.ts.map +1 -0
  29. package/dist/handlers/timers.d.ts +6 -0
  30. package/dist/handlers/timers.d.ts.map +1 -0
  31. package/dist/handlers/types.d.ts +78 -0
  32. package/dist/handlers/types.d.ts.map +1 -0
  33. package/dist/handlers/utils.d.ts +17 -0
  34. package/dist/handlers/utils.d.ts.map +1 -0
  35. package/dist/handlers.d.ts +3 -12
  36. package/dist/handlers.d.ts.map +1 -1
  37. package/dist/handlers.js +2 -135
  38. package/dist/handlers.js.map +1 -1
  39. package/dist/http.js +3 -3
  40. package/dist/http.js.map +1 -1
  41. package/dist/index-CmTDkz-y.js +480 -0
  42. package/dist/index-CmTDkz-y.js.map +1 -0
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +1 -1
  45. package/dist/index.js.map +1 -1
  46. package/dist/oauth.d.ts +1 -1
  47. package/dist/oauth.d.ts.map +1 -1
  48. package/dist/oauth.js +111 -12
  49. package/dist/oauth.js.map +1 -1
  50. package/dist/server.d.ts.map +1 -1
  51. package/dist/server.js +2 -2
  52. package/dist/server.js.map +1 -1
  53. package/dist/stdio.d.ts.map +1 -1
  54. package/dist/stdio.js +1 -1
  55. package/dist/stdio.js.map +1 -1
  56. package/dist/tools.d.ts +3 -2
  57. package/dist/tools.d.ts.map +1 -1
  58. package/dist/tools.js +55 -193
  59. package/dist/tools.js.map +1 -1
  60. package/dist/version-BmTJXDFu.js +5 -0
  61. package/dist/{version-uWLfG4Z0.js.map → version-BmTJXDFu.js.map} +1 -1
  62. package/package.json +45 -45
  63. package/dist/version-uWLfG4Z0.js +0 -5
package/Dockerfile CHANGED
@@ -18,6 +18,10 @@ ENV NODE_ENV=production
18
18
  ENV PORT=3000
19
19
  ENV HOST=0.0.0.0
20
20
 
21
+ # Create non-root user for security
22
+ RUN addgroup -g 1001 -S nodejs && \
23
+ adduser -S nodejs -u 1001 -G nodejs
24
+
21
25
  # Expose port
22
26
  EXPOSE 3000
23
27
 
@@ -25,5 +29,8 @@ EXPOSE 3000
25
29
  HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
26
30
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
27
31
 
32
+ # Switch to non-root user
33
+ USER nodejs
34
+
28
35
  # Run the HTTP server
29
36
  CMD ["productive-mcp-server"]
package/README.md CHANGED
@@ -13,15 +13,16 @@ 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
19
20
 
20
21
  This package supports two modes:
21
22
 
22
- | Mode | Command | Use Case |
23
- |------|---------|----------|
24
- | **Local (stdio)** | `productive-mcp` | Personal use via Claude Desktop config |
23
+ | Mode | Command | Use Case |
24
+ | ----------------- | ----------------------- | -------------------------------------------- |
25
+ | **Local (stdio)** | `productive-mcp` | Personal use via Claude Desktop config |
25
26
  | **Remote (HTTP)** | `productive-mcp-server` | Team use via Claude Desktop custom connector |
26
27
 
27
28
  ---
@@ -39,6 +40,7 @@ npm install -g @studiometa/productive-mcp
39
40
  ### Claude Desktop Configuration
40
41
 
41
42
  Edit your Claude Desktop config:
43
+
42
44
  - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
43
45
  - **Windows**: `%APPDATA%/Claude/claude_desktop_config.json`
44
46
  - **Linux**: `~/.config/Claude/claude_desktop_config.json`
@@ -130,22 +132,23 @@ services:
130
132
  dockerfile: packages/productive-mcp/Dockerfile
131
133
  restart: unless-stopped
132
134
  ports:
133
- - "3000:3000"
135
+ - '3000:3000'
134
136
  environment:
135
137
  PORT: 3000
136
138
  HOST: 0.0.0.0
137
- OAUTH_SECRET: "your-random-secret-here" # Required for production!
139
+ OAUTH_SECRET: 'your-random-secret-here' # Required for production!
138
140
  ```
139
141
 
140
142
  ### Environment Variables
141
143
 
142
- | Variable | Required | Description |
143
- |----------|----------|-------------|
144
- | `PORT` | No | Server port (default: 3000) |
145
- | `HOST` | No | Bind address (default: 0.0.0.0) |
144
+ | Variable | Required | Description |
145
+ | -------------- | -------------------- | -------------------------------------- |
146
+ | `PORT` | No | Server port (default: 3000) |
147
+ | `HOST` | No | Bind address (default: 0.0.0.0) |
146
148
  | `OAUTH_SECRET` | **Yes (production)** | Secret key for encrypting OAuth tokens |
147
149
 
148
150
  > ⚠️ **Important**: Always set `OAUTH_SECRET` in production. Generate a random secret:
151
+ >
149
152
  > ```bash
150
153
  > openssl rand -base64 32
151
154
  > ```
@@ -173,6 +176,7 @@ echo -n "YOUR_ORG_ID:YOUR_API_TOKEN:YOUR_USER_ID" | base64
173
176
  ```
174
177
 
175
178
  Example:
179
+
176
180
  ```bash
177
181
  echo -n "12345:pk_abc123xyz:67890" | base64
178
182
  # Output: MTIzNDU6cGtfYWJjMTIzeHl6OjY3ODkw
@@ -180,56 +184,89 @@ echo -n "12345:pk_abc123xyz:67890" | base64
180
184
 
181
185
  ### Server Endpoints
182
186
 
183
- | Endpoint | Method | Description |
184
- |----------|--------|-------------|
185
- | `/mcp` | POST | MCP JSON-RPC endpoint |
186
- | `/health` | GET | Health check |
187
- | `/` | GET | Server info |
188
- | `/authorize` | GET | OAuth authorization (login form) |
189
- | `/authorize` | POST | OAuth authorization (process login) |
190
- | `/token` | POST | OAuth token exchange |
191
- | `/.well-known/oauth-authorization-server` | GET | OAuth metadata |
187
+ | Endpoint | Method | Description |
188
+ | ----------------------------------------- | ------ | ----------------------------------- |
189
+ | `/mcp` | POST | MCP JSON-RPC endpoint |
190
+ | `/health` | GET | Health check |
191
+ | `/` | GET | Server info |
192
+ | `/authorize` | GET | OAuth authorization (login form) |
193
+ | `/authorize` | POST | OAuth authorization (process login) |
194
+ | `/token` | POST | OAuth token exchange |
195
+ | `/.well-known/oauth-authorization-server` | GET | OAuth metadata |
192
196
 
193
197
  ---
194
198
 
195
- ## Available Tools
199
+ ## The `productive` Tool
196
200
 
197
- ### Projects
198
- - `productive_list_projects` - List projects with optional filters
199
- - `productive_get_project` - Get project details by ID
201
+ The MCP server exposes a single unified tool optimized for minimal token usage:
200
202
 
201
- ### Tasks
202
- - `productive_list_tasks` - List tasks with optional filters
203
- - `productive_get_task` - Get task details by ID
203
+ ```
204
+ productive(resource, action, ...)
205
+ ```
204
206
 
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
207
+ ### Resources & Actions
211
208
 
212
- ### Services
213
- - `productive_list_services` - List services (budget line items)
209
+ | Resource | Actions | Description |
210
+ | ---------- | ------------------------------------------- | ------------------ |
211
+ | `projects` | `list`, `get` | Project management |
212
+ | `time` | `list`, `get`, `create`, `update`, `delete` | Time tracking |
213
+ | `tasks` | `list`, `get` | Task management |
214
+ | `services` | `list` | Budget line items |
215
+ | `people` | `list`, `get`, `me` | Team members |
214
216
 
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
217
+ ### Parameters
219
218
 
220
- ### Configuration (Local mode only)
221
- - `productive_configure` - Configure credentials
222
- - `productive_get_config` - View current configuration
219
+ | Parameter | Type | Description |
220
+ | ------------ | ------- | ----------------------------------------------------------------------- |
221
+ | `resource` | string | **Required**. One of: `projects`, `time`, `tasks`, `services`, `people` |
222
+ | `action` | string | **Required**. Action to perform (see table above) |
223
+ | `id` | string | Resource ID (required for `get`, `update`, `delete`) |
224
+ | `filter` | object | Filter criteria for `list` actions |
225
+ | `page` | number | Page number for pagination |
226
+ | `per_page` | number | Items per page (default: 20, max: 200) |
227
+ | `compact` | boolean | Compact output mode (default: true) |
228
+ | `person_id` | string | Person ID (for time entry creation) |
229
+ | `service_id` | string | Service ID (for time entry creation) |
230
+ | `time` | number | Time in minutes (for time entries) |
231
+ | `date` | string | Date in YYYY-MM-DD format |
232
+ | `note` | string | Note/description |
223
233
 
224
- ---
234
+ ### Filter Options
225
235
 
226
- ## Get Your Productive.io Credentials
236
+ #### Projects
227
237
 
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)
238
+ - `company_id` - Filter by company
239
+ - `project_manager_id` - Filter by project manager
240
+
241
+ #### Time Entries
242
+
243
+ - `person_id` - Filter by person
244
+ - `project_id` - Filter by project
245
+ - `service_id` - Filter by service
246
+ - `after` - After date (YYYY-MM-DD)
247
+ - `before` - Before date (YYYY-MM-DD)
248
+
249
+ #### Tasks
250
+
251
+ - `project_id` - Filter by project
252
+ - `assignee_id` - Filter by assignee
253
+ - `task_list_id` - Filter by task list
254
+
255
+ #### Services
256
+
257
+ - `project_id` - Filter by project
258
+ - `deal_id` - Filter by deal
259
+
260
+ #### People
261
+
262
+ - `archived` - Include archived (boolean)
263
+
264
+ ### Configuration Tools (Local mode only)
265
+
266
+ | Tool | Description |
267
+ | ----------------------- | -------------------------------------------------------- |
268
+ | `productive_configure` | Configure credentials (organizationId, apiToken, userId) |
269
+ | `productive_get_config` | View current configuration (token masked) |
233
270
 
234
271
  ---
235
272
 
@@ -242,13 +279,57 @@ You: "Configure my Productive.io credentials"
242
279
  Claude: "I'll help you set up. Please provide your Organization ID and API Token..."
243
280
  ```
244
281
 
245
- ### Common Queries
282
+ ### List Projects
283
+
284
+ ```
285
+ You: "Show me all projects"
286
+ Claude uses: productive(resource="projects", action="list")
287
+ ```
246
288
 
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"
289
+ ### Get Project Details
290
+
291
+ ```
292
+ You: "Get details for project 12345"
293
+ Claude uses: productive(resource="projects", action="get", id="12345")
294
+ ```
295
+
296
+ ### Create Time Entry
297
+
298
+ ```
299
+ You: "Log 2 hours today on service 456"
300
+ Claude uses: productive(resource="time", action="create", person_id="...", service_id="456", time=120, date="2024-01-15")
301
+ ```
302
+
303
+ ### List My Time Entries
304
+
305
+ ```
306
+ You: "What did I work on last week?"
307
+ Claude uses: productive(resource="time", action="list", filter={person_id: "...", after: "2024-01-08", before: "2024-01-14"})
308
+ ```
309
+
310
+ ### Get Current User
311
+
312
+ ```
313
+ You: "Who am I logged in as?"
314
+ Claude uses: productive(resource="people", action="me")
315
+ ```
316
+
317
+ ### List Tasks for a Project
318
+
319
+ ```
320
+ You: "Show tasks for project 789"
321
+ Claude uses: productive(resource="tasks", action="list", filter={project_id: "789"})
322
+ ```
323
+
324
+ ---
325
+
326
+ ## Get Your Productive.io Credentials
327
+
328
+ 1. Log into [Productive.io](https://productive.io)
329
+ 2. Go to **Settings → Integrations → API**
330
+ 3. Generate an API token
331
+ 4. Note your Organization ID (visible in URL or API settings)
332
+ 5. Note your User ID (click your profile, visible in URL)
252
333
 
253
334
  ---
254
335
 
@@ -256,8 +337,8 @@ Claude: "I'll help you set up. Please provide your Organization ID and API Token
256
337
 
257
338
  ```bash
258
339
  # Clone the repository
259
- git clone https://github.com/studiometa/productive-cli
260
- cd productive-cli
340
+ git clone https://github.com/studiometa/productive-tools
341
+ cd productive-tools
261
342
 
262
343
  # Install dependencies
263
344
  npm install
@@ -271,6 +352,9 @@ npm run build -w @studiometa/productive-mcp
271
352
  # Development mode (watch)
272
353
  npm run dev -w @studiometa/productive-mcp
273
354
 
355
+ # Run tests
356
+ npm test -w @studiometa/productive-mcp
357
+
274
358
  # Test local server
275
359
  node packages/productive-mcp/dist/index.js
276
360
 
@@ -290,7 +374,7 @@ TOKEN=$(echo -n "YOUR_ORG_ID:YOUR_API_TOKEN:YOUR_USER_ID" | base64)
290
374
  # Test health endpoint
291
375
  curl http://localhost:3000/health
292
376
 
293
- # Test MCP endpoint
377
+ # Test MCP endpoint - list tools
294
378
  curl -X POST http://localhost:3000/mcp \
295
379
  -H "Authorization: Bearer $TOKEN" \
296
380
  -H "Content-Type: application/json" \
@@ -300,7 +384,13 @@ curl -X POST http://localhost:3000/mcp \
300
384
  curl -X POST http://localhost:3000/mcp \
301
385
  -H "Authorization: Bearer $TOKEN" \
302
386
  -H "Content-Type: application/json" \
303
- -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"productive_list_projects","arguments":{}},"id":2}'
387
+ -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"productive","arguments":{"resource":"projects","action":"list"}},"id":2}'
388
+
389
+ # Get a specific project
390
+ curl -X POST http://localhost:3000/mcp \
391
+ -H "Authorization: Bearer $TOKEN" \
392
+ -H "Content-Type: application/json" \
393
+ -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"productive","arguments":{"resource":"projects","action":"get","id":"12345"}},"id":3}'
304
394
  ```
305
395
 
306
396
  ---
@@ -353,13 +443,22 @@ productive-mcp/
353
443
  │ ├── http.ts # HTTP routes and MCP endpoint
354
444
  │ ├── oauth.ts # OAuth 2.0 endpoints
355
445
  │ ├── crypto.ts # Encryption for stateless OAuth tokens
356
- │ ├── tools.ts # Tool definitions (shared)
357
- │ ├── handlers.ts # Tool execution (shared)
446
+ │ ├── tools.ts # Tool definitions (single unified tool)
447
+ │ ├── handlers.ts # Tool execution logic
448
+ │ ├── formatters.ts # Response formatting with compact mode
358
449
  │ └── auth.ts # Bearer token parsing
359
450
  ├── Dockerfile
360
451
  └── README.md
361
452
  ```
362
453
 
454
+ ### Token Optimization
455
+
456
+ 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:
457
+
458
+ - Reduces context window usage
459
+ - Minimizes compaction frequency
460
+ - Improves response times
461
+
363
462
  ### OAuth Flow (Stateless)
364
463
 
365
464
  The OAuth implementation is **stateless** - no database or session storage required:
@@ -386,8 +485,8 @@ MIT © [Studio Meta](https://www.studiometa.fr)
386
485
 
387
486
  ## Links
388
487
 
389
- - [GitHub Repository](https://github.com/studiometa/productive-cli)
488
+ - [GitHub Repository](https://github.com/studiometa/productive-tools)
390
489
  - [Productive.io API Docs](https://developer.productive.io)
391
490
  - [MCP Documentation](https://modelcontextprotocol.io)
392
491
  - [Claude Desktop Custom Connectors](https://docs.anthropic.com)
393
- - [Issues](https://github.com/studiometa/productive-cli/issues)
492
+ - [Issues](https://github.com/studiometa/productive-tools/issues)
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,qBAAqB,GAAG,IAAI,CAkCnG;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,qBAAqB,GAAG,MAAM,CAM1E"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GACpC,qBAAqB,GAAG,IAAI,CAkC9B;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,qBAAqB,GAAG,MAAM,CAM1E"}
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sources":["../src/auth.ts"],"sourcesContent":["/**\n * Authentication utilities for Productive MCP server\n */\n\nexport interface ProductiveCredentials {\n organizationId: string;\n apiToken: string;\n userId?: string;\n}\n\n/**\n * Parse Bearer token containing Productive credentials\n * Token format: base64(organizationId:apiToken) or base64(organizationId:apiToken:userId)\n *\n * @param authHeader - Authorization header value (e.g., \"Bearer base64...\")\n * @returns Parsed credentials or null if invalid\n */\nexport function parseAuthHeader(authHeader: string | undefined | null): ProductiveCredentials | null {\n if (!authHeader) {\n return null;\n }\n\n const match = authHeader.match(/^Bearer\\s+(.+)$/i);\n if (!match) {\n return null;\n }\n\n const token = match[1];\n\n try {\n const decoded = Buffer.from(token, 'base64').toString('utf-8');\n const parts = decoded.split(':');\n\n if (parts.length < 2) {\n return null;\n }\n\n const [organizationId, apiToken, userId] = parts;\n\n if (!organizationId || !apiToken) {\n return null;\n }\n\n return {\n organizationId,\n apiToken,\n userId: userId || undefined,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Create a Bearer token from Productive credentials\n * Useful for documentation and testing\n *\n * @param credentials - Productive credentials\n * @returns Base64 encoded token (without \"Bearer \" prefix)\n */\nexport function createAuthToken(credentials: ProductiveCredentials): string {\n const parts = [credentials.organizationId, credentials.apiToken];\n if (credentials.userId) {\n parts.push(credentials.userId);\n }\n return Buffer.from(parts.join(':')).toString('base64');\n}\n"],"names":[],"mappings":"AAiBO,SAAS,gBAAgB,YAAqE;AACnG,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,WAAW,MAAM,kBAAkB;AACjD,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,MAAM,CAAC;AAErB,MAAI;AACF,UAAM,UAAU,OAAO,KAAK,OAAO,QAAQ,EAAE,SAAS,OAAO;AAC7D,UAAM,QAAQ,QAAQ,MAAM,GAAG;AAE/B,QAAI,MAAM,SAAS,GAAG;AACpB,aAAO;AAAA,IACT;AAEA,UAAM,CAAC,gBAAgB,UAAU,MAAM,IAAI;AAE3C,QAAI,CAAC,kBAAkB,CAAC,UAAU;AAChC,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,QAAQ,UAAU;AAAA,IAAA;AAAA,EAEtB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASO,SAAS,gBAAgB,aAA4C;AAC1E,QAAM,QAAQ,CAAC,YAAY,gBAAgB,YAAY,QAAQ;AAC/D,MAAI,YAAY,QAAQ;AACtB,UAAM,KAAK,YAAY,MAAM;AAAA,EAC/B;AACA,SAAO,OAAO,KAAK,MAAM,KAAK,GAAG,CAAC,EAAE,SAAS,QAAQ;AACvD;"}
1
+ {"version":3,"file":"auth.js","sources":["../src/auth.ts"],"sourcesContent":["/**\n * Authentication utilities for Productive MCP server\n */\n\nexport interface ProductiveCredentials {\n organizationId: string;\n apiToken: string;\n userId?: string;\n}\n\n/**\n * Parse Bearer token containing Productive credentials\n * Token format: base64(organizationId:apiToken) or base64(organizationId:apiToken:userId)\n *\n * @param authHeader - Authorization header value (e.g., \"Bearer base64...\")\n * @returns Parsed credentials or null if invalid\n */\nexport function parseAuthHeader(\n authHeader: string | undefined | null,\n): ProductiveCredentials | null {\n if (!authHeader) {\n return null;\n }\n\n const match = authHeader.match(/^Bearer\\s+(.+)$/i);\n if (!match) {\n return null;\n }\n\n const token = match[1];\n\n try {\n const decoded = Buffer.from(token, 'base64').toString('utf-8');\n const parts = decoded.split(':');\n\n if (parts.length < 2) {\n return null;\n }\n\n const [organizationId, apiToken, userId] = parts;\n\n if (!organizationId || !apiToken) {\n return null;\n }\n\n return {\n organizationId,\n apiToken,\n userId: userId || undefined,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Create a Bearer token from Productive credentials\n * Useful for documentation and testing\n *\n * @param credentials - Productive credentials\n * @returns Base64 encoded token (without \"Bearer \" prefix)\n */\nexport function createAuthToken(credentials: ProductiveCredentials): string {\n const parts = [credentials.organizationId, credentials.apiToken];\n if (credentials.userId) {\n parts.push(credentials.userId);\n }\n return Buffer.from(parts.join(':')).toString('base64');\n}\n"],"names":[],"mappings":"AAiBO,SAAS,gBACd,YAC8B;AAC9B,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,WAAW,MAAM,kBAAkB;AACjD,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,MAAM,CAAC;AAErB,MAAI;AACF,UAAM,UAAU,OAAO,KAAK,OAAO,QAAQ,EAAE,SAAS,OAAO;AAC7D,UAAM,QAAQ,QAAQ,MAAM,GAAG;AAE/B,QAAI,MAAM,SAAS,GAAG;AACpB,aAAO;AAAA,IACT;AAEA,UAAM,CAAC,gBAAgB,UAAU,MAAM,IAAI;AAE3C,QAAI,CAAC,kBAAkB,CAAC,UAAU;AAChC,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,QAAQ,UAAU;AAAA,IAAA;AAAA,EAEtB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASO,SAAS,gBAAgB,aAA4C;AAC1E,QAAM,QAAQ,CAAC,YAAY,gBAAgB,YAAY,QAAQ;AAC/D,MAAI,YAAY,QAAQ;AACtB,UAAM,KAAK,YAAY,MAAM;AAAA,EAC/B;AACA,SAAO,OAAO,KAAK,MAAM,KAAK,GAAG,CAAC,EAAE,SAAS,QAAQ;AACvD;"}
package/dist/crypto.js CHANGED
@@ -37,7 +37,7 @@ function decrypt(ciphertext, secret = getSecret()) {
37
37
  );
38
38
  const encrypted = combined.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
39
39
  const key = deriveKey(secret, salt);
40
- const decipher = createDecipheriv(ALGORITHM, key, iv);
40
+ const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
41
41
  decipher.setAuthTag(authTag);
42
42
  const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
43
43
  return decrypted.toString("utf8");
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.js","sources":["../src/crypto.ts"],"sourcesContent":["/**\n * Cryptographic utilities for stateless OAuth tokens\n *\n * Uses AES-256-GCM for authenticated encryption.\n * The authorization code contains encrypted credentials that can be\n * decrypted without server-side storage.\n */\n\nimport { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';\n\nconst ALGORITHM = 'aes-256-gcm';\nconst IV_LENGTH = 12; // GCM recommended IV length\nconst AUTH_TAG_LENGTH = 16;\nconst SALT_LENGTH = 16;\n\n/**\n * Derive a 256-bit key from a password using scrypt\n */\nfunction deriveKey(password: string, salt: Buffer): Buffer {\n return scryptSync(password, salt, 32);\n}\n\n/**\n * Get the encryption secret from environment or generate a default\n * In production, OAUTH_SECRET should always be set\n */\nexport function getSecret(): string {\n const secret = process.env.OAUTH_SECRET;\n if (!secret) {\n console.warn(\n 'WARNING: OAUTH_SECRET not set. Using default secret. Set OAUTH_SECRET in production!'\n );\n return 'productive-mcp-default-secret-change-me';\n }\n return secret;\n}\n\n/**\n * Encrypt data using AES-256-GCM\n *\n * Output format: base64(salt + iv + authTag + ciphertext)\n *\n * @param plaintext - Data to encrypt\n * @param secret - Encryption secret (defaults to OAUTH_SECRET env var)\n * @returns Base64-encoded encrypted data\n */\nexport function encrypt(plaintext: string, secret: string = getSecret()): string {\n const salt = randomBytes(SALT_LENGTH);\n const key = deriveKey(secret, salt);\n const iv = randomBytes(IV_LENGTH);\n\n const cipher = createCipheriv(ALGORITHM, key, iv);\n const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n const authTag = cipher.getAuthTag();\n\n // Combine: salt + iv + authTag + ciphertext\n const combined = Buffer.concat([salt, iv, authTag, encrypted]);\n\n return combined.toString('base64url');\n}\n\n/**\n * Decrypt data encrypted with encrypt()\n *\n * @param ciphertext - Base64-encoded encrypted data\n * @param secret - Encryption secret (defaults to OAUTH_SECRET env var)\n * @returns Decrypted plaintext\n * @throws Error if decryption fails (invalid data or wrong secret)\n */\nexport function decrypt(ciphertext: string, secret: string = getSecret()): string {\n try {\n const combined = Buffer.from(ciphertext, 'base64url');\n\n // Extract components\n const salt = combined.subarray(0, SALT_LENGTH);\n const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);\n const authTag = combined.subarray(\n SALT_LENGTH + IV_LENGTH,\n SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH\n );\n const encrypted = combined.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);\n\n const key = deriveKey(secret, salt);\n\n const decipher = createDecipheriv(ALGORITHM, key, iv);\n decipher.setAuthTag(authTag);\n\n const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);\n\n return decrypted.toString('utf8');\n } catch {\n throw new Error('Decryption failed: invalid token or secret');\n }\n}\n\n/**\n * Authorization code payload structure\n */\nexport interface AuthCodePayload {\n orgId: string;\n apiToken: string;\n userId?: string;\n codeChallenge?: string;\n codeChallengeMethod?: string;\n}\n\n/**\n * Create an encrypted authorization code containing credentials and PKCE challenge\n *\n * @param credentials - Object with orgId, apiToken, userId, and optional PKCE params\n * @param expiresInSeconds - Code expiration time (default: 5 minutes)\n * @returns Encrypted authorization code\n */\nexport function createAuthCode(\n credentials: AuthCodePayload,\n expiresInSeconds: number = 300\n): string {\n const payload = {\n ...credentials,\n exp: Date.now() + expiresInSeconds * 1000,\n };\n return encrypt(JSON.stringify(payload));\n}\n\n/**\n * Decode and validate an authorization code\n *\n * @param code - Encrypted authorization code\n * @returns Decoded payload with credentials and PKCE challenge\n * @throws Error if code is invalid or expired\n */\nexport function decodeAuthCode(code: string): AuthCodePayload {\n const payload = JSON.parse(decrypt(code));\n\n if (payload.exp && Date.now() > payload.exp) {\n throw new Error('Authorization code expired');\n }\n\n const { orgId, apiToken, userId, codeChallenge, codeChallengeMethod } = payload;\n\n if (!orgId || !apiToken) {\n throw new Error('Invalid authorization code: missing credentials');\n }\n\n return { orgId, apiToken, userId, codeChallenge, codeChallengeMethod };\n}\n"],"names":[],"mappings":";AAUA,MAAM,YAAY;AAClB,MAAM,YAAY;AAClB,MAAM,kBAAkB;AACxB,MAAM,cAAc;AAKpB,SAAS,UAAU,UAAkB,MAAsB;AACzD,SAAO,WAAW,UAAU,MAAM,EAAE;AACtC;AAMO,SAAS,YAAoB;AAClC,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,CAAC,QAAQ;AACX,YAAQ;AAAA,MACN;AAAA,IAAA;AAEF,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAWO,SAAS,QAAQ,WAAmB,SAAiB,aAAqB;AAC/E,QAAM,OAAO,YAAY,WAAW;AACpC,QAAM,MAAM,UAAU,QAAQ,IAAI;AAClC,QAAM,KAAK,YAAY,SAAS;AAEhC,QAAM,SAAS,eAAe,WAAW,KAAK,EAAE;AAChD,QAAM,YAAY,OAAO,OAAO,CAAC,OAAO,OAAO,WAAW,MAAM,GAAG,OAAO,MAAA,CAAO,CAAC;AAClF,QAAM,UAAU,OAAO,WAAA;AAGvB,QAAM,WAAW,OAAO,OAAO,CAAC,MAAM,IAAI,SAAS,SAAS,CAAC;AAE7D,SAAO,SAAS,SAAS,WAAW;AACtC;AAUO,SAAS,QAAQ,YAAoB,SAAiB,aAAqB;AAChF,MAAI;AACF,UAAM,WAAW,OAAO,KAAK,YAAY,WAAW;AAGpD,UAAM,OAAO,SAAS,SAAS,GAAG,WAAW;AAC7C,UAAM,KAAK,SAAS,SAAS,aAAa,cAAc,SAAS;AACjE,UAAM,UAAU,SAAS;AAAA,MACvB,cAAc;AAAA,MACd,cAAc,YAAY;AAAA,IAAA;AAE5B,UAAM,YAAY,SAAS,SAAS,cAAc,YAAY,eAAe;AAE7E,UAAM,MAAM,UAAU,QAAQ,IAAI;AAElC,UAAM,WAAW,iBAAiB,WAAW,KAAK,EAAE;AACpD,aAAS,WAAW,OAAO;AAE3B,UAAM,YAAY,OAAO,OAAO,CAAC,SAAS,OAAO,SAAS,GAAG,SAAS,MAAA,CAAO,CAAC;AAE9E,WAAO,UAAU,SAAS,MAAM;AAAA,EAClC,QAAQ;AACN,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AACF;AAoBO,SAAS,eACd,aACA,mBAA2B,KACnB;AACR,QAAM,UAAU;AAAA,IACd,GAAG;AAAA,IACH,KAAK,KAAK,IAAA,IAAQ,mBAAmB;AAAA,EAAA;AAEvC,SAAO,QAAQ,KAAK,UAAU,OAAO,CAAC;AACxC;AASO,SAAS,eAAe,MAA+B;AAC5D,QAAM,UAAU,KAAK,MAAM,QAAQ,IAAI,CAAC;AAExC,MAAI,QAAQ,OAAO,KAAK,IAAA,IAAQ,QAAQ,KAAK;AAC3C,UAAM,IAAI,MAAM,4BAA4B;AAAA,EAC9C;AAEA,QAAM,EAAE,OAAO,UAAU,QAAQ,eAAe,wBAAwB;AAExE,MAAI,CAAC,SAAS,CAAC,UAAU;AACvB,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AAEA,SAAO,EAAE,OAAO,UAAU,QAAQ,eAAe,oBAAA;AACnD;"}
1
+ {"version":3,"file":"crypto.js","sources":["../src/crypto.ts"],"sourcesContent":["/**\n * Cryptographic utilities for stateless OAuth tokens\n *\n * Uses AES-256-GCM for authenticated encryption.\n * The authorization code contains encrypted credentials that can be\n * decrypted without server-side storage.\n */\n\nimport { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';\n\nconst ALGORITHM = 'aes-256-gcm';\nconst IV_LENGTH = 12; // GCM recommended IV length\nconst AUTH_TAG_LENGTH = 16;\nconst SALT_LENGTH = 16;\n\n/**\n * Derive a 256-bit key from a password using scrypt\n */\nfunction deriveKey(password: string, salt: Buffer): Buffer {\n return scryptSync(password, salt, 32);\n}\n\n/**\n * Get the encryption secret from environment or generate a default\n * In production, OAUTH_SECRET should always be set\n */\nexport function getSecret(): string {\n const secret = process.env.OAUTH_SECRET;\n if (!secret) {\n console.warn(\n 'WARNING: OAUTH_SECRET not set. Using default secret. Set OAUTH_SECRET in production!',\n );\n return 'productive-mcp-default-secret-change-me';\n }\n return secret;\n}\n\n/**\n * Encrypt data using AES-256-GCM\n *\n * Output format: base64(salt + iv + authTag + ciphertext)\n *\n * @param plaintext - Data to encrypt\n * @param secret - Encryption secret (defaults to OAUTH_SECRET env var)\n * @returns Base64-encoded encrypted data\n */\nexport function encrypt(plaintext: string, secret: string = getSecret()): string {\n const salt = randomBytes(SALT_LENGTH);\n const key = deriveKey(secret, salt);\n const iv = randomBytes(IV_LENGTH);\n\n const cipher = createCipheriv(ALGORITHM, key, iv);\n const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n const authTag = cipher.getAuthTag();\n\n // Combine: salt + iv + authTag + ciphertext\n const combined = Buffer.concat([salt, iv, authTag, encrypted]);\n\n return combined.toString('base64url');\n}\n\n/**\n * Decrypt data encrypted with encrypt()\n *\n * @param ciphertext - Base64-encoded encrypted data\n * @param secret - Encryption secret (defaults to OAUTH_SECRET env var)\n * @returns Decrypted plaintext\n * @throws Error if decryption fails (invalid data or wrong secret)\n */\nexport function decrypt(ciphertext: string, secret: string = getSecret()): string {\n try {\n const combined = Buffer.from(ciphertext, 'base64url');\n\n // Extract components\n const salt = combined.subarray(0, SALT_LENGTH);\n const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);\n const authTag = combined.subarray(\n SALT_LENGTH + IV_LENGTH,\n SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH,\n );\n const encrypted = combined.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);\n\n const key = deriveKey(secret, salt);\n\n const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });\n decipher.setAuthTag(authTag);\n\n const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);\n\n return decrypted.toString('utf8');\n } catch {\n throw new Error('Decryption failed: invalid token or secret');\n }\n}\n\n/**\n * Authorization code payload structure\n */\nexport interface AuthCodePayload {\n orgId: string;\n apiToken: string;\n userId?: string;\n codeChallenge?: string;\n codeChallengeMethod?: string;\n}\n\n/**\n * Create an encrypted authorization code containing credentials and PKCE challenge\n *\n * @param credentials - Object with orgId, apiToken, userId, and optional PKCE params\n * @param expiresInSeconds - Code expiration time (default: 5 minutes)\n * @returns Encrypted authorization code\n */\nexport function createAuthCode(\n credentials: AuthCodePayload,\n expiresInSeconds: number = 300,\n): string {\n const payload = {\n ...credentials,\n exp: Date.now() + expiresInSeconds * 1000,\n };\n return encrypt(JSON.stringify(payload));\n}\n\n/**\n * Decode and validate an authorization code\n *\n * @param code - Encrypted authorization code\n * @returns Decoded payload with credentials and PKCE challenge\n * @throws Error if code is invalid or expired\n */\nexport function decodeAuthCode(code: string): AuthCodePayload {\n const payload = JSON.parse(decrypt(code));\n\n if (payload.exp && Date.now() > payload.exp) {\n throw new Error('Authorization code expired');\n }\n\n const { orgId, apiToken, userId, codeChallenge, codeChallengeMethod } = payload;\n\n if (!orgId || !apiToken) {\n throw new Error('Invalid authorization code: missing credentials');\n }\n\n return { orgId, apiToken, userId, codeChallenge, codeChallengeMethod };\n}\n"],"names":[],"mappings":";AAUA,MAAM,YAAY;AAClB,MAAM,YAAY;AAClB,MAAM,kBAAkB;AACxB,MAAM,cAAc;AAKpB,SAAS,UAAU,UAAkB,MAAsB;AACzD,SAAO,WAAW,UAAU,MAAM,EAAE;AACtC;AAMO,SAAS,YAAoB;AAClC,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,CAAC,QAAQ;AACX,YAAQ;AAAA,MACN;AAAA,IAAA;AAEF,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAWO,SAAS,QAAQ,WAAmB,SAAiB,aAAqB;AAC/E,QAAM,OAAO,YAAY,WAAW;AACpC,QAAM,MAAM,UAAU,QAAQ,IAAI;AAClC,QAAM,KAAK,YAAY,SAAS;AAEhC,QAAM,SAAS,eAAe,WAAW,KAAK,EAAE;AAChD,QAAM,YAAY,OAAO,OAAO,CAAC,OAAO,OAAO,WAAW,MAAM,GAAG,OAAO,MAAA,CAAO,CAAC;AAClF,QAAM,UAAU,OAAO,WAAA;AAGvB,QAAM,WAAW,OAAO,OAAO,CAAC,MAAM,IAAI,SAAS,SAAS,CAAC;AAE7D,SAAO,SAAS,SAAS,WAAW;AACtC;AAUO,SAAS,QAAQ,YAAoB,SAAiB,aAAqB;AAChF,MAAI;AACF,UAAM,WAAW,OAAO,KAAK,YAAY,WAAW;AAGpD,UAAM,OAAO,SAAS,SAAS,GAAG,WAAW;AAC7C,UAAM,KAAK,SAAS,SAAS,aAAa,cAAc,SAAS;AACjE,UAAM,UAAU,SAAS;AAAA,MACvB,cAAc;AAAA,MACd,cAAc,YAAY;AAAA,IAAA;AAE5B,UAAM,YAAY,SAAS,SAAS,cAAc,YAAY,eAAe;AAE7E,UAAM,MAAM,UAAU,QAAQ,IAAI;AAElC,UAAM,WAAW,iBAAiB,WAAW,KAAK,IAAI,EAAE,eAAe,iBAAiB;AACxF,aAAS,WAAW,OAAO;AAE3B,UAAM,YAAY,OAAO,OAAO,CAAC,SAAS,OAAO,SAAS,GAAG,SAAS,MAAA,CAAO,CAAC;AAE9E,WAAO,UAAU,SAAS,MAAM;AAAA,EAClC,QAAQ;AACN,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AACF;AAoBO,SAAS,eACd,aACA,mBAA2B,KACnB;AACR,QAAM,UAAU;AAAA,IACd,GAAG;AAAA,IACH,KAAK,KAAK,IAAA,IAAQ,mBAAmB;AAAA,EAAA;AAEvC,SAAO,QAAQ,KAAK,UAAU,OAAO,CAAC;AACxC;AASO,SAAS,eAAe,MAA+B;AAC5D,QAAM,UAAU,KAAK,MAAM,QAAQ,IAAI,CAAC;AAExC,MAAI,QAAQ,OAAO,KAAK,IAAA,IAAQ,QAAQ,KAAK;AAC3C,UAAM,IAAI,MAAM,4BAA4B;AAAA,EAC9C;AAEA,QAAM,EAAE,OAAO,UAAU,QAAQ,eAAe,wBAAwB;AAExE,MAAI,CAAC,SAAS,CAAC,UAAU;AACvB,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AAEA,SAAO,EAAE,OAAO,UAAU,QAAQ,eAAe,oBAAA;AACnD;"}
@@ -3,39 +3,69 @@
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>;
40
+ /**
41
+ * Format company for agent consumption
42
+ */
43
+ export declare function formatCompany(company: JsonApiResource, options?: McpFormatOptions): Record<string, unknown>;
44
+ /**
45
+ * Format comment for agent consumption
46
+ */
47
+ export declare function formatComment(comment: JsonApiResource, options?: McpFormatOptions): Record<string, unknown>;
48
+ /**
49
+ * Format timer for agent consumption
50
+ */
51
+ export declare function formatTimer(timer: JsonApiResource, _options?: McpFormatOptions): Record<string, unknown>;
52
+ /**
53
+ * Format deal for agent consumption
54
+ */
55
+ export declare function formatDeal(deal: JsonApiResource, options?: McpFormatOptions): Record<string, unknown>;
56
+ /**
57
+ * Format booking for agent consumption
58
+ */
59
+ export declare function formatBooking(booking: JsonApiResource, options?: McpFormatOptions): Record<string, unknown>;
30
60
  /**
31
61
  * Format list response with pagination
32
62
  *
33
63
  * @param data - Array of JSON:API resources
34
- * @param formatter - Formatter function (item, included?) => T
64
+ * @param formatter - Formatter function (item, options?) => T
35
65
  * @param meta - Pagination metadata
36
- * @param included - Included resources for relationship resolution
66
+ * @param options - MCP format options (compact, included)
37
67
  */
38
- export declare function formatListResponse<T>(data: JsonApiResource[], formatter: (item: JsonApiResource, included?: JsonApiResource[]) => T, meta?: JsonApiMeta, included?: JsonApiResource[]): {
68
+ export declare function formatListResponse<T>(data: JsonApiResource[], formatter: (item: JsonApiResource, options?: McpFormatOptions) => T, meta?: JsonApiMeta, options?: McpFormatOptions): {
39
69
  data: T[];
40
70
  meta?: FormattedPagination;
41
71
  };
@@ -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,EAYL,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;AAaD;;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;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,eAAe,EACxB,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,CAGzB;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,eAAe,EACtB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAGzB;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,eAAe,EACrB,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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Bookings resource handler
3
+ */
4
+ import type { HandlerContext, BookingArgs, ToolResult } from './types.js';
5
+ export declare function handleBookings(action: string, args: BookingArgs, ctx: HandlerContext): Promise<ToolResult>;
6
+ //# sourceMappingURL=bookings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bookings.d.ts","sourceRoot":"","sources":["../../src/handlers/bookings.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAK1E,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,UAAU,CAAC,CAmDrB"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Comments resource handler
3
+ */
4
+ import type { HandlerContext, CommentArgs, ToolResult } from './types.js';
5
+ export declare function handleComments(action: string, args: CommentArgs, ctx: HandlerContext): Promise<ToolResult>;
6
+ //# sourceMappingURL=comments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comments.d.ts","sourceRoot":"","sources":["../../src/handlers/comments.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAK1E,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,UAAU,CAAC,CA0CrB"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Companies resource handler
3
+ */
4
+ import type { HandlerContext, CompanyArgs, ToolResult } from './types.js';
5
+ export declare function handleCompanies(action: string, args: CompanyArgs, ctx: HandlerContext): Promise<ToolResult>;
6
+ //# sourceMappingURL=companies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"companies.d.ts","sourceRoot":"","sources":["../../src/handlers/companies.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAK1E,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,UAAU,CAAC,CA8BrB"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Deals resource handler
3
+ */
4
+ import type { HandlerContext, DealArgs, ToolResult } from './types.js';
5
+ export declare function handleDeals(action: string, args: DealArgs, ctx: HandlerContext): Promise<ToolResult>;
6
+ //# sourceMappingURL=deals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deals.d.ts","sourceRoot":"","sources":["../../src/handlers/deals.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAKvE,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,QAAQ,EACd,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,UAAU,CAAC,CA0CrB"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Tool execution handlers for Productive MCP server
3
+ * These are shared between stdio and HTTP transports
4
+ *
5
+ * Single consolidated tool for minimal token overhead:
6
+ * - productive: resource + action based API
7
+ */
8
+ import type { ProductiveCredentials } from '../auth.js';
9
+ import type { ToolResult } from './types.js';
10
+ export type { ToolResult } from './types.js';
11
+ /**
12
+ * Execute a tool with the given credentials and arguments
13
+ */
14
+ export declare function executeToolWithCredentials(name: string, args: Record<string, unknown>, credentials: ProductiveCredentials): Promise<ToolResult>;
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,OAAO,KAAK,EAAE,UAAU,EAAkB,MAAM,YAAY,CAAC;AAgB7D,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA2C7C;;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,CA4ErB"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * People resource handler
3
+ */
4
+ import type { ProductiveCredentials } from '../auth.js';
5
+ import type { HandlerContext, CommonArgs, ToolResult } from './types.js';
6
+ export declare function handlePeople(action: string, args: CommonArgs, ctx: HandlerContext, credentials: ProductiveCredentials): Promise<ToolResult>;
7
+ //# sourceMappingURL=people.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"people.d.ts","sourceRoot":"","sources":["../../src/handlers/people.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAKzE,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,cAAc,EACnB,WAAW,EAAE,qBAAqB,GACjC,OAAO,CAAC,UAAU,CAAC,CA2BrB"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Projects resource handler
3
+ */
4
+ import type { HandlerContext, CommonArgs, ToolResult } from './types.js';
5
+ export declare function handleProjects(action: string, args: CommonArgs, ctx: HandlerContext): Promise<ToolResult>;
6
+ //# sourceMappingURL=projects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/handlers/projects.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAKzE,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,UAAU,CAAC,CAgBrB"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Services resource handler
3
+ */
4
+ import type { HandlerContext, CommonArgs, ToolResult } from './types.js';
5
+ export declare function handleServices(action: string, _args: CommonArgs, ctx: HandlerContext): Promise<ToolResult>;
6
+ //# sourceMappingURL=services.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../../src/handlers/services.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAKzE,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,UAAU,CAAC,CASrB"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tasks resource handler
3
+ */
4
+ import type { HandlerContext, TaskArgs, ToolResult } from './types.js';
5
+ export declare function handleTasks(action: string, args: TaskArgs, ctx: HandlerContext): Promise<ToolResult>;
6
+ //# sourceMappingURL=tasks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../../src/handlers/tasks.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAKvE,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,QAAQ,EACd,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,UAAU,CAAC,CA8CrB"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Time entries resource handler
3
+ */
4
+ import type { HandlerContext, CommonArgs, ToolResult } from './types.js';
5
+ export declare function handleTime(action: string, args: CommonArgs, ctx: HandlerContext): Promise<ToolResult>;
6
+ //# sourceMappingURL=time.d.ts.map