jujugrowth-mcp 1.0.2 → 1.0.5

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 +1 -1
  2. package/server.mjs +36 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jujugrowth-mcp",
3
- "version": "1.0.2",
3
+ "version": "1.0.5",
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
@@ -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 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"),
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: { name: "jujugrowth", version: "1.0.0" },
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: { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] },
161
+ result: {
162
+ content: [{ type: "text", text: siteBanner() + JSON.stringify(result, null, 2) }],
163
+ },
133
164
  });
134
165
  } catch (err) {
135
166
  return send({