import { type Token } from '@frontmcp/di'; import { z } from '@frontmcp/lazy-zod'; import { type AdapterType, type PluginType, type PromptType, type ProviderType, type ResourceType, type SkillType, type ToolType, } from '../interfaces'; import { annotatedFrontMcpAdaptersSchema, annotatedFrontMcpPluginsSchema, annotatedFrontMcpPromptsSchema, annotatedFrontMcpProvidersSchema, annotatedFrontMcpResourcesSchema, annotatedFrontMcpSkillsSchema, annotatedFrontMcpToolsSchema, } from '../schemas'; import { type RawZodShape } from '../types'; /** * Context extension declaration for plugins. * Allows plugins to add properties to ExecutionContextBase (ToolContext, etc.) * without directly accessing SDK internals. * * @example * ```typescript * @Plugin({ * name: 'remember', * contextExtensions: [ * { property: 'remember', token: RememberAccessorToken }, * { property: 'approval', token: ApprovalServiceToken }, * ], * }) * ``` */ export interface ContextExtension { /** * Property name to add to ExecutionContextBase (e.g., 'remember', 'approval'). * Will be accessible as `this.remember` in tools. */ property: string; /** * DI token to resolve when the property is accessed. * The resolved value is returned when accessing `this.{property}`. */ token: Token; /** * Custom error message when the token cannot be resolved. * Default: "{PluginName} is not installed or {property} is not configured." */ errorMessage?: string; } /** * Declarative metadata describing what an McpPlugin contributes at app scope. */ export interface PluginMetadata { /** * Human-friendly name for this app as shown in UIs, logs, and describe reports. * Keep it short, stable, and unique within a gateway (consider prefixes). */ id?: string; /** * Human-friendly name for this app as shown in UIs, logs, and describe reports. * Keep it short, stable, and unique within a gateway (consider prefixes). */ name: string; /** * Optional longer explanation of what the app does and what it exposes. * Used for documentation, describe reports, and UIs. Avoid secrets/PII. */ description?: string; /** * Plugin-scoped providers Named singleton (or scoped-singleton) dependencies used by tools/plugins at runtime * — e.g., config, database pools, Redis clients, queues, KMS. * * Resolution is hierarchical (tool → app → gateway). Providers may depend on * other providers and are usually initialized lazily on first use. */ providers?: ProviderType[]; /** * Plugin can export providers to be used in the host app or other plugins, * e.g. for AuthPlugin can export AuthSession to be used in other plugins / apps. * * Resolution is hierarchical (tool → app → gateway). Providers may depend on * other providers and are usually initialized lazily on first use. */ exports?: ProviderType[]; /** * Plugin-scoped plugins that participate in lifecycle events and can contribute * additional capabilities (tools, resources, prompts, providers, adapters). * Use plugins to enforce policy, auth, PII reduction, tracing, etc. */ plugins?: PluginType[]; /** * Plugin-scoped Adapters attached to this app that convert external definitions or sources * (e.g., OpenAPI, Lambda, Custom) into generated tools/resources/prompts. * Common options include include/exclude filters, name prefixes, base URLs, * and authentication (often provided by plugins). */ adapters?: AdapterType[]; /** * Plugin-scoped Inline tools authored by this app (via builder or class). These are active * actions with input/output schemas that the model can call. Tools generated * by adapters do not need to be listed here. */ tools?: ToolType[]; resources?: ResourceType[]; prompts?: PromptType[]; /** * Plugin-scoped Skills that teach AI how to perform multi-step tasks. * Skills are workflow guides that combine tools into coherent recipes. * They can be discovered via the `skill://index.json` MCP resource * and loaded via `skill://{+skillPath}/SKILL.md` MCP resources (per * MCP SEP-2640). */ skills?: SkillType[]; /** * Determines where plugin hooks are registered: * - 'app' (default): Hooks fire only for requests to this app * - 'server': Hooks fire at gateway level for all apps * * Note: Plugins with scope='server' cannot be used in standalone apps. */ scope?: 'app' | 'server'; /** * Context extensions to add to ExecutionContextBase. * Allows plugins to provide `this.{property}` access in tools. * * @example * ```typescript * @Plugin({ * name: 'remember', * contextExtensions: [ * { property: 'remember', token: RememberAccessorToken }, * ], * }) * ``` */ contextExtensions?: ContextExtension[]; /** * Entry metadata keys this plugin's hooks enforce (e.g. `['approval']`), on the entries of the * flows those hooks run in: `tools/call` for tools and agents, `resources/read` for resources and * resource templates, `prompts/get` for prompts, and `skills:filter` for skills. * * Declare it on the plugin class whose hooks do the enforcing. A server where an entry declares * such a key and no hook of a plugin that enforces it reaches the entry refuses to start, so a * field that looks protective never silently does nothing. */ enforcesMetadata?: string[]; /** * The plugin registers skills at runtime (`scope.skills.registerSkillContent`), after the server * starts, such as skills from a bundle it loads. The server then serves the skills capability and * methods (`skills/list`, `skills/search`, `skills/load`, the SEP-2640 `skill://` resources and, * when enabled, the skills HTTP endpoints) from startup, answering with the skills registered so * far, even when no other skill exists. */ dynamicSkills?: boolean; } // Schema for context extensions (uses passthrough since token is a Symbol) const contextExtensionSchema = z .object({ property: z.string().min(1), token: z.any(), // Token is a Symbol, can't validate with zod errorMessage: z.string().optional(), }) .passthrough(); export const frontMcpPluginMetadataSchema = z .object({ id: z.string().optional(), name: z.string().min(1), description: z.string().optional(), providers: z.array(z.lazy(() => annotatedFrontMcpProvidersSchema)).optional(), exports: z.array(z.lazy(() => annotatedFrontMcpProvidersSchema)).optional(), plugins: z.array(z.lazy(() => annotatedFrontMcpPluginsSchema)).optional(), adapters: z.array(z.lazy(() => annotatedFrontMcpAdaptersSchema)).optional(), tools: z.array(z.lazy(() => annotatedFrontMcpToolsSchema)).optional(), resources: z.array(z.lazy(() => annotatedFrontMcpResourcesSchema)).optional(), prompts: z.array(z.lazy(() => annotatedFrontMcpPromptsSchema)).optional(), skills: z.array(z.lazy(() => annotatedFrontMcpSkillsSchema)).optional(), scope: z.enum(['app', 'server']).optional().default('app'), contextExtensions: z.array(contextExtensionSchema).optional(), enforcesMetadata: z.array(z.string().min(1)).optional(), dynamicSkills: z.boolean().optional(), } satisfies RawZodShape) .passthrough();