@studiometa/productive-mcp 0.10.17 → 0.10.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,6 +9,7 @@ MCP (Model Context Protocol) server for [Productive.io](https://productive.io).
9
9
  ## Features
10
10
 
11
11
  - Single unified `productive` tool — minimal token overhead (~170 tokens)
12
+ - Read-only `productive_read` sibling tool (`readOnlyHint: true`) for clients that only allow read-only tools
12
13
  - Smart ID resolution — use emails and project numbers instead of numeric IDs
13
14
  - Rich context — `action=context` fetches a resource with all related data in one call
14
15
  - Proactive suggestions — data-aware warnings (overdue tasks, long-running timers, etc.)
@@ -132,6 +133,17 @@ productive(resource, action, ...)
132
133
 
133
134
  Use `action="help"` with any resource for detailed documentation on available parameters and filters.
134
135
 
136
+ ### Read-only Tool
137
+
138
+ Some MCP clients only expose tools that have the `readOnlyHint: true` annotation. For these clients, the server also exposes `productive_read`.
139
+
140
+ | Tool | Description |
141
+ | ----------------- | ---------------------------------------------------------------------------------------- |
142
+ | `productive` | All resources and actions, reads and writes |
143
+ | `productive_read` | Same routing and read parameters as `productive`, but only read actions (`readOnlyHint`) |
144
+
145
+ `productive_read` rejects the actions that change data (`create`, `update`, `delete`, `start`, `stop`, `reopen`, `complete_task`, `log_day`, and `resolve` on `discussions`). It also rejects a `batch` that contains one of these actions. Use the `productive` tool for writes.
146
+
135
147
  ### Common Parameters
136
148
 
137
149
  | Parameter | Type | Description |
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,OAAO,KAAK,EAAkB,UAAU,EAAE,MAAM,YAAY,CAAC;AA0C7D,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA8I7C;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,WAAW,EAAE,qBAAqB,GACjC,OAAO,CAAC,UAAU,CAAC,CAuMrB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,OAAO,KAAK,EAAkB,UAAU,EAAE,MAAM,YAAY,CAAC;AA0C7D,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAkK7C;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,WAAW,EAAE,qBAAqB,GACjC,OAAO,CAAC,UAAU,CAAC,CAqNrB"}
@@ -1,5 +1,5 @@
1
1
  import { ProductiveApi, formatActivity, formatAttachment, formatBooking, formatComment, formatCompany, formatCustomField, formatDeal, formatDiscussion, formatListResponse, formatPage, formatPerson, formatProject, formatService, formatTask, formatTimeEntry, formatTimer } from "@studiometa/productive-api";
2
- import { ACTIONS, REPORT_TYPES, RESOURCES, ResolveError, VALID_REPORT_TYPES, completeTask, createBooking, createComment, createCompany, createDeal, createDiscussion, createPage, createTask, createTimeEntry, deleteAttachment, deleteDiscussion, deletePage, deleteTimeEntry, fromHandlerContext, getAttachment, getBooking, getComment, getCompany, getCustomField, getDeal, getDealContext, getDiscussion, getMyDaySummary, getPage, getPerson, getProject, getProjectContext, getProjectHealthSummary, getReport, getService, getTask, getTaskContext, getTeamPulseSummary, getTimeEntry, getTimer, listActivities, listAttachments, listBookings, listComments, listCompanies, listCustomFields, listDeals, listDiscussions, listPages, listPeople, listProjects, listServices, listTasks, listTimeEntries, listTimers, logDay, readApi, reopenDiscussion, resolveDiscussion, resolveResource, startTimer, stopTimer, updateBooking, updateComment, updateCompany, updateDeal, updateDiscussion, updatePage, updateTask, updateTimeEntry, weeklyStandup, writeApi } from "@studiometa/productive-core";
2
+ import { ACTIONS, REPORT_TYPES, RESOURCES, ResolveError, VALID_REPORT_TYPES, completeTask, createBooking, createComment, createCompany, createDeal, createDiscussion, createPage, createTask, createTimeEntry, deleteAttachment, deleteDiscussion, deletePage, deleteTimeEntry, fromHandlerContext, getAttachment, getBooking, getComment, getCompany, getCustomField, getDeal, getDealContext, getDiscussion, getMyDaySummary, getPage, getPerson, getProject, getProjectContext, getProjectHealthSummary, getReport, getService, getTask, getTaskContext, getTeamPulseSummary, getTimeEntry, getTimer, isReadCall, listActivities, listAttachments, listBookings, listComments, listCompanies, listCustomFields, listDeals, listDiscussions, listPages, listPeople, listProjects, listServices, listTasks, listTimeEntries, listTimers, logDay, readApi, reopenDiscussion, resolveDiscussion, resolveResource, startTimer, stopTimer, updateBooking, updateComment, updateCompany, updateDeal, updateDiscussion, updatePage, updateTask, updateTimeEntry, weeklyStandup, writeApi } from "@studiometa/productive-core";
3
3
  import variant from "@jitl/quickjs-singlefile-cjs-release-sync";
4
4
  import { newQuickJSWASMModuleFromVariant } from "quickjs-emscripten-core";
5
5
  import { timingSafeEqual } from "node:crypto";
@@ -40870,21 +40870,27 @@ async function handleReports(action, args, ctx) {
40870
40870
  }
40871
40871
  //#endregion
40872
40872
  //#region src/run/bridge.ts
40873
- /** Productive actions that mutate data — intercepted in dry-run mode. */
40874
- var MUTATING_ACTIONS = new Set([
40875
- "create",
40876
- "update",
40877
- "delete",
40878
- "start",
40879
- "stop",
40880
- "reopen",
40881
- "complete_task",
40882
- "log_day"
40883
- ]);
40884
- /** Whether a call would mutate data (used for dry-run classification). */
40873
+ /**
40874
+ * Host-side capability bridge for sandboxed scripts.
40875
+ *
40876
+ * The QuickJS sandbox has no direct network, filesystem, or process access.
40877
+ * The only way a script reaches the Productive API is through this bridge,
40878
+ * which re-enters the exact same `executeToolWithCredentials` pipeline used by
40879
+ * every other MCP tool call — the real HTTP request happens here, on the host. That means scripts automatically inherit credential scoping, rate
40880
+ * limiting, include validation, and formatting — and egress is constrained to
40881
+ * the Productive API by construction.
40882
+ *
40883
+ * The bridge also enforces the per-run API-call budget, honours the run's
40884
+ * abort signal (wall-clock timeout), and implements dry-run by classifying
40885
+ * mutating operations and recording them instead of executing.
40886
+ */
40887
+ /**
40888
+ * Whether a call may mutate data (used for dry-run classification). Fails
40889
+ * closed: a `productive` call that is not a known read is recorded.
40890
+ */
40885
40891
  function isMutating(channel, payload) {
40886
40892
  if (channel === "api_write") return true;
40887
- if (channel === "productive") return MUTATING_ACTIONS.has(String(payload.action));
40893
+ if (channel === "productive") return !isReadCall(payload.resource, payload.action);
40888
40894
  return false;
40889
40895
  }
40890
40896
  /** Map a channel to its tool name. */
@@ -42268,6 +42274,20 @@ async function routeToHandler(resource, action, restArgs, resolveArgs, ctx, cred
42268
42274
  }
42269
42275
  }
42270
42276
  /**
42277
+ * Find the first call in a `productive_read` request that is not a read,
42278
+ * including the operations of a batch (recursively, since a batch operation
42279
+ * may itself be a batch). This is an allowlist (see `isReadCall`): unknown or
42280
+ * non-string actions are rejected too. Returns a label for the rejected
42281
+ * action, or undefined when the whole request only reads.
42282
+ */
42283
+ function findNonReadAction(args) {
42284
+ if (!isReadCall(args.resource, args.action)) return JSON.stringify(args.action) ?? "undefined";
42285
+ if (args.resource === "batch" && Array.isArray(args.operations)) for (const op of args.operations) {
42286
+ const found = findNonReadAction(op && typeof op === "object" ? op : {});
42287
+ if (found) return found;
42288
+ }
42289
+ }
42290
+ /**
42271
42291
  * Execute a tool with the given credentials and arguments
42272
42292
  */
42273
42293
  async function executeToolWithCredentials(name, args, credentials) {
@@ -42311,6 +42331,11 @@ async function executeToolWithCredentials(name, args, credentials) {
42311
42331
  if (!parsed.success) return inputErrorResult(new UserInputError(formatValidationErrors(parsed.error)));
42312
42332
  return handleRunScript(parsed.data, credentials, executeToolWithCredentials);
42313
42333
  }
42334
+ if (name === "productive_read") {
42335
+ const rejectedAction = findNonReadAction(args);
42336
+ if (rejectedAction) return inputErrorResult(new UserInputError(`Action ${rejectedAction} is not allowed on the read-only tool`, ["productive_read only supports read actions (list, get, resolve, context, help, schema, ...)", `Use the \`productive\` tool to run ${rejectedAction}`]));
42337
+ return executeToolWithCredentials("productive", args, credentials);
42338
+ }
42314
42339
  if (name !== "productive") return errorResult(`Unknown tool: ${name}`);
42315
42340
  const typedArgs = args;
42316
42341
  if (typedArgs.resource === "batch") return handleBatch(typedArgs.operations, credentials, executeToolWithCredentials);
@@ -42379,4 +42404,4 @@ async function executeToolWithCredentials(name, args, credentials) {
42379
42404
  //#endregion
42380
42405
  export { handleSummaries as C, handlePeople as D, handleProjects as E, handleDeals as O, handleTasks as S, handleSchemaOverview as T, record as _, _null as a, unknown as b, custom as c, literal as d, looseObject as f, preprocess as g, optional as h, _enum as i, discriminatedUnion as l, object as m, resolveRunnerConfig as n, array as o, number as p, runnerTokenMatches as r, boolean as s, executeToolWithCredentials as t, intersection as u, string as v, handleServices as w, datetime as x, union as y };
42381
42406
 
42382
- //# sourceMappingURL=handlers-Dk4AQQ3K.js.map
42407
+ //# sourceMappingURL=handlers-DEpVjmEe.js.map