@studiometa/productive-mcp 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Dockerfile ADDED
@@ -0,0 +1,29 @@
1
+ # Productive MCP Server - HTTP Transport
2
+ #
3
+ # Build from the repository root:
4
+ # docker build -t productive-mcp-server -f packages/productive-mcp/Dockerfile .
5
+ #
6
+ # Or from the package directory with published packages:
7
+ # docker build -t productive-mcp-server .
8
+
9
+ FROM node:20-alpine
10
+
11
+ WORKDIR /app
12
+
13
+ # Install the package globally from npm
14
+ RUN npm install -g @studiometa/productive-mcp
15
+
16
+ # Set environment variables
17
+ ENV NODE_ENV=production
18
+ ENV PORT=3000
19
+ ENV HOST=0.0.0.0
20
+
21
+ # Expose port
22
+ EXPOSE 3000
23
+
24
+ # Health check
25
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
26
+ CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
27
+
28
+ # Run the HTTP server
29
+ CMD ["productive-mcp-server"]
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Studio Meta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,352 @@
1
+ # @studiometa/productive-mcp
2
+
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
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat&colorB=3e63dd&colorA=414853)](https://opensource.org/licenses/MIT)
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.
7
+
8
+ ## Features
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
+ - 🌐 Deploy once, share with your team via Claude Desktop custom connectors
14
+ - 🐳 Docker-ready for easy deployment
15
+ - 📦 Built on [@studiometa/productive-cli](../productive-cli)
16
+
17
+ ## Usage Modes
18
+
19
+ This package supports two modes:
20
+
21
+ | Mode | Command | Use Case |
22
+ |------|---------|----------|
23
+ | **Local (stdio)** | `productive-mcp` | Personal use via Claude Desktop config |
24
+ | **Remote (HTTP)** | `productive-mcp-server` | Team use via Claude Desktop custom connector |
25
+
26
+ ---
27
+
28
+ ## Mode 1: Local (stdio) - Personal Use
29
+
30
+ Use this mode when you want to run the MCP server locally on your machine.
31
+
32
+ ### Installation
33
+
34
+ ```bash
35
+ npm install -g @studiometa/productive-mcp
36
+ ```
37
+
38
+ ### Claude Desktop Configuration
39
+
40
+ Edit your Claude Desktop config:
41
+ - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
42
+ - **Windows**: `%APPDATA%/Claude/claude_desktop_config.json`
43
+ - **Linux**: `~/.config/Claude/claude_desktop_config.json`
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "productive": {
49
+ "command": "productive-mcp"
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ Restart Claude Desktop, then ask Claude:
56
+
57
+ > "Configure my Productive.io credentials"
58
+
59
+ Claude will guide you through the setup.
60
+
61
+ ### Alternative: Environment Variables
62
+
63
+ ```json
64
+ {
65
+ "mcpServers": {
66
+ "productive": {
67
+ "command": "productive-mcp",
68
+ "env": {
69
+ "PRODUCTIVE_ORG_ID": "your-org-id",
70
+ "PRODUCTIVE_API_TOKEN": "your-auth-token",
71
+ "PRODUCTIVE_USER_ID": "your-user-id"
72
+ }
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Mode 2: Remote (HTTP) - Team Use
81
+
82
+ Deploy once, share with your entire team via Claude Desktop's **custom connector** feature.
83
+
84
+ ### How It Works
85
+
86
+ 1. Deploy the HTTP server to a URL (e.g., `https://productive.mcp.example.com`)
87
+ 2. Each team member generates their own Bearer token with their Productive credentials
88
+ 3. Team members add the custom connector in Claude Desktop with their personal token
89
+
90
+ ### Deploy the Server
91
+
92
+ #### Option A: Docker
93
+
94
+ ```bash
95
+ # Build
96
+ docker build -t productive-mcp-server -f packages/productive-mcp/Dockerfile .
97
+
98
+ # Run
99
+ docker run -d \
100
+ --name productive-mcp-server \
101
+ --restart unless-stopped \
102
+ -p 3000:3000 \
103
+ productive-mcp-server
104
+ ```
105
+
106
+ #### Option B: Node.js
107
+
108
+ ```bash
109
+ npm install -g @studiometa/productive-mcp
110
+
111
+ # Start server
112
+ PORT=3000 productive-mcp-server
113
+ ```
114
+
115
+ #### Option C: Docker Compose
116
+
117
+ ```yaml
118
+ version: '3.8'
119
+
120
+ services:
121
+ productive-mcp:
122
+ build:
123
+ context: .
124
+ dockerfile: packages/productive-mcp/Dockerfile
125
+ restart: unless-stopped
126
+ ports:
127
+ - "3000:3000"
128
+ environment:
129
+ PORT: 3000
130
+ HOST: 0.0.0.0
131
+ ```
132
+
133
+ ### Generate Your Token
134
+
135
+ Each team member generates their own token containing their Productive credentials:
136
+
137
+ ```bash
138
+ # Format: base64(organizationId:apiToken:userId)
139
+ echo -n "YOUR_ORG_ID:YOUR_API_TOKEN:YOUR_USER_ID" | base64
140
+ ```
141
+
142
+ Example:
143
+ ```bash
144
+ echo -n "12345:pk_abc123xyz:67890" | base64
145
+ # Output: MTIzNDU6cGtfYWJjMTIzeHl6OjY3ODkw
146
+ ```
147
+
148
+ ### Configure Claude Desktop Custom Connector
149
+
150
+ 1. Open Claude Desktop Settings
151
+ 2. Go to **Custom Connectors** (beta)
152
+ 3. Click **Add custom connector**
153
+ 4. Configure:
154
+ - **Name**: `Productive`
155
+ - **Remote MCP server URL**: `https://productive.mcp.example.com/mcp`
156
+ - Leave OAuth fields empty (we use Bearer token)
157
+ 5. When making requests, Claude will include your token in the `Authorization` header
158
+
159
+ > **Note**: As of now, Claude Desktop custom connectors may require OAuth. If Bearer token auth isn't supported directly, you can use a reverse proxy to inject the Authorization header, or wait for Claude Desktop to support custom headers.
160
+
161
+ ### Server Endpoints
162
+
163
+ | Endpoint | Method | Description |
164
+ |----------|--------|-------------|
165
+ | `/mcp` | POST | MCP JSON-RPC endpoint |
166
+ | `/health` | GET | Health check |
167
+ | `/` | GET | Server info |
168
+
169
+ ---
170
+
171
+ ## Available Tools
172
+
173
+ ### Projects
174
+ - `productive_list_projects` - List projects with optional filters
175
+ - `productive_get_project` - Get project details by ID
176
+
177
+ ### Tasks
178
+ - `productive_list_tasks` - List tasks with optional filters
179
+ - `productive_get_task` - Get task details by ID
180
+
181
+ ### Time Entries
182
+ - `productive_list_time_entries` - List time entries with filters
183
+ - `productive_get_time_entry` - Get time entry details by ID
184
+ - `productive_create_time_entry` - Create a new time entry
185
+ - `productive_update_time_entry` - Update an existing time entry
186
+ - `productive_delete_time_entry` - Delete a time entry
187
+
188
+ ### Services
189
+ - `productive_list_services` - List services (budget line items)
190
+
191
+ ### People
192
+ - `productive_list_people` - List people in the organization
193
+ - `productive_get_person` - Get person details by ID
194
+ - `productive_get_current_user` - Get current authenticated user
195
+
196
+ ### Configuration (Local mode only)
197
+ - `productive_configure` - Configure credentials
198
+ - `productive_get_config` - View current configuration
199
+
200
+ ---
201
+
202
+ ## Get Your Productive.io Credentials
203
+
204
+ 1. Log into [Productive.io](https://productive.io)
205
+ 2. Go to **Settings → Integrations → API**
206
+ 3. Generate an API token
207
+ 4. Note your Organization ID (visible in URL or API settings)
208
+ 5. Note your User ID (click your profile, visible in URL)
209
+
210
+ ---
211
+
212
+ ## Usage Examples
213
+
214
+ ### First Time Setup (Local Mode)
215
+
216
+ ```
217
+ You: "Configure my Productive.io credentials"
218
+ Claude: "I'll help you set up. Please provide your Organization ID and API Token..."
219
+ ```
220
+
221
+ ### Common Queries
222
+
223
+ - "Show me all active projects in Productive"
224
+ - "Create a time entry for 2 hours today on project X"
225
+ - "List all tasks assigned to me"
226
+ - "What did I work on last week?"
227
+ - "Show me the services/budgets for project 12345"
228
+
229
+ ---
230
+
231
+ ## Development
232
+
233
+ ```bash
234
+ # Clone the repository
235
+ git clone https://github.com/studiometa/productive-cli
236
+ cd productive-cli
237
+
238
+ # Install dependencies
239
+ npm install
240
+
241
+ # Build all packages
242
+ npm run build
243
+
244
+ # Or build only MCP package
245
+ npm run build -w @studiometa/productive-mcp
246
+
247
+ # Development mode (watch)
248
+ npm run dev -w @studiometa/productive-mcp
249
+
250
+ # Test local server
251
+ node packages/productive-mcp/dist/index.js
252
+
253
+ # Test HTTP server
254
+ node packages/productive-mcp/dist/server.js
255
+ ```
256
+
257
+ ### Testing the HTTP Server
258
+
259
+ ```bash
260
+ # Start the server
261
+ PORT=3000 node packages/productive-mcp/dist/server.js
262
+
263
+ # Generate a test token
264
+ TOKEN=$(echo -n "YOUR_ORG_ID:YOUR_API_TOKEN:YOUR_USER_ID" | base64)
265
+
266
+ # Test health endpoint
267
+ curl http://localhost:3000/health
268
+
269
+ # Test MCP endpoint
270
+ curl -X POST http://localhost:3000/mcp \
271
+ -H "Authorization: Bearer $TOKEN" \
272
+ -H "Content-Type: application/json" \
273
+ -d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}'
274
+
275
+ # List projects
276
+ curl -X POST http://localhost:3000/mcp \
277
+ -H "Authorization: Bearer $TOKEN" \
278
+ -H "Content-Type: application/json" \
279
+ -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"productive_list_projects","arguments":{}},"id":2}'
280
+ ```
281
+
282
+ ---
283
+
284
+ ## Troubleshooting
285
+
286
+ ### Local mode: Credentials not found
287
+
288
+ ```bash
289
+ # Check environment variables
290
+ echo $PRODUCTIVE_ORG_ID
291
+ echo $PRODUCTIVE_API_TOKEN
292
+
293
+ # Or use the configure tool via Claude
294
+ ```
295
+
296
+ ### HTTP mode: 401 Unauthorized
297
+
298
+ - Verify your token is correctly base64-encoded
299
+ - Check that orgId:apiToken:userId are separated by colons
300
+ - Ensure no newlines in the base64 output
301
+
302
+ ### Docker: View logs
303
+
304
+ ```bash
305
+ docker logs productive-mcp-server -f
306
+ ```
307
+
308
+ ### Test server manually (local mode)
309
+
310
+ ```bash
311
+ echo '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}' | productive-mcp
312
+ ```
313
+
314
+ ---
315
+
316
+ ## Requirements
317
+
318
+ - Node.js 20+
319
+ - Productive.io account with API access
320
+ - Docker (optional, for server deployment)
321
+
322
+ ## Architecture
323
+
324
+ ```
325
+ productive-mcp/
326
+ ├── src/
327
+ │ ├── index.ts # Stdio transport (local mode)
328
+ │ ├── server.ts # HTTP transport (remote mode)
329
+ │ ├── tools.ts # Tool definitions (shared)
330
+ │ ├── handlers.ts # Tool execution (shared)
331
+ │ └── auth.ts # Bearer token parsing
332
+ ├── Dockerfile
333
+ └── README.md
334
+ ```
335
+
336
+ ## Related Packages
337
+
338
+ - [@studiometa/productive-cli](../productive-cli) - CLI tool for Productive.io
339
+ - [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/sdk) - Official MCP SDK
340
+ - [h3](https://github.com/unjs/h3) - HTTP framework for the server
341
+
342
+ ## License
343
+
344
+ MIT © [Studio Meta](https://www.studiometa.fr)
345
+
346
+ ## Links
347
+
348
+ - [GitHub Repository](https://github.com/studiometa/productive-cli)
349
+ - [Productive.io API Docs](https://developer.productive.io)
350
+ - [MCP Documentation](https://modelcontextprotocol.io)
351
+ - [Claude Desktop Custom Connectors](https://docs.anthropic.com)
352
+ - [Issues](https://github.com/studiometa/productive-cli/issues)
package/dist/auth.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Authentication utilities for Productive MCP server
3
+ */
4
+ export interface ProductiveCredentials {
5
+ organizationId: string;
6
+ apiToken: string;
7
+ userId?: string;
8
+ }
9
+ /**
10
+ * Parse Bearer token containing Productive credentials
11
+ * Token format: base64(organizationId:apiToken) or base64(organizationId:apiToken:userId)
12
+ *
13
+ * @param authHeader - Authorization header value (e.g., "Bearer base64...")
14
+ * @returns Parsed credentials or null if invalid
15
+ */
16
+ export declare function parseAuthHeader(authHeader: string | undefined | null): ProductiveCredentials | null;
17
+ /**
18
+ * Create a Bearer token from Productive credentials
19
+ * Useful for documentation and testing
20
+ *
21
+ * @param credentials - Productive credentials
22
+ * @returns Base64 encoded token (without "Bearer " prefix)
23
+ */
24
+ export declare function createAuthToken(credentials: ProductiveCredentials): string;
25
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +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"}
package/dist/auth.js ADDED
@@ -0,0 +1,40 @@
1
+ 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
+ }
28
+ }
29
+ 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
+ }
36
+ export {
37
+ createAuthToken,
38
+ parseAuthHeader
39
+ };
40
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +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;"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Response formatters for agent-friendly output
3
+ *
4
+ * This module re-exports formatters from @studiometa/productive-cli
5
+ * with MCP-specific defaults (no relationship IDs, no timestamps).
6
+ */
7
+ import { type JsonApiResource, type JsonApiMeta, type FormatOptions, type FormattedPagination } from '@studiometa/productive-cli';
8
+ export type { JsonApiResource, JsonApiMeta, FormatOptions, FormattedPagination };
9
+ /**
10
+ * Format time entry for agent consumption
11
+ */
12
+ export declare function formatTimeEntry(entry: JsonApiResource, _included?: JsonApiResource[]): Record<string, unknown>;
13
+ /**
14
+ * Format project for agent consumption
15
+ */
16
+ export declare function formatProject(project: JsonApiResource, _included?: JsonApiResource[]): Record<string, unknown>;
17
+ /**
18
+ * Format task for agent consumption
19
+ * Tasks use included resources to resolve project/company names
20
+ */
21
+ export declare function formatTask(task: JsonApiResource, included?: JsonApiResource[]): Record<string, unknown>;
22
+ /**
23
+ * Format person for agent consumption
24
+ */
25
+ export declare function formatPerson(person: JsonApiResource, _included?: JsonApiResource[]): Record<string, unknown>;
26
+ /**
27
+ * Format service for agent consumption
28
+ */
29
+ export declare function formatService(service: JsonApiResource, _included?: JsonApiResource[]): Record<string, unknown>;
30
+ /**
31
+ * Format list response with pagination
32
+ *
33
+ * @param data - Array of JSON:API resources
34
+ * @param formatter - Formatter function (item, included?) => T
35
+ * @param meta - Pagination metadata
36
+ * @param included - Included resources for relationship resolution
37
+ */
38
+ export declare function formatListResponse<T>(data: JsonApiResource[], formatter: (item: JsonApiResource, included?: JsonApiResource[]) => T, meta?: JsonApiMeta, included?: JsonApiResource[]): {
39
+ data: T[];
40
+ meta?: FormattedPagination;
41
+ };
42
+ //# sourceMappingURL=formatters.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Tool execution handlers for Productive MCP server
3
+ * These are shared between stdio and HTTP transports
4
+ */
5
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
6
+ import type { ProductiveCredentials } from './auth.js';
7
+ export type ToolResult = CallToolResult;
8
+ /**
9
+ * Execute a tool with the given credentials and arguments
10
+ *
11
+ * @param name - Tool name
12
+ * @param args - Tool arguments
13
+ * @param credentials - Productive API credentials
14
+ * @returns Tool execution result
15
+ */
16
+ export declare function executeToolWithCredentials(name: string, args: Record<string, unknown>, credentials: ProductiveCredentials): Promise<ToolResult>;
17
+ //# sourceMappingURL=handlers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAUvD,MAAM,MAAM,UAAU,GAAG,cAAc,CAAC;AAqBxC;;;;;;;GAOG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,WAAW,EAAE,qBAAqB,GACjC,OAAO,CAAC,UAAU,CAAC,CA0GrB"}
@@ -0,0 +1,137 @@
1
+ import { formatPerson as formatPerson$1, formatListResponse as formatListResponse$1, formatService as formatService$1, formatTask as formatTask$1, formatTimeEntry as formatTimeEntry$1, formatProject as formatProject$1, ProductiveApi } from "@studiometa/productive-cli";
2
+ const MCP_FORMAT_OPTIONS = {
3
+ includeRelationshipIds: false,
4
+ includeTimestamps: false,
5
+ stripHtml: true
6
+ };
7
+ function formatTimeEntry(entry, _included) {
8
+ return formatTimeEntry$1(entry, MCP_FORMAT_OPTIONS);
9
+ }
10
+ function formatProject(project, _included) {
11
+ return formatProject$1(project, MCP_FORMAT_OPTIONS);
12
+ }
13
+ function formatTask(task, included) {
14
+ return formatTask$1(task, { ...MCP_FORMAT_OPTIONS, included });
15
+ }
16
+ function formatPerson(person, _included) {
17
+ return formatPerson$1(person, MCP_FORMAT_OPTIONS);
18
+ }
19
+ function formatService(service, _included) {
20
+ return formatService$1(service, MCP_FORMAT_OPTIONS);
21
+ }
22
+ function formatListResponse(data, formatter, meta, included) {
23
+ const wrappedFormatter = (item, _options) => {
24
+ return formatter(item, included);
25
+ };
26
+ const result = formatListResponse$1(data, wrappedFormatter, meta, {
27
+ ...MCP_FORMAT_OPTIONS,
28
+ included
29
+ });
30
+ return result;
31
+ }
32
+ function jsonResult(data) {
33
+ return {
34
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
35
+ };
36
+ }
37
+ function errorResult(message) {
38
+ return {
39
+ content: [{ type: "text", text: `Error: ${message}` }],
40
+ isError: true
41
+ };
42
+ }
43
+ async function executeToolWithCredentials(name, args, credentials) {
44
+ const api = new ProductiveApi({
45
+ apiToken: credentials.apiToken,
46
+ organizationId: credentials.organizationId
47
+ });
48
+ try {
49
+ switch (name) {
50
+ case "productive_list_projects": {
51
+ const result = await api.getProjects(args);
52
+ return jsonResult(formatListResponse(result.data, formatProject, result.meta));
53
+ }
54
+ case "productive_get_project": {
55
+ const result = await api.getProject(args.id);
56
+ return jsonResult(formatProject(result.data));
57
+ }
58
+ case "productive_list_time_entries": {
59
+ const result = await api.getTimeEntries(args);
60
+ return jsonResult(formatListResponse(result.data, formatTimeEntry, result.meta));
61
+ }
62
+ case "productive_get_time_entry": {
63
+ const result = await api.getTimeEntry(args.id);
64
+ return jsonResult(formatTimeEntry(result.data));
65
+ }
66
+ case "productive_create_time_entry": {
67
+ const result = await api.createTimeEntry(
68
+ args
69
+ );
70
+ return jsonResult({
71
+ success: true,
72
+ ...formatTimeEntry(result.data)
73
+ });
74
+ }
75
+ case "productive_update_time_entry": {
76
+ const { id, ...data } = args;
77
+ const result = await api.updateTimeEntry(id, data);
78
+ return jsonResult({
79
+ success: true,
80
+ ...formatTimeEntry(result.data)
81
+ });
82
+ }
83
+ case "productive_delete_time_entry": {
84
+ await api.deleteTimeEntry(args.id);
85
+ return jsonResult({ success: true, message: "Time entry deleted" });
86
+ }
87
+ case "productive_list_tasks": {
88
+ const params = args || {};
89
+ params.include = ["project", "project.company"];
90
+ const result = await api.getTasks(params);
91
+ return jsonResult(formatListResponse(
92
+ result.data,
93
+ formatTask,
94
+ result.meta,
95
+ result.included
96
+ ));
97
+ }
98
+ case "productive_get_task": {
99
+ const result = await api.getTask(args.id, {
100
+ include: ["project", "project.company"]
101
+ });
102
+ return jsonResult(formatTask(result.data, result.included));
103
+ }
104
+ case "productive_list_services": {
105
+ const result = await api.getServices(args);
106
+ return jsonResult(formatListResponse(result.data, formatService, result.meta));
107
+ }
108
+ case "productive_list_people": {
109
+ const result = await api.getPeople(args);
110
+ return jsonResult(formatListResponse(result.data, formatPerson, result.meta));
111
+ }
112
+ case "productive_get_person": {
113
+ const result = await api.getPerson(args.id);
114
+ return jsonResult(formatPerson(result.data));
115
+ }
116
+ case "productive_get_current_user": {
117
+ if (credentials.userId) {
118
+ const result = await api.getPerson(credentials.userId);
119
+ return jsonResult(formatPerson(result.data));
120
+ }
121
+ return jsonResult({
122
+ message: "User ID not configured. Set userId in credentials to use this tool.",
123
+ organizationId: credentials.organizationId
124
+ });
125
+ }
126
+ default:
127
+ return errorResult(`Unknown tool: ${name}`);
128
+ }
129
+ } catch (error) {
130
+ const message = error instanceof Error ? error.message : String(error);
131
+ return errorResult(message);
132
+ }
133
+ }
134
+ export {
135
+ executeToolWithCredentials
136
+ };
137
+ //# sourceMappingURL=handlers.js.map