* refactor(web): update OAuth E2E tests to use new authentication method - Replaced `runGuidedAuth` with `authenticate` in the InspectorClient OAuth end-to-end tests to support quick authentication with CIMD pre-registration. - Updated comments to reflect the change in authentication method and its support for HTTP test metadata URLs. - Ensured that the tests continue to validate the expected authorization URL after the change. Additionally, imported `ensureCimdClientRegistration` in the OAuthManager to streamline client registration during the authentication process. * Guided-auth removal, OAuth smoke testing, and TUI/CLI support * Added CLI OAuth runner flags tests * Added clarifying comment on port 0 binding * README notes on TUI oauth callback port, prettier fixes for CLI tests * CIMD validation uses inline like EMA IdP * Fixed TUI test failures from merge * test: bring 5 OAuth files over the 90 per-file coverage gate The guided-auth-removal work added/touched several files that fell below the repo's >=90 per-file coverage gate (npm run coverage). CI doesn't run the gate (it's local-only), so validate + test:integration passed while these gaps slipped through. Bring them all back over the line: - core/auth/node/runner-oauth-callback.ts: test bad-URL/non-http-scheme throws, no-port->80 default, and IPv6 hostname bracketing. Annotate the provably-dead guards (empty host, out-of-range port, String(err) fallback, pathname "/" fallback) -- all rejected by new URL() upstream -- with justified v8 ignore comments instead of lowering the gate. - core/auth/cimd.ts: test the AS-doesn't-support-CIMD early return. - core/react/useClientSettingsDraft.ts: test the functional-updater path, the stale-onChange-after-close prev===null guard, and the timer-fires-after-close value===null guard. - ClientSettingsForm.tsx: test the CIMD checkbox toggle and CIMD URL clear. - clients/tui/src/utils/oauthDisplay.ts: test static/dcr registration kinds, the expired IdP session, and the configured-scope/empty-scope branches of formatScopes. npm run coverage now passes (exit 0) across web, cli, tui, launcher. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: cliffhall <cliff@futurescale.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
188 lines
5.1 KiB
TypeScript
188 lines
5.1 KiB
TypeScript
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
import * as runner from "@inspector/core/client/runner.js";
|
|
import {
|
|
createTestServerHttp,
|
|
createEchoTool,
|
|
createTestServerInfo,
|
|
} from "@modelcontextprotocol/inspector-test-server";
|
|
import { runCli } from "./helpers/cli-runner.js";
|
|
import {
|
|
expectCliFailure,
|
|
expectCliSuccess,
|
|
expectOutputContains,
|
|
} from "./helpers/assertions.js";
|
|
import {
|
|
createClientConfigFile,
|
|
deleteClientConfigFile,
|
|
} from "./helpers/fixtures.js";
|
|
|
|
/**
|
|
* Covers OAuth runner flag wiring in `src/cli.ts` (#1514): --client-config,
|
|
* --client-id/--client-secret/--client-metadata-url, and --callback-url /
|
|
* MCP_OAUTH_CALLBACK_URL. Shared auth logic is unit-tested in
|
|
* clients/web/src/test/core/client/runner.test.ts; these assert the CLI
|
|
* parses flags and passes them through on HTTP (OAuth-capable) connects.
|
|
*/
|
|
describe("CLI OAuth runner flags", () => {
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("connects over HTTP with OAuth CLI overrides and custom callback URL", async () => {
|
|
const server = createTestServerHttp({
|
|
serverInfo: createTestServerInfo(),
|
|
tools: [createEchoTool()],
|
|
});
|
|
const buildSpy = vi.spyOn(runner, "buildRunnerClientAuthOptions");
|
|
|
|
try {
|
|
await server.start();
|
|
const result = await runCli([
|
|
server.url,
|
|
"--cli",
|
|
"--method",
|
|
"tools/list",
|
|
"--transport",
|
|
"http",
|
|
"--client-id",
|
|
"test-client-id",
|
|
"--client-secret",
|
|
"test-client-secret",
|
|
"--client-metadata-url",
|
|
"https://example.com/oauth/client-metadata.json",
|
|
"--callback-url",
|
|
"http://127.0.0.1:9999/oauth/callback",
|
|
]);
|
|
|
|
expectCliSuccess(result);
|
|
expect(buildSpy).toHaveBeenCalled();
|
|
expect(buildSpy.mock.calls[0]?.[2]).toEqual({
|
|
clientId: "test-client-id",
|
|
clientSecret: "test-client-secret",
|
|
clientMetadataUrl: "https://example.com/oauth/client-metadata.json",
|
|
});
|
|
} finally {
|
|
await server.stop();
|
|
}
|
|
});
|
|
|
|
it("loads install client.json from --client-config and prefers CLI CIMD override", async () => {
|
|
const server = createTestServerHttp({
|
|
serverInfo: createTestServerInfo(),
|
|
tools: [createEchoTool()],
|
|
});
|
|
const buildSpy = vi.spyOn(runner, "buildRunnerClientAuthOptions");
|
|
const clientConfigPath = createClientConfigFile({
|
|
cimd: {
|
|
enabled: true,
|
|
clientMetadataUrl: "https://example.com/from-client-json.json",
|
|
},
|
|
});
|
|
|
|
try {
|
|
await server.start();
|
|
const result = await runCli([
|
|
server.url,
|
|
"--cli",
|
|
"--method",
|
|
"tools/list",
|
|
"--transport",
|
|
"http",
|
|
"--client-config",
|
|
clientConfigPath,
|
|
"--client-metadata-url",
|
|
"https://example.com/from-cli-flag.json",
|
|
]);
|
|
|
|
expectCliSuccess(result);
|
|
expect(buildSpy).toHaveBeenCalled();
|
|
expect(buildSpy.mock.calls[0]?.[0]).toMatchObject({
|
|
cimd: {
|
|
enabled: true,
|
|
clientMetadataUrl: "https://example.com/from-client-json.json",
|
|
},
|
|
});
|
|
expect(buildSpy.mock.calls[0]?.[2]).toEqual({
|
|
clientMetadataUrl: "https://example.com/from-cli-flag.json",
|
|
});
|
|
} finally {
|
|
await server.stop();
|
|
deleteClientConfigFile(clientConfigPath);
|
|
}
|
|
});
|
|
|
|
it("accepts port 0 callback URL for ephemeral listener binding", async () => {
|
|
const server = createTestServerHttp({
|
|
serverInfo: createTestServerInfo(),
|
|
tools: [createEchoTool()],
|
|
});
|
|
|
|
try {
|
|
await server.start();
|
|
const result = await runCli([
|
|
server.url,
|
|
"--cli",
|
|
"--method",
|
|
"tools/list",
|
|
"--transport",
|
|
"http",
|
|
"--callback-url",
|
|
"http://127.0.0.1:0/oauth/callback",
|
|
]);
|
|
|
|
expectCliSuccess(result);
|
|
} finally {
|
|
await server.stop();
|
|
}
|
|
});
|
|
|
|
it("uses MCP_OAUTH_CALLBACK_URL when --callback-url is absent", async () => {
|
|
const server = createTestServerHttp({
|
|
serverInfo: createTestServerInfo(),
|
|
tools: [createEchoTool()],
|
|
});
|
|
|
|
try {
|
|
await server.start();
|
|
const result = await runCli(
|
|
[server.url, "--cli", "--method", "tools/list", "--transport", "http"],
|
|
{
|
|
env: {
|
|
MCP_OAUTH_CALLBACK_URL:
|
|
"http://127.0.0.1:8888/custom/oauth/callback",
|
|
},
|
|
},
|
|
);
|
|
|
|
expectCliSuccess(result);
|
|
} finally {
|
|
await server.stop();
|
|
}
|
|
});
|
|
|
|
it("rejects an invalid OAuth callback URL before connecting", async () => {
|
|
const server = createTestServerHttp({
|
|
serverInfo: createTestServerInfo(),
|
|
tools: [createEchoTool()],
|
|
});
|
|
|
|
try {
|
|
await server.start();
|
|
const result = await runCli([
|
|
server.url,
|
|
"--cli",
|
|
"--method",
|
|
"tools/list",
|
|
"--transport",
|
|
"http",
|
|
"--callback-url",
|
|
"not-a-valid-url",
|
|
]);
|
|
|
|
expectCliFailure(result);
|
|
expectOutputContains(result, "Invalid OAuth callback URL");
|
|
} finally {
|
|
await server.stop();
|
|
}
|
|
});
|
|
});
|