smilenexus-mcp 1.0.4 → 1.0.5
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 +13 -0
- package/cli.mjs +340 -249
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -6,10 +6,23 @@ MCP (Model Context Protocol) connector for smileNexus.
|
|
|
6
6
|
|
|
7
7
|
This package is intended to be run via `npx` and handles authentication and tunneling for the smileNexus MCP server.
|
|
8
8
|
|
|
9
|
+
**Install & login (dev):**
|
|
10
|
+
```bash
|
|
11
|
+
npx smilenexus-mcp install
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**Install with a custom URL (e.g. production):**
|
|
15
|
+
```bash
|
|
16
|
+
npx smilenexus-mcp install https://smile-nexus.kantapon-r.workers.dev/mcp/sse
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Login only (no config update):**
|
|
9
20
|
```bash
|
|
10
21
|
npx -y smilenexus-mcp login
|
|
11
22
|
```
|
|
12
23
|
|
|
24
|
+
> **Note:** The default install URL points to the dev environment (`smile-nexus-dev`). To use production, pass the URL explicitly and ensure `https://smile-nexus.kantapon-r.workers.dev/auth/callback` is registered in the Azure app registration.
|
|
25
|
+
|
|
13
26
|
## Development & Publishing
|
|
14
27
|
|
|
15
28
|
To publish a new version of this package to npm:
|
package/cli.mjs
CHANGED
|
@@ -1,40 +1,49 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import fs from
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import path from "path";
|
|
5
5
|
|
|
6
6
|
// ── Credentials helpers ────────────────────────────────────────────────────────
|
|
7
7
|
|
|
8
|
-
const CREDS_PATH = path.join(
|
|
8
|
+
const CREDS_PATH = path.join(
|
|
9
|
+
os.homedir(),
|
|
10
|
+
".config",
|
|
11
|
+
"smilenexus",
|
|
12
|
+
"credentials.json",
|
|
13
|
+
);
|
|
9
14
|
|
|
10
15
|
function readCredentials() {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
try {
|
|
17
|
+
if (!fs.existsSync(CREDS_PATH)) return null;
|
|
18
|
+
return JSON.parse(fs.readFileSync(CREDS_PATH, "utf8"));
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
function writeCredentials(data) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
const dir = path.dirname(CREDS_PATH);
|
|
26
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
27
|
+
fs.writeFileSync(CREDS_PATH, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
// ── Config ────────────────────────────────────────────────────────────────────
|
|
26
31
|
|
|
27
|
-
const MCP_URL =
|
|
32
|
+
const MCP_URL =
|
|
33
|
+
process.env.SMILENEXUS_URL ??
|
|
34
|
+
"https://smile-nexus-dev.kantapon-r.workers.dev/mcp/sse";
|
|
28
35
|
|
|
29
36
|
// mutable — updated after successful login tool call during active MCP session
|
|
30
|
-
let currentApiKey =
|
|
37
|
+
let currentApiKey =
|
|
38
|
+
process.env.SMILENEXUS_API_KEY || readCredentials()?.api_key || null;
|
|
31
39
|
|
|
32
40
|
// ── Login tool definition (injected client-side into tools/list) ──────────────
|
|
33
41
|
|
|
34
42
|
const LOGIN_TOOL_DEF = {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
name: "login",
|
|
44
|
+
description:
|
|
45
|
+
"เชื่อมต่อ smileNexus ด้วย Microsoft account — รัน tool นี้ก่อนใช้งาน tool อื่น หรือเมื่อต้องการ rotate key",
|
|
46
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
38
47
|
};
|
|
39
48
|
|
|
40
49
|
// ── Command dispatch ──────────────────────────────────────────────────────────
|
|
@@ -42,254 +51,336 @@ const LOGIN_TOOL_DEF = {
|
|
|
42
51
|
const args = process.argv.slice(2);
|
|
43
52
|
const command = args[0];
|
|
44
53
|
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
54
|
+
if (
|
|
55
|
+
command === "install" ||
|
|
56
|
+
command === "install-codex" ||
|
|
57
|
+
command === "install-agy"
|
|
58
|
+
) {
|
|
59
|
+
const customUrl = args[1];
|
|
60
|
+
process.stderr.write(
|
|
61
|
+
`\n========================================\n smileNexus MCP Auto-Installer\n========================================\n\n`,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (command === "install") {
|
|
65
|
+
const defaultUrl =
|
|
66
|
+
customUrl || "https://smile-nexus-dev.kantapon-r.workers.dev/mcp/sse";
|
|
67
|
+
let configPath;
|
|
68
|
+
if (process.platform === "darwin") {
|
|
69
|
+
configPath = path.join(
|
|
70
|
+
os.homedir(),
|
|
71
|
+
"Library",
|
|
72
|
+
"Application Support",
|
|
73
|
+
"Claude",
|
|
74
|
+
"claude_desktop_config.json",
|
|
75
|
+
);
|
|
76
|
+
} else if (process.platform === "win32") {
|
|
77
|
+
configPath = path.join(
|
|
78
|
+
os.homedir(),
|
|
79
|
+
"AppData",
|
|
80
|
+
"Roaming",
|
|
81
|
+
"Claude",
|
|
82
|
+
"claude_desktop_config.json",
|
|
83
|
+
);
|
|
84
|
+
} else {
|
|
85
|
+
process.stderr.write(
|
|
86
|
+
"Unsupported OS for auto-install. Please configure manually.\n",
|
|
87
|
+
);
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let installOk = false;
|
|
92
|
+
try {
|
|
93
|
+
let config = { mcpServers: {} };
|
|
94
|
+
if (fs.existsSync(configPath))
|
|
95
|
+
config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
96
|
+
config.mcpServers = config.mcpServers || {};
|
|
97
|
+
config.mcpServers.smileNexus = {
|
|
98
|
+
command: "npx",
|
|
99
|
+
args: ["-y", "smilenexus-mcp"],
|
|
100
|
+
env: { SMILENEXUS_URL: defaultUrl },
|
|
101
|
+
};
|
|
102
|
+
const dir = path.dirname(configPath);
|
|
103
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
104
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
105
|
+
process.stderr.write(
|
|
106
|
+
`✅ Successfully added smileNexus to Claude Desktop!\nConfig updated at: ${configPath}\n`,
|
|
107
|
+
);
|
|
108
|
+
installOk = true;
|
|
109
|
+
} catch (err) {
|
|
110
|
+
process.stderr.write(
|
|
111
|
+
`❌ Failed to update Claude config: ${err.message}\n`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Option B: auto-login immediately after install (mandatory — login is required to use smileNexus)
|
|
116
|
+
if (installOk) {
|
|
117
|
+
process.stderr.write(`\n🔐 กำลัง login เพื่อเปิดใช้งาน...\n`);
|
|
118
|
+
try {
|
|
119
|
+
await runLogin(new URL(defaultUrl).origin);
|
|
120
|
+
process.stderr.write(
|
|
121
|
+
`\nRestart Claude Desktop แล้วใช้งาน smileNexus ได้เลย!\n`,
|
|
122
|
+
);
|
|
123
|
+
} catch {
|
|
124
|
+
process.stderr.write(
|
|
125
|
+
`\nLogin ล้มเหลว กรุณารัน \`npx smilenexus-mcp login\` ก่อนใช้งาน\n`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
process.stderr.write(
|
|
131
|
+
`Auto-install for ${command} is coming soon!\nPlease configure manually.\n`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
process.exit(0);
|
|
94
135
|
}
|
|
95
136
|
|
|
96
|
-
if (command ===
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
137
|
+
if (command === "login") {
|
|
138
|
+
try {
|
|
139
|
+
await runLogin();
|
|
140
|
+
process.exit(0);
|
|
141
|
+
} catch {
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
103
144
|
}
|
|
104
145
|
|
|
105
146
|
// ── Login flow ────────────────────────────────────────────────────────────────
|
|
106
147
|
// Throws on failure instead of calling process.exit — callers decide how to handle.
|
|
107
148
|
|
|
108
149
|
async function runLogin(baseUrl) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
150
|
+
if (!baseUrl) baseUrl = new URL(MCP_URL).origin;
|
|
151
|
+
|
|
152
|
+
// 1. Create session
|
|
153
|
+
let sessionData;
|
|
154
|
+
try {
|
|
155
|
+
const res = await fetch(`${baseUrl}/auth/mcp/session`, { method: "POST" });
|
|
156
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
157
|
+
sessionData = await res.json();
|
|
158
|
+
} catch (err) {
|
|
159
|
+
process.stderr.write(`❌ ไม่สามารถสร้าง login session ได้: ${err.message}\n`);
|
|
160
|
+
throw err;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const { session_code, login_url } = sessionData;
|
|
164
|
+
|
|
165
|
+
// 2. Open browser
|
|
166
|
+
process.stderr.write(`\n🔐 เปิด URL นี้ใน browser:\n\n ${login_url}\n\n`);
|
|
167
|
+
try {
|
|
168
|
+
const { spawn } = await import("child_process");
|
|
169
|
+
let proc;
|
|
170
|
+
if (process.platform === "win32") {
|
|
171
|
+
proc = spawn("cmd", ["/c", "start", "", login_url], {
|
|
172
|
+
detached: true,
|
|
173
|
+
stdio: "ignore",
|
|
174
|
+
});
|
|
175
|
+
} else {
|
|
176
|
+
const opener = process.platform === "darwin" ? "open" : "xdg-open";
|
|
177
|
+
proc = spawn(opener, [login_url], { detached: true, stdio: "ignore" });
|
|
178
|
+
}
|
|
179
|
+
proc.on("error", () => {
|
|
180
|
+
process.stderr.write(
|
|
181
|
+
`⚠️ ไม่สามารถเปิด browser อัตโนมัติ — กรุณาเปิด URL ด้านบนด้วยตนเอง\n`,
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
proc.unref();
|
|
185
|
+
} catch {}
|
|
186
|
+
|
|
187
|
+
// 3. Poll every 2s, max 10 minutes
|
|
188
|
+
const MAX_POLLS = 300;
|
|
189
|
+
const spinner = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
190
|
+
let spinIdx = 0;
|
|
191
|
+
|
|
192
|
+
for (let i = 0; i < MAX_POLLS; i++) {
|
|
193
|
+
process.stderr.write(
|
|
194
|
+
`\r${spinner[spinIdx++ % spinner.length]} รอการ login... `,
|
|
195
|
+
);
|
|
196
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
197
|
+
|
|
198
|
+
let pollData;
|
|
199
|
+
let retries = 0;
|
|
200
|
+
while (true) {
|
|
201
|
+
try {
|
|
202
|
+
const res = await fetch(
|
|
203
|
+
`${baseUrl}/auth/mcp/poll?code=${session_code}`,
|
|
204
|
+
);
|
|
205
|
+
pollData = await res.json();
|
|
206
|
+
break;
|
|
207
|
+
} catch {
|
|
208
|
+
retries++;
|
|
209
|
+
if (retries >= 3) {
|
|
210
|
+
process.stderr.write("❌ Network error. กรุณาตรวจสอบการเชื่อมต่อ\n");
|
|
211
|
+
throw new Error("Network error after 3 retries");
|
|
212
|
+
}
|
|
213
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (pollData.status === "done") {
|
|
218
|
+
writeCredentials({
|
|
219
|
+
api_key: pollData.api_key,
|
|
220
|
+
created_at: new Date().toISOString(),
|
|
221
|
+
});
|
|
222
|
+
process.stderr.write(`\r\x1b[K✅ Login สำเร็จ! smileNexus พร้อมใช้งานแล้ว\n`);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (pollData.status === "expired") {
|
|
226
|
+
process.stderr.write("\r\x1b[K❌ Session หมดอายุ กรุณาลองใหม่\n");
|
|
227
|
+
throw new Error("Session expired");
|
|
228
|
+
}
|
|
229
|
+
// status === 'pending' — continue polling
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
process.stderr.write("❌ หมดเวลา กรุณาลอง `npx smilenexus-mcp login` ใหม่\n");
|
|
233
|
+
throw new Error("Login timeout");
|
|
174
234
|
}
|
|
175
235
|
|
|
176
236
|
// ── Proxy Logic (stdio <-> HTTP) ──────────────────────────────────────────────
|
|
177
237
|
|
|
178
|
-
let buf =
|
|
238
|
+
let buf = "";
|
|
179
239
|
let pending = 0;
|
|
180
240
|
let stdinDone = false;
|
|
181
|
-
process.stdin.setEncoding(
|
|
182
|
-
|
|
183
|
-
process.stdin.on(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
241
|
+
process.stdin.setEncoding("utf8");
|
|
242
|
+
|
|
243
|
+
process.stdin.on("data", (chunk) => {
|
|
244
|
+
buf += chunk;
|
|
245
|
+
while (buf.includes("\n")) {
|
|
246
|
+
const idx = buf.indexOf("\n");
|
|
247
|
+
const line = buf.slice(0, idx).trim();
|
|
248
|
+
buf = buf.slice(idx + 1);
|
|
249
|
+
if (line) handle(line);
|
|
250
|
+
}
|
|
191
251
|
});
|
|
192
252
|
|
|
193
|
-
process.stdin.on(
|
|
194
|
-
|
|
195
|
-
|
|
253
|
+
process.stdin.on("end", () => {
|
|
254
|
+
stdinDone = true;
|
|
255
|
+
if (pending === 0) process.exit(0);
|
|
196
256
|
});
|
|
197
257
|
|
|
198
258
|
async function handle(line) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
259
|
+
let msg;
|
|
260
|
+
try {
|
|
261
|
+
msg = JSON.parse(line);
|
|
262
|
+
} catch {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Option A: intercept tools/list — inject login tool
|
|
267
|
+
if (msg.method === "tools/list") {
|
|
268
|
+
if (!currentApiKey) {
|
|
269
|
+
// Not authenticated — return only the login tool so the AI knows what to call
|
|
270
|
+
process.stdout.write(
|
|
271
|
+
JSON.stringify({
|
|
272
|
+
jsonrpc: "2.0",
|
|
273
|
+
id: msg.id,
|
|
274
|
+
result: { tools: [LOGIN_TOOL_DEF] },
|
|
275
|
+
}) + "\n",
|
|
276
|
+
);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
// Authenticated — proxy and append login tool to the result
|
|
280
|
+
pending++;
|
|
281
|
+
try {
|
|
282
|
+
const res = await fetch(MCP_URL, {
|
|
283
|
+
method: "POST",
|
|
284
|
+
headers: {
|
|
285
|
+
"content-type": "application/json",
|
|
286
|
+
authorization: `Bearer ${currentApiKey}`,
|
|
287
|
+
},
|
|
288
|
+
body: JSON.stringify(msg),
|
|
289
|
+
});
|
|
290
|
+
if (res.status !== 202) {
|
|
291
|
+
const json = await res.json();
|
|
292
|
+
if (json.result?.tools) json.result.tools.push(LOGIN_TOOL_DEF);
|
|
293
|
+
process.stdout.write(JSON.stringify(json) + "\n");
|
|
294
|
+
}
|
|
295
|
+
} catch (err) {
|
|
296
|
+
process.stdout.write(
|
|
297
|
+
JSON.stringify({
|
|
298
|
+
jsonrpc: "2.0",
|
|
299
|
+
id: msg?.id ?? null,
|
|
300
|
+
error: { code: -32603, message: String(err) },
|
|
301
|
+
}) + "\n",
|
|
302
|
+
);
|
|
303
|
+
} finally {
|
|
304
|
+
pending--;
|
|
305
|
+
if (stdinDone && pending === 0) process.exit(0);
|
|
306
|
+
}
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Option A: intercept tools/call for the login tool — handle locally
|
|
311
|
+
if (msg.method === "tools/call" && msg.params?.name === "login") {
|
|
312
|
+
pending++;
|
|
313
|
+
try {
|
|
314
|
+
await runLogin();
|
|
315
|
+
currentApiKey = readCredentials()?.api_key ?? null;
|
|
316
|
+
process.stdout.write(
|
|
317
|
+
JSON.stringify({
|
|
318
|
+
jsonrpc: "2.0",
|
|
319
|
+
id: msg.id,
|
|
320
|
+
result: {
|
|
321
|
+
content: [
|
|
322
|
+
{
|
|
323
|
+
type: "text",
|
|
324
|
+
text: "✅ Login สำเร็จ! smileNexus พร้อมใช้งานแล้ว ลองใช้ tool อื่นได้เลย",
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
},
|
|
328
|
+
}) + "\n",
|
|
329
|
+
);
|
|
330
|
+
} catch (err) {
|
|
331
|
+
process.stdout.write(
|
|
332
|
+
JSON.stringify({
|
|
333
|
+
jsonrpc: "2.0",
|
|
334
|
+
id: msg.id,
|
|
335
|
+
result: {
|
|
336
|
+
content: [
|
|
337
|
+
{
|
|
338
|
+
type: "text",
|
|
339
|
+
text: `❌ Login ล้มเหลว: ${err.message} — กรุณาลองใหม่`,
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
isError: true,
|
|
343
|
+
},
|
|
344
|
+
}) + "\n",
|
|
345
|
+
);
|
|
346
|
+
} finally {
|
|
347
|
+
pending--;
|
|
348
|
+
if (stdinDone && pending === 0) process.exit(0);
|
|
349
|
+
}
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Normal proxy
|
|
354
|
+
pending++;
|
|
355
|
+
try {
|
|
356
|
+
const res = await fetch(MCP_URL, {
|
|
357
|
+
method: "POST",
|
|
358
|
+
headers: {
|
|
359
|
+
"content-type": "application/json",
|
|
360
|
+
...(currentApiKey ? { authorization: `Bearer ${currentApiKey}` } : {}),
|
|
361
|
+
},
|
|
362
|
+
body: JSON.stringify(msg),
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
if (res.status !== 202) {
|
|
366
|
+
const json = await res.json();
|
|
367
|
+
// Transform auth error into actionable hint for the AI
|
|
368
|
+
if (!currentApiKey && json.error?.code === -32600) {
|
|
369
|
+
json.error.message =
|
|
370
|
+
'smileNexus: ยังไม่ได้ authenticate — กรุณาเรียกใช้ tool "login" เพื่อเชื่อมต่อก่อน';
|
|
371
|
+
}
|
|
372
|
+
process.stdout.write(JSON.stringify(json) + "\n");
|
|
373
|
+
}
|
|
374
|
+
} catch (err) {
|
|
375
|
+
process.stdout.write(
|
|
376
|
+
JSON.stringify({
|
|
377
|
+
jsonrpc: "2.0",
|
|
378
|
+
id: msg?.id ?? null,
|
|
379
|
+
error: { code: -32603, message: String(err) },
|
|
380
|
+
}) + "\n",
|
|
381
|
+
);
|
|
382
|
+
} finally {
|
|
383
|
+
pending--;
|
|
384
|
+
if (stdinDone && pending === 0) process.exit(0);
|
|
385
|
+
}
|
|
295
386
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
"name": "smilenexus-mcp",
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"description": "MCP connector for smileNexus",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": "./cli.mjs",
|
|
7
|
+
"author": "Smile Fokus",
|
|
8
|
+
"license": "MIT"
|
|
9
9
|
}
|