@striderlabs/mcp-hm 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/README.md +141 -0
- package/dist/index.js +21921 -0
- package/package.json +44 -0
- package/server.json +20 -0
- package/src/auth.ts +114 -0
- package/src/browser.ts +820 -0
- package/src/index.ts +565 -0
- package/tsconfig.json +14 -0
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@striderlabs/mcp-hm",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for H&M - let AI agents search fashion products, manage cart, track orders, browse sales, and more",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"striderlabs-mcp-hm": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js='#!/usr/bin/env node' --external:patchright --external:patchright-core --external:chromium-bidi",
|
|
12
|
+
"start": "node dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mcp",
|
|
16
|
+
"hm",
|
|
17
|
+
"h&m",
|
|
18
|
+
"fashion",
|
|
19
|
+
"clothing",
|
|
20
|
+
"shopping",
|
|
21
|
+
"ai-agent",
|
|
22
|
+
"strider",
|
|
23
|
+
"model-context-protocol",
|
|
24
|
+
"browser-automation",
|
|
25
|
+
"retail"
|
|
26
|
+
],
|
|
27
|
+
"author": "Strider Labs <hello@striderlabs.ai>",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/striderlabs/mcp-hm"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://striderlabs.ai",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
36
|
+
"patchright": "^1.58.2"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.11.0",
|
|
40
|
+
"esbuild": "^0.24.0",
|
|
41
|
+
"typescript": "^5.3.3"
|
|
42
|
+
},
|
|
43
|
+
"mcpName": "io.github.striderlabs/hm"
|
|
44
|
+
}
|
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/hm",
|
|
4
|
+
"description": "H&M MCP server — search fashion products, manage cart, track orders, browse sales, and more. By Strider Labs.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/striderlabs/mcp-hm",
|
|
7
|
+
"source": "github"
|
|
8
|
+
},
|
|
9
|
+
"version": "0.1.0",
|
|
10
|
+
"packages": [
|
|
11
|
+
{
|
|
12
|
+
"registryType": "npm",
|
|
13
|
+
"identifier": "@striderlabs/mcp-hm",
|
|
14
|
+
"version": "0.1.0",
|
|
15
|
+
"transport": {
|
|
16
|
+
"type": "stdio"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
package/src/auth.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import type { BrowserContext } from "patchright";
|
|
5
|
+
|
|
6
|
+
export interface SessionInfo {
|
|
7
|
+
isLoggedIn: boolean;
|
|
8
|
+
userEmail?: string;
|
|
9
|
+
userName?: string;
|
|
10
|
+
memberId?: string;
|
|
11
|
+
lastUpdated: string;
|
|
12
|
+
country?: string;
|
|
13
|
+
currency?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getConfigDir(): string {
|
|
17
|
+
return join(homedir(), ".strider", "hm");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getCookiesPath(): string {
|
|
21
|
+
return join(getConfigDir(), "cookies.json");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getSessionPath(): string {
|
|
25
|
+
return join(getConfigDir(), "session.json");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function ensureConfigDir(): void {
|
|
29
|
+
const dir = getConfigDir();
|
|
30
|
+
if (!existsSync(dir)) {
|
|
31
|
+
mkdirSync(dir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function saveCookies(context: BrowserContext): Promise<void> {
|
|
36
|
+
try {
|
|
37
|
+
ensureConfigDir();
|
|
38
|
+
const cookies = await context.cookies();
|
|
39
|
+
const validCookies = cookies.filter((c) => {
|
|
40
|
+
if (!c.expires || c.expires === -1) return true;
|
|
41
|
+
return c.expires * 1000 > Date.now();
|
|
42
|
+
});
|
|
43
|
+
writeFileSync(getCookiesPath(), JSON.stringify(validCookies, null, 2));
|
|
44
|
+
} catch {
|
|
45
|
+
// ignore
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function loadCookies(context: BrowserContext): Promise<boolean> {
|
|
50
|
+
try {
|
|
51
|
+
const path = getCookiesPath();
|
|
52
|
+
if (!existsSync(path)) return false;
|
|
53
|
+
const raw = readFileSync(path, "utf-8");
|
|
54
|
+
const cookies = JSON.parse(raw);
|
|
55
|
+
if (!Array.isArray(cookies) || cookies.length === 0) return false;
|
|
56
|
+
const valid = cookies.filter((c) => {
|
|
57
|
+
if (!c.expires || c.expires === -1) return true;
|
|
58
|
+
return c.expires * 1000 > Date.now();
|
|
59
|
+
});
|
|
60
|
+
if (valid.length === 0) return false;
|
|
61
|
+
await context.addCookies(valid);
|
|
62
|
+
return true;
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function saveSessionInfo(info: SessionInfo): void {
|
|
69
|
+
try {
|
|
70
|
+
ensureConfigDir();
|
|
71
|
+
writeFileSync(getSessionPath(), JSON.stringify(info, null, 2));
|
|
72
|
+
} catch {
|
|
73
|
+
// ignore
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function loadSessionInfo(): SessionInfo {
|
|
78
|
+
try {
|
|
79
|
+
const path = getSessionPath();
|
|
80
|
+
if (!existsSync(path)) return { isLoggedIn: false, lastUpdated: new Date().toISOString() };
|
|
81
|
+
const raw = readFileSync(path, "utf-8");
|
|
82
|
+
return JSON.parse(raw) as SessionInfo;
|
|
83
|
+
} catch {
|
|
84
|
+
return { isLoggedIn: false, lastUpdated: new Date().toISOString() };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function clearAuthData(): void {
|
|
89
|
+
try {
|
|
90
|
+
const cookiesPath = getCookiesPath();
|
|
91
|
+
const sessionPath = getSessionPath();
|
|
92
|
+
if (existsSync(cookiesPath)) writeFileSync(cookiesPath, "[]");
|
|
93
|
+
if (existsSync(sessionPath)) {
|
|
94
|
+
writeFileSync(
|
|
95
|
+
sessionPath,
|
|
96
|
+
JSON.stringify({ isLoggedIn: false, lastUpdated: new Date().toISOString() }, null, 2)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
// ignore
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function hasSavedCookies(): boolean {
|
|
105
|
+
const path = getCookiesPath();
|
|
106
|
+
if (!existsSync(path)) return false;
|
|
107
|
+
try {
|
|
108
|
+
const raw = readFileSync(path, "utf-8");
|
|
109
|
+
const cookies = JSON.parse(raw);
|
|
110
|
+
return Array.isArray(cookies) && cookies.length > 0;
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|