#!/usr/bin/env node
/**
* MCP Internal Thin Client
*
* 本地 MCP Server,將所有請求轉發到 Cloud Run Backend
*
* 環境變數:
* - CLOUD_RUN_URL: Cloud Run 後端 URL
* - MCP_TOKEN: 員工專屬 Token
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js'
import 'dotenv/config'
import { getHttpClient } from './proxy/http-client.js'
import { toolDefinitions } from './tools/definitions.js'
// ============================================
// 設定驗證
// ============================================
const CLOUD_RUN_URL = process.env.CLOUD_RUN_URL
const FSUID = process.env.FSUID
if (!CLOUD_RUN_URL) {
console.error('錯誤: CLOUD_RUN_URL 環境變數未設定')
process.exit(1)
}
if (!FSUID) {
console.error('錯誤: FSUID 環境變數未設定')
process.exit(1)
}
if (!FSUID.startsWith('FSUID_')) {
console.error('錯誤: FSUID 格式不正確,應為 FSUID_xxxxx')
process.exit(1)
}
// ============================================
// MCP Server
// ============================================
const server = new Server(
{
name: 'mcp-internal',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
)
// ============================================
// 工具列表
// ============================================
server.setRequestHandler(ListToolsRequestSchema, async () => {
// 返回靜態工具定義
// 實際權限過濾由 Cloud Run Backend 處理
return {
tools: toolDefinitions,
}
})
// ============================================
// 工具執行
// ============================================
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params
const httpClient = getHttpClient()
// 轉發請求到 Cloud Run Backend
const response = await httpClient.executeTool(name, args as Record<string, unknown>)
if (!response.success) {
return {
content: [{
type: 'text',
text: JSON.stringify({
error: true,
code: response.error?.code,
message: response.error?.message,
}, null, 2),
}],
isError: true,
}
}
// 直接返回 Backend 的結果
return response.result || {
content: [{ type: 'text', text: '操作成功' }],
}
})
// ============================================
// 啟動
// ============================================
async function main() {
const httpClient = getHttpClient()
// 檢查 Backend 連線
const isHealthy = await httpClient.healthCheck()
if (!isHealthy) {
console.error('警告: 無法連接到 Cloud Run Backend')
}
const transport = new StdioServerTransport()
await server.connect(transport)
console.error('MCP Internal Thin Client 啟動')
console.error(`Backend: ${CLOUD_RUN_URL}`)
console.error(`User: ${FSUID}`)
}
main().catch((error) => {
console.error('啟動失敗:', error)
process.exit(1)
})