Guided-auth removal, OAuth smoke testing, and TUI/CLI support (#1531)
* 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>
This commit is contained in:
parent
4a927d3978
commit
13dafe457b
2 changed files with 5702 additions and 4444 deletions
|
|
@ -88,3 +88,22 @@ export function createInvalidConfig(): string {
|
|||
export function deleteConfigFile(configPath: string): void {
|
||||
cleanupTempDir(path.dirname(configPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a temporary install-level client.json for --client-config tests.
|
||||
*/
|
||||
export function createClientConfigFile(
|
||||
config: Record<string, unknown>,
|
||||
): string {
|
||||
const tempDir = createTempDir("mcp-inspector-client-config-");
|
||||
const configPath = path.join(tempDir, "client.json");
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
return configPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a client config file and its containing directory
|
||||
*/
|
||||
export function deleteClientConfigFile(configPath: string): void {
|
||||
cleanupTempDir(path.dirname(configPath));
|
||||
}
|
||||
|
|
|
|||
188
clients/cli/__tests__/oauth-runner.test.ts
Normal file
188
clients/cli/__tests__/oauth-runner.test.ts
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue