MCP AI 集成
概述
InnoShop 通过 MCP(Model Context Protocol) 将后台能力暴露为 AI 可调用的工具,让 Claude Code、Cursor、Cline 等 AI 助手可以直接与你的店铺数据交互 — 查询产品、管理订单、检查库存等。
MCP 基于 JSON-RPC 2.0 over HTTP(Streamable HTTP 传输),出于安全考虑默认关闭。
架构
MCP 系统分为三层,完全解耦:
┌─────────────────────────────────────┐
│ Claude Code / Cursor / Cline │ ← MCP 客户端
│ (JSON-RPC 2.0 over HTTP) │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ innopacks/mcp │ ← 协议层
│ InnoShopMcpServer │ Sanctum 认证、Origin 校验、
│ RegistryToolAdapter │ 开关控制、响应格式化
│ McpController (欢迎页) │
└──────────────┬──────────────────────┘
│ ToolInterface 契约
┌──────────────▼──────────────────────┐
│ innopacks/ai │ ← 工具定义
│ ToolRegistry (单例) │ 45+ 内置工具、
│ BaseTool, ToolInterface │ 插件通过 Hook 扩展
│ PanelChatAgent │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ innopacks/common │ ← 业务逻辑
│ Repositories, Models, Services │
└─────────────────────────────────────┘核心原则:工具只是现有 Repository 的薄适配层 — 不包含任何业务逻辑。
启用 MCP
MCP 由系统设置控制,可在管理后台开启:
- 进入 面板 → 系统设置 → 工具 → AI
- 找到 MCP 服务 卡片
- 打开开关
或通过代码:
system_setting('mcp_enabled', true);关闭后,所有 MCP 端点返回 404。
安全层级
| 层级 | 机制 | 用途 |
|---|---|---|
| 功能开关 | system_setting('mcp_enabled') | 总开关 |
| Origin 校验 | ValidateMcpOrigin 中间件 | 阻止 DNS 重绑定攻击 |
| 身份认证 | Laravel Sanctum (auth:sanctum) | 需要管理员 API Token |
| 权限控制 | $user->can($permission) | 工具级别的权限检查 |
获取 API Token
curl -X POST https://your-store.com/api/panel/login \
-H "Content-Type: application/json" \
-d '{"email": "admin@example.com", "password": "your-password"}'返回的 token 字段即 Bearer Token:
Authorization: Bearer <token>连接 AI 助手
Claude Code
claude mcp add --transport http innoshop https://your-store.com/mcp \
--header "Authorization: Bearer YOUR_TOKEN"Cursor / Cline
在 MCP 配置中添加:
{
"mcpServers": {
"innoshop": {
"url": "https://your-store.com/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
}内置工具
InnoShop 内置 45+ 个工具,覆盖全部店铺操作。标记 ⚠️ 的为写入操作。
| 分类 | 工具 |
|---|---|
| 产品 | product_list, product_detail, product_create ⚠️, product_update ⚠️, product_autocomplete, sku_autocomplete |
| 订单 | order_list, order_detail, order_update_status ⚠️, order_note ⚠️, order_return_list, order_return_detail |
| 客户 | customer_list, customer_detail, customer_update ⚠️, customer_autocomplete |
| 分类 | category_list, category_detail, category_create ⚠️, category_update ⚠️, category_autocomplete |
| 品牌 | brand_list |
| 物流 | shipment_list, shipment_detail, shipment_create ⚠️, shipment_traces |
| 统计 | dashboard, sales_stats, stock_report |
| 内容 | article_list, article_detail, catalog_list, page_list, tag_list |
| 文件 | file_list, file_upload ⚠️ |
| 评价 | review_list, review_detail |
| 属性 | attribute_list, option_list |
| 本地化 | locale_list, currency_list, country_list, region_list |
| 税费 | tax_rate_list, tax_class_list |
完整列表始终可在 GET /mcp 公开欢迎页查看。
在插件中开发 MCP 工具
插件可以通过 ai.tools Hook 注册自定义 MCP 工具。这是主要的扩展点 — 注册后,工具自动出现在 MCP tools/list、tools/call、欢迎页和后台 AI 聊天中。
核心契约
ToolInterface (innopacks/ai/src/Contracts/ToolInterface.php):
interface ToolInterface
{
public function name(): string; // 唯一标识,snake_case
public function description(): string; // 给 LLM 看的描述
public function inputSchema(): array; // 参数的 JSON Schema
public function requiredPermission(): ?string; // 权限标识,null = 无需权限
public function execute(array $arguments): mixed; // 执行,参数已经过校验
}BaseTool (innopacks/ai/src/Tools/BaseTool.php):
abstract class BaseTool implements ToolInterface
{
public function inputSchema(): array
{
return ['type' => 'object', 'properties' => new \stdClass];
}
public function requiredPermission(): ?string
{
return null; // 默认无需权限
}
}分步指南:只读工具
我们来构建一个暴露 recent_orders 工具的插件。创建以下文件:
1. 插件目录结构
plugins/RecentOrders/
├── Boot.php
├── config.json
├── Lang/
│ ├── en/common.php
│ └── zh-cn/common.php
└── Tools/
└── RecentOrdersTool.php2. config.json
{
"code": "recent_orders",
"name": { "en": "Recent Orders MCP", "zh-cn": "最近订单 MCP" },
"description": { "en": "Expose recent orders via MCP", "zh-cn": "通过 MCP 暴露最近订单" },
"type": "feature",
"version": "v1.0.0",
"author": { "name": "InnoShop", "email": "team@innoshop.com" }
}3. Tools/RecentOrdersTool.php
<?php
namespace Plugin\RecentOrders\Tools;
use InnoShop\AI\Tools\BaseTool;
use InnoShop\Common\Repositories\OrderRepo;
class RecentOrdersTool extends BaseTool
{
public function name(): string
{
return 'recent_orders';
}
public function description(): string
{
return '获取最近订单,支持按状态筛选。';
}
public function inputSchema(): array
{
return [
'type' => 'object',
'properties' => [
'limit' => [
'type' => 'integer',
'description' => '返回数量,默认 10,最大 50',
],
'status' => [
'type' => 'string',
'description' => '按状态筛选:unpaid, paid, shipped, completed, cancelled',
],
],
];
}
public function requiredPermission(): ?string
{
return 'orders_index';
}
public function execute(array $arguments): mixed
{
$filters = ['per_page' => min(50, max(1, (int) ($arguments['limit'] ?? 10)))];
if (!empty($arguments['status'])) {
$filters['status'] = $arguments['status'];
}
$paginator = OrderRepo::getInstance()->list($filters);
return [
'total' => $paginator->total(),
'items' => $paginator->map(fn($order) => [
'number' => $order->number,
'customer' => $order->customer_name,
'total' => (float) $order->total,
'status' => $order->status,
'created_at' => (string) $order->created_at,
])->values()->all(),
];
}
}4. Boot.php
<?php
namespace Plugin\RecentOrders;
class Boot
{
public function init(): void
{
add_hook_filter('ai.tools', function ($registry) {
$registry->register(new Tools\RecentOrdersTool);
return $registry;
});
}
}完成。安装并启用插件后,recent_orders 工具自动出现在 MCP 欢迎页和 tools/list 中。
分步指南:写入工具
写入工具遵循相同模式,但权限更严格:
<?php
namespace Plugin\RecentOrders\Tools;
use InnoShop\AI\Tools\BaseTool;
use InnoShop\Common\Repositories\OrderRepo;
use InvalidArgumentException;
class OrderMarkPaidTool extends BaseTool
{
public function name(): string
{
return 'order_mark_paid';
}
public function description(): string
{
return '⚠️ WRITE: 将未付款订单标记为已付款。';
}
public function inputSchema(): array
{
return [
'type' => 'object',
'properties' => [
'number' => ['type' => 'string', 'description' => '订单号'],
'comment' => ['type' => 'string', 'description' => '备注(可选)'],
],
'required' => ['number'],
];
}
public function requiredPermission(): ?string
{
return 'orders_update_status';
}
public function execute(array $arguments): mixed
{
$order = OrderRepo::getInstance()->findByNumber($arguments['number']);
if (!$order) {
throw new InvalidArgumentException("订单 [{$arguments['number']}] 不存在。");
}
$order->status = 'paid';
$order->save();
return [
'number' => $order->number,
'status' => $order->status,
'updated_at' => (string) $order->updated_at,
];
}
}写入工具约定
- 在描述开头加
⚠️ WRITE:让 LLM 知道这是会修改数据的操作 - 用
requiredPermission()将写入操作限制在特定后台权限之后 - 始终在修改前验证目标实体是否存在
- 对缺失/无效输入抛出
InvalidArgumentException— MCP 层会捕获并转为错误响应
Hook 时机
ai.tools Hook 在首次访问 ToolRegistry 时延迟触发。这意味着在 AIServiceProvider 之后启动的插件也能注册工具,无需关心启动顺序。
权限参考
| 权限标识 | 所需场景 |
|---|---|
products_index | 读取产品列表 |
products_show | 读取产品详情 |
products_create | 创建产品 |
products_update | 更新产品 |
orders_index | 读取订单列表 |
orders_show | 读取订单详情 |
orders_update_status | 修改订单状态 |
customers_index | 读取客户列表 |
customers_show | 读取客户详情 |
customers_update | 更新客户 |
files_index | 浏览媒体文件 |
files_create | 上传文件 |
shipments_create | 创建发货单 |
不设置 requiredPermission()(返回 null)的工具对所有已认证管理员开放。
工具命名规范
- 使用
snake_case命名 - 列表类:
{entity}_list(如product_list、order_list) - 详情类:
{entity}_detail(如product_detail、customer_detail) - 创建/更新:
{entity}_create、{entity}_update - 自动补全:
{entity}_autocomplete - 实体名使用复数形式(与后台权限标识保持一致)
响应格式
工具应返回数组而非 JSON 字符串,MCP 层负责序列化:
// 列表响应
return [
'total' => $paginator->total(),
'page' => $paginator->currentPage(),
'items' => [...],
];
// 详情响应
return [
'id' => $entity->id,
'name' => $entity->name,
// ... 实体字段
];MCP 层会自动在响应中注入 _shop 元数据(店铺名称、域名、URL),让 LLM 知道自己在操作哪个店铺。
测试工具
<?php
use Plugin\RecentOrders\Tools\RecentOrdersTool;
use InnoShop\AI\Services\ToolRegistry;
test('recent_orders 工具已注册', function () {
$registry = app(ToolRegistry::class);
$registry->register(new RecentOrdersTool);
expect($registry->has('recent_orders'))->toBeTrue();
});
test('recent_orders 工具返回数据', function () {
$tool = new RecentOrdersTool;
$result = $tool->execute(['limit' => 5]);
expect($result)->toHaveKeys(['total', 'items']);
expect(count($result['items']))->toBeLessThanOrEqual(5);
});
test('recent_orders 工具有正确的权限要求', function () {
$tool = new RecentOrdersTool;
expect($tool->requiredPermission())->toBe('orders_index');
});AI 聊天集成
通过 ai.tools 注册的工具同时也在后台 AI 聊天(内置管理面板助手)中可用。同一个 ToolInterface 实现由 LaravelAiToolAdapter 适配为 MCP 和面板聊天两套接口。
欢迎页
每个启用 MCP 的 InnoShop 实例都有一个公开欢迎页 GET /mcp,展示:
- 服务端点 URL
- Claude Code / Cursor 连接说明
- Token 获取指引
- 完整工具目录及描述
- 插件扩展示例代码
欢迎页无需认证即可访问。
常见问题
访问 /mcp 返回 404
MCP 功能未开启。在 面板 → 系统设置 → 工具 → AI → MCP 服务 中启用。
401 Unauthenticated
- 确认请求携带
Authorization: Bearer <token>头 - 验证 Token 仍然有效(Sanctum Token 默认不过期)
- 认证用户必须是
Admin模型实例
工具返回 Permission denied
工具的 requiredPermission() 返回了当前管理员不具备的权限。解决方案:
- 在 面板 → 角色管理 中授予相应权限
- 如果工具应该无限制使用,移除
requiredPermission()的返回值
工具没有出现
- 确认插件已在 面板 → 插件管理 中启用
- 检查
Boot.php的init()方法中是否调用了add_hook_filter('ai.tools', ...) - 确认工具类实现了
ToolInterface接口 - 检查是否有重名工具(会抛出
LogicException)