Enable plugins to declare per-project MCP server registrations. - Add plugin MCP server contribution types, loading, and resolution across core and engine runtimes. - Expose resolved plugin registrations through project configuration APIs and MCP settings UI. - Document the declarative contribution API and add release metadata and regression coverage. Files changed: .changeset/plugin-mcp-servers.md | 7 ++ docs/PLUGIN_AUTHORING.md | 17 +++++ docs/mcp.md | 4 ++ docs/settings-reference.md | 4 ++ packages/core/src/__tests__/mcp-config.test.ts | 33 +++++++++ .../__tests__/plugin-contribution-types.test.ts | 16 +++++ .../__tests__/plugin-loader-single-load.test.ts | 23 +++++++ packages/core/src/index.gate.ts | 3 + packages/core/src/index.ts | 3 + packages/core/src/mcp-config.ts | 37 ++++++++-- packages/core/src/plugin-loader.ts | 24 +++++++ packages/core/src/plugin-mcp-servers.ts | 78 ++++++++++++++++++++++ packages/core/src/plugin-types.ts | 15 ++++- packages/core/src/types.ts | 2 +- .../__tests__/SettingsModal.mcp.test.tsx | 34 +++++++++- .../settings/sections/McpServersCard.tsx | 52 ++++++++++----- .../settings/sections/ProjectMcpSection.tsx | 31 ++++++++- .../register-config-mcp-pi-settings-routes.test.ts | 18 ++++- packages/dashboard/src/routes/context.ts | 71 +++++++++++++++++++- .../register-config-mcp-pi-settings-routes.ts | 30 ++++++++- .../engine/src/__tests__/mcp-resolution.test.ts | 20 ++++++ packages/engine/src/mcp-resolution.ts | 15 ++++- packages/engine/src/plugin-runner.ts | 38 +++++++++++ packages/engine/src/runtimes/in-process-runtime.ts | 23 +++++++ packages/plugin-sdk/src/index.ts | 1 + 25 files changed, 563 insertions(+), 36 deletions(-) Fusion-Task-Id: FN-8491 Fusion-Task-Lineage: be7e22fa-5776-4b3d-9fd1-a873799e6427 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
97 lines
4.0 KiB
TypeScript
97 lines
4.0 KiB
TypeScript
import {
|
|
materializeMcpServersSecrets,
|
|
resolveEffectiveMcpServers,
|
|
type GlobalSettings,
|
|
type McpSecretReader,
|
|
type McpSecretReaderIdentity,
|
|
type McpSecretResolutionError,
|
|
type ProjectSettings,
|
|
type PluginMcpServerContribution,
|
|
type ResolvedMcpServerDefinition,
|
|
} from "@fusion/core";
|
|
|
|
export interface ResolveMcpServersForRuntimeOptions {
|
|
globalSettings?: Pick<GlobalSettings, "mcpServers"> | null;
|
|
projectSettings?: Pick<ProjectSettings, "mcpServers"> | null;
|
|
secrets: McpSecretReader;
|
|
reader?: McpSecretReaderIdentity;
|
|
/** Already project-scoped plugin entries. Raw runner/loader output is forbidden here. */
|
|
pluginServers?: Array<{ pluginId: string; server: PluginMcpServerContribution }>;
|
|
}
|
|
|
|
export interface ResolvedMcpServersForRuntime {
|
|
servers: ResolvedMcpServerDefinition[];
|
|
errors: McpSecretResolutionError[];
|
|
}
|
|
|
|
/**
|
|
* FNXC:McpConfig 2026-06-25-21:43:
|
|
* FNXC:PluginMcpServers 2026-07-22-12:00:
|
|
* FN-8491 accepts only entries that the shared project-scoped provider already
|
|
* filtered by project_plugin_states; raw loader/runner output must never cross this seam.
|
|
*
|
|
* Runtime MCP forwarding uses Fusion's trusted-once-enabled model: enabled effective servers are materialized once at session/probe creation and then forwarded without per-call prompts. Plaintext env/header values exist only in this in-memory return value and callers must log only counts/errors, never server contents.
|
|
*/
|
|
export async function resolveMcpServersForRuntime(
|
|
options: ResolveMcpServersForRuntimeOptions,
|
|
): Promise<ResolvedMcpServersForRuntime> {
|
|
const effective = resolveEffectiveMcpServers(options.globalSettings, options.projectSettings, options.pluginServers);
|
|
if (effective.length === 0) return { servers: [], errors: [] };
|
|
|
|
const materialized = await materializeMcpServersSecrets(
|
|
effective,
|
|
options.secrets,
|
|
options.reader ?? {},
|
|
);
|
|
const failedServerNames = new Set(materialized.errors.map((error) => error.serverName));
|
|
return {
|
|
// Never forward a partially materialized definition: it could connect
|
|
// without the operator-required credential. Other healthy MCP servers and
|
|
// the owning agent session remain available.
|
|
servers: (materialized.value ?? []).filter((server) => !failedServerNames.has(server.name)),
|
|
errors: materialized.errors,
|
|
};
|
|
}
|
|
|
|
export interface McpSettingsAndSecretsStore {
|
|
getSettingsByScope?(): Promise<{
|
|
global: Pick<GlobalSettings, "mcpServers">;
|
|
project: Partial<Pick<ProjectSettings, "mcpServers">>;
|
|
}>;
|
|
getSecretsStore?(): Promise<McpSecretReader> | McpSecretReader;
|
|
/** Shared provider hook; implementations must filter project_plugin_states. */
|
|
getProjectScopedPluginMcpServers?(): Promise<Array<{ pluginId: string; server: PluginMcpServerContribution }>> | Array<{ pluginId: string; server: PluginMcpServerContribution }>;
|
|
}
|
|
|
|
const emptyMcpSecretReader: McpSecretReader = {
|
|
async revealSecret() {
|
|
throw new Error("MCP secret reader is unavailable");
|
|
},
|
|
};
|
|
|
|
export async function resolveMcpServersForStore(
|
|
store: McpSettingsAndSecretsStore,
|
|
reader?: McpSecretReaderIdentity,
|
|
): Promise<ResolvedMcpServersForRuntime> {
|
|
/*
|
|
* FNXC:McpConfig 2026-06-26-01:07:
|
|
* Older tests and lightweight TaskStore doubles may not implement the settings/secrets seams because they never configure MCP. Treat those stores as having no enabled MCP servers so all AI lanes keep their existing behavior while real stores still forward the resolved runtime configuration.
|
|
*/
|
|
if (typeof store.getSettingsByScope !== "function") {
|
|
return { servers: [], errors: [] };
|
|
}
|
|
|
|
const [settings, secrets, pluginServers] = await Promise.all([
|
|
store.getSettingsByScope(),
|
|
typeof store.getSecretsStore === "function" ? store.getSecretsStore() : emptyMcpSecretReader,
|
|
typeof store.getProjectScopedPluginMcpServers === "function" ? store.getProjectScopedPluginMcpServers() : [],
|
|
]);
|
|
return resolveMcpServersForRuntime({
|
|
globalSettings: settings.global,
|
|
projectSettings: settings.project,
|
|
secrets,
|
|
reader,
|
|
pluginServers,
|
|
});
|
|
}
|