@lofder/dsers-mcp-product 1.3.8 → 1.4.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 +43 -19
- package/dist/auth/browser-finder.d.ts +5 -5
- package/dist/auth/browser-finder.d.ts.map +1 -1
- package/dist/auth/browser-finder.js +13 -100
- package/dist/auth/browser-finder.js.map +1 -1
- package/dist/auth/index.d.ts +3 -5
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +3 -5
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/oauth.d.ts +36 -0
- package/dist/auth/oauth.d.ts.map +1 -0
- package/dist/auth/oauth.js +173 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/auth/token-store.d.ts +10 -2
- package/dist/auth/token-store.d.ts.map +1 -1
- package/dist/auth/token-store.js +27 -2
- package/dist/auth/token-store.js.map +1 -1
- package/dist/cli.js +38 -63
- package/dist/cli.js.map +1 -1
- package/dist/dsers/auth.d.ts +5 -6
- package/dist/dsers/auth.d.ts.map +1 -1
- package/dist/dsers/auth.js +57 -88
- package/dist/dsers/auth.js.map +1 -1
- package/dist/dsers/client.d.ts +0 -1
- package/dist/dsers/client.d.ts.map +1 -1
- package/dist/dsers/client.js +6 -8
- package/dist/dsers/client.js.map +1 -1
- package/dist/dsers/config.d.ts +19 -16
- package/dist/dsers/config.d.ts.map +1 -1
- package/dist/dsers/config.js +22 -31
- package/dist/dsers/config.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -10
- package/dist/index.js.map +1 -1
- package/dist/instructions.d.ts.map +1 -1
- package/dist/instructions.js +3 -1
- package/dist/instructions.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
- **Safety checks** — automatically blocks pushes that would result in below-cost pricing, zero price, or zero stock
|
|
31
31
|
- **SEO optimization** — let AI rewrite the title and description for better search rankings before pushing
|
|
32
32
|
|
|
33
|
-
The server is hosted on [Vercel](https://
|
|
33
|
+
The server is hosted on [Vercel](https://ai.silentrillmcp.com/dropshipping/mcp) and published across multiple platforms:
|
|
34
34
|
|
|
35
35
|
### Available On
|
|
36
36
|
|
|
@@ -105,21 +105,33 @@ A browser window opens to the official DSers login page. You log in on DSers's o
|
|
|
105
105
|
|
|
106
106
|
That's it. No passwords in config files.
|
|
107
107
|
|
|
108
|
+
**Remote server (no install needed):**
|
|
109
|
+
|
|
110
|
+
If you don't want to install anything locally, you can connect directly to the hosted MCP server at `https://ai.silentrillmcp.com/dropshipping/mcp`. This works with any MCP client that supports Streamable HTTP transport. You'll be prompted to authorize with your DSers account on first connect.
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"mcpServers": {
|
|
115
|
+
"dropshipping": {
|
|
116
|
+
"url": "https://ai.silentrillmcp.com/dropshipping/mcp"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
108
122
|
Also listed on the official [MCP Registry](https://registry.modelcontextprotocol.io/servers/io.github.lofder/dsers-mcp-product).
|
|
109
123
|
|
|
110
|
-
### Authentication —
|
|
124
|
+
### Authentication — OAuth 2.1
|
|
111
125
|
|
|
112
126
|
Your DSers password **never touches this tool**. Here's how login works:
|
|
113
127
|
|
|
114
128
|
1. You run `npx @lofder/dsers-mcp-product login`
|
|
115
|
-
2. Your browser opens the
|
|
116
|
-
3. You
|
|
117
|
-
4. The tool
|
|
118
|
-
5. Done —
|
|
119
|
-
|
|
120
|
-
Works with Chrome, Edge, Brave, and other Chromium browsers. On Mac, Safari works too.
|
|
129
|
+
2. Your browser opens the DSers OAuth authorization page
|
|
130
|
+
3. You authorize access on DSers's own website
|
|
131
|
+
4. The tool receives an OAuth token and encrypts it locally
|
|
132
|
+
5. Done — the MCP server uses the token automatically. No passwords in config files.
|
|
121
133
|
|
|
122
|
-
**
|
|
134
|
+
**Tokens refresh automatically** — a long-lived refresh token keeps your session active without manual re-login. You should rarely need to run `login` again.
|
|
123
135
|
|
|
124
136
|
**Switching accounts?**
|
|
125
137
|
|
|
@@ -128,7 +140,7 @@ npx @lofder/dsers-mcp-product logout
|
|
|
128
140
|
npx @lofder/dsers-mcp-product login
|
|
129
141
|
```
|
|
130
142
|
|
|
131
|
-
> **For developers:** The server also accepts
|
|
143
|
+
> **For developers:** The server also accepts `DSERS_ACCESS_TOKEN` and `DSERS_REFRESH_TOKEN` env vars for headless/CI environments.
|
|
132
144
|
|
|
133
145
|
### Usage Examples
|
|
134
146
|
|
|
@@ -329,7 +341,7 @@ MIT
|
|
|
329
341
|
- **安全校验** — 推送前自动拦截低于成本价、零售价为零、库存为零的商品
|
|
330
342
|
- **SEO 优化** — 让 AI 重写标题和描述,提高搜索排名后再推送
|
|
331
343
|
|
|
332
|
-
服务已托管在 [Vercel](https://
|
|
344
|
+
服务已托管在 [Vercel](https://ai.silentrillmcp.com/dropshipping/mcp),并发布到多个平台:
|
|
333
345
|
|
|
334
346
|
### 发布平台
|
|
335
347
|
|
|
@@ -404,21 +416,33 @@ npx @lofder/dsers-mcp-product login
|
|
|
404
416
|
|
|
405
417
|
搞定。配置文件里不需要任何密码。
|
|
406
418
|
|
|
419
|
+
**在线版(免安装):**
|
|
420
|
+
|
|
421
|
+
如果不想在本地安装,可以直接连接托管的 MCP 服务端 `https://ai.silentrillmcp.com/dropshipping/mcp`。支持 Streamable HTTP transport 的 MCP 客户端都能用,首次连接会引导你授权 DSers 账号。
|
|
422
|
+
|
|
423
|
+
```json
|
|
424
|
+
{
|
|
425
|
+
"mcpServers": {
|
|
426
|
+
"dropshipping": {
|
|
427
|
+
"url": "https://ai.silentrillmcp.com/dropshipping/mcp"
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
407
433
|
同时已收录到官方 [MCP Registry](https://registry.modelcontextprotocol.io/servers/io.github.lofder/dsers-mcp-product)。
|
|
408
434
|
|
|
409
|
-
### 授权认证 —
|
|
435
|
+
### 授权认证 — OAuth 2.1
|
|
410
436
|
|
|
411
437
|
你的 DSers 密码**完全不经过本工具**。登录过程是这样的:
|
|
412
438
|
|
|
413
439
|
1. 运行 `npx @lofder/dsers-mcp-product login`
|
|
414
|
-
2. 浏览器自动打开 DSers
|
|
415
|
-
3. 你在 DSers
|
|
416
|
-
4.
|
|
440
|
+
2. 浏览器自动打开 DSers OAuth 授权页
|
|
441
|
+
3. 你在 DSers 网站上授权
|
|
442
|
+
4. 工具拿到 OAuth token,加密存到本地
|
|
417
443
|
5. 搞定 — 之后 MCP 直接能用,配置文件里不需要写任何密码
|
|
418
444
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
**登录大约 6 小时有效。** 过期了 AI 助手会提醒你重新跑一下 `login`,10 秒的事。
|
|
445
|
+
**Token 自动续期** — refresh token 会自动保持登录状态,你基本不需要重新登录。
|
|
422
446
|
|
|
423
447
|
**换账号?**
|
|
424
448
|
|
|
@@ -427,7 +451,7 @@ npx @lofder/dsers-mcp-product logout
|
|
|
427
451
|
npx @lofder/dsers-mcp-product login
|
|
428
452
|
```
|
|
429
453
|
|
|
430
|
-
> **开发者注:** headless / CI
|
|
454
|
+
> **开发者注:** headless / CI 环境支持通过 `DSERS_ACCESS_TOKEN` 和 `DSERS_REFRESH_TOKEN` 环境变量传入凭据。
|
|
431
455
|
|
|
432
456
|
### 使用示例
|
|
433
457
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export declare function
|
|
1
|
+
/**
|
|
2
|
+
* Open a URL in the system's default browser.
|
|
3
|
+
* Uses spawn() with explicit arguments to avoid command injection.
|
|
4
|
+
*/
|
|
5
|
+
export declare function openBrowser(url: string): void;
|
|
6
6
|
//# sourceMappingURL=browser-finder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-finder.d.ts","sourceRoot":"","sources":["../../src/auth/browser-finder.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"browser-finder.d.ts","sourceRoot":"","sources":["../../src/auth/browser-finder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAW7C"}
|
|
@@ -1,104 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
];
|
|
13
|
-
const LINUX_COMMANDS = [
|
|
14
|
-
["Google Chrome", "google-chrome-stable"],
|
|
15
|
-
["Google Chrome", "google-chrome"],
|
|
16
|
-
["Chromium", "chromium-browser"],
|
|
17
|
-
["Chromium", "chromium"],
|
|
18
|
-
["Microsoft Edge", "microsoft-edge-stable"],
|
|
19
|
-
["Microsoft Edge", "microsoft-edge"],
|
|
20
|
-
["Brave Browser", "brave-browser"],
|
|
21
|
-
["Brave Browser", "brave-browser-stable"],
|
|
22
|
-
["Opera", "opera"],
|
|
23
|
-
["Vivaldi", "vivaldi-stable"],
|
|
24
|
-
["Vivaldi", "vivaldi"],
|
|
25
|
-
];
|
|
26
|
-
const WIN_PATHS = [
|
|
27
|
-
["Google Chrome", "Google\\Chrome\\Application\\chrome.exe"],
|
|
28
|
-
["Microsoft Edge", "Microsoft\\Edge\\Application\\msedge.exe"],
|
|
29
|
-
["Brave Browser", "BraveSoftware\\Brave-Browser\\Application\\brave.exe"],
|
|
30
|
-
["Opera", "Opera\\opera.exe"],
|
|
31
|
-
["Vivaldi", "Vivaldi\\Application\\vivaldi.exe"],
|
|
32
|
-
["Chromium", "Chromium\\Application\\chrome.exe"],
|
|
33
|
-
];
|
|
34
|
-
function whichSync(cmd) {
|
|
35
|
-
if (!/^[a-z0-9._-]+$/i.test(cmd))
|
|
36
|
-
return null;
|
|
37
|
-
try {
|
|
38
|
-
return execSync(`which ${cmd} 2>/dev/null`, { encoding: "utf-8", timeout: 3000 }).trim() || null;
|
|
1
|
+
/**
|
|
2
|
+
* Open a URL in the system's default browser.
|
|
3
|
+
* Uses spawn() with explicit arguments to avoid command injection.
|
|
4
|
+
*/
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
export function openBrowser(url) {
|
|
7
|
+
const cmd = process.platform === "darwin" ? "open"
|
|
8
|
+
: process.platform === "win32" ? "cmd"
|
|
9
|
+
: "xdg-open";
|
|
10
|
+
if (process.platform === "win32") {
|
|
11
|
+
spawnSync(cmd, ["/c", "start", "", url], { stdio: "ignore" });
|
|
39
12
|
}
|
|
40
|
-
|
|
41
|
-
|
|
13
|
+
else {
|
|
14
|
+
spawnSync(cmd, [url], { stdio: "ignore" });
|
|
42
15
|
}
|
|
43
16
|
}
|
|
44
|
-
function findOnMacOS() {
|
|
45
|
-
for (const [name, p] of MACOS_BROWSERS) {
|
|
46
|
-
if (existsSync(p))
|
|
47
|
-
return { name, path: p };
|
|
48
|
-
}
|
|
49
|
-
// mdfind fallback for non-standard install locations
|
|
50
|
-
try {
|
|
51
|
-
const result = execSync('mdfind "kMDItemCFBundleIdentifier == com.google.Chrome" 2>/dev/null', {
|
|
52
|
-
encoding: "utf-8",
|
|
53
|
-
timeout: 3000,
|
|
54
|
-
}).trim();
|
|
55
|
-
if (result) {
|
|
56
|
-
const appPath = result.split("\n")[0];
|
|
57
|
-
const execPath = join(appPath, "Contents/MacOS/Google Chrome");
|
|
58
|
-
if (existsSync(execPath))
|
|
59
|
-
return { name: "Google Chrome", path: execPath };
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
/* mdfind unavailable or timed out */
|
|
64
|
-
}
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
function findOnLinux() {
|
|
68
|
-
for (const [name, cmd] of LINUX_COMMANDS) {
|
|
69
|
-
const p = whichSync(cmd);
|
|
70
|
-
if (p)
|
|
71
|
-
return { name, path: p };
|
|
72
|
-
}
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
function findOnWindows() {
|
|
76
|
-
const roots = [
|
|
77
|
-
process.env.PROGRAMFILES ?? "C:\\Program Files",
|
|
78
|
-
process.env["PROGRAMFILES(X86)"] ?? "C:\\Program Files (x86)",
|
|
79
|
-
process.env.LOCALAPPDATA ?? "",
|
|
80
|
-
].filter(Boolean);
|
|
81
|
-
for (const [name, rel] of WIN_PATHS) {
|
|
82
|
-
for (const root of roots) {
|
|
83
|
-
const full = join(root, rel);
|
|
84
|
-
if (existsSync(full))
|
|
85
|
-
return { name, path: full };
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
// Edge is pre-installed on Windows 10+ in a special location
|
|
89
|
-
const edgeSys = "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe";
|
|
90
|
-
if (existsSync(edgeSys))
|
|
91
|
-
return { name: "Microsoft Edge", path: edgeSys };
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
export function findChromiumBrowser() {
|
|
95
|
-
const platform = process.platform;
|
|
96
|
-
if (platform === "darwin")
|
|
97
|
-
return findOnMacOS();
|
|
98
|
-
if (platform === "linux")
|
|
99
|
-
return findOnLinux();
|
|
100
|
-
if (platform === "win32")
|
|
101
|
-
return findOnWindows();
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
17
|
//# sourceMappingURL=browser-finder.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-finder.js","sourceRoot":"","sources":["../../src/auth/browser-finder.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"browser-finder.js","sourceRoot":"","sources":["../../src/auth/browser-finder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,GAAG,GACP,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM;QACtC,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK;YACtC,CAAC,CAAC,UAAU,CAAC;IAEf,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAChE,CAAC;SAAM,CAAC;QACN,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC"}
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export { loginViaTerminal, type TerminalLoginResult } from "./terminal-prompt.js";
|
|
5
|
-
export { saveToken, loadToken, clearToken, tokenFilePath, type StoredSession } from "./token-store.js";
|
|
1
|
+
export { openBrowser } from "./browser-finder.js";
|
|
2
|
+
export { authorizeWithPKCE, refreshAccessToken } from "./oauth.js";
|
|
3
|
+
export { saveToken, loadToken, clearToken, tokenFilePath, hasLegacyCredentials, isLegacySession, type StoredSession, } from "./token-store.js";
|
|
6
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/auth/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACnE,OAAO,EACL,SAAS,EACT,SAAS,EACT,UAAU,EACV,aAAa,EACb,oBAAoB,EACpB,eAAe,EACf,KAAK,aAAa,GACnB,MAAM,kBAAkB,CAAC"}
|
package/dist/auth/index.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export { loginViaTerminal } from "./terminal-prompt.js";
|
|
5
|
-
export { saveToken, loadToken, clearToken, tokenFilePath } from "./token-store.js";
|
|
1
|
+
export { openBrowser } from "./browser-finder.js";
|
|
2
|
+
export { authorizeWithPKCE, refreshAccessToken } from "./oauth.js";
|
|
3
|
+
export { saveToken, loadToken, clearToken, tokenFilePath, hasLegacyCredentials, isLegacySession, } from "./token-store.js";
|
|
6
4
|
//# sourceMappingURL=index.js.map
|
package/dist/auth/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACnE,OAAO,EACL,SAAS,EACT,SAAS,EACT,UAAU,EACV,aAAa,EACb,oBAAoB,EACpB,eAAe,GAEhB,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.1 PKCE authorization flow for DSers MCP.
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* 1. Fetch AS metadata from /.well-known/oauth-authorization-server
|
|
6
|
+
* 2. Register client dynamically (or reuse saved client_id)
|
|
7
|
+
* 3. Open browser to authorization_endpoint with PKCE challenge
|
|
8
|
+
* 4. Listen on localhost for callback with authorization code
|
|
9
|
+
* 5. Exchange code for access_token + refresh_token
|
|
10
|
+
*
|
|
11
|
+
* Reference: client-simple/src/index.ts (verified working with DSers OAuth AS)
|
|
12
|
+
*/
|
|
13
|
+
export interface OAuthTokens {
|
|
14
|
+
access_token: string;
|
|
15
|
+
refresh_token: string;
|
|
16
|
+
expires_at: number;
|
|
17
|
+
client_id: string;
|
|
18
|
+
oauth_base: string;
|
|
19
|
+
base_url: string;
|
|
20
|
+
ts: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Full OAuth 2.1 PKCE authorization flow.
|
|
24
|
+
* Opens browser, waits for callback, exchanges code for tokens.
|
|
25
|
+
*/
|
|
26
|
+
export declare function authorizeWithPKCE(log: (msg: string) => void, savedClientId?: string, oauthBase?: string): Promise<OAuthTokens>;
|
|
27
|
+
/**
|
|
28
|
+
* Refresh an expired access_token using the refresh_token.
|
|
29
|
+
* Returns updated tokens or throws on failure.
|
|
30
|
+
*/
|
|
31
|
+
export declare function refreshAccessToken(refreshToken: string, clientId: string, oauthBase?: string): Promise<{
|
|
32
|
+
access_token: string;
|
|
33
|
+
refresh_token: string;
|
|
34
|
+
expires_at: number;
|
|
35
|
+
}>;
|
|
36
|
+
//# sourceMappingURL=oauth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAeH,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;CACZ;AA+GD;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAC1B,aAAa,CAAC,EAAE,MAAM,EACtB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,WAAW,CAAC,CAwCtB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAsB9E"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.1 PKCE authorization flow for DSers MCP.
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* 1. Fetch AS metadata from /.well-known/oauth-authorization-server
|
|
6
|
+
* 2. Register client dynamically (or reuse saved client_id)
|
|
7
|
+
* 3. Open browser to authorization_endpoint with PKCE challenge
|
|
8
|
+
* 4. Listen on localhost for callback with authorization code
|
|
9
|
+
* 5. Exchange code for access_token + refresh_token
|
|
10
|
+
*
|
|
11
|
+
* Reference: client-simple/src/index.ts (verified working with DSers OAuth AS)
|
|
12
|
+
*/
|
|
13
|
+
import * as http from "node:http";
|
|
14
|
+
import * as crypto from "node:crypto";
|
|
15
|
+
import { spawnSync } from "node:child_process";
|
|
16
|
+
const DEFAULT_OAUTH_BASE = "https://mcp-oauth-api-gw.dsers.com";
|
|
17
|
+
const DEFAULT_BFF_BASE = "https://bff-api-gw.dsers.com";
|
|
18
|
+
const CB_PORT = 3001;
|
|
19
|
+
const REDIRECT_URI = `http://localhost:${CB_PORT}/callback`;
|
|
20
|
+
const OAUTH_SCOPES = "user:read store:read store:write staff:read supplier:read " +
|
|
21
|
+
"product:read product:import product:push " +
|
|
22
|
+
"settings:read billing:read plan:read order:read";
|
|
23
|
+
// ── PKCE helpers ──
|
|
24
|
+
function generateVerifier() {
|
|
25
|
+
return crypto.randomBytes(32).toString("base64url");
|
|
26
|
+
}
|
|
27
|
+
function generateChallenge(verifier) {
|
|
28
|
+
return crypto.createHash("sha256").update(verifier).digest("base64url");
|
|
29
|
+
}
|
|
30
|
+
// ── HTTP helpers ──
|
|
31
|
+
async function fetchJSON(url, init) {
|
|
32
|
+
const res = await fetch(url, init);
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
const body = await res.text().catch(() => "");
|
|
35
|
+
throw new Error(`${init?.method ?? "GET"} ${url} → ${res.status}: ${body.slice(0, 200)}`);
|
|
36
|
+
}
|
|
37
|
+
return res.json();
|
|
38
|
+
}
|
|
39
|
+
function openBrowser(url, log) {
|
|
40
|
+
const cmd = process.platform === "darwin" ? "open"
|
|
41
|
+
: process.platform === "win32" ? "cmd"
|
|
42
|
+
: "xdg-open";
|
|
43
|
+
try {
|
|
44
|
+
if (process.platform === "win32") {
|
|
45
|
+
spawnSync(cmd, ["/c", "start", "", url], { stdio: "ignore" });
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
spawnSync(cmd, [url], { stdio: "ignore" });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
log(`Please open this URL in your browser:\n${url}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// ── OAuth flow ──
|
|
56
|
+
async function getASMetadata(oauthBase) {
|
|
57
|
+
return fetchJSON(`${oauthBase}/.well-known/oauth-authorization-server`);
|
|
58
|
+
}
|
|
59
|
+
async function registerClient(registrationEndpoint, log) {
|
|
60
|
+
log("Registering OAuth client...");
|
|
61
|
+
const data = await fetchJSON(registrationEndpoint, {
|
|
62
|
+
method: "POST",
|
|
63
|
+
headers: { "Content-Type": "application/json" },
|
|
64
|
+
body: JSON.stringify({
|
|
65
|
+
client_name: "DSers MCP Product",
|
|
66
|
+
redirect_uris: [REDIRECT_URI],
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
return data.client_id;
|
|
70
|
+
}
|
|
71
|
+
function waitForCallback(log) {
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const server = http.createServer((req, res) => {
|
|
74
|
+
const url = new URL(req.url, `http://localhost:${CB_PORT}`);
|
|
75
|
+
const code = url.searchParams.get("code");
|
|
76
|
+
const error = url.searchParams.get("error");
|
|
77
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
78
|
+
res.end("<html><body><h2>Authorization successful. You can close this tab.</h2></body></html>");
|
|
79
|
+
server.close();
|
|
80
|
+
if (error)
|
|
81
|
+
reject(new Error(`OAuth error: ${error}`));
|
|
82
|
+
else if (code)
|
|
83
|
+
resolve(code);
|
|
84
|
+
else
|
|
85
|
+
reject(new Error("Callback missing authorization code"));
|
|
86
|
+
});
|
|
87
|
+
server.listen(CB_PORT, () => log(`Waiting for OAuth callback on port ${CB_PORT}...`));
|
|
88
|
+
server.on("error", (err) => {
|
|
89
|
+
if (err.code === "EADDRINUSE") {
|
|
90
|
+
reject(new Error(`Port ${CB_PORT} is in use. Close the other process and try again.`));
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
reject(err);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async function exchangeCode(tokenEndpoint, clientId, code, verifier, oauthBase) {
|
|
99
|
+
return fetchJSON(tokenEndpoint, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
102
|
+
body: new URLSearchParams({
|
|
103
|
+
grant_type: "authorization_code",
|
|
104
|
+
code,
|
|
105
|
+
code_verifier: verifier,
|
|
106
|
+
client_id: clientId,
|
|
107
|
+
redirect_uri: REDIRECT_URI,
|
|
108
|
+
resource: oauthBase,
|
|
109
|
+
}).toString(),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Full OAuth 2.1 PKCE authorization flow.
|
|
114
|
+
* Opens browser, waits for callback, exchanges code for tokens.
|
|
115
|
+
*/
|
|
116
|
+
export async function authorizeWithPKCE(log, savedClientId, oauthBase) {
|
|
117
|
+
const base = oauthBase || process.env.DSERS_OAUTH_BASE || DEFAULT_OAUTH_BASE;
|
|
118
|
+
const meta = await getASMetadata(base);
|
|
119
|
+
// Reuse saved client_id or register new one
|
|
120
|
+
let clientId = savedClientId;
|
|
121
|
+
if (!clientId) {
|
|
122
|
+
clientId = await registerClient(meta.registration_endpoint, log);
|
|
123
|
+
log(`Client registered: ${clientId}`);
|
|
124
|
+
}
|
|
125
|
+
const verifier = generateVerifier();
|
|
126
|
+
const challenge = generateChallenge(verifier);
|
|
127
|
+
const state = crypto.randomBytes(16).toString("hex");
|
|
128
|
+
const authURL = new URL(meta.authorization_endpoint);
|
|
129
|
+
authURL.searchParams.set("response_type", "code");
|
|
130
|
+
authURL.searchParams.set("client_id", clientId);
|
|
131
|
+
authURL.searchParams.set("redirect_uri", REDIRECT_URI);
|
|
132
|
+
authURL.searchParams.set("scope", OAUTH_SCOPES);
|
|
133
|
+
authURL.searchParams.set("code_challenge", challenge);
|
|
134
|
+
authURL.searchParams.set("code_challenge_method", "S256");
|
|
135
|
+
authURL.searchParams.set("state", state);
|
|
136
|
+
authURL.searchParams.set("resource", base);
|
|
137
|
+
log("Opening browser for DSers authorization...");
|
|
138
|
+
openBrowser(authURL.toString(), log);
|
|
139
|
+
const code = await waitForCallback(log);
|
|
140
|
+
const tokenData = await exchangeCode(meta.token_endpoint, clientId, code, verifier, base);
|
|
141
|
+
return {
|
|
142
|
+
access_token: tokenData.access_token,
|
|
143
|
+
refresh_token: tokenData.refresh_token,
|
|
144
|
+
expires_at: Date.now() + tokenData.expires_in * 1000 - 60_000,
|
|
145
|
+
client_id: clientId,
|
|
146
|
+
oauth_base: base,
|
|
147
|
+
base_url: process.env.DSERS_BASE_URL || DEFAULT_BFF_BASE,
|
|
148
|
+
ts: Date.now(),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Refresh an expired access_token using the refresh_token.
|
|
153
|
+
* Returns updated tokens or throws on failure.
|
|
154
|
+
*/
|
|
155
|
+
export async function refreshAccessToken(refreshToken, clientId, oauthBase) {
|
|
156
|
+
const base = oauthBase || DEFAULT_OAUTH_BASE;
|
|
157
|
+
const meta = await getASMetadata(base);
|
|
158
|
+
const data = await fetchJSON(meta.token_endpoint, {
|
|
159
|
+
method: "POST",
|
|
160
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
161
|
+
body: new URLSearchParams({
|
|
162
|
+
grant_type: "refresh_token",
|
|
163
|
+
refresh_token: refreshToken,
|
|
164
|
+
client_id: clientId,
|
|
165
|
+
}).toString(),
|
|
166
|
+
});
|
|
167
|
+
return {
|
|
168
|
+
access_token: data.access_token,
|
|
169
|
+
refresh_token: data.refresh_token,
|
|
170
|
+
expires_at: Date.now() + data.expires_in * 1000 - 60_000,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
+
/** OAuth token storage format (shared with DSClaw) */
|
|
1
2
|
export interface StoredSession {
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
access_token: string;
|
|
4
|
+
refresh_token: string;
|
|
5
|
+
expires_at: number;
|
|
6
|
+
client_id: string;
|
|
7
|
+
oauth_base: string;
|
|
4
8
|
base_url: string;
|
|
5
9
|
ts: number;
|
|
6
10
|
}
|
|
11
|
+
/** Check if a parsed credential is the old CDP format */
|
|
12
|
+
export declare function isLegacySession(obj: Record<string, unknown>): boolean;
|
|
7
13
|
export declare function saveToken(session: StoredSession): void;
|
|
8
14
|
export declare function loadToken(): StoredSession | null;
|
|
9
15
|
export declare function clearToken(): boolean;
|
|
10
16
|
export declare function tokenFilePath(): string;
|
|
17
|
+
/** Check if a legacy CDP credential file exists (for migration messaging) */
|
|
18
|
+
export declare function hasLegacyCredentials(): boolean;
|
|
11
19
|
//# sourceMappingURL=token-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../src/auth/token-store.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,
|
|
1
|
+
{"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../src/auth/token-store.ts"],"names":[],"mappings":"AAWA,sDAAsD;AACtD,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;CACZ;AAsDD,yDAAyD;AACzD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAErE;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAYtD;AAED,wBAAgB,SAAS,IAAI,aAAa,GAAG,IAAI,CAkBhD;AAED,wBAAgB,UAAU,IAAI,OAAO,CASpC;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,IAAI,OAAO,CAY9C"}
|
package/dist/auth/token-store.js
CHANGED
|
@@ -49,6 +49,10 @@ function decrypt(encoded) {
|
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
/** Check if a parsed credential is the old CDP format */
|
|
53
|
+
export function isLegacySession(obj) {
|
|
54
|
+
return typeof obj.session_id === "string" && !obj.access_token;
|
|
55
|
+
}
|
|
52
56
|
export function saveToken(session) {
|
|
53
57
|
const dir = getCredDir();
|
|
54
58
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
@@ -72,8 +76,12 @@ export function loadToken() {
|
|
|
72
76
|
const json = decrypt(encrypted);
|
|
73
77
|
if (!json)
|
|
74
78
|
return null;
|
|
75
|
-
const
|
|
76
|
-
|
|
79
|
+
const parsed = JSON.parse(json);
|
|
80
|
+
// Detect legacy CDP format → caller should prompt re-login
|
|
81
|
+
if (isLegacySession(parsed))
|
|
82
|
+
return null;
|
|
83
|
+
const session = parsed;
|
|
84
|
+
if (!session.access_token)
|
|
77
85
|
return null;
|
|
78
86
|
return session;
|
|
79
87
|
}
|
|
@@ -96,4 +104,21 @@ export function clearToken() {
|
|
|
96
104
|
export function tokenFilePath() {
|
|
97
105
|
return getCredFile();
|
|
98
106
|
}
|
|
107
|
+
/** Check if a legacy CDP credential file exists (for migration messaging) */
|
|
108
|
+
export function hasLegacyCredentials() {
|
|
109
|
+
const file = getCredFile();
|
|
110
|
+
if (!existsSync(file))
|
|
111
|
+
return false;
|
|
112
|
+
try {
|
|
113
|
+
const encrypted = readFileSync(file, "utf-8").trim();
|
|
114
|
+
const json = decrypt(encrypted);
|
|
115
|
+
if (!json)
|
|
116
|
+
return false;
|
|
117
|
+
const parsed = JSON.parse(json);
|
|
118
|
+
return isLegacySession(parsed);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
99
124
|
//# sourceMappingURL=token-store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../src/auth/token-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACxF,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEtD,MAAM,GAAG,GAAG,aAAa,CAAC;AAC1B,MAAM,MAAM,GAAG,EAAE,CAAC;AAClB,MAAM,OAAO,GAAG,EAAE,CAAC;AACnB,MAAM,YAAY,GAAG,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../src/auth/token-store.ts"],"names":[],"mappings":"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"}
|