@studiometa/productive-mcp 0.10.14 → 0.10.16

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 (53) hide show
  1. package/dist/handlers/api-read.d.ts +2 -1
  2. package/dist/handlers/api-read.d.ts.map +1 -1
  3. package/dist/handlers/api-utils.d.ts +20 -0
  4. package/dist/handlers/api-utils.d.ts.map +1 -1
  5. package/dist/handlers/help.d.ts +20 -0
  6. package/dist/handlers/help.d.ts.map +1 -1
  7. package/dist/handlers/index.d.ts.map +1 -1
  8. package/dist/handlers/run-endpoint.d.ts +27 -0
  9. package/dist/handlers/run-endpoint.d.ts.map +1 -0
  10. package/dist/handlers/run.d.ts +28 -0
  11. package/dist/handlers/run.d.ts.map +1 -0
  12. package/dist/handlers/search-docs.d.ts +22 -0
  13. package/dist/handlers/search-docs.d.ts.map +1 -0
  14. package/dist/{handlers-BE96O-uy.js → handlers-Ca2x4dM8.js} +1050 -11
  15. package/dist/handlers-Ca2x4dM8.js.map +1 -0
  16. package/dist/handlers.js +1 -1
  17. package/dist/{http-DF9K8Fr4.js → http-xNZOdN_t.js} +72 -4
  18. package/dist/http-xNZOdN_t.js.map +1 -0
  19. package/dist/http.d.ts.map +1 -1
  20. package/dist/http.js +1 -1
  21. package/dist/index.js +2 -2
  22. package/dist/oauth.js +1 -1
  23. package/dist/run/bridge.d.ts +53 -0
  24. package/dist/run/bridge.d.ts.map +1 -0
  25. package/dist/run/docs.d.ts +23 -0
  26. package/dist/run/docs.d.ts.map +1 -0
  27. package/dist/run/engine.d.ts +60 -0
  28. package/dist/run/engine.d.ts.map +1 -0
  29. package/dist/run/limits.d.ts +33 -0
  30. package/dist/run/limits.d.ts.map +1 -0
  31. package/dist/run/prelude.d.ts +27 -0
  32. package/dist/run/prelude.d.ts.map +1 -0
  33. package/dist/run/remote.d.ts +44 -0
  34. package/dist/run/remote.d.ts.map +1 -0
  35. package/dist/run/render.d.ts +30 -0
  36. package/dist/run/render.d.ts.map +1 -0
  37. package/dist/run/strip.d.ts +20 -0
  38. package/dist/run/strip.d.ts.map +1 -0
  39. package/dist/schema.d.ts +13 -2
  40. package/dist/schema.d.ts.map +1 -1
  41. package/dist/server.js +2 -2
  42. package/dist/{stdio-BnfO285Q.js → stdio-jqfOnE6-.js} +2 -2
  43. package/dist/{stdio-BnfO285Q.js.map → stdio-jqfOnE6-.js.map} +1 -1
  44. package/dist/stdio.js +1 -1
  45. package/dist/tools.d.ts.map +1 -1
  46. package/dist/tools.js +93 -4
  47. package/dist/tools.js.map +1 -1
  48. package/dist/{version-XQYsroYk.js → version-Jj_0Ypf8.js} +3 -3
  49. package/dist/{version-XQYsroYk.js.map → version-Jj_0Ypf8.js.map} +1 -1
  50. package/package.json +5 -3
  51. package/skills/SKILL.md +84 -1
  52. package/dist/handlers-BE96O-uy.js.map +0 -1
  53. package/dist/http-DF9K8Fr4.js.map +0 -1
package/dist/tools.js CHANGED
@@ -9,7 +9,7 @@ function generateDescription() {
9
9
  "Productive.io API.",
10
10
  `Resources: ${RESOURCES.join(", ")}.`,
11
11
  `Actions: ${ACTIONS.join(", ")} (varies by resource).`,
12
- "Discovery: action=help with any resource for filters, fields, examples. action=schema for compact machine-readable spec.",
12
+ "Discovery: action=help with a resource for filters/fields/examples, action=help with query to search across resources, action=schema for a compact spec. Or use the search_docs tool to discover across resources, raw endpoints, and scripting.",
13
13
  "Filters: filter:{key:value}. Common: project_id, person_id, after/before (YYYY-MM-DD).",
14
14
  "Includes: include:[...] for related data (e.g. [\"project\",\"assignee\"]).",
15
15
  "Output: compact=false for full detail (default for get; list defaults true).",
@@ -160,7 +160,7 @@ var TOOLS = [
160
160
  },
161
161
  {
162
162
  name: "api_read",
163
- description: "Read-only raw Productive API access for documented GET endpoints. Supports describe=true for endpoint docs, filter/include/sort, and optional safe pagination.",
163
+ description: "Read-only raw Productive API access for documented GET endpoints. Use search=\"<term>\" to discover endpoints, describe=true for an endpoint spec, or path + filter/include/sort with optional safe pagination.",
164
164
  annotations: {
165
165
  title: "Productive API Read",
166
166
  readOnlyHint: true,
@@ -175,6 +175,10 @@ var TOOLS = [
175
175
  type: "string",
176
176
  description: "Relative Productive API path, e.g. /invoices"
177
177
  },
178
+ search: {
179
+ type: "string",
180
+ description: "Find documented endpoints by keyword (returns matching paths). Use instead of path."
181
+ },
178
182
  describe: {
179
183
  type: "boolean",
180
184
  description: "Return endpoint documentation instead of calling the API"
@@ -192,8 +196,7 @@ var TOOLS = [
192
196
  per_page: { type: "number" },
193
197
  paginate: { type: "boolean" },
194
198
  max_pages: { type: "number" }
195
- },
196
- required: ["path"]
199
+ }
197
200
  }
198
201
  },
199
202
  {
@@ -229,6 +232,92 @@ var TOOLS = [
229
232
  "confirm"
230
233
  ]
231
234
  }
235
+ },
236
+ {
237
+ name: "run_script",
238
+ description: "Run a sandboxed JavaScript/TypeScript script for advanced multi-step logic in one call. Globals: productive(resource, action, params), api.read/write, output.*, args, flags; `return` a value for the result. Call `search_docs` (e.g. query=\"output\", \"productive\") for the scripting API before writing a script. dry_run=true previews mutations. No direct network/filesystem access (API calls run host-side). Disabled by default, requires PRODUCTIVE_MCP_ENABLE_RUN=true.",
239
+ annotations: {
240
+ title: "Run Script",
241
+ readOnlyHint: false,
242
+ destructiveHint: false,
243
+ idempotentHint: false,
244
+ openWorldHint: true
245
+ },
246
+ inputSchema: {
247
+ type: "object",
248
+ properties: {
249
+ code: {
250
+ type: "string",
251
+ description: "JavaScript/TypeScript source to execute in the sandbox."
252
+ },
253
+ args: {
254
+ type: "array",
255
+ items: { type: "string" },
256
+ description: "Positional arguments exposed to the script as `args`."
257
+ },
258
+ flags: {
259
+ type: "object",
260
+ description: "Named values exposed to the script as `flags`."
261
+ },
262
+ dry_run: {
263
+ type: "boolean",
264
+ description: "Record mutating calls (create/update/delete/...) instead of executing them."
265
+ }
266
+ },
267
+ required: ["code"]
268
+ },
269
+ outputSchema: {
270
+ type: "object",
271
+ properties: {
272
+ result: { description: "The value the script returned (any JSON)." },
273
+ output: {
274
+ type: "array",
275
+ description: "Buffered output.* entries, in order.",
276
+ items: {
277
+ type: "object",
278
+ properties: {
279
+ type: { type: "string" },
280
+ data: {}
281
+ },
282
+ required: ["type"]
283
+ }
284
+ },
285
+ _run: {
286
+ type: "object",
287
+ description: "Run metadata.",
288
+ properties: {
289
+ apiCalls: { type: "number" },
290
+ dryRun: { type: "boolean" },
291
+ outputTruncated: { type: "boolean" },
292
+ recorded: { type: "array" }
293
+ },
294
+ required: ["apiCalls", "dryRun"]
295
+ }
296
+ },
297
+ required: [
298
+ "result",
299
+ "output",
300
+ "_run"
301
+ ]
302
+ }
303
+ },
304
+ {
305
+ name: "search_docs",
306
+ description: "Discover documentation across everything this server exposes — Productive resources, raw API endpoints, and the run_script scripting API. Call with no query for a table of contents, or a query (e.g. \"invoices\", \"billing\", \"output\") for ranked cross-domain matches: resources/endpoints point at the tool to drill in, run_script sections are returned in full. Good first call when unsure where to look.",
307
+ annotations: {
308
+ title: "Search Docs",
309
+ readOnlyHint: true,
310
+ destructiveHint: false,
311
+ idempotentHint: true,
312
+ openWorldHint: false
313
+ },
314
+ inputSchema: {
315
+ type: "object",
316
+ properties: { query: {
317
+ type: "string",
318
+ description: "Optional keyword to search across resources, endpoints, and scripting docs."
319
+ } }
320
+ }
232
321
  }
233
322
  ];
234
323
  /**
package/dist/tools.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tools.js","names":[],"sources":["../src/tools.ts"],"sourcesContent":["import type { Tool } from '@modelcontextprotocol/sdk/types.js';\n\nimport { RESOURCES, ACTIONS, REPORT_TYPES } from '@studiometa/productive-core';\n\n/**\n * Generate the tool description dynamically from the constants.\n * Adding a resource or action to constants.ts automatically updates this description.\n */\nfunction generateDescription(): string {\n return [\n 'Productive.io API.',\n `Resources: ${RESOURCES.join(', ')}.`,\n `Actions: ${ACTIONS.join(', ')} (varies by resource).`,\n 'Discovery: action=help with any resource for filters, fields, examples. action=schema for compact machine-readable spec.',\n 'Filters: filter:{key:value}. Common: project_id, person_id, after/before (YYYY-MM-DD).',\n 'Includes: include:[...] for related data (e.g. [\"project\",\"assignee\"]).',\n 'Output: compact=false for full detail (default for get; list defaults true).',\n 'Search: query for text search on list actions. Cross-resource: resource=search action=run with query searches projects, companies, people, tasks simultaneously.',\n 'Reports: resource=reports action=get with report_type, from, to.',\n 'Batch: resource=batch action=run with operations=[{resource,action,...}] executes up to 10 ops in parallel.',\n 'Rich context: action=context on tasks/projects/deals for full context in one call.',\n ].join('\\n');\n}\n\n/**\n * Single consolidated tool for Productive.io MCP server\n *\n * The resource/action/report_type enums and description are derived from\n * the shared constants in constants.ts — the single source of truth for both\n * the MCP tool definition and the Zod validation schemas.\n * Adding a new resource, action, or report type there automatically updates\n * both the tool exposed to clients and the validation layer.\n *\n * MCP Annotations (for MCP directory compliance):\n * - readOnlyHint: false - Tool can create/update data\n * - destructiveHint: false - Tool does not permanently delete data\n * - idempotentHint: false - Create actions are not idempotent\n * - openWorldHint: true - Tool interacts with external Productive.io API\n */\nexport const TOOLS: Tool[] = [\n {\n name: 'productive',\n description: generateDescription(),\n annotations: {\n title: 'Productive.io',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n inputSchema: {\n type: 'object',\n properties: {\n resource: {\n type: 'string',\n enum: [...RESOURCES],\n },\n action: {\n type: 'string',\n enum: [...ACTIONS],\n description: 'Use \"help\" for resource documentation',\n },\n id: { type: 'string' },\n filter: { type: 'object' },\n page: { type: 'number' },\n per_page: { type: 'number' },\n compact: {\n type: 'boolean',\n description: 'Compact output (default: true for list, false for get)',\n },\n include: {\n type: 'array',\n items: { type: 'string' },\n description: 'Related data to include (e.g. [\"project\",\"assignee\"])',\n },\n query: { type: 'string', description: 'Text search for list actions' },\n resources: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Resource types to search (for resource=search). Defaults to [projects, companies, people, tasks]. Valid values: projects, companies, people, tasks, deals.',\n },\n // Common fields\n person_id: { type: 'string' },\n service_id: { type: 'string' },\n task_id: { type: 'string' },\n company_id: { type: 'string' },\n time: { type: 'number' },\n date: { type: 'string' },\n note: { type: 'string' },\n // Task fields\n title: { type: 'string' },\n project_id: { type: 'string' },\n task_list_id: { type: 'string' },\n description: { type: 'string' },\n assignee_id: { type: 'string' },\n // Company fields\n name: { type: 'string' },\n // Page fields\n page_id: { type: 'string', description: 'Page ID (list pages to find)' },\n parent_page_id: { type: 'string', description: 'Parent page ID for sub-pages' },\n // Comment fields\n body: { type: 'string', description: 'Comment/page body content' },\n hidden: {\n type: 'boolean',\n description: 'Set to true to hide comment from client (comments only)',\n },\n deal_id: { type: 'string' },\n // Attachment fields\n comment_id: { type: 'string', description: 'Comment ID (for attachments)' },\n // Timer fields\n time_entry_id: { type: 'string' },\n // Booking fields\n started_on: { type: 'string', description: 'Booking date (YYYY-MM-DD)' },\n ended_on: { type: 'string', description: 'Booking end date (YYYY-MM-DD)' },\n event_id: { type: 'string' },\n // Report fields\n report_type: {\n type: 'string',\n enum: [...REPORT_TYPES],\n description: 'Required for resource=reports action=get',\n },\n group: { type: 'string', description: 'Report grouping: person, project, service' },\n from: { type: 'string', description: 'Report start (YYYY-MM-DD); filter.after for time' },\n to: { type: 'string', description: 'Report end (YYYY-MM-DD); filter.before for time' },\n status: { type: 'string' },\n // Batch fields\n operations: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n resource: { type: 'string' },\n action: { type: 'string' },\n },\n required: ['resource', 'action'],\n },\n maxItems: 10,\n description:\n 'Array of operations for batch execution (max 10). Each operation needs resource, action, and any additional params.',\n },\n },\n required: ['resource', 'action'],\n },\n },\n {\n name: 'api_read',\n description:\n 'Read-only raw Productive API access for documented GET endpoints. Supports describe=true for endpoint docs, filter/include/sort, and optional safe pagination.',\n annotations: {\n title: 'Productive API Read',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n inputSchema: {\n type: 'object',\n properties: {\n path: { type: 'string', description: 'Relative Productive API path, e.g. /invoices' },\n describe: {\n type: 'boolean',\n description: 'Return endpoint documentation instead of calling the API',\n },\n filter: { type: 'object' },\n include: { type: 'array', items: { type: 'string' } },\n sort: { type: 'array', items: { type: 'string' } },\n page: { type: 'number' },\n per_page: { type: 'number' },\n paginate: { type: 'boolean' },\n max_pages: { type: 'number' },\n },\n required: ['path'],\n },\n },\n {\n name: 'api_write',\n description:\n 'Gated raw Productive API write access for documented endpoints. Disabled by default, requires PRODUCTIVE_MCP_ENABLE_API_WRITE=true and confirm=true. Supports dry_run=true.',\n annotations: {\n title: 'Productive API Write',\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: false,\n openWorldHint: true,\n },\n inputSchema: {\n type: 'object',\n properties: {\n method: { type: 'string', enum: ['POST', 'PATCH', 'PUT', 'DELETE'] },\n path: { type: 'string' },\n body: { type: 'object' },\n confirm: { type: 'boolean' },\n dry_run: { type: 'boolean' },\n },\n required: ['method', 'path', 'confirm'],\n },\n },\n];\n\n/**\n * Additional tools only available in stdio mode (local execution)\n * These tools manage persistent configuration\n */\nexport const STDIO_ONLY_TOOLS: Tool[] = [\n {\n name: 'productive_configure',\n description: 'Configure Productive.io credentials',\n annotations: {\n title: 'Configure Productive',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n inputSchema: {\n type: 'object',\n properties: {\n organizationId: { type: 'string' },\n apiToken: { type: 'string' },\n userId: { type: 'string' },\n },\n required: ['organizationId', 'apiToken'],\n },\n },\n {\n name: 'productive_get_config',\n description: 'Get current configuration',\n annotations: {\n title: 'Get Productive Config',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n];\n"],"mappings":"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"}
1
+ {"version":3,"file":"tools.js","names":[],"sources":["../src/tools.ts"],"sourcesContent":["import type { Tool } from '@modelcontextprotocol/sdk/types.js';\n\nimport { RESOURCES, ACTIONS, REPORT_TYPES } from '@studiometa/productive-core';\n\n/**\n * Generate the tool description dynamically from the constants.\n * Adding a resource or action to constants.ts automatically updates this description.\n */\nfunction generateDescription(): string {\n return [\n 'Productive.io API.',\n `Resources: ${RESOURCES.join(', ')}.`,\n `Actions: ${ACTIONS.join(', ')} (varies by resource).`,\n 'Discovery: action=help with a resource for filters/fields/examples, action=help with query to search across resources, action=schema for a compact spec. Or use the search_docs tool to discover across resources, raw endpoints, and scripting.',\n 'Filters: filter:{key:value}. Common: project_id, person_id, after/before (YYYY-MM-DD).',\n 'Includes: include:[...] for related data (e.g. [\"project\",\"assignee\"]).',\n 'Output: compact=false for full detail (default for get; list defaults true).',\n 'Search: query for text search on list actions. Cross-resource: resource=search action=run with query searches projects, companies, people, tasks simultaneously.',\n 'Reports: resource=reports action=get with report_type, from, to.',\n 'Batch: resource=batch action=run with operations=[{resource,action,...}] executes up to 10 ops in parallel.',\n 'Rich context: action=context on tasks/projects/deals for full context in one call.',\n ].join('\\n');\n}\n\n/**\n * Single consolidated tool for Productive.io MCP server\n *\n * The resource/action/report_type enums and description are derived from\n * the shared constants in constants.ts — the single source of truth for both\n * the MCP tool definition and the Zod validation schemas.\n * Adding a new resource, action, or report type there automatically updates\n * both the tool exposed to clients and the validation layer.\n *\n * MCP Annotations (for MCP directory compliance):\n * - readOnlyHint: false - Tool can create/update data\n * - destructiveHint: false - Tool does not permanently delete data\n * - idempotentHint: false - Create actions are not idempotent\n * - openWorldHint: true - Tool interacts with external Productive.io API\n */\nexport const TOOLS: Tool[] = [\n {\n name: 'productive',\n description: generateDescription(),\n annotations: {\n title: 'Productive.io',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n inputSchema: {\n type: 'object',\n properties: {\n resource: {\n type: 'string',\n enum: [...RESOURCES],\n },\n action: {\n type: 'string',\n enum: [...ACTIONS],\n description: 'Use \"help\" for resource documentation',\n },\n id: { type: 'string' },\n filter: { type: 'object' },\n page: { type: 'number' },\n per_page: { type: 'number' },\n compact: {\n type: 'boolean',\n description: 'Compact output (default: true for list, false for get)',\n },\n include: {\n type: 'array',\n items: { type: 'string' },\n description: 'Related data to include (e.g. [\"project\",\"assignee\"])',\n },\n query: { type: 'string', description: 'Text search for list actions' },\n resources: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Resource types to search (for resource=search). Defaults to [projects, companies, people, tasks]. Valid values: projects, companies, people, tasks, deals.',\n },\n // Common fields\n person_id: { type: 'string' },\n service_id: { type: 'string' },\n task_id: { type: 'string' },\n company_id: { type: 'string' },\n time: { type: 'number' },\n date: { type: 'string' },\n note: { type: 'string' },\n // Task fields\n title: { type: 'string' },\n project_id: { type: 'string' },\n task_list_id: { type: 'string' },\n description: { type: 'string' },\n assignee_id: { type: 'string' },\n // Company fields\n name: { type: 'string' },\n // Page fields\n page_id: { type: 'string', description: 'Page ID (list pages to find)' },\n parent_page_id: { type: 'string', description: 'Parent page ID for sub-pages' },\n // Comment fields\n body: { type: 'string', description: 'Comment/page body content' },\n hidden: {\n type: 'boolean',\n description: 'Set to true to hide comment from client (comments only)',\n },\n deal_id: { type: 'string' },\n // Attachment fields\n comment_id: { type: 'string', description: 'Comment ID (for attachments)' },\n // Timer fields\n time_entry_id: { type: 'string' },\n // Booking fields\n started_on: { type: 'string', description: 'Booking date (YYYY-MM-DD)' },\n ended_on: { type: 'string', description: 'Booking end date (YYYY-MM-DD)' },\n event_id: { type: 'string' },\n // Report fields\n report_type: {\n type: 'string',\n enum: [...REPORT_TYPES],\n description: 'Required for resource=reports action=get',\n },\n group: { type: 'string', description: 'Report grouping: person, project, service' },\n from: { type: 'string', description: 'Report start (YYYY-MM-DD); filter.after for time' },\n to: { type: 'string', description: 'Report end (YYYY-MM-DD); filter.before for time' },\n status: { type: 'string' },\n // Batch fields\n operations: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n resource: { type: 'string' },\n action: { type: 'string' },\n },\n required: ['resource', 'action'],\n },\n maxItems: 10,\n description:\n 'Array of operations for batch execution (max 10). Each operation needs resource, action, and any additional params.',\n },\n },\n required: ['resource', 'action'],\n },\n },\n {\n name: 'api_read',\n description:\n 'Read-only raw Productive API access for documented GET endpoints. Use search=\"<term>\" to discover endpoints, describe=true for an endpoint spec, or path + filter/include/sort with optional safe pagination.',\n annotations: {\n title: 'Productive API Read',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n inputSchema: {\n type: 'object',\n properties: {\n path: { type: 'string', description: 'Relative Productive API path, e.g. /invoices' },\n search: {\n type: 'string',\n description:\n 'Find documented endpoints by keyword (returns matching paths). Use instead of path.',\n },\n describe: {\n type: 'boolean',\n description: 'Return endpoint documentation instead of calling the API',\n },\n filter: { type: 'object' },\n include: { type: 'array', items: { type: 'string' } },\n sort: { type: 'array', items: { type: 'string' } },\n page: { type: 'number' },\n per_page: { type: 'number' },\n paginate: { type: 'boolean' },\n max_pages: { type: 'number' },\n },\n },\n },\n {\n name: 'api_write',\n description:\n 'Gated raw Productive API write access for documented endpoints. Disabled by default, requires PRODUCTIVE_MCP_ENABLE_API_WRITE=true and confirm=true. Supports dry_run=true.',\n annotations: {\n title: 'Productive API Write',\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: false,\n openWorldHint: true,\n },\n inputSchema: {\n type: 'object',\n properties: {\n method: { type: 'string', enum: ['POST', 'PATCH', 'PUT', 'DELETE'] },\n path: { type: 'string' },\n body: { type: 'object' },\n confirm: { type: 'boolean' },\n dry_run: { type: 'boolean' },\n },\n required: ['method', 'path', 'confirm'],\n },\n },\n {\n name: 'run_script',\n description:\n 'Run a sandboxed JavaScript/TypeScript script for advanced multi-step logic in one call. ' +\n 'Globals: productive(resource, action, params), api.read/write, output.*, args, flags; `return` a value for the result. ' +\n 'Call `search_docs` (e.g. query=\"output\", \"productive\") for the scripting API before writing a script. ' +\n 'dry_run=true previews mutations. No direct network/filesystem access (API calls run host-side). ' +\n 'Disabled by default, requires PRODUCTIVE_MCP_ENABLE_RUN=true.',\n annotations: {\n title: 'Run Script',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n inputSchema: {\n type: 'object',\n properties: {\n code: {\n type: 'string',\n description: 'JavaScript/TypeScript source to execute in the sandbox.',\n },\n args: {\n type: 'array',\n items: { type: 'string' },\n description: 'Positional arguments exposed to the script as `args`.',\n },\n flags: {\n type: 'object',\n description: 'Named values exposed to the script as `flags`.',\n },\n dry_run: {\n type: 'boolean',\n description:\n 'Record mutating calls (create/update/delete/...) instead of executing them.',\n },\n },\n required: ['code'],\n },\n outputSchema: {\n type: 'object',\n properties: {\n result: { description: 'The value the script returned (any JSON).' },\n output: {\n type: 'array',\n description: 'Buffered output.* entries, in order.',\n items: {\n type: 'object',\n properties: { type: { type: 'string' }, data: {} },\n required: ['type'],\n },\n },\n _run: {\n type: 'object',\n description: 'Run metadata.',\n properties: {\n apiCalls: { type: 'number' },\n dryRun: { type: 'boolean' },\n outputTruncated: { type: 'boolean' },\n recorded: { type: 'array' },\n },\n required: ['apiCalls', 'dryRun'],\n },\n },\n required: ['result', 'output', '_run'],\n },\n },\n {\n name: 'search_docs',\n description:\n 'Discover documentation across everything this server exposes — Productive resources, raw API ' +\n 'endpoints, and the run_script scripting API. Call with no query for a table of contents, or a query ' +\n '(e.g. \"invoices\", \"billing\", \"output\") for ranked cross-domain matches: resources/endpoints point at ' +\n 'the tool to drill in, run_script sections are returned in full. Good first call when unsure where to look.',\n annotations: {\n title: 'Search Docs',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n inputSchema: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description:\n 'Optional keyword to search across resources, endpoints, and scripting docs.',\n },\n },\n },\n },\n];\n\n/**\n * Additional tools only available in stdio mode (local execution)\n * These tools manage persistent configuration\n */\nexport const STDIO_ONLY_TOOLS: Tool[] = [\n {\n name: 'productive_configure',\n description: 'Configure Productive.io credentials',\n annotations: {\n title: 'Configure Productive',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n inputSchema: {\n type: 'object',\n properties: {\n organizationId: { type: 'string' },\n apiToken: { type: 'string' },\n userId: { type: 'string' },\n },\n required: ['organizationId', 'apiToken'],\n },\n },\n {\n name: 'productive_get_config',\n description: 'Get current configuration',\n annotations: {\n title: 'Get Productive Config',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n];\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { C as handleSchemaOverview, E as handleDeals, S as handleServices, T as handlePeople, b as handleTasks, w as handleProjects, x as handleSummaries } from "./handlers-BE96O-uy.js";
1
+ import { C as handleSummaries, D as handlePeople, E as handleProjects, O as handleDeals, S as handleTasks, T as handleSchemaOverview, w as handleServices } from "./handlers-Ca2x4dM8.js";
2
2
  import { ProductiveApi } from "@studiometa/productive-api";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { dirname, join } from "node:path";
@@ -235,8 +235,8 @@ async function readResource(uri, credentials) {
235
235
  }
236
236
  //#endregion
237
237
  //#region src/version.ts
238
- var VERSION = "0.10.14";
238
+ var VERSION = "0.10.16";
239
239
  //#endregion
240
240
  export { INSTRUCTIONS as a, readResource as i, listResourceTemplates as n, listResources as r, VERSION as t };
241
241
 
242
- //# sourceMappingURL=version-XQYsroYk.js.map
242
+ //# sourceMappingURL=version-Jj_0Ypf8.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version-XQYsroYk.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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"}
1
+ {"version":3,"file":"version-Jj_0Ypf8.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":";;;;;;;;;;;;;;;;AAcA,IAAM,YAAY,QAAQ,cAAc,OAAO,KAAK,GAAG,CAAC;;;;;AAMxD,SAAS,mBAA2B;CAClC,IAAI;EAQF,OALgB,aADE,KAAK,WAAW,MAAM,UAAU,UACrB,GAAW,OAGb,EAAQ,QAAQ,0BAA0B,EAE9D,EAAmB,KAAK;CACjC,QAAQ;EAEN,OAAO;CACT;AACF;AAEA,IAAa,eAAe,iBAAiB;;;;ACK7C,IAAM,YAAY;;;;AA2ClB,IAAa,mBAAqC,CAChD;CACE,KAAK;CACL,MAAM;CACN,aAAa;CACb,UAAU;AACZ,GACA;CACE,KAAK;CACL,MAAM;CACN,aAAa;CACb,UAAU;AACZ,CACF;;;;AASA,IAAa,oBAAsC,CACjD;CACE,KAAK;CACL,MAAM;CACN,aAAa;CACb,UAAU;AACZ,GACA;CACE,KAAK;CACL,MAAM;CACN,aAAa;CACb,UAAU;AACZ,CACF;;;;AASA,IAAa,qBAAyC;CACpD;EACE,aAAa;EACb,MAAM;EACN,aAAa;EACb,UAAU;CACZ;CACA;EACE,aAAa;EACb,MAAM;EACN,aAAa;EACb,UAAU;CACZ;CACA;EACE,aAAa;EACb,MAAM;EACN,aAAa;EACb,UAAU;CACZ;CACA;EACE,aAAa;EACb,MAAM;EACN,aAAa;EACb,UAAU;CACZ;CACA;EACE,aAAa;EACb,MAAM;EACN,aAAa;EACb,UAAU;CACZ;CACA;EACE,aAAa;EACb,MAAM;EACN,aAAa;EACb,UAAU;CACZ;AACF;;AAOA,IAAM,eAGD;CAEH;EACE,SAAS;EACT,SAAS,YAAY;GAEnB,MAAM,OADS,qBACF,EAAO,QAAQ;GAC5B,OAAO,KAAK,MAAM,KAAK,IAAI;EAC7B;CACF;CACA;EACE,SAAS;EACT,SAAS,aAAa,EAAE,cAAc,aAAa;CACrD;CAGA;EACE,SAAS;EACT,SAAS,OAAO,GAAG,gBAAgB;GAGjC,OAAO,sBAAsB,MADR,gBAAgB,UAAU,CAAC,GADpC,oBAAoB,WACmB,CAAG,CACnB;EACrC;CACF;CACA;EACE,SAAS;EACT,SAAS,OAAO,GAAG,gBAAgB;GAGjC,OAAO,sBAAsB,MADR,gBAAgB,cAAc,CAAC,GADxC,oBAAoB,WACuB,CAAG,CACvB;EACrC;CACF;CAGA;EACE,SAAS;EACT,SAAS,OAAO,GAAG,KAAK,gBAAgB;GACtC,MAAM,MAAM,oBAAoB,aAAa,EAAE,QAAQ,EAAE,YAAY,GAAG,EAAE,CAAC;GAE3E,OAAO,sBAAsB,MADR,YAAY,QAAQ,EAAE,YAAY,GAAG,GAAG,GAAG,CAC7B;EACrC;CACF;CACA;EACE,SAAS;EACT,SAAS,OAAO,GAAG,KAAK,gBAAgB;GAItC,OAAO,sBAAsB,MADR,eAAe,QAAQ,CAAC,GAFjC,oBAAoB,aAAa,EAAE,QAAQ,EAAE,YAAY,GAAG,EAAE,CAE1B,CAAG,CAChB;EACrC;CACF;CAGA;EACE,SAAS;EACT,SAAS,OAAO,GAAG,KAAK,gBAAgB;GACtC,MAAM,MAAM,oBAAoB,WAAW;GAE3C,OAAO,sBAAsB,MADR,eAAe,OAAO,EAAE,GAAG,GAAG,GAAG,CACnB;EACrC;CACF;CACA;EACE,SAAS;EACT,SAAS,OAAO,GAAG,KAAK,gBAAgB;GACtC,MAAM,MAAM,oBAAoB,WAAW;GAE3C,OAAO,sBAAsB,MADR,YAAY,OAAO,EAAE,GAAG,GAAG,GAAG,CAChB;EACrC;CACF;CACA;EACE,SAAS;EACT,SAAS,OAAO,GAAG,KAAK,gBAAgB;GACtC,MAAM,MAAM,oBAAoB,WAAW;GAE3C,OAAO,sBAAsB,MADR,aAAa,OAAO,EAAE,GAAG,GAAG,KAAK,WAAW,CAC9B;EACrC;CACF;CACA;EACE,SAAS;EACT,SAAS,OAAO,GAAG,KAAK,gBAAgB;GACtC,MAAM,MAAM,oBAAoB,WAAW;GAE3C,OAAO,sBAAsB,MADR,YAAY,OAAO,EAAE,GAAG,GAAG,GAAG,CAChB;EACrC;CACF;AACF;;;;AASA,SAAS,oBACP,aACA,YAAiD,CAAC,GAClC;CAUhB,MAAM,UAAU,mBAAmB,EAAE,KAAA,IATrB,cAAc,EAC5B,QAAQ;EACN,UAAU,YAAY;EACtB,gBAAgB,YAAY;EAC5B,QAAQ,YAAY;EACpB,SAAS,QAAQ,IAAI;CACvB,EACF,CAEqC,EAAI,GAAG,EAAE,QAAQ,YAAY,OAAO,CAAC;CAE1E,OAAO;EACL,eAAe,EAAE,SAAS,MAAM;EAChC,QAAQ,UAAU;EAClB,SAAS;EACT,cAAc;EACd,oBAAoB;EACpB,gBAAgB;CAClB;AACF;;;;;AAMA,SAAS,sBAAsB,QAA4D;CACzF,IAAI,OAAO,SAAS;EAClB,MAAM,OAAQ,OAAO,QAAQ,GAAwB;EACrD,MAAM,IAAI,MAAM,IAAI;CACtB;CACA,MAAM,OAAQ,OAAO,QAAQ,GAAwB;CACrD,IAAI;EACF,OAAO,KAAK,MAAM,IAAI;CACxB,QAAQ;EACN,OAAO,EAAE,KAAK;CAChB;AACF;;;;AASA,SAAgB,gBAAkC;CAChD,OAAO,CAAC,GAAG,kBAAkB,GAAG,iBAAiB;AACnD;;;;AAKA,SAAgB,wBAA4C;CAC1D,OAAO;AACT;;;;;;AAOA,eAAsB,aACpB,KACA,aAC6B;CAC7B,KAAK,MAAM,EAAE,SAAS,aAAa,cAAc;EAC/C,MAAM,QAAQ,IAAI,MAAM,OAAO;EAC/B,IAAI,OAAO;GACT,MAAM,OAAO,MAAM,QAAQ,OAAO,WAAW;GAC7C,OAAO,EACL,UAAU,CACR;IACE;IACA,UAAU;IACV,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC;GACpC,CACF,EACF;EACF;CACF;CAEA,MAAM,IAAI,MACR,yBAAyB,IAAI,gCACI,iBAAiB,KAAK,MAAM,EAAE,GAAG,EAAE,KAAK,IAAI,EAAE,iCAC7C,kBAAkB,KAAK,MAAM,EAAE,GAAG,EAAE,KAAK,IAAI,EAAE,wBACxD,mBAAmB,KAAK,MAAM,EAAE,WAAW,EAAE,KAAK,IAAI,EAAE,EACnF;AACF;;;ACpWA,IAAa,UAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studiometa/productive-mcp",
3
- "version": "0.10.14",
3
+ "version": "0.10.16",
4
4
  "description": "MCP server for Productive.io API - Model Context Protocol integration for Claude Desktop",
5
5
  "keywords": [
6
6
  "ai",
@@ -81,10 +81,12 @@
81
81
  "typecheck": "tsgo --noEmit"
82
82
  },
83
83
  "dependencies": {
84
+ "@jitl/quickjs-singlefile-cjs-release-sync": "^0.32.0",
84
85
  "@modelcontextprotocol/sdk": "^1.29.0",
85
- "@studiometa/productive-api": "0.10.14",
86
- "@studiometa/productive-core": "0.10.14",
86
+ "@studiometa/productive-api": "0.10.16",
87
+ "@studiometa/productive-core": "0.10.16",
87
88
  "h3": "^2.0.1-rc.22",
89
+ "quickjs-emscripten-core": "^0.32.0",
88
90
  "zod": "4.3.6"
89
91
  },
90
92
  "devDependencies": {
package/skills/SKILL.md CHANGED
@@ -11,9 +11,18 @@ MCP (Model Context Protocol) server for Productive.io. Provides a single unified
11
11
 
12
12
  Before your first interaction with any resource, call `action=help` with that resource to discover valid filters, required fields, includes, and examples.
13
13
 
14
+ ## Discovering documentation
15
+
16
+ There are several ways to find docs — use whichever fits:
17
+
18
+ - **`search_docs`** — the global front door. No query returns a table of contents across all domains; a `query` (e.g. `"invoices"`, `"billing"`) returns ranked matches across **resources**, **raw API endpoints**, and the **run_script scripting API**, each pointing at the tool to drill in. Best when you don't know where to look.
19
+ - **`productive` `action=help`** — per resource (filters, fields, includes, examples), or with a `query` to search across all resources. `action=schema` for a compact spec.
20
+ - **`api_read` `search="<term>"`** — find documented raw endpoints by keyword; then `describe=true` with a path for its full spec.
21
+ - The **scripting API** (run_script globals, output, limits) is part of `search_docs` too — it appears in the table of contents and a matching query (e.g. `"output"`, `"productive"`) returns the full section.
22
+
14
23
  ## MCP Tools
15
24
 
16
- This server exposes one high-level tool and two low-level raw API tools.
25
+ This server exposes one high-level tool, two low-level raw API tools, a sandboxed scripting tool (`run_script`), and a documentation-discovery tool (`search_docs`).
17
26
 
18
27
  ### `productive`
19
28
 
@@ -58,6 +67,80 @@ Use `action: "help"` to get detailed documentation for any resource:
58
67
 
59
68
  Returns filters, fields, includes, and examples for that resource.
60
69
 
70
+ ## Running Scripts (`run_script`)
71
+
72
+ For advanced multi-step logic — fetching across resources, filtering, aggregating, or
73
+ conditional flows — use `run_script` instead of many individual calls. It runs a
74
+ JavaScript/TypeScript script in a sandbox and returns the value the script returns plus any
75
+ buffered output, all in a single tool call.
76
+
77
+ The sandbox has **no direct network or filesystem access** — scripts cannot open sockets, call
78
+ `fetch`, reach arbitrary URLs, or read files. Productive API access **is** available: calls made
79
+ through the injected `productive`/`api` client are executed on the host (which performs the real
80
+ HTTP request) and go through the same validated, rate-limited pipeline as normal tool calls. This
81
+ keeps credentials out of the sandbox and constrains egress to the Productive API.
82
+
83
+ **Disabled by default** — the server operator must set `PRODUCTIVE_MCP_ENABLE_RUN=true`.
84
+
85
+ Discover the scripting API through **`search_docs`** — it lists the run_script topics in its table of contents, and a matching `query` (e.g. `"output"`, `"productive"`, `"dry_run"`) returns the full section (globals, resources, output rendering, limits, examples).
86
+
87
+ ### Parameters
88
+
89
+ | Parameter | Type | Description |
90
+ | --------- | ------- | -------------------------------------------------------------------- |
91
+ | `code` | string | **Required**. JavaScript/TypeScript source to run. |
92
+ | `args` | array | Positional strings exposed to the script as `args`. |
93
+ | `flags` | object | Named values exposed to the script as `flags`. |
94
+ | `dry_run` | boolean | Record mutating calls (create/update/delete/…) instead of executing. |
95
+
96
+ ### Globals available inside a script
97
+
98
+ - `productive(resource, action, params)` — low-level call, mirrors the `productive` tool.
99
+ - `productive.<resource>.list(filter?, opts?)`, `.get(id, opts?)`, `.create(params)`, `.update(id, params)` — convenience accessors for data resources.
100
+ - `api.read(path, opts?)` / `api.write(method, path, body)` — raw API access.
101
+ - `output.json(data)`, `output.table(rows)`, `output.csv(rows)`, `output.log(...)`, `output.info/warn/error/success(msg)` — buffered output returned alongside the result.
102
+ - `args` (string[]) and `flags` (object) — the inputs above.
103
+ - `return <value>` to surface a result. `await` is supported.
104
+
105
+ Imports/`require` are not available — use the injected globals only.
106
+
107
+ ### Example
108
+
109
+ ```json
110
+ {
111
+ "name": "run_script",
112
+ "arguments": {
113
+ "code": "const projects = await productive.projects.list();\nconst tasks = await productive.tasks.list({ status: 'open' });\noutput.json({ projects: projects.length, openTasks: tasks.length });\nreturn 'summary ready';"
114
+ }
115
+ }
116
+ ```
117
+
118
+ The response carries both forms (per the MCP structured-output convention): machine-readable
119
+ `structuredContent` as `{ result, output, _run: { apiCalls, dryRun } }`, and a human-readable
120
+ Markdown rendering in the text block — `output.table(rows)` becomes a Markdown table,
121
+ `output.csv`/`output.json` become fenced code blocks, log lines are labelled, and the returned
122
+ value appears under **Result**. With `dry_run: true`, mutating calls are not executed and are
123
+ listed under `_run.recorded`.
124
+
125
+ ### Limits (operator-configurable)
126
+
127
+ `PRODUCTIVE_MCP_RUN_TIMEOUT_MS` (5000), `PRODUCTIVE_MCP_RUN_MEMORY_MB` (64),
128
+ `PRODUCTIVE_MCP_RUN_MAX_API_CALLS` (50), `PRODUCTIVE_MCP_RUN_MAX_OUTPUT_KB` (256),
129
+ `PRODUCTIVE_MCP_RUN_MAX_CODE_KB` (128).
130
+
131
+ ### Remote runner (operator-configurable)
132
+
133
+ To isolate script execution (memory/CPU/OOM) from the main server, the front can
134
+ delegate `run_script` to a separate runner over one stateless HTTP POST — agents see no
135
+ difference. Set on the **front**: `PRODUCTIVE_MCP_RUN_RUNNER_URL` (the runner's `/run`
136
+ endpoint; its presence enables `run_script` without the local `ENABLE_RUN` flag),
137
+ `PRODUCTIVE_MCP_RUN_RUNNER_TOKEN` (shared secret), and optionally
138
+ `PRODUCTIVE_MCP_RUN_RUNNER_TIMEOUT_MS` (30000). On the **runner** (HTTP transport): set
139
+ `PRODUCTIVE_MCP_RUN_RUNNER_TOKEN` (activates the `/run` endpoint) and
140
+ `PRODUCTIVE_MCP_ENABLE_RUN=true`. The `/run` contract is stateless and must not be retried
141
+ at the proxy layer (a non-dry-run script may mutate), so any load balancer works in front
142
+ of a runner pool.
143
+
61
144
  ## Common Parameters
62
145
 
63
146
  | Parameter | Type | Description |