jujugrowth-mcp 1.0.2 → 1.0.6
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 +14 -3
- package/package.json +1 -1
- package/server.mjs +36 -5
package/README.md
CHANGED
|
@@ -39,9 +39,20 @@ claude mcp add jujugrowth --scope user \
|
|
|
39
39
|
The jujugrowth Settings → Developer panel generates the token and shows these
|
|
40
40
|
commands pre-filled.
|
|
41
41
|
|
|
42
|
-
3. Restart the client.
|
|
43
|
-
|
|
42
|
+
3. Restart the client. **First use:** your AI will ask to approve the jujugrowth
|
|
43
|
+
tools the first time it calls them — approve them. (In Claude Code, if it's in
|
|
44
|
+
"don't ask"/deny mode it silently refuses — toggle the permission mode or run
|
|
45
|
+
`/allowed-tools`, then approve once.)
|
|
46
|
+
|
|
47
|
+
You'll have these tools:
|
|
48
|
+
- `list_recommendations` — open recommendations (one site if the token is scoped)
|
|
44
49
|
- `get_recommendation` — the full brief for one (implement it in your repo)
|
|
45
|
-
- `mark_recommendation_handled` — mark it done
|
|
50
|
+
- `mark_recommendation_handled` — mark it done (with a note of what you changed)
|
|
51
|
+
- `request_site_advice` — Pro: trigger a fresh website analysis
|
|
52
|
+
- `request_campaign_brief` — Pro: trigger a campaign brief
|
|
53
|
+
|
|
54
|
+
A token can be scoped to one business; the server then announces itself as
|
|
55
|
+
`jujugrowth (yoursite.com)` and only ever touches that site. Generate scoped
|
|
56
|
+
tokens in jujugrowth → Settings → Developer (one per business/repo).
|
|
46
57
|
|
|
47
58
|
Requires Node ≥ 22. Optional env `JUJUGROWTH_API` (defaults to https://jujugrowth.com).
|
package/package.json
CHANGED
package/server.mjs
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
const API = (process.env.JUJUGROWTH_API ?? "https://jujugrowth.com").replace(/\/+$/, "");
|
|
19
19
|
const TOKEN = process.env.JUJUGROWTH_TOKEN ?? "";
|
|
20
|
+
// Published automatically by CI (.github/workflows/publish-mcp.yml) on version bump.
|
|
20
21
|
|
|
21
22
|
async function call(method, path, body) {
|
|
22
23
|
const res = await fetch(`${API}/api/mcp${path}`, {
|
|
@@ -32,13 +33,37 @@ async function call(method, path, body) {
|
|
|
32
33
|
return text ? JSON.parse(text) : {};
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
// This server is bound to ONE token → usually ONE site. It announces that
|
|
37
|
+
// site (serverInfo + a banner on every result) so a dev-AI with several
|
|
38
|
+
// jujugrowth servers on one machine can never act on the wrong site.
|
|
39
|
+
let SITE_LABEL = null; // e.g. "jujublocks.com"
|
|
40
|
+
async function loadIdentity() {
|
|
41
|
+
try {
|
|
42
|
+
const me = await call("GET", "/whoami");
|
|
43
|
+
SITE_LABEL = me.scoped ? me.business : null; // null = multi-site (unscoped) token
|
|
44
|
+
} catch {
|
|
45
|
+
SITE_LABEL = null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const siteBanner = () =>
|
|
49
|
+
SITE_LABEL
|
|
50
|
+
? `⚠️ This jujugrowth server is bound to the site "${SITE_LABEL}". Only act on this site. If the user meant a different site, stop and use that site's jujugrowth server.\n\n`
|
|
51
|
+
: "";
|
|
52
|
+
|
|
35
53
|
const TOOLS = [
|
|
36
54
|
{
|
|
37
55
|
name: "list_recommendations",
|
|
38
56
|
description:
|
|
39
|
-
"List open jujugrowth recommendations
|
|
40
|
-
inputSchema: {
|
|
41
|
-
|
|
57
|
+
"List open jujugrowth recommendations. Pass `tenantId` to target a specific site. If you don't and more than one site is connected, the result is {needsSite:true, message, businesses[]} — in that case ASK THE USER which site (show the names), then call again with that tenantId. Never pick a site for them.",
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {
|
|
61
|
+
tenantId: { type: "string", description: "the site to list (optional; required when more than one site is connected)" },
|
|
62
|
+
},
|
|
63
|
+
additionalProperties: false,
|
|
64
|
+
},
|
|
65
|
+
run: async (a) =>
|
|
66
|
+
call("GET", `/recommendations${a.tenantId ? `?tenantId=${encodeURIComponent(a.tenantId)}` : ""}`),
|
|
42
67
|
},
|
|
43
68
|
{
|
|
44
69
|
name: "get_recommendation",
|
|
@@ -102,13 +127,17 @@ function send(msg) {
|
|
|
102
127
|
async function handle(msg) {
|
|
103
128
|
const { id, method, params } = msg;
|
|
104
129
|
if (method === "initialize") {
|
|
130
|
+
await loadIdentity(); // learn which site this token is bound to
|
|
105
131
|
return send({
|
|
106
132
|
jsonrpc: "2.0",
|
|
107
133
|
id,
|
|
108
134
|
result: {
|
|
109
135
|
protocolVersion: "2024-11-05",
|
|
110
136
|
capabilities: { tools: {} },
|
|
111
|
-
serverInfo: {
|
|
137
|
+
serverInfo: {
|
|
138
|
+
name: SITE_LABEL ? `jujugrowth (${SITE_LABEL})` : "jujugrowth",
|
|
139
|
+
version: "1.0.0",
|
|
140
|
+
},
|
|
112
141
|
},
|
|
113
142
|
});
|
|
114
143
|
}
|
|
@@ -129,7 +158,9 @@ async function handle(msg) {
|
|
|
129
158
|
return send({
|
|
130
159
|
jsonrpc: "2.0",
|
|
131
160
|
id,
|
|
132
|
-
result: {
|
|
161
|
+
result: {
|
|
162
|
+
content: [{ type: "text", text: siteBanner() + JSON.stringify(result, null, 2) }],
|
|
163
|
+
},
|
|
133
164
|
});
|
|
134
165
|
} catch (err) {
|
|
135
166
|
return send({
|