通过插件扩展 MCP 服务器
🌐 Extending the MCP server with plugins
Page summary:Strapi 插件可以通过
strapi.ai.mcp服务注册额外的 MCP 工具。注册必须在 MCP 服务器空闲时(在插件的register()生命周期阶段),并且在服务器启动之前进行。
Strapi 包含一个内置的 模型上下文协议 (MCP) 服务器,可以向 AI 客户端提供内容管理工具。除了从你的模式生成的工具之外,插件还可以注册自己的 MCP 功能,以便 AI 客户端触发特定插件的操作。插件可以通过 strapi.ai.mcp 服务注册 3 种功能类型:工具、资源和提示。
🌐 Strapi includes a built-in Model Context Protocol (MCP) server that exposes content management tools to AI clients. In addition to the tools generated from your schema, plugins can register their own MCP capabilities so AI clients can trigger plugin-specific actions. Plugins can register 3 capability types through the strapi.ai.mcp service: tools, resources, and prompts.
注册必须在 MCP 服务器空闲时发生,在它启动之前。在 Strapi 的加载生命周期中,在插件的 register() 阶段注册一个工具。
🌐 Registrations must happen while the MCP server is idle, before it starts. In Strapi's load lifecycle, register a tool during the plugin's register() phase.
注册自定义工具
🌐 Registering a custom tool
使用 strapi.ai.mcp.registerTool() 向 AI 客户端公开自定义工具:
🌐 Use strapi.ai.mcp.registerTool() to expose a custom tool to AI clients:
- JavaScript
- TypeScript
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 },
}),
});
},
};
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 },
}),
});
},
};
工具定义选项
🌐 Tool definition options
| 选项 | 类型 | 必填 | 描述 |
|---|---|---|---|
name | 字符串 | 是 | 工具唯一名称。必须在所有已注册的MCP工具中唯一。 |
title | 字符串 | 是 | 显示给 AI 客户端的人类可读标题。 |
description | 字符串 | 是 | 工具功能的简短描述。 |
auth | 对象 | 是(或 devModeOnly) | 身份验证要求。当令牌符合 policies 数组中的任一策略时,会话门将通过。每个策略都是 { action, subject? }。 |
devModeOnly | 布尔值 | 是(或 auth) | 设置为 true 以将工具限制为仅开发模式(等同于内置的 log 工具)。 |
resolveInputSchema | 功能 | 否 | 返回工具输入参数的 Zod 模式。每个请求都会调用,以便可以动态应用 RBAC 限制。对于没有输入的工具可以省略。 |
resolveOutputSchema | 功能 | 是 | 返回工具结构化输出的 Zod 模式。每次请求都会调用。 |
createHandler | 功能 | 是 | 返回异步工 具处理器的工厂。接收 Strapi 实例和每次请求的上下文(包括 userAbility 和 user)。 |
resolveInputSchema 和 resolveOutputSchema 会在每个进入的 MCP 请求时调用一次,因此你可以基于令牌的权限(通过 context.userAbility)动态缩小模式。
使用构建器辅助定义功能
🌐 Defining capabilities with builder helpers
构建器辅助工具是 TypeScript 用户的可选便利功能。注册能力的标准推荐方式是将其定义内联传递给 registerTool(),如上一节所示。你从不需要构建器辅助工具来注册工具、资源或提示:除非你特别想要它提供的额外 TypeScript 推断,否则可以跳过本节。
🌐 Builder helpers are an optional convenience for TypeScript users. The standard, recommended way to register a capability is to pass its definition inline to registerTool(), as shown in the previous section. You never need a builder helper to register a tool, resource, or prompt: skip this section unless you specifically want the extra TypeScript inference it provides.
将工具定义内联传递给 registerTool() 是标准方法,并且在大多数情况下效果良好。对于将功能定义保存在其自身模块中的较大插件,Strapi 可以选择性地导出一组构建器辅助工具,这些工具在定义在其 register 调用之外时可以改善 TypeScript 推断。
🌐 Passing the tool definition inline to registerTool() is the standard approach and works well for most cases. For larger plugins that keep capability definitions in their own modules, Strapi optionally exports a set of builder helpers that improve TypeScript inference when a definition is declared away from its register call.
这些辅助工具在 @strapi/strapi 上以 ai.mcp 命名空间导出:ai.mcp.defineTool、ai.mcp.defineResource 和 ai.mcp.definePrompt。每一个在运行时都返回其定义本身:它是一个纯类型推断辅助工具,而不是注册功能的另一种方式。它们推断功能的 name、模式和处理程序类型,并缩小访问变体 (devModeOnly 或 auth),因此结果可以直接分配给匹配的 register 方法。这类似于用于内容管理器 API 的 factories 辅助工具。
🌐 These helpers are exported under the ai.mcp namespace on @strapi/strapi: ai.mcp.defineTool, ai.mcp.defineResource, and ai.mcp.definePrompt. Each one returns its definition unchanged at runtime: it is a pure type-inference helper, not a different way to register a capability. They infer the capability's name, schemas, and handler types, and narrow the access variant (devModeOnly or auth) so the result is directly assignable to the matching register method. This is similar to the factories helpers used for content-manager APIs.
无论是否使用构建器,注册的方式都是相同的:在插件的 register() 阶段,将定义传递给 registerTool()(或 registerResource() / registerPrompt())。每个定义要么采用 devModeOnly: true,要么采用 auth 策略集合,从不同时使用两者。
🌐 Whether or not you use a builder, registration still happens the same way: pass the definition to registerTool() (or registerResource() / registerPrompt()) during the plugin's register() phase. Each definition takes either devModeOnly: true or an auth policy set, never both.
定义一个工具
🌐 Defining a tool
以下示例为了简洁使用了 devModeOnly。一个 auth 策略集,就像上面工具定义选项中所示的那样,工作方式相同:
🌐 The following example uses devModeOnly for brevity. An auth policy set, like the one shown in the tool definition options above, works the same way:
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 } };
},
});
从插件的服务器入口文件注册工具:
🌐 Register the tool from the plugin's server entry file:
import { greet } from './mcp/greet';
export default {
register({ strapi }) {
strapi.ai.mcp.registerTool(greet);
},
};
定义资源
🌐 Defining a resource
一个资源通过 URI 向 AI 客户端公开只读数据。使用 ai.mcp.defineResource 定义它,然后使用 strapi.ai.mcp.registerResource() 注册它:
🌐 A resource exposes read-only data to AI clients through a URI. Define it with ai.mcp.defineResource, then register it with 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 }) }],
}),
});
定义提示
🌐 Defining a prompt
一个提示向 AI 客户端暴露一个可重用的提示模板。使用 ai.mcp.definePrompt 定义它,然后使用 strapi.ai.mcp.registerPrompt() 注册它:
🌐 A prompt exposes a reusable prompt template to AI clients. Define it with ai.mcp.definePrompt, then register it with 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.' } }],
}),
});
构建器是恒等函数:它们在运行时不会改变定义。定义一个能力并不会注册它。在 MCP 服务器仍处于空闲状态时,将结果传递给 strapi.ai.mcp.registerTool()、registerResource() 或 registerPrompt(),在 register() 期间使用。
🌐 The builders are identity functions: they do not change the definition at runtime. Defining a capability does not register it. Pass the result to strapi.ai.mcp.registerTool(), registerResource(), or registerPrompt() during register(), while the MCP server is still idle.