Skip to content

Commit b48413e

Browse files
committed
Plugins: surface MCP servers and bundle capabilities in inspect reports
1 parent b9b891b commit b48413e

4 files changed

Lines changed: 156 additions & 2 deletions

File tree

src/cli/plugins-cli.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,8 @@ export function registerPluginsCli(program: Command) {
660660
.map((entry) => (entry.severity === "warn" ? `warn:${entry.code}` : entry.code))
661661
.join(", ")
662662
: "none",
663+
Bundle:
664+
inspect.bundleCapabilities.length > 0 ? inspect.bundleCapabilities.join(", ") : "-",
663665
Hooks: formatHookSummary({
664666
usesLegacyBeforeAgentStart: inspect.usesLegacyBeforeAgentStart,
665667
typedHookCount: inspect.typedHooks.length,
@@ -676,6 +678,7 @@ export function registerPluginsCli(program: Command) {
676678
{ key: "Shape", header: "Shape", minWidth: 18 },
677679
{ key: "Capabilities", header: "Capabilities", minWidth: 28, flex: true },
678680
{ key: "Compatibility", header: "Compatibility", minWidth: 24, flex: true },
681+
{ key: "Bundle", header: "Bundle", minWidth: 14, flex: true },
679682
{ key: "Hooks", header: "Hooks", minWidth: 20, flex: true },
680683
],
681684
rows,
@@ -738,9 +741,9 @@ export function registerPluginsCli(program: Command) {
738741
lines.push(
739742
`${theme.muted("Legacy before_agent_start:")} ${inspect.usesLegacyBeforeAgentStart ? "yes" : "no"}`,
740743
);
741-
if ((inspect.plugin.bundleCapabilities?.length ?? 0) > 0) {
744+
if (inspect.bundleCapabilities.length > 0) {
742745
lines.push(
743-
`${theme.muted("Bundle capabilities:")} ${inspect.plugin.bundleCapabilities?.join(", ")}`,
746+
`${theme.muted("Bundle capabilities:")} ${inspect.bundleCapabilities.join(", ")}`,
744747
);
745748
}
746749
lines.push(
@@ -785,6 +788,14 @@ export function registerPluginsCli(program: Command) {
785788
lines.push(...formatInspectSection("CLI commands", inspect.cliCommands));
786789
lines.push(...formatInspectSection("Services", inspect.services));
787790
lines.push(...formatInspectSection("Gateway methods", inspect.gatewayMethods));
791+
lines.push(
792+
...formatInspectSection(
793+
"MCP servers",
794+
inspect.mcpServers.map((entry) =>
795+
entry.hasStdioTransport ? entry.name : `${entry.name} (unsupported transport)`,
796+
),
797+
),
798+
);
788799
if (inspect.httpRouteCount > 0) {
789800
lines.push(...formatInspectSection("HTTP routes", [String(inspect.httpRouteCount)]));
790801
}

src/plugins/bundle-mcp.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export type EnabledBundleMcpConfigResult = {
3232
};
3333
export type BundleMcpRuntimeSupport = {
3434
hasSupportedStdioServer: boolean;
35+
supportedServerNames: string[];
3536
unsupportedServerNames: string[];
3637
diagnostics: string[];
3738
};
@@ -279,17 +280,20 @@ export function inspectBundleMcpRuntimeSupport(params: {
279280
bundleFormat: PluginBundleFormat;
280281
}): BundleMcpRuntimeSupport {
281282
const loaded = loadBundleMcpConfig(params);
283+
const supportedServerNames: string[] = [];
282284
const unsupportedServerNames: string[] = [];
283285
let hasSupportedStdioServer = false;
284286
for (const [serverName, server] of Object.entries(loaded.config.mcpServers)) {
285287
if (typeof server.command === "string" && server.command.trim().length > 0) {
286288
hasSupportedStdioServer = true;
289+
supportedServerNames.push(serverName);
287290
continue;
288291
}
289292
unsupportedServerNames.push(serverName);
290293
}
291294
return {
292295
hasSupportedStdioServer,
296+
supportedServerNames,
293297
unsupportedServerNames,
294298
diagnostics: loaded.diagnostics,
295299
};

src/plugins/status.test.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,117 @@ describe("buildPluginStatusReport", () => {
493493
expect(buildPluginCompatibilityWarnings()).toEqual([]);
494494
});
495495

496+
it("populates bundleCapabilities from plugin record", () => {
497+
loadOpenClawPluginsMock.mockReturnValue({
498+
plugins: [
499+
{
500+
id: "claude-bundle",
501+
name: "Claude Bundle",
502+
description: "A bundle plugin with skills and commands",
503+
source: "/tmp/claude-bundle/.claude-plugin/plugin.json",
504+
origin: "workspace",
505+
enabled: true,
506+
status: "loaded",
507+
format: "bundle",
508+
bundleFormat: "claude",
509+
bundleCapabilities: ["skills", "commands", "agents", "settings"],
510+
rootDir: "/tmp/claude-bundle",
511+
toolNames: [],
512+
hookNames: [],
513+
channelIds: [],
514+
providerIds: [],
515+
speechProviderIds: [],
516+
mediaUnderstandingProviderIds: [],
517+
imageGenerationProviderIds: [],
518+
webSearchProviderIds: [],
519+
gatewayMethods: [],
520+
cliCommands: [],
521+
services: [],
522+
commands: [],
523+
httpRoutes: 0,
524+
hookCount: 0,
525+
configSchema: false,
526+
},
527+
],
528+
diagnostics: [],
529+
channels: [],
530+
channelSetups: [],
531+
providers: [],
532+
speechProviders: [],
533+
mediaUnderstandingProviders: [],
534+
imageGenerationProviders: [],
535+
webSearchProviders: [],
536+
tools: [],
537+
hooks: [],
538+
typedHooks: [],
539+
httpRoutes: [],
540+
gatewayHandlers: {},
541+
cliRegistrars: [],
542+
services: [],
543+
commands: [],
544+
});
545+
546+
const inspect = buildPluginInspectReport({ id: "claude-bundle" });
547+
548+
expect(inspect).not.toBeNull();
549+
expect(inspect?.bundleCapabilities).toEqual(["skills", "commands", "agents", "settings"]);
550+
expect(inspect?.mcpServers).toEqual([]);
551+
expect(inspect?.shape).toBe("non-capability");
552+
});
553+
554+
it("returns empty bundleCapabilities and mcpServers for non-bundle plugins", () => {
555+
loadOpenClawPluginsMock.mockReturnValue({
556+
plugins: [
557+
{
558+
id: "plain-plugin",
559+
name: "Plain Plugin",
560+
description: "A regular plugin",
561+
source: "/tmp/plain-plugin/index.ts",
562+
origin: "workspace",
563+
enabled: true,
564+
status: "loaded",
565+
toolNames: [],
566+
hookNames: [],
567+
channelIds: [],
568+
providerIds: ["plain"],
569+
speechProviderIds: [],
570+
mediaUnderstandingProviderIds: [],
571+
imageGenerationProviderIds: [],
572+
webSearchProviderIds: [],
573+
gatewayMethods: [],
574+
cliCommands: [],
575+
services: [],
576+
commands: [],
577+
httpRoutes: 0,
578+
hookCount: 0,
579+
configSchema: false,
580+
},
581+
],
582+
diagnostics: [],
583+
channels: [],
584+
channelSetups: [],
585+
providers: [],
586+
speechProviders: [],
587+
mediaUnderstandingProviders: [],
588+
imageGenerationProviders: [],
589+
webSearchProviders: [],
590+
tools: [],
591+
hooks: [],
592+
typedHooks: [],
593+
httpRoutes: [],
594+
gatewayHandlers: {},
595+
cliRegistrars: [],
596+
services: [],
597+
commands: [],
598+
});
599+
600+
const inspect = buildPluginInspectReport({ id: "plain-plugin" });
601+
602+
expect(inspect).not.toBeNull();
603+
expect(inspect?.bundleCapabilities).toEqual([]);
604+
expect(inspect?.mcpServers).toEqual([]);
605+
});
606+
496607
it("formats and summarizes compatibility notices", () => {
497608
const notice = {
498609
pluginId: "legacy-plugin",

src/plugins/status.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent
22
import { resolveDefaultAgentWorkspaceDir } from "../agents/workspace.js";
33
import { loadConfig } from "../config/config.js";
44
import { createSubsystemLogger } from "../logging/subsystem.js";
5+
import { inspectBundleMcpRuntimeSupport } from "./bundle-mcp.js";
56
import { normalizePluginsConfig } from "./config-state.js";
67
import { loadOpenClawPlugins } from "./loader.js";
78
import { createPluginLoaderLogger } from "./logger.js";
@@ -64,7 +65,12 @@ export type PluginInspectReport = {
6465
cliCommands: string[];
6566
services: string[];
6667
gatewayMethods: string[];
68+
mcpServers: Array<{
69+
name: string;
70+
hasStdioTransport: boolean;
71+
}>;
6772
httpRouteCount: number;
73+
bundleCapabilities: string[];
6874
diagnostics: PluginDiagnostic[];
6975
policy: {
7076
allowPromptInjection?: boolean;
@@ -226,6 +232,26 @@ export function buildPluginInspectReport(params: {
226232
httpRouteCount: plugin.httpRoutes,
227233
});
228234

235+
// Populate MCP server info for bundle-format plugins with a known rootDir.
236+
let mcpServers: PluginInspectReport["mcpServers"] = [];
237+
if (plugin.format === "bundle" && plugin.bundleFormat && plugin.rootDir) {
238+
const mcpSupport = inspectBundleMcpRuntimeSupport({
239+
pluginId: plugin.id,
240+
rootDir: plugin.rootDir,
241+
bundleFormat: plugin.bundleFormat,
242+
});
243+
mcpServers = [
244+
...mcpSupport.supportedServerNames.map((name) => ({
245+
name,
246+
hasStdioTransport: true,
247+
})),
248+
...mcpSupport.unsupportedServerNames.map((name) => ({
249+
name,
250+
hasStdioTransport: false,
251+
})),
252+
];
253+
}
254+
229255
const usesLegacyBeforeAgentStart = typedHooks.some(
230256
(entry) => entry.name === "before_agent_start",
231257
);
@@ -248,7 +274,9 @@ export function buildPluginInspectReport(params: {
248274
cliCommands: [...plugin.cliCommands],
249275
services: [...plugin.services],
250276
gatewayMethods: [...plugin.gatewayMethods],
277+
mcpServers,
251278
httpRouteCount: plugin.httpRoutes,
279+
bundleCapabilities: plugin.bundleCapabilities ?? [],
252280
diagnostics,
253281
policy: {
254282
allowPromptInjection: policyEntry?.hooks?.allowPromptInjection,

0 commit comments

Comments
 (0)