jujugrowth-mcp 1.0.7 → 1.0.9

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 +3 -1
  2. package/package.json +1 -1
  3. package/server.mjs +34 -19
package/README.md CHANGED
@@ -49,7 +49,9 @@ commands pre-filled.
49
49
  - `get_recommendation` — the full brief for one (implement it in your repo)
50
50
  - `mark_recommendation_handled` — mark it done (with a note of what you changed)
51
51
  - `request_site_advice` — Pro: trigger a fresh website analysis
52
- - `request_campaign_brief` — Pro: trigger a campaign brief
52
+
53
+ (Campaigns/ad briefs aren't here by design — those are owner budget
54
+ decisions, handled in the jujugrowth web UI, not dev-AI work.)
53
55
 
54
56
  A token can be scoped to one business; the server then announces itself as
55
57
  `jujugrowth (yoursite.com)` and only ever touches that site. Generate scoped
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jujugrowth-mcp",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "MCP server connecting your AI coding assistant (Claude, Cursor, Codex) to jujugrowth — pull recommendations and apply them in your repo.",
5
5
  "type": "module",
6
6
  "bin": {
package/server.mjs CHANGED
@@ -58,12 +58,18 @@ const TOOLS = [
58
58
  inputSchema: {
59
59
  type: "object",
60
60
  properties: {
61
- tenantId: { type: "string", description: "the site to list (optional; required when more than one site is connected)" },
61
+ tenantId: {
62
+ type: "string",
63
+ description: "the site to list (optional; required when more than one site is connected)",
64
+ },
62
65
  },
63
66
  additionalProperties: false,
64
67
  },
65
68
  run: async (a) =>
66
- call("GET", `/recommendations${a.tenantId ? `?tenantId=${encodeURIComponent(a.tenantId)}` : ""}`),
69
+ call(
70
+ "GET",
71
+ `/recommendations${a.tenantId ? `?tenantId=${encodeURIComponent(a.tenantId)}` : ""}`,
72
+ ),
67
73
  },
68
74
  {
69
75
  name: "get_recommendation",
@@ -92,7 +98,11 @@ const TOOLS = [
92
98
  additionalProperties: false,
93
99
  },
94
100
  run: async (a) =>
95
- call("POST", `/recommendations/${a.tenantId}/${a.recId}/handled`, a.note ? { note: a.note } : undefined),
101
+ call(
102
+ "POST",
103
+ `/recommendations/${a.tenantId}/${a.recId}/handled`,
104
+ a.note ? { note: a.note } : undefined,
105
+ ),
96
106
  },
97
107
  {
98
108
  name: "request_site_advice",
@@ -107,16 +117,11 @@ const TOOLS = [
107
117
  run: async (a) => call("POST", `/site-advice/${a.tenantId}`),
108
118
  },
109
119
  {
110
- name: "request_campaign_brief",
120
+ name: "list_capability_gaps",
111
121
  description:
112
- "Pro: kick off a campaign brief for a business (the system picks the platform). Files a new recommendation with the economics shown (~2-3 min). Costs LLM tokens; requires an active (Pro) account.",
113
- inputSchema: {
114
- type: "object",
115
- properties: { tenantId: { type: "string" } },
116
- required: ["tenantId"],
117
- additionalProperties: false,
118
- },
119
- run: async (a) => call("POST", `/campaign-brief/${a.tenantId}`),
122
+ "ADMIN/OPERATOR TOKEN ONLY. The jujugrowth system's OWN dev backlog: platform rules it has LEARNED (from docs + real platform responses) but cannot yet check because no collector observes the property the rule needs. Each item names the generic tool to build — `subject.attribute` per platform — with an example rule. Build that collector in the jujugrowth repo (make it write the property as a flat dotted attribute key on the synced entity); the gap auto-resolves once the fact is observable. The list is generated from real learned rules, never a hardcoded checklist. Returns 403 for non-admin tokens.",
123
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
124
+ run: async () => call("GET", "/capability-gaps"),
120
125
  },
121
126
  ];
122
127
 
@@ -147,7 +152,7 @@ async function handle(msg) {
147
152
  "3) Before changing anything, VERIFY the brief's current-state claims against the actual code — they can be stale; skip what's already done.\n" +
148
153
  "4) Implement in the user's repo, scoped to wording/structure/SEO. Never change checkout, auth, or analytics events. Show a diff / open a PR for the user to review.\n" +
149
154
  "5) ALWAYS call mark_recommendation_handled with a short note of what you changed once it's shipped — don't wait to be told.\n" +
150
- "Skip GA4-console toggles and business-judgment items — flag those for the user instead of editing code. This server is bound to ONE site; never act on another.",
155
+ "Skip GA4-console toggles and business-judgment items — flag those for the user instead of editing code. Campaigns and ad budgets are NOT here — those are owner decisions in the jujugrowth web UI. This server is bound to ONE site; never act on another.",
151
156
  },
152
157
  });
153
158
  }
@@ -156,14 +161,22 @@ async function handle(msg) {
156
161
  return send({
157
162
  jsonrpc: "2.0",
158
163
  id,
159
- result: { tools: TOOLS.map(({ name, description, inputSchema }) => ({ name, description, inputSchema })) },
164
+ result: {
165
+ tools: TOOLS.map(({ name, description, inputSchema }) => ({
166
+ name,
167
+ description,
168
+ inputSchema,
169
+ })),
170
+ },
160
171
  });
161
172
  }
162
173
  if (method === "tools/call") {
163
174
  const tool = TOOLS.find((t) => t.name === params?.name);
164
- if (!tool) return send({ jsonrpc: "2.0", id, error: { code: -32601, message: "unknown tool" } });
175
+ if (!tool)
176
+ return send({ jsonrpc: "2.0", id, error: { code: -32601, message: "unknown tool" } });
165
177
  try {
166
- if (!TOKEN) throw new Error("JUJUGROWTH_TOKEN not set — generate one in Settings → Developer");
178
+ if (!TOKEN)
179
+ throw new Error("JUJUGROWTH_TOKEN not set — generate one in Settings → Developer");
167
180
  const result = await tool.run(params.arguments ?? {});
168
181
  return send({
169
182
  jsonrpc: "2.0",
@@ -180,17 +193,19 @@ async function handle(msg) {
180
193
  });
181
194
  }
182
195
  }
183
- if (id !== undefined) send({ jsonrpc: "2.0", id, error: { code: -32601, message: "method not found" } });
196
+ if (id !== undefined)
197
+ send({ jsonrpc: "2.0", id, error: { code: -32601, message: "method not found" } });
184
198
  }
185
199
 
186
200
  let buf = "";
187
201
  process.stdin.setEncoding("utf8");
188
202
  process.stdin.on("data", (chunk) => {
189
203
  buf += chunk;
190
- let nl;
191
- while ((nl = buf.indexOf("\n")) >= 0) {
204
+ let nl = buf.indexOf("\n");
205
+ while (nl >= 0) {
192
206
  const line = buf.slice(0, nl).trim();
193
207
  buf = buf.slice(nl + 1);
194
208
  if (line) handle(JSON.parse(line)).catch((e) => process.stderr.write(`${e}\n`));
209
+ nl = buf.indexOf("\n");
195
210
  }
196
211
  });