mcp-server-wework 0.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/index.js ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+ const { spawn } = require("child_process");
5
+
6
+ const { startUpdateCheck } = require("./update_check");
7
+
8
+ const exe = process.platform === "win32" ? "mcp-server-wework.exe" : "mcp-server-wework";
9
+ const binPath = path.join(__dirname, exe);
10
+
11
+ const PKG = (() => {
12
+ try {
13
+ return require("./package.json");
14
+ } catch {
15
+ return null;
16
+ }
17
+ })();
18
+
19
+ if (!fs.existsSync(binPath)) {
20
+ console.error(`Binary not found: ${binPath}`);
21
+ process.exit(1);
22
+ }
23
+
24
+ if (process.platform !== "win32") {
25
+ try {
26
+ fs.chmodSync(binPath, 0o755);
27
+ } catch {}
28
+ }
29
+
30
+ const child = spawn(binPath, process.argv.slice(2), {
31
+ stdio: "inherit",
32
+ windowsHide: true,
33
+ });
34
+
35
+ // Best-effort update notice (cached + short timeout).
36
+ // Aborts on command exit so it never delays shutdown.
37
+ const updateAbort = new AbortController();
38
+ startUpdateCheck({ installedVersion: PKG && PKG.version, signal: updateAbort.signal });
39
+
40
+ child.on("exit", (code) => {
41
+ try {
42
+ updateAbort.abort();
43
+ } catch {}
44
+ process.exitCode = code == null ? 1 : code;
45
+ });
46
+ child.on("error", (err) => {
47
+ console.error(err.message);
48
+ process.exitCode = 1;
49
+ });
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "mcp-server-wework",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for WeWork bookings and space search",
5
+ "scripts": {
6
+ "postinstall": "node postinstall.js"
7
+ },
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "bin": {
12
+ "mcp-server-wework": "index.js"
13
+ },
14
+ "dependencies": {
15
+ "semver": "^7.7.4"
16
+ }
17
+ }
package/postinstall.js ADDED
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+ const os = require("os");
5
+ const https = require("https");
6
+ const crypto = require("crypto");
7
+ const { spawnSync } = require("child_process");
8
+
9
+ const OWNER = "dvcrn";
10
+ const REPO = "mcp-server-wework";
11
+ const BIN = "mcp-server-wework";
12
+ const VERSION_ENV = "MCP_SERVER_WEWORK_VERSION";
13
+ const BASE_URL_ENV = "MCP_SERVER_WEWORK_BASE_URL";
14
+ const ARCH_ENV = "MCP_SERVER_WEWORK_ARCH";
15
+ const PLATFORM_ENV = "MCP_SERVER_WEWORK_PLATFORM";
16
+
17
+ function httpGet(url, { headers } = {}) {
18
+ return new Promise((resolve, reject) => {
19
+ const req = https.get(url, { headers }, (res) => {
20
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
21
+ resolve(httpGet(res.headers.location, { headers }));
22
+ return;
23
+ }
24
+ if (res.statusCode !== 200) {
25
+ reject(new Error(`GET ${url} -> ${res.statusCode}`));
26
+ return;
27
+ }
28
+ const chunks = [];
29
+ res.on("data", (c) => chunks.push(c));
30
+ res.on("end", () => resolve(Buffer.concat(chunks)));
31
+ });
32
+ req.on("error", reject);
33
+ });
34
+ }
35
+
36
+ function sha256(buf) {
37
+ const h = crypto.createHash("sha256");
38
+ h.update(buf);
39
+ return h.digest("hex");
40
+ }
41
+
42
+ function parseChecksums(text) {
43
+ const map = new Map();
44
+ const lines = text
45
+ .split(/\r?\n/)
46
+ .map((l) => l.trim())
47
+ .filter(Boolean);
48
+ for (const line of lines) {
49
+ let m = line.match(/^([a-f0-9]{64})\s+(.+)$/i);
50
+ if (m) {
51
+ map.set(m[2], m[1]);
52
+ continue;
53
+ }
54
+ m = line.match(/^sha256:([a-f0-9]{64})\s+(.+)$/i);
55
+ if (m) {
56
+ map.set(m[2], m[1]);
57
+ continue;
58
+ }
59
+ m = line.match(/^SHA256\s+\((.+)\)\s+=\s+([a-f0-9]{64})$/i);
60
+ if (m) {
61
+ map.set(m[1], m[2]);
62
+ continue;
63
+ }
64
+ }
65
+ return map;
66
+ }
67
+
68
+ (async function main() {
69
+ try {
70
+ const platformRaw = process.env[PLATFORM_ENV] || process.platform;
71
+ const platform = platformRaw === "win32" ? "windows" : platformRaw;
72
+ if (!["darwin", "linux", "windows"].includes(platform)) {
73
+ console.error("mcp-server-wework: npm install supports macOS (darwin), Linux, and Windows only");
74
+ process.exit(1);
75
+ }
76
+
77
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8"));
78
+ const version = process.env[VERSION_ENV] || pkg.version || "";
79
+ if (!version) {
80
+ console.error("postinstall: could not determine version");
81
+ process.exit(1);
82
+ }
83
+
84
+ const detectedArch =
85
+ process.arch === "x64"
86
+ ? "amd64"
87
+ : process.arch === "arm64"
88
+ ? "arm64"
89
+ : process.arch === "arm"
90
+ ? "armv7"
91
+ : process.arch;
92
+ const arch = process.env[ARCH_ENV] || detectedArch;
93
+ if (!["amd64", "arm64", "armv7"].includes(arch)) {
94
+ console.error(`Unsupported arch: ${arch}`);
95
+ process.exit(1);
96
+ }
97
+ if (platform === "windows" && arch === "armv7") {
98
+ console.error(`Unsupported Windows arch: ${arch}`);
99
+ process.exit(1);
100
+ }
101
+
102
+ const assetName = `${BIN}_${version}_${platform}_${arch}.tar.gz`;
103
+ const baseOverride = process.env[BASE_URL_ENV];
104
+ const bases = baseOverride
105
+ ? [baseOverride]
106
+ : [
107
+ `https://github.com/${OWNER}/${REPO}/releases/download/${version}`,
108
+ `https://github.com/${OWNER}/${REPO}/releases/download/v${version}`,
109
+ ];
110
+ const headers = { "User-Agent": `${REPO}-postinstall` };
111
+ const outDir = __dirname;
112
+ const exe = platform === "windows" ? `${BIN}.exe` : BIN;
113
+ const binPath = path.join(outDir, exe);
114
+
115
+ if (fs.existsSync(binPath)) {
116
+ try {
117
+ fs.chmodSync(binPath, 0o755);
118
+ } catch {}
119
+ process.exit(0);
120
+ }
121
+
122
+ let tarGz = null;
123
+ let baseUsed = "";
124
+ let lastErr = null;
125
+ for (const base of bases) {
126
+ const url = `${base}/${assetName}`;
127
+ console.log(`postinstall: downloading ${assetName} from ${url}`);
128
+ try {
129
+ tarGz = await httpGet(url, { headers });
130
+ baseUsed = base;
131
+ break;
132
+ } catch (e) {
133
+ lastErr = e;
134
+ }
135
+ }
136
+ if (!tarGz) throw lastErr || new Error("failed to download binary");
137
+
138
+ // checksum (best effort)
139
+ try {
140
+ const checksumsUrl = `${baseUsed}/checksums.txt`;
141
+ const checksumsBuf = await httpGet(checksumsUrl, { headers });
142
+ const checksums = parseChecksums(checksumsBuf.toString("utf8"));
143
+ const sumExpected = checksums.get(assetName);
144
+ if (!sumExpected) throw new Error("asset not in checksums.txt");
145
+ const sumActual = sha256(tarGz);
146
+ if (sumActual.toLowerCase() !== sumExpected.toLowerCase()) throw new Error("checksum mismatch");
147
+ console.log("postinstall: checksum OK");
148
+ } catch (e) {
149
+ console.warn(`postinstall: checksum skipped/failed: ${e.message}`);
150
+ }
151
+
152
+ // extract only the binary into npm directory (archive contains binary at root)
153
+ const tmpFile = path.join(os.tmpdir(), `${REPO}-${Date.now()}.tar.gz`);
154
+ fs.writeFileSync(tmpFile, tarGz);
155
+ const tarRes = spawnSync("tar", ["-xzf", tmpFile, "-C", outDir, exe], { stdio: "inherit" });
156
+ if (tarRes.status !== 0) {
157
+ console.error("postinstall: failed to extract binary");
158
+ process.exit(1);
159
+ }
160
+ try {
161
+ fs.chmodSync(binPath, 0o755);
162
+ } catch {}
163
+ try {
164
+ fs.unlinkSync(tmpFile);
165
+ } catch {}
166
+ console.log(`postinstall: installed ${exe} to ${outDir}`);
167
+ process.exit(0);
168
+ } catch (err) {
169
+ console.error(`postinstall error: ${err.message}`);
170
+ process.exit(1);
171
+ }
172
+ })();
@@ -0,0 +1,166 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const os = require("os");
4
+ const https = require("https");
5
+ const semver = require("semver");
6
+
7
+ const DEFAULT_PACKAGE_NAME = "mcp-server-wework";
8
+ const DEFAULT_UPDATE_COMMAND = "npm install -g mcp-server-wework";
9
+ const CACHE_DIR_NAME = "mcp-server-wework";
10
+
11
+ const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
12
+ const UPDATE_CHECK_TIMEOUT_MS = 1200;
13
+
14
+ function isDisabled() {
15
+ return (
16
+ process.env.MCP_SERVER_WEWORK_NO_UPDATE_NOTICE === "1" ||
17
+ process.env.MCP_SERVER_WEWORK_NO_UPDATE_NOTICE === "true" ||
18
+ process.env.NO_UPDATE_NOTIFIER === "1" ||
19
+ process.env.NO_UPDATE_NOTIFIER === "true" ||
20
+ process.env.CI === "1" ||
21
+ process.env.CI === "true" ||
22
+ process.env.CI === "yes"
23
+ );
24
+ }
25
+
26
+ function getCacheFile() {
27
+ const home = os.homedir();
28
+ if (!home) return null;
29
+
30
+ if (process.platform === "win32") {
31
+ const base = process.env.LOCALAPPDATA || process.env.APPDATA;
32
+ if (!base) return path.join(home, "AppData", "Local", CACHE_DIR_NAME, "update.json");
33
+ return path.join(base, CACHE_DIR_NAME, "update.json");
34
+ }
35
+
36
+ if (process.platform === "darwin") {
37
+ return path.join(home, "Library", "Caches", CACHE_DIR_NAME, "update.json");
38
+ }
39
+
40
+ const base = process.env.XDG_CACHE_HOME || path.join(home, ".cache");
41
+ return path.join(base, CACHE_DIR_NAME, "update.json");
42
+ }
43
+
44
+ function readCache() {
45
+ const file = getCacheFile();
46
+ if (!file) return null;
47
+ try {
48
+ return JSON.parse(fs.readFileSync(file, "utf8"));
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ function writeCache(data) {
55
+ const file = getCacheFile();
56
+ if (!file) return;
57
+ try {
58
+ fs.mkdirSync(path.dirname(file), { recursive: true });
59
+ fs.writeFileSync(file, JSON.stringify(data), "utf8");
60
+ } catch {}
61
+ }
62
+
63
+ function fetchLatestVersion(packageName, timeoutMs, signal) {
64
+ const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
65
+
66
+ return new Promise((resolve, reject) => {
67
+ const req = https.get(url, { headers: { "User-Agent": `${DEFAULT_PACKAGE_NAME}-update-check` } }, (res) => {
68
+ if (res.statusCode !== 200) {
69
+ reject(new Error(`GET ${url} -> ${res.statusCode}`));
70
+ res.resume();
71
+ return;
72
+ }
73
+
74
+ const chunks = [];
75
+ res.on("data", (c) => chunks.push(c));
76
+ res.on("end", () => {
77
+ try {
78
+ const json = JSON.parse(Buffer.concat(chunks).toString("utf8"));
79
+ resolve(typeof json.version === "string" ? json.version : "");
80
+ } catch (e) {
81
+ reject(e);
82
+ }
83
+ });
84
+ });
85
+
86
+ req.on("error", reject);
87
+ req.setTimeout(timeoutMs, () => req.destroy(new Error("timeout")));
88
+
89
+ if (signal) {
90
+ if (signal.aborted) {
91
+ req.destroy(new Error("aborted"));
92
+ return;
93
+ }
94
+ signal.addEventListener("abort", () => req.destroy(new Error("aborted")), { once: true });
95
+ }
96
+ });
97
+ }
98
+
99
+ function normalizeVersion(version) {
100
+ if (!version) return "";
101
+ const cleaned = semver.clean(version);
102
+ if (cleaned) return cleaned;
103
+ const coerced = semver.coerce(version);
104
+ return coerced ? semver.valid(coerced) || "" : "";
105
+ }
106
+
107
+ function printNotice({ packageName, installed, latest, updateCommand }) {
108
+ const prefix = `${packageName}:`;
109
+ if (installed && latest) {
110
+ console.error(`${prefix} an update is available (installed ${installed}, latest ${latest}). Update with: ${updateCommand}`);
111
+ return;
112
+ }
113
+ console.error(`${prefix} an update is available. Update with: ${updateCommand}`);
114
+ }
115
+
116
+ async function runUpdateCheck({ packageName, installedVersion, updateCommand, signal }) {
117
+ if (isDisabled()) return;
118
+ if (!process.stderr.isTTY) return;
119
+
120
+ const installed = normalizeVersion(installedVersion);
121
+ if (!installed) return;
122
+
123
+ const cache = readCache() || {};
124
+ const lastChecked = Number.isFinite(cache.lastChecked) ? cache.lastChecked : 0;
125
+ const lastNotified = Number.isFinite(cache.lastNotified) ? cache.lastNotified : 0;
126
+ const cachedLatest = typeof cache.latest === "string" ? cache.latest : "";
127
+
128
+ const now = Date.now();
129
+
130
+ if (lastChecked && now - lastChecked < UPDATE_CHECK_INTERVAL_MS) {
131
+ const latestCached = normalizeVersion(cachedLatest);
132
+ if (latestCached && semver.gt(latestCached, installed)) {
133
+ if (!lastNotified || now - lastNotified >= UPDATE_CHECK_INTERVAL_MS) {
134
+ printNotice({ packageName, installed, latest: latestCached, updateCommand });
135
+ writeCache({ ...cache, lastNotified: now });
136
+ }
137
+ }
138
+ return;
139
+ }
140
+
141
+ const latestRaw = await fetchLatestVersion(packageName, UPDATE_CHECK_TIMEOUT_MS, signal);
142
+ const latest = normalizeVersion(latestRaw);
143
+
144
+ writeCache({ lastChecked: now, lastNotified, latest: latest || latestRaw });
145
+
146
+ if (latest && semver.gt(latest, installed)) {
147
+ if (!lastNotified || now - lastNotified >= UPDATE_CHECK_INTERVAL_MS) {
148
+ printNotice({ packageName, installed, latest, updateCommand });
149
+ writeCache({ lastChecked: now, lastNotified: now, latest });
150
+ }
151
+ }
152
+ }
153
+
154
+ function startUpdateCheck({
155
+ packageName = DEFAULT_PACKAGE_NAME,
156
+ installedVersion,
157
+ updateCommand = DEFAULT_UPDATE_COMMAND,
158
+ signal,
159
+ } = {}) {
160
+ // Fire-and-forget; never block MCP server startup.
161
+ runUpdateCheck({ packageName, installedVersion, updateCommand, signal }).catch(() => {});
162
+ }
163
+
164
+ module.exports = {
165
+ startUpdateCheck,
166
+ };