From daa863ddfcd38c61a2d4dddfea574571d0f26b02 Mon Sep 17 00:00:00 2001 From: Ryan Clark Date: Sun, 1 Jun 2025 13:27:16 -0500 Subject: [PATCH 01/48] feat: add transport option to cli --- cli/src/index.ts | 48 +++++++++++++++++++++++++++++++++++++++++--- cli/src/transport.ts | 20 ++++++++++++++++-- 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/cli/src/index.ts b/cli/src/index.ts index bda1e8f7..5d5dcf8b 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -29,9 +29,13 @@ type Args = { logLevel?: LogLevel; toolName?: string; toolArg?: Record; + transport?: "sse" | "stdio" | "http"; }; -function createTransportOptions(target: string[]): TransportOptions { +function createTransportOptions( + target: string[], + transport?: "sse" | "stdio" | "http", +): TransportOptions { if (target.length === 0) { throw new Error( "Target is required. Specify a URL or a command to execute.", @@ -50,8 +54,30 @@ function createTransportOptions(target: string[]): TransportOptions { throw new Error("Arguments cannot be passed to a URL-based MCP server."); } + let transportType: "sse" | "stdio" | "http"; + if (transport) { + if (!isUrl && transport !== "stdio") { + throw new Error("Only stdio transport can be used with local commands."); + } + if (isUrl && transport === "stdio") { + throw new Error("stdio transport cannot be used with URLs."); + } + transportType = transport; + } else if (isUrl) { + const url = new URL(command); + if (url.pathname.endsWith("/mcp")) { + transportType = "http"; + } else if (url.pathname.endsWith("/sse")) { + transportType = "sse"; + } else { + transportType = "sse"; + } + } else { + transportType = "stdio"; + } + return { - transportType: isUrl ? "sse" : "stdio", + transportType, command: isUrl ? undefined : command, args: isUrl ? undefined : commandArgs, url: isUrl ? command : undefined, @@ -59,7 +85,7 @@ function createTransportOptions(target: string[]): TransportOptions { } async function callMethod(args: Args): Promise { - const transportOptions = createTransportOptions(args.target); + const transportOptions = createTransportOptions(args.target, args.transport); const transport = createTransport(transportOptions); const client = new Client({ name: "inspector-cli", @@ -214,6 +240,22 @@ function parseArgs(): Args { return value as LogLevel; }, + ) + // + // Transport options + // + .option( + "--transport ", + "Transport type (sse, http, or stdio). Auto-detected from URL: /mcp → http, /sse → sse, commands → stdio", + (value: string) => { + const validTransports = ["sse", "http", "stdio"]; + if (!validTransports.includes(value)) { + throw new Error( + `Invalid transport type: ${value}. Valid types are: ${validTransports.join(", ")}`, + ); + } + return value as "sse" | "http" | "stdio"; + }, ); // Parse only the arguments before -- diff --git a/cli/src/transport.ts b/cli/src/transport.ts index e693f246..e0d67b4e 100644 --- a/cli/src/transport.ts +++ b/cli/src/transport.ts @@ -3,11 +3,12 @@ import { getDefaultEnvironment, StdioClientTransport, } from "@modelcontextprotocol/sdk/client/stdio.js"; +import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; import { findActualExecutable } from "spawn-rx"; export type TransportOptions = { - transportType: "sse" | "stdio"; + transportType: "sse" | "stdio" | "http"; command?: string; args?: string[]; url?: string; @@ -15,11 +16,22 @@ export type TransportOptions = { function createSSETransport(options: TransportOptions): Transport { const baseUrl = new URL(options.url ?? ""); - const sseUrl = new URL("/sse", baseUrl); + const sseUrl = baseUrl.pathname.endsWith("/sse") + ? baseUrl + : new URL("/sse", baseUrl); return new SSEClientTransport(sseUrl); } +function createHTTPTransport(options: TransportOptions): Transport { + const baseUrl = new URL(options.url ?? ""); + const mcpUrl = baseUrl.pathname.endsWith("/mcp") + ? baseUrl + : new URL("/mcp", baseUrl); + + return new StreamableHTTPClientTransport(mcpUrl); +} + function createStdioTransport(options: TransportOptions): Transport { let args: string[] = []; @@ -67,6 +79,10 @@ export function createTransport(options: TransportOptions): Transport { return createSSETransport(options); } + if (transportType === "http") { + return createHTTPTransport(options); + } + throw new Error(`Unsupported transport type: ${transportType}`); } catch (error) { throw new Error( From 5b674cfe351b76f976bef8bac03f6619167d14a2 Mon Sep 17 00:00:00 2001 From: Ryan Clark Date: Sun, 1 Jun 2025 13:44:19 -0500 Subject: [PATCH 02/48] feat: update README with example for http transport --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d5017dc..56b4f59b 100644 --- a/README.md +++ b/README.md @@ -252,9 +252,12 @@ npx @modelcontextprotocol/inspector --cli node build/index.js --method resources # List available prompts npx @modelcontextprotocol/inspector --cli node build/index.js --method prompts/list -# Connect to a remote MCP server +# Connect to a remote MCP server (default is SSE transport) npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com +# Connect to a remote MCP server (with Streamable HTTP transport) +npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --transport http + # Call a tool on a remote server npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --method tools/call --tool-name remotetool --tool-arg param=value From 226ad251bdc046bbe945702d253a18a819f1c3aa Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 27 May 2025 18:33:05 -0700 Subject: [PATCH 03/48] Add a Playwright e2e test --- client/e2e/transport-type-dropdown.spec.ts | 22 ++++++++ package-lock.json | 64 ++++++++++++++++++++++ package.json | 1 + 3 files changed, 87 insertions(+) create mode 100644 client/e2e/transport-type-dropdown.spec.ts diff --git a/client/e2e/transport-type-dropdown.spec.ts b/client/e2e/transport-type-dropdown.spec.ts new file mode 100644 index 00000000..003420d2 --- /dev/null +++ b/client/e2e/transport-type-dropdown.spec.ts @@ -0,0 +1,22 @@ +import { test, expect } from '@playwright/test'; + +// Adjust the URL if your dev server runs on a different port +const APP_URL = 'http://localhost:6274/'; + +test.describe('Transport Type Dropdown', () => { + test('should have options for STDIO, SSE, and Streamable HTTP', async ({ page }) => { + await page.goto(APP_URL); + + // Wait for the Transport Type dropdown to be visible + const selectTrigger = page.getByLabel('Transport Type'); + await expect(selectTrigger).toBeVisible(); + + // Open the dropdown + await selectTrigger.click(); + + // Check for the three options + await expect(page.getByRole('option', { name: 'STDIO' })).toBeVisible(); + await expect(page.getByRole('option', { name: 'SSE' })).toBeVisible(); + await expect(page.getByRole('option', { name: 'Streamable HTTP' })).toBeVisible(); + }); +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 846d2eaa..25a242fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "mcp-inspector": "cli/build/cli.js" }, "devDependencies": { + "@playwright/test": "^1.52.0", "@types/jest": "^29.5.14", "@types/node": "^22.7.5", "@types/shell-quote": "^1.7.5", @@ -2070,6 +2071,22 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz", + "integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@radix-ui/number": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", @@ -8519,6 +8536,53 @@ "node": ">=8" } }, + "node_modules/playwright": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", + "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", + "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", diff --git a/package.json b/package.json index 8742cc8b..0e5cc4c2 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "zod": "^3.23.8" }, "devDependencies": { + "@playwright/test": "^1.52.0", "@types/jest": "^29.5.14", "@types/node": "^22.7.5", "@types/shell-quote": "^1.7.5", From 69da70acb7316a04baa811f740c3b8d08063644f Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 27 May 2025 18:35:22 -0700 Subject: [PATCH 04/48] npm run prettier-fix --- client/e2e/transport-type-dropdown.spec.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/client/e2e/transport-type-dropdown.spec.ts b/client/e2e/transport-type-dropdown.spec.ts index 003420d2..7ca55655 100644 --- a/client/e2e/transport-type-dropdown.spec.ts +++ b/client/e2e/transport-type-dropdown.spec.ts @@ -1,22 +1,26 @@ -import { test, expect } from '@playwright/test'; +import { test, expect } from "@playwright/test"; // Adjust the URL if your dev server runs on a different port -const APP_URL = 'http://localhost:6274/'; +const APP_URL = "http://localhost:6274/"; -test.describe('Transport Type Dropdown', () => { - test('should have options for STDIO, SSE, and Streamable HTTP', async ({ page }) => { +test.describe("Transport Type Dropdown", () => { + test("should have options for STDIO, SSE, and Streamable HTTP", async ({ + page, + }) => { await page.goto(APP_URL); // Wait for the Transport Type dropdown to be visible - const selectTrigger = page.getByLabel('Transport Type'); + const selectTrigger = page.getByLabel("Transport Type"); await expect(selectTrigger).toBeVisible(); // Open the dropdown await selectTrigger.click(); // Check for the three options - await expect(page.getByRole('option', { name: 'STDIO' })).toBeVisible(); - await expect(page.getByRole('option', { name: 'SSE' })).toBeVisible(); - await expect(page.getByRole('option', { name: 'Streamable HTTP' })).toBeVisible(); + await expect(page.getByRole("option", { name: "STDIO" })).toBeVisible(); + await expect(page.getByRole("option", { name: "SSE" })).toBeVisible(); + await expect( + page.getByRole("option", { name: "Streamable HTTP" }), + ).toBeVisible(); }); -}); \ No newline at end of file +}); From 8a13526c8b24c12f169152675b9754c7d8d9f6cd Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 27 May 2025 18:38:20 -0700 Subject: [PATCH 05/48] Exclude e2e from Jest --- client/jest.config.cjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/jest.config.cjs b/client/jest.config.cjs index b4d81cfc..8652eda5 100644 --- a/client/jest.config.cjs +++ b/client/jest.config.cjs @@ -21,6 +21,7 @@ module.exports = { "/node_modules/", "/dist/", "/bin/", + "/e2e/", "\\.config\\.(js|ts|cjs|mjs)$", ], // Exclude the same patterns from coverage reports @@ -28,6 +29,7 @@ module.exports = { "/node_modules/", "/dist/", "/bin/", + "/e2e/", "\\.config\\.(js|ts|cjs|mjs)$", ], }; From 8babc44f0b04a6037deee2de205710eda7c3b023 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 27 May 2025 18:43:29 -0700 Subject: [PATCH 06/48] Make `npm run test:e2e` work --- client/package.json | 3 ++- package.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/client/package.json b/client/package.json index daf549fc..9eb49574 100644 --- a/client/package.json +++ b/client/package.json @@ -20,7 +20,8 @@ "lint": "eslint .", "preview": "vite preview --port 6274", "test": "jest --config jest.config.cjs", - "test:watch": "jest --config jest.config.cjs --watch" + "test:watch": "jest --config jest.config.cjs --watch", + "test:e2e": "playwright test e2e" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", diff --git a/package.json b/package.json index 0e5cc4c2..1e7a931e 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,8 @@ "prettier-check": "prettier --check .", "lint": "prettier --check . && cd client && npm run lint", "prepare": "npm run build", - "publish-all": "npm publish --workspaces --access public && npm publish --access public" + "publish-all": "npm publish --workspaces --access public && npm publish --access public", + "test:e2e": "npm run test:e2e --workspace=client" }, "dependencies": { "@modelcontextprotocol/inspector-cli": "^0.14.0", From 3a28efe5861f9416d41abb472e75059bf4f3c605 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 27 May 2025 19:21:06 -0700 Subject: [PATCH 07/48] Mention `npm run test:e2e` in CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 72502f96..1eeeb864 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,7 @@ Thanks for your interest in contributing! This guide explains how to get involve 1. Create a new branch for your changes 2. Make your changes following existing code style and conventions. You can run `npm run prettier-check` and `npm run prettier-fix` as applicable. -3. Test changes locally by running `npm test` +3. Test changes locally by running `npm test` and `npm run test:e2e` 4. Update documentation as needed 5. Use clear commit messages explaining your changes 6. Verify all changes work as expected From 89e1efc443614aed0075958168c7db27db4d6f2a Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 3 Jun 2025 23:29:33 -0700 Subject: [PATCH 08/48] Add more Playwright tests --- client/e2e/transport-type-dropdown.spec.ts | 87 ++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/client/e2e/transport-type-dropdown.spec.ts b/client/e2e/transport-type-dropdown.spec.ts index 7ca55655..e4ac8fbb 100644 --- a/client/e2e/transport-type-dropdown.spec.ts +++ b/client/e2e/transport-type-dropdown.spec.ts @@ -23,4 +23,91 @@ test.describe("Transport Type Dropdown", () => { page.getByRole("option", { name: "Streamable HTTP" }), ).toBeVisible(); }); + + test("should show Command and Arguments fields and hide URL field when Transport Type is STDIO", async ({ + page, + }) => { + await page.goto(APP_URL); + + // Wait for the Transport Type dropdown to be visible + const selectTrigger = page.getByLabel("Transport Type"); + await expect(selectTrigger).toBeVisible(); + + // Open the dropdown and select STDIO + await selectTrigger.click(); + await page.getByRole("option", { name: "STDIO" }).click(); + + // Wait for the form to update + await page.waitForTimeout(100); + + // Check that Command and Arguments fields are visible + await expect(page.locator("#command-input")).toBeVisible(); + await expect(page.locator("#arguments-input")).toBeVisible(); + + // Check that URL field is not visible + await expect(page.locator("#sse-url-input")).not.toBeVisible(); + + // Also verify the labels are present + await expect(page.getByText("Command")).toBeVisible(); + await expect(page.getByText("Arguments")).toBeVisible(); + await expect(page.getByText("URL")).not.toBeVisible(); + }); + + test("should show URL field and hide Command and Arguments fields when Transport Type is SSE", async ({ + page, + }) => { + await page.goto(APP_URL); + + // Wait for the Transport Type dropdown to be visible + const selectTrigger = page.getByLabel("Transport Type"); + await expect(selectTrigger).toBeVisible(); + + // Open the dropdown and select SSE + await selectTrigger.click(); + await page.getByRole("option", { name: "SSE" }).click(); + + // Wait for the form to update + await page.waitForTimeout(100); + + // Check that URL field is visible + await expect(page.locator("#sse-url-input")).toBeVisible(); + + // Check that Command and Arguments fields are not visible + await expect(page.locator("#command-input")).not.toBeVisible(); + await expect(page.locator("#arguments-input")).not.toBeVisible(); + + // Also verify the labels are present/absent + await expect(page.getByText("URL")).toBeVisible(); + await expect(page.getByText("Command")).not.toBeVisible(); + await expect(page.getByText("Arguments")).not.toBeVisible(); + }); + + test("should show URL field and hide Command and Arguments fields when Transport Type is Streamable HTTP", async ({ + page, + }) => { + await page.goto(APP_URL); + + // Wait for the Transport Type dropdown to be visible + const selectTrigger = page.getByLabel("Transport Type"); + await expect(selectTrigger).toBeVisible(); + + // Open the dropdown and select Streamable HTTP + await selectTrigger.click(); + await page.getByRole("option", { name: "Streamable HTTP" }).click(); + + // Wait for the form to update + await page.waitForTimeout(100); + + // Check that URL field is visible + await expect(page.locator("#sse-url-input")).toBeVisible(); + + // Check that Command and Arguments fields are not visible + await expect(page.locator("#command-input")).not.toBeVisible(); + await expect(page.locator("#arguments-input")).not.toBeVisible(); + + // Also verify the labels are present/absent + await expect(page.getByText("URL")).toBeVisible(); + await expect(page.getByText("Command")).not.toBeVisible(); + await expect(page.getByText("Arguments")).not.toBeVisible(); + }); }); From c803d9327e1397ede53d1dc88031625655a78244 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 3 Jun 2025 23:51:14 -0700 Subject: [PATCH 09/48] Move test:e2e up with other two test scripts --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 1e7a931e..9ef35437 100644 --- a/package.json +++ b/package.json @@ -34,12 +34,12 @@ "start-client": "cd client && npm run preview", "test": "npm run prettier-check && cd client && npm test", "test-cli": "cd cli && npm run test", + "test:e2e": "npm run test:e2e --workspace=client", "prettier-fix": "prettier --write .", "prettier-check": "prettier --check .", "lint": "prettier --check . && cd client && npm run lint", "prepare": "npm run build", - "publish-all": "npm publish --workspaces --access public && npm publish --access public", - "test:e2e": "npm run test:e2e --workspace=client" + "publish-all": "npm publish --workspaces --access public && npm publish --access public" }, "dependencies": { "@modelcontextprotocol/inspector-cli": "^0.14.0", From 4f6a4cebde023d6d262fab05443cdfe0b4a99a86 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 3 Jun 2025 23:54:25 -0700 Subject: [PATCH 10/48] Add .github/workflows/e2e_tests.yml --- .github/workflows/e2e_tests.yml | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/e2e_tests.yml diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml new file mode 100644 index 00000000..538d8da2 --- /dev/null +++ b/.github/workflows/e2e_tests.yml @@ -0,0 +1,45 @@ +name: Playwright Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + timeout-minutes: 5 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 18 + + # Cache Playwright browsers + - name: Cache Playwright browsers + id: cache-playwright + uses: actions/cache@v4 + with: + path: ~/.cache/ms-playwright # The default Playwright cache path + key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }} # Cache key based on OS and package-lock.json + restore-keys: | + ${{ runner.os }}-playwright- + + - name: Install dependencies + run: npm ci + + - name: Install Playwright and browsers unless cached + run: npx playwright install --with-deps + if: steps.cache-playwright.outputs.cache-hit != 'true' + + - name: Start Server + run: npm run start + + - name: Wait for server to start + run: npx wait-on http://localhost:6274 + + - name: Run Playwright tests + run: npm run test:e2e From 66ad85aadc27694432b94020a26f468f160a8517 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 6 Jun 2025 18:19:56 -0700 Subject: [PATCH 11/48] npm run prettier-fix --- .github/workflows/e2e_tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index 538d8da2..599d90df 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -2,26 +2,26 @@ name: Playwright Tests on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: test: timeout-minutes: 5 - runs-on: ubuntu-latest + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 18 # Cache Playwright browsers - name: Cache Playwright browsers id: cache-playwright - uses: actions/cache@v4 + uses: actions/cache@v4 with: path: ~/.cache/ms-playwright # The default Playwright cache path key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }} # Cache key based on OS and package-lock.json @@ -29,14 +29,14 @@ jobs: ${{ runner.os }}-playwright- - name: Install dependencies - run: npm ci + run: npm ci - name: Install Playwright and browsers unless cached run: npx playwright install --with-deps - if: steps.cache-playwright.outputs.cache-hit != 'true' + if: steps.cache-playwright.outputs.cache-hit != 'true' - name: Start Server - run: npm run start + run: npm run start - name: Wait for server to start run: npx wait-on http://localhost:6274 From 1df83d73f9604858ac4559c59b8eea6b2ee6fca4 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 6 Jun 2025 18:26:05 -0700 Subject: [PATCH 12/48] Do npm run start in background so that the CI job can proceed --- .github/workflows/e2e_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index 599d90df..14553600 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -36,7 +36,7 @@ jobs: if: steps.cache-playwright.outputs.cache-hit != 'true' - name: Start Server - run: npm run start + run: npm run start & - name: Wait for server to start run: npx wait-on http://localhost:6274 From 826bc35cf2e5c6be740b8d32efc95b46082d9d9e Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 6 Jun 2025 18:31:07 -0700 Subject: [PATCH 13/48] Upload Playwright Report and Screenshots --- .github/workflows/e2e_tests.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index 14553600..652863e4 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -43,3 +43,13 @@ jobs: - name: Run Playwright tests run: npm run test:e2e + + - name: Upload Playwright Report and Screenshots + uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: | + playwright-report/ + test-results/ + retention-days: 30 From a421f329e98bff46a4d3e360ddd7dcf926065d96 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 6 Jun 2025 18:43:04 -0700 Subject: [PATCH 14/48] git add playwright.config.ts --- client/playwright.config.ts | 82 +++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 client/playwright.config.ts diff --git a/client/playwright.config.ts b/client/playwright.config.ts new file mode 100644 index 00000000..5e848d26 --- /dev/null +++ b/client/playwright.config.ts @@ -0,0 +1,82 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * @see https://playwright.dev/docs/test-configuration + */ +export default defineConfig({ + testDir: './e2e', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [ + ['html', { outputFolder: 'playwright-report' }], + ['line'] + ], + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost:6274', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + + /* Take screenshots on failure */ + screenshot: 'on', + // screenshot: 'only-on-failure', + + /* Record video on failure */ + video: 'retain-on-failure', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + // Skip WebKit on macOS due to compatibility issues + ...(process.platform !== 'darwin' ? [{ + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }] : []), + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:6274', + // reuseExistingServer: !process.env.CI, + // }, +}); \ No newline at end of file From f06a6e1fb7fcbfd08c5397751b130fe808cd8b4d Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 6 Jun 2025 18:44:27 -0700 Subject: [PATCH 15/48] npm run prettier-fix --- client/playwright.config.ts | 39 +++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/client/playwright.config.ts b/client/playwright.config.ts index 5e848d26..551f2d38 100644 --- a/client/playwright.config.ts +++ b/client/playwright.config.ts @@ -1,10 +1,10 @@ -import { defineConfig, devices } from '@playwright/test'; +import { defineConfig, devices } from "@playwright/test"; /** * @see https://playwright.dev/docs/test-configuration */ export default defineConfig({ - testDir: './e2e', + testDir: "./e2e", /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ @@ -14,43 +14,44 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: [ - ['html', { outputFolder: 'playwright-report' }], - ['line'] - ], + reporter: [["html", { outputFolder: "playwright-report" }], ["line"]], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:6274', + baseURL: "http://localhost:6274", /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', + trace: "on-first-retry", /* Take screenshots on failure */ - screenshot: 'on', + screenshot: "on", // screenshot: 'only-on-failure', /* Record video on failure */ - video: 'retain-on-failure', + video: "retain-on-failure", }, /* Configure projects for major browsers */ projects: [ { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, + name: "chromium", + use: { ...devices["Desktop Chrome"] }, }, { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, + name: "firefox", + use: { ...devices["Desktop Firefox"] }, }, // Skip WebKit on macOS due to compatibility issues - ...(process.platform !== 'darwin' ? [{ - name: 'webkit', - use: { ...devices['Desktop Safari'] }, - }] : []), + ...(process.platform !== "darwin" + ? [ + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + ] + : []), /* Test against mobile viewports. */ // { @@ -79,4 +80,4 @@ export default defineConfig({ // url: 'http://127.0.0.1:6274', // reuseExistingServer: !process.env.CI, // }, -}); \ No newline at end of file +}); From a2a58c2bc21baa55d9eac6d1e48d0662d82181de Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 6 Jun 2025 18:57:02 -0700 Subject: [PATCH 16/48] Install Playwright dependencies --- .github/workflows/e2e_tests.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index 652863e4..b80ffd81 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -12,6 +12,11 @@ jobs: runs-on: ubuntu-latest steps: + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y libwoff1 + - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -31,6 +36,9 @@ jobs: - name: Install dependencies run: npm ci + - name: Install Playwright dependencies + run: npx playwright install-deps + - name: Install Playwright and browsers unless cached run: npx playwright install --with-deps if: steps.cache-playwright.outputs.cache-hit != 'true' From 705d7b46c27016f16befa44ae5311b5f98f43a43 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 6 Jun 2025 19:05:07 -0700 Subject: [PATCH 17/48] e2e_tests.yml: playwright-report and test-result in client dir --- .github/workflows/e2e_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index b80ffd81..d61227ba 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -58,6 +58,6 @@ jobs: with: name: playwright-report path: | - playwright-report/ - test-results/ + client/playwright-report/ + client/test-results/ retention-days: 30 From cdb9180629542dc87fe2b13e0e274277493b3768 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Sat, 7 Jun 2025 08:11:23 -0700 Subject: [PATCH 18/48] Generate report with https://github.com/daun/playwright-report-summary --- .github/workflows/e2e_tests.yml | 16 ++++++++++++++++ client/playwright.config.ts | 6 +++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index d61227ba..df323c07 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -60,4 +60,20 @@ jobs: path: | client/playwright-report/ client/test-results/ + client/results.json retention-days: 30 + + - name: Publish Playwright Test Summary + uses: daun/playwright-report-summary@v3 + if: always() + with: + report-file: client/results.json + comment-title: "🎭 Playwright E2E Test Results" + job-summary: true + icon-style: "emojis" + custom-info: | + **Test Environment:** Ubuntu Latest, Node.js 18 + **Browsers:** Chromium, Firefox + + 📊 [View Detailed HTML Report](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) (download artifacts) + test-command: "npm run test:e2e" diff --git a/client/playwright.config.ts b/client/playwright.config.ts index 551f2d38..935e4b3d 100644 --- a/client/playwright.config.ts +++ b/client/playwright.config.ts @@ -14,7 +14,11 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: [["html", { outputFolder: "playwright-report" }], ["line"]], + reporter: [ + ["html", { outputFolder: "playwright-report" }], + ["json", { outputFile: "results.json" }], + ["line"], + ], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ From 2c0af88f504e09461b7bf5c307d84e9bd70485ae Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Sat, 7 Jun 2025 08:35:13 -0700 Subject: [PATCH 19/48] Take screenshots on failure --- client/playwright.config.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/playwright.config.ts b/client/playwright.config.ts index 935e4b3d..fad4176b 100644 --- a/client/playwright.config.ts +++ b/client/playwright.config.ts @@ -28,8 +28,7 @@ export default defineConfig({ trace: "on-first-retry", /* Take screenshots on failure */ - screenshot: "on", - // screenshot: 'only-on-failure', + screenshot: "only-on-failure", /* Record video on failure */ video: "retain-on-failure", From 6e5ccf679ac6cd4e8c1ca4f728ee4e7b0dfb24be Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Mon, 16 Jun 2025 20:55:45 -0700 Subject: [PATCH 20/48] Remove commented out stuff in playwright.config.ts --- client/playwright.config.ts | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/client/playwright.config.ts b/client/playwright.config.ts index fad4176b..42612a26 100644 --- a/client/playwright.config.ts +++ b/client/playwright.config.ts @@ -55,32 +55,5 @@ export default defineConfig({ }, ] : []), - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, - // }, ], - - /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://127.0.0.1:6274', - // reuseExistingServer: !process.env.CI, - // }, }); From 8b1faf110cbf5956df05dd0bd18589c3ba1a972f Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 17 Jun 2025 20:18:32 -0700 Subject: [PATCH 21/48] playwright.config.ts: Start dev server automatically --- client/playwright.config.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/playwright.config.ts b/client/playwright.config.ts index 42612a26..e4d6a30d 100644 --- a/client/playwright.config.ts +++ b/client/playwright.config.ts @@ -4,6 +4,14 @@ import { defineConfig, devices } from "@playwright/test"; * @see https://playwright.dev/docs/test-configuration */ export default defineConfig({ + /* Run your local dev server before starting the tests */ + webServer: { + cwd: '..', + command: 'npm run dev', + url: 'http://localhost:6274', + reuseExistingServer: !process.env.CI, + }, + testDir: "./e2e", /* Run tests in files in parallel */ fullyParallel: true, From 4ef6bdb1b8144b23d698fe13f47f4e380059ad64 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 17 Jun 2025 20:19:41 -0700 Subject: [PATCH 22/48] e2e_tests.yml: Remove start dev server since now we are doing that in `playwright.config.ts` --- .github/workflows/e2e_tests.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index df323c07..b47daf2b 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -43,12 +43,6 @@ jobs: run: npx playwright install --with-deps if: steps.cache-playwright.outputs.cache-hit != 'true' - - name: Start Server - run: npm run start & - - - name: Wait for server to start - run: npx wait-on http://localhost:6274 - - name: Run Playwright tests run: npm run test:e2e From 0f1cc199268d5c8e45dcbdac898948d788c857db Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 17 Jun 2025 20:26:22 -0700 Subject: [PATCH 23/48] prettier fix playwright.config.ts --- client/playwright.config.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/playwright.config.ts b/client/playwright.config.ts index e4d6a30d..f4650843 100644 --- a/client/playwright.config.ts +++ b/client/playwright.config.ts @@ -6,9 +6,9 @@ import { defineConfig, devices } from "@playwright/test"; export default defineConfig({ /* Run your local dev server before starting the tests */ webServer: { - cwd: '..', - command: 'npm run dev', - url: 'http://localhost:6274', + cwd: "..", + command: "npm run dev", + url: "http://localhost:6274", reuseExistingServer: !process.env.CI, }, From 0b31ce745423f50a070e593bdb7ec2daf3e54d55 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 17 Jun 2025 23:44:49 -0700 Subject: [PATCH 24/48] .gitignore: Add playwright artifacts --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 60faf653..8e184de8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,7 @@ client/tsconfig.app.tsbuildinfo client/tsconfig.node.tsbuildinfo cli/build test-output +client/playwright-report/ +client/results.json +client/test-results/ + From b783b50f7f5c30e1e8dad9ffc8e6a95fe4871aae Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 17 Jun 2025 23:56:18 -0700 Subject: [PATCH 25/48] Make "npm run clean" remove Playwright files --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9ef35437..9108e3e3 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "build-server": "cd server && npm run build", "build-client": "cd client && npm run build", "build-cli": "cd cli && npm run build", - "clean": "rimraf ./node_modules ./client/node_modules ./cli/node_modules ./build ./client/dist ./server/build ./cli/build ./package-lock.json && npm install", + "clean": "rimraf ./node_modules ./client/node_modules ./cli/node_modules ./build ./client/dist ./server/build ./cli/build ./package-lock.json client/playwright-report client/results.json client/test-results && npm install", "dev": "concurrently \"cd client && npm run dev\" \"cd server && npm run dev\"", "dev:windows": "concurrently \"cd client && npm run dev\" \"cd server && npm run dev:windows\"", "start": "node client/bin/start.js", From db9ca3762a9c99501d45f72c4724ad2089e2fa82 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Thu, 19 Jun 2025 00:00:32 -0700 Subject: [PATCH 26/48] No playwright-report or results.json for non-CI --- client/playwright.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/playwright.config.ts b/client/playwright.config.ts index f4650843..3e4f1d2e 100644 --- a/client/playwright.config.ts +++ b/client/playwright.config.ts @@ -22,11 +22,11 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: [ + reporter: process.env.CI ? [ ["html", { outputFolder: "playwright-report" }], ["json", { outputFile: "results.json" }], ["line"], - ], + ] : [["line"]], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ From 08e4f6b4a83c8e4ac753974269c7f8f0fda74e5c Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Thu, 19 Jun 2025 00:06:36 -0700 Subject: [PATCH 27/48] outputDir: "./e2e/test-results" --- client/playwright.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/client/playwright.config.ts b/client/playwright.config.ts index 3e4f1d2e..fd24eb17 100644 --- a/client/playwright.config.ts +++ b/client/playwright.config.ts @@ -13,6 +13,7 @@ export default defineConfig({ }, testDir: "./e2e", + outputDir: "./e2e/test-results", /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ From 424b44966bd711b794efbd375108116bbfc6fd12 Mon Sep 17 00:00:00 2001 From: Alex Andru Date: Thu, 19 Jun 2025 14:22:02 +0200 Subject: [PATCH 28/48] chore: bump @modelcontextprotocol/sdk to version 1.13.0 --- package-lock.json | 9 +++++---- package.json | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index a54bf664..e557269a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@modelcontextprotocol/inspector-cli": "^0.14.3", "@modelcontextprotocol/inspector-client": "^0.14.3", "@modelcontextprotocol/inspector-server": "^0.14.3", - "@modelcontextprotocol/sdk": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.13.0", "concurrently": "^9.0.1", "open": "^10.1.0", "shell-quote": "^1.8.2", @@ -2005,9 +2005,10 @@ "link": true }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.12.1.tgz", - "integrity": "sha512-KG1CZhZfWg+u8pxeM/mByJDScJSrjjxLc8fwQqbsS8xCjBmQfMNEBTotYdNanKekepnfRI85GtgQlctLFpcYPw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.13.0.tgz", + "integrity": "sha512-P5FZsXU0kY881F6Hbk9GhsYx02/KgWK1DYf7/tyE/1lcFKhDYPQR9iYjhQXJn+Sg6hQleMo3DB7h7+p4wgp2Lw==", + "license": "MIT", "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", diff --git a/package.json b/package.json index 20c69c2a..a21363a7 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "@modelcontextprotocol/inspector-cli": "^0.14.3", "@modelcontextprotocol/inspector-client": "^0.14.3", "@modelcontextprotocol/inspector-server": "^0.14.3", - "@modelcontextprotocol/sdk": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.13.0", "concurrently": "^9.0.1", "open": "^10.1.0", "shell-quote": "^1.8.2", From 8b3613e11ae76d3f29ad84c6e8a4bc38a5e76c52 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Thu, 19 Jun 2025 07:02:06 -0700 Subject: [PATCH 29/48] prettier fix --- client/playwright.config.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/client/playwright.config.ts b/client/playwright.config.ts index fd24eb17..570dd054 100644 --- a/client/playwright.config.ts +++ b/client/playwright.config.ts @@ -23,11 +23,13 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: process.env.CI ? [ - ["html", { outputFolder: "playwright-report" }], - ["json", { outputFile: "results.json" }], - ["line"], - ] : [["line"]], + reporter: process.env.CI + ? [ + ["html", { outputFolder: "playwright-report" }], + ["json", { outputFile: "results.json" }], + ["line"], + ] + : [["line"]], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ From 1807542582482cb2f769ea69c5686cafd6ddb088 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Thu, 19 Jun 2025 07:22:38 -0700 Subject: [PATCH 30/48] Cleanup client/e2e/test-results/ after test:e2e --- client/e2e/global-teardown.js | 18 ++++++++++++++++++ client/package.json | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 client/e2e/global-teardown.js diff --git a/client/e2e/global-teardown.js b/client/e2e/global-teardown.js new file mode 100644 index 00000000..9308d940 --- /dev/null +++ b/client/e2e/global-teardown.js @@ -0,0 +1,18 @@ +import { rimraf } from "rimraf"; + +async function globalTeardown() { + if (!process.env.CI) { + console.log("Cleaning up test-results directory..."); + // Add a small delay to ensure all Playwright files are written + await new Promise((resolve) => setTimeout(resolve, 100)); + await rimraf("./e2e/test-results"); + console.log("Test-results directory cleaned up."); + } +} + +export default globalTeardown; + +// Call the function when this script is run directly +if (import.meta.url === `file://${process.argv[1]}`) { + globalTeardown().catch(console.error); +} diff --git a/client/package.json b/client/package.json index a34ecbc4..6d2e7d1a 100644 --- a/client/package.json +++ b/client/package.json @@ -21,7 +21,8 @@ "preview": "vite preview --port 6274", "test": "jest --config jest.config.cjs", "test:watch": "jest --config jest.config.cjs --watch", - "test:e2e": "playwright test e2e" + "test:e2e": "playwright test e2e && npm run cleanup:e2e", + "cleanup:e2e": "node e2e/global-teardown.js" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", From b481ed0077f662562ddfb0ffd5b81eec8e9578be Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Thu, 19 Jun 2025 09:39:52 -0700 Subject: [PATCH 31/48] Set `MCP_AUTO_OPEN_ENABLED=false` for `test:e2e` script Since merging the latest changes from `main` that put auto-open back in when running the start or dev scripts, the `test:e2e` run results in the Inspector being opened in the browser. This change suppresses that (tested locally). Thanks, @cliffhall! Co-authored-by: Cliff Hall --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f333ef9e..3461976b 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "start-client": "cd client && npm run preview", "test": "npm run prettier-check && cd client && npm test", "test-cli": "cd cli && npm run test", - "test:e2e": "npm run test:e2e --workspace=client", + "test:e2e": "MCP_AUTO_OPEN_ENABLED=false npm run test:e2e --workspace=client", "prettier-fix": "prettier --write .", "prettier-check": "prettier --check .", "lint": "prettier --check . && cd client && npm run lint", From 0e6915a061f6cbef16fe29eb8d271a5869e46afb Mon Sep 17 00:00:00 2001 From: Alex Andru Date: Fri, 20 Jun 2025 18:54:13 +0200 Subject: [PATCH 32/48] chore: bump workspace deps --- cli/package.json | 2 +- client/package.json | 2 +- server/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/package.json b/cli/package.json index f1ff1c33..382eb1b4 100644 --- a/cli/package.json +++ b/cli/package.json @@ -21,7 +21,7 @@ }, "devDependencies": {}, "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.13.0", "commander": "^13.1.0", "spawn-rx": "^5.1.2" } diff --git a/client/package.json b/client/package.json index 37a0675b..449e0637 100644 --- a/client/package.json +++ b/client/package.json @@ -23,7 +23,7 @@ "test:watch": "jest --config jest.config.cjs --watch" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.13.0", "@radix-ui/react-checkbox": "^1.1.4", "ajv": "^6.12.6", "@radix-ui/react-dialog": "^1.1.3", diff --git a/server/package.json b/server/package.json index 8f81a9df..9c248635 100644 --- a/server/package.json +++ b/server/package.json @@ -27,7 +27,7 @@ "typescript": "^5.6.2" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.13.0", "cors": "^2.8.5", "express": "^5.1.0", "ws": "^8.18.0", From d5b4a6e6a77f52b510306c9a6ba3bb256559469a Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 20 Jun 2025 11:12:03 -0700 Subject: [PATCH 33/48] Update .github/workflows/e2e_tests.yml Co-authored-by: Cliff Hall --- .github/workflows/e2e_tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index b47daf2b..2d36f128 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [main] +permissions: + pull-requests: write + jobs: test: timeout-minutes: 5 From eb89e0191f7a54ac421e593c0af520e8349a20e7 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 20 Jun 2025 11:39:18 -0700 Subject: [PATCH 34/48] Change retention-days from 30 to 2 --- .github/workflows/e2e_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index 2d36f128..f620c318 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -58,7 +58,7 @@ jobs: client/playwright-report/ client/test-results/ client/results.json - retention-days: 30 + retention-days: 2 - name: Publish Playwright Test Summary uses: daun/playwright-report-summary@v3 From 1ff39f13f5339a1bf87101f80848bc7b69c089b4 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 20 Jun 2025 11:44:45 -0700 Subject: [PATCH 35/48] Replace pull_request with pull_request_target --- .github/workflows/e2e_tests.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index f620c318..c601e45b 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -3,7 +3,7 @@ name: Playwright Tests on: push: branches: [main] - pull_request: + pull_request_target: branches: [main] permissions: @@ -20,7 +20,10 @@ jobs: sudo apt-get update sudo apt-get install -y libwoff1 + # Security: Explicitly checkout the PR's code, not the target branch - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} - uses: actions/setup-node@v4 with: From 4b14b96f417598bde7fbae5acfe99b3b8faff2e5 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 20 Jun 2025 12:13:29 -0700 Subject: [PATCH 36/48] Both pull_request and pull_request_target --- .github/workflows/e2e_tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index c601e45b..f52e928f 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -3,6 +3,8 @@ name: Playwright Tests on: push: branches: [main] + pull_request: + branches: [main] pull_request_target: branches: [main] From 9ffe63ee08908718b0ade8bdd457cbe46567dae1 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 20 Jun 2025 13:35:45 -0700 Subject: [PATCH 37/48] Revert "Both pull_request and pull_request_target" This reverts commit 4b14b96f417598bde7fbae5acfe99b3b8faff2e5. --- .github/workflows/e2e_tests.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index f52e928f..c601e45b 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -3,8 +3,6 @@ name: Playwright Tests on: push: branches: [main] - pull_request: - branches: [main] pull_request_target: branches: [main] From 3e28357e9cdb0434a4d519608f9bd49ad6cdf049 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 20 Jun 2025 13:36:00 -0700 Subject: [PATCH 38/48] Revert "Replace pull_request with pull_request_target" This reverts commit 1ff39f13f5339a1bf87101f80848bc7b69c089b4. --- .github/workflows/e2e_tests.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index c601e45b..f620c318 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -3,7 +3,7 @@ name: Playwright Tests on: push: branches: [main] - pull_request_target: + pull_request: branches: [main] permissions: @@ -20,10 +20,7 @@ jobs: sudo apt-get update sudo apt-get install -y libwoff1 - # Security: Explicitly checkout the PR's code, not the target branch - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - uses: actions/setup-node@v4 with: From bda9b139718d288ad5224219599d9729bb30df7f Mon Sep 17 00:00:00 2001 From: cliffhall Date: Fri, 20 Jun 2025 16:46:25 -0400 Subject: [PATCH 39/48] Wasn't fully updated with dependency versions in #532 Update package-lock.json --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e557269a..09db8c0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "version": "0.14.3", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.13.0", "commander": "^13.1.0", "spawn-rx": "^5.1.2" }, @@ -66,7 +66,7 @@ "version": "0.14.3", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.13.0", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", @@ -10924,7 +10924,7 @@ "version": "0.14.3", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.1", + "@modelcontextprotocol/sdk": "^1.13.0", "cors": "^2.8.5", "express": "^5.1.0", "ws": "^8.18.0", From 465ee9aaf4480e9703039bb72c18b2774329ec34 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 20 Jun 2025 13:55:12 -0700 Subject: [PATCH 40/48] Revert "Update .github/workflows/e2e_tests.yml" This reverts commit d5b4a6e6a77f52b510306c9a6ba3bb256559469a. --- .github/workflows/e2e_tests.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index f620c318..83f7da96 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -6,9 +6,6 @@ on: pull_request: branches: [main] -permissions: - pull-requests: write - jobs: test: timeout-minutes: 5 From 4375433a8550117b5260f56f401a27750637deda Mon Sep 17 00:00:00 2001 From: Richard Michael Date: Fri, 20 Jun 2025 15:27:39 -0700 Subject: [PATCH 41/48] docs: Update project organization in CLAUDE.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add cli/ directory description - Remove non-existent bin/ directory reference 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index e62fd188..ec826b1f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,4 +30,4 @@ The project is organized as a monorepo with workspaces: - `client/`: React frontend with Vite, TypeScript and Tailwind - `server/`: Express backend with TypeScript -- `bin/`: CLI scripts +- `cli/`: Command-line interface for testing and invoking MCP server methods directly From c747fe85d8e1f0619ed761366dd66c8c30aeced4 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 20 Jun 2025 19:17:12 -0700 Subject: [PATCH 42/48] Only try to comment on PR if the PR is from the canonical repo and not from a fork --- .github/workflows/e2e_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index 83f7da96..e56a49d8 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -59,7 +59,7 @@ jobs: - name: Publish Playwright Test Summary uses: daun/playwright-report-summary@v3 - if: always() + if: always() && github.event.pull_request.head.repo.full_name == github.repository with: report-file: client/results.json comment-title: "🎭 Playwright E2E Test Results" From 94a35d07f3f3e7b98de1ae72c9b22c2604685c3e Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 20 Jun 2025 19:24:32 -0700 Subject: [PATCH 43/48] Only try to comment on PR if the PR is from the canonical repo and not from a fork --- .github/workflows/e2e_tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index e56a49d8..35786cbe 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -59,8 +59,9 @@ jobs: - name: Publish Playwright Test Summary uses: daun/playwright-report-summary@v3 - if: always() && github.event.pull_request.head.repo.full_name == github.repository + if: always() with: + create-comment: github.event.pull_request.head.repo.full_name == github.repository report-file: client/results.json comment-title: "🎭 Playwright E2E Test Results" job-summary: true From 2cd7b7490c1e68a01fcb214f659d6b6eaad822ae Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 20 Jun 2025 19:29:47 -0700 Subject: [PATCH 44/48] Revert --- .github/workflows/e2e_tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index 35786cbe..e56a49d8 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -59,9 +59,8 @@ jobs: - name: Publish Playwright Test Summary uses: daun/playwright-report-summary@v3 - if: always() + if: always() && github.event.pull_request.head.repo.full_name == github.repository with: - create-comment: github.event.pull_request.head.repo.full_name == github.repository report-file: client/results.json comment-title: "🎭 Playwright E2E Test Results" job-summary: true From ebc249b3d5144cdc27c5763c2c1ea6cd7c991cb5 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 20 Jun 2025 19:43:19 -0700 Subject: [PATCH 45/48] Always gen test summary but only comment on same repo PRs --- .github/workflows/e2e_tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index e56a49d8..f40dd582 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -59,8 +59,9 @@ jobs: - name: Publish Playwright Test Summary uses: daun/playwright-report-summary@v3 - if: always() && github.event.pull_request.head.repo.full_name == github.repository + if: always() with: + create-comment: ${{ github.event.pull_request.head.repo.full_name == github.repository }} report-file: client/results.json comment-title: "🎭 Playwright E2E Test Results" job-summary: true From 5db64265cec2942ace9f5aceebd7862aeef3625c Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Mon, 23 Jun 2025 16:39:07 +0100 Subject: [PATCH 46/48] [auth] Add resource compatibility to debugger (RFC 8707) (#526) * pass resource in auth debugger, reusing server url * throw error on resource url mismatch to match regular auth flow * lint & tests * add test * make authserver url make more sense * use latest typescript sdk * more lenient resource checking * pick resource, and store in state * fixing tests and types * [auth] use sdk function for selecting resource (#537) * use sdk function * bump typescript sdk * fix some tests --- client/src/components/OAuthFlowProgress.tsx | 2 +- .../__tests__/AuthDebugger.test.tsx | 22 ++++++++++--------- client/src/lib/auth-types.ts | 2 ++ client/src/lib/oauth-state-machine.ts | 19 +++++++++++----- package-lock.json | 9 ++++---- package.json | 2 +- 6 files changed, 34 insertions(+), 22 deletions(-) diff --git a/client/src/components/OAuthFlowProgress.tsx b/client/src/components/OAuthFlowProgress.tsx index b9b67c6c..0e260556 100644 --- a/client/src/components/OAuthFlowProgress.tsx +++ b/client/src/components/OAuthFlowProgress.tsx @@ -156,7 +156,7 @@ export const OAuthFlowProgress = ({ {authState.resourceMetadataError && (

- â„šī¸ No resource metadata available from{" "} + â„šī¸ Problem with resource metadata from{" "} ({ startAuthorization: jest.fn(), exchangeAuthorization: jest.fn(), discoverOAuthProtectedResourceMetadata: jest.fn(), + selectResourceURL: jest.fn(), })); // Import the functions to get their types @@ -88,7 +89,7 @@ describe("AuthDebugger", () => { const defaultAuthState = EMPTY_DEBUGGER_STATE; const defaultProps = { - serverUrl: "https://example.com", + serverUrl: "https://example.com/mcp", onBack: jest.fn(), authState: defaultAuthState, updateAuthState: jest.fn(), @@ -203,7 +204,7 @@ describe("AuthDebugger", () => { // Should first discover and save OAuth metadata expect(mockDiscoverOAuthMetadata).toHaveBeenCalledWith( - new URL("https://example.com"), + new URL("https://example.com/"), ); // Check that updateAuthState was called with the right info message @@ -320,6 +321,7 @@ describe("AuthDebugger", () => { isInitiatingAuth: false, resourceMetadata: null, resourceMetadataError: null, + resource: null, oauthTokens: null, oauthStep: "metadata_discovery", latestError: null, @@ -361,7 +363,7 @@ describe("AuthDebugger", () => { }); expect(mockDiscoverOAuthMetadata).toHaveBeenCalledWith( - new URL("https://example.com"), + new URL("https://example.com/"), ); }); @@ -496,11 +498,11 @@ describe("AuthDebugger", () => { it("should successfully fetch and display protected resource metadata", async () => { const updateAuthState = jest.fn(); const mockResourceMetadata = { - resource: "https://example.com/api", + resource: "https://example.com/mcp", authorization_servers: ["https://custom-auth.example.com"], bearer_methods_supported: ["header", "body"], - resource_documentation: "https://example.com/api/docs", - resource_policy_uri: "https://example.com/api/policy", + resource_documentation: "https://example.com/mcp/docs", + resource_policy_uri: "https://example.com/mcp/policy", }; // Mock successful metadata discovery @@ -538,7 +540,7 @@ describe("AuthDebugger", () => { // Wait for the metadata to be fetched await waitFor(() => { expect(mockDiscoverOAuthProtectedResourceMetadata).toHaveBeenCalledWith( - "https://example.com", + "https://example.com/mcp", ); }); @@ -584,7 +586,7 @@ describe("AuthDebugger", () => { // Wait for the metadata fetch to fail await waitFor(() => { expect(mockDiscoverOAuthProtectedResourceMetadata).toHaveBeenCalledWith( - "https://example.com", + "https://example.com/mcp", ); }); @@ -594,7 +596,7 @@ describe("AuthDebugger", () => { expect.objectContaining({ resourceMetadataError: mockError, // Should use the original server URL as fallback - authServerUrl: new URL("https://example.com"), + authServerUrl: new URL("https://example.com/"), oauthStep: "client_registration", }), ); @@ -602,7 +604,7 @@ describe("AuthDebugger", () => { // Verify that regular OAuth metadata discovery was still called expect(mockDiscoverOAuthMetadata).toHaveBeenCalledWith( - new URL("https://example.com"), + new URL("https://example.com/"), ); }); }); diff --git a/client/src/lib/auth-types.ts b/client/src/lib/auth-types.ts index 5e8113ef..f8f927c6 100644 --- a/client/src/lib/auth-types.ts +++ b/client/src/lib/auth-types.ts @@ -30,6 +30,7 @@ export interface AuthDebuggerState { oauthStep: OAuthStep; resourceMetadata: OAuthProtectedResourceMetadata | null; resourceMetadataError: Error | null; + resource: URL | null; authServerUrl: URL | null; oauthMetadata: OAuthMetadata | null; oauthClientInfo: OAuthClientInformationFull | OAuthClientInformation | null; @@ -47,6 +48,7 @@ export const EMPTY_DEBUGGER_STATE: AuthDebuggerState = { oauthMetadata: null, resourceMetadata: null, resourceMetadataError: null, + resource: null, authServerUrl: null, oauthClientInfo: null, authorizationUrl: null, diff --git a/client/src/lib/oauth-state-machine.ts b/client/src/lib/oauth-state-machine.ts index 5f10a783..d87b3ecd 100644 --- a/client/src/lib/oauth-state-machine.ts +++ b/client/src/lib/oauth-state-machine.ts @@ -6,6 +6,7 @@ import { startAuthorization, exchangeAuthorization, discoverOAuthProtectedResourceMetadata, + selectResourceURL, } from "@modelcontextprotocol/sdk/client/auth.js"; import { OAuthMetadataSchema, @@ -29,17 +30,15 @@ export const oauthTransitions: Record = { metadata_discovery: { canTransition: async () => true, execute: async (context) => { - let authServerUrl = new URL(context.serverUrl); + // Default to discovering from the server's URL + let authServerUrl = new URL("/", context.serverUrl); let resourceMetadata: OAuthProtectedResourceMetadata | null = null; let resourceMetadataError: Error | null = null; try { resourceMetadata = await discoverOAuthProtectedResourceMetadata( context.serverUrl, ); - if ( - resourceMetadata && - resourceMetadata.authorization_servers?.length - ) { + if (resourceMetadata?.authorization_servers?.length) { authServerUrl = new URL(resourceMetadata.authorization_servers[0]); } } catch (e) { @@ -50,6 +49,13 @@ export const oauthTransitions: Record = { } } + const resource: URL | undefined = await selectResourceURL( + context.serverUrl, + context.provider, + // we default to null, so swap it for undefined if not set + resourceMetadata ?? undefined, + ); + const metadata = await discoverOAuthMetadata(authServerUrl); if (!metadata) { throw new Error("Failed to discover OAuth metadata"); @@ -58,6 +64,7 @@ export const oauthTransitions: Record = { context.provider.saveServerMetadata(parsedMetadata); context.updateState({ resourceMetadata, + resource, resourceMetadataError, authServerUrl, oauthMetadata: parsedMetadata, @@ -113,6 +120,7 @@ export const oauthTransitions: Record = { clientInformation, redirectUrl: context.provider.redirectUrl, scope, + resource: context.state.resource ?? undefined, }, ); @@ -163,6 +171,7 @@ export const oauthTransitions: Record = { authorizationCode: context.state.authorizationCode, codeVerifier, redirectUri: context.provider.redirectUrl, + resource: context.state.resource ?? undefined, }); context.provider.saveTokens(tokens); diff --git a/package-lock.json b/package-lock.json index 09db8c0d..695f6a02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@modelcontextprotocol/inspector-cli": "^0.14.3", "@modelcontextprotocol/inspector-client": "^0.14.3", "@modelcontextprotocol/inspector-server": "^0.14.3", - "@modelcontextprotocol/sdk": "^1.13.0", + "@modelcontextprotocol/sdk": "^1.13.1", "concurrently": "^9.0.1", "open": "^10.1.0", "shell-quote": "^1.8.2", @@ -2005,10 +2005,9 @@ "link": true }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.13.0.tgz", - "integrity": "sha512-P5FZsXU0kY881F6Hbk9GhsYx02/KgWK1DYf7/tyE/1lcFKhDYPQR9iYjhQXJn+Sg6hQleMo3DB7h7+p4wgp2Lw==", - "license": "MIT", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.13.1.tgz", + "integrity": "sha512-8q6+9aF0yA39/qWT/uaIj6zTpC+Qu07DnN/lb9mjoquCJsAh6l3HyYqc9O3t2j7GilseOQOQimLg7W3By6jqvg==", "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", diff --git a/package.json b/package.json index a21363a7..7b89edb8 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "@modelcontextprotocol/inspector-cli": "^0.14.3", "@modelcontextprotocol/inspector-client": "^0.14.3", "@modelcontextprotocol/inspector-server": "^0.14.3", - "@modelcontextprotocol/sdk": "^1.13.0", + "@modelcontextprotocol/sdk": "^1.13.1", "concurrently": "^9.0.1", "open": "^10.1.0", "shell-quote": "^1.8.2", From 0bef6f71ad9e9671b71ecb9c1460729b506de0e6 Mon Sep 17 00:00:00 2001 From: Ryan Clark Date: Tue, 24 Jun 2025 09:23:51 -0500 Subject: [PATCH 47/48] feat: add tests for http transport --- cli/scripts/cli-tests.js | 105 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 101 insertions(+), 4 deletions(-) diff --git a/cli/scripts/cli-tests.js b/cli/scripts/cli-tests.js index 6e6b56f9..ab5481c6 100755 --- a/cli/scripts/cli-tests.js +++ b/cli/scripts/cli-tests.js @@ -44,7 +44,10 @@ console.log(`${colors.BLUE}- Resource-related options (--uri)${colors.NC}`); console.log( `${colors.BLUE}- Prompt-related options (--prompt-name, --prompt-args)${colors.NC}`, ); -console.log(`${colors.BLUE}- Logging options (--log-level)${colors.NC}\n`); +console.log(`${colors.BLUE}- Logging options (--log-level)${colors.NC}`); +console.log(`${colors.BLUE}- Transport types (--transport http/sse/stdio)${colors.NC}`); +console.log(`${colors.BLUE}- Transport inference from URL suffixes (/mcp, /sse)${colors.NC}`); +console.log(`\n`); // Get directory paths const SCRIPTS_DIR = __dirname; @@ -62,9 +65,11 @@ if (!fs.existsSync(OUTPUT_DIR)) { } // Create a temporary directory for test files -const TEMP_DIR = fs.mkdirSync(path.join(os.tmpdir(), "mcp-inspector-tests"), { - recursive: true, -}); +const TEMP_DIR = path.join(os.tmpdir(), "mcp-inspector-tests"); +fs.mkdirSync(TEMP_DIR, { recursive: true }); + +// Track servers for cleanup +let runningServers = []; process.on("exit", () => { try { @@ -74,6 +79,23 @@ process.on("exit", () => { `${colors.RED}Failed to remove temp directory: ${err.message}${colors.NC}`, ); } + + runningServers.forEach(server => { + try { + process.kill(-server.pid); + } catch (e) { + } + }); +}); + +process.on("SIGINT", () => { + runningServers.forEach(server => { + try { + process.kill(-server.pid); + } catch (e) { + } + }); + process.exit(1); }); // Use the existing sample config file @@ -121,6 +143,11 @@ async function runBasicTest(testName, ...args) { stdio: ["ignore", "pipe", "pipe"], }); + const timeout = setTimeout(() => { + console.log(`${colors.YELLOW}Test timed out: ${testName}${colors.NC}`); + child.kill(); + }, 10000); + // Pipe stdout and stderr to the output file child.stdout.pipe(outputStream); child.stderr.pipe(outputStream); @@ -135,6 +162,7 @@ async function runBasicTest(testName, ...args) { }); child.on("close", (code) => { + clearTimeout(timeout); outputStream.end(); if (code === 0) { @@ -201,6 +229,11 @@ async function runErrorTest(testName, ...args) { stdio: ["ignore", "pipe", "pipe"], }); + const timeout = setTimeout(() => { + console.log(`${colors.YELLOW}Error test timed out: ${testName}${colors.NC}`); + child.kill(); + }, 10000); + // Pipe stdout and stderr to the output file child.stdout.pipe(outputStream); child.stderr.pipe(outputStream); @@ -215,6 +248,7 @@ async function runErrorTest(testName, ...args) { }); child.on("close", (code) => { + clearTimeout(timeout); outputStream.end(); // For error tests, we expect a non-zero exit code @@ -611,6 +645,69 @@ async function runTests() { "debug", ); + console.log( + `\n${colors.YELLOW}=== Running HTTP Transport Tests ===${colors.NC}`, + ); + + console.log(`${colors.BLUE}Starting server-everything in streamableHttp mode.${colors.NC}`); + const httpServer = spawn("npx", ["@modelcontextprotocol/server-everything", "streamableHttp"], { + detached: true, + stdio: "ignore" + }); + runningServers.push(httpServer); + + await new Promise(resolve => setTimeout(resolve, 3000)); + + // Test 25: HTTP transport inferred from URL ending with /mcp + await runBasicTest( + "http_transport_inferred", + "http://127.0.0.1:3001/mcp", + "--cli", + "--method", + "tools/list", + ); + + // Test 26: HTTP transport with explicit --transport http flag + await runBasicTest( + "http_transport_with_explicit_flag", + "http://127.0.0.1:3001", + "--transport", + "http", + "--cli", + "--method", + "tools/list", + ); + + // Test 27: HTTP transport with suffix and --transport http flag + await runBasicTest( + "http_transport_with_explicit_flag_and_suffix", + "http://127.0.0.1:3001/mcp", + "--transport", + "http", + "--cli", + "--method", + "tools/list", + ); + + // Test 28: SSE transport given to HTTP server (should fail) + await runErrorTest( + "sse_transport_given_to_http_server", + "http://127.0.0.1:3001", + "--transport", + "sse", + "--cli", + "--method", + "tools/list", + ); + + // Kill HTTP server + try { + process.kill(-httpServer.pid); + console.log(`${colors.BLUE}HTTP server killed, waiting for port to be released...${colors.NC}`); + } catch (e) { + console.log(`${colors.RED}Error killing HTTP server: ${e.message}${colors.NC}`); + } + // Print test summary console.log(`\n${colors.YELLOW}=== Test Summary ===${colors.NC}`); console.log(`${colors.GREEN}Passed: ${PASSED_TESTS}${colors.NC}`); From 14a9b8fb36ca752707089f7f7c6d3e8420158470 Mon Sep 17 00:00:00 2001 From: Ryan Clark Date: Wed, 25 Jun 2025 00:46:11 -0500 Subject: [PATCH 48/48] feat: formatting --- cli/scripts/cli-tests.js | 48 ++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/cli/scripts/cli-tests.js b/cli/scripts/cli-tests.js index ab5481c6..51bda553 100755 --- a/cli/scripts/cli-tests.js +++ b/cli/scripts/cli-tests.js @@ -45,8 +45,12 @@ console.log( `${colors.BLUE}- Prompt-related options (--prompt-name, --prompt-args)${colors.NC}`, ); console.log(`${colors.BLUE}- Logging options (--log-level)${colors.NC}`); -console.log(`${colors.BLUE}- Transport types (--transport http/sse/stdio)${colors.NC}`); -console.log(`${colors.BLUE}- Transport inference from URL suffixes (/mcp, /sse)${colors.NC}`); +console.log( + `${colors.BLUE}- Transport types (--transport http/sse/stdio)${colors.NC}`, +); +console.log( + `${colors.BLUE}- Transport inference from URL suffixes (/mcp, /sse)${colors.NC}`, +); console.log(`\n`); // Get directory paths @@ -80,20 +84,18 @@ process.on("exit", () => { ); } - runningServers.forEach(server => { + runningServers.forEach((server) => { try { process.kill(-server.pid); - } catch (e) { - } + } catch (e) {} }); }); process.on("SIGINT", () => { - runningServers.forEach(server => { + runningServers.forEach((server) => { try { process.kill(-server.pid); - } catch (e) { - } + } catch (e) {} }); process.exit(1); }); @@ -230,7 +232,9 @@ async function runErrorTest(testName, ...args) { }); const timeout = setTimeout(() => { - console.log(`${colors.YELLOW}Error test timed out: ${testName}${colors.NC}`); + console.log( + `${colors.YELLOW}Error test timed out: ${testName}${colors.NC}`, + ); child.kill(); }, 10000); @@ -649,14 +653,20 @@ async function runTests() { `\n${colors.YELLOW}=== Running HTTP Transport Tests ===${colors.NC}`, ); - console.log(`${colors.BLUE}Starting server-everything in streamableHttp mode.${colors.NC}`); - const httpServer = spawn("npx", ["@modelcontextprotocol/server-everything", "streamableHttp"], { - detached: true, - stdio: "ignore" - }); + console.log( + `${colors.BLUE}Starting server-everything in streamableHttp mode.${colors.NC}`, + ); + const httpServer = spawn( + "npx", + ["@modelcontextprotocol/server-everything", "streamableHttp"], + { + detached: true, + stdio: "ignore", + }, + ); runningServers.push(httpServer); - await new Promise(resolve => setTimeout(resolve, 3000)); + await new Promise((resolve) => setTimeout(resolve, 3000)); // Test 25: HTTP transport inferred from URL ending with /mcp await runBasicTest( @@ -703,9 +713,13 @@ async function runTests() { // Kill HTTP server try { process.kill(-httpServer.pid); - console.log(`${colors.BLUE}HTTP server killed, waiting for port to be released...${colors.NC}`); + console.log( + `${colors.BLUE}HTTP server killed, waiting for port to be released...${colors.NC}`, + ); } catch (e) { - console.log(`${colors.RED}Error killing HTTP server: ${e.message}${colors.NC}`); + console.log( + `${colors.RED}Error killing HTTP server: ${e.message}${colors.NC}`, + ); } // Print test summary