@studiometa/productive-mcp 0.8.5 → 0.9.1
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 +84 -412
- package/dist/auth.js +37 -36
- package/dist/auth.js.map +1 -1
- package/dist/crypto.js +100 -61
- package/dist/crypto.js.map +1 -1
- package/dist/formatters.d.ts +18 -2
- package/dist/formatters.d.ts.map +1 -1
- package/dist/handlers/attachments.d.ts +6 -0
- package/dist/handlers/attachments.d.ts.map +1 -0
- package/dist/handlers/bookings.d.ts +1 -1
- package/dist/handlers/bookings.d.ts.map +1 -1
- package/dist/handlers/budgets.d.ts +9 -0
- package/dist/handlers/budgets.d.ts.map +1 -0
- package/dist/handlers/comments.d.ts +1 -1
- package/dist/handlers/comments.d.ts.map +1 -1
- package/dist/handlers/companies.d.ts +6 -2
- package/dist/handlers/companies.d.ts.map +1 -1
- package/dist/handlers/deals.d.ts +6 -2
- package/dist/handlers/deals.d.ts.map +1 -1
- package/dist/handlers/discussions.d.ts +13 -0
- package/dist/handlers/discussions.d.ts.map +1 -0
- package/dist/handlers/help.d.ts.map +1 -1
- package/dist/handlers/index.d.ts.map +1 -1
- package/dist/handlers/pages.d.ts +13 -0
- package/dist/handlers/pages.d.ts.map +1 -0
- package/dist/handlers/people.d.ts +6 -2
- package/dist/handlers/people.d.ts.map +1 -1
- package/dist/handlers/projects.d.ts +6 -2
- package/dist/handlers/projects.d.ts.map +1 -1
- package/dist/handlers/reports.d.ts +1 -4
- package/dist/handlers/reports.d.ts.map +1 -1
- package/dist/handlers/resolve.d.ts +24 -0
- package/dist/handlers/resolve.d.ts.map +1 -0
- package/dist/handlers/services.d.ts +1 -1
- package/dist/handlers/services.d.ts.map +1 -1
- package/dist/handlers/tasks.d.ts +6 -2
- package/dist/handlers/tasks.d.ts.map +1 -1
- package/dist/handlers/time.d.ts +10 -2
- package/dist/handlers/time.d.ts.map +1 -1
- package/dist/handlers/timers.d.ts +1 -1
- package/dist/handlers/timers.d.ts.map +1 -1
- package/dist/handlers/types.d.ts +42 -3
- package/dist/handlers/types.d.ts.map +1 -1
- package/dist/handlers-BYE2INiR.js +2681 -0
- package/dist/handlers-BYE2INiR.js.map +1 -0
- package/dist/handlers.js +2 -5
- package/dist/hints.d.ts +16 -0
- package/dist/hints.d.ts.map +1 -1
- package/dist/http.js +139 -160
- package/dist/http.js.map +1 -1
- package/dist/index.js +74 -54
- package/dist/index.js.map +1 -1
- package/dist/oauth.js +285 -255
- package/dist/oauth.js.map +1 -1
- package/dist/schema.d.ts +17 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/server.js +67 -50
- package/dist/server.js.map +1 -1
- package/dist/stdio.js +85 -105
- package/dist/stdio.js.map +1 -1
- package/dist/tools.js +155 -145
- package/dist/tools.js.map +1 -1
- package/dist/version-D3sSBq_j.js +29 -0
- package/dist/version-D3sSBq_j.js.map +1 -0
- package/package.json +10 -10
- package/skills/SKILL.md +209 -13
- package/Dockerfile +0 -36
- package/dist/handlers.js.map +0 -1
- package/dist/index-CZpVCEu4.js +0 -1681
- package/dist/index-CZpVCEu4.js.map +0 -1
- package/dist/version-BPy06P7x.js +0 -21
- package/dist/version-BPy06P7x.js.map +0 -1
package/README.md
CHANGED
|
@@ -3,66 +3,30 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@studiometa/productive-mcp)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
MCP (Model Context Protocol) server for [Productive.io](https://productive.io)
|
|
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
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
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
|
-
##
|
|
16
|
+
## Mode 1: Local (stdio)
|
|
20
17
|
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
+
## Mode 2: Remote (HTTP)
|
|
88
48
|
|
|
89
|
-
|
|
49
|
+
Deploy once, share with your team via Claude Desktop's custom connector feature.
|
|
90
50
|
|
|
91
|
-
|
|
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
|
-
#
|
|
104
|
-
docker build -t productive-mcp-server -f packages/
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
65
|
+
### Configure Claude Desktop
|
|
157
66
|
|
|
158
|
-
1. Open Claude Desktop Settings
|
|
159
|
-
2.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
- **
|
|
163
|
-
|
|
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
|
-
|
|
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
|
|
188
|
-
| ----------------------------------------- |
|
|
189
|
-
| `/mcp` | POST
|
|
190
|
-
| `/health` | GET
|
|
191
|
-
|
|
|
192
|
-
| `/
|
|
193
|
-
|
|
|
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
|
-
|
|
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
|
|
210
|
-
|
|
|
211
|
-
| `projects`
|
|
212
|
-
| `time`
|
|
213
|
-
| `tasks`
|
|
214
|
-
| `services`
|
|
215
|
-
| `people`
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
|
220
|
-
|
|
|
221
|
-
| `
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
|
228
|
-
|
|
|
229
|
-
| `
|
|
230
|
-
| `
|
|
231
|
-
| `
|
|
232
|
-
| `
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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` |
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
### List Projects
|
|
143
|
+
"Show me all projects"
|
|
144
|
+
→ productive(resource="projects", action="list")
|
|
283
145
|
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
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
|
|
158
|
+
- Node.js 24+
|
|
433
159
|
- Productive.io account with API access
|
|
434
|
-
- Docker (optional, for
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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"],"
|
|
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"}
|