使用插件扩展 MCP 服务端(MCP server)

页面摘要: Strapi 插件可以通过 strapi.ai.mcp 服务注册额外的 MCP 工具。注册必须在 MCP 服务端空闲时(即在插件的 register() 生命周期阶段)、服务端启动之前进行。

Strapi 包含一个内置的 模型上下文协议(MCP)服务端,它向 AI 客户端暴露内容管理工具。除了从你的 schema 生成的工具之外,插件还可以注册自己的 MCP 能力,以便 AI 客户端能够触发插件特定的操作。插件可以通过 strapi.ai.mcp 服务注册 3 种能力类型:tools(工具)、resources(资源)和 prompts(提示)。

注册必须在 MCP 服务端空闲时、服务端启动之前进行。在 Strapi 的加载生命周期中,请在插件的 register() 阶段注册工具。

注册自定义工具

使用 strapi.ai.mcp.registerTool() 向 AI 客户端暴露一个自定义工具:

JavaScript

const { z } = require('@strapi/utils');

module.exports = {
  register({ strapi }) {
    strapi.ai.mcp.registerTool({
      name: 'my_custom_tool',
      title: 'My Custom Tool',
      description: 'A short description shown to the AI client.',
      auth: {
        // The session gate passes when the token satisfies ANY policy in the array.
        policies: [{ action: 'plugin::my-plugin.my-action' }],
      },
      // resolveInputSchema and resolveOutputSchema are called per request,
      // so they can narrow schemas based on the token's permissions.
      resolveInputSchema: (context) =>
        z.object({
          message: z.string().describe('The message to echo.'),
        }),
      resolveOutputSchema: (context) =>
        z.object({
          result: z.string(),
        }),
      createHandler: (strapi, context) => async ({ args }) => ({
        content: [{ type: 'text', text: args.message }],
        structuredContent: { result: args.message },
      }),
    });
  },
};

TypeScript

import { z } from '@strapi/utils';

export default {
  register({ strapi }) {
    strapi.ai.mcp.registerTool({
      name: 'my_custom_tool',
      title: 'My Custom Tool',
      description: 'A short description shown to the AI client.',
      auth: {
        // The session gate passes when the token satisfies ANY policy in the array.
        policies: [{ action: 'plugin::my-plugin.my-action' }],
      },
      // resolveInputSchema and resolveOutputSchema are called per request,
      // so they can narrow schemas based on the token's permissions.
      resolveInputSchema: (context) =>
        z.object({
          message: z.string().describe('The message to echo.'),
        }),
      resolveOutputSchema: (context) =>
        z.object({
          result: z.string(),
        }),
      createHandler: (strapi, context) => async ({ args }) => ({
        content: [{ type: 'text', text: args.message }],
        structuredContent: { result: args.message },
      }),
    });
  },
};

工具定义选项

OptionTypeRequiredDescription
nameStringYes唯一的工具名称。必须在所有已注册的 MCP 工具中保持唯一。
titleStringYes向 AI 客户端显示的可读标题。
descriptionStringYes关于该工具功能的简短描述。
authObjectYes(或 devModeOnly)身份验证要求。当令牌满足 policies 数组中的任意策略时,会话门(session gate)通过。每个策略为 { action, subject? }。
devModeOnlyBooleanYes(或 auth)设为 true 以将该工具限制为仅开发模式(等同于内置的 log 工具)。
resolveInputSchemaFunctionNo返回该工具输入参数的 Zod schema。每次请求都会调用,以便可以动态应用 RBAC 约束。对于没有输入的工具有省略。
resolveOutputSchemaFunctionYes返回该工具结构化输出的 Zod schema。每次请求都会调用。
createHandlerFunctionYes返回异步工具处理函数的工厂函数。接收 Strapi 实例和每次请求的上下文(包括 userAbility 和 user)。
NOTE

resolveInputSchema 和 resolveOutputSchema 每次传入的 MCP 请求都会调用一次,因此你可以根据令牌的权限(通过 context.userAbility)动态收窄 schema。

使用构建器辅助函数定义能力

WARNING

构建器辅助函数是对 TypeScript 用户的可选便利。注册能力的标准、推荐方式是将能力定义内联传递给 registerTool(),如 上一节 所示。你永远不需要构建器辅助函数来注册工具、资源或提示:除非你特别需要它所提供的额外 TypeScript 类型推断,否则可以跳过本节。

将工具定义内联传递给 registerTool() 是标准做法,在多数情况下都很好用。对于将能力定义保存在各自模块中的较大插件,Strapi 选择性地导出一组构建器辅助函数,用于在与 register 调用分离声明定义时改善 TypeScript 推断。

这些辅助函数通过 @strapi/strapi 上的 ai.mcp 命名空间导出:ai.mcp.defineTool、ai.mcp.defineResource 和 ai.mcp.definePrompt。每个函数在运行时都会原样返回其定义:它是一个纯粹的类型推断辅助函数,而不是注册能力的另一种方式。它们推断能力的 name、schema 和处理函数类型,并收窄访问变体(devModeOnly 或 auth),使结果可直接赋值给匹配的 register 方法。这类似于内容管理器 API 所用的 factories 辅助函数。

无论你是否使用构建器,注册方式仍然相同:在插件的 register() 阶段将定义传递给 registerTool()(或 registerResource() / registerPrompt())。每个定义要么取 devModeOnly: true,要么取一个 auth 策略集,绝不能两者兼有。

定义工具

以下示例为了简洁使用 devModeOnly。一个 auth 策略集,如上方 工具定义选项 所示,工作方式相同:

import { ai } from '@strapi/strapi';
import { z } from '@strapi/utils';

export const greet = ai.mcp.defineTool({
  name: 'greet',
  title: 'Greet',
  description: 'Greets a user by name',
  devModeOnly: true,
  resolveInputSchema: () => z.object({ name: z.string() }),
  resolveOutputSchema: () => z.object({ message: z.string() }),
  createHandler: (strapi) => async ({ args }) => {
    const message = `Hello, ${args.name}!`;
    return { content: [{ type: 'text', text: message }], structuredContent: { message } };
  },
});

从插件的服务端入口文件注册该工具:

import { greet } from './mcp/greet';

export default {
  register({ strapi }) {
    strapi.ai.mcp.registerTool(greet);
  },
};

定义资源

资源通过 URI 向 AI 客户端暴露只读数据。使用 ai.mcp.defineResource 定义它,然后使用 strapi.ai.mcp.registerResource() 注册它:

import { ai } from '@strapi/strapi';

export const appInfo = ai.mcp.defineResource({
  name: 'app-info',
  uri: 'strapi://app/info',
  metadata: { description: 'Metadata about the app', mimeType: 'application/json' },
  devModeOnly: true,
  createHandler: (strapi) => async (uri) => ({
    contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify({ ok: true }) }],
  }),
});

定义提示

提示向 AI 客户端暴露一个可复用的提示模板。使用 ai.mcp.definePrompt 定义它,然后使用 strapi.ai.mcp.registerPrompt() 注册它:

import { ai } from '@strapi/strapi';

export const appContext = ai.mcp.definePrompt({
  name: 'app-context',
  title: 'App Context',
  description: 'Provides context about the app',
  devModeOnly: true,
  createHandler: (strapi) => async () => ({
    messages: [{ role: 'user', content: { type: 'text', text: 'You are connected to Strapi.' } }],
  }),
});
NOTE

这些构建器是恒等函数:它们在运行时不会改变定义。定义一个能力并不会注册它。请在 register() 期间、且 MCP 服务端仍处于空闲状态时,将结果传递给 strapi.ai.mcp.registerTool()、registerResource() 或 registerPrompt()。