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.
@@ -4,14 +4,13 @@
4
4
  * SwagManager MCP Server CLI
5
5
  *
6
6
  * Usage:
7
- * npx @swagmanager/mcp
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
- // Import and run the server
24
- const serverPath = join(__dirname, "..", "dist", "index.js");
25
- await import(serverPath);
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);
@@ -0,0 +1,8 @@
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
+ export declare function runSetup(): Promise<void>;
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;
@@ -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.0.0",
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",