/**
* Shopify 工具執行器
*
* 權限規則:
* - 讀取工具:所有人可用
* - 寫入工具:僅 admin/assistant 可用
*/
import type { ToolResponse } from '@mcp-internal/shared'
import { getShopifyClient } from './client.js'
import type { Permission } from '../permission/types.js'
/** 需要寫入權限的工具 */
const SHOPIFY_WRITE_TOOLS = [
'shopify_create_product',
'shopify_update_product',
'shopify_update_price',
]
/** 可執行寫入操作的權限 */
const WRITE_PERMISSIONS: Permission[] = ['admin', 'assistant']
export const shopifyToolDefinitions = [
{
name: 'shopify_search_products',
description: '搜尋 Shopify 產品(所有人可用)',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: '搜尋關鍵字' },
limit: { type: 'number', description: '返回數量限制', default: 20 },
cursor: { type: 'string', description: '分頁游標' },
},
},
},
{
name: 'shopify_get_product',
description: '取得單一 Shopify 產品詳情(所有人可用)',
inputSchema: {
type: 'object',
properties: {
id: { type: 'string', description: '產品 ID' },
},
required: ['id'],
},
},
{
name: 'shopify_get_locations',
description: '取得 Shopify 庫存位置列表(所有人可用)',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'shopify_create_product',
description: '建立 Shopify 產品(僅 admin/assistant 可用)',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: '產品名稱' },
bodyHtml: { type: 'string', description: '產品描述(HTML)' },
vendor: { type: 'string', description: '供應商' },
productType: { type: 'string', description: '產品類型' },
status: { type: 'string', enum: ['ACTIVE', 'DRAFT', 'ARCHIVED'], description: '產品狀態', default: 'DRAFT' },
},
required: ['title'],
},
},
{
name: 'shopify_update_product',
description: '更新 Shopify 產品(僅 admin/assistant 可用)',
inputSchema: {
type: 'object',
properties: {
id: { type: 'string', description: '產品 ID' },
title: { type: 'string', description: '新產品名稱' },
bodyHtml: { type: 'string', description: '產品描述(HTML)' },
status: { type: 'string', enum: ['ACTIVE', 'DRAFT', 'ARCHIVED'], description: '產品狀態' },
},
required: ['id'],
},
},
{
name: 'shopify_update_price',
description: '更新 Shopify 產品變體價格(僅 admin/assistant 可用)',
inputSchema: {
type: 'object',
properties: {
variantId: { type: 'string', description: '變體 ID' },
price: { type: 'string', description: '新價格' },
},
required: ['variantId', 'price'],
},
},
]
/**
* 檢查 Shopify 工具權限
*/
export function checkShopifyPermission(
toolName: string,
userPermission: Permission
): { allowed: boolean; reason?: string } {
// 讀取工具:所有人可用
if (!SHOPIFY_WRITE_TOOLS.includes(toolName)) {
return { allowed: true }
}
// 寫入工具:僅 admin/assistant 可用
if (WRITE_PERMISSIONS.includes(userPermission)) {
return { allowed: true }
}
return {
allowed: false,
reason: `Shopify 寫入操作需要 admin 或 assistant 權限,您的權限為 ${userPermission}`,
}
}
/**
* 執行 Shopify 工具
*/
export async function executeShopifyTool(
toolName: string,
args: any,
userPermission?: Permission
): Promise<ToolResponse> {
const startTime = Date.now()
// 防禦性權限檢查(即使路由層已檢查)
if (userPermission) {
const permCheck = checkShopifyPermission(toolName, userPermission)
if (!permCheck.allowed) {
return {
success: false,
error: {
code: 'PERMISSION_DENIED',
message: permCheck.reason || '權限不足',
},
metadata: {
executionTimeMs: Date.now() - startTime,
tool: toolName,
timestamp: new Date().toISOString(),
},
}
}
}
try {
const client = await getShopifyClient()
let result: any
switch (toolName) {
case 'shopify_search_products':
result = await client.searchProducts({
query: args.query,
limit: args.limit,
cursor: args.cursor,
})
break
case 'shopify_get_product':
result = await client.getProduct(args.id)
if (!result) {
throw new Error(`找不到產品: ${args.id}`)
}
break
case 'shopify_get_locations':
result = await client.getLocations()
break
case 'shopify_create_product':
result = await client.createProduct({
title: args.title,
bodyHtml: args.bodyHtml,
vendor: args.vendor,
productType: args.productType,
status: args.status || 'DRAFT',
})
break
case 'shopify_update_product':
result = await client.updateProduct({
id: args.id,
title: args.title,
bodyHtml: args.bodyHtml,
status: args.status,
})
break
case 'shopify_update_price':
await client.updateVariantPrice(args.variantId, args.price)
result = { success: true, variantId: args.variantId, price: args.price }
break
default:
throw new Error(`未知的 Shopify 工具: ${toolName}`)
}
return {
success: true,
result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] },
metadata: {
executionTimeMs: Date.now() - startTime,
tool: toolName,
timestamp: new Date().toISOString(),
},
}
} catch (error) {
return {
success: false,
error: {
code: 'EXTERNAL_SERVICE_ERROR',
message: error instanceof Error ? error.message : String(error),
},
metadata: {
executionTimeMs: Date.now() - startTime,
tool: toolName,
timestamp: new Date().toISOString(),
},
}
}
}