/**
* FX-CRM 工具執行服務
*
* 此服務代理 FX-CRM 的 CRUD 操作,並整合權限檢查
*/
import { logger } from '../../middleware/logging.js'
import { queryFxcrm, getCorpAccessToken } from './client.js'
import { getAppSecrets } from '../../utils/secrets.js'
import { checkPermission, getActionType } from '../permission/index.js'
import type { Permission, PermissionCheckResult } from '../permission/types.js'
import type { ToolResponse } from '@mcp-internal/shared'
const FXCRM_BASE_URL = 'https://open.fxiaoke.com'
/**
* FX-CRM 工具定義
*/
export const fxcrmToolDefinitions = [
{
name: 'fxcrm_query',
description: '查詢 FX-CRM 物件',
inputSchema: {
type: 'object',
properties: {
object_api_name: { type: 'string', description: 'FX-CRM 物件 API 名稱' },
filters: {
type: 'array',
items: {
type: 'object',
properties: {
field_name: { type: 'string' },
field_values: { type: 'array' },
operator: { type: 'string', enum: ['EQ', 'NE', 'GT', 'LT', 'GTE', 'LTE', 'LIKE', 'IN', 'CONTAIN'] },
},
required: ['field_name', 'field_values', 'operator'],
},
description: '過濾條件'
},
field_projection: { type: 'array', items: { type: 'string' }, description: '要返回的欄位' },
limit: { type: 'number', default: 20, description: '返回數量限制' },
offset: { type: 'number', default: 0, description: '跳過數量' },
},
required: ['object_api_name'],
},
},
{
name: 'fxcrm_get',
description: '取得單一 FX-CRM 記錄',
inputSchema: {
type: 'object',
properties: {
object_api_name: { type: 'string', description: 'FX-CRM 物件 API 名稱' },
record_id: { type: 'string', description: '記錄 ID' },
field_projection: { type: 'array', items: { type: 'string' }, description: '要返回的欄位' },
},
required: ['object_api_name', 'record_id'],
},
},
{
name: 'fxcrm_create',
description: '建立 FX-CRM 記錄',
inputSchema: {
type: 'object',
properties: {
object_api_name: { type: 'string', description: 'FX-CRM 物件 API 名稱' },
data: { type: 'object', description: '記錄資料' },
opportunity_id: { type: 'string', description: '關聯商機 ID(用於權限檢查)' },
},
required: ['object_api_name', 'data'],
},
},
{
name: 'fxcrm_update',
description: '更新 FX-CRM 記錄',
inputSchema: {
type: 'object',
properties: {
object_api_name: { type: 'string', description: 'FX-CRM 物件 API 名稱' },
record_id: { type: 'string', description: '記錄 ID' },
data: { type: 'object', description: '要更新的資料' },
opportunity_id: { type: 'string', description: '關聯商機 ID(用於權限檢查)' },
},
required: ['object_api_name', 'record_id', 'data'],
},
},
{
name: 'fxcrm_invalid',
description: '作廢 FX-CRM 記錄',
inputSchema: {
type: 'object',
properties: {
object_api_name: { type: 'string', description: 'FX-CRM 物件 API 名稱' },
record_id: { type: 'string', description: '記錄 ID' },
opportunity_id: { type: 'string', description: '關聯商機 ID(用於權限檢查)' },
},
required: ['object_api_name', 'record_id'],
},
},
]
/**
* 執行 FX-CRM 工具
*/
export async function executeFxcrmTool(
toolName: string,
args: Record<string, unknown>,
userFsuid: string,
userPermission: string
): Promise<ToolResponse> {
try {
// 權限檢查
const permissionResult = await checkPermission({
userFsuid,
userPermission: userPermission as Permission,
objectApiName: args.object_api_name as string,
action: getActionType(toolName),
recordId: args.record_id as string | undefined,
opportunityId: args.opportunity_id as string | undefined,
})
if (!permissionResult.allowed) {
return {
success: false,
error: {
code: 'PERMISSION_DENIED',
message: permissionResult.reason || '權限不足',
},
}
}
// 根據工具類型執行操作
let result: any
switch (toolName) {
case 'fxcrm_query':
result = await handleQuery(args, permissionResult)
break
case 'fxcrm_get':
result = await handleGet(args)
break
case 'fxcrm_create':
result = await handleCreate(args, userFsuid)
break
case 'fxcrm_update':
result = await handleUpdate(args)
break
case 'fxcrm_invalid':
result = await handleInvalid(args)
break
default:
return {
success: false,
error: {
code: 'UNKNOWN_TOOL',
message: `未知工具: ${toolName}`,
},
}
}
return {
success: true,
result: {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2),
}],
},
}
} catch (error) {
logger.error({ error, toolName }, 'FX-CRM 工具執行失敗')
return {
success: false,
error: {
code: 'EXECUTION_ERROR',
message: error instanceof Error ? error.message : String(error),
},
}
}
}
/**
* 處理查詢
*/
async function handleQuery(
args: Record<string, unknown>,
permissionResult: PermissionCheckResult
): Promise<any> {
const objectApiName = args.object_api_name as string
let filters = (args.filters as any[]) || []
// 如果權限檢查返回過濾條件,合併進去
if (permissionResult.recordFilter) {
filters = [...permissionResult.recordFilter, ...filters]
}
const results = await queryFxcrm(
objectApiName,
filters,
{
fieldProjection: args.field_projection as string[] | undefined,
limit: (args.limit as number) || 20,
offset: (args.offset as number) || 0,
}
)
return {
total: results.length,
records: results,
}
}
/**
* 處理取得單一記錄
*/
async function handleGet(args: Record<string, unknown>): Promise<any> {
const objectApiName = args.object_api_name as string
const recordId = args.record_id as string
const results = await queryFxcrm(
objectApiName,
[{ field_name: '_id', field_values: [recordId], operator: 'EQ' }],
{
fieldProjection: args.field_projection as string[] | undefined,
limit: 1,
}
)
if (results.length === 0) {
throw new Error(`找不到記錄: ${recordId}`)
}
return results[0]
}
/**
* 處理建立記錄
*/
async function handleCreate(
args: Record<string, unknown>,
userFsuid: string
): Promise<any> {
const secrets = await getAppSecrets()
const accessToken = await getCorpAccessToken()
const response = await fetch(`${FXCRM_BASE_URL}/cgi/crm/custom/v2/data/create`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
corpAccessToken: accessToken,
corpId: secrets.fxcrmCorpId,
currentOpenUserId: userFsuid,
data: {
object_data: {
dataObjectApiName: args.object_api_name,
...(args.data as Record<string, unknown>),
},
},
}),
})
const data = await response.json() as any
if (data.errorCode !== 0) {
throw new Error(`FX-CRM 建立失敗: ${data.errorMessage}`)
}
return {
success: true,
record_id: data.data?._id || data.data?.objectDataId,
}
}
/**
* 處理更新記錄
*/
async function handleUpdate(args: Record<string, unknown>): Promise<any> {
const secrets = await getAppSecrets()
const accessToken = await getCorpAccessToken()
const response = await fetch(`${FXCRM_BASE_URL}/cgi/crm/custom/v2/data/update`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
corpAccessToken: accessToken,
corpId: secrets.fxcrmCorpId,
currentOpenUserId: secrets.fxcrmDefaultUserId,
data: {
object_data: {
dataObjectApiName: args.object_api_name,
_id: args.record_id,
...(args.data as Record<string, unknown>),
},
},
}),
})
const data = await response.json() as any
if (data.errorCode !== 0) {
throw new Error(`FX-CRM 更新失敗: ${data.errorMessage}`)
}
return {
success: true,
record_id: args.record_id,
}
}
/**
* 處理作廢記錄
*/
async function handleInvalid(args: Record<string, unknown>): Promise<any> {
const secrets = await getAppSecrets()
const accessToken = await getCorpAccessToken()
const response = await fetch(`${FXCRM_BASE_URL}/cgi/crm/custom/v2/data/invalid`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
corpAccessToken: accessToken,
corpId: secrets.fxcrmCorpId,
currentOpenUserId: secrets.fxcrmDefaultUserId,
data: {
object_data_id: args.record_id,
dataObjectApiName: args.object_api_name,
},
}),
})
const data = await response.json() as any
if (data.errorCode !== 0) {
throw new Error(`FX-CRM 作廢失敗: ${data.errorMessage}`)
}
return {
success: true,
record_id: args.record_id,
}
}