jujugrowth-mcp 1.0.8 → 1.1.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 (2) hide show
  1. package/package.json +2 -2
  2. package/server.mjs +63 -16
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jujugrowth-mcp",
3
- "version": "1.0.8",
4
- "description": "MCP server connecting your AI coding assistant (Claude, Cursor, Codex) to jujugrowth — pull recommendations and apply them in your repo.",
3
+ "version": "1.1.0",
4
+ "description": "MCP server connecting your AI coding assistant (Claude, Cursor, Codex) to jujugrowth — pull your living marketing plan's dev tasks + recommendations and apply them in your repo.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "jujugrowth-mcp": "server.mjs"
package/server.mjs CHANGED
@@ -58,12 +58,35 @@ 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
+ ),
73
+ },
74
+ {
75
+ name: "list_plan_tasks",
76
+ description:
77
+ "List the dev-AI tasks from this site's LIVING MARKETING PLAN — the roadmap steps the plan assigned to the developer AI (changes to the business's own site/app/code; jujugrowth executes the 'autonomous' marketing steps itself). Each task has a ready-to-run `prompt`, plus why it matters, its priority, and the plan's north-star + strategy for context. This is your growth to-do list for the business: pull it, implement the highest-priority tasks in the repo (verify current state first; show a diff/PR), and the next plan regeneration reflects what you shipped via its evidence-grounded status. Pass `tenantId` to target a site; if more than one is connected and you omit it you get {needsSite:true,...} — ASK the user which site, then call again. Never pick a site for them.",
78
+ inputSchema: {
79
+ type: "object",
80
+ properties: {
81
+ tenantId: {
82
+ type: "string",
83
+ description: "the site (optional; required when more than one site is connected)",
84
+ },
85
+ },
86
+ additionalProperties: false,
87
+ },
88
+ run: async (a) =>
89
+ call("GET", `/plan-tasks${a.tenantId ? `?tenantId=${encodeURIComponent(a.tenantId)}` : ""}`),
67
90
  },
68
91
  {
69
92
  name: "get_recommendation",
@@ -92,7 +115,11 @@ const TOOLS = [
92
115
  additionalProperties: false,
93
116
  },
94
117
  run: async (a) =>
95
- call("POST", `/recommendations/${a.tenantId}/${a.recId}/handled`, a.note ? { note: a.note } : undefined),
118
+ call(
119
+ "POST",
120
+ `/recommendations/${a.tenantId}/${a.recId}/handled`,
121
+ a.note ? { note: a.note } : undefined,
122
+ ),
96
123
  },
97
124
  {
98
125
  name: "request_site_advice",
@@ -106,6 +133,13 @@ const TOOLS = [
106
133
  },
107
134
  run: async (a) => call("POST", `/site-advice/${a.tenantId}`),
108
135
  },
136
+ {
137
+ name: "list_capability_gaps",
138
+ description:
139
+ "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.",
140
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
141
+ run: async () => call("GET", "/capability-gaps"),
142
+ },
109
143
  ];
110
144
 
111
145
  function send(msg) {
@@ -129,13 +163,16 @@ async function handle(msg) {
129
163
  // Workflow the AI should follow unprompted (so the human never has to
130
164
  // say "now mark it handled"). Surfaced to the model by MCP clients.
131
165
  instructions:
132
- `This server provides growth recommendations for ${SITE_LABEL ? `the site "${SITE_LABEL}"` : "the user's site(s)"} and connects them to your code work. Standard loop:\n` +
133
- "1) list_recommendations.\n" +
134
- "2) get_recommendation for one you'll act on.\n" +
135
- "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" +
136
- "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" +
137
- "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" +
138
- "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.",
166
+ `This server connects ${SITE_LABEL ? `the site "${SITE_LABEL}"` : "the user's site(s)"} growth work to your coding. Two task sources:\n` +
167
+ "• list_plan_tasks — the dev-AI steps of the business's LIVING MARKETING PLAN (the strategic roadmap). Start here: these are the prioritized site/app/code changes the growth plan needs (each has a ready-to-run prompt + the north-star for context).\n" +
168
+ " list_recommendations individual one-off growth recommendations.\n" +
169
+ "Standard loop:\n" +
170
+ "1) list_plan_tasks (and/or list_recommendations).\n" +
171
+ "2) get_recommendation for any rec you'll act on.\n" +
172
+ "3) Before changing anything, VERIFY current-state claims against the actual code — they can be stale; skip what's already done.\n" +
173
+ "4) Implement in the user's repo, scoped to wording/structure/SEO/tracking the plan asks for. Show a diff / open a PR for the user to review.\n" +
174
+ "5) ALWAYS call mark_recommendation_handled with a short note once a recommendation is shipped — don't wait to be told. (Plan-task progress is reflected automatically by the next plan regeneration's evidence-grounded status.)\n" +
175
+ "Campaigns and ad budgets are NOT here — those are owner decisions the jujugrowth system runs itself. This server is bound to ONE site; never act on another.",
139
176
  },
140
177
  });
141
178
  }
@@ -144,14 +181,22 @@ async function handle(msg) {
144
181
  return send({
145
182
  jsonrpc: "2.0",
146
183
  id,
147
- result: { tools: TOOLS.map(({ name, description, inputSchema }) => ({ name, description, inputSchema })) },
184
+ result: {
185
+ tools: TOOLS.map(({ name, description, inputSchema }) => ({
186
+ name,
187
+ description,
188
+ inputSchema,
189
+ })),
190
+ },
148
191
  });
149
192
  }
150
193
  if (method === "tools/call") {
151
194
  const tool = TOOLS.find((t) => t.name === params?.name);
152
- if (!tool) return send({ jsonrpc: "2.0", id, error: { code: -32601, message: "unknown tool" } });
195
+ if (!tool)
196
+ return send({ jsonrpc: "2.0", id, error: { code: -32601, message: "unknown tool" } });
153
197
  try {
154
- if (!TOKEN) throw new Error("JUJUGROWTH_TOKEN not set — generate one in Settings → Developer");
198
+ if (!TOKEN)
199
+ throw new Error("JUJUGROWTH_TOKEN not set — generate one in Settings → Developer");
155
200
  const result = await tool.run(params.arguments ?? {});
156
201
  return send({
157
202
  jsonrpc: "2.0",
@@ -168,17 +213,19 @@ async function handle(msg) {
168
213
  });
169
214
  }
170
215
  }
171
- if (id !== undefined) send({ jsonrpc: "2.0", id, error: { code: -32601, message: "method not found" } });
216
+ if (id !== undefined)
217
+ send({ jsonrpc: "2.0", id, error: { code: -32601, message: "method not found" } });
172
218
  }
173
219
 
174
220
  let buf = "";
175
221
  process.stdin.setEncoding("utf8");
176
222
  process.stdin.on("data", (chunk) => {
177
223
  buf += chunk;
178
- let nl;
179
- while ((nl = buf.indexOf("\n")) >= 0) {
224
+ let nl = buf.indexOf("\n");
225
+ while (nl >= 0) {
180
226
  const line = buf.slice(0, nl).trim();
181
227
  buf = buf.slice(nl + 1);
182
228
  if (line) handle(JSON.parse(line)).catch((e) => process.stderr.write(`${e}\n`));
229
+ nl = buf.indexOf("\n");
183
230
  }
184
231
  });