sitezen-mcp 1.0.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 +107 -0
- package/dist/conversion-log.js +67 -0
- package/dist/conversion-rules.md +1361 -0
- package/dist/errors.js +37 -0
- package/dist/figma.js +1369 -0
- package/dist/index.js +37 -0
- package/dist/license.js +121 -0
- package/dist/normalize.js +692 -0
- package/dist/state.js +81 -0
- package/dist/tools-session.js +131 -0
- package/dist/tools.js +1378 -0
- package/dist/validate.js +114 -0
- package/dist/wp-client.js +130 -0
- package/package.json +35 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SiteZen MCP server entry point.
|
|
4
|
+
*
|
|
5
|
+
* Boots the MCP server over stdio (the transport Claude Desktop and most
|
|
6
|
+
* MCP clients use). All tool definitions live in `tools.ts` — this file
|
|
7
|
+
* just wires the server up and starts listening.
|
|
8
|
+
*
|
|
9
|
+
* Usage (after `npm install` + `npm run build`):
|
|
10
|
+
* sitezen-mcp # via the package bin
|
|
11
|
+
* node dist/index.js # direct
|
|
12
|
+
* npx -y @sitezen/mcp # one-shot (once published)
|
|
13
|
+
*
|
|
14
|
+
* Required env vars (set in Claude Desktop's MCP server config):
|
|
15
|
+
* SITEZEN_SITE_URL e.g. https://yoursite.com
|
|
16
|
+
* SITEZEN_CONNECTION_KEY from WP Admin → SiteZen → Connection
|
|
17
|
+
*/
|
|
18
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
19
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
20
|
+
import { registerAllTools } from "./tools.js";
|
|
21
|
+
async function main() {
|
|
22
|
+
const server = new McpServer({
|
|
23
|
+
name: "sitezen-mcp",
|
|
24
|
+
version: "0.1.0",
|
|
25
|
+
});
|
|
26
|
+
registerAllTools(server);
|
|
27
|
+
const transport = new StdioServerTransport();
|
|
28
|
+
await server.connect(transport);
|
|
29
|
+
// Stdio transport stays open until the client disconnects. No further
|
|
30
|
+
// code runs here — Node keeps the process alive on the transport's
|
|
31
|
+
// open file descriptors.
|
|
32
|
+
}
|
|
33
|
+
main().catch((err) => {
|
|
34
|
+
// MCP clients read stdout for the protocol — keep error output on stderr.
|
|
35
|
+
process.stderr.write(`[sitezen-mcp] fatal: ${err instanceof Error ? err.stack || err.message : String(err)}\n`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
});
|
package/dist/license.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// License validation for the SiteZen MCP.
|
|
2
|
+
//
|
|
3
|
+
// Today: stubbed locally — any non-empty key returns the FREE plan so the
|
|
4
|
+
// product flow is testable end-to-end while the real dashboard is built.
|
|
5
|
+
// Override later by setting env SITEZEN_LICENSE_API_URL to the production
|
|
6
|
+
// validate endpoint — the MCP will POST {license_key, action} and use the
|
|
7
|
+
// real response instead of the stub.
|
|
8
|
+
//
|
|
9
|
+
// Validated responses are cached on disk (state.json -> license_cache) for
|
|
10
|
+
// 1 hour so we don't hit the network on every conversion. Cache is keyed by
|
|
11
|
+
// SHA-256 of the raw key so changing the key in the config invalidates the
|
|
12
|
+
// cache automatically.
|
|
13
|
+
import * as crypto from "node:crypto";
|
|
14
|
+
import { readState, writeState } from "./state.js";
|
|
15
|
+
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
16
|
+
const UPGRADE_URL = "https://sitezen.io/pricing";
|
|
17
|
+
function hashKey(key) {
|
|
18
|
+
return crypto.createHash("sha256").update(key.trim()).digest("hex");
|
|
19
|
+
}
|
|
20
|
+
function cacheIsFresh(cache, key) {
|
|
21
|
+
if (cache.key_hash !== hashKey(key))
|
|
22
|
+
return false;
|
|
23
|
+
const age = Date.now() - new Date(cache.cached_at).getTime();
|
|
24
|
+
return age < CACHE_TTL_MS;
|
|
25
|
+
}
|
|
26
|
+
async function callValidateEndpoint(key) {
|
|
27
|
+
const apiUrl = process.env.SITEZEN_LICENSE_API_URL;
|
|
28
|
+
if (!apiUrl)
|
|
29
|
+
return null;
|
|
30
|
+
try {
|
|
31
|
+
const r = await fetch(apiUrl, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: { "Content-Type": "application/json" },
|
|
34
|
+
body: JSON.stringify({ license_key: key, action: "validate" }),
|
|
35
|
+
});
|
|
36
|
+
if (!r.ok)
|
|
37
|
+
return null;
|
|
38
|
+
const j = await r.json();
|
|
39
|
+
if (!j || j.ok !== true)
|
|
40
|
+
return null;
|
|
41
|
+
return {
|
|
42
|
+
ok: true,
|
|
43
|
+
plan: j.plan,
|
|
44
|
+
sites_allowed: typeof j.sites_allowed === "number" ? j.sites_allowed : 1,
|
|
45
|
+
conversions_remaining: typeof j.conversions_remaining === "number" ? j.conversions_remaining : 0,
|
|
46
|
+
upgrade_url: j.upgrade_url || UPGRADE_URL,
|
|
47
|
+
cached: false,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function stubValidate(key) {
|
|
55
|
+
// Stub: any non-empty key → FREE plan. Replace by setting
|
|
56
|
+
// SITEZEN_LICENSE_API_URL in env.
|
|
57
|
+
return {
|
|
58
|
+
ok: true,
|
|
59
|
+
plan: "free",
|
|
60
|
+
sites_allowed: 1,
|
|
61
|
+
conversions_remaining: 3,
|
|
62
|
+
upgrade_url: UPGRADE_URL,
|
|
63
|
+
cached: false,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Validate the user's license key. Returns null if the env var is missing or
|
|
68
|
+
* the key is empty — caller should surface Errors.noLicenseKey() then.
|
|
69
|
+
* Always cached for 1 hour.
|
|
70
|
+
*/
|
|
71
|
+
export async function validateLicense(licenseKey) {
|
|
72
|
+
if (!licenseKey || !licenseKey.trim())
|
|
73
|
+
return null;
|
|
74
|
+
const key = licenseKey.trim();
|
|
75
|
+
// Hit cache first
|
|
76
|
+
const state = readState();
|
|
77
|
+
if (state.license_cache && cacheIsFresh(state.license_cache, key)) {
|
|
78
|
+
return {
|
|
79
|
+
ok: true,
|
|
80
|
+
plan: state.license_cache.plan,
|
|
81
|
+
sites_allowed: state.license_cache.sites_allowed,
|
|
82
|
+
conversions_remaining: state.license_cache.conversions_remaining,
|
|
83
|
+
upgrade_url: UPGRADE_URL,
|
|
84
|
+
cached: true,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Fresh validation — real endpoint if configured, else stub
|
|
88
|
+
const fresh = (await callValidateEndpoint(key)) ?? stubValidate(key);
|
|
89
|
+
// Persist to cache
|
|
90
|
+
state.license_cache = {
|
|
91
|
+
key_hash: hashKey(key),
|
|
92
|
+
plan: fresh.plan,
|
|
93
|
+
sites_allowed: fresh.sites_allowed,
|
|
94
|
+
conversions_remaining: fresh.conversions_remaining,
|
|
95
|
+
cached_at: new Date().toISOString(),
|
|
96
|
+
};
|
|
97
|
+
writeState(state);
|
|
98
|
+
return fresh;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Decrement the cached conversion count after a successful conversion.
|
|
102
|
+
* Unlimited (-1) is a no-op. Falls through silently if no cache exists
|
|
103
|
+
* (next call to validateLicense rebuilds it).
|
|
104
|
+
*/
|
|
105
|
+
export function decrementConversionsRemaining() {
|
|
106
|
+
const state = readState();
|
|
107
|
+
if (!state.license_cache)
|
|
108
|
+
return;
|
|
109
|
+
if (state.license_cache.conversions_remaining < 0)
|
|
110
|
+
return; // unlimited
|
|
111
|
+
state.license_cache.conversions_remaining = Math.max(0, state.license_cache.conversions_remaining - 1);
|
|
112
|
+
writeState(state);
|
|
113
|
+
}
|
|
114
|
+
export function planLabel(plan) {
|
|
115
|
+
switch (plan) {
|
|
116
|
+
case "free": return "Free";
|
|
117
|
+
case "solo": return "Solo";
|
|
118
|
+
case "multi": return "Multi";
|
|
119
|
+
case "agency": return "Agency";
|
|
120
|
+
}
|
|
121
|
+
}
|