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.
- package/README.md +47 -0
- package/package.json +11 -0
- 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
|
+
});
|