@striderlabs/mcp-bjs 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/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@striderlabs/mcp-bjs",
3
+ "version": "0.1.0",
4
+ "description": "BJs Wholesale Club MCP server — search, cart, and order from BJs. By Strider Labs.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "striderlabs-mcp-bjs": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "esbuild src/index.ts --bundle --platform=node --target=node20 --outfile=dist/index.js --banner:js='#!/usr/bin/env node' --external:patchright --external:chromium-bidi",
12
+ "start": "node dist/index.js",
13
+ "dev": "npm run build && npm start"
14
+ },
15
+ "dependencies": {
16
+ "@modelcontextprotocol/sdk": "^1.0.0",
17
+ "patchright": "^1.58.2"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^20.11.0",
21
+ "esbuild": "^0.24.0",
22
+ "typescript": "^5.3.3"
23
+ },
24
+ "keywords": [
25
+ "mcp",
26
+ "bjs",
27
+ "wholesale",
28
+ "shopping",
29
+ "ai",
30
+ "claude"
31
+ ],
32
+ "author": "Strider Labs",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/striderlabs/mcp-bjs"
37
+ },
38
+ "mcp": {
39
+ "name": "io.github.striderlabs/bjs"
40
+ }
41
+ }
package/server.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.striderlabs/bjs",
4
+ "description": "BJs Wholesale Club MCP server — search products, manage cart, check inventory, find clubs, and more. By Strider Labs.",
5
+ "repository": {
6
+ "url": "https://github.com/striderlabs/mcp-bjs",
7
+ "source": "github"
8
+ },
9
+ "version": "0.1.0",
10
+ "packages": [
11
+ {
12
+ "registryType": "npm",
13
+ "identifier": "@striderlabs/mcp-bjs",
14
+ "version": "0.1.0",
15
+ "transport": {
16
+ "type": "stdio"
17
+ }
18
+ }
19
+ ]
20
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,81 @@
1
+ import { BrowserContext } from "patchright";
2
+ import { readFile, writeFile, mkdir, rm } from "fs/promises";
3
+ import { existsSync } from "fs";
4
+ import { homedir } from "os";
5
+ import { join } from "path";
6
+
7
+ export interface SessionInfo {
8
+ isLoggedIn: boolean;
9
+ memberName?: string;
10
+ memberEmail?: string;
11
+ membershipNumber?: string;
12
+ membershipType?: string;
13
+ membershipExpiry?: string;
14
+ lastUpdated: string;
15
+ preferredClubId?: string;
16
+ preferredClubName?: string;
17
+ }
18
+
19
+ export function getConfigDir(): string {
20
+ return join(homedir(), ".strider", "bjs");
21
+ }
22
+
23
+ export async function saveCookies(context: BrowserContext): Promise<void> {
24
+ const configDir = getConfigDir();
25
+ await mkdir(configDir, { recursive: true });
26
+ const cookies = await context.cookies();
27
+ // Only save non-expired cookies
28
+ const now = Date.now() / 1000;
29
+ const validCookies = cookies.filter(
30
+ (c) => !c.expires || c.expires === -1 || c.expires > now
31
+ );
32
+ await writeFile(
33
+ join(configDir, "cookies.json"),
34
+ JSON.stringify(validCookies, null, 2)
35
+ );
36
+ }
37
+
38
+ export async function loadCookies(context: BrowserContext): Promise<void> {
39
+ const cookiePath = join(getConfigDir(), "cookies.json");
40
+ if (!existsSync(cookiePath)) return;
41
+ try {
42
+ const data = await readFile(cookiePath, "utf-8");
43
+ const cookies = JSON.parse(data);
44
+ if (Array.isArray(cookies) && cookies.length > 0) {
45
+ await context.addCookies(cookies);
46
+ }
47
+ } catch {
48
+ // Silently ignore parse errors — stale cookies
49
+ }
50
+ }
51
+
52
+ export async function saveSessionInfo(info: SessionInfo): Promise<void> {
53
+ const configDir = getConfigDir();
54
+ await mkdir(configDir, { recursive: true });
55
+ await writeFile(
56
+ join(configDir, "session.json"),
57
+ JSON.stringify(info, null, 2)
58
+ );
59
+ }
60
+
61
+ export async function loadSessionInfo(): Promise<SessionInfo | null> {
62
+ const sessionPath = join(getConfigDir(), "session.json");
63
+ if (!existsSync(sessionPath)) return null;
64
+ try {
65
+ const data = await readFile(sessionPath, "utf-8");
66
+ return JSON.parse(data) as SessionInfo;
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+
72
+ export async function clearAuthData(): Promise<void> {
73
+ const configDir = getConfigDir();
74
+ if (existsSync(configDir)) {
75
+ await rm(configDir, { recursive: true, force: true });
76
+ }
77
+ }
78
+
79
+ export async function hasSavedCookies(): Promise<boolean> {
80
+ return existsSync(join(getConfigDir(), "cookies.json"));
81
+ }