@studiometa/productive-mcp 0.8.4 → 0.9.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 (72) hide show
  1. package/README.md +84 -412
  2. package/dist/auth.js +37 -36
  3. package/dist/auth.js.map +1 -1
  4. package/dist/crypto.js +100 -61
  5. package/dist/crypto.js.map +1 -1
  6. package/dist/formatters.d.ts +18 -2
  7. package/dist/formatters.d.ts.map +1 -1
  8. package/dist/handlers/attachments.d.ts +6 -0
  9. package/dist/handlers/attachments.d.ts.map +1 -0
  10. package/dist/handlers/bookings.d.ts +1 -1
  11. package/dist/handlers/bookings.d.ts.map +1 -1
  12. package/dist/handlers/budgets.d.ts +9 -0
  13. package/dist/handlers/budgets.d.ts.map +1 -0
  14. package/dist/handlers/comments.d.ts +1 -1
  15. package/dist/handlers/comments.d.ts.map +1 -1
  16. package/dist/handlers/companies.d.ts +6 -2
  17. package/dist/handlers/companies.d.ts.map +1 -1
  18. package/dist/handlers/deals.d.ts +6 -2
  19. package/dist/handlers/deals.d.ts.map +1 -1
  20. package/dist/handlers/discussions.d.ts +13 -0
  21. package/dist/handlers/discussions.d.ts.map +1 -0
  22. package/dist/handlers/help.d.ts.map +1 -1
  23. package/dist/handlers/index.d.ts.map +1 -1
  24. package/dist/handlers/pages.d.ts +13 -0
  25. package/dist/handlers/pages.d.ts.map +1 -0
  26. package/dist/handlers/people.d.ts +6 -2
  27. package/dist/handlers/people.d.ts.map +1 -1
  28. package/dist/handlers/projects.d.ts +6 -2
  29. package/dist/handlers/projects.d.ts.map +1 -1
  30. package/dist/handlers/reports.d.ts +1 -4
  31. package/dist/handlers/reports.d.ts.map +1 -1
  32. package/dist/handlers/resolve.d.ts +24 -0
  33. package/dist/handlers/resolve.d.ts.map +1 -0
  34. package/dist/handlers/services.d.ts +1 -1
  35. package/dist/handlers/services.d.ts.map +1 -1
  36. package/dist/handlers/tasks.d.ts +6 -2
  37. package/dist/handlers/tasks.d.ts.map +1 -1
  38. package/dist/handlers/time.d.ts +10 -2
  39. package/dist/handlers/time.d.ts.map +1 -1
  40. package/dist/handlers/timers.d.ts +1 -1
  41. package/dist/handlers/timers.d.ts.map +1 -1
  42. package/dist/handlers/types.d.ts +42 -3
  43. package/dist/handlers/types.d.ts.map +1 -1
  44. package/dist/handlers-BYE2INiR.js +2681 -0
  45. package/dist/handlers-BYE2INiR.js.map +1 -0
  46. package/dist/handlers.js +2 -5
  47. package/dist/hints.d.ts +16 -0
  48. package/dist/hints.d.ts.map +1 -1
  49. package/dist/http.js +139 -160
  50. package/dist/http.js.map +1 -1
  51. package/dist/index.js +74 -54
  52. package/dist/index.js.map +1 -1
  53. package/dist/oauth.js +285 -255
  54. package/dist/oauth.js.map +1 -1
  55. package/dist/schema.d.ts +17 -0
  56. package/dist/schema.d.ts.map +1 -1
  57. package/dist/server.js +67 -50
  58. package/dist/server.js.map +1 -1
  59. package/dist/stdio.js +85 -105
  60. package/dist/stdio.js.map +1 -1
  61. package/dist/tools.js +155 -145
  62. package/dist/tools.js.map +1 -1
  63. package/dist/version-Dj8VXyV0.js +29 -0
  64. package/dist/version-Dj8VXyV0.js.map +1 -0
  65. package/package.json +10 -10
  66. package/skills/SKILL.md +209 -13
  67. package/Dockerfile +0 -36
  68. package/dist/handlers.js.map +0 -1
  69. package/dist/index-CZpVCEu4.js +0 -1681
  70. package/dist/index-CZpVCEu4.js.map +0 -1
  71. package/dist/version-DLWm7CFT.js +0 -21
  72. package/dist/version-DLWm7CFT.js.map +0 -1
package/README.md CHANGED
@@ -3,66 +3,30 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@studiometa/productive-mcp?style=flat&colorB=3e63dd&colorA=414853)](https://www.npmjs.com/package/@studiometa/productive-mcp)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat&colorB=3e63dd&colorA=414853)](https://opensource.org/licenses/MIT)
5
5
 
6
- MCP (Model Context Protocol) server for [Productive.io](https://productive.io) API integration. Enables Claude Desktop and other MCP clients to interact with Productive.io for project management, time tracking, and task management.
6
+ MCP (Model Context Protocol) server for [Productive.io](https://productive.io). Enables Claude Desktop and other MCP clients to interact with Productive.io for project management, time tracking, task management, and reporting.
7
7
 
8
8
  ## Features
9
9
 
10
- - Full Productive.io API access via MCP
11
- - 🔧 Support for projects, tasks, time entries, services, and people
12
- - 🔐 Two modes: local (stdio) and remote (HTTP)
13
- - 🔑 **OAuth 2.0 support** for Claude Desktop custom connectors
14
- - 🌐 Deploy once, share with your team via Claude Desktop custom connectors
15
- - 🐳 Docker-ready for easy deployment
16
- - ⚡ **Token-optimized** - Single tool design minimizes context usage (~180 tokens)
17
- - 📦 Built on [@studiometa/productive-cli](../productive-cli)
10
+ - Single unified `productive` tool minimal token overhead (~170 tokens)
11
+ - Smart ID resolution use emails and project numbers instead of numeric IDs
12
+ - Two modes: **local (stdio)** for personal use, **remote (HTTP)** for teams
13
+ - OAuth 2.0 support for Claude Desktop custom connectors
14
+ - Built-in `help` action for self-documentation
18
15
 
19
- ## Usage Modes
16
+ ## Mode 1: Local (stdio)
20
17
 
21
- This package supports two modes:
22
-
23
- | Mode | Command | Use Case |
24
- | ----------------- | ----------------------- | -------------------------------------------- |
25
- | **Local (stdio)** | `productive-mcp` | Personal use via Claude Desktop config |
26
- | **Remote (HTTP)** | `productive-mcp-server` | Team use via Claude Desktop custom connector |
27
-
28
- ---
29
-
30
- ## Mode 1: Local (stdio) - Personal Use
31
-
32
- Use this mode when you want to run the MCP server locally on your machine.
33
-
34
- ### Installation
18
+ Run the MCP server locally on your machine.
35
19
 
36
20
  ```bash
37
21
  npm install -g @studiometa/productive-mcp
38
22
  ```
39
23
 
40
- ### Claude Desktop Configuration
41
-
42
- Edit your Claude Desktop config:
24
+ Add to your Claude Desktop config:
43
25
 
44
26
  - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
45
27
  - **Windows**: `%APPDATA%/Claude/claude_desktop_config.json`
46
28
  - **Linux**: `~/.config/Claude/claude_desktop_config.json`
47
29
 
48
- ```json
49
- {
50
- "mcpServers": {
51
- "productive": {
52
- "command": "productive-mcp"
53
- }
54
- }
55
- }
56
- ```
57
-
58
- Restart Claude Desktop, then ask Claude:
59
-
60
- > "Configure my Productive.io credentials"
61
-
62
- Claude will guide you through the setup.
63
-
64
- ### Alternative: Environment Variables
65
-
66
30
  ```json
67
31
  {
68
32
  "mcpServers": {
@@ -78,127 +42,58 @@ Claude will guide you through the setup.
78
42
  }
79
43
  ```
80
44
 
81
- ---
82
-
83
- ## Mode 2: Remote (HTTP) - Team Use
84
-
85
- Deploy once, share with your entire team via Claude Desktop's **custom connector** feature.
45
+ Alternatively, omit the `env` block and ask Claude to configure credentials interactively.
86
46
 
87
- ### How It Works
47
+ ## Mode 2: Remote (HTTP)
88
48
 
89
- The server supports **OAuth 2.0** for seamless Claude Desktop integration:
49
+ Deploy once, share with your team via Claude Desktop's custom connector feature.
90
50
 
91
- 1. Deploy the HTTP server to a URL (e.g., `https://productive.mcp.example.com`)
92
- 2. Add the custom connector in Claude Desktop with OAuth enabled
93
- 3. When connecting, users are presented with a login form to enter their Productive credentials
94
- 4. Credentials are securely encrypted and exchanged via OAuth tokens
95
-
96
- No central credential storage required - each user's credentials are encrypted directly in their OAuth token.
97
-
98
- ### Deploy the Server
99
-
100
- #### Option A: Docker
51
+ ### Deploy
101
52
 
102
53
  ```bash
103
- # Build
104
- docker build -t productive-mcp-server -f packages/productive-mcp/Dockerfile .
105
-
106
- # Run
107
- docker run -d \
108
- --name productive-mcp-server \
109
- --restart unless-stopped \
110
- -p 3000:3000 \
111
- productive-mcp-server
112
- ```
54
+ # Docker
55
+ docker build -t productive-mcp-server -f packages/mcp/Dockerfile .
56
+ docker run -d -p 3000:3000 -e OAUTH_SECRET="$(openssl rand -base64 32)" productive-mcp-server
113
57
 
114
- #### Option B: Node.js
115
-
116
- ```bash
58
+ # Or Node.js
117
59
  npm install -g @studiometa/productive-mcp
118
-
119
- # Start server
120
- PORT=3000 productive-mcp-server
121
- ```
122
-
123
- #### Option C: Docker Compose
124
-
125
- ```yaml
126
- version: '3.8'
127
-
128
- services:
129
- productive-mcp:
130
- build:
131
- context: .
132
- dockerfile: packages/productive-mcp/Dockerfile
133
- restart: unless-stopped
134
- ports:
135
- - '3000:3000'
136
- environment:
137
- PORT: 3000
138
- HOST: 0.0.0.0
139
- OAUTH_SECRET: 'your-random-secret-here' # Required for production!
60
+ PORT=3000 OAUTH_SECRET="your-secret" productive-mcp-server
140
61
  ```
141
62
 
142
- ### Environment Variables
143
-
144
- | Variable | Required | Description |
145
- | -------------- | -------------------- | -------------------------------------- |
146
- | `PORT` | No | Server port (default: 3000) |
147
- | `HOST` | No | Bind address (default: 0.0.0.0) |
148
- | `OAUTH_SECRET` | **Yes (production)** | Secret key for encrypting OAuth tokens |
149
-
150
- > ⚠️ **Important**: Always set `OAUTH_SECRET` in production. Generate a random secret:
151
- >
152
- > ```bash
153
- > openssl rand -base64 32
154
- > ```
63
+ > **Important**: Always set `OAUTH_SECRET` in production for secure OAuth token encryption.
155
64
 
156
- ### Configure Claude Desktop Custom Connector
65
+ ### Configure Claude Desktop
157
66
 
158
- 1. Open Claude Desktop Settings
159
- 2. Go to **Custom Connectors** (beta)
160
- 3. Click **Add custom connector**
161
- 4. Configure:
162
- - **Name**: `Productive`
163
- - **Remote MCP server URL**: `https://productive.mcp.example.com/mcp`
164
- - **Authorization URL**: `https://productive.mcp.example.com/authorize`
165
- - **Token URL**: `https://productive.mcp.example.com/token`
166
- 5. Claude will redirect you to a login form to enter your Productive credentials
167
- 6. After login, you're connected and can start using Productive tools
67
+ 1. Open Claude Desktop **Settings → Custom Connectors**
68
+ 2. Add a custom connector:
69
+ - **Remote MCP server URL**: `https://your-server.example.com/mcp`
70
+ - **Authorization URL**: `https://your-server.example.com/authorize`
71
+ - **Token URL**: `https://your-server.example.com/token`
72
+ 3. Users log in with their own Productive credentials via the OAuth form
168
73
 
169
- ### Alternative: Manual Bearer Token
170
-
171
- If you prefer not to use OAuth, you can generate a Bearer token manually:
172
-
173
- ```bash
174
- # Format: base64(organizationId:apiToken:userId)
175
- echo -n "YOUR_ORG_ID:YOUR_API_TOKEN:YOUR_USER_ID" | base64
176
- ```
177
-
178
- Example:
179
-
180
- ```bash
181
- echo -n "12345:pk_abc123xyz:67890" | base64
182
- # Output: MTIzNDU6cGtfYWJjMTIzeHl6OjY3ODkw
183
- ```
74
+ The OAuth implementation is **stateless** — credentials are encrypted directly into the token, no database required.
184
75
 
185
76
  ### Server Endpoints
186
77
 
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 |
78
+ | Endpoint | Method | Description |
79
+ | ----------------------------------------- | -------- | -------------------------------- |
80
+ | `/mcp` | POST | MCP JSON-RPC endpoint |
81
+ | `/health` | GET | Health check |
82
+ | `/authorize` | GET/POST | OAuth authorization (login form) |
83
+ | `/token` | POST | OAuth token exchange |
84
+ | `/.well-known/oauth-authorization-server` | GET | OAuth metadata |
196
85
 
197
- ---
86
+ ### Environment Variables
87
+
88
+ | Variable | Required | Description |
89
+ | -------------- | ---------------- | ---------------------------------- |
90
+ | `PORT` | No | Server port (default: 3000) |
91
+ | `HOST` | No | Bind address (default: 0.0.0.0) |
92
+ | `OAUTH_SECRET` | Yes (production) | Secret for encrypting OAuth tokens |
198
93
 
199
94
  ## The `productive` Tool
200
95
 
201
- The MCP server exposes a single unified tool optimized for minimal token usage:
96
+ A single unified tool for all Productive.io operations:
202
97
 
203
98
  ```
204
99
  productive(resource, action, ...)
@@ -206,287 +101,64 @@ productive(resource, action, ...)
206
101
 
207
102
  ### Resources & Actions
208
103
 
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 |
216
-
217
- ### Parameters
218
-
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 |
233
-
234
- ### Filter Options
235
-
236
- #### Projects
237
-
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)
104
+ | Resource | Actions |
105
+ | ----------- | ------------------------------------------- |
106
+ | `projects` | `list`, `get` |
107
+ | `time` | `list`, `get`, `create`, `update`, `delete` |
108
+ | `tasks` | `list`, `get`, `create`, `update` |
109
+ | `services` | `list` |
110
+ | `people` | `list`, `get`, `me` |
111
+ | `companies` | `list`, `get`, `create`, `update` |
112
+ | `comments` | `list`, `get`, `create`, `update` |
113
+ | `timers` | `list`, `get`, `start`, `stop` |
114
+ | `deals` | `list`, `get`, `create`, `update` |
115
+ | `bookings` | `list`, `get`, `create`, `update` |
116
+ | `reports` | `get` (11 report types) |
117
+
118
+ Use `action="help"` with any resource for detailed documentation on available parameters and filters.
119
+
120
+ ### Common Parameters
121
+
122
+ | Parameter | Type | Description |
123
+ | ------------------- | -------- | ------------------------------------------------------------- |
124
+ | `resource` | string | **Required** resource name (see table above) |
125
+ | `action` | string | **Required** action to perform |
126
+ | `id` | string | Resource ID (for `get`, `update`, `delete`) |
127
+ | `filter` | object | Filter criteria for `list` actions |
128
+ | `page` / `per_page` | number | Pagination (default: 20 items per page, max: 200) |
129
+ | `compact` | boolean | Compact output (default: true for list, false for get) |
130
+ | `include` | string[] | Related resources to include (e.g. `["project", "assignee"]`) |
131
+ | `query` | string | Text search for `list` actions |
263
132
 
264
133
  ### Configuration Tools (Local mode only)
265
134
 
266
- | Tool | Description |
267
- | ----------------------- | -------------------------------------------------------- |
268
- | `productive_configure` | Configure credentials (organizationId, apiToken, userId) |
269
- | `productive_get_config` | View current configuration (token masked) |
270
-
271
- ---
135
+ | Tool | Description |
136
+ | ----------------------- | -------------------------------------------------- |
137
+ | `productive_configure` | Set credentials (organizationId, apiToken, userId) |
138
+ | `productive_get_config` | View current configuration (token masked) |
272
139
 
273
140
  ## Usage Examples
274
141
 
275
- ### First Time Setup (Local Mode)
276
-
277
142
  ```
278
- You: "Configure my Productive.io credentials"
279
- Claude: "I'll help you set up. Please provide your Organization ID and API Token..."
280
- ```
281
-
282
- ### List Projects
143
+ "Show me all projects"
144
+ productive(resource="projects", action="list")
283
145
 
284
- ```
285
- You: "Show me all projects"
286
- Claude uses: productive(resource="projects", action="list")
287
- ```
146
+ "Log 2 hours today on service 456"
147
+ productive(resource="time", action="create", service_id="456", time=120, date="2025-01-15")
288
148
 
289
- ### Get Project Details
149
+ "What did I work on last week?"
150
+ → productive(resource="time", action="list", filter={person_id: "me", after: "2025-01-08", before: "2025-01-14"})
290
151
 
152
+ "Show tasks for project 789"
153
+ → productive(resource="tasks", action="list", filter={project_id: "789"})
291
154
  ```
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)
333
-
334
- ---
335
-
336
- ## Development
337
-
338
- ```bash
339
- # Clone the repository
340
- git clone https://github.com/studiometa/productive-tools
341
- cd productive-tools
342
-
343
- # Install dependencies
344
- npm install
345
-
346
- # Build all packages
347
- npm run build
348
-
349
- # Or build only MCP package
350
- npm run build -w @studiometa/productive-mcp
351
-
352
- # Development mode (watch)
353
- npm run dev -w @studiometa/productive-mcp
354
-
355
- # Run tests
356
- npm test -w @studiometa/productive-mcp
357
-
358
- # Test local server
359
- node packages/productive-mcp/dist/index.js
360
-
361
- # Test HTTP server
362
- node packages/productive-mcp/dist/server.js
363
- ```
364
-
365
- ### Testing the HTTP Server
366
-
367
- ```bash
368
- # Start the server
369
- PORT=3000 node packages/productive-mcp/dist/server.js
370
-
371
- # Generate a test token
372
- TOKEN=$(echo -n "YOUR_ORG_ID:YOUR_API_TOKEN:YOUR_USER_ID" | base64)
373
-
374
- # Test health endpoint
375
- curl http://localhost:3000/health
376
-
377
- # Test MCP endpoint - list tools
378
- curl -X POST http://localhost:3000/mcp \
379
- -H "Authorization: Bearer $TOKEN" \
380
- -H "Content-Type: application/json" \
381
- -d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}'
382
-
383
- # List projects
384
- curl -X POST http://localhost:3000/mcp \
385
- -H "Authorization: Bearer $TOKEN" \
386
- -H "Content-Type: application/json" \
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}'
394
- ```
395
-
396
- ---
397
-
398
- ## Troubleshooting
399
-
400
- ### Local mode: Credentials not found
401
-
402
- ```bash
403
- # Check environment variables
404
- echo $PRODUCTIVE_ORG_ID
405
- echo $PRODUCTIVE_API_TOKEN
406
-
407
- # Or use the configure tool via Claude
408
- ```
409
-
410
- ### HTTP mode: 401 Unauthorized
411
-
412
- - Verify your token is correctly base64-encoded
413
- - Check that orgId:apiToken:userId are separated by colons
414
- - Ensure no newlines in the base64 output
415
-
416
- ### Docker: View logs
417
-
418
- ```bash
419
- docker logs productive-mcp-server -f
420
- ```
421
-
422
- ### Test server manually (local mode)
423
-
424
- ```bash
425
- echo '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}' | productive-mcp
426
- ```
427
-
428
- ---
429
155
 
430
156
  ## Requirements
431
157
 
432
- - Node.js 20+
158
+ - Node.js 24+
433
159
  - Productive.io account with API access
434
- - Docker (optional, for server deployment)
435
-
436
- ## Architecture
437
-
438
- ```
439
- productive-mcp/
440
- ├── src/
441
- │ ├── index.ts # Stdio transport (local mode)
442
- │ ├── server.ts # HTTP transport (remote mode)
443
- │ ├── http.ts # HTTP routes and MCP endpoint
444
- │ ├── oauth.ts # OAuth 2.0 endpoints
445
- │ ├── crypto.ts # Encryption for stateless OAuth tokens
446
- │ ├── tools.ts # Tool definitions (single unified tool)
447
- │ ├── handlers.ts # Tool execution logic
448
- │ ├── formatters.ts # Response formatting with compact mode
449
- │ └── auth.ts # Bearer token parsing
450
- ├── Dockerfile
451
- └── README.md
452
- ```
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
-
462
- ### OAuth Flow (Stateless)
463
-
464
- The OAuth implementation is **stateless** - no database or session storage required:
465
-
466
- 1. User visits `/authorize` → sees login form
467
- 2. User enters Productive credentials (org ID, API token, user ID)
468
- 3. Server encrypts credentials into the authorization code using `OAUTH_SECRET`
469
- 4. Redirects back to Claude with the encrypted code
470
- 5. Claude calls `/token` with the code
471
- 6. Server decrypts the code → returns access token (base64 credentials)
472
- 7. All MCP requests include this token in the `Authorization: Bearer` header
473
-
474
- This approach keeps the server stateless while securely passing credentials through the OAuth flow.
475
-
476
- ## Related Packages
477
-
478
- - [@studiometa/productive-cli](../productive-cli) - CLI tool for Productive.io
479
- - [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/sdk) - Official MCP SDK
480
- - [h3](https://github.com/unjs/h3) - HTTP framework for the server
160
+ - Docker (optional, for remote deployment)
481
161
 
482
162
  ## License
483
163
 
484
164
  MIT © [Studio Meta](https://www.studiometa.fr)
485
-
486
- ## Links
487
-
488
- - [GitHub Repository](https://github.com/studiometa/productive-tools)
489
- - [Productive.io API Docs](https://developer.productive.io)
490
- - [MCP Documentation](https://modelcontextprotocol.io)
491
- - [Claude Desktop Custom Connectors](https://docs.anthropic.com)
492
- - [Issues](https://github.com/studiometa/productive-tools/issues)
package/dist/auth.js CHANGED
@@ -1,40 +1,41 @@
1
+ /**
2
+ * Parse Bearer token containing Productive credentials
3
+ * Token format: base64(organizationId:apiToken) or base64(organizationId:apiToken:userId)
4
+ *
5
+ * @param authHeader - Authorization header value (e.g., "Bearer base64...")
6
+ * @returns Parsed credentials or null if invalid
7
+ */
1
8
  function parseAuthHeader(authHeader) {
2
- if (!authHeader) {
3
- return null;
4
- }
5
- const match = authHeader.match(/^Bearer\s+(.+)$/i);
6
- if (!match) {
7
- return null;
8
- }
9
- const token = match[1];
10
- try {
11
- const decoded = Buffer.from(token, "base64").toString("utf-8");
12
- const parts = decoded.split(":");
13
- if (parts.length < 2) {
14
- return null;
15
- }
16
- const [organizationId, apiToken, userId] = parts;
17
- if (!organizationId || !apiToken) {
18
- return null;
19
- }
20
- return {
21
- organizationId,
22
- apiToken,
23
- userId: userId || void 0
24
- };
25
- } catch {
26
- return null;
27
- }
9
+ if (!authHeader) return null;
10
+ const match = authHeader.match(/^Bearer\s+(.+)$/i);
11
+ if (!match) return null;
12
+ const token = match[1];
13
+ try {
14
+ const parts = Buffer.from(token, "base64").toString("utf-8").split(":");
15
+ if (parts.length < 2) return null;
16
+ const [organizationId, apiToken, userId] = parts;
17
+ if (!organizationId || !apiToken) return null;
18
+ return {
19
+ organizationId,
20
+ apiToken,
21
+ userId: userId || void 0
22
+ };
23
+ } catch {
24
+ return null;
25
+ }
28
26
  }
27
+ /**
28
+ * Create a Bearer token from Productive credentials
29
+ * Useful for documentation and testing
30
+ *
31
+ * @param credentials - Productive credentials
32
+ * @returns Base64 encoded token (without "Bearer " prefix)
33
+ */
29
34
  function createAuthToken(credentials) {
30
- const parts = [credentials.organizationId, credentials.apiToken];
31
- if (credentials.userId) {
32
- parts.push(credentials.userId);
33
- }
34
- return Buffer.from(parts.join(":")).toString("base64");
35
+ const parts = [credentials.organizationId, credentials.apiToken];
36
+ if (credentials.userId) parts.push(credentials.userId);
37
+ return Buffer.from(parts.join(":")).toString("base64");
35
38
  }
36
- export {
37
- createAuthToken,
38
- parseAuthHeader
39
- };
40
- //# sourceMappingURL=auth.js.map
39
+ export { createAuthToken, parseAuthHeader };
40
+
41
+ //# sourceMappingURL=auth.js.map
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(\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;"}
1
+ {"version":3,"file":"auth.js","names":[],"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"],"mappings":";;;;;;;AAiBA,SAAgB,gBACd,YAC8B;AAC9B,KAAI,CAAC,WACH,QAAO;CAGT,MAAM,QAAQ,WAAW,MAAM,mBAAmB;AAClD,KAAI,CAAC,MACH,QAAO;CAGT,MAAM,QAAQ,MAAM;AAEpB,KAAI;EAEF,MAAM,QADU,OAAO,KAAK,OAAO,SAAS,CAAC,SAAS,QAAQ,CACxC,MAAM,IAAI;AAEhC,MAAI,MAAM,SAAS,EACjB,QAAO;EAGT,MAAM,CAAC,gBAAgB,UAAU,UAAU;AAE3C,MAAI,CAAC,kBAAkB,CAAC,SACtB,QAAO;AAGT,SAAO;GACL;GACA;GACA,QAAQ,UAAU,KAAA;GACnB;SACK;AACN,SAAO;;;;;;;;;;AAWX,SAAgB,gBAAgB,aAA4C;CAC1E,MAAM,QAAQ,CAAC,YAAY,gBAAgB,YAAY,SAAS;AAChE,KAAI,YAAY,OACd,OAAM,KAAK,YAAY,OAAO;AAEhC,QAAO,OAAO,KAAK,MAAM,KAAK,IAAI,CAAC,CAAC,SAAS,SAAS"}