merge(sdk): intègre feature/296-unified-agent-capabilities — metadata statiques mcpServers[].tools #296 (QA verte: npm run check)

This commit is contained in:
2026-09-09 19:52:23 +02:00
5 changed files with 81 additions and 2 deletions

View File

@ -268,7 +268,16 @@ in the manifest, and that server advertises its own tools through the MCP
"args": [], "args": [],
"cwd": "${pluginRoot}", "cwd": "${pluginRoot}",
"transport": "stdio", "transport": "stdio",
"autoStart": true "autoStart": true,
"tools": [
{
"name": "unity_get_scene",
"description": "Read the active Unity scene."
},
{
"name": "unity_run_tests"
}
]
} }
] ]
} }
@ -288,6 +297,11 @@ MCP server contribution rules:
- A `command` that becomes absolute after `${pluginRoot}` or `${appDataDir}` - A `command` that becomes absolute after `${pluginRoot}` or `${appDataDir}`
substitution is allowed and is passed through as substituted. substitution is allowed and is passed through as substituted.
- `cwd` defaults to `${pluginRoot}` when omitted. - `cwd` defaults to `${pluginRoot}` when omitted.
- `tools` is optional static display metadata for IdeA's capability-assignment
UI. Each entry has an exact non-empty `name` and an optional non-empty
`description`; names must be unique within the server.
- This metadata does not replace or cache MCP `tools/list`, and declaring a tool
here does not register it or prove that the running server exposes it.
The tools an agent can call are assigned with the triplet The tools an agent can call are assigned with the triplet
`pluginId`/`serverId`/`toolName` in project plugin settings. `toolName` must `pluginId`/`serverId`/`toolName` in project plugin settings. `toolName` must

View File

@ -18,10 +18,11 @@
], ],
"scripts": { "scripts": {
"build": "tsc -p tsconfig.json", "build": "tsc -p tsconfig.json",
"test:manifest": "npm run build && node scripts/check-manifest-tools.mjs",
"build:hello-plugin": "tsc -p examples/hello-plugin/tsconfig.build.json", "build:hello-plugin": "tsc -p examples/hello-plugin/tsconfig.build.json",
"package:hello-plugin": "npm run build && npm run build:hello-plugin && node scripts/package-hello-plugin.mjs", "package:hello-plugin": "npm run build && npm run build:hello-plugin && node scripts/package-hello-plugin.mjs",
"typecheck:examples": "tsc -p examples/hello-plugin/tsconfig.json --noEmit", "typecheck:examples": "tsc -p examples/hello-plugin/tsconfig.json --noEmit",
"check": "npm run build && npm run typecheck:examples && npm run package:hello-plugin" "check": "npm run test:manifest && npm run typecheck:examples && npm run package:hello-plugin"
}, },
"keywords": [ "keywords": [
"idea", "idea",

View File

@ -0,0 +1,38 @@
import assert from "node:assert/strict";
import { validatePluginManifest } from "../dist/index.js";
const base = {
ideaPluginManifestVersion: 1,
id: "dev.example.tools",
displayName: "Tools",
version: "1.0.0",
main: "dist/index.js",
trustLevel: "full",
capabilities: ["mcp"],
contributes: {
mcpServers: [
{
id: "server",
displayName: "Server",
command: "bin/server",
transport: "stdio"
}
]
}
};
assert.equal(validatePluginManifest(base).success, true, "tools metadata remains optional");
const withTools = structuredClone(base);
withTools.contributes.mcpServers[0].tools = [
{ name: "read_scene", description: "Read the active scene." },
{ name: "run_tests" }
];
assert.equal(validatePluginManifest(withTools).success, true, "valid tools metadata is accepted");
const duplicate = structuredClone(withTools);
duplicate.contributes.mcpServers[0].tools.push({ name: "read_scene" });
const duplicateResult = validatePluginManifest(duplicate);
assert.equal(duplicateResult.success, false, "duplicate tool metadata is rejected");
assert.ok(duplicateResult.errors.some((error) => error.includes("must be unique")));

View File

@ -5,6 +5,7 @@ export type {
IdeAPluginEngineConstraints, IdeAPluginEngineConstraints,
IdeAPluginLayoutContribution, IdeAPluginLayoutContribution,
IdeAPluginMcpServerContribution, IdeAPluginMcpServerContribution,
IdeAPluginMcpToolMetadata,
IdeAPluginMenuItemContribution, IdeAPluginMenuItemContribution,
IdeAPluginSkillContribution, IdeAPluginSkillContribution,
IdeAPluginSkillKind, IdeAPluginSkillKind,

View File

@ -95,6 +95,15 @@ export interface IdeAPluginMcpServerContribution {
transport: "stdio"; transport: "stdio";
autoStart?: boolean; autoStart?: boolean;
allowAbsoluteCommand?: boolean; allowAbsoluteCommand?: boolean;
/** Optional static UI metadata; runtime MCP tools/list remains authoritative. */
tools?: IdeAPluginMcpToolMetadata[];
}
export interface IdeAPluginMcpToolMetadata {
/** Exact tool name advertised by the MCP server. */
name: string;
/** Optional human-readable summary for capability-management UI. */
description?: string;
} }
export type PluginManifestValidationResult = export type PluginManifestValidationResult =
@ -269,6 +278,22 @@ function validateContributes(value: unknown, errors: string[]): void {
optionalString(server, "cwd", errors, `contributes.mcpServers[${index}].cwd`); optionalString(server, "cwd", errors, `contributes.mcpServers[${index}].cwd`);
optionalBoolean(server, "autoStart", errors, `contributes.mcpServers[${index}].autoStart`); optionalBoolean(server, "autoStart", errors, `contributes.mcpServers[${index}].autoStart`);
optionalBoolean(server, "allowAbsoluteCommand", errors, `contributes.mcpServers[${index}].allowAbsoluteCommand`); optionalBoolean(server, "allowAbsoluteCommand", errors, `contributes.mcpServers[${index}].allowAbsoluteCommand`);
validateArray(server, "tools", errors, (tool, toolIndex) => {
requireString(tool, "name", errors, `contributes.mcpServers[${index}].tools[${toolIndex}].name`);
if (tool.description !== undefined) {
requireString(tool, "description", errors, `contributes.mcpServers[${index}].tools[${toolIndex}].description`);
}
});
if (Array.isArray(server.tools)) {
const names = new Set<string>();
server.tools.forEach((tool, toolIndex) => {
if (!isRecord(tool) || typeof tool.name !== "string" || tool.name.trim() === "") return;
if (names.has(tool.name)) {
errors.push(`contributes.mcpServers[${index}].tools[${toolIndex}].name must be unique within the server`);
}
names.add(tool.name);
});
}
}); });
} }