@practicemakes/mcp 26.710.3
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 +78 -0
- package/dist/auth.d.ts +25 -0
- package/dist/auth.js +92 -0
- package/dist/auth.js.map +1 -0
- package/dist/config.d.ts +18 -0
- package/dist/config.js +19 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/login.d.ts +2 -0
- package/dist/login.js +26 -0
- package/dist/login.js.map +1 -0
- package/dist/pmClient.d.ts +18 -0
- package/dist/pmClient.js +54 -0
- package/dist/pmClient.js.map +1 -0
- package/dist/smoke.d.ts +2 -0
- package/dist/smoke.js +33 -0
- package/dist/smoke.js.map +1 -0
- package/dist/tools/billing.d.ts +3 -0
- package/dist/tools/billing.js +9 -0
- package/dist/tools/billing.js.map +1 -0
- package/dist/tools/organizations.d.ts +3 -0
- package/dist/tools/organizations.js +17 -0
- package/dist/tools/organizations.js.map +1 -0
- package/dist/tools/planner.d.ts +3 -0
- package/dist/tools/planner.js +50 -0
- package/dist/tools/planner.js.map +1 -0
- package/dist/tools/profiles.d.ts +3 -0
- package/dist/tools/profiles.js +22 -0
- package/dist/tools/profiles.js.map +1 -0
- package/dist/tools/shared.d.ts +22 -0
- package/dist/tools/shared.js +30 -0
- package/dist/tools/shared.js.map +1 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# @practicemakes/mcp
|
|
2
|
+
|
|
3
|
+
A small [MCP](https://modelcontextprotocol.io) server that exposes **PracticeMakes** production data (read-only) to Claude for admin and development work — and surfaces your **Planner backlog** so your own task list can drive development.
|
|
4
|
+
|
|
5
|
+
It authenticates as **your own platform identity** (your Firebase user), so every result is scoped to your real role and organization by the backend. A SYSADMIN sees the whole platform; a regular user sees only their org. That's what lets the same server be handed to other users on the platform.
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Claude ──stdio──▶ @practicemakes/mcp ──HTTPS Bearer──▶ https://api.practicemakes.io/api/...
|
|
11
|
+
(refreshes your Firebase ID token) (existing authz + business logic)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
No backend changes and no secret credentials — the Firebase Web API key it uses is a **public** client value (baked in as a default), so each user supplies only a credential for **their own** account. Read-only.
|
|
15
|
+
|
|
16
|
+
## Use it (published package)
|
|
17
|
+
|
|
18
|
+
Once published to npm, any platform user connects with one line — nothing to clone or build:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
claude mcp add practicemakes \
|
|
22
|
+
--env FIREBASE_REFRESH_TOKEN=<your-refresh-token> \
|
|
23
|
+
-- npx -y @practicemakes/mcp
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Point at a local backend during development with `--env PM_API_BASE_URL=http://localhost:9090`.
|
|
27
|
+
|
|
28
|
+
### Getting a refresh token
|
|
29
|
+
|
|
30
|
+
- **Password account:** create `.env` (see `.env.example`) with `PM_EMAIL` / `PM_PASSWORD`, run `yarn workspace @practicemakes/mcp login` — it prints a `FIREBASE_REFRESH_TOKEN=...` line to use.
|
|
31
|
+
- **Google/Apple sign-in:** log into the web app, open DevTools → Application → IndexedDB → `firebaseLocalStorage` → the auth user record, and copy `stsTokenManager.refreshToken`.
|
|
32
|
+
|
|
33
|
+
## Develop (from the monorepo)
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
yarn install # at repo root, links the workspace
|
|
37
|
+
yarn workspace @practicemakes/mcp build
|
|
38
|
+
cp packages/mcp/.env.example packages/mcp/.env # then fill in a credential
|
|
39
|
+
yarn workspace @practicemakes/mcp smoke # end-to-end check against prod
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`smoke` prints who you're authenticated as, how many orgs are visible, and your Planner backlog. To run the local build directly in Claude instead of the published package:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
claude mcp add practicemakes \
|
|
46
|
+
--env FIREBASE_REFRESH_TOKEN=... \
|
|
47
|
+
-- node /Users/johnfonte/IdeaProjects/common-frontend/packages/mcp/dist/index.js
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Publish
|
|
51
|
+
|
|
52
|
+
Versioned on the monorepo's calendar scheme (`26.MMDD.patch`). Scoped public package:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
yarn workspace @practicemakes/mcp publish # runs prepublishOnly (clean + build) first
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Tools (v1, read-only)
|
|
59
|
+
|
|
60
|
+
| Tool | What it returns |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `list_organizations` | Orgs visible to you (SYSADMIN → all) |
|
|
63
|
+
| `get_organization` | One org by UUID |
|
|
64
|
+
| `get_my_profile` | Your profile + entitlements/roles/org context |
|
|
65
|
+
| `list_profiles` | Profiles (SYSADMIN → all, ADMIN → your org) |
|
|
66
|
+
| `get_profile` | One profile by UUID |
|
|
67
|
+
| `get_my_planner_tasks` | Your Planner backlog (drives development) |
|
|
68
|
+
| `search_tasks` | Search tasks; reveal completed/deleted/snoozed via `showFields` |
|
|
69
|
+
| `get_task` | One task by UUID |
|
|
70
|
+
| `list_workflows` | Your Planner workflows |
|
|
71
|
+
| `get_workflow` | One workflow, hydrated with run state |
|
|
72
|
+
| `get_billing_config` | Billing/entitlement config for your org |
|
|
73
|
+
|
|
74
|
+
## Roadmap
|
|
75
|
+
|
|
76
|
+
- **Write tools** (complete/create a task, switch org context, admin actions) with per-call confirmation.
|
|
77
|
+
- **OpenAPI-driven** tool generation from the backend's grouped specs (the monorepo already uses `openapi-typescript` in `packages/core`).
|
|
78
|
+
- **Hosted remote server** (streamable-HTTP + per-user OAuth) so users connect to a URL with nothing to install.
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Config } from "./config.js";
|
|
2
|
+
/** Exchange an email/password for Firebase tokens (password accounts only). */
|
|
3
|
+
export declare function signInWithPassword(apiKey: string, email: string, password: string): Promise<{
|
|
4
|
+
idToken: string;
|
|
5
|
+
refreshToken: string;
|
|
6
|
+
expiresInSeconds: number;
|
|
7
|
+
}>;
|
|
8
|
+
/**
|
|
9
|
+
* Holds the caller's Firebase identity and hands out a valid ID token on demand,
|
|
10
|
+
* transparently refreshing before expiry. Networks are only touched lazily on the
|
|
11
|
+
* first token request, so an invalid credential surfaces on the first tool call,
|
|
12
|
+
* not at process start.
|
|
13
|
+
*/
|
|
14
|
+
export declare class TokenManager {
|
|
15
|
+
private apiKey;
|
|
16
|
+
private refreshToken?;
|
|
17
|
+
private email?;
|
|
18
|
+
private password?;
|
|
19
|
+
private cachedIdToken?;
|
|
20
|
+
private expiresAtMs;
|
|
21
|
+
private inFlight?;
|
|
22
|
+
constructor(config: Config);
|
|
23
|
+
getIdToken(): Promise<string>;
|
|
24
|
+
private fetchFreshToken;
|
|
25
|
+
}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const IDENTITY_TOOLKIT = "https://identitytoolkit.googleapis.com/v1";
|
|
2
|
+
const SECURE_TOKEN = "https://securetoken.googleapis.com/v1";
|
|
3
|
+
/** Abort a Firebase auth request that hangs longer than this so token refresh can't wedge a tool call. */
|
|
4
|
+
const AUTH_TIMEOUT_MS = 15_000;
|
|
5
|
+
/** Exchange an email/password for Firebase tokens (password accounts only). */
|
|
6
|
+
export async function signInWithPassword(apiKey, email, password) {
|
|
7
|
+
const res = await fetch(`${IDENTITY_TOOLKIT}/accounts:signInWithPassword?key=${apiKey}`, {
|
|
8
|
+
method: "POST",
|
|
9
|
+
headers: { "Content-Type": "application/json" },
|
|
10
|
+
body: JSON.stringify({ email, password, returnSecureToken: true }),
|
|
11
|
+
signal: AbortSignal.timeout(AUTH_TIMEOUT_MS),
|
|
12
|
+
});
|
|
13
|
+
const body = (await res.json());
|
|
14
|
+
if (!res.ok) {
|
|
15
|
+
throw new Error(`Firebase sign-in failed (${res.status}): ${body?.error?.message ?? JSON.stringify(body)}`);
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
idToken: body.idToken,
|
|
19
|
+
refreshToken: body.refreshToken,
|
|
20
|
+
expiresInSeconds: Number(body.expiresIn),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Exchange a refresh token for a fresh ID token (provider-agnostic). */
|
|
24
|
+
async function refreshIdToken(apiKey, refreshToken) {
|
|
25
|
+
const res = await fetch(`${SECURE_TOKEN}/token?key=${apiKey}`, {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
28
|
+
body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken }),
|
|
29
|
+
signal: AbortSignal.timeout(AUTH_TIMEOUT_MS),
|
|
30
|
+
});
|
|
31
|
+
const body = (await res.json());
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
throw new Error(`Firebase token refresh failed (${res.status}): ${body?.error?.message ?? JSON.stringify(body)}`);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
idToken: body.id_token,
|
|
37
|
+
refreshToken: body.refresh_token,
|
|
38
|
+
expiresInSeconds: Number(body.expires_in),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Holds the caller's Firebase identity and hands out a valid ID token on demand,
|
|
43
|
+
* transparently refreshing before expiry. Networks are only touched lazily on the
|
|
44
|
+
* first token request, so an invalid credential surfaces on the first tool call,
|
|
45
|
+
* not at process start.
|
|
46
|
+
*/
|
|
47
|
+
export class TokenManager {
|
|
48
|
+
apiKey;
|
|
49
|
+
refreshToken;
|
|
50
|
+
email;
|
|
51
|
+
password;
|
|
52
|
+
cachedIdToken;
|
|
53
|
+
expiresAtMs = 0;
|
|
54
|
+
inFlight;
|
|
55
|
+
constructor(config) {
|
|
56
|
+
this.apiKey = config.firebaseApiKey;
|
|
57
|
+
this.refreshToken = config.refreshToken;
|
|
58
|
+
this.email = config.email;
|
|
59
|
+
this.password = config.password;
|
|
60
|
+
}
|
|
61
|
+
async getIdToken() {
|
|
62
|
+
// 60s safety buffer so a token never expires mid-request.
|
|
63
|
+
if (this.cachedIdToken && Date.now() < this.expiresAtMs - 60_000) {
|
|
64
|
+
return this.cachedIdToken;
|
|
65
|
+
}
|
|
66
|
+
// Coalesce concurrent refreshes into one network call.
|
|
67
|
+
if (!this.inFlight) {
|
|
68
|
+
this.inFlight = this.fetchFreshToken().finally(() => {
|
|
69
|
+
this.inFlight = undefined;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return this.inFlight;
|
|
73
|
+
}
|
|
74
|
+
async fetchFreshToken() {
|
|
75
|
+
let result;
|
|
76
|
+
if (this.refreshToken) {
|
|
77
|
+
result = await refreshIdToken(this.apiKey, this.refreshToken);
|
|
78
|
+
}
|
|
79
|
+
else if (this.email && this.password) {
|
|
80
|
+
// Bootstrap from password once; thereafter we have a refresh token to reuse.
|
|
81
|
+
result = await signInWithPassword(this.apiKey, this.email, this.password);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
throw new Error("No Firebase credential available to obtain an ID token.");
|
|
85
|
+
}
|
|
86
|
+
this.refreshToken = result.refreshToken;
|
|
87
|
+
this.cachedIdToken = result.idToken;
|
|
88
|
+
this.expiresAtMs = Date.now() + result.expiresInSeconds * 1000;
|
|
89
|
+
return result.idToken;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads and validates configuration from the environment.
|
|
3
|
+
*
|
|
4
|
+
* Auth is per-user: the server acts as whoever the supplied Firebase credential
|
|
5
|
+
* belongs to. All data access is therefore scoped to that user's real role and
|
|
6
|
+
* organization by the backend (a SYSADMIN sees everything; a regular user sees
|
|
7
|
+
* only their org). This is what lets the same server be handed to other users.
|
|
8
|
+
*/
|
|
9
|
+
export interface Config {
|
|
10
|
+
apiBaseUrl: string;
|
|
11
|
+
firebaseApiKey: string;
|
|
12
|
+
/** Provider-agnostic refresh token (preferred). */
|
|
13
|
+
refreshToken?: string;
|
|
14
|
+
/** Password-account bootstrap (used only if no refresh token is present). */
|
|
15
|
+
email?: string;
|
|
16
|
+
password?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function loadConfig(): Config;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public Firebase Web API key for the "practicemakes-sports" project. This is a
|
|
3
|
+
* client-side identifier (it ships in the web/mobile bundles), not a secret —
|
|
4
|
+
* baking it in means users only need to supply their own account credential.
|
|
5
|
+
* Overridable via FIREBASE_API_KEY for other environments.
|
|
6
|
+
*/
|
|
7
|
+
const DEFAULT_FIREBASE_API_KEY = "AIzaSyB-Ipn44LycKj7GFbKP124htwIKnLqeia0";
|
|
8
|
+
export function loadConfig() {
|
|
9
|
+
const apiBaseUrl = (process.env.PM_API_BASE_URL ?? "https://api.practicemakes.io").replace(/\/+$/, "");
|
|
10
|
+
const firebaseApiKey = process.env.FIREBASE_API_KEY?.trim() || DEFAULT_FIREBASE_API_KEY;
|
|
11
|
+
const refreshToken = process.env.FIREBASE_REFRESH_TOKEN?.trim() || undefined;
|
|
12
|
+
const email = process.env.PM_EMAIL?.trim() || undefined;
|
|
13
|
+
const password = process.env.PM_PASSWORD || undefined;
|
|
14
|
+
if (!refreshToken && !(email && password)) {
|
|
15
|
+
throw new Error("No credential provided. Set FIREBASE_REFRESH_TOKEN, or PM_EMAIL + PM_PASSWORD.");
|
|
16
|
+
}
|
|
17
|
+
return { apiBaseUrl, firebaseApiKey, refreshToken, email, password };
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAkBA;;;;;GAKG;AACH,MAAM,wBAAwB,GAAG,yCAAyC,CAAC;AAE3E,MAAM,UAAU,UAAU;IACxB,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,8BAA8B,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACvG,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,EAAE,IAAI,wBAAwB,CAAC;IACxF,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,EAAE,IAAI,EAAE,IAAI,SAAS,CAAC;IAC7E,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,SAAS,CAAC;IACxD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,SAAS,CAAC;IAEtD,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC,KAAK,IAAI,QAAQ,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CACb,gFAAgF,CACjF,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AACvE,CAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { loadConfig } from "./config.js";
|
|
5
|
+
import { TokenManager } from "./auth.js";
|
|
6
|
+
import { PmClient } from "./pmClient.js";
|
|
7
|
+
import { registerOrganizationTools } from "./tools/organizations.js";
|
|
8
|
+
import { registerProfileTools } from "./tools/profiles.js";
|
|
9
|
+
import { registerPlannerTools } from "./tools/planner.js";
|
|
10
|
+
import { registerBillingTools } from "./tools/billing.js";
|
|
11
|
+
async function main() {
|
|
12
|
+
const config = loadConfig();
|
|
13
|
+
const tokens = new TokenManager(config);
|
|
14
|
+
const client = new PmClient(config.apiBaseUrl, tokens);
|
|
15
|
+
const server = new McpServer({
|
|
16
|
+
name: "practicemakes-mcp",
|
|
17
|
+
version: "0.1.0",
|
|
18
|
+
});
|
|
19
|
+
registerOrganizationTools(server, client);
|
|
20
|
+
registerProfileTools(server, client);
|
|
21
|
+
registerPlannerTools(server, client);
|
|
22
|
+
registerBillingTools(server, client);
|
|
23
|
+
const transport = new StdioServerTransport();
|
|
24
|
+
await server.connect(transport);
|
|
25
|
+
// stdout is the MCP transport; log to stderr only.
|
|
26
|
+
console.error(`practicemakes-mcp connected (base=${config.apiBaseUrl})`);
|
|
27
|
+
}
|
|
28
|
+
main().catch((err) => {
|
|
29
|
+
console.error("Fatal:", err instanceof Error ? err.message : err);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"}
|
package/dist/login.d.ts
ADDED
package/dist/login.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* One-time helper for password accounts: exchange PM_EMAIL + PM_PASSWORD for a
|
|
4
|
+
* Firebase refresh token you can paste into FIREBASE_REFRESH_TOKEN.
|
|
5
|
+
*
|
|
6
|
+
* Run: npm run login (loads .env)
|
|
7
|
+
*
|
|
8
|
+
* OAuth (Google/Apple) accounts have no password — grab a refresh token from the
|
|
9
|
+
* web app instead (see README).
|
|
10
|
+
*/
|
|
11
|
+
import { loadConfig } from "./config.js";
|
|
12
|
+
import { signInWithPassword } from "./auth.js";
|
|
13
|
+
async function main() {
|
|
14
|
+
const cfg = loadConfig();
|
|
15
|
+
if (!cfg.email || !cfg.password) {
|
|
16
|
+
throw new Error("Set PM_EMAIL and PM_PASSWORD in your .env to use `npm run login`.");
|
|
17
|
+
}
|
|
18
|
+
const { refreshToken } = await signInWithPassword(cfg.firebaseApiKey, cfg.email, cfg.password);
|
|
19
|
+
console.log("\nSuccess. Add this to your .env and remove PM_EMAIL / PM_PASSWORD:\n");
|
|
20
|
+
console.log(`FIREBASE_REFRESH_TOKEN=${refreshToken}\n`);
|
|
21
|
+
}
|
|
22
|
+
main().catch((err) => {
|
|
23
|
+
console.error("Login failed:", err instanceof Error ? err.message : err);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
26
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE/C,KAAK,UAAU,IAAI;IACjB,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,IAAI,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAC;IACvF,CAAC;IACD,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,kBAAkB,CAAC,GAAG,CAAC,cAAc,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC/F,OAAO,CAAC,GAAG,CAAC,uEAAuE,CAAC,CAAC;IACrF,OAAO,CAAC,GAAG,CAAC,0BAA0B,YAAY,IAAI,CAAC,CAAC;AAC1D,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACzE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TokenManager } from "./auth.js";
|
|
2
|
+
/** Raised on any non-2xx response; carries status + body for surfacing to the tool caller. */
|
|
3
|
+
export declare class PmApiError extends Error {
|
|
4
|
+
status: number;
|
|
5
|
+
bodyText: string;
|
|
6
|
+
method: string;
|
|
7
|
+
path: string;
|
|
8
|
+
constructor(status: number, bodyText: string, method: string, path: string);
|
|
9
|
+
}
|
|
10
|
+
/** Thin authenticated fetch wrapper over the PracticeMakes REST API. */
|
|
11
|
+
export declare class PmClient {
|
|
12
|
+
private baseUrl;
|
|
13
|
+
private tokens;
|
|
14
|
+
constructor(baseUrl: string, tokens: TokenManager);
|
|
15
|
+
get(path: string): Promise<unknown>;
|
|
16
|
+
post(path: string, body: unknown): Promise<unknown>;
|
|
17
|
+
private request;
|
|
18
|
+
}
|
package/dist/pmClient.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** Abort an API request that hangs longer than this so a stuck backend can't wedge a tool call. */
|
|
2
|
+
const REQUEST_TIMEOUT_MS = 15_000;
|
|
3
|
+
/** Raised on any non-2xx response; carries status + body for surfacing to the tool caller. */
|
|
4
|
+
export class PmApiError extends Error {
|
|
5
|
+
status;
|
|
6
|
+
bodyText;
|
|
7
|
+
method;
|
|
8
|
+
path;
|
|
9
|
+
constructor(status, bodyText, method, path) {
|
|
10
|
+
super(`${method} ${path} -> ${status}: ${bodyText.slice(0, 800)}`);
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.bodyText = bodyText;
|
|
13
|
+
this.method = method;
|
|
14
|
+
this.path = path;
|
|
15
|
+
this.name = "PmApiError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** Thin authenticated fetch wrapper over the PracticeMakes REST API. */
|
|
19
|
+
export class PmClient {
|
|
20
|
+
baseUrl;
|
|
21
|
+
tokens;
|
|
22
|
+
constructor(baseUrl, tokens) {
|
|
23
|
+
this.baseUrl = baseUrl;
|
|
24
|
+
this.tokens = tokens;
|
|
25
|
+
}
|
|
26
|
+
async get(path) {
|
|
27
|
+
return this.request("GET", path);
|
|
28
|
+
}
|
|
29
|
+
async post(path, body) {
|
|
30
|
+
return this.request("POST", path, body);
|
|
31
|
+
}
|
|
32
|
+
async request(method, path, body) {
|
|
33
|
+
const idToken = await this.tokens.getIdToken();
|
|
34
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
35
|
+
method,
|
|
36
|
+
headers: {
|
|
37
|
+
Authorization: `Bearer ${idToken}`,
|
|
38
|
+
Accept: "application/json",
|
|
39
|
+
...(body !== undefined ? { "Content-Type": "application/json" } : {}),
|
|
40
|
+
},
|
|
41
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
42
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
43
|
+
});
|
|
44
|
+
const text = await res.text();
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
throw new PmApiError(res.status, text, method, path);
|
|
47
|
+
}
|
|
48
|
+
if (!text)
|
|
49
|
+
return null;
|
|
50
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
51
|
+
return contentType.includes("application/json") ? JSON.parse(text) : text;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=pmClient.js.map
|
|
@@ -0,0 +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"}
|
package/dist/smoke.d.ts
ADDED
package/dist/smoke.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* End-to-end smoke test: authenticate and hit a couple of read-only endpoints,
|
|
4
|
+
* printing what came back. Confirms the Firebase credential + backend authz work
|
|
5
|
+
* before wiring the server into Claude. Run: npm run smoke
|
|
6
|
+
*/
|
|
7
|
+
import { loadConfig } from "./config.js";
|
|
8
|
+
import { TokenManager } from "./auth.js";
|
|
9
|
+
import { PmClient } from "./pmClient.js";
|
|
10
|
+
async function main() {
|
|
11
|
+
const config = loadConfig();
|
|
12
|
+
const client = new PmClient(config.apiBaseUrl, new TokenManager(config));
|
|
13
|
+
console.log(`Base URL: ${config.apiBaseUrl}\n`);
|
|
14
|
+
console.log("→ GET /api/profile (who am I?)");
|
|
15
|
+
const profile = (await client.get("/api/profile"));
|
|
16
|
+
console.log(` ${profile?.emailAddress ?? profile?.email ?? "?"} — roles: ${JSON.stringify(profile?.roles ?? profile?.authorities ?? "?")}\n`);
|
|
17
|
+
console.log("→ GET /api/organization/all");
|
|
18
|
+
const orgs = (await client.get("/api/organization/all"));
|
|
19
|
+
console.log(` ${Array.isArray(orgs) ? orgs.length : 0} organization(s) visible\n`);
|
|
20
|
+
console.log("→ GET /api/task (my Planner backlog)");
|
|
21
|
+
const tasks = (await client.get("/api/task"));
|
|
22
|
+
console.log(` ${Array.isArray(tasks) ? tasks.length : 0} task(s)`);
|
|
23
|
+
if (Array.isArray(tasks)) {
|
|
24
|
+
for (const t of tasks.slice(0, 5))
|
|
25
|
+
console.log(` - [${t.priorityValue ?? "?"}] ${t.name}`);
|
|
26
|
+
}
|
|
27
|
+
console.log("\n✓ Smoke test passed.");
|
|
28
|
+
}
|
|
29
|
+
main().catch((err) => {
|
|
30
|
+
console.error("\n✗ Smoke test failed:", err instanceof Error ? err.message : err);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
});
|
|
33
|
+
//# sourceMappingURL=smoke.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke.js","sourceRoot":"","sources":["../src/smoke.ts"],"names":[],"mappings":";AACA;;;;GAIG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;IACzE,OAAO,CAAC,GAAG,CAAC,aAAa,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC;IAEhD,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAQ,CAAC;IAC1D,OAAO,CAAC,GAAG,CACT,MAAM,OAAO,EAAE,YAAY,IAAI,OAAO,EAAE,KAAK,IAAI,GAAG,aAAa,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,IAAI,OAAO,EAAE,WAAW,IAAI,GAAG,CAAC,IAAI,CACnI,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAU,CAAC;IAClE,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC;IAErF,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,CAAU,CAAC;IACvD,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IACrE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,aAAa,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/F,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;AACxC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAClF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { runTool } from "./shared.js";
|
|
2
|
+
export function registerBillingTools(server, client) {
|
|
3
|
+
server.registerTool("get_billing_config", {
|
|
4
|
+
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")));
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=billing.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runTool } from "./shared.js";
|
|
3
|
+
export function registerOrganizationTools(server, client) {
|
|
4
|
+
server.registerTool("list_organizations", {
|
|
5
|
+
title: "List organizations",
|
|
6
|
+
description: "List organizations visible to your identity. A SYSADMIN sees every organization on the platform; other users see only the orgs they belong to.",
|
|
7
|
+
inputSchema: {},
|
|
8
|
+
}, async () => runTool(() => client.get("/api/organization/all")));
|
|
9
|
+
server.registerTool("get_organization", {
|
|
10
|
+
title: "Get organization",
|
|
11
|
+
description: "Fetch a single organization by its UUID.",
|
|
12
|
+
inputSchema: {
|
|
13
|
+
organizationId: z.string().uuid().describe("Organization UUID"),
|
|
14
|
+
},
|
|
15
|
+
}, async ({ organizationId }) => runTool(() => client.get(`/api/organization/${organizationId}`)));
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=organizations.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runTool } from "./shared.js";
|
|
3
|
+
export function registerPlannerTools(server, client) {
|
|
4
|
+
server.registerTool("get_my_planner_tasks", {
|
|
5
|
+
title: "Get my Planner tasks",
|
|
6
|
+
description: "Your personal Planner backlog: active tasks for your profile in your current organization context, filtered to your enabled tags. Ordered by priorityValue (ascending = higher on the backlog). Status is derived from timestamps — completedAt/deletedAt/snoozeUntil being null means active. This is the list meant to drive development.",
|
|
7
|
+
inputSchema: {},
|
|
8
|
+
}, async () => runTool(() => client.get("/api/task")));
|
|
9
|
+
server.registerTool("search_tasks", {
|
|
10
|
+
title: "Search Planner tasks",
|
|
11
|
+
description: "Search your Planner tasks (Elasticsearch when available, otherwise your full task list). Use showFields to include normally-hidden tasks: pass any of 'completedAt', 'deletedAt', 'snoozeUntil' to reveal completed/deleted/snoozed tasks.",
|
|
12
|
+
inputSchema: {
|
|
13
|
+
searchTerm: z.string().default("").describe("Free-text query; empty returns your task list."),
|
|
14
|
+
selectedTags: z
|
|
15
|
+
.array(z.string())
|
|
16
|
+
.default([])
|
|
17
|
+
.describe("Tag IDs to filter by."),
|
|
18
|
+
showFields: z
|
|
19
|
+
.array(z.enum(["completedAt", "deletedAt", "snoozeUntil"]))
|
|
20
|
+
.default([])
|
|
21
|
+
.describe("Reveal otherwise-hidden tasks (completed/deleted/snoozed)."),
|
|
22
|
+
sortField: z.string().default("createdAt").describe("Field to sort by."),
|
|
23
|
+
sortDirection: z.enum(["Asc", "Desc"]).default("Desc").describe("Sort direction."),
|
|
24
|
+
},
|
|
25
|
+
}, async ({ searchTerm, selectedTags, showFields, sortField, sortDirection }) => runTool(() => client.post("/api/task/search", {
|
|
26
|
+
searchTerm,
|
|
27
|
+
filters: { showFields, selectedTags },
|
|
28
|
+
sort: { field: sortField, direction: sortDirection },
|
|
29
|
+
})));
|
|
30
|
+
server.registerTool("get_task", {
|
|
31
|
+
title: "Get Planner task",
|
|
32
|
+
description: "Fetch a single Planner task by UUID.",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
taskId: z.string().uuid().describe("Task UUID"),
|
|
35
|
+
},
|
|
36
|
+
}, async ({ taskId }) => runTool(() => client.get(`/api/task/${taskId}`)));
|
|
37
|
+
server.registerTool("list_workflows", {
|
|
38
|
+
title: "List workflows",
|
|
39
|
+
description: "List your Planner workflows (reusable task templates that spawn tasks through steps).",
|
|
40
|
+
inputSchema: {},
|
|
41
|
+
}, async () => runTool(() => client.get("/api/workflow")));
|
|
42
|
+
server.registerTool("get_workflow", {
|
|
43
|
+
title: "Get workflow",
|
|
44
|
+
description: "Fetch a workflow by UUID, hydrated with its current run state: active tasks, pending/scheduled tasks, run events, and archived tasks.",
|
|
45
|
+
inputSchema: {
|
|
46
|
+
workflowId: z.string().uuid().describe("Workflow UUID"),
|
|
47
|
+
},
|
|
48
|
+
}, async ({ workflowId }) => runTool(() => client.get(`/api/workflow/${workflowId}`)));
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=planner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"planner.js","sourceRoot":"","sources":["../../src/tools/planner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runTool } from "./shared.js";
|
|
3
|
+
export function registerProfileTools(server, client) {
|
|
4
|
+
server.registerTool("get_my_profile", {
|
|
5
|
+
title: "Get my profile",
|
|
6
|
+
description: "Fetch the authenticated user's own profile, including computed entitlements, roles, current organization context, and account status.",
|
|
7
|
+
inputSchema: {},
|
|
8
|
+
}, async () => runTool(() => client.get("/api/profile")));
|
|
9
|
+
server.registerTool("list_profiles", {
|
|
10
|
+
title: "List profiles",
|
|
11
|
+
description: "List user profiles. A SYSADMIN sees all profiles across every org; an ADMIN sees profiles within their organization only.",
|
|
12
|
+
inputSchema: {},
|
|
13
|
+
}, async () => runTool(() => client.get("/api/profile/all")));
|
|
14
|
+
server.registerTool("get_profile", {
|
|
15
|
+
title: "Get profile",
|
|
16
|
+
description: "Fetch a single profile by UUID (includes computed entitlements/status). SYSADMIN can view any profile; ADMIN only within their org.",
|
|
17
|
+
inputSchema: {
|
|
18
|
+
profileId: z.string().uuid().describe("Profile UUID"),
|
|
19
|
+
},
|
|
20
|
+
}, async ({ profileId }) => runTool(() => client.get(`/api/profile/profile/${profileId}`)));
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=profiles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profiles.js","sourceRoot":"","sources":["../../src/tools/profiles.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,gBAAgB,EAChB;QACE,KAAK,EAAE,gBAAgB;QACvB,WAAW,EACT,uIAAuI;QACzI,WAAW,EAAE,EAAE;KAChB,EACD,KAAK,IAAI,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CACtD,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,eAAe,EACf;QACE,KAAK,EAAE,eAAe;QACtB,WAAW,EACT,2HAA2H;QAC7H,WAAW,EAAE,EAAE;KAChB,EACD,KAAK,IAAI,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAC1D,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,aAAa,EACb;QACE,KAAK,EAAE,aAAa;QACpB,WAAW,EACT,qIAAqI;QACvI,WAAW,EAAE;YACX,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;SACtD;KACF,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,wBAAwB,SAAS,EAAE,CAAC,CAAC,CACxF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** MCP tool result carrying pretty-printed JSON. */
|
|
2
|
+
export declare function jsonResult(data: unknown): {
|
|
3
|
+
content: {
|
|
4
|
+
type: "text";
|
|
5
|
+
text: string;
|
|
6
|
+
}[];
|
|
7
|
+
};
|
|
8
|
+
/** MCP error result — surfaces HTTP status/body so the caller understands scope/auth failures. */
|
|
9
|
+
export declare function errorResult(err: unknown): {
|
|
10
|
+
content: {
|
|
11
|
+
type: "text";
|
|
12
|
+
text: string;
|
|
13
|
+
}[];
|
|
14
|
+
isError: true;
|
|
15
|
+
};
|
|
16
|
+
/** Wraps a tool body so every tool reports errors uniformly instead of throwing. */
|
|
17
|
+
export declare function runTool(fn: () => Promise<unknown>): Promise<{
|
|
18
|
+
content: {
|
|
19
|
+
type: "text";
|
|
20
|
+
text: string;
|
|
21
|
+
}[];
|
|
22
|
+
}>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { PmApiError } from "../pmClient.js";
|
|
2
|
+
/** MCP tool result carrying pretty-printed JSON. */
|
|
3
|
+
export function jsonResult(data) {
|
|
4
|
+
const text = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
5
|
+
return { content: [{ type: "text", text }] };
|
|
6
|
+
}
|
|
7
|
+
/** MCP error result — surfaces HTTP status/body so the caller understands scope/auth failures. */
|
|
8
|
+
export function errorResult(err) {
|
|
9
|
+
let text;
|
|
10
|
+
if (err instanceof PmApiError) {
|
|
11
|
+
const hint = err.status === 401 || err.status === 403
|
|
12
|
+
? " (your identity may lack the role/org access for this data)"
|
|
13
|
+
: "";
|
|
14
|
+
text = `PracticeMakes API error ${err.status}${hint}: ${err.bodyText.slice(0, 1000)}`;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
text = err instanceof Error ? err.message : String(err);
|
|
18
|
+
}
|
|
19
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
20
|
+
}
|
|
21
|
+
/** Wraps a tool body so every tool reports errors uniformly instead of throwing. */
|
|
22
|
+
export async function runTool(fn) {
|
|
23
|
+
try {
|
|
24
|
+
return jsonResult(await fn());
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
return errorResult(err);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/tools/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,oDAAoD;AACpD,MAAM,UAAU,UAAU,CAAC,IAAa;IACtC,MAAM,IAAI,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC7E,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AACxD,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,WAAW,CAAC,GAAY;IACtC,IAAI,IAAY,CAAC;IACjB,IAAI,GAAG,YAAY,UAAU,EAAE,CAAC;QAC9B,MAAM,IAAI,GACR,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;YACtC,CAAC,CAAC,6DAA6D;YAC/D,CAAC,CAAC,EAAE,CAAC;QACT,IAAI,GAAG,2BAA2B,GAAG,CAAC,MAAM,GAAG,IAAI,KAAK,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC;IACxF,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAa,EAAE,CAAC;AAChF,CAAC;AAED,oFAAoF;AACpF,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,EAA0B;IACtD,IAAI,CAAC;QACH,OAAO,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@practicemakes/mcp",
|
|
3
|
+
"version": "26.710.3",
|
|
4
|
+
"description": "MCP server exposing PracticeMakes prod data (read-only) to Claude, authenticated as the calling user's own Firebase identity.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"practicemakes-mcp": "dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"dev": "tsc --watch",
|
|
21
|
+
"clean": "rimraf dist",
|
|
22
|
+
"clean-build": "yarn clean && yarn build",
|
|
23
|
+
"start": "node dist/index.js",
|
|
24
|
+
"smoke": "node --env-file=.env dist/smoke.js",
|
|
25
|
+
"login": "node --env-file=.env dist/login.js",
|
|
26
|
+
"prepublishOnly": "yarn clean && yarn build"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
30
|
+
"zod": "^3.23.8"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^20.14.0",
|
|
34
|
+
"rimraf": "4.1.2",
|
|
35
|
+
"typescript": "6.0.3"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=20.6.0"
|
|
39
|
+
}
|
|
40
|
+
}
|