jujugrowth-mcp 1.0.1 → 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.
- package/package.json +1 -1
- package/server.mjs +60 -5
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",
|
|
@@ -69,6 +94,30 @@ const TOOLS = [
|
|
|
69
94
|
run: async (a) =>
|
|
70
95
|
call("POST", `/recommendations/${a.tenantId}/${a.recId}/handled`, a.note ? { note: a.note } : undefined),
|
|
71
96
|
},
|
|
97
|
+
{
|
|
98
|
+
name: "request_site_advice",
|
|
99
|
+
description:
|
|
100
|
+
"Pro: kick off a fresh website improvement analysis for a business. The Site Advisor reads the live site + competitors and files a new recommendation (~3-5 min). Costs LLM tokens; requires an active (Pro) account.",
|
|
101
|
+
inputSchema: {
|
|
102
|
+
type: "object",
|
|
103
|
+
properties: { tenantId: { type: "string" } },
|
|
104
|
+
required: ["tenantId"],
|
|
105
|
+
additionalProperties: false,
|
|
106
|
+
},
|
|
107
|
+
run: async (a) => call("POST", `/site-advice/${a.tenantId}`),
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "request_campaign_brief",
|
|
111
|
+
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}`),
|
|
120
|
+
},
|
|
72
121
|
];
|
|
73
122
|
|
|
74
123
|
function send(msg) {
|
|
@@ -78,13 +127,17 @@ function send(msg) {
|
|
|
78
127
|
async function handle(msg) {
|
|
79
128
|
const { id, method, params } = msg;
|
|
80
129
|
if (method === "initialize") {
|
|
130
|
+
await loadIdentity(); // learn which site this token is bound to
|
|
81
131
|
return send({
|
|
82
132
|
jsonrpc: "2.0",
|
|
83
133
|
id,
|
|
84
134
|
result: {
|
|
85
135
|
protocolVersion: "2024-11-05",
|
|
86
136
|
capabilities: { tools: {} },
|
|
87
|
-
serverInfo: {
|
|
137
|
+
serverInfo: {
|
|
138
|
+
name: SITE_LABEL ? `jujugrowth (${SITE_LABEL})` : "jujugrowth",
|
|
139
|
+
version: "1.0.0",
|
|
140
|
+
},
|
|
88
141
|
},
|
|
89
142
|
});
|
|
90
143
|
}
|
|
@@ -105,7 +158,9 @@ async function handle(msg) {
|
|
|
105
158
|
return send({
|
|
106
159
|
jsonrpc: "2.0",
|
|
107
160
|
id,
|
|
108
|
-
result: {
|
|
161
|
+
result: {
|
|
162
|
+
content: [{ type: "text", text: siteBanner() + JSON.stringify(result, null, 2) }],
|
|
163
|
+
},
|
|
109
164
|
});
|
|
110
165
|
} catch (err) {
|
|
111
166
|
return send({
|