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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/integrations/claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ GET https://<your-host>/.well-known/oauth-protected-resource/mcp/<server-id>
GET https://<your-host>/.well-known/oauth-authorization-server
```

### Test with the MCP Inspector

The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) shows what this server exposes from the outside: `tools/list`, input schemas, a test call and the raw JSON-RPC messages. On the server page, open **Quick Connect → MCP Inspector** and run the command it shows (`npx @modelcontextprotocol/inspector --server-url <endpoint> --transport http`, Node 22.19+); the Inspector opens at http://localhost:6274 already pointed at this server. Sign in with OAuth, or add an `X-API-Key` header in the Inspector's server settings. Passing `--header "X-API-Key: <your key>"` also works, but leaves the key in your shell history. Never put a key in a link.

---

## Troubleshooting
Expand Down
32 changes: 32 additions & 0 deletions packages/frontend/src/app/mcp-server/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { cn } from '@/lib/utils';
import { ConnectionCheck } from '@/components/connection-check';
import { copyText } from '@/lib/clipboard';
import { CLAUDE_DIRECTORY_URL } from '@/lib/marketing';
import { INSPECTOR_URL, inspectorCommand } from '@/lib/inspector-url';

// Opens claude.ai straight on its "Add custom connector" dialog. Connectors
// moved from Settings to Customize → Connectors; the old settings URL now only
Expand Down Expand Up @@ -355,6 +356,7 @@ export default function McpServerDetailPage() {
{ id: 'muse', name: 'Meta Muse', init: 'Mu', tone: 'info' },
{ id: 'gemini', name: 'Gemini CLI', init: 'Ge', tone: 'pink' },
{ id: 'windsurf', name: 'Windsurf', init: 'Wi', tone: 'success' },
{ id: 'inspector', name: 'MCP Inspector', init: 'In', tone: 'neutral' },
];

const codeBlock = (text: string, label: string) => (
Expand Down Expand Up @@ -618,6 +620,36 @@ Auth: OAuth`;
{codeBlock(windsurfConfig, 'windsurf')}
</div>
);
case 'inspector': {
const cmd = inspectorCommand(endpointUrl);
return (
<div className="space-y-4">
<p className="text-[13px] leading-[1.55] text-[var(--text-2)]">
Run this command in your terminal (Node 22.19 or later). It starts the Inspector already pointed at this server:
</p>
<div className="flex gap-2">
<code className="flex-1 break-all rounded-[8px] border border-[var(--border)] bg-[var(--surface-2)] px-[11px] py-[9px] font-mono text-[12px] text-[var(--text-2)]">{cmd}</code>
<button
onClick={() => handleCopy(cmd, 'modal-inspector')}
className={cn(buttonVariants({ variant: 'secondary', size: 'md' }), 'flex-shrink-0')}
>
{copied === 'modal-inspector' ? 'Copied!' : 'Copy'}
</button>
</div>
<p className="text-[13px] leading-[1.55] text-[var(--text-2)]">
The Inspector opens in your browser. If it doesn&apos;t, open it here:
</p>
{linkAction(INSPECTOR_URL, 'Open in MCP Inspector', true)}
<p className="text-xs leading-[1.55] text-[var(--text-3)]">
The Inspector hands its session token (<code className="rounded bg-[var(--surface-2)] px-1 font-mono">MCP_INSPECTOR_API_TOKEN</code>) to the page it opens, so you don&apos;t need to copy it.
</p>
<p className="text-[13px] leading-[1.55] text-[var(--text-2)]">
Sign in with OAuth when the Inspector asks (connect, sign in and approve access), or use an API key: set an <code className="rounded bg-[var(--surface-2)] px-1 font-mono text-xs">X-API-Key</code> header in the server&apos;s settings in the Inspector. You can also add <code className="rounded bg-[var(--surface-2)] px-1 font-mono text-xs">--header &quot;X-API-Key: &lt;your key&gt;&quot;</code> to the command, but the key then stays in your shell history.
</p>
{endpointRow('modal-inspector-url')}
</div>
);
}
default:
return null;
}
Expand Down
14 changes: 14 additions & 0 deletions packages/frontend/src/lib/inspector-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/** Where `npx @modelcontextprotocol/inspector` serves its web UI (v2 default port). */
export const INSPECTOR_URL = 'http://localhost:6274/';

/**
* Command that starts the MCP Inspector already pointed at this server.
*
* Inspector v2 ignores `?serverUrl=` deep links unless they also carry the
* per-launch session token (`autoConnect`), which is secret, so we pre-fill the
* connection through CLI flags instead. Only the server URL goes in here:
* never API keys or tokens.
*/
export function inspectorCommand(endpointUrl: string): string {
return `npx @modelcontextprotocol/inspector --server-url "${endpointUrl}" --transport http`;
}
18 changes: 18 additions & 0 deletions packages/frontend/tests/e2e/mcp-server-connect-clients.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,22 @@ test.describe('MCP server Quick Connect', () => {
await expect(modal).toContainText('US and Canada');
await expect(modal.getByTestId('claude-directory')).toHaveCount(0);
});

for (const mode of ['cloud', 'self-hosted'] as const) {
test(`MCP Inspector shows a pre-filled command, a plain link and sign-in steps (${mode})`, async ({ page }) => {
await openServerPage(page, mode);
await openClient(page, 'MCP Inspector');

const modal = page.locator('div.fixed').filter({ hasText: 'Connect to MCP Inspector' });

// The command points the Inspector at exactly the endpoint shown on the page.
const endpoint = (await modal.locator('code', { hasText: /\/mcp\/s1$/ }).textContent())!.trim();
const command = modal.locator('code', { hasText: 'npx @modelcontextprotocol/inspector' });
await expect(command).toHaveText(`npx @modelcontextprotocol/inspector --server-url "${endpoint}" --transport http`);

// The link opens the local Inspector with no query string, so no credentials can leak.
await expect(modal.getByRole('link', { name: 'Open in MCP Inspector' })).toHaveAttribute('href', 'http://localhost:6274/');
await expect(modal).toContainText('X-API-Key');
});
}
});
Loading