@greennx/sales-mcp 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.
- package/README.md +129 -0
- package/dist/AGENTS.md +217 -0
- package/dist/CLAUDE.md +1 -0
- package/dist/bundle.cjs +22136 -0
- package/dist/instructions.md +216 -0
- package/dist/scripts/cli.js +329 -0
- package/package.json +40 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Sales MCP — Instructions for AI Agents
|
|
2
|
+
|
|
3
|
+
These instructions guide how Claude/Cursor must use the tools exposed by the Sales MCP server. Follow each protocol exactly — they encode non-negotiable product decisions.
|
|
4
|
+
|
|
5
|
+
## Scope — AI-Generated Pages Only
|
|
6
|
+
|
|
7
|
+
This MCP operates **exclusively on pages with `type = "AI_GENERATED"`**. Pages created manually by the user through the visual editor are intentionally excluded from all tools in this server.
|
|
8
|
+
|
|
9
|
+
Consequences:
|
|
10
|
+
- `list_pages` will return fewer pages than the frontend panel shows — this is expected and correct.
|
|
11
|
+
- Never attempt to read, edit, or delete a page that does not appear in `list_pages` results.
|
|
12
|
+
- If the user references a page that is not visible via `list_pages`, tell them in plain language: "Essa página foi criada diretamente no editor e só pode ser editada por lá. Só consigo gerenciar as páginas que foram criadas pela IA."
|
|
13
|
+
|
|
14
|
+
## Out-of-Scope Requests
|
|
15
|
+
|
|
16
|
+
Whenever the user asks for something that cannot be done through this MCP's tools (e.g., managing contacts, editing funnels, configuring automations, billing, account settings, or any feature not exposed here), respond clearly and direct them to the panel:
|
|
17
|
+
|
|
18
|
+
> "Isso está fora do que consigo fazer por aqui. Acesse o painel em **adm.greennsales.com.br** para realizar essa ação."
|
|
19
|
+
|
|
20
|
+
Do **not** attempt workarounds, do **not** fabricate tool calls, and do **not** leave the user without a clear next step.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Persona Tecnica
|
|
25
|
+
|
|
26
|
+
You are a front-end developer specialized in landing pages. Before using any resource ID (funnel_id, form_id, page_id, pixel_id), you MUST call the corresponding `list_*` tool to discover the real IDs. Never guess, never invent IDs. Before overwriting existing content (HTML, pixel links, metadata), ask the user for explicit confirmation and present the current state fetched from the platform.
|
|
27
|
+
|
|
28
|
+
Required behaviors:
|
|
29
|
+
- Always call `list_funnels`, `list_forms`, `list_pages`, `list_pixels`, `list_page_pixels`, or `list_media` before operations that need their IDs.
|
|
30
|
+
- If a tool returns an empty list, stop and ask the user — do not proceed with fabricated IDs.
|
|
31
|
+
- When multiple candidates match a user description (e.g., two pages with similar slugs), list them and ask the user which to use.
|
|
32
|
+
|
|
33
|
+
## Form Submission in HTML — REQUIRED
|
|
34
|
+
|
|
35
|
+
Whenever a page contains a form, you MUST call `implement_form_on_html(form_id, page_id, redirect_url?)` and use the returned `form_open_tag` and `script` strings **verbatim** in the HTML. Never write form submission logic manually — the tool generates the exact snippet with the correct tenant_id, field names, loading state, and success screen already wired up.
|
|
36
|
+
|
|
37
|
+
## Visual Continuity Protocol
|
|
38
|
+
|
|
39
|
+
Before creating OR editing the HTML of any page, you MUST call `get_page_content` against either (a) the target page itself if it already has content, or (b) a reference page from the same funnel or brand. From the returned HTML, extract:
|
|
40
|
+
- Color palette (hex values in inline styles or classes)
|
|
41
|
+
- Typography choices (font-family declarations)
|
|
42
|
+
- Section structure and layout conventions (hero layout, card styles, CTA placement)
|
|
43
|
+
- Any reusable patterns (button shapes, radius, shadow language)
|
|
44
|
+
|
|
45
|
+
Apply these same patterns when generating the new HTML passed to `save_page_content`. The visual continuity between AI-generated pages and the surrounding brand is non-negotiable — do not produce a page that visually breaks the rest of the funnel.
|
|
46
|
+
|
|
47
|
+
## Data Precedence Protocol
|
|
48
|
+
|
|
49
|
+
Platform data is the source of truth. Before any write operation (`create_page`, `update_page_metadata`, `save_page_content`, `link_page_pixel`, `create_pixel`, `upload_media`):
|
|
50
|
+
|
|
51
|
+
1. Read the current state via the appropriate `list_*` or `get_*` tool.
|
|
52
|
+
2. Identify conflicts explicitly and surface them to the user:
|
|
53
|
+
- "A page with slug `X` already exists in funnel Y."
|
|
54
|
+
- "Pixel `pixel_id=123` is already linked to page Z."
|
|
55
|
+
- "Media named `hero.png` already exists — reuse or upload a new copy?"
|
|
56
|
+
3. Never overwrite without explicit user confirmation in the conversation.
|
|
57
|
+
|
|
58
|
+
Do not assume the user wants to replace existing records. When in doubt, surface the conflict and wait.
|
|
59
|
+
|
|
60
|
+
## Asset Intelligence
|
|
61
|
+
|
|
62
|
+
Before calling `upload_media`, you MUST call `list_media` and check whether a relevant asset already exists (by filename, description, or alt text). If a match is found:
|
|
63
|
+
- Reuse the existing URL in your HTML instead of uploading a duplicate.
|
|
64
|
+
- Inform the user that you are reusing an existing asset and show the name/URL.
|
|
65
|
+
|
|
66
|
+
Only call `upload_media` when there is no reasonable match in the existing media library. Duplicated uploads pollute the media catalog and waste S3 storage.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Pixel Link Protocol
|
|
71
|
+
|
|
72
|
+
`link_page_pixel` uses **replace-sync** — the list you send replaces **all** pixels currently linked to the page. Before **any** call to `link_page_pixel` (add OR remove):
|
|
73
|
+
|
|
74
|
+
1. Call `list_page_pixels` with the target `page_id` to retrieve every pixel currently linked.
|
|
75
|
+
2. Build the new complete list:
|
|
76
|
+
- **Adding:** include all existing pixels + the new one(s).
|
|
77
|
+
- **Removing:** include all existing pixels except the one(s) to unlink.
|
|
78
|
+
3. Call `link_page_pixel` with the complete list.
|
|
79
|
+
|
|
80
|
+
Only send `pixel_ids: []` when the user explicitly asks to remove **all** pixels from the page.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Pixel Creation — Mandatory Questions
|
|
85
|
+
|
|
86
|
+
Before calling `create_pixel`, you MUST ask the user for every applicable field. Mirror the manual UI form exactly — do not assume defaults or skip questions.
|
|
87
|
+
|
|
88
|
+
**Universal fields (all pixel types):**
|
|
89
|
+
|
|
90
|
+
| Pergunta ao usuário | Campo na API | Observação |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| Tipo do pixel | `type` | `FACEBOOK` \| `GOOGLEADWORDS` \| `GOOGLETAGMANAGER` \| `GOOGLEANALYTICS` \| `TIKTOK` |
|
|
93
|
+
| Título | `title` | **Obrigatório** — nome de exibição |
|
|
94
|
+
| Pixel ID | `pixel_id` | ID da plataforma externa |
|
|
95
|
+
| Ativar eventos de **visualização**? | `view` | 1 = sim, 0 = não (padrão: sim) |
|
|
96
|
+
| Ativar eventos de **conversão**? | `conversion` | 1 = sim, 0 = não (padrão: não) |
|
|
97
|
+
|
|
98
|
+
**Campos exclusivos do Facebook (perguntar apenas quando `type = FACEBOOK`):**
|
|
99
|
+
|
|
100
|
+
| Pergunta ao usuário | Campo na API | Observação |
|
|
101
|
+
|---|---|---|
|
|
102
|
+
| Ativar **Navegador**? ("Dados salvos direto do navegador do usuário") | `web` | 1 = sim, 0 = não |
|
|
103
|
+
| Ativar **API de conversão**? ("Dados enviados para a API de conversão") | `api` | 1 = sim, 0 = não |
|
|
104
|
+
| **Token** da API de conversão | `token` | Obrigatório quando `api = 1` |
|
|
105
|
+
| Código de **teste do pixel** | `test_event_code_pixel` | Opcional; para testar, adicionar `?test_event=teste` na URL da página |
|
|
106
|
+
|
|
107
|
+
Do **not** call `create_pixel` until all applicable questions have been answered by the user.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Execution Protocol (Plan → Implement → Audit)
|
|
112
|
+
|
|
113
|
+
Every non-trivial request (creating a landing page, updating a funnel's pages, linking pixels in bulk) follows three explicit phases:
|
|
114
|
+
|
|
115
|
+
### Plan
|
|
116
|
+
Before calling any tool, output a numbered list describing what you will do. Include:
|
|
117
|
+
- Target funnel (funnel_id resolved from `list_funnels`)
|
|
118
|
+
- Form to embed (form_id resolved from `list_forms`)
|
|
119
|
+
- Pixels to link (from `list_pixels` + `list_page_pixels` for current state)
|
|
120
|
+
- Page structure (hero, features, CTA, FAQ, etc.)
|
|
121
|
+
- Which tools will be called and in what order
|
|
122
|
+
|
|
123
|
+
Wait for the user to approve or adjust the plan before moving to Implement.
|
|
124
|
+
|
|
125
|
+
### Implement
|
|
126
|
+
Execute the planned tool calls in sequence. Respect the Data Precedence Protocol at each write. If a tool returns `isError: true`, stop and explain the failure — do not chain further tools against inconsistent state.
|
|
127
|
+
|
|
128
|
+
### Audit
|
|
129
|
+
Once the implementation sequence completes, call:
|
|
130
|
+
1. `get_page` — confirm metadata (slug, status, campaign_id, domain) was persisted correctly.
|
|
131
|
+
2. `get_page_content` — confirm HTML was persisted and retrievable.
|
|
132
|
+
3. Report to the user the final public URL of the page and a 3-line summary of what was created/changed.
|
|
133
|
+
|
|
134
|
+
If any audit step shows inconsistency (e.g., page created but content missing), report it explicitly and propose a fix — do not silently continue.
|
|
135
|
+
|
|
136
|
+
## HTML Generation Rules
|
|
137
|
+
|
|
138
|
+
Every HTML passed to `save_page_content` must follow these rules.
|
|
139
|
+
|
|
140
|
+
**Required structure:**
|
|
141
|
+
```html
|
|
142
|
+
<!DOCTYPE html>
|
|
143
|
+
<html lang="pt-BR">
|
|
144
|
+
<head>
|
|
145
|
+
<meta charset="UTF-8" />
|
|
146
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
147
|
+
<title>{{PAGE_TITLE}}</title>
|
|
148
|
+
<style>
|
|
149
|
+
/* Inline CSS only — no external CDN without fallback */
|
|
150
|
+
</style>
|
|
151
|
+
</head>
|
|
152
|
+
<body>
|
|
153
|
+
<!-- sections: hero → features → social-proof → CTA → footer -->
|
|
154
|
+
<!-- pixel scripts at end of body -->
|
|
155
|
+
</body>
|
|
156
|
+
</html>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**CSS:**
|
|
160
|
+
- Define tokens in `:root` via CSS custom properties (`--color-primary`, `--font-body`) extracted from the Visual Continuity Protocol.
|
|
161
|
+
- No external frameworks (Tailwind, Bootstrap) unless already present in the existing HTML of the panel.
|
|
162
|
+
- Mobile-first: default breakpoint `@media (min-width: 768px)`.
|
|
163
|
+
- Always include `*, *::before, *::after { box-sizing: border-box; }`.
|
|
164
|
+
|
|
165
|
+
**Typography:**
|
|
166
|
+
- Extract `font-family` from existing HTML via `get_page_content`. If no reference exists, use `system-ui, -apple-system, sans-serif`.
|
|
167
|
+
- Base: 16px body, 1.5 line-height.
|
|
168
|
+
|
|
169
|
+
**Images:**
|
|
170
|
+
- Never use placeholder URLs (`picsum.photos`, `placeholder.com`, etc.).
|
|
171
|
+
- Only use URLs returned by `list_media` or `upload_media`.
|
|
172
|
+
- Always include a descriptive `alt` attribute and `loading="lazy"` on below-the-fold images.
|
|
173
|
+
|
|
174
|
+
**Accessibility minimums:**
|
|
175
|
+
- Single `<h1>` per page.
|
|
176
|
+
- Links with descriptive text (never "clique aqui").
|
|
177
|
+
- Buttons with explicit `type="button"` or `type="submit"`.
|
|
178
|
+
|
|
179
|
+
**Default sections for a landing page:**
|
|
180
|
+
```
|
|
181
|
+
hero — headline, subheadline, primary CTA, hero image
|
|
182
|
+
features — 3–4 cards with benefits (icon + text)
|
|
183
|
+
proof — testimonial or social proof numbers
|
|
184
|
+
cta-final — CTA repetition, optional urgency element
|
|
185
|
+
footer — copyright, minimal legal links
|
|
186
|
+
```
|
|
187
|
+
For simple capture pages (opt-in only), `hero + form + footer` is sufficient.
|
|
188
|
+
|
|
189
|
+
**Pre-save checklist:**
|
|
190
|
+
- `<!DOCTYPE html>` present
|
|
191
|
+
- `<meta viewport>` present
|
|
192
|
+
- No placeholder image URLs
|
|
193
|
+
- If page has a form: `implement_form_on_html` was called and its output is used verbatim
|
|
194
|
+
|
|
195
|
+
## Page Creation Inputs — What Comes From the User
|
|
196
|
+
|
|
197
|
+
`title`, `slug`, and `campaign_id` are **user decisions**, not agent decisions. Your job is to surface the options:
|
|
198
|
+
|
|
199
|
+
- Call `list_funnels` and present the list so the user can pick the funnel (`campaign_id`).
|
|
200
|
+
- Ask the user for the page title and slug — never invent them.
|
|
201
|
+
- Call `list_forms` and present the list if the user wants a form on the page.
|
|
202
|
+
|
|
203
|
+
Do not proceed to `create_page` until the user has explicitly confirmed all three values.
|
|
204
|
+
|
|
205
|
+
## Known Bugs Already Fixed in This MCP
|
|
206
|
+
|
|
207
|
+
These were discovered in production and fixed. Document them here so future agents don't waste time re-investigating:
|
|
208
|
+
|
|
209
|
+
**`create_page` and `update_page_metadata` — slug vs path_name mismatch**
|
|
210
|
+
Both tools accept `slug` but the backend API expects the field as `path_name`. The MCP already remaps `slug → path_name` before sending in both handlers. If you ever see a 422 "path_name required" error, or a slug update that silently has no effect, the fix is in `tools/pages.ts` — ensure `{ ...rest, path_name: slug }` is sent instead of passing `slug` directly in the body.
|
|
211
|
+
|
|
212
|
+
**`save_page_content` — PUT rejected file uploads**
|
|
213
|
+
PHP does not populate `$_FILES` for PUT requests, so multipart file uploads sent via PUT silently arrive with no file. The MCP was updated to use `POST` and the backend route was changed from `Route::put` to `Route::post`. If you see a 422 "html file required" error, confirm both the MCP method and the Laravel route use POST.
|
|
214
|
+
|
|
215
|
+
**`ai_pages_enabled` — feature flag gate**
|
|
216
|
+
A 403 with code `AI_PAGES_DISABLED` means the panel does not have the AI Pages feature enabled. This is a panel-level flag in the middleware — the agent cannot fix it. Tell the user to enable the feature in their account settings or contact support.
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// scripts/cli.ts
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { mkdirSync, copyFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { join, dirname } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { homedir, platform } from "node:os";
|
|
10
|
+
var here = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
var DIST_DIR = join(here, "..");
|
|
12
|
+
var BUNDLE_SRC = join(DIST_DIR, "bundle.cjs");
|
|
13
|
+
var IS_WIN = platform() === "win32";
|
|
14
|
+
var IS_MAC = platform() === "darwin";
|
|
15
|
+
var CLAUDE_CMD = IS_WIN ? "claude.cmd" : "claude";
|
|
16
|
+
function expandTilde(p) {
|
|
17
|
+
if (p === "~") return homedir();
|
|
18
|
+
if (p.startsWith("~/") || p.startsWith("~\\")) return join(homedir(), p.slice(2));
|
|
19
|
+
return p;
|
|
20
|
+
}
|
|
21
|
+
var APP_BASE = IS_WIN ? process.env["LOCALAPPDATA"] ?? join(homedir(), "AppData", "Local") : join(homedir(), ".local", "share");
|
|
22
|
+
var DEFAULT_INSTALL_DIR = join(APP_BASE, "greenn", "sales-mcp");
|
|
23
|
+
var DEFAULT_HISTORY_PATH = join(APP_BASE, "greenn", "sales-mcp-history");
|
|
24
|
+
var TTY = process.stdout.isTTY ?? false;
|
|
25
|
+
var bold = (s) => TTY ? `\x1B[1m${s}\x1B[0m` : s;
|
|
26
|
+
var dim = (s) => TTY ? `\x1B[2m${s}\x1B[0m` : s;
|
|
27
|
+
var cyan = (s) => TTY ? `\x1B[36m${s}\x1B[0m` : s;
|
|
28
|
+
var green = (s) => TTY ? `\x1B[32m${s}\x1B[0m` : s;
|
|
29
|
+
var yellow = (s) => TTY ? `\x1B[33m${s}\x1B[0m` : s;
|
|
30
|
+
var red = (s) => TTY ? `\x1B[31m${s}\x1B[0m` : s;
|
|
31
|
+
function readVersion() {
|
|
32
|
+
try {
|
|
33
|
+
const pkgPath = join(here, "..", "..", "package.json");
|
|
34
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
35
|
+
return pkg.version ?? "unknown";
|
|
36
|
+
} catch {
|
|
37
|
+
return "unknown";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
var VERSION = readVersion();
|
|
41
|
+
function printHelp() {
|
|
42
|
+
console.log(`
|
|
43
|
+
${bold("sales-mcp")} ${dim(`v${VERSION}`)}
|
|
44
|
+
MCP server installer for the Greenn Sales platform.
|
|
45
|
+
|
|
46
|
+
${bold("USAGE")}
|
|
47
|
+
|
|
48
|
+
${cyan("sales-mcp")} <command> [options]
|
|
49
|
+
|
|
50
|
+
${bold("COMMANDS")}
|
|
51
|
+
|
|
52
|
+
${cyan("setup")} Auto-detect and configure all installed MCP clients
|
|
53
|
+
${cyan("uninstall")} Remove sales-mcp from all detected tools
|
|
54
|
+
|
|
55
|
+
${bold("OPTIONS")}
|
|
56
|
+
|
|
57
|
+
${cyan("--help")}, ${cyan("-h")} Show help (use after a command for contextual help)
|
|
58
|
+
${cyan("--version")} Print version number
|
|
59
|
+
|
|
60
|
+
${bold("EXAMPLES")}
|
|
61
|
+
|
|
62
|
+
${dim("# Configure all detected tools with your Greenn token")}
|
|
63
|
+
${cyan(`npx @greenn/sales-mcp setup --token`)} <your-token>
|
|
64
|
+
|
|
65
|
+
${dim("# Use a custom directory for HTML backups")}
|
|
66
|
+
${cyan(`npx @greenn/sales-mcp setup --token`)} <your-token> ${cyan("--history-path")} ~/backups/greenn
|
|
67
|
+
|
|
68
|
+
${dim("# Remove from all tools")}
|
|
69
|
+
${cyan("npx @greenn/sales-mcp uninstall")}
|
|
70
|
+
|
|
71
|
+
Run ${cyan(`"npx @greenn/sales-mcp <command> --help"`)} for command-specific options.
|
|
72
|
+
`);
|
|
73
|
+
}
|
|
74
|
+
function printSetupHelp() {
|
|
75
|
+
console.log(`
|
|
76
|
+
${bold("sales-mcp setup")} ${dim(`v${VERSION}`)}
|
|
77
|
+
|
|
78
|
+
Auto-detects installed MCP clients and injects the sales-mcp server
|
|
79
|
+
configuration. Supports Claude Desktop, Cursor, Antigravity (Windsurf),
|
|
80
|
+
Cline (VS Code), and Claude Code CLI.
|
|
81
|
+
|
|
82
|
+
${bold("USAGE")}
|
|
83
|
+
|
|
84
|
+
${cyan("sales-mcp setup")} --token <token> [options]
|
|
85
|
+
|
|
86
|
+
${bold("OPTIONS")}
|
|
87
|
+
|
|
88
|
+
${cyan("--token")} ${yellow("<token>")} ${bold("Required.")} Your Greenn API token.
|
|
89
|
+
Obtain it at ${dim("adm.greennsales.com.br")} \u2192 Integrations & Tokens.
|
|
90
|
+
|
|
91
|
+
${cyan("--history-path")} ${yellow("<path>")} Directory where HTML backups are saved before
|
|
92
|
+
each page update. Created automatically if missing.
|
|
93
|
+
${dim(`Default: ${DEFAULT_HISTORY_PATH}`)}
|
|
94
|
+
|
|
95
|
+
${cyan("--help")}, ${cyan("-h")} Show this help message.
|
|
96
|
+
|
|
97
|
+
${bold("EXAMPLES")}
|
|
98
|
+
|
|
99
|
+
${cyan("npx @greenn/sales-mcp setup --token")} sk-abc123
|
|
100
|
+
${cyan("npx @greenn/sales-mcp setup --token")} sk-abc123 ${cyan("--history-path")} ~/backups/greenn
|
|
101
|
+
`);
|
|
102
|
+
}
|
|
103
|
+
function resolveConfigPath(tool) {
|
|
104
|
+
const appdata = process.env["APPDATA"] ?? "";
|
|
105
|
+
const home = homedir();
|
|
106
|
+
switch (tool) {
|
|
107
|
+
case "claude-desktop":
|
|
108
|
+
if (IS_MAC) return join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
109
|
+
if (IS_WIN) return join(appdata, "Claude", "claude_desktop_config.json");
|
|
110
|
+
return null;
|
|
111
|
+
// Claude Desktop is not available on Linux
|
|
112
|
+
case "cursor":
|
|
113
|
+
if (IS_WIN) return join(process.env["USERPROFILE"] ?? home, ".cursor", "mcp.json");
|
|
114
|
+
return join(home, ".cursor", "mcp.json");
|
|
115
|
+
case "antigravity":
|
|
116
|
+
if (IS_WIN) return join(process.env["USERPROFILE"] ?? home, ".gemini", "antigravity", "mcp_config.json");
|
|
117
|
+
return join(home, ".gemini", "antigravity", "mcp_config.json");
|
|
118
|
+
case "cline":
|
|
119
|
+
if (IS_MAC) return join(home, "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json");
|
|
120
|
+
if (IS_WIN) return join(appdata, "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json");
|
|
121
|
+
return join(home, ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json");
|
|
122
|
+
case "claude-code-user":
|
|
123
|
+
if (IS_WIN) return join(process.env["USERPROFILE"] ?? home, ".claude.json");
|
|
124
|
+
return join(home, ".claude.json");
|
|
125
|
+
default:
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function patchJsonConfig(configPath, serverPath, mcpContext) {
|
|
130
|
+
let config = { mcpServers: {} };
|
|
131
|
+
let wasCreated = true;
|
|
132
|
+
if (existsSync(configPath)) {
|
|
133
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
134
|
+
try {
|
|
135
|
+
config = JSON.parse(raw);
|
|
136
|
+
} catch {
|
|
137
|
+
}
|
|
138
|
+
if (!config["mcpServers"] || typeof config["mcpServers"] !== "object") {
|
|
139
|
+
config["mcpServers"] = {};
|
|
140
|
+
}
|
|
141
|
+
wasCreated = false;
|
|
142
|
+
} else {
|
|
143
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
144
|
+
}
|
|
145
|
+
config["mcpServers"]["sales-mcp"] = {
|
|
146
|
+
command: process.execPath,
|
|
147
|
+
args: [serverPath],
|
|
148
|
+
env: { MCP_CONTEXT: mcpContext }
|
|
149
|
+
};
|
|
150
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
151
|
+
return wasCreated ? "created" : "patched";
|
|
152
|
+
}
|
|
153
|
+
function removeSalesMcpFromJsonConfig(configPath) {
|
|
154
|
+
if (!existsSync(configPath)) return false;
|
|
155
|
+
let config;
|
|
156
|
+
try {
|
|
157
|
+
config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
158
|
+
} catch {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
if (!config["mcpServers"] || typeof config["mcpServers"] !== "object") return false;
|
|
162
|
+
const servers = config["mcpServers"];
|
|
163
|
+
if (!("sales-mcp" in servers)) return false;
|
|
164
|
+
delete servers["sales-mcp"];
|
|
165
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
var [command, ...rest] = process.argv.slice(2);
|
|
169
|
+
var hasHelp = rest.includes("--help") || rest.includes("-h");
|
|
170
|
+
if (!command || command === "--help" || command === "-h") {
|
|
171
|
+
printHelp();
|
|
172
|
+
process.exit(0);
|
|
173
|
+
}
|
|
174
|
+
if (command === "--version" || command === "-v") {
|
|
175
|
+
console.log(VERSION);
|
|
176
|
+
process.exit(0);
|
|
177
|
+
}
|
|
178
|
+
if (command === "setup") {
|
|
179
|
+
if (hasHelp) {
|
|
180
|
+
printSetupHelp();
|
|
181
|
+
process.exit(0);
|
|
182
|
+
}
|
|
183
|
+
const { values } = parseArgs({
|
|
184
|
+
args: rest,
|
|
185
|
+
options: {
|
|
186
|
+
token: { type: "string" },
|
|
187
|
+
"history-path": { type: "string" }
|
|
188
|
+
},
|
|
189
|
+
strict: false
|
|
190
|
+
});
|
|
191
|
+
const token = values["token"];
|
|
192
|
+
if (!token) {
|
|
193
|
+
console.error(`
|
|
194
|
+
${red("error")} --token is required
|
|
195
|
+
`);
|
|
196
|
+
console.error(` Run ${cyan('"sales-mcp setup --help"')} for more information.
|
|
197
|
+
`);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
if (!existsSync(BUNDLE_SRC)) {
|
|
201
|
+
console.error(`
|
|
202
|
+
${red("error")} bundle not found at ${BUNDLE_SRC}
|
|
203
|
+
`);
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
const historyPath = expandTilde(values["history-path"] ?? DEFAULT_HISTORY_PATH);
|
|
207
|
+
const serverDest = join(DEFAULT_INSTALL_DIR, "server.cjs");
|
|
208
|
+
mkdirSync(DEFAULT_INSTALL_DIR, { recursive: true });
|
|
209
|
+
mkdirSync(historyPath, { recursive: true });
|
|
210
|
+
copyFileSync(BUNDLE_SRC, serverDest);
|
|
211
|
+
const cwd = process.cwd();
|
|
212
|
+
for (const file of ["CLAUDE.md", "AGENTS.md", "instructions.md"]) {
|
|
213
|
+
const src = join(DIST_DIR, file);
|
|
214
|
+
const dest = join(cwd, file);
|
|
215
|
+
if (existsSync(src) && !existsSync(dest)) copyFileSync(src, dest);
|
|
216
|
+
}
|
|
217
|
+
const mcpContext = JSON.stringify({ SALES_GLOBAL_TOKEN: token, HISTORY_PATH: historyPath });
|
|
218
|
+
console.log("");
|
|
219
|
+
const tools = [
|
|
220
|
+
{ id: "claude-desktop", name: "Claude Desktop", restart: "Restart Claude Desktop." },
|
|
221
|
+
{ id: "cursor", name: "Cursor", restart: 'Settings > MCP > click "Refresh".' },
|
|
222
|
+
{ id: "antigravity", name: "Antigravity (Windsurf)", restart: 'Manage MCP Servers > click "Refresh".' },
|
|
223
|
+
{ id: "cline", name: "Cline (VS Code)", restart: 'Cline sidebar > MCP Servers > click "Refresh".' }
|
|
224
|
+
];
|
|
225
|
+
let configured = 0;
|
|
226
|
+
for (const tool of tools) {
|
|
227
|
+
const configPath = resolveConfigPath(tool.id);
|
|
228
|
+
if (!configPath) {
|
|
229
|
+
console.log(` ${dim("skip")} ${tool.name} ${dim("\u2014 not supported on this OS.")}`);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (!existsSync(dirname(configPath)) && !existsSync(configPath)) {
|
|
233
|
+
console.log(` ${dim("skip")} ${tool.name} ${dim("\u2014 not found.")}`);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
const action = patchJsonConfig(configPath, serverDest, mcpContext);
|
|
238
|
+
console.log(` ${green("ok")} ${bold(tool.name)} ${dim(`\u2014 config ${action}.`)}`);
|
|
239
|
+
console.log(` ${dim("file:")} ${configPath}`);
|
|
240
|
+
console.log(` ${dim("next:")} ${tool.restart}`);
|
|
241
|
+
configured++;
|
|
242
|
+
} catch (err) {
|
|
243
|
+
console.error(` ${red("err")} ${tool.name} \u2014 ${err.message}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
spawnSync(CLAUDE_CMD, ["mcp", "remove", "sales-mcp"], { stdio: "ignore" });
|
|
247
|
+
const claudeResult = spawnSync(
|
|
248
|
+
CLAUDE_CMD,
|
|
249
|
+
["mcp", "add", "--scope", "user", "sales-mcp", "node", serverDest, "-e", `MCP_CONTEXT=${mcpContext}`],
|
|
250
|
+
{ stdio: "ignore" }
|
|
251
|
+
);
|
|
252
|
+
if (claudeResult.status === 0) {
|
|
253
|
+
console.log(` ${green("ok")} ${bold("Claude Code CLI")} ${dim("\u2014 registered.")}`);
|
|
254
|
+
console.log(` ${dim("next:")} Restart Claude Code.`);
|
|
255
|
+
configured++;
|
|
256
|
+
} else {
|
|
257
|
+
console.log(` ${dim("skip")} Claude Code CLI ${dim("\u2014 not found.")}`);
|
|
258
|
+
}
|
|
259
|
+
console.log("");
|
|
260
|
+
if (configured === 0) {
|
|
261
|
+
console.warn(` ${yellow("warn")} No tools detected.
|
|
262
|
+
`);
|
|
263
|
+
console.warn(` Make sure Claude Desktop, Cursor, Windsurf, or VS Code with Cline are installed.
|
|
264
|
+
`);
|
|
265
|
+
console.warn(` Run ${cyan('"sales-mcp --help"')} for more information.
|
|
266
|
+
`);
|
|
267
|
+
} else {
|
|
268
|
+
const t = String(token);
|
|
269
|
+
const tokenPreview = `${t.slice(0, 6)}${"*".repeat(Math.max(0, t.length - 6))}`;
|
|
270
|
+
console.log(` ${bold(green("sales-mcp setup complete"))}
|
|
271
|
+
`);
|
|
272
|
+
console.log(` ${dim("token:")} ${tokenPreview}`);
|
|
273
|
+
console.log(` ${dim("server:")} ${serverDest}`);
|
|
274
|
+
console.log(` ${dim("history:")} ${historyPath}`);
|
|
275
|
+
console.log(` ${dim("node:")} ${process.execPath}`);
|
|
276
|
+
console.log(`
|
|
277
|
+
${bold("NEXT STEPS")}
|
|
278
|
+
`);
|
|
279
|
+
console.log(` ${dim("1.")} Reload each configured tool (see instructions above).`);
|
|
280
|
+
console.log(` ${dim("2.")} Open your AI assistant and type:`);
|
|
281
|
+
console.log(`
|
|
282
|
+
${cyan('"List my funnels"')}
|
|
283
|
+
`);
|
|
284
|
+
console.log(` ${dim("3.")} You should see your Greenn funnels listed \u2014 setup is complete.
|
|
285
|
+
`);
|
|
286
|
+
}
|
|
287
|
+
} else if (command === "uninstall") {
|
|
288
|
+
console.log("");
|
|
289
|
+
for (const scope of ["user", "project", "local"]) {
|
|
290
|
+
spawnSync(CLAUDE_CMD, ["mcp", "remove", "--scope", scope, "sales-mcp"], { stdio: "ignore" });
|
|
291
|
+
}
|
|
292
|
+
const tools = [
|
|
293
|
+
{ id: "claude-code-user", name: "Claude Code (user scope)" },
|
|
294
|
+
{ id: "claude-desktop", name: "Claude Desktop" },
|
|
295
|
+
{ id: "cursor", name: "Cursor" },
|
|
296
|
+
{ id: "antigravity", name: "Antigravity (Windsurf)" },
|
|
297
|
+
{ id: "cline", name: "Cline (VS Code)" }
|
|
298
|
+
];
|
|
299
|
+
let removed = 0;
|
|
300
|
+
for (const tool of tools) {
|
|
301
|
+
const configPath = resolveConfigPath(tool.id);
|
|
302
|
+
if (!configPath) continue;
|
|
303
|
+
try {
|
|
304
|
+
const ok = removeSalesMcpFromJsonConfig(configPath);
|
|
305
|
+
if (ok) {
|
|
306
|
+
console.log(` ${green("ok")} ${tool.name} ${dim("\u2014 removed.")}`);
|
|
307
|
+
removed++;
|
|
308
|
+
}
|
|
309
|
+
} catch (err) {
|
|
310
|
+
console.error(` ${red("err")} ${tool.name} \u2014 ${err.message}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (removed === 0) {
|
|
314
|
+
console.log(` ${dim("info")} sales-mcp was not found in any installed tool.
|
|
315
|
+
`);
|
|
316
|
+
} else {
|
|
317
|
+
console.log(`
|
|
318
|
+
Restart your tools to apply the changes.`);
|
|
319
|
+
}
|
|
320
|
+
console.log(` ${dim("Note:")} project-level ${cyan(".mcp.json")} files must be removed manually.
|
|
321
|
+
`);
|
|
322
|
+
} else {
|
|
323
|
+
console.error(`
|
|
324
|
+
${red("error")} unknown command: ${cyan(`"${command}"`)}
|
|
325
|
+
`);
|
|
326
|
+
console.error(` Run ${cyan('"sales-mcp --help"')} for more information.
|
|
327
|
+
`);
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@greennx/sales-mcp",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "MCP server for AI-generated landing pages on the Greenn Sales platform",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sales-mcp": "dist/scripts/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/bundle.cjs",
|
|
11
|
+
"dist/scripts/cli.js",
|
|
12
|
+
"dist/CLAUDE.md",
|
|
13
|
+
"dist/AGENTS.md",
|
|
14
|
+
"dist/instructions.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsx scripts/embed-instructions.ts && esbuild server.ts --bundle --platform=node --format=cjs --outfile=dist/bundle.cjs && esbuild scripts/cli.ts --bundle --platform=node --format=esm --outfile=dist/scripts/cli.js && cp instructions.md dist/instructions.md && cp CLAUDE.md dist/CLAUDE.md && cp AGENTS.md dist/AGENTS.md",
|
|
18
|
+
"local:setup": "npm run build && node dist/scripts/cli.js setup --token $SALES_TOKEN",
|
|
19
|
+
"prepare": "npm run build",
|
|
20
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
21
|
+
"test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
|
|
22
|
+
"lint": "eslint ."
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@modelcontextprotocol/sdk": "~1.29.0",
|
|
26
|
+
"zod": "^3.25"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/jest": "^29.5.14",
|
|
30
|
+
"@types/node": "^22.13.0",
|
|
31
|
+
"@typescript-eslint/eslint-plugin": "^8.59.0",
|
|
32
|
+
"@typescript-eslint/parser": "^8.59.0",
|
|
33
|
+
"esbuild": "^0.28.0",
|
|
34
|
+
"eslint": "^9.15.0",
|
|
35
|
+
"jest": "^29.7.0",
|
|
36
|
+
"ts-jest": "^29.4.9",
|
|
37
|
+
"tsx": "^4.21.0",
|
|
38
|
+
"typescript": "^5.7.3"
|
|
39
|
+
}
|
|
40
|
+
}
|