@practicemakes/mcp 26.710.3 → 26.823.2

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
@@ -44,7 +44,7 @@ yarn workspace @practicemakes/mcp smoke # end-to-end check against prod
44
44
  ```sh
45
45
  claude mcp add practicemakes \
46
46
  --env FIREBASE_REFRESH_TOKEN=... \
47
- -- node /Users/johnfonte/IdeaProjects/common-frontend/packages/mcp/dist/index.js
47
+ -- node "$(pwd)/packages/mcp/dist/index.js"
48
48
  ```
49
49
 
50
50
  ## Publish
@@ -55,7 +55,7 @@ Versioned on the monorepo's calendar scheme (`26.MMDD.patch`). Scoped public pac
55
55
  yarn workspace @practicemakes/mcp publish # runs prepublishOnly (clean + build) first
56
56
  ```
57
57
 
58
- ## Tools (v1, read-only)
58
+ ## Tools (read)
59
59
 
60
60
  | Tool | What it returns |
61
61
  |---|---|
@@ -69,10 +69,63 @@ yarn workspace @practicemakes/mcp publish # runs prepublishOnly (clean + build
69
69
  | `get_task` | One task by UUID |
70
70
  | `list_workflows` | Your Planner workflows |
71
71
  | `get_workflow` | One workflow, hydrated with run state |
72
- | `get_billing_config` | Billing/entitlement config for your org |
72
+ | `get_billing_config` | Billing/entitlement config for your org (takes a required `platform`) |
73
+ | `list_servers` | Monitored servers + both identifiers the tools below need |
74
+ | `get_server` | One server's config, including its maintenance windows |
75
+ | `list_server_statuses` | Live status of **all** your servers (is anything down now) |
76
+ | `get_server_uptime` | Uptime % over the last 1–168 hours, from raw heartbeats |
77
+ | `get_server_uptime_daily` | Per-day uptime history beyond heartbeat retention |
78
+ | `list_server_incidents` | Outage history for one server, planned vs unplanned |
79
+
80
+ ### Server identifiers
81
+
82
+ The monitoring tools take **two different, non-interchangeable identifiers**, and passing the wrong
83
+ one returns a 404 that reads like a missing server. `list_servers` returns both:
84
+
85
+ | Identifier | Used by | Route family |
86
+ |---|---|---|
87
+ | `serverShortLink` | `get_server_uptime`, `get_server_uptime_daily` | `/api/public/...` (unauthenticated) |
88
+ | `serverId` | `get_server`, `list_server_incidents` | `/api/server/...` (authenticated, org-scoped) |
89
+
90
+ Neither is a UUID — `serverId` is the operator-assigned id (e.g. `web-01`).
91
+
92
+ ## Tools (write)
93
+
94
+ Every write tool asks you to confirm the exact request before it sends anything.
95
+
96
+ | Tool | What it does |
97
+ |---|---|
98
+ | `create_task` | Creates a Planner task in your current org context (needs ADMIN there) |
99
+ | `complete_task` | Sets `completedAt` on a task — the tool that closes the Planner-driven-dev loop |
100
+ | `switch_org_context` | Switches your active org context (SYSADMIN → any org, otherwise invite-only) |
101
+
102
+ ### Read this before enabling write tools
103
+
104
+ **These tools run as you, with all of your permissions.** Authentication is a per-user Firebase
105
+ ID token, not a service account — there is no read-only role, no separate service identity, and
106
+ no server-side allow-list behind them. If your account is SYSADMIN, a write tool can write across
107
+ every tenant on the platform. The tool list and the confirmation prompt are the entire safety
108
+ boundary.
109
+
110
+ **Confirmation needs client support.** The gate uses MCP *elicitation*. If your client did not
111
+ advertise elicitation support during the handshake, there is no channel to ask you on, and every
112
+ write tool **refuses and sends nothing** — it will not fall back to writing unconfirmed. The
113
+ error names the mutation it declined to perform. This is deliberate: silently proceeding would
114
+ disable the guardrail for exactly the clients that never agreed to be asked.
115
+
116
+ **`switch_org_context` is session-global.** It mutates your profile, so it changes what *every
117
+ later read tool* returns for the rest of the session — not just the next call.
118
+
119
+ **`complete_task` is a read-modify-write.** There is no dedicated completion endpoint; the tool
120
+ reads the task, sets `completedAt`, and re-posts it, carrying the read's `ETag` back as
121
+ `If-Match` so a concurrent edit is not silently clobbered.
122
+
123
+ `yarn smoke` is unaffected — it issues three fixed read-only requests and never touches the tool
124
+ registry, so no write can reach production through it.
73
125
 
74
126
  ## Roadmap
75
127
 
76
- - **Write tools** (complete/create a task, switch org context, admin actions) with per-call confirmation.
128
+ - **Admin actions** (e.g. the ARCH-153 org/user purge) — blocked: no executable endpoint exists
129
+ yet, only a `GET .../data-inventory` that reports what a purge *would* do and deletes nothing.
77
130
  - **OpenAPI-driven** tool generation from the backend's grouped specs (the monorepo already uses `openapi-typescript` in `packages/core`).
78
131
  - **Hosted remote server** (streamable-HTTP + per-user OAuth) so users connect to a URL with nothing to install.
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import { registerOrganizationTools } from "./tools/organizations.js";
8
8
  import { registerProfileTools } from "./tools/profiles.js";
9
9
  import { registerPlannerTools } from "./tools/planner.js";
10
10
  import { registerBillingTools } from "./tools/billing.js";
11
+ import { registerMonitorTools } from "./tools/monitor.js";
11
12
  async function main() {
12
13
  const config = loadConfig();
13
14
  const tokens = new TokenManager(config);
@@ -20,6 +21,7 @@ async function main() {
20
21
  registerProfileTools(server, client);
21
22
  registerPlannerTools(server, client);
22
23
  registerBillingTools(server, client);
24
+ registerMonitorTools(server, client);
23
25
  const transport = new StdioServerTransport();
24
26
  await server.connect(transport);
25
27
  // stdout is the MCP transport; log to stderr only.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAEvD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,yBAAyB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAErC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,mDAAmD;IACnD,OAAO,CAAC,KAAK,CAAC,qCAAqC,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC;AAC3E,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAClE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAEvD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,yBAAyB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAErC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,mDAAmD;IACnD,OAAO,CAAC,KAAK,CAAC,qCAAqC,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC;AAC3E,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAClE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -13,6 +13,18 @@ export declare class PmClient {
13
13
  private tokens;
14
14
  constructor(baseUrl: string, tokens: TokenManager);
15
15
  get(path: string): Promise<unknown>;
16
- post(path: string, body: unknown): Promise<unknown>;
16
+ /**
17
+ * A GET that also surfaces the response's `ETag`.
18
+ *
19
+ * The concurrency token travels in headers only: the backend's `ConcurrencyGuard` documents
20
+ * that a body's `updatedAt` serializes at second precision and so cannot reproduce the
21
+ * millisecond token. A read-modify-write therefore has no other way to obtain the `If-Match`
22
+ * value it has to send back.
23
+ */
24
+ getWithEtag(path: string): Promise<{
25
+ data: unknown;
26
+ etag: string | null;
27
+ }>;
28
+ post(path: string, body?: unknown, headers?: Record<string, string>): Promise<unknown>;
17
29
  private request;
18
30
  }
package/dist/pmClient.js CHANGED
@@ -24,12 +24,23 @@ export class PmClient {
24
24
  this.tokens = tokens;
25
25
  }
26
26
  async get(path) {
27
+ return (await this.request("GET", path)).data;
28
+ }
29
+ /**
30
+ * A GET that also surfaces the response's `ETag`.
31
+ *
32
+ * The concurrency token travels in headers only: the backend's `ConcurrencyGuard` documents
33
+ * that a body's `updatedAt` serializes at second precision and so cannot reproduce the
34
+ * millisecond token. A read-modify-write therefore has no other way to obtain the `If-Match`
35
+ * value it has to send back.
36
+ */
37
+ async getWithEtag(path) {
27
38
  return this.request("GET", path);
28
39
  }
29
- async post(path, body) {
30
- return this.request("POST", path, body);
40
+ async post(path, body, headers) {
41
+ return (await this.request("POST", path, body, headers)).data;
31
42
  }
32
- async request(method, path, body) {
43
+ async request(method, path, body, headers) {
33
44
  const idToken = await this.tokens.getIdToken();
34
45
  const res = await fetch(`${this.baseUrl}${path}`, {
35
46
  method,
@@ -37,6 +48,7 @@ export class PmClient {
37
48
  Authorization: `Bearer ${idToken}`,
38
49
  Accept: "application/json",
39
50
  ...(body !== undefined ? { "Content-Type": "application/json" } : {}),
51
+ ...(headers ?? {}),
40
52
  },
41
53
  body: body !== undefined ? JSON.stringify(body) : undefined,
42
54
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
@@ -45,10 +57,14 @@ export class PmClient {
45
57
  if (!res.ok) {
46
58
  throw new PmApiError(res.status, text, method, path);
47
59
  }
60
+ const etag = res.headers.get("etag");
48
61
  if (!text)
49
- return null;
62
+ return { data: null, etag };
50
63
  const contentType = res.headers.get("content-type") ?? "";
51
- return contentType.includes("application/json") ? JSON.parse(text) : text;
64
+ return {
65
+ data: contentType.includes("application/json") ? JSON.parse(text) : text,
66
+ etag,
67
+ };
52
68
  }
53
69
  }
54
70
  //# sourceMappingURL=pmClient.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pmClient.js","sourceRoot":"","sources":["../src/pmClient.ts"],"names":[],"mappings":"AAEA,mGAAmG;AACnG,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC,8FAA8F;AAC9F,MAAM,OAAO,UAAW,SAAQ,KAAK;IAE1B;IACA;IACA;IACA;IAJT,YACS,MAAc,EACd,QAAgB,EAChB,MAAc,EACd,IAAY;QAEnB,KAAK,CAAC,GAAG,MAAM,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QAL5D,WAAM,GAAN,MAAM,CAAQ;QACd,aAAQ,GAAR,QAAQ,CAAQ;QAChB,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAQ;QAGnB,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF;AAED,wEAAwE;AACxE,MAAM,OAAO,QAAQ;IAET;IACA;IAFV,YACU,OAAe,EACf,MAAoB;QADpB,YAAO,GAAP,OAAO,CAAQ;QACf,WAAM,GAAN,MAAM,CAAc;IAC3B,CAAC;IAEJ,KAAK,CAAC,GAAG,CAAC,IAAY;QACpB,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,IAAa;QACpC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC;IAEO,KAAK,CAAC,OAAO,CAAC,MAAc,EAAE,IAAY,EAAE,IAAc;QAChE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QAC/C,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,EAAE;YAChD,MAAM;YACN,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,OAAO,EAAE;gBAClC,MAAM,EAAE,kBAAkB;gBAC1B,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACtE;YACD,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;YAC3D,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,kBAAkB,CAAC;SAChD,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QACvD,CAAC;QACD,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;QAC1D,OAAO,WAAW,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5E,CAAC;CACF"}
1
+ {"version":3,"file":"pmClient.js","sourceRoot":"","sources":["../src/pmClient.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,27 @@
1
+ import { z } from "zod";
1
2
  import { runTool } from "./shared.js";
2
3
  export function registerBillingTools(server, client) {
3
4
  server.registerTool("get_billing_config", {
4
5
  title: "Get billing config",
5
- description: "Fetch the billing/entitlement configuration for your current organization context (products, entitlements, plan state).",
6
- inputSchema: {},
7
- }, async () => runTool(() => client.get("/api/billing/config")));
6
+ description: "Fetch the billing/entitlement configuration for your current organization context (products, entitlements, plan state). Billing config is per-platform, so the platform must be named.",
7
+ inputSchema: {
8
+ // `platform` is a REQUIRED query param on GET /api/billing/config (operation
9
+ // `getConfig` in the committed spec). This tool used to declare no inputs and call
10
+ // the bare path, so every call was malformed.
11
+ platform: z
12
+ .string()
13
+ .min(1)
14
+ .describe("Store platform the config is for, e.g. 'ios', 'android', 'web'."),
15
+ useTestStore: z
16
+ .boolean()
17
+ .optional()
18
+ .describe("Read the store's sandbox/test catalogue instead of production."),
19
+ },
20
+ }, async ({ platform, useTestStore }) => {
21
+ const query = new URLSearchParams({ platform });
22
+ if (useTestStore !== undefined)
23
+ query.set("useTestStore", String(useTestStore));
24
+ return runTool(() => client.get(`/api/billing/config?${query.toString()}`));
25
+ });
8
26
  }
9
27
  //# sourceMappingURL=billing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"billing.js","sourceRoot":"","sources":["../../src/tools/billing.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,MAAM,UAAU,oBAAoB,CAAC,MAAiB,EAAE,MAAgB;IACtE,MAAM,CAAC,YAAY,CACjB,oBAAoB,EACpB;QACE,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EACT,yHAAyH;QAC3H,WAAW,EAAE,EAAE;KAChB,EACD,KAAK,IAAI,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,CAC7D,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"billing.js","sourceRoot":"","sources":["../../src/tools/billing.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,MAAM,UAAU,oBAAoB,CAAC,MAAiB,EAAE,MAAgB;IACtE,MAAM,CAAC,YAAY,CACjB,oBAAoB,EACpB;QACE,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EACT,wLAAwL;QAC1L,WAAW,EAAE;YACX,6EAA6E;YAC7E,mFAAmF;YACnF,8CAA8C;YAC9C,QAAQ,EAAE,CAAC;iBACR,MAAM,EAAE;iBACR,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,CAAC,iEAAiE,CAAC;YAC9E,YAAY,EAAE,CAAC;iBACZ,OAAO,EAAE;iBACT,QAAQ,EAAE;iBACV,QAAQ,CAAC,gEAAgE,CAAC;SAC9E;KACF,EACD,KAAK,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,EAAE;QACnC,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QAChD,IAAI,YAAY,KAAK,SAAS;YAAE,KAAK,CAAC,GAAG,CAAC,cAAc,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;QAChF,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,uBAAuB,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC;IAC9E,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ /** The mutation a write tool intends to perform, rendered for a human to accept or reject. */
3
+ export interface WriteIntent {
4
+ /** Imperative one-liner, e.g. `Complete Planner task "Ship ARCH-173"`. */
5
+ summary: string;
6
+ /** The exact request issued on accept — this is what the human is agreeing to. */
7
+ request: {
8
+ method: "POST";
9
+ path: string;
10
+ body?: unknown;
11
+ };
12
+ }
13
+ /**
14
+ * Runs [perform] only after the human accepts [buildIntent]'s description of it.
15
+ *
16
+ * When the client cannot be asked, the write is refused rather than performed. That is the
17
+ * deliberate call: the alternative — proceeding unconfirmed — turns the ticket's one hard
18
+ * requirement off for exactly the clients that never opted into being asked.
19
+ */
20
+ export declare function withConfirmation(server: McpServer, toolName: string, buildIntent: () => WriteIntent | Promise<WriteIntent>, perform: (intent: WriteIntent) => Promise<unknown>): Promise<{
21
+ content: {
22
+ type: "text";
23
+ text: string;
24
+ }[];
25
+ }>;
@@ -0,0 +1,69 @@
1
+ import { errorResult, jsonResult, runTool } from "./shared.js";
2
+ /**
3
+ * Per-call confirmation for write tools (ARCH-173).
4
+ *
5
+ * Why this exists at all: authentication is a per-user Firebase ID token, not a service
6
+ * account, so a write tool inherits everything the connected human can do — up to
7
+ * cross-tenant SYSADMIN. There is no read-only role and no server-side allow-list behind
8
+ * this. The tool list plus this gate are the entire safety boundary.
9
+ */
10
+ /** How much of a request body to show in the prompt before truncating. */
11
+ const MAX_BODY_PREVIEW = 1200;
12
+ /**
13
+ * The SDK gates `elicitInput` on `elicitation.form` and *throws* when it is absent
14
+ * (`server/index.js`: "Client does not support form elicitation"). It does not silently
15
+ * no-op. Reading the same flag first lets the refusal name the write that was blocked
16
+ * instead of surfacing a bare SDK message.
17
+ */
18
+ function supportsFormElicitation(server) {
19
+ const elicitation = server.server.getClientCapabilities()?.elicitation;
20
+ return Boolean(elicitation?.form);
21
+ }
22
+ function renderRequest(intent) {
23
+ const line = `${intent.request.method} ${intent.request.path}`;
24
+ if (intent.request.body === undefined)
25
+ return line;
26
+ const body = JSON.stringify(intent.request.body, null, 2);
27
+ const shown = body.length > MAX_BODY_PREVIEW ? `${body.slice(0, MAX_BODY_PREVIEW)}\n… (truncated)` : body;
28
+ return `${line}\n${shown}`;
29
+ }
30
+ /**
31
+ * Runs [perform] only after the human accepts [buildIntent]'s description of it.
32
+ *
33
+ * When the client cannot be asked, the write is refused rather than performed. That is the
34
+ * deliberate call: the alternative — proceeding unconfirmed — turns the ticket's one hard
35
+ * requirement off for exactly the clients that never opted into being asked.
36
+ */
37
+ export async function withConfirmation(server, toolName, buildIntent, perform) {
38
+ let intent;
39
+ try {
40
+ intent = await buildIntent();
41
+ }
42
+ catch (err) {
43
+ return errorResult(err);
44
+ }
45
+ if (!supportsFormElicitation(server)) {
46
+ return errorResult(new Error(`${toolName} performs a write and requires per-call confirmation, but this MCP client ` +
47
+ `did not advertise elicitation support, so there is no way to ask you.\n\n` +
48
+ `Intended mutation:\n${intent.summary}\n${renderRequest(intent)}\n\n` +
49
+ `No request was sent.`));
50
+ }
51
+ let outcome;
52
+ try {
53
+ outcome = await server.server.elicitInput({
54
+ message: `${toolName} wants to write to PracticeMakes.\n\n` +
55
+ `${intent.summary}\n\n${renderRequest(intent)}\n\n` +
56
+ `This runs as your own account, with your own permissions. Approve?`,
57
+ requestedSchema: { type: "object", properties: {} },
58
+ });
59
+ }
60
+ catch (err) {
61
+ // Transport/protocol failure while asking. Nothing was sent, and it must stay that way.
62
+ return errorResult(err);
63
+ }
64
+ if (outcome.action !== "accept") {
65
+ return jsonResult(`${toolName}: ${outcome.action} — no request was sent.`);
66
+ }
67
+ return runTool(() => perform(intent));
68
+ }
69
+ //# sourceMappingURL=confirm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirm.js","sourceRoot":"","sources":["../../src/tools/confirm.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE/D;;;;;;;GAOG;AAEH,0EAA0E;AAC1E,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAU9B;;;;;GAKG;AACH,SAAS,uBAAuB,CAAC,MAAiB;IAChD,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,qBAAqB,EAAE,EAAE,WAE9C,CAAC;IACd,OAAO,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,aAAa,CAAC,MAAmB;IACxC,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IAC/D,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC1D,MAAM,KAAK,GACT,IAAI,CAAC,MAAM,GAAG,gBAAgB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9F,OAAO,GAAG,IAAI,KAAK,KAAK,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,MAAiB,EACjB,QAAgB,EAChB,WAAqD,EACrD,OAAkD;IAElD,IAAI,MAAmB,CAAC;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,WAAW,EAAE,CAAC;IAC/B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,CAAC,uBAAuB,CAAC,MAAM,CAAC,EAAE,CAAC;QACrC,OAAO,WAAW,CAChB,IAAI,KAAK,CACP,GAAG,QAAQ,4EAA4E;YACrF,2EAA2E;YAC3E,uBAAuB,MAAM,CAAC,OAAO,KAAK,aAAa,CAAC,MAAM,CAAC,MAAM;YACrE,sBAAsB,CACzB,CACF,CAAC;IACJ,CAAC;IAED,IAAI,OAA2B,CAAC;IAChC,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC;YACxC,OAAO,EACL,GAAG,QAAQ,uCAAuC;gBAClD,GAAG,MAAM,CAAC,OAAO,OAAO,aAAa,CAAC,MAAM,CAAC,MAAM;gBACnD,oEAAoE;YACtE,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;SACpD,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,wFAAwF;QACxF,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,UAAU,CAAC,GAAG,QAAQ,KAAK,OAAO,CAAC,MAAM,yBAAyB,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { PmClient } from "../pmClient.js";
3
+ export declare function registerMonitorTools(server: McpServer, client: PmClient): void;
@@ -0,0 +1,96 @@
1
+ import { z } from "zod";
2
+ import { runTool } from "./shared.js";
3
+ /**
4
+ * Server-monitoring tools.
5
+ *
6
+ * Two identifiers are in play and they are NOT interchangeable — passing one where the other
7
+ * belongs yields a 404 that looks like a missing server:
8
+ *
9
+ * - `serverShortLink` keys the unauthenticated `/api/public/...` uptime routes.
10
+ * - `serverId` keys the authenticated, org-scoped `/api/server/...` routes. It is the
11
+ * operator-assigned identifier (e.g. "web-01"), not the row's UUID.
12
+ *
13
+ * `list_servers` returns both fields, so it is the way to resolve either one.
14
+ */
15
+ /** The backend caps a daily-uptime range at 366 days; 30 is the useful default window. */
16
+ const DEFAULT_DAILY_DAYS = 30;
17
+ /** Mirrors ServerController.MAX_INCIDENT_PAGE_SIZE — the backend silently caps above this. */
18
+ const MAX_INCIDENT_PAGE_SIZE = 100;
19
+ function isoDate(date) {
20
+ return date.toISOString().slice(0, 10);
21
+ }
22
+ export function registerMonitorTools(server, client) {
23
+ server.registerTool("list_servers", {
24
+ title: "List monitored servers",
25
+ description: "Every server your organization monitors, with its configuration. Start here: the response carries both identifiers the other monitoring tools need — `serverId` (the operator-assigned id used by get_server and list_server_incidents) and `serverShortLink` (used by the uptime tools).",
26
+ inputSchema: {},
27
+ }, async () => runTool(() => client.get("/api/server")));
28
+ server.registerTool("get_server", {
29
+ title: "Get server",
30
+ description: "One monitored server's full configuration, including its heartbeat interval and maintenance-window schedule (which determines whether an outage is recorded as planned). Keyed by `serverId` — the operator-assigned identifier from list_servers, NOT a UUID and NOT the serverShortLink.",
31
+ inputSchema: {
32
+ serverId: z.string().describe("Operator-assigned server id from list_servers, e.g. 'web-01'."),
33
+ },
34
+ }, async ({ serverId }) => runTool(() => client.get(`/api/server/${encodeURIComponent(serverId)}`)));
35
+ server.registerTool("list_server_statuses", {
36
+ title: "List server statuses",
37
+ description: "Current live status (online/offline, last heartbeat, last speedtest) for ALL servers in your organization — this returns a list, it does not take a server identifier. Use it to answer 'is anything down right now'.",
38
+ inputSchema: {},
39
+ }, async () => runTool(() => client.get("/api/server/status")));
40
+ server.registerTool("get_server_uptime", {
41
+ title: "Get server uptime",
42
+ description: "Uptime percentage over a recent window, computed from raw heartbeats (retained ~31 days). Keyed by `serverShortLink` from list_servers — NOT the serverId the /api/server tools take. For history older than the heartbeat retention, use get_server_uptime_daily.",
43
+ inputSchema: {
44
+ serverShortLink: z.string().describe("Server short link from list_servers (public uptime identifier)."),
45
+ hours: z
46
+ .number()
47
+ .int()
48
+ .min(1)
49
+ .max(168)
50
+ .default(24)
51
+ .describe("Window in hours. Values outside 1-168 are rejected before the request is sent."),
52
+ },
53
+ }, async ({ serverShortLink, hours }) => runTool(() => client.get(`/api/public/server/${encodeURIComponent(serverShortLink)}/uptime?hours=${hours}`)));
54
+ server.registerTool("get_server_uptime_daily", {
55
+ title: "Get daily uptime history",
56
+ description: "Per-day uptime rollups: uptime percent, online/downtime/planned-downtime seconds and incident count for each day. This is the long-term history — it survives past the ~31-day raw heartbeat retention that get_server_uptime is limited to. Keyed by `serverShortLink`, NOT serverId. Defaults to the last 30 days; the backend rejects ranges wider than 366 days.",
57
+ inputSchema: {
58
+ serverShortLink: z.string().describe("Server short link from list_servers (public uptime identifier)."),
59
+ from: z
60
+ .string()
61
+ .regex(/^\d{4}-\d{2}-\d{2}$/)
62
+ .optional()
63
+ .describe("Start date, ISO YYYY-MM-DD. Defaults to 30 days ago."),
64
+ to: z
65
+ .string()
66
+ .regex(/^\d{4}-\d{2}-\d{2}$/)
67
+ .optional()
68
+ .describe("End date, ISO YYYY-MM-DD. Defaults to today."),
69
+ },
70
+ }, async ({ serverShortLink, from, to }) => runTool(() => {
71
+ const today = new Date();
72
+ const resolvedTo = to ?? isoDate(today);
73
+ const resolvedFrom = from ?? isoDate(new Date(today.getTime() - DEFAULT_DAILY_DAYS * 24 * 60 * 60 * 1000));
74
+ const query = new URLSearchParams({ from: resolvedFrom, to: resolvedTo });
75
+ return client.get(`/api/public/server/${encodeURIComponent(serverShortLink)}/uptime/daily?${query}`);
76
+ }));
77
+ server.registerTool("list_server_incidents", {
78
+ title: "List server incidents",
79
+ description: "Outage history for one server, newest first: when each outage started and ended, how long it lasted, and whether it was `planned` (started inside a configured maintenance window) or unplanned. An incident with `ongoing: true` has no end yet — the server is down right now. Keyed by `serverId` from list_servers, NOT the serverShortLink.",
80
+ inputSchema: {
81
+ serverId: z.string().describe("Operator-assigned server id from list_servers, e.g. 'web-01'."),
82
+ page: z.number().int().min(0).default(0).describe("Zero-based page number."),
83
+ size: z
84
+ .number()
85
+ .int()
86
+ .min(1)
87
+ .max(MAX_INCIDENT_PAGE_SIZE)
88
+ .default(50)
89
+ .describe(`Incidents per page, max ${MAX_INCIDENT_PAGE_SIZE}.`),
90
+ },
91
+ }, async ({ serverId, page, size }) => runTool(() => {
92
+ const query = new URLSearchParams({ page: String(page), size: String(size) });
93
+ return client.get(`/api/server/${encodeURIComponent(serverId)}/incidents?${query}`);
94
+ }));
95
+ }
96
+ //# sourceMappingURL=monitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monitor.js","sourceRoot":"","sources":["../../src/tools/monitor.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { withConfirmation } from "./confirm.js";
2
3
  import { runTool } from "./shared.js";
3
4
  export function registerOrganizationTools(server, client) {
4
5
  server.registerTool("list_organizations", {
@@ -13,5 +14,17 @@ export function registerOrganizationTools(server, client) {
13
14
  organizationId: z.string().uuid().describe("Organization UUID"),
14
15
  },
15
16
  }, async ({ organizationId }) => runTool(() => client.get(`/api/organization/${organizationId}`)));
17
+ server.registerTool("switch_org_context", {
18
+ title: "Switch organization context",
19
+ description: "Switch your active organization context. WRITE: this mutates your profile, so it changes what EVERY subsequent read tool returns for the rest of the session, not just the next call. Prompts you for confirmation before sending. A SYSADMIN can switch into any organization; everyone else needs an invite to the target.",
20
+ inputSchema: {
21
+ organizationId: z.string().uuid().describe("Organization UUID to switch into"),
22
+ },
23
+ }, async ({ organizationId }) => withConfirmation(server, "switch_org_context", () => ({
24
+ summary: `Switch your active organization context to ${organizationId}. ` +
25
+ `Every later read tool in this session will return that org's data.`,
26
+ // The endpoint takes no request body — the id is the path variable.
27
+ request: { method: "POST", path: `/api/organization/context/${organizationId}` },
28
+ }), () => client.post(`/api/organization/context/${organizationId}`)));
16
29
  }
17
30
  //# sourceMappingURL=organizations.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"organizations.js","sourceRoot":"","sources":["../../src/tools/organizations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,MAAM,UAAU,yBAAyB,CAAC,MAAiB,EAAE,MAAgB;IAC3E,MAAM,CAAC,YAAY,CACjB,oBAAoB,EACpB;QACE,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EACT,gJAAgJ;QAClJ,WAAW,EAAE,EAAE;KAChB,EACD,KAAK,IAAI,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC,CAC/D,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,kBAAkB,EAClB;QACE,KAAK,EAAE,kBAAkB;QACzB,WAAW,EAAE,0CAA0C;QACvD,WAAW,EAAE;YACX,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC;SAChE;KACF,EACD,KAAK,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,CAC3B,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,qBAAqB,cAAc,EAAE,CAAC,CAAC,CACnE,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"organizations.js","sourceRoot":"","sources":["../../src/tools/organizations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,MAAM,UAAU,yBAAyB,CAAC,MAAiB,EAAE,MAAgB;IAC3E,MAAM,CAAC,YAAY,CACjB,oBAAoB,EACpB;QACE,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EACT,gJAAgJ;QAClJ,WAAW,EAAE,EAAE;KAChB,EACD,KAAK,IAAI,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC,CAC/D,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,kBAAkB,EAClB;QACE,KAAK,EAAE,kBAAkB;QACzB,WAAW,EAAE,0CAA0C;QACvD,WAAW,EAAE;YACX,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC;SAChE;KACF,EACD,KAAK,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,CAC3B,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,qBAAqB,cAAc,EAAE,CAAC,CAAC,CACnE,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,oBAAoB,EACpB;QACE,KAAK,EAAE,6BAA6B;QACpC,WAAW,EACT,8TAA8T;QAChU,WAAW,EAAE;YACX,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;SAC/E;KACF,EACD,KAAK,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,CAC3B,gBAAgB,CACd,MAAM,EACN,oBAAoB,EACpB,GAAG,EAAE,CAAC,CAAC;QACL,OAAO,EACL,8CAA8C,cAAc,IAAI;YAChE,oEAAoE;QACtE,oEAAoE;QACpE,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,6BAA6B,cAAc,EAAE,EAAE;KACjF,CAAC,EACF,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,cAAc,EAAE,CAAC,CACjE,CACJ,CAAC;AACJ,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
- import { runTool } from "./shared.js";
2
+ import { withConfirmation } from "./confirm.js";
3
+ import { errorResult, jsonResult, runTool } from "./shared.js";
3
4
  export function registerPlannerTools(server, client) {
4
5
  server.registerTool("get_my_planner_tasks", {
5
6
  title: "Get my Planner tasks",
@@ -46,5 +47,85 @@ export function registerPlannerTools(server, client) {
46
47
  workflowId: z.string().uuid().describe("Workflow UUID"),
47
48
  },
48
49
  }, async ({ workflowId }) => runTool(() => client.get(`/api/workflow/${workflowId}`)));
50
+ server.registerTool("create_task", {
51
+ title: "Create Planner task",
52
+ description: "Create a new Planner task in your current organization context. WRITE: prompts you for confirmation before sending, and runs as your own account with your own permissions. Requires ADMIN in the active org context.",
53
+ inputSchema: {
54
+ name: z.string().min(1).describe("Task name — what the task is."),
55
+ dueAt: z
56
+ .string()
57
+ .datetime()
58
+ .optional()
59
+ .describe("ISO-8601 due timestamp, e.g. 2026-09-01T17:00:00Z."),
60
+ priorityValue: z
61
+ .number()
62
+ .int()
63
+ .optional()
64
+ .describe("Backlog priority; ascending, so a lower value sorts higher."),
65
+ durationEstimate: z.number().int().optional().describe("Estimated duration, in minutes."),
66
+ requiresCompletion: z
67
+ .boolean()
68
+ .optional()
69
+ .describe("Whether the task must be completed rather than expiring."),
70
+ },
71
+ }, async ({ name, dueAt, priorityValue, durationEstimate, requiresCompletion }) => {
72
+ // `tags` and `taskLinks` are non-nullable on the Task schema, so a create has to send
73
+ // them even when empty; omitting them fails validation before it reaches the funnel.
74
+ const body = {
75
+ name,
76
+ tags: [],
77
+ taskLinks: [],
78
+ ...(dueAt !== undefined ? { dueAt } : {}),
79
+ ...(priorityValue !== undefined ? { priorityValue } : {}),
80
+ ...(durationEstimate !== undefined ? { durationEstimate } : {}),
81
+ ...(requiresCompletion !== undefined ? { requiresCompletion } : {}),
82
+ };
83
+ return withConfirmation(server, "create_task", () => ({
84
+ summary: `Create Planner task "${name}" in your current organization context.`,
85
+ request: { method: "POST", path: "/api/task", body },
86
+ }),
87
+ // A create has no prior version, so the backend does not consult If-Match here.
88
+ () => client.post("/api/task", body));
89
+ });
90
+ server.registerTool("complete_task", {
91
+ title: "Complete Planner task",
92
+ description: "Mark a Planner task complete by setting its completedAt. This is the tool that closes the Planner-driven-dev loop. WRITE: prompts you for confirmation before sending, and runs as your own account with your own permissions.",
93
+ inputSchema: {
94
+ taskId: z.string().uuid().describe("Task UUID"),
95
+ },
96
+ }, async ({ taskId }) => {
97
+ // Completion is a read-modify-write over POST /api/task — there is no dedicated
98
+ // complete endpoint. The ETag from this read is the If-Match token for the write.
99
+ let fetched;
100
+ try {
101
+ fetched = await client.getWithEtag(`/api/task/${taskId}`);
102
+ }
103
+ catch (err) {
104
+ return errorResult(err);
105
+ }
106
+ const task = fetched.data;
107
+ if (!task) {
108
+ return errorResult(new Error(`Task ${taskId} was not found.`));
109
+ }
110
+ if (task.completedAt) {
111
+ // Nothing to change: re-posting would spend a confirmation to rewrite the same row.
112
+ return jsonResult(`Task ${taskId} is already complete (completedAt ${String(task.completedAt)}). No write needed.`);
113
+ }
114
+ // Echo the whole read back rather than patching {id, completedAt}. POST /api/task is a
115
+ // full-representation update: SafeWriteService copies every field on its writable list
116
+ // across unconditionally, so anything omitted here is blanked on the stored row — and
117
+ // requireClearingWasRequested rejects a body that drops a non-empty collection outright
118
+ // rather than silently detaching it (ARCH-416). The server-managed fields that ride along
119
+ // are inert: `id` is what routes this to an update instead of a create, and the rest are
120
+ // absent from the writable list, so the funnel ignores them.
121
+ const body = { ...task, completedAt: new Date().toISOString() };
122
+ return withConfirmation(server, "complete_task", () => ({
123
+ summary: `Complete Planner task "${String(task.name ?? taskId)}".`,
124
+ request: { method: "POST", path: "/api/task", body },
125
+ }), () => client.post("/api/task", body,
126
+ // Without this the backend's ConcurrencyGuard records a MISSING_TOKEN stale
127
+ // write to Sentry on every call, and would reject it once enforcement is on.
128
+ fetched.etag ? { "If-Match": fetched.etag } : undefined));
129
+ });
49
130
  }
50
131
  //# sourceMappingURL=planner.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"planner.js","sourceRoot":"","sources":["../../src/tools/planner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"planner.js","sourceRoot":"","sources":["../../src/tools/planner.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@practicemakes/mcp",
3
- "version": "26.710.3",
3
+ "version": "26.823.2",
4
4
  "description": "MCP server exposing PracticeMakes prod data (read-only) to Claude, authenticated as the calling user's own Firebase identity.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -23,16 +23,20 @@
23
23
  "start": "node dist/index.js",
24
24
  "smoke": "node --env-file=.env dist/smoke.js",
25
25
  "login": "node --env-file=.env dist/login.js",
26
+ "test": "vitest run",
27
+ "test:watch": "vitest",
28
+ "test:ci": "vitest run",
26
29
  "prepublishOnly": "yarn clean && yarn build"
27
30
  },
28
31
  "dependencies": {
29
- "@modelcontextprotocol/sdk": "^1.12.0",
32
+ "@modelcontextprotocol/sdk": "^1.30.0",
30
33
  "zod": "^3.23.8"
31
34
  },
32
35
  "devDependencies": {
33
36
  "@types/node": "^20.14.0",
34
37
  "rimraf": "4.1.2",
35
- "typescript": "6.0.3"
38
+ "typescript": "6.0.3",
39
+ "vitest": "4.1.10"
36
40
  },
37
41
  "engines": {
38
42
  "node": ">=20.6.0"