@studiometa/productive-mcp 0.10.15 → 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.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * `/run` HTTP endpoint — the runner side of remote `run_script` execution.
3
+ *
4
+ * A front server (with `PRODUCTIVE_MCP_RUN_RUNNER_URL` set) POSTs a stateless
5
+ * payload here; this endpoint authenticates the hop with a shared token and
6
+ * runs the script through the normal `run_script` pipeline (so the runner
7
+ * enforces its own `PRODUCTIVE_MCP_ENABLE_RUN` and limits). The result is the
8
+ * exact `ToolResult` the front relays back to the client.
9
+ *
10
+ * The endpoint only exists when `PRODUCTIVE_MCP_RUN_RUNNER_TOKEN` is set —
11
+ * otherwise it reports as not found, so a normal deployment never exposes it.
12
+ */
13
+ import type { ProductiveCredentials } from '../auth.js';
14
+ import type { ToolResult } from './types.js';
15
+ /** Executor signature (executeToolWithCredentials), injected for testability. */
16
+ export type RunEndpointExecutor = (name: string, args: Record<string, unknown>, credentials: ProductiveCredentials) => Promise<ToolResult>;
17
+ export interface RunEndpointResult {
18
+ status: number;
19
+ body: unknown;
20
+ }
21
+ /**
22
+ * Core logic for the `/run` endpoint, decoupled from the HTTP framework.
23
+ *
24
+ * @returns the HTTP status and JSON body to send back.
25
+ */
26
+ export declare function executeRunRequest(rawBody: unknown, authHeader: string | null | undefined, exec: RunEndpointExecutor, env?: NodeJS.ProcessEnv): Promise<RunEndpointResult>;
27
+ //# sourceMappingURL=run-endpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-endpoint.d.ts","sourceRoot":"","sources":["../../src/handlers/run-endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C,iFAAiF;AACjF,MAAM,MAAM,mBAAmB,GAAG,CAChC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,WAAW,EAAE,qBAAqB,KAC/B,OAAO,CAAC,UAAU,CAAC,CAAC;AAEzB,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACf;AAoBD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACrC,IAAI,EAAE,mBAAmB,EACzB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAAC,iBAAiB,CAAC,CAyC5B"}
@@ -17,8 +17,12 @@ export interface RunScriptArgs {
17
17
  flags?: unknown;
18
18
  dry_run?: unknown;
19
19
  }
20
+ /** Optional injected dependencies (for testing the remote path). */
21
+ export interface RunScriptDeps {
22
+ fetch?: typeof fetch;
23
+ }
20
24
  /**
21
25
  * Execute the `run_script` tool.
22
26
  */
23
- export declare function handleRunScript(rawArgs: RunScriptArgs, credentials: ProductiveCredentials, exec: ToolExecutor, env?: NodeJS.ProcessEnv): Promise<ToolResult>;
27
+ export declare function handleRunScript(rawArgs: RunScriptArgs, credentials: ProductiveCredentials, exec: ToolExecutor, env?: NodeJS.ProcessEnv, deps?: RunScriptDeps): Promise<ToolResult>;
24
28
  //# sourceMappingURL=run.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/handlers/run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAU7C,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAgBD;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,aAAa,EACtB,WAAW,EAAE,qBAAqB,EAClC,IAAI,EAAE,YAAY,EAClB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAAC,UAAU,CAAC,CAsErB"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/handlers/run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAW7C,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,oEAAoE;AACpE,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAgBD;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,aAAa,EACtB,WAAW,EAAE,qBAAqB,EAClC,IAAI,EAAE,YAAY,EAClB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,UAAU,CAAC,CA0FrB"}
@@ -2,6 +2,7 @@ import { ProductiveApi, formatActivity, formatAttachment, formatBooking, formatC
2
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";
3
3
  import variant from "@jitl/quickjs-singlefile-cjs-release-sync";
4
4
  import { newQuickJSWASMModuleFromVariant } from "quickjs-emscripten-core";
5
+ import { timingSafeEqual } from "node:crypto";
5
6
  import { stripTypeScriptTypes } from "node:module";
6
7
  //#region src/errors.ts
7
8
  /**
@@ -41296,6 +41297,72 @@ function resolveRunLimits(env = process.env) {
41296
41297
  };
41297
41298
  }
41298
41299
  //#endregion
41300
+ //#region src/run/remote.ts
41301
+ /**
41302
+ * Remote execution for `run_script`.
41303
+ *
41304
+ * When `PRODUCTIVE_MCP_RUN_RUNNER_URL` is set, the front server forwards a
41305
+ * `run_script` call to a separate runner over a single stateless HTTP POST,
41306
+ * instead of executing the QuickJS sandbox in-process. This keeps the front
41307
+ * small and isolates a script's memory/CPU (and any OOM) on the runner.
41308
+ *
41309
+ * The contract is deliberately infrastructure-agnostic — any service that
41310
+ * accepts the payload and returns a `ToolResult` works (a Fly machine pool, a
41311
+ * load balancer in front of several runners, a serverless function, …). It is
41312
+ * stateless (everything needed is in the body) and must NOT be retried at the
41313
+ * proxy layer, since a non-dry-run script may mutate.
41314
+ */
41315
+ var DEFAULT_RUNNER_TIMEOUT_MS = 3e4;
41316
+ /** Resolve runner configuration from the environment. */
41317
+ function resolveRunnerConfig(env = process.env) {
41318
+ const raw = env.PRODUCTIVE_MCP_RUN_RUNNER_TIMEOUT_MS;
41319
+ const parsed = raw === void 0 ? NaN : Number(raw);
41320
+ const timeoutMs = Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_RUNNER_TIMEOUT_MS;
41321
+ return {
41322
+ url: env.PRODUCTIVE_MCP_RUN_RUNNER_URL || void 0,
41323
+ token: env.PRODUCTIVE_MCP_RUN_RUNNER_TOKEN || void 0,
41324
+ timeoutMs
41325
+ };
41326
+ }
41327
+ /** Constant-time comparison of a provided runner token against the expected one. */
41328
+ function runnerTokenMatches(provided, expected) {
41329
+ if (!expected || !provided) return false;
41330
+ const a = Buffer.from(provided);
41331
+ const b = Buffer.from(expected);
41332
+ if (a.length !== b.length) return false;
41333
+ return timingSafeEqual(a, b);
41334
+ }
41335
+ /**
41336
+ * Forward a run_script call to the remote runner. Never throws: any transport
41337
+ * or runner error is mapped to an error `ToolResult`.
41338
+ */
41339
+ async function runScriptRemote(payload, config, fetchImpl = fetch) {
41340
+ if (!config.url) return errorResult("Remote runner URL is not configured.");
41341
+ const controller = new AbortController();
41342
+ const timer = setTimeout(() => controller.abort(), config.timeoutMs);
41343
+ try {
41344
+ const response = await fetchImpl(config.url, {
41345
+ method: "POST",
41346
+ headers: {
41347
+ "content-type": "application/json",
41348
+ ...config.token ? { authorization: `Bearer ${config.token}` } : {}
41349
+ },
41350
+ body: JSON.stringify(payload),
41351
+ signal: controller.signal
41352
+ });
41353
+ if (!response.ok) {
41354
+ const detail = await response.text().catch(() => "");
41355
+ return errorResult(`Remote runner returned ${response.status}${detail ? `: ${detail.slice(0, 500)}` : ""}`);
41356
+ }
41357
+ return await response.json();
41358
+ } catch (error) {
41359
+ if (error instanceof Error && error.name === "AbortError") return errorResult(`Remote runner timed out after ${config.timeoutMs}ms.`);
41360
+ return errorResult(`Remote runner request failed: ${error instanceof Error ? error.message : String(error)}`);
41361
+ } finally {
41362
+ clearTimeout(timer);
41363
+ }
41364
+ }
41365
+ //#endregion
41299
41366
  //#region src/run/render.ts
41300
41367
  /** Labels for log-style output entries. */
41301
41368
  var LOG_LABELS = {
@@ -41410,13 +41477,21 @@ function normalizeFlags(value) {
41410
41477
  /**
41411
41478
  * Execute the `run_script` tool.
41412
41479
  */
41413
- async function handleRunScript(rawArgs, credentials, exec, env = process.env) {
41414
- if (!isRunScriptEnabled(env)) return inputErrorResult(new UserInputError("run_script is disabled. Set PRODUCTIVE_MCP_ENABLE_RUN=true to enable it."));
41480
+ async function handleRunScript(rawArgs, credentials, exec, env = process.env, deps = {}) {
41481
+ const runner = resolveRunnerConfig(env);
41482
+ if (!runner.url && !isRunScriptEnabled(env)) return inputErrorResult(new UserInputError("run_script is disabled. Set PRODUCTIVE_MCP_ENABLE_RUN=true (or PRODUCTIVE_MCP_RUN_RUNNER_URL to delegate to a runner) to enable it."));
41415
41483
  if (typeof rawArgs.code !== "string" || rawArgs.code.trim() === "") return inputErrorResult(new UserInputError("code is required and must be a non-empty string.", [
41416
41484
  "Provide a JavaScript/TypeScript script in the \"code\" parameter.",
41417
41485
  "Available globals: productive(resource, action, params), api.read/write, output.*, args, flags.",
41418
41486
  "Return a value to surface it as the result; use output.json(...) for additional data."
41419
41487
  ]));
41488
+ if (runner.url) return runScriptRemote({
41489
+ code: rawArgs.code,
41490
+ args: normalizeArgs(rawArgs.args),
41491
+ flags: normalizeFlags(rawArgs.flags),
41492
+ dry_run: rawArgs.dry_run === true,
41493
+ credentials
41494
+ }, runner, deps.fetch);
41420
41495
  const limits = resolveRunLimits(env);
41421
41496
  const codeBytes = Buffer.byteLength(rawArgs.code, "utf8");
41422
41497
  if (codeBytes > limits.maxCodeBytes) return inputErrorResult(new UserInputError(`Script is too large (${codeBytes} bytes, max ${limits.maxCodeBytes}).`));
@@ -42298,6 +42373,6 @@ async function executeToolWithCredentials(name, args, credentials) {
42298
42373
  }
42299
42374
  }
42300
42375
  //#endregion
42301
- export { handleSchemaOverview as C, handleDeals as E, handleServices as S, handlePeople as T, union as _, boolean as a, handleTasks as b, intersection as c, number as d, object as f, string as g, record as h, array as i, literal as l, preprocess as m, _enum as n, custom as o, optional as p, _null as r, discriminatedUnion as s, executeToolWithCredentials as t, looseObject as u, unknown as v, handleProjects as w, handleSummaries as x, datetime as y };
42376
+ 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 };
42302
42377
 
42303
- //# sourceMappingURL=handlers-BEeOjytC.js.map
42378
+ //# sourceMappingURL=handlers-Ca2x4dM8.js.map