Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion docs/step-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,19 @@ markers do not suppress this repair. Healthy executables in `~/.local/bin` can
be used even when the current shell's PATH lacks that directory. Failed setup is
retried on the next session; `step mcp prepare` also retries without requiring
sign-in. The server name combines the plugin ID and its server name with `__`.
Automatic StepPage installation is supported on macOS/Linux, including WSL.
Automatic StepPage installation is supported on macOS, Linux, Windows, and WSL.
On Windows the published bundle is downloaded directly and wrapped in a
`steppage-mcp.cmd` launcher under `~/.local/bin`, because the POSIX installer
script cannot run there; `step mcp prepare` probes that wrapper through
`cmd /c` so no shell PATH change is required.
Windows resolves `node.exe`, `cmd.exe`, and `curl.exe` only from absolute PATH
entries, so the working directory cannot supply those executables. The launcher
uses the same `node.exe` that passed the Node.js >= 20 check.
`STEPCODE_STEPPAGE_INSTALLER_URL` can select a channel or mirror. Windows derives
the sibling `latest/steppage-mcp.mjs` path with URL parsing and preserves the
configured URL's authentication parameters and encoding.
Setup errors and install hints redact URL credentials and token-like query
parameters before they are shown.

Preparation preserves credentials, custom server commands, global overrides,
disabled servers, and intentionally uninstalled plugins. A global declaration
Expand Down
29 changes: 29 additions & 0 deletions packages/coding-agent/src/step/mcp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,33 @@ test("login guidance quotes server names containing shell syntax", () => {
expect(message).toContain("step mcp login 'team'\\''s mcp'");
});

test("missing StepPage executable hint does not print mirror credentials", () => {
const message = describeMcpStartFailure({
name: "steppage__steppage",
command: "steppage-mcp",
provision: { command: "steppage-mcp", installer: "steppageInstaller" },
error: Object.assign(new Error("spawn steppage-mcp ENOENT"), { code: "ENOENT" }),
platform: "linux",
env: { STEPCODE_STEPPAGE_INSTALLER_URL: "https://alice:hunter2@mirror.example/install.sh?token=abc123" },
});
expect(message).toContain("curl -fsSL 'https://[redacted]@mirror.example/install.sh?token=[redacted]' | sh");
for (const secret of ["alice", "hunter2", "abc123"]) expect(message).not.toContain(secret);
});

test("Windows missing StepPage executable hint points to native preparation", () => {
const message = describeMcpStartFailure({
name: "steppage__steppage",
command: "steppage-mcp",
provision: { command: "steppage-mcp", installer: "steppageInstaller" },
error: Object.assign(new Error("spawn steppage-mcp ENOENT"), { code: "ENOENT" }),
platform: "win32",
env: { STEPCODE_STEPPAGE_INSTALLER_URL: "https://alice:hunter2@mirror.example/install.sh?token=abc123" },
});
expect(message).toContain("step mcp prepare");
expect(message).not.toContain("| sh");
for (const secret of ["alice", "hunter2", "abc123"]) expect(message).not.toContain(secret);
});

afterEach(async () => {
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
});
Expand Down Expand Up @@ -137,6 +164,7 @@ test("a missing executable is reported with the installer command instead of a r
error: Object.assign(new Error("spawn steppage-mcp ENOENT"), {
code: "ENOENT",
}),
platform: "linux",
env: {},
});

Expand All @@ -154,6 +182,7 @@ test("the installer override is honoured in the guidance", () => {
error: Object.assign(new Error("spawn steppage-mcp ENOENT"), {
code: "ENOENT",
}),
platform: "linux",
env: { STEPCODE_STEPPAGE_INSTALLER_URL: "https://example.test/install.sh" },
});

Expand Down
16 changes: 13 additions & 3 deletions packages/coding-agent/src/step/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
readStepPluginManifest,
type StepPluginProvision,
} from "./plugins.ts";
import { ensureStepPageReady, isManagedStepPageCommand } from "./steppage-provision.ts";
import { ensureStepPageReady, isManagedStepPageCommand, redactStepPageSecrets } from "./steppage-provision.ts";
import { STEPCODE_VERSION } from "./version.ts";

export { resolveStepMcpEnvironment } from "./mcp-environment.ts";
Expand Down Expand Up @@ -694,6 +694,7 @@ export function describeMcpStartFailure(input: {
provision?: StepPluginProvision;
error: unknown;
env?: NodeJS.ProcessEnv;
platform?: NodeJS.Platform;
}): string {
const detail = input.error instanceof Error ? input.error.message : String(input.error);
if (
Expand All @@ -708,8 +709,17 @@ export function describeMcpStartFailure(input: {
return `MCP server '${input.name}' could not start: ${detail}\nAuthenticate with: step mcp login ${quoted}, then restart Step.`;
}
if (!isMissingExecutable(input.error)) return `MCP server '${input.name}' could not start: ${detail}`;
const install = input.provision ? provisionInstallCommand(input.provision, input.env ?? process.env) : undefined;
const remedy = install ? `Install it with: ${install}, then restart Step.` : "Install it, then restart Step.";
const nativeWindowsPreparation =
(input.platform ?? process.platform) === "win32" && input.provision?.installer === "steppageInstaller";
const install =
input.provision && !nativeWindowsPreparation
? provisionInstallCommand(input.provision, input.env ?? process.env)
: undefined;
const remedy = nativeWindowsPreparation
? "Run: step mcp prepare, then restart Step."
: install
? `Install it with: ${redactStepPageSecrets(install, input.env ?? process.env)}, then restart Step.`
: "Install it, then restart Step.";
return `MCP server '${input.name}' could not start: '${input.command}' is not installed or not on PATH. ${remedy}`;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/coding-agent/src/step/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,7 @@ export function provisionInstallCommand(

/** Install the executable declared by a built-in plugin, when it is missing. */
export async function provisionBuiltinPlugin(provision: StepPluginProvision): Promise<string | undefined> {
if (provision.command !== "steppage-mcp" || process.platform === "win32") {
if (provision.command !== "steppage-mcp") {
return undefined;
}
const install = provisionInstallCommand(provision);
Expand Down
Loading
Loading