swagmanager-mcp 1.0.0 → 1.2.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/bin/swagmanager-mcp.js +10 -6
- package/dist/index.js +3 -0
- package/dist/setup.d.ts +8 -0
- package/dist/setup.js +181 -0
- package/dist/updater.d.ts +25 -0
- package/dist/updater.js +140 -0
- package/package.json +9 -1
package/bin/swagmanager-mcp.js
CHANGED
|
@@ -4,14 +4,13 @@
|
|
|
4
4
|
* SwagManager MCP Server CLI
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
* npx
|
|
8
|
-
* swagmanager-mcp
|
|
7
|
+
* npx swagmanager-mcp — Run the MCP server (stdio)
|
|
8
|
+
* npx swagmanager-mcp setup — Install to Claude Code, Cursor, etc.
|
|
9
9
|
*
|
|
10
10
|
* Environment variables:
|
|
11
11
|
* SUPABASE_URL - Your Supabase project URL
|
|
12
12
|
* SUPABASE_SERVICE_ROLE_KEY - Your Supabase service role key
|
|
13
13
|
* STORE_ID - Default store ID (optional)
|
|
14
|
-
* SWAG_API_KEY - API key for auth (future)
|
|
15
14
|
*/
|
|
16
15
|
|
|
17
16
|
import { fileURLToPath } from "url";
|
|
@@ -20,6 +19,11 @@ import { dirname, join } from "path";
|
|
|
20
19
|
const __filename = fileURLToPath(import.meta.url);
|
|
21
20
|
const __dirname = dirname(__filename);
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
const command = process.argv[2];
|
|
23
|
+
|
|
24
|
+
if (command === "setup") {
|
|
25
|
+
const { runSetup } = await import(join(__dirname, "..", "dist", "setup.js"));
|
|
26
|
+
await runSetup();
|
|
27
|
+
} else {
|
|
28
|
+
await import(join(__dirname, "..", "dist", "index.js"));
|
|
29
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
14
14
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
15
15
|
import { createClient } from "@supabase/supabase-js";
|
|
16
16
|
import { executeTool, getImplementedTools } from "./tools/executor.js";
|
|
17
|
+
import { startUpdateLoop } from "./updater.js";
|
|
17
18
|
// ============================================================================
|
|
18
19
|
// CONFIGURATION
|
|
19
20
|
// ============================================================================
|
|
@@ -130,6 +131,8 @@ async function main() {
|
|
|
130
131
|
// Pre-load tools
|
|
131
132
|
const tools = await loadToolDefinitions(true);
|
|
132
133
|
console.error(`[MCP] Loaded ${tools.length} tools`);
|
|
134
|
+
// Start OTA update checker (non-blocking, runs in background)
|
|
135
|
+
startUpdateLoop(true);
|
|
133
136
|
// Connect via stdio
|
|
134
137
|
const transport = new StdioServerTransport();
|
|
135
138
|
await server.connect(transport);
|
package/dist/setup.d.ts
ADDED
package/dist/setup.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SwagManager MCP — Interactive Setup
|
|
4
|
+
*
|
|
5
|
+
* Detects installed MCP-compatible CLIs and writes config for each.
|
|
6
|
+
* Usage: npx swagmanager-mcp setup
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
9
|
+
import { homedir } from "os";
|
|
10
|
+
import { dirname, join } from "path";
|
|
11
|
+
import { createInterface } from "readline";
|
|
12
|
+
const home = homedir();
|
|
13
|
+
const CLI_TARGETS = [
|
|
14
|
+
{
|
|
15
|
+
name: "Claude Code",
|
|
16
|
+
configPath: join(home, ".claude", "settings.json"),
|
|
17
|
+
configKey: "mcpServers",
|
|
18
|
+
detect: () => existsSync(join(home, ".claude")),
|
|
19
|
+
format: (servers) => servers,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: "Claude Desktop",
|
|
23
|
+
configPath: process.platform === "darwin"
|
|
24
|
+
? join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")
|
|
25
|
+
: join(home, "AppData", "Roaming", "Claude", "claude_desktop_config.json"),
|
|
26
|
+
configKey: "mcpServers",
|
|
27
|
+
detect: () => existsSync(process.platform === "darwin"
|
|
28
|
+
? join(home, "Library", "Application Support", "Claude")
|
|
29
|
+
: join(home, "AppData", "Roaming", "Claude")),
|
|
30
|
+
format: (servers) => servers,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "Cursor",
|
|
34
|
+
configPath: join(home, ".cursor", "mcp.json"),
|
|
35
|
+
configKey: "mcpServers",
|
|
36
|
+
detect: () => existsSync(join(home, ".cursor")),
|
|
37
|
+
format: (servers) => servers,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "Windsurf",
|
|
41
|
+
configPath: join(home, ".codeium", "windsurf", "mcp_config.json"),
|
|
42
|
+
configKey: "mcpServers",
|
|
43
|
+
detect: () => existsSync(join(home, ".codeium", "windsurf")),
|
|
44
|
+
format: (servers) => servers,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "Gemini CLI",
|
|
48
|
+
configPath: join(home, ".gemini", "settings.json"),
|
|
49
|
+
configKey: "mcpServers",
|
|
50
|
+
detect: () => existsSync(join(home, ".gemini")),
|
|
51
|
+
format: (servers) => servers,
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// HELPERS
|
|
56
|
+
// ============================================================================
|
|
57
|
+
function readJSON(path) {
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function writeJSON(path, data) {
|
|
66
|
+
const dir = dirname(path);
|
|
67
|
+
if (!existsSync(dir))
|
|
68
|
+
mkdirSync(dir, { recursive: true });
|
|
69
|
+
writeFileSync(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
70
|
+
}
|
|
71
|
+
function prompt(rl, question, defaultValue) {
|
|
72
|
+
const suffix = defaultValue ? ` (${defaultValue})` : "";
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
rl.question(`${question}${suffix}: `, (answer) => {
|
|
75
|
+
resolve(answer.trim() || defaultValue || "");
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function promptYN(rl, question, defaultYes = true) {
|
|
80
|
+
const hint = defaultYes ? "[Y/n]" : "[y/N]";
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
rl.question(`${question} ${hint} `, (answer) => {
|
|
83
|
+
const a = answer.trim().toLowerCase();
|
|
84
|
+
if (a === "")
|
|
85
|
+
resolve(defaultYes);
|
|
86
|
+
else
|
|
87
|
+
resolve(a === "y" || a === "yes");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// MAIN
|
|
93
|
+
// ============================================================================
|
|
94
|
+
export async function runSetup() {
|
|
95
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
96
|
+
console.log("");
|
|
97
|
+
console.log(" SwagManager MCP — Setup");
|
|
98
|
+
console.log(" ═══════════════════════");
|
|
99
|
+
console.log("");
|
|
100
|
+
// Detect installed CLIs
|
|
101
|
+
const detected = CLI_TARGETS.filter((t) => t.detect());
|
|
102
|
+
const notDetected = CLI_TARGETS.filter((t) => !t.detect());
|
|
103
|
+
if (detected.length > 0) {
|
|
104
|
+
console.log(" Detected CLIs:");
|
|
105
|
+
detected.forEach((t) => console.log(` + ${t.name}`));
|
|
106
|
+
}
|
|
107
|
+
if (notDetected.length > 0) {
|
|
108
|
+
console.log(" Not found:");
|
|
109
|
+
notDetected.forEach((t) => console.log(` - ${t.name}`));
|
|
110
|
+
}
|
|
111
|
+
console.log("");
|
|
112
|
+
if (detected.length === 0) {
|
|
113
|
+
console.log(" No supported MCP clients detected.");
|
|
114
|
+
console.log(" Install Claude Code, Claude Desktop, Cursor, Windsurf, or Gemini CLI first.");
|
|
115
|
+
rl.close();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// Collect credentials
|
|
119
|
+
console.log(" Enter your Supabase credentials:");
|
|
120
|
+
console.log("");
|
|
121
|
+
const supabaseUrl = await prompt(rl, " Supabase URL");
|
|
122
|
+
if (!supabaseUrl) {
|
|
123
|
+
console.log(" Supabase URL is required. Aborting.");
|
|
124
|
+
rl.close();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const supabaseKey = await prompt(rl, " Supabase Service Role Key");
|
|
128
|
+
if (!supabaseKey) {
|
|
129
|
+
console.log(" Service Role Key is required. Aborting.");
|
|
130
|
+
rl.close();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const storeId = await prompt(rl, " Store ID (optional, press enter to skip)");
|
|
134
|
+
console.log("");
|
|
135
|
+
// Build MCP server entry
|
|
136
|
+
const serverEntry = {
|
|
137
|
+
type: "stdio",
|
|
138
|
+
command: "npx",
|
|
139
|
+
args: ["-y", "swagmanager-mcp"],
|
|
140
|
+
env: {
|
|
141
|
+
SUPABASE_URL: supabaseUrl,
|
|
142
|
+
SUPABASE_SERVICE_ROLE_KEY: supabaseKey,
|
|
143
|
+
...(storeId ? { STORE_ID: storeId } : {}),
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
// Install to each detected CLI
|
|
147
|
+
let installed = 0;
|
|
148
|
+
for (const target of detected) {
|
|
149
|
+
const install = await promptYN(rl, ` Install to ${target.name}?`);
|
|
150
|
+
if (!install)
|
|
151
|
+
continue;
|
|
152
|
+
const config = readJSON(target.configPath);
|
|
153
|
+
if (!config[target.configKey])
|
|
154
|
+
config[target.configKey] = {};
|
|
155
|
+
// Check for existing entry
|
|
156
|
+
if (config[target.configKey].swagmanager) {
|
|
157
|
+
const overwrite = await promptYN(rl, ` swagmanager already configured in ${target.name}. Overwrite?`, false);
|
|
158
|
+
if (!overwrite)
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
config[target.configKey].swagmanager = target.format(serverEntry);
|
|
162
|
+
writeJSON(target.configPath, config);
|
|
163
|
+
console.log(` Wrote ${target.configPath}`);
|
|
164
|
+
installed++;
|
|
165
|
+
}
|
|
166
|
+
console.log("");
|
|
167
|
+
if (installed > 0) {
|
|
168
|
+
console.log(` Done! Installed to ${installed} CLI${installed > 1 ? "s" : ""}.`);
|
|
169
|
+
console.log(" Restart your CLI(s) to load the MCP server.");
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
console.log(" No changes made.");
|
|
173
|
+
}
|
|
174
|
+
console.log("");
|
|
175
|
+
rl.close();
|
|
176
|
+
}
|
|
177
|
+
// Run if called directly
|
|
178
|
+
const isDirectRun = process.argv[1]?.includes("setup");
|
|
179
|
+
if (isDirectRun) {
|
|
180
|
+
runSetup().catch(console.error);
|
|
181
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OTA Auto-Updater for SwagManager MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Checks the npm registry for newer versions and self-updates.
|
|
5
|
+
* - Runs on server startup (non-blocking)
|
|
6
|
+
* - Runs periodically (default: every 4 hours)
|
|
7
|
+
* - Updates in-place via `npm install -g` or local install
|
|
8
|
+
* - Signals the process to restart after update
|
|
9
|
+
*/
|
|
10
|
+
export interface UpdateCheckResult {
|
|
11
|
+
currentVersion: string;
|
|
12
|
+
latestVersion: string | null;
|
|
13
|
+
updateAvailable: boolean;
|
|
14
|
+
updated: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Check for updates and install if available.
|
|
18
|
+
* Returns the result without restarting — caller decides restart behavior.
|
|
19
|
+
*/
|
|
20
|
+
export declare function checkForUpdates(autoInstall?: boolean): Promise<UpdateCheckResult>;
|
|
21
|
+
/**
|
|
22
|
+
* Start periodic update checks in the background.
|
|
23
|
+
* First check runs immediately (non-blocking), then every CHECK_INTERVAL_MS.
|
|
24
|
+
*/
|
|
25
|
+
export declare function startUpdateLoop(autoInstall?: boolean): NodeJS.Timeout;
|
package/dist/updater.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OTA Auto-Updater for SwagManager MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Checks the npm registry for newer versions and self-updates.
|
|
5
|
+
* - Runs on server startup (non-blocking)
|
|
6
|
+
* - Runs periodically (default: every 4 hours)
|
|
7
|
+
* - Updates in-place via `npm install -g` or local install
|
|
8
|
+
* - Signals the process to restart after update
|
|
9
|
+
*/
|
|
10
|
+
import { execFile } from "child_process";
|
|
11
|
+
import { readFileSync } from "fs";
|
|
12
|
+
import { dirname, join } from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
import https from "https";
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// CONFIG
|
|
17
|
+
// ============================================================================
|
|
18
|
+
const PACKAGE_NAME = "swagmanager-mcp";
|
|
19
|
+
const CHECK_INTERVAL_MS = 4 * 60 * 60 * 1000; // 4 hours
|
|
20
|
+
const REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// HELPERS
|
|
23
|
+
// ============================================================================
|
|
24
|
+
function getCurrentVersion() {
|
|
25
|
+
try {
|
|
26
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
27
|
+
const __dirname = dirname(__filename);
|
|
28
|
+
const pkgPath = join(__dirname, "..", "package.json");
|
|
29
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
30
|
+
return pkg.version || "0.0.0";
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return "0.0.0";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function parseVersion(v) {
|
|
37
|
+
return v.replace(/^v/, "").split(".").map(Number);
|
|
38
|
+
}
|
|
39
|
+
function isNewer(remote, local) {
|
|
40
|
+
const r = parseVersion(remote);
|
|
41
|
+
const l = parseVersion(local);
|
|
42
|
+
for (let i = 0; i < 3; i++) {
|
|
43
|
+
if ((r[i] || 0) > (l[i] || 0))
|
|
44
|
+
return true;
|
|
45
|
+
if ((r[i] || 0) < (l[i] || 0))
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
function fetchLatestVersion() {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
const req = https.get(REGISTRY_URL, { timeout: 10_000 }, (res) => {
|
|
53
|
+
let body = "";
|
|
54
|
+
res.on("data", (chunk) => (body += chunk.toString()));
|
|
55
|
+
res.on("end", () => {
|
|
56
|
+
try {
|
|
57
|
+
const data = JSON.parse(body);
|
|
58
|
+
resolve({
|
|
59
|
+
version: data.version,
|
|
60
|
+
changelog: data.changelog || data.description,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
resolve(null);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
req.on("error", () => resolve(null));
|
|
69
|
+
req.on("timeout", () => {
|
|
70
|
+
req.destroy();
|
|
71
|
+
resolve(null);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function runNpmInstall(version) {
|
|
76
|
+
return new Promise((resolve) => {
|
|
77
|
+
const args = ["install", "-g", `${PACKAGE_NAME}@${version}`];
|
|
78
|
+
console.error(`[updater] Running: npm ${args.join(" ")}`);
|
|
79
|
+
execFile("npm", args, { timeout: 120_000 }, (error, stdout, stderr) => {
|
|
80
|
+
if (error) {
|
|
81
|
+
console.error(`[updater] Install failed: ${error.message}`);
|
|
82
|
+
if (stderr)
|
|
83
|
+
console.error(`[updater] stderr: ${stderr}`);
|
|
84
|
+
resolve(false);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.error(`[updater] Install succeeded`);
|
|
88
|
+
if (stdout)
|
|
89
|
+
console.error(`[updater] ${stdout.trim()}`);
|
|
90
|
+
resolve(true);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Check for updates and install if available.
|
|
97
|
+
* Returns the result without restarting — caller decides restart behavior.
|
|
98
|
+
*/
|
|
99
|
+
export async function checkForUpdates(autoInstall = true) {
|
|
100
|
+
const currentVersion = getCurrentVersion();
|
|
101
|
+
const latest = await fetchLatestVersion();
|
|
102
|
+
if (!latest) {
|
|
103
|
+
console.error(`[updater] Could not reach npm registry (offline?)`);
|
|
104
|
+
return { currentVersion, latestVersion: null, updateAvailable: false, updated: false };
|
|
105
|
+
}
|
|
106
|
+
const updateAvailable = isNewer(latest.version, currentVersion);
|
|
107
|
+
if (!updateAvailable) {
|
|
108
|
+
console.error(`[updater] Up to date (v${currentVersion})`);
|
|
109
|
+
return { currentVersion, latestVersion: latest.version, updateAvailable: false, updated: false };
|
|
110
|
+
}
|
|
111
|
+
console.error(`[updater] Update available: v${currentVersion} → v${latest.version}`);
|
|
112
|
+
if (!autoInstall) {
|
|
113
|
+
return { currentVersion, latestVersion: latest.version, updateAvailable: true, updated: false };
|
|
114
|
+
}
|
|
115
|
+
const installed = await runNpmInstall(latest.version);
|
|
116
|
+
if (installed) {
|
|
117
|
+
console.error(`[updater] Updated to v${latest.version}. Restart to use new version.`);
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
currentVersion,
|
|
121
|
+
latestVersion: latest.version,
|
|
122
|
+
updateAvailable: true,
|
|
123
|
+
updated: installed,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Start periodic update checks in the background.
|
|
128
|
+
* First check runs immediately (non-blocking), then every CHECK_INTERVAL_MS.
|
|
129
|
+
*/
|
|
130
|
+
export function startUpdateLoop(autoInstall = true) {
|
|
131
|
+
// Initial check (fire-and-forget)
|
|
132
|
+
checkForUpdates(autoInstall).catch(() => { });
|
|
133
|
+
// Periodic checks
|
|
134
|
+
const timer = setInterval(() => {
|
|
135
|
+
checkForUpdates(autoInstall).catch(() => { });
|
|
136
|
+
}, CHECK_INTERVAL_MS);
|
|
137
|
+
// Don't prevent process exit
|
|
138
|
+
timer.unref();
|
|
139
|
+
return timer;
|
|
140
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swagmanager-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "SwagManager MCP Server — manage inventory, orders, analytics, and more from Claude Code/Desktop",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,6 +30,14 @@
|
|
|
30
30
|
"bin",
|
|
31
31
|
"README.md"
|
|
32
32
|
],
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/floradistro/whale-mcp.git"
|
|
39
|
+
},
|
|
40
|
+
"author": "Flora Distribution",
|
|
33
41
|
"keywords": [
|
|
34
42
|
"mcp",
|
|
35
43
|
"claude",
|