jujugrowth-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +47 -0
  2. package/package.json +11 -0
  3. package/server.mjs +126 -0
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # @jujugrowth/mcp
2
+
3
+ A dependency-free MCP (Model Context Protocol) stdio server that connects your
4
+ dev-AI — **Claude Desktop, Claude Code, or Cursor** — directly to jujugrowth.
5
+ Your AI pulls recommendations and their implementable briefs, makes the changes
6
+ **in your own repo** (where you review them via git), and marks them handled —
7
+ no copy-paste.
8
+
9
+ jujugrowth stays read-only to your systems: this server only reads
10
+ recommendations and writes status back to jujugrowth.
11
+
12
+ ## Setup
13
+
14
+ 1. In jujugrowth: **Settings → Developer → Generate API token** (copy it — shown once).
15
+ 2. Add it to your AI tool (path-free via npx — no local install):
16
+
17
+ **Claude Code:**
18
+
19
+ ```bash
20
+ claude mcp add jujugrowth --scope user \
21
+ --env JUJUGROWTH_TOKEN=jgt_your_token_here \
22
+ -- npx -y jujugrowth-mcp
23
+ ```
24
+
25
+ **Claude Desktop** (`claude_desktop_config.json`) / **Cursor** (`.cursor/mcp.json`):
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "jujugrowth": {
31
+ "command": "npx",
32
+ "args": ["-y", "jujugrowth-mcp"],
33
+ "env": { "JUJUGROWTH_TOKEN": "jgt_your_token_here" }
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ The jujugrowth Settings → Developer panel generates the token and shows these
40
+ commands pre-filled.
41
+
42
+ 3. Restart the client. You'll have three tools:
43
+ - `list_recommendations` — open recommendations across your businesses
44
+ - `get_recommendation` — the full brief for one (implement it in your repo)
45
+ - `mark_recommendation_handled` — mark it done after you've shipped it
46
+
47
+ Requires Node ≥ 22. Optional env `JUJUGROWTH_API` (defaults to https://jujugrowth.com).
package/package.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "jujugrowth-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server connecting your AI coding assistant (Claude, Cursor, Codex) to jujugrowth — pull recommendations and apply them in your repo.",
5
+ "type": "module",
6
+ "bin": { "jujugrowth-mcp": "server.mjs" },
7
+ "files": ["server.mjs", "README.md"],
8
+ "engines": { "node": ">=22" },
9
+ "license": "UNLICENSED",
10
+ "publishConfig": { "access": "public" }
11
+ }
package/server.mjs ADDED
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * jujugrowth MCP server (dependency-free stdio JSON-RPC, Node ≥ 22).
4
+ *
5
+ * Lets your dev-AI (Claude Desktop / Claude Code / Cursor) pull jujugrowth
6
+ * recommendations + their implementable briefs, and mark them handled — no
7
+ * copy-paste. jujugrowth stays read-only to your systems: this server only
8
+ * READS recommendations and WRITES status back to jujugrowth. The dev-AI does
9
+ * the actual code changes in YOUR repo, where you review them via git.
10
+ *
11
+ * Config (in your AI client's mcp settings):
12
+ * command: node
13
+ * args: ["/abs/path/to/packages/mcp/server.mjs"]
14
+ * env: { "JUJUGROWTH_TOKEN": "jgt_…" } // from Settings → Developer
15
+ * Optional env: JUJUGROWTH_API (default https://jujugrowth.com)
16
+ */
17
+
18
+ const API = (process.env.JUJUGROWTH_API ?? "https://jujugrowth.com").replace(/\/+$/, "");
19
+ const TOKEN = process.env.JUJUGROWTH_TOKEN ?? "";
20
+
21
+ async function call(method, path, body) {
22
+ const res = await fetch(`${API}/api/mcp${path}`, {
23
+ method,
24
+ headers: {
25
+ authorization: `Bearer ${TOKEN}`,
26
+ ...(body ? { "content-type": "application/json" } : {}),
27
+ },
28
+ ...(body ? { body: JSON.stringify(body) } : {}),
29
+ });
30
+ const text = await res.text();
31
+ if (!res.ok) throw new Error(`jujugrowth ${res.status}: ${text.slice(0, 300)}`);
32
+ return text ? JSON.parse(text) : {};
33
+ }
34
+
35
+ const TOOLS = [
36
+ {
37
+ name: "list_recommendations",
38
+ description:
39
+ "List open jujugrowth recommendations across the businesses you can access. Returns id, tenantId, title, action_type, risk_class.",
40
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
41
+ run: async () => call("GET", "/recommendations"),
42
+ },
43
+ {
44
+ name: "get_recommendation",
45
+ description:
46
+ "Get the full implementable brief for one recommendation (title, body, and a ready-to-implement claude_prompt when present). Implement it in the user's repo, scoped to wording/structure/SEO — never touch checkout, auth, or analytics events.",
47
+ inputSchema: {
48
+ type: "object",
49
+ properties: { tenantId: { type: "string" }, recId: { type: "string" } },
50
+ required: ["tenantId", "recId"],
51
+ additionalProperties: false,
52
+ },
53
+ run: async (a) => call("GET", `/recommendations/${a.tenantId}/${a.recId}`),
54
+ },
55
+ {
56
+ name: "mark_recommendation_handled",
57
+ description:
58
+ "Mark a recommendation handled in jujugrowth (status → approved) AFTER you've implemented it in the user's repo. Writes back to jujugrowth only.",
59
+ inputSchema: {
60
+ type: "object",
61
+ properties: { tenantId: { type: "string" }, recId: { type: "string" } },
62
+ required: ["tenantId", "recId"],
63
+ additionalProperties: false,
64
+ },
65
+ run: async (a) => call("POST", `/recommendations/${a.tenantId}/${a.recId}/handled`),
66
+ },
67
+ ];
68
+
69
+ function send(msg) {
70
+ process.stdout.write(`${JSON.stringify(msg)}\n`);
71
+ }
72
+
73
+ async function handle(msg) {
74
+ const { id, method, params } = msg;
75
+ if (method === "initialize") {
76
+ return send({
77
+ jsonrpc: "2.0",
78
+ id,
79
+ result: {
80
+ protocolVersion: "2024-11-05",
81
+ capabilities: { tools: {} },
82
+ serverInfo: { name: "jujugrowth", version: "1.0.0" },
83
+ },
84
+ });
85
+ }
86
+ if (method === "notifications/initialized") return; // no response to notifications
87
+ if (method === "tools/list") {
88
+ return send({
89
+ jsonrpc: "2.0",
90
+ id,
91
+ result: { tools: TOOLS.map(({ name, description, inputSchema }) => ({ name, description, inputSchema })) },
92
+ });
93
+ }
94
+ if (method === "tools/call") {
95
+ const tool = TOOLS.find((t) => t.name === params?.name);
96
+ if (!tool) return send({ jsonrpc: "2.0", id, error: { code: -32601, message: "unknown tool" } });
97
+ try {
98
+ if (!TOKEN) throw new Error("JUJUGROWTH_TOKEN not set — generate one in Settings → Developer");
99
+ const result = await tool.run(params.arguments ?? {});
100
+ return send({
101
+ jsonrpc: "2.0",
102
+ id,
103
+ result: { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] },
104
+ });
105
+ } catch (err) {
106
+ return send({
107
+ jsonrpc: "2.0",
108
+ id,
109
+ result: { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true },
110
+ });
111
+ }
112
+ }
113
+ if (id !== undefined) send({ jsonrpc: "2.0", id, error: { code: -32601, message: "method not found" } });
114
+ }
115
+
116
+ let buf = "";
117
+ process.stdin.setEncoding("utf8");
118
+ process.stdin.on("data", (chunk) => {
119
+ buf += chunk;
120
+ let nl;
121
+ while ((nl = buf.indexOf("\n")) >= 0) {
122
+ const line = buf.slice(0, nl).trim();
123
+ buf = buf.slice(nl + 1);
124
+ if (line) handle(JSON.parse(line)).catch((e) => process.stderr.write(`${e}\n`));
125
+ }
126
+ });