diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4381d72..ffd08b7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,19 +3,19 @@
## [Unreleased]
### CLI
-- `mcporter config add` now accepts plural `--args` as an alias for repeated stdio arguments, matching common CLI muscle memory. (PR #93, thanks @Jah-yee)
+- Preserve OAuth flow vs post-auth transport failures so invalid OAuth/provider errors surface directly, while real legacy 404/405 transport mismatches still fall back to SSE correctly. (PR #97, thanks @mavam)
+- Ignore static `Authorization` headers once OAuth is active so imported editor configs cannot override fresh OAuth tokens. (PR #123, thanks @ahonn)
+- Keep `mcporter call --output json` parseable by emitting valid JSON even when the command falls back to raw output. (PR #128, thanks @armanddp)
+- Render `resource` content blocks in call output helpers instead of dropping them, including markdown resources and JSON text payloads. (PR #124, thanks @mvanhorn)
+- Preserve full JSON/error payloads when `data` is just one field instead of collapsing the response to `data` alone. (PR #106, thanks @AielloChan)
- Generated CLIs now parse object-valued flags as JSON and render object placeholders/examples with JSON-shaped help text, so tools like Jira `fields` no longer receive raw strings. (PR #114, thanks @v2nic)
- Deduplicate concurrent keep-alive daemon restarts per server so repeated fatal errors only force-close the cached daemon transport once before retrying. (PR #125, thanks @zm2231)
-- Keep `mcporter call --output json` parseable by emitting valid JSON even when the command falls back to raw output. (PR #128, thanks @armanddp)
-- Ignore static `Authorization` headers once OAuth is active so imported editor configs cannot override fresh OAuth tokens. (PR #123, thanks @ahonn)
-- Preserve full JSON/error payloads when `data` is just one field instead of collapsing the response to `data` alone. (PR #106, thanks @AielloChan)
-- Render `resource` content blocks in call output helpers instead of dropping them, including markdown resources and JSON text payloads. (PR #124, thanks @mvanhorn)
-- Preserve OAuth flow vs post-auth transport failures so invalid OAuth/provider errors surface directly, while real legacy 404/405 transport mismatches still fall back to SSE correctly. (PR #97, thanks @mavam)
+- `mcporter config add` now accepts plural `--args` as an alias for repeated stdio arguments, matching common CLI muscle memory. (PR #93, thanks @Jah-yee)
- Preserve default imports when `mcporter config add` writes a config file, instead of forcing `"imports": []`.
- OAuth: avoid crashing on headless Linux when `xdg-open` is unavailable; clear stale dynamic-port client registrations; close callback server if stale-client persistence reads fail. (PR #72, thanks @mgonto)
- Added optional `oauthScope`/`oauth_scope` config override as an escape hatch for providers that require explicit scopes.
-- `createCallResult().json()` now collects all parseable JSON entries from MCP content arrays (single item stays backward-compatible), and raw inspect depth now stays readable without unbounded traversal. (PR #91, thanks @Blankdlh)
- OAuth wait/redirect now share one deferred to eliminate authorization race windows and preserve stable close-path errors, including wait-before-redirect and repeated-redirect flows. (PR #70, thanks @monotykamary)
+- `createCallResult().json()` now collects all parseable JSON entries from MCP content arrays (single item stays backward-compatible), and raw inspect depth now stays readable without unbounded traversal. (PR #91, thanks @Blankdlh)
- Added `--raw-strings` (numeric coercion off) and `--no-coerce` (all coercion off) for `mcporter call` argument parsing so IDs/codes can stay literal strings. (PR #59, thanks @nobrainer-tech)
- Added `CallResult.images()` plus opt-in `mcporter call --save-images
` so image content blocks can be persisted without changing existing stdout output contracts. (PR #61, thanks @daniella-11ways)
- OAuth transport retries now classify HTTP 405 as HTTP (not auth) and OAuth promotion applies to configured HTTP servers too, so post-auth fallback flows no longer drop credentials on 405-only endpoints. (PR #48, thanks @caseyg)
diff --git a/docs/livetests.md b/docs/livetests.md
index 46862a2..c6763e8 100644
--- a/docs/livetests.md
+++ b/docs/livetests.md
@@ -20,12 +20,15 @@ MCP_LIVE_TESTS=1 pnpm test:live
This runs the Vitest suite under `tests/live`, in-band, with longer timeouts.
## Current coverage
-- **DeepWiki** (both wire protocols):
- - Streamable HTTP: `https://mcp.deepwiki.com/mcp`
- - SSE: `https://mcp.deepwiki.com/sse`
- - Test: calls `read_wiki_structure repoName:facebook/react` and asserts a non-empty result.
+- **DeepWiki**:
+ - Streamable HTTP success path: `https://mcp.deepwiki.com/mcp`
+ - Deprecated SSE endpoint classification: `https://mcp.deepwiki.com/sse`
+ - Tests:
+ - call `read_wiki_structure repoName:facebook/react` and assert a non-empty result over Streamable HTTP
+ - assert the legacy SSE endpoint currently returns a structured HTTP `410` issue envelope
## Notes
- Tests are skipped entirely unless `MCP_LIVE_TESTS=1` is set.
- Ensure network egress is allowed. No secrets are required for the current DeepWiki checks.
+- As of 2026-03-29, DeepWiki's hosted `/sse` endpoint responds with HTTP `410`, so the live suite treats that as a compatibility/error-classification smoke rather than a success-path transport check.
- Keep assertions minimal to reduce flake; these are availability smokes, not full contract tests.
diff --git a/tests/live/deepwiki-live.test.ts b/tests/live/deepwiki-live.test.ts
index 214842b..8579e41 100644
--- a/tests/live/deepwiki-live.test.ts
+++ b/tests/live/deepwiki-live.test.ts
@@ -3,10 +3,8 @@ import { promisify } from 'node:util';
import { describe, expect, it } from 'vitest';
const LIVE_FLAG = process.env.MCP_LIVE_TESTS === '1';
-const ENDPOINTS = [
- { name: 'streamable-http', url: 'https://mcp.deepwiki.com/mcp' },
- { name: 'sse', url: 'https://mcp.deepwiki.com/sse' },
-];
+const STREAMABLE_HTTP_URL = 'https://mcp.deepwiki.com/mcp';
+const SSE_URL = 'https://mcp.deepwiki.com/sse';
const execFileAsync = promisify(execFile);
@@ -18,36 +16,47 @@ function skipReason(): string | undefined {
}
describe.skipIf(Boolean(skipReason()))('deepwiki live', () => {
- ENDPOINTS.forEach(({ name, url }) => {
- it(`lists wiki structure via ${name}`, async () => {
- const { stdout, stderr } = await execFileAsync('node', [
- 'dist/cli.js',
- 'call',
- url,
- 'read_wiki_structure',
- 'repoName:facebook/react',
- '--output',
- 'json',
- ]);
- const normalized = stdout.trim() || stderr.trim();
- // Response comes back as a JS-object literal string; just assert it contains the section list.
- expect(normalized).toContain('Available pages for facebook/react');
- expect(normalized).toContain('Overview');
- }, 30_000);
+ it('lists wiki structure via streamable-http', async () => {
+ const { stdout, stderr } = await execFileAsync('node', [
+ 'dist/cli.js',
+ 'call',
+ STREAMABLE_HTTP_URL,
+ 'read_wiki_structure',
+ 'repoName:facebook/react',
+ '--output',
+ 'json',
+ ]);
+ const normalized = stdout.trim() || stderr.trim();
+ expect(normalized).toContain('Available pages for facebook/react');
+ expect(normalized).toContain('Overview');
+ }, 30_000);
- it(`prints plain text when default output is used via ${name}`, async () => {
- const { stdout, stderr } = await execFileAsync('node', [
- 'dist/cli.js',
- 'call',
- url,
- 'read_wiki_structure',
- 'repoName:facebook/react',
- ]);
- const normalized = (stdout || stderr).trim();
- expect(normalized).toContain('Available pages for facebook/react');
- // Ensure we rendered the text content, not the JSON envelope.
- expect(normalized).not.toContain('"type"');
- expect(normalized.startsWith('{')).toBe(false);
- }, 30_000);
- });
+ it('prints the readable result when default output is used via streamable-http', async () => {
+ const { stdout, stderr } = await execFileAsync('node', [
+ 'dist/cli.js',
+ 'call',
+ STREAMABLE_HTTP_URL,
+ 'read_wiki_structure',
+ 'repoName:facebook/react',
+ ]);
+ const normalized = (stdout || stderr).trim();
+ expect(normalized).toContain('Available pages for facebook/react');
+ expect(normalized).toContain('Overview');
+ expect(normalized).not.toContain('"type"');
+ }, 30_000);
+
+ it('reports the deprecated sse endpoint as a structured 410 issue', async () => {
+ const { stdout, stderr } = await execFileAsync('node', [
+ 'dist/cli.js',
+ 'call',
+ SSE_URL,
+ 'read_wiki_structure',
+ 'repoName:facebook/react',
+ '--output',
+ 'json',
+ ]);
+ const normalized = stdout.trim() || stderr.trim();
+ expect(normalized).toContain('"statusCode": 410');
+ expect(normalized).toContain('"kind": "http"');
+ }, 30_000);
});