2025-06-19 gateway.mjs

2025-06-19 gateway.mjs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// gateway.mjs
import express from 'express';
import cors from "cors";
import dotenv from 'dotenv';
dotenv.config();
const app = express();
app.use(cors());
app.use(express.json());

const OPENAI_KEY = process.env.OPENAI_API_KEY || '';
const DEEPSEEK_KEY = process.env.DEEPSEEK_API_KEY || '';

// provider adapters (最小示範:若沒 key 則回 mock)
const providers = {
openai: async ({ messages }) => {
if (!OPENAI_KEY) return { text: '[mock openai] 這是模擬回覆' };
const resp = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: { 'Authorization': `Bearer ${OPENAI_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ model: 'gpt-3.5-turbo', messages })
});
const data = await resp.json();
return { text: data?.choices?.[0]?.message?.content ?? '', raw: data };
},

deepseek: async ({ messages }) => {
if (!DEEPSEEK_KEY) return { text: '[mock deepseek] 這是模擬回覆' };
const resp = await fetch('https://api.deepseek.com/v1/chat/completions', {
method: 'POST',
headers: { 'Authorization': `Bearer ${DEEPSEEK_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ model: 'deepseek-v1', messages })
});
const data = await resp.json();
return { text: data?.choices?.[0]?.message?.content ?? '', raw: data };
}
};

app.get('/health', (req, res) => res.json({ status: 'ok', role: 'mcp-gateway' }));

app.post('/api/chat', async (req, res) => {
const { provider = 'openai', messages = [{ role:'user', content:'hello' }] } = req.body;
const adapter = providers[provider];
if (!adapter) return res.status(400).json({ error: 'unknown provider' });
try {
const { text, raw } = await adapter({ messages });
return res.json({ provider, text, raw });
} catch (err) {
console.error('provider error', err);
return res.status(500).json({ error: 'provider error' });
}
});

const port = process.env.PORT || 4000;
app.listen(port, () => console.log(`MCP gateway listening on ${port}`));

很好 — 你貼的 gateway.mjs 看起來很完整了,功能與結構也正確。下面我把它逐項檢查、補充建議、常見問題排查指令都寫清楚,方便你立刻驗證、上線(或繼續下一步)。我會把簡稱翻譯也放進去(你之前要求的)。


1) 立即檢查:這個檔案做了什麼(摘要)

  • 用 Express 建一個 HTTP server。
  • cors() 已啟用 → 允許跨來源(方便前端從 Windows 呼叫)。
  • 定義了兩個 provider adapter:openaideepseek。若沒有各自的 API key,會回 mock 回覆(方便測試)。
  • GET /healthPOST /api/chat 路由都已正確設置。
  • 監聽 PORT(預設 4000)。

(簡稱翻譯:CORS = Cross-Origin Resource Sharing(跨來源資源共享),API = Application Programming Interface(應用程式介面)。)


2) 如果你要馬上測試(步驟)

  1. 在 Mac B 的專案資料夾確定有 .env(若要用真實 API):

    1
    2
    3
    PORT=4000
    OPENAI_API_KEY=sk-...
    DEEPSEEK_API_KEY=ds-...
  2. 啟動/重啟 server:

    1
    2
    3
    node gateway.mjs
    # 或(建議未來用 pm2)
    pm2 start gateway.mjs --name mcp-gateway
  3. 在同機或 Windows 測試(你之前 curl /health 成功):

    1
    2
    3
    curl http://192.168.0.103:4000/health
    curl -X POST http://192.168.0.103:4000/api/chat -H "Content-Type: application/json" \
    -d '{"provider":"openai","messages":[{"role":"user","content":"嗨"}]}'

3) 常見問題與排查(你會遇到的)

  • Failed to fetch(在瀏覽器)→ 通常是 CORS網路。你已加 cors(),且之前能 curl 成功,所以已解決。

  • fetch is not defined(在 Node)→ 代表你的 Node 版本 < 18(舊版 Node 沒有內建 fetch)。解法:

    • 檢查版本:node -v

    • 若 < 18,安裝 node-fetch 並修改開頭:

      1
      npm i node-fetch

      在檔案開頭:

      1
      import fetch from 'node-fetch';
  • 若回傳 500 且 console 顯示 provider error → 把 console.error(err) 補成 console.error(err?.stack ?? err) 看詳細錯誤,或在 adapter 裡加 try/catch 逐步 log。


4) 建議(小改善、比較實務)

  1. 限制 CORS(生產) — 目前 app.use(cors()) 允許所有來源,測試時方便;上線時改成只允許你的前端:

    1
    app.use(cors({ origin: ['http://localhost:5173','http://你的前端網址'] }));
  2. 增加 request logging(方便除錯)

    1
    2
    3
    4
    app.use((req, res, next) => {
    console.log(new Date().toISOString(), req.method, req.url);
    next();
    });
  3. 加上超時/重試(避免 provider 卡住)— 可用 AbortController(Node 18+)或在 fetch 外包 timeout:

    1
    2
    3
    const controller = new AbortController();
    setTimeout(() => controller.abort(), 15000); // 15s timeout
    const resp = await fetch(url, { ..., signal: controller.signal });
  4. 更友善的錯誤回應

    1
    2
    3
    4
    5
    try { ... }
    catch (err) {
    console.error(err);
    res.status(502).json({ error: 'upstream provider failed', detail: err.message });
    }
  5. 不要把 API key 放到版本控制.env 加到 .gitignore


5) 如果你要把真實 API key 放進去(步驟)

  1. ~/mcp-gateway/.env 填入:

    1
    2
    3
    OPENAI_API_KEY=sk-...
    DEEPSEEK_API_KEY=ds-...
    PORT=4000
  2. 重啟 server(或用 pm2 restart):

    1
    2
    3
    pm2 restart mcp-gateway   # 如果用 pm2
    # 或
    node gateway.mjs
  3. 用前端或 curl 測試(若 API key 有問題,OpenAI 會回 401 或 429)。


6) 要常駐 & 合螢幕也能跑(快速指令)

1
2
3
4
npm i -g pm2
pm2 start gateway.mjs --name mcp-gateway
pm2 startup # 產生開機啟動指令(依系統提示執行)
pm2 save

另外 mac 合螢幕睡眠要關(System Settings → Battery → Prevent computer from sleeping when display is off),或用 Amphetamine App。


7) 下一步建議(你可以選一個)

  • A. 我幫你把 gateway.mjs 加上:request loggingfetch timeout、更清楚的 error message(貼給你修改後的完整檔案)。
  • B. 你把 node -v 回我,我幫你檢查是否需要 node-fetch,如果需要,我直接給可貼上的修改。
  • C. 你想現在把 OPENAI_KEY 放 .env,我給你測試指令與如何檢查 token 被使用的簡單方法。

你選一個,我立即把對應的完整程式或指令貼給你。