diff --git a/docs/manifest.md b/docs/manifest.md index 892bb96..657b736 100644 --- a/docs/manifest.md +++ b/docs/manifest.md @@ -283,8 +283,10 @@ MCP server contribution rules: - `autoStart: true` is required for IdeA to start the server during plugin MCP reconciliation. Non-auto-start servers are manifest metadata only today. - A relative `command` is resolved under the installed plugin package root. -- An absolute `command` is rejected unless `allowAbsoluteCommand: true` is set. - Use this only for an intentional dependency on a host binary. +- A literal absolute `command` is rejected unless `allowAbsoluteCommand: true` + is set. Use this only for an intentional dependency on a host binary. +- A `command` that becomes absolute after `${pluginRoot}` or `${appDataDir}` + substitution is allowed and is passed through as substituted. - `cwd` defaults to `${pluginRoot}` when omitted. The tools an agent can call are assigned with the triplet @@ -307,16 +309,56 @@ declared MCP server. In `command`, `args`, `env` and `cwd`, the host expands: - `${pluginRoot}` to the installed plugin package root. - `${appDataDir}` to the host-owned application data directory. +There is currently no `${node}`, `${runtimeNode}`, `${hostNode}` substitution +and no `runtime` field. IdeA does not provide a plugin-scoped Node.js runtime +for MCP servers. + This string substitution is limited to manifest-declared MCP server startup. Runtime handlers receive the same installed package location separately as `ctx.pluginRoot`; `ctx.services.tasks.runCommand()` does not perform placeholder substitution, and workspace APIs remain project-confined. -Prefer a packaged executable script for plugin-owned MCP servers, for example -`command: "scripts/unity-mcp-server.mjs"` with an appropriate shebang and file -mode. If the server must be launched through a host executable such as Node, -use an absolute command with `allowAbsoluteCommand: true` and keep plugin-owned -paths in `args`, for example `${pluginRoot}/scripts/unity-mcp-server.mjs`. +`command` is not shell syntax. IdeA starts it directly and passes `args` +separately. Do not rely on shell startup files, shell aliases, or a user's login +shell `PATH`. + +Prefer a packaged executable for plugin-owned MCP servers: + +```json +{ + "command": "scripts/unity-mcp-server", + "transport": "stdio", + "autoStart": true +} +``` + +Because the command is package-relative, IdeA resolves it to +`${pluginRoot}/scripts/unity-mcp-server`. If this file is a script, its shebang +and executable bit must be valid in the installed package. A JavaScript shebang +such as `#!/usr/bin/env node` still depends on a `node` binary visible to the +IdeA app process, not necessarily the user's interactive shell. + +For JavaScript MCP servers that must not depend on the user's shell `PATH`, use +one of these approaches: + +- Ship an executable server that includes its runtime, or install a managed + runtime under plugin/app-owned storage and point `command` at that executable + with `${pluginRoot}` or `${appDataDir}`. +- Declare an explicit host dependency with an absolute command such as + `/usr/bin/node`, set `allowAbsoluteCommand: true`, and keep plugin-owned + script paths in `args`. + +Avoid `command: "node"` for a packaged MCP server. With +`allowAbsoluteCommand: false`, it is treated as package-relative and resolves to +`${pluginRoot}/node`. With `allowAbsoluteCommand: true`, it is passed as a host +command name and depends on the environment inherited by IdeA, which may differ +from an interactive shell. + +The same rule applies to shell wrappers. `command: "bash"` resolves to +`${pluginRoot}/bash` unless absolute-command handling is explicitly enabled. Use +a package-relative wrapper executable, or use an absolute shell such as +`/bin/bash` with `allowAbsoluteCommand: true` when that host dependency is +intentional. ## Validation diff --git a/docs/packaging-distribution.md b/docs/packaging-distribution.md index 71c21eb..e774e5b 100644 --- a/docs/packaging-distribution.md +++ b/docs/packaging-distribution.md @@ -43,6 +43,25 @@ IdeA resolves React/ReactDOM bare imports to the host instance. Other bare dependencies are not host-resolved. Bundle or vendor third-party dependencies other than React/ReactDOM into package-relative files. +## Packaged Executables And Runtimes + +Manifest-declared MCP servers are launched from `contributes.mcpServers` by +starting the declared `command` directly. IdeA does not inject a shell and does +not provide a plugin-scoped Node.js runtime. + +For a packaged MCP server, prefer a package-relative executable such as +`scripts/my-plugin-mcp-server`. The installed file must keep the executable bit +and, when it is a script, a valid shebang. A JavaScript server with +`#!/usr/bin/env node` still requires `node` in the environment inherited by the +IdeA app process; this is not guaranteed to match the user's interactive shell, +especially when Node is provided by shell startup tooling. + +For reproducible JavaScript MCP servers, either ship/install a runtime under the +plugin package or app-owned storage and point the manifest at that executable, +or declare an explicit absolute host dependency with `allowAbsoluteCommand: +true`. Avoid relying on a bare `command: "node"` in distributed or multi-harness +plugins. + ## TypeScript Build The hello plugin uses plain `tsc`: diff --git a/docs/services.md b/docs/services.md index 282a93b..5db0ec7 100644 --- a/docs/services.md +++ b/docs/services.md @@ -42,7 +42,8 @@ path under the project root. The host does not implicitly resolve `command` or `args` against the plugin package. Use the absolute `ctx.pluginRoot` supplied at activation time to construct an explicit path to a packaged script, and pass that path as `command` or as an argument to its interpreter. Treat package files -as read-only. +as read-only. Unlike manifest-declared MCP servers, `runCommand()` does not +substitute `${pluginRoot}` or `${appDataDir}`. ```ts const script = `${ctx.pluginRoot.replace(/[\\/]+$/, "")}/scripts/check.mjs`; @@ -56,6 +57,12 @@ await ctx.services.tasks.runCommand({ }); ``` +This task example uses `node` as a host executable visible to the task +environment. That is not the contract for `contributes.mcpServers`: MCP server +`command` values are resolved by the plugin manifest rules documented in +[Manifest](manifest.md), where a bare `command: "node"` is package-relative +unless external host-command handling is explicitly enabled. + ## Tooling `services.tooling.diagnose()` checks executables, environment values and files