merge(sdk): intègre feature/296-unified-agent-capabilities — metadata statiques mcpServers[].tools #296 (QA verte: npm run check)
This commit is contained in:
@ -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
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
38
scripts/check-manifest-tools.mjs
Normal file
38
scripts/check-manifest-tools.mjs
Normal 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")));
|
||||||
@ -5,6 +5,7 @@ export type {
|
|||||||
IdeAPluginEngineConstraints,
|
IdeAPluginEngineConstraints,
|
||||||
IdeAPluginLayoutContribution,
|
IdeAPluginLayoutContribution,
|
||||||
IdeAPluginMcpServerContribution,
|
IdeAPluginMcpServerContribution,
|
||||||
|
IdeAPluginMcpToolMetadata,
|
||||||
IdeAPluginMenuItemContribution,
|
IdeAPluginMenuItemContribution,
|
||||||
IdeAPluginSkillContribution,
|
IdeAPluginSkillContribution,
|
||||||
IdeAPluginSkillKind,
|
IdeAPluginSkillKind,
|
||||||
|
|||||||
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user