@studiometa/productive-mcp 0.10.6 → 0.10.8

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 (39) hide show
  1. package/dist/formatters.d.ts +4 -0
  2. package/dist/formatters.d.ts.map +1 -1
  3. package/dist/handlers/custom-fields.d.ts +26 -0
  4. package/dist/handlers/custom-fields.d.ts.map +1 -0
  5. package/dist/handlers/help.d.ts.map +1 -1
  6. package/dist/handlers/index.d.ts.map +1 -1
  7. package/dist/handlers/pre-validation-guards.d.ts +64 -0
  8. package/dist/handlers/pre-validation-guards.d.ts.map +1 -0
  9. package/dist/handlers/schema.d.ts.map +1 -1
  10. package/dist/handlers/valid-includes.d.ts.map +1 -1
  11. package/dist/{handlers-BvwBrRHp.js → handlers-t95fhdps.js} +2844 -2349
  12. package/dist/handlers-t95fhdps.js.map +1 -0
  13. package/dist/handlers.js +1 -1
  14. package/dist/hints.d.ts +4 -0
  15. package/dist/hints.d.ts.map +1 -1
  16. package/dist/http.d.ts +1 -0
  17. package/dist/http.d.ts.map +1 -1
  18. package/dist/http.js +13 -4
  19. package/dist/http.js.map +1 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +23 -5
  22. package/dist/index.js.map +1 -1
  23. package/dist/resources.d.ts +81 -0
  24. package/dist/resources.d.ts.map +1 -0
  25. package/dist/schema.d.ts +4 -2
  26. package/dist/schema.d.ts.map +1 -1
  27. package/dist/server.js +2 -2
  28. package/dist/{handlers-Cha6_ulB.js → stdio-Bi1Lvp8O.js} +97 -2
  29. package/dist/stdio-Bi1Lvp8O.js.map +1 -0
  30. package/dist/stdio.js +2 -99
  31. package/dist/version-DpBFJ7eV.js +236 -0
  32. package/dist/version-DpBFJ7eV.js.map +1 -0
  33. package/package.json +3 -3
  34. package/skills/SKILL.md +168 -59
  35. package/dist/handlers-BvwBrRHp.js.map +0 -1
  36. package/dist/handlers-Cha6_ulB.js.map +0 -1
  37. package/dist/stdio.js.map +0 -1
  38. package/dist/version-KdH6s_ty.js +0 -29
  39. package/dist/version-KdH6s_ty.js.map +0 -1
@@ -0,0 +1,236 @@
1
+ import { a as handleSchemaOverview, c as handleDeals, i as handleServices, n as handleTasks, o as handleProjects, r as handleSummaries, s as handlePeople } from "./handlers-t95fhdps.js";
2
+ import { ProductiveApi } from "@studiometa/productive-api";
3
+ import { readFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { fromHandlerContext } from "@studiometa/productive-core";
7
+ /**
8
+ * MCP Server Instructions
9
+ *
10
+ * These instructions are sent to Claude Desktop during initialization
11
+ * and used as context/hints for the LLM. This ensures the AI agent
12
+ * knows how to properly use the Productive.io MCP server.
13
+ *
14
+ * The content is derived from skills/SKILL.md (without YAML frontmatter).
15
+ */
16
+ var __dirname = dirname(fileURLToPath(import.meta.url));
17
+ /**
18
+ * Load instructions from SKILL.md file
19
+ * Removes YAML frontmatter (content between --- markers)
20
+ */
21
+ function loadInstructions() {
22
+ try {
23
+ return readFileSync(join(__dirname, "..", "skills", "SKILL.md"), "utf-8").replace(/^---\n[\s\S]*?\n---\n+/, "").trim();
24
+ } catch {
25
+ return "Productive.io MCP Server - Use the productive tool with resource and action parameters.";
26
+ }
27
+ }
28
+ const INSTRUCTIONS = loadInstructions();
29
+ /** MIME type used for all resource content */
30
+ var MIME_TYPE = "application/json";
31
+ /**
32
+ * Static resources that are always available without API credentials
33
+ */
34
+ const STATIC_RESOURCES = [{
35
+ uri: "productive://schema",
36
+ name: "Schema",
37
+ description: "Overview of all Productive.io resources, their actions and filters",
38
+ mimeType: MIME_TYPE
39
+ }, {
40
+ uri: "productive://instructions",
41
+ name: "Instructions",
42
+ description: "Server instructions and usage guide for the Productive.io MCP server",
43
+ mimeType: MIME_TYPE
44
+ }];
45
+ /**
46
+ * Dynamic resources that are computed at read time (require credentials)
47
+ */
48
+ const DYNAMIC_RESOURCES = [{
49
+ uri: "productive://summaries/my_day",
50
+ name: "My Day",
51
+ description: "Personal dashboard: open tasks, today's time entries, active timers",
52
+ mimeType: MIME_TYPE
53
+ }, {
54
+ uri: "productive://summaries/team_pulse",
55
+ name: "Team Pulse",
56
+ description: "Team-wide time tracking activity for today",
57
+ mimeType: MIME_TYPE
58
+ }];
59
+ /**
60
+ * Resource templates that accept URI parameters (require credentials)
61
+ */
62
+ const RESOURCE_TEMPLATES = [
63
+ {
64
+ uriTemplate: "productive://projects/{id}",
65
+ name: "Project",
66
+ description: "Details of a specific project by ID",
67
+ mimeType: MIME_TYPE
68
+ },
69
+ {
70
+ uriTemplate: "productive://tasks/{id}",
71
+ name: "Task",
72
+ description: "Details of a specific task by ID",
73
+ mimeType: MIME_TYPE
74
+ },
75
+ {
76
+ uriTemplate: "productive://people/{id}",
77
+ name: "Person",
78
+ description: "Details of a specific person by ID",
79
+ mimeType: MIME_TYPE
80
+ },
81
+ {
82
+ uriTemplate: "productive://deals/{id}",
83
+ name: "Deal",
84
+ description: "Details of a specific deal or budget by ID",
85
+ mimeType: MIME_TYPE
86
+ },
87
+ {
88
+ uriTemplate: "productive://projects/{id}/tasks",
89
+ name: "Project Tasks",
90
+ description: "All tasks belonging to a specific project",
91
+ mimeType: MIME_TYPE
92
+ },
93
+ {
94
+ uriTemplate: "productive://projects/{id}/services",
95
+ name: "Project Services",
96
+ description: "All services belonging to a specific project",
97
+ mimeType: MIME_TYPE
98
+ }
99
+ ];
100
+ /** Route patterns and their handler factories */
101
+ var URI_PATTERNS = [
102
+ {
103
+ pattern: /^productive:\/\/schema$/,
104
+ handler: async () => {
105
+ const text = handleSchemaOverview().content[0];
106
+ return JSON.parse(text.text);
107
+ }
108
+ },
109
+ {
110
+ pattern: /^productive:\/\/instructions$/,
111
+ handler: async () => ({ instructions: INSTRUCTIONS })
112
+ },
113
+ {
114
+ pattern: /^productive:\/\/summaries\/my_day$/,
115
+ handler: async (_, credentials) => {
116
+ return extractJsonFromResult(await handleSummaries("my_day", {}, buildHandlerContext(credentials)));
117
+ }
118
+ },
119
+ {
120
+ pattern: /^productive:\/\/summaries\/team_pulse$/,
121
+ handler: async (_, credentials) => {
122
+ return extractJsonFromResult(await handleSummaries("team_pulse", {}, buildHandlerContext(credentials)));
123
+ }
124
+ },
125
+ {
126
+ pattern: /^productive:\/\/projects\/([^/]+)\/tasks$/,
127
+ handler: async ([, id], credentials) => {
128
+ const ctx = buildHandlerContext(credentials, { filter: { project_id: id } });
129
+ return extractJsonFromResult(await handleTasks("list", { project_id: id }, ctx));
130
+ }
131
+ },
132
+ {
133
+ pattern: /^productive:\/\/projects\/([^/]+)\/services$/,
134
+ handler: async ([, id], credentials) => {
135
+ return extractJsonFromResult(await handleServices("list", {}, buildHandlerContext(credentials, { filter: { project_id: id } })));
136
+ }
137
+ },
138
+ {
139
+ pattern: /^productive:\/\/projects\/([^/]+)$/,
140
+ handler: async ([, id], credentials) => {
141
+ const ctx = buildHandlerContext(credentials);
142
+ return extractJsonFromResult(await handleProjects("get", { id }, ctx));
143
+ }
144
+ },
145
+ {
146
+ pattern: /^productive:\/\/tasks\/([^/]+)$/,
147
+ handler: async ([, id], credentials) => {
148
+ const ctx = buildHandlerContext(credentials);
149
+ return extractJsonFromResult(await handleTasks("get", { id }, ctx));
150
+ }
151
+ },
152
+ {
153
+ pattern: /^productive:\/\/people\/([^/]+)$/,
154
+ handler: async ([, id], credentials) => {
155
+ const ctx = buildHandlerContext(credentials);
156
+ return extractJsonFromResult(await handlePeople("get", { id }, ctx, credentials));
157
+ }
158
+ },
159
+ {
160
+ pattern: /^productive:\/\/deals\/([^/]+)$/,
161
+ handler: async ([, id], credentials) => {
162
+ const ctx = buildHandlerContext(credentials);
163
+ return extractJsonFromResult(await handleDeals("get", { id }, ctx));
164
+ }
165
+ }
166
+ ];
167
+ /**
168
+ * Build a HandlerContext from credentials, with optional filter overrides.
169
+ */
170
+ function buildHandlerContext(credentials, overrides = {}) {
171
+ const execCtx = fromHandlerContext({ api: new ProductiveApi({ config: {
172
+ apiToken: credentials.apiToken,
173
+ organizationId: credentials.organizationId,
174
+ userId: credentials.userId,
175
+ baseUrl: process.env.PRODUCTIVE_BASE_URL
176
+ } }) }, { userId: credentials.userId });
177
+ return {
178
+ formatOptions: { compact: false },
179
+ filter: overrides.filter,
180
+ perPage: 50,
181
+ includeHints: false,
182
+ includeSuggestions: false,
183
+ executor: () => execCtx
184
+ };
185
+ }
186
+ /**
187
+ * Extract the parsed JSON data from a ToolResult.
188
+ * Throws if the result is an error or not parseable.
189
+ */
190
+ function extractJsonFromResult(result) {
191
+ if (result.isError) {
192
+ const text = result.content[0].text;
193
+ throw new Error(text);
194
+ }
195
+ const text = result.content[0].text;
196
+ try {
197
+ return JSON.parse(text);
198
+ } catch {
199
+ return { text };
200
+ }
201
+ }
202
+ /**
203
+ * List all static and dynamic resources (no credentials needed for static).
204
+ */
205
+ function listResources() {
206
+ return [...STATIC_RESOURCES, ...DYNAMIC_RESOURCES];
207
+ }
208
+ /**
209
+ * List all resource templates.
210
+ */
211
+ function listResourceTemplates() {
212
+ return RESOURCE_TEMPLATES;
213
+ }
214
+ /**
215
+ * Read a resource by URI.
216
+ *
217
+ * Routes the URI to the appropriate handler. Throws on unknown URI.
218
+ */
219
+ async function readResource(uri, credentials) {
220
+ for (const { pattern, handler } of URI_PATTERNS) {
221
+ const match = uri.match(pattern);
222
+ if (match) {
223
+ const data = await handler(match, credentials);
224
+ return { contents: [{
225
+ uri,
226
+ mimeType: MIME_TYPE,
227
+ text: JSON.stringify(data, null, 2)
228
+ }] };
229
+ }
230
+ }
231
+ throw new Error(`Unknown resource URI: ${uri}. Available static resources: ${STATIC_RESOURCES.map((r) => r.uri).join(", ")}. Available dynamic resources: ${DYNAMIC_RESOURCES.map((r) => r.uri).join(", ")}. Resource templates: ${RESOURCE_TEMPLATES.map((t) => t.uriTemplate).join(", ")}.`);
232
+ }
233
+ const VERSION = "0.10.8";
234
+ export { INSTRUCTIONS as a, readResource as i, listResourceTemplates as n, listResources as r, VERSION as t };
235
+
236
+ //# sourceMappingURL=version-DpBFJ7eV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-DpBFJ7eV.js","names":[],"sources":["../src/instructions.ts","../src/resources.ts","../src/version.ts"],"sourcesContent":["/**\n * MCP Server Instructions\n *\n * These instructions are sent to Claude Desktop during initialization\n * and used as context/hints for the LLM. This ensures the AI agent\n * knows how to properly use the Productive.io MCP server.\n *\n * The content is derived from skills/SKILL.md (without YAML frontmatter).\n */\n\nimport { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n/**\n * Load instructions from SKILL.md file\n * Removes YAML frontmatter (content between --- markers)\n */\nfunction loadInstructions(): string {\n try {\n // In dist/, go up to package root, then to skills/\n const skillPath = join(__dirname, '..', 'skills', 'SKILL.md');\n const content = readFileSync(skillPath, 'utf-8');\n\n // Remove YAML frontmatter (between --- markers at start of file)\n const withoutFrontmatter = content.replace(/^---\\n[\\s\\S]*?\\n---\\n+/, '');\n\n return withoutFrontmatter.trim();\n } catch {\n // Fallback if file not found (shouldn't happen in production)\n return 'Productive.io MCP Server - Use the productive tool with resource and action parameters.';\n }\n}\n\nexport const INSTRUCTIONS = loadInstructions();\n","/**\n * MCP Resources handlers for Productive MCP Server\n *\n * Exposes Productive data via MCP resources/ capability so clients can browse\n * and read data without tool calls.\n *\n * Static resources (always available):\n * productive://schema — full resource schema overview\n * productive://instructions — server instructions / SKILL.md content\n *\n * Resource templates (parameterized, require API calls):\n * productive://projects/{id} — project details\n * productive://tasks/{id} — task details\n * productive://people/{id} — person details\n * productive://deals/{id} — deal details\n * productive://projects/{id}/tasks — tasks for a project\n * productive://projects/{id}/services — services for a project\n *\n * Dynamic resources (computed):\n * productive://summaries/my_day — personal dashboard\n * productive://summaries/team_pulse — team activity\n */\n\nimport type { ReadResourceResult as McpReadResourceResult } from '@modelcontextprotocol/sdk/types.js';\n\nimport { ProductiveApi } from '@studiometa/productive-api';\nimport { fromHandlerContext } from '@studiometa/productive-core';\n\nimport type { ProductiveCredentials } from './auth.js';\nimport type { HandlerContext } from './handlers/types.js';\n\nimport { handleDeals } from './handlers/deals.js';\nimport { handlePeople } from './handlers/people.js';\nimport { handleProjects } from './handlers/projects.js';\nimport { handleSchemaOverview } from './handlers/schema.js';\nimport { handleServices } from './handlers/services.js';\nimport { handleSummaries } from './handlers/summaries.js';\nimport { handleTasks } from './handlers/tasks.js';\nimport { INSTRUCTIONS } from './instructions.js';\n\n/** MIME type used for all resource content */\nconst MIME_TYPE = 'application/json';\n\n/**\n * A single resource content item returned in resources/read responses\n */\nexport interface ResourceContent {\n uri: string;\n mimeType: string;\n text: string;\n}\n\n/**\n * Shape of a resources/read response (re-export of SDK type for consumers)\n */\nexport type ReadResourceResult = McpReadResourceResult;\n\n/**\n * Shape of a static resource descriptor (resources/list)\n */\nexport interface StaticResource {\n uri: string;\n name: string;\n description: string;\n mimeType: string;\n}\n\n/**\n * Shape of a resource template descriptor (resources/templates/list)\n */\nexport interface ResourceTemplate {\n uriTemplate: string;\n name: string;\n description: string;\n mimeType: string;\n}\n\n// ---------------------------------------------------------------------------\n// Static resource definitions\n// ---------------------------------------------------------------------------\n\n/**\n * Static resources that are always available without API credentials\n */\nexport const STATIC_RESOURCES: StaticResource[] = [\n {\n uri: 'productive://schema',\n name: 'Schema',\n description: 'Overview of all Productive.io resources, their actions and filters',\n mimeType: MIME_TYPE,\n },\n {\n uri: 'productive://instructions',\n name: 'Instructions',\n description: 'Server instructions and usage guide for the Productive.io MCP server',\n mimeType: MIME_TYPE,\n },\n];\n\n// ---------------------------------------------------------------------------\n// Dynamic resource definitions\n// ---------------------------------------------------------------------------\n\n/**\n * Dynamic resources that are computed at read time (require credentials)\n */\nexport const DYNAMIC_RESOURCES: StaticResource[] = [\n {\n uri: 'productive://summaries/my_day',\n name: 'My Day',\n description: \"Personal dashboard: open tasks, today's time entries, active timers\",\n mimeType: MIME_TYPE,\n },\n {\n uri: 'productive://summaries/team_pulse',\n name: 'Team Pulse',\n description: 'Team-wide time tracking activity for today',\n mimeType: MIME_TYPE,\n },\n];\n\n// ---------------------------------------------------------------------------\n// Resource template definitions\n// ---------------------------------------------------------------------------\n\n/**\n * Resource templates that accept URI parameters (require credentials)\n */\nexport const RESOURCE_TEMPLATES: ResourceTemplate[] = [\n {\n uriTemplate: 'productive://projects/{id}',\n name: 'Project',\n description: 'Details of a specific project by ID',\n mimeType: MIME_TYPE,\n },\n {\n uriTemplate: 'productive://tasks/{id}',\n name: 'Task',\n description: 'Details of a specific task by ID',\n mimeType: MIME_TYPE,\n },\n {\n uriTemplate: 'productive://people/{id}',\n name: 'Person',\n description: 'Details of a specific person by ID',\n mimeType: MIME_TYPE,\n },\n {\n uriTemplate: 'productive://deals/{id}',\n name: 'Deal',\n description: 'Details of a specific deal or budget by ID',\n mimeType: MIME_TYPE,\n },\n {\n uriTemplate: 'productive://projects/{id}/tasks',\n name: 'Project Tasks',\n description: 'All tasks belonging to a specific project',\n mimeType: MIME_TYPE,\n },\n {\n uriTemplate: 'productive://projects/{id}/services',\n name: 'Project Services',\n description: 'All services belonging to a specific project',\n mimeType: MIME_TYPE,\n },\n];\n\n// ---------------------------------------------------------------------------\n// URI pattern matching\n// ---------------------------------------------------------------------------\n\n/** Route patterns and their handler factories */\nconst URI_PATTERNS: Array<{\n pattern: RegExp;\n handler: (match: RegExpMatchArray, credentials: ProductiveCredentials) => Promise<unknown>;\n}> = [\n // Static resources (no credentials needed)\n {\n pattern: /^productive:\\/\\/schema$/,\n handler: async () => {\n const result = handleSchemaOverview();\n const text = result.content[0] as { text: string };\n return JSON.parse(text.text);\n },\n },\n {\n pattern: /^productive:\\/\\/instructions$/,\n handler: async () => ({ instructions: INSTRUCTIONS }),\n },\n\n // Dynamic summaries\n {\n pattern: /^productive:\\/\\/summaries\\/my_day$/,\n handler: async (_, credentials) => {\n const ctx = buildHandlerContext(credentials);\n const result = await handleSummaries('my_day', {}, ctx);\n return extractJsonFromResult(result);\n },\n },\n {\n pattern: /^productive:\\/\\/summaries\\/team_pulse$/,\n handler: async (_, credentials) => {\n const ctx = buildHandlerContext(credentials);\n const result = await handleSummaries('team_pulse', {}, ctx);\n return extractJsonFromResult(result);\n },\n },\n\n // Project nested resources (before single project to avoid conflict)\n {\n pattern: /^productive:\\/\\/projects\\/([^/]+)\\/tasks$/,\n handler: async ([, id], credentials) => {\n const ctx = buildHandlerContext(credentials, { filter: { project_id: id } });\n const result = await handleTasks('list', { project_id: id }, ctx);\n return extractJsonFromResult(result);\n },\n },\n {\n pattern: /^productive:\\/\\/projects\\/([^/]+)\\/services$/,\n handler: async ([, id], credentials) => {\n const ctx = buildHandlerContext(credentials, { filter: { project_id: id } });\n // handleServices uses CommonArgs; project_id is passed via ctx.filter\n const result = await handleServices('list', {}, ctx);\n return extractJsonFromResult(result);\n },\n },\n\n // Single entity resources\n {\n pattern: /^productive:\\/\\/projects\\/([^/]+)$/,\n handler: async ([, id], credentials) => {\n const ctx = buildHandlerContext(credentials);\n const result = await handleProjects('get', { id }, ctx);\n return extractJsonFromResult(result);\n },\n },\n {\n pattern: /^productive:\\/\\/tasks\\/([^/]+)$/,\n handler: async ([, id], credentials) => {\n const ctx = buildHandlerContext(credentials);\n const result = await handleTasks('get', { id }, ctx);\n return extractJsonFromResult(result);\n },\n },\n {\n pattern: /^productive:\\/\\/people\\/([^/]+)$/,\n handler: async ([, id], credentials) => {\n const ctx = buildHandlerContext(credentials);\n const result = await handlePeople('get', { id }, ctx, credentials);\n return extractJsonFromResult(result);\n },\n },\n {\n pattern: /^productive:\\/\\/deals\\/([^/]+)$/,\n handler: async ([, id], credentials) => {\n const ctx = buildHandlerContext(credentials);\n const result = await handleDeals('get', { id }, ctx);\n return extractJsonFromResult(result);\n },\n },\n];\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Build a HandlerContext from credentials, with optional filter overrides.\n */\nfunction buildHandlerContext(\n credentials: ProductiveCredentials,\n overrides: { filter?: Record<string, string> } = {},\n): HandlerContext {\n const api = new ProductiveApi({\n config: {\n apiToken: credentials.apiToken,\n organizationId: credentials.organizationId,\n userId: credentials.userId,\n baseUrl: process.env.PRODUCTIVE_BASE_URL,\n },\n });\n\n const execCtx = fromHandlerContext({ api }, { userId: credentials.userId });\n\n return {\n formatOptions: { compact: false },\n filter: overrides.filter,\n perPage: 50,\n includeHints: false,\n includeSuggestions: false,\n executor: () => execCtx,\n };\n}\n\n/**\n * Extract the parsed JSON data from a ToolResult.\n * Throws if the result is an error or not parseable.\n */\nfunction extractJsonFromResult(result: { content: unknown[]; isError?: boolean }): unknown {\n if (result.isError) {\n const text = (result.content[0] as { text: string }).text;\n throw new Error(text);\n }\n const text = (result.content[0] as { text: string }).text;\n try {\n return JSON.parse(text);\n } catch {\n return { text };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * List all static and dynamic resources (no credentials needed for static).\n */\nexport function listResources(): StaticResource[] {\n return [...STATIC_RESOURCES, ...DYNAMIC_RESOURCES];\n}\n\n/**\n * List all resource templates.\n */\nexport function listResourceTemplates(): ResourceTemplate[] {\n return RESOURCE_TEMPLATES;\n}\n\n/**\n * Read a resource by URI.\n *\n * Routes the URI to the appropriate handler. Throws on unknown URI.\n */\nexport async function readResource(\n uri: string,\n credentials: ProductiveCredentials,\n): Promise<ReadResourceResult> {\n for (const { pattern, handler } of URI_PATTERNS) {\n const match = uri.match(pattern);\n if (match) {\n const data = await handler(match, credentials);\n return {\n contents: [\n {\n uri,\n mimeType: MIME_TYPE,\n text: JSON.stringify(data, null, 2),\n },\n ],\n };\n }\n }\n\n throw new Error(\n `Unknown resource URI: ${uri}. ` +\n `Available static resources: ${STATIC_RESOURCES.map((r) => r.uri).join(', ')}. ` +\n `Available dynamic resources: ${DYNAMIC_RESOURCES.map((r) => r.uri).join(', ')}. ` +\n `Resource templates: ${RESOURCE_TEMPLATES.map((t) => t.uriTemplate).join(', ')}.`,\n );\n}\n","/**\n * Package version - injected from package.json at build time\n */\ndeclare const __VERSION__: string;\nexport const VERSION = __VERSION__;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studiometa/productive-mcp",
3
- "version": "0.10.6",
3
+ "version": "0.10.8",
4
4
  "description": "MCP server for Productive.io API - Model Context Protocol integration for Claude Desktop",
5
5
  "keywords": [
6
6
  "ai",
@@ -87,8 +87,8 @@
87
87
  },
88
88
  "dependencies": {
89
89
  "@modelcontextprotocol/sdk": "^1.26.0",
90
- "@studiometa/productive-api": "0.10.6",
91
- "@studiometa/productive-core": "0.10.6",
90
+ "@studiometa/productive-api": "0.10.8",
91
+ "@studiometa/productive-core": "0.10.8",
92
92
  "h3": "^1.15.1",
93
93
  "zod": "4.3.6"
94
94
  },
package/skills/SKILL.md CHANGED
@@ -21,24 +21,25 @@ productive(resource, action, [parameters...])
21
21
 
22
22
  ### Resources & Actions
23
23
 
24
- | Resource | Actions | Description |
25
- | ------------- | ------------------------------------------------------------------------ | -------------------------------------------------------- |
26
- | `projects` | `list`, `get`, `resolve`, `context`, `help` | Project management |
27
- | `time` | `list`, `get`, `create`, `update`, `resolve`, `help` | Time tracking |
28
- | `tasks` | `list`, `get`, `create`, `update`, `resolve`, `context`, `help` | Task management |
29
- | `services` | `list`, `get`, `resolve`, `help` | Budget line items |
30
- | `people` | `list`, `get`, `me`, `resolve`, `help` | Team members |
31
- | `companies` | `list`, `get`, `create`, `update`, `resolve`, `help` | Client companies |
32
- | `comments` | `list`, `get`, `create`, `update`, `help` | Comments on tasks/deals |
33
- | `attachments` | `list`, `get`, `delete`, `help` | File attachments |
34
- | `timers` | `list`, `get`, `start`, `stop`, `help` | Active timers |
35
- | `deals` | `list`, `get`, `create`, `update`, `resolve`, `context`, `help` | Sales deals & budgets (use `filter[type]=2` for budgets) |
36
- | `bookings` | `list`, `get`, `create`, `update`, `help` | Resource scheduling |
37
- | `pages` | `list`, `get`, `create`, `update`, `delete`, `help` | Wiki/docs pages |
38
- | `discussions` | `list`, `get`, `create`, `update`, `delete`, `resolve`, `reopen`, `help` | Discussions on pages |
39
- | `activities` | `list`, `help` | Activity feed (audit log of create/update/delete events) |
40
- | `reports` | `get`, `help` | Generate reports |
41
- | `workflows` | `complete_task`, `log_day`, `weekly_standup`, `help` | Compound workflows chaining multiple operations |
24
+ | Resource | Actions | Description |
25
+ | --------------- | ------------------------------------------------------------------------ | -------------------------------------------------------- |
26
+ | `projects` | `list`, `get`, `resolve`, `context`, `help` | Project management |
27
+ | `time` | `list`, `get`, `create`, `update`, `resolve`, `help` | Time tracking |
28
+ | `tasks` | `list`, `get`, `create`, `update`, `resolve`, `context`, `help` | Task management |
29
+ | `services` | `list`, `get`, `resolve`, `help` | Budget line items |
30
+ | `people` | `list`, `get`, `me`, `resolve`, `help` | Team members |
31
+ | `companies` | `list`, `get`, `create`, `update`, `resolve`, `help` | Client companies |
32
+ | `comments` | `list`, `get`, `create`, `update`, `help` | Comments on tasks/deals |
33
+ | `attachments` | `list`, `get`, `delete`, `help` | File attachments |
34
+ | `timers` | `list`, `get`, `start`, `stop`, `help` | Active timers |
35
+ | `deals` | `list`, `get`, `create`, `update`, `resolve`, `context`, `help` | Sales deals & budgets (use `filter[type]=2` for budgets) |
36
+ | `bookings` | `list`, `get`, `create`, `update`, `help` | Resource scheduling |
37
+ | `pages` | `list`, `get`, `create`, `update`, `delete`, `help` | Wiki/docs pages |
38
+ | `discussions` | `list`, `get`, `create`, `update`, `delete`, `resolve`, `reopen`, `help` | Discussions on pages |
39
+ | `activities` | `list`, `help` | Activity feed (audit log of create/update/delete events) |
40
+ | `custom_fields` | `list`, `get`, `help` | Custom field definitions and option values |
41
+ | `reports` | `get`, `help` | Generate reports |
42
+ | `workflows` | `complete_task`, `log_day`, `weekly_standup`, `help` | Compound workflows chaining multiple operations |
42
43
 
43
44
  ### Getting Help
44
45
 
@@ -472,89 +473,166 @@ Compound workflows that chain multiple operations into a single tool call.
472
473
 
473
474
  ## Filters Reference
474
475
 
476
+ > **Tip:** Use `action: "help"` on any resource to see the full, up-to-date list of filters, fields, and examples. Use `action: "schema"` for a compact machine-readable spec.
477
+ >
478
+ > ```json
479
+ > { "resource": "tasks", "action": "help" }
480
+ > { "resource": "tasks", "action": "schema" }
481
+ > ```
482
+
483
+ ### Text Search with `query`
484
+
485
+ Many resources support a `query` filter for full-text search. You can pass it either as a top-level shorthand or via the `filter` object (passthrough pattern):
486
+
487
+ ```json
488
+ // Shorthand (top-level)
489
+ { "resource": "projects", "action": "list", "query": "website" }
490
+
491
+ // Filter passthrough (explicit)
492
+ { "resource": "projects", "action": "list", "filter": { "query": "website" } }
493
+ ```
494
+
495
+ Resources that support `query`: **projects**, **tasks**, **people**, **companies**, **deals**
496
+
475
497
  ### Time Entries
476
498
 
477
- - `person_id` - Filter by person (use "me" for current user)
478
- - `project_id` - Filter by project
479
- - `service_id` - Filter by service
480
- - `task_id` - Filter by task
481
- - `company_id` - Filter by company
482
- - `deal_id` / `budget_id` - Filter by deal/budget
483
- - `after` - After date (YYYY-MM-DD)
484
- - `before` - Before date (YYYY-MM-DD)
499
+ - `person_id` - Filter by person (use "me" for current user) (array)
500
+ - `project_id` - Filter by project (array)
501
+ - `service_id` - Filter by service (array)
502
+ - `task_id` - Filter by task (array)
503
+ - `company_id` - Filter by company (array)
504
+ - `deal_id` / `budget_id` - Filter by deal/budget (array)
505
+ - `after` / `before` - Date range (YYYY-MM-DD)
506
+ - `date` - Exact date (YYYY-MM-DD)
485
507
  - `status` - Approval status: `1`=approved, `2`=unapproved, `3`=rejected
486
508
  - `billing_type_id` - Billing type: `1`=fixed, `2`=actuals, `3`=non_billable
487
509
  - `invoicing_status` - Invoicing: `1`=not_invoiced, `2`=drafted, `3`=finalized
510
+ - `invoiced` - Invoiced status (boolean)
511
+ - `creator_id` / `approver_id` - Filter by creator or approver (array)
512
+ - `booking_id` - Filter by booking (array)
513
+ - `autotracked` - Auto-tracked entries (boolean)
488
514
 
489
515
  ### Tasks
490
516
 
491
- - `project_id` - Filter by project
492
- - `company_id` - Filter by company
493
- - `assignee_id` - Filter by assigned person
494
- - `creator_id` - Filter by task creator
517
+ - `query` - Text search on task title
518
+ - `project_id` - Filter by project (array)
519
+ - `company_id` - Filter by company (array)
520
+ - `assignee_id` - Filter by assigned person (array)
521
+ - `creator_id` - Filter by task creator (array)
495
522
  - `status` - Status: `1`=open, `2`=closed (or "open", "closed", "all")
496
- - `task_list_id` - Filter by task list
497
- - `board_id` - Filter by board
498
- - `workflow_status_id` - Filter by workflow status (kanban column)
499
- - `parent_task_id` - Filter by parent task (for subtasks)
523
+ - `task_list_id` - Filter by task list (array)
524
+ - `task_list_status` - Task list status: `1`=open, `2`=closed
525
+ - `board_id` - Filter by board (array)
526
+ - `workflow_status_id` - Filter by workflow status/kanban column (array)
527
+ - `workflow_status_category_id` - Workflow category: `1`=not started, `2`=started, `3`=closed
528
+ - `workflow_id` - Filter by workflow (array)
529
+ - `parent_task_id` - Filter by parent task (for subtasks) (array)
530
+ - `task_type` - Task type: `1`=parent task, `2`=subtask
500
531
  - `overdue_status` - Overdue: `1`=not overdue, `2`=overdue
501
532
  - `due_date_on` / `due_date_before` / `due_date_after` - Due date filters
533
+ - `start_date_before` / `start_date_after` - Start date filters
534
+ - `after` / `before` - Created date range
535
+ - `closed_after` / `closed_before` - Closed date range
536
+ - `project_manager_id` - Filter by project manager (array)
537
+ - `subscriber_id` - Filter by subscriber/watcher (array)
538
+ - `tags` - Filter by tags
502
539
 
503
540
  ### Projects
504
541
 
505
- - `company_id` - Filter by company
542
+ - `query` - Text search on project name
543
+ - `company_id` - Filter by company (array)
506
544
  - `project_type` - Type: `1`=internal, `2`=client
507
- - `responsible_id` - Filter by project manager
508
- - `person_id` - Filter by team member
545
+ - `responsible_id` - Filter by project manager (array)
546
+ - `person_id` - Filter by team member (array)
509
547
  - `status` - Status: `1`=active, `2`=archived
510
548
 
511
549
  ### Services
512
550
 
513
- - `project_id` - Filter by project
514
- - `deal_id` - Filter by deal
515
- - `task_id` - Filter by task
516
- - `person_id` - Filter by person (trackable by)
551
+ - `project_id` - Filter by project (array)
552
+ - `deal_id` - Filter by deal (array)
553
+ - `task_id` - Filter by task (array)
554
+ - `person_id` - Filter by person/trackable by (array)
555
+ - `name` - Filter by service name (text match)
517
556
  - `budget_status` - Status: `1`=open, `2`=delivered
557
+ - `stage_status_id` - Stage: `1`=open, `2`=won, `3`=lost, `4`=delivered (array)
518
558
  - `billing_type` - Type: `1`=fixed, `2`=actuals, `3`=none
519
- - `time_tracking_enabled` - Boolean
559
+ - `unit` - Unit: `1`=hour, `2`=piece, `3`=day
560
+ - `time_tracking_enabled` / `expense_tracking_enabled` - Boolean
561
+ - `trackable_by_person_id` - Services trackable by a specific person
562
+ - `after` / `before` - Date range
520
563
 
521
564
  ### People
522
565
 
566
+ - `query` - Text search on name or email
567
+ - `email` - Filter by exact email address
523
568
  - `status` - Status: `1`=active, `2`=deactivated
524
569
  - `person_type` - Type: `1`=user, `2`=contact, `3`=placeholder
525
- - `company_id` - Filter by company
570
+ - `company_id` - Filter by company (array)
526
571
  - `project_id` - Filter by project
527
- - `role_id` - Filter by role
572
+ - `role_id` - Filter by role (array)
528
573
  - `team` - Filter by team name
574
+ - `manager_id` - Filter by manager
575
+ - `custom_role_id` - Filter by custom role
576
+ - `tags` - Filter by tags
577
+
578
+ ### Companies
579
+
580
+ - `query` - Text search on company name
581
+ - `name` - Exact name match
582
+ - `company_code` / `billing_name` / `vat` - Filter by specific fields
583
+ - `status` - Status (integer)
584
+ - `archived` - Archived status (boolean)
585
+ - `project_id` - Filter by project (array)
586
+ - `subsidiary_id` - Filter by subsidiary (array)
587
+ - `default_currency` - Filter by currency code (e.g. USD, EUR)
529
588
 
530
589
  ### Deals
531
590
 
532
- - `company_id` - Filter by company
533
- - `project_id` - Filter by project
534
- - `responsible_id` - Filter by responsible person
535
- - `pipeline_id` - Filter by pipeline
536
- - `stage_status_id` - Stage: `1`=open, `2`=won, `3`=lost
591
+ - `query` - Text search on deal name
592
+ - `number` - Filter by deal number
593
+ - `company_id` - Filter by company (array)
594
+ - `project_id` - Filter by project (array)
595
+ - `responsible_id` - Filter by responsible person (array)
596
+ - `creator_id` - Filter by creator (array)
597
+ - `pipeline_id` - Filter by pipeline (array)
598
+ - `stage_status_id` - Stage: `1`=open, `2`=won, `3`=lost (array)
599
+ - `status_id` - Filter by deal status (array)
537
600
  - `type` - Type: `1`=deal, `2`=budget
601
+ - `deal_type_id` - Deal type: `1`=internal, `2`=client
538
602
  - `budget_status` - Budget status: `1`=open, `2`=closed
603
+ - `project_type` - Project type: `1`=internal, `2`=client
604
+ - `subsidiary_id` - Filter by subsidiary (array)
605
+ - `tags` - Filter by tags
606
+ - `recurring` - Recurring deals (boolean)
607
+ - `needs_invoicing` / `time_approval` - Boolean filters
539
608
 
540
609
  > **Note:** Budgets are deals with `budget=true`. There is no separate `/budgets` endpoint. Use `filter[type]=2` to list only budgets.
541
610
 
542
611
  ### Bookings
543
612
 
544
- - `person_id` - Filter by person
613
+ - `person_id` - Filter by person (array)
545
614
  - `service_id` - Filter by service
546
- - `project_id` - Filter by project
547
- - `company_id` - Filter by company
548
- - `event_id` - Filter by event
549
- - `after` / `before` - Date range
615
+ - `project_id` - Filter by project (array)
616
+ - `company_id` - Filter by company (array)
617
+ - `event_id` - Filter by event/absence (array)
618
+ - `task_id` - Filter by task (array)
619
+ - `approver_id` - Filter by approver (array)
620
+ - `after` / `before` - Date range (YYYY-MM-DD)
621
+ - `started_on` / `ended_on` - Exact start/end date
550
622
  - `booking_type` - Type: `event` (absence) or `service` (budget)
551
- - `draft` - Tentative status: `true`/`false`
623
+ - `draft` - Tentative bookings only: `true`/`false`
624
+ - `with_draft` - Include tentative bookings: `true`/`false`
625
+ - `status` / `approval_status` - Approval status (array)
626
+ - `billing_type_id` - Billing type: `1`=fixed, `2`=actuals, `3`=none (array)
627
+ - `person_type` - Person type: `1`=user, `2`=contact, `3`=placeholder
628
+ - `canceled` - Canceled bookings (boolean)
552
629
 
553
630
  ### Pages
554
631
 
555
- - `project_id` - Filter by project
632
+ - `project_id` - Filter by project (array)
556
633
  - `creator_id` - Filter by creator
557
634
  - `parent_page_id` - Filter by parent page (for sub-pages)
635
+ - `edited_at` - Filter by last edited date (ISO 8601)
558
636
 
559
637
  ### Discussions
560
638
 
@@ -564,15 +642,46 @@ Compound workflows that chain multiple operations into a single tool call.
564
642
  ### Comments
565
643
 
566
644
  - `task_id` - Filter by task
567
- - `deal_id` - Filter by deal
568
- - `project_id` - Filter by project
569
- - `page_id` - Filter by page
645
+ - `project_id` - Filter by project (array)
646
+ - `page_id` - Filter by page (array)
570
647
  - `discussion_id` - Filter by discussion
648
+ - `draft` - Draft comments: `true`/`false`
649
+ - `workflow_status_category_id` - Filter by workflow status category (array)
650
+
651
+ ### Activities
652
+
653
+ - `event` - Event type: create, copy, update, delete, etc.
654
+ - `type` - Activity type: `1`=Comment, `2`=Changeset, `3`=Email
655
+ - `after` / `before` - ISO 8601 timestamp range
656
+ - `person_id` - Filter by person (array)
657
+ - `project_id` - Filter by project (array)
658
+ - `company_id` / `task_id` / `deal_id` / `discussion_id` - Filter by resource (array)
659
+ - `item_type` - Resource type (e.g. Task, Page, Deal, Workspace)
660
+ - `parent_type` / `root_type` - Parent/root resource type
661
+ - `has_attachments` / `pinned` - Boolean filters
662
+
663
+ ### Custom Fields
664
+
665
+ - `customizable_type` - Resource type: Task, Deal, Company, Project, Booking, Service, etc.
666
+ - `archived` - Archived status: `true`/`false`
667
+ - `name` - Filter by field name
668
+ - `project_id` - Filter by project
669
+ - `global` - Global custom fields: `true`/`false`
670
+
671
+ **Workflow to resolve custom field values:**
672
+
673
+ 1. Fetch a task/deal with `custom_fields` attribute (raw `{field_id: value}` hash)
674
+ 2. List definitions: `resource=custom_fields, action=list, filter={customizable_type: "Task"}`
675
+ 3. For select/multi-select: get field with options: `resource=custom_fields, action=get, id=<field_id>, include=["options"]`
676
+ 4. Map field IDs to names, option IDs to values
677
+
678
+ **Data types:** 1=Text, 2=Number, 3=Select, 4=Date, 5=Multi-select, 6=Person, 7=Attachment
571
679
 
572
680
  ### Timers
573
681
 
574
682
  - `person_id` - Filter by person
575
683
  - `time_entry_id` - Filter by time entry
684
+ - `started_at` / `stopped_at` - Filter by start/stop time (ISO 8601)
576
685
 
577
686
  ## Include (Related Resources)
578
687