Skip to content

Latest commit

 

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MCP Explorer

A web-based GUI for exploring and interacting with Model Context Protocol (MCP) servers. Connect to any MCP server, browse its capabilities, and execute tools, read resources, and test prompts — all from your browser.

Python 3.13+ License: MIT

Features

  • Connect to any MCP server — supports both Streamable HTTP and SSE transports (auto-detects)
  • Both protocol eras — speaks the stateless 2026-07-28 spec and the older initialize-handshake revisions, negotiated automatically or pinned by hand
  • Authentication — OAuth 2.1 (PKCE, discovery, CIMD/DCR), Bearer token, custom header, or none
  • Browse Tools — list all tools, view schemas, fill in parameters, and execute them
  • Browse Resources — list and read resources, including URI templates (repo://{owner}/{name}/blob/{path}), which get a form and are expanded per RFC 6570
  • Browse Prompts — list prompts, fill in arguments, and retrieve rendered output
  • Parameter Store — save and reuse frequently used parameter values across sessions
  • URL History — remembers previously connected server URLs
  • Clean UI — minimal, responsive interface with tabbed navigation

Quick Start

Prerequisites

  • Python 3.13+
  • uv (recommended) or pip

Install & Run

# Clone the repo
git clone https://github.com/ventz/mcp-explorer.git
cd mcp-explorer

# Install dependencies
uv sync

# Run the server
uv run python app.py

Open http://localhost:8000 in your browser.

Using pip

pip install fastapi 'uvicorn[standard]' jinja2 'mcp>=2.0.0'
python app.py

Usage

  1. Enter an MCP server URL (e.g., http://localhost:3000/mcp)
  2. Optionally configure authentication (OAuth 2.1, Bearer token, or custom header)
  3. Optionally pick a protocol mode — Auto suits almost everything
  4. Click Connect
  5. Use the tabs to explore Tools, Resources, and Prompts
  6. Select an item to view its details, fill in parameters, and execute

The badge next to the status dot shows the negotiated protocol version and the transport actually in use.

OAuth 2.1

Select OAuth 2.1 as the auth type and click Connect. Nothing else is required — no token to paste, no client to pre-register. The explorer follows the MCP authorization spec end to end:

  1. It calls the server, which answers 401 with a pointer to its metadata: WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"
  2. It fetches that Protected Resource Metadata (RFC 9728) to learn which authorization server to trust.
  3. It discovers that server's metadata, registers a client (or uses one you supplied), and builds an authorization URL with PKCE S256 and an RFC 8707 resource indicator naming the MCP server the token is for.
  4. It opens the consent page in a browser pop-up.
  5. You approve; the authorization server redirects back to http://localhost:8000/oauth/callback; the explorer exchanges the code for a token and finishes connecting.

Important

Allow pop-ups for localhost:8000. Step 4 is a real browser navigation to your identity provider — it cannot happen inside the page. If your browser blocks the pop-up, the connection cannot complete on its own.

You are not stuck if that happens: an Open authorization page ↗ link appears in the OAuth row for as long as the grant is pending. Click it to run the same flow in a normal tab. Better, allow pop-ups once for this origin:

  • Chrome / Edge — click the blocked-pop-up icon in the address bar → Always allow pop-ups and redirects from http://localhost:8000
  • FirefoxOptions on the yellow notification bar → Allow pop-ups for localhost
  • SafariSafari → Settings → Websites → Pop-up Windows → set localhost to Allow

Closing the consent window before approving cancels the attempt and the explorer says so, rather than waiting for the grant to time out.

Every field on the OAuth row is optional. Reach for them only when the defaults do not fit your provider:

Field When you need it Example
Scope The authorization server publishes no scopes_supported, but still rejects a request that omits scope. Overrides discovery. Treated as a floor — if the server later demands more, the extra scopes are added rather than replacing yours. notes:read notes:write
Client ID You registered a client with your provider by hand and want to skip registration. mcp-explorer-local
Client secret Your pre-registered client is confidential rather than public. Stored only if Remember is ticked. s3cr3t-value-from-your-idp
Client metadata URL You host a Client ID Metadata Document. Must be an HTTPS URL with a path, and the document's own client_id must equal that URL exactly. Preferred by the spec over the now-deprecated Dynamic Client Registration. https://apps.example.com/mcp-explorer/client.json

All values above are illustrative. Substitute the ones your own identity provider issues.

How the client is identified, in the order the explorer tries:

  1. Client ID you supplied — used as-is, no registration request.
  2. Client Metadata URL (CIMD) — used when the authorization server advertises client_id_metadata_document_supported.
  3. Dynamic Client Registration — the deprecated fallback, used only when the server supports nothing better.

Tokens are held in memory only. They are reused while the app is running, so reconnecting to the same server does not prompt you again, and they are gone the moment the process exits. Sign out discards them immediately and forces a fresh grant on the next connect.

If the server later demands a permission the current token lacks (403 insufficient_scope), the explorer starts a step-up grant and re-opens the pop-up mid-session — the same pop-up allowance applies.

Behind a proxy or on a non-default origin, set MCP_EXPLORER_REDIRECT_URI to the callback URL the browser can actually reach, and register that exact URL with your provider:

MCP_EXPLORER_REDIRECT_URI="https://mcp-explorer.internal.example/oauth/callback" \
  uv run python app.py

Resource templates

Servers can expose resources as parameterised URI templates rather than fixed URIs. Those appear in the Resources tab marked TEMPLATE, listed after the concrete resources.

Selecting one builds a form from the template's variables. Filling it in and clicking Read Resource expands the template (RFC 6570) and reads the resulting URI, which is shown above the form so you can see exactly what was requested:

repo://{owner}/{name}/blob/{path}      →      repo://acme/widgets/blob/src%2Fmain.py

Values are percent-encoded, so a value containing / or ? becomes part of the variable rather than a new path segment or query string. The simple ({var}), reserved ({+var}), fragment ({#var}), path ({/var}), label ({.var}), and query ({?var} / {&var}) operators are all supported; templates are also fed by the Parameter Store like any other form.

A server that implements no templates is not an error — many answer resources/templates/list with "method not found", which is treated as an empty list rather than a failure.

Project Structure

mcp-explorer/
├── app.py              # FastAPI backend — MCP client + API routes
├── oauth.py            # OAuth 2.1 plumbing (token storage, browser redirect bridge)
├── pyproject.toml       # Python project metadata & dependencies
├── uv.lock              # Pinned dependency versions (committed for reproducible installs)
├── templates/
│   └── index.html       # Main HTML template
└── static/
    ├── app.js           # Frontend application logic
    └── style.css        # Styles

API Endpoints

Method Path Description
GET / Web UI
GET /health Health check
POST /api/connect Connect to an MCP server
POST /api/disconnect Disconnect
GET /api/status Connection status
GET /api/tools List available tools
POST /api/tools/call Execute a tool
GET /api/resources List resources and URI templates
POST /api/resources/read Read a resource (or an expanded template)
GET /api/prompts List available prompts
GET /api/ping Liveness probe (polled by the UI)
POST /api/prompts/get Get a rendered prompt
GET /oauth/callback OAuth 2.1 authorization redirect target
POST /api/oauth/forget Discard cached OAuth tokens

Configuration

All optional; the defaults suit local use.

Variable Default Purpose
MCP_EXPLORER_HOST 127.0.0.1 Bind address. Only change if you understand the exposure — see below.
MCP_EXPLORER_BLOCK_PRIVATE unset Set to 1 to also refuse loopback and RFC1918 targets.
MCP_EXPLORER_REDIRECT_URI derived from the request OAuth callback URL, for running behind a proxy.
MCP_EXPLORER_ALLOWED_HOSTS localhost,127.0.0.1,::1 Extra Host values to accept, comma-separated.

Security & Deployment Notes

This is a local developer tool. It connects to whatever URL you give it and has no authentication of its own, so keep the following in mind:

  • Binds to 127.0.0.1 by default. Only processes on your machine can reach it. Override with MCP_EXPLORER_HOST only if you understand that exposing the proxy lets anyone who can reach it connect outward through your machine.
  • SSRF guard. Requests to link-local (including the cloud metadata address 169.254.169.254), multicast, and reserved addresses are always refused. Loopback and private (RFC1918) addresses are allowed by default so you can reach local MCP servers; set MCP_EXPLORER_BLOCK_PRIVATE=1 to refuse those too (useful if you ever expose the server). The guard covers every outbound request, including the authorization-server, token, and registration endpoints an OAuth-protected server names in its own metadata.
  • Host header is pinned. Only localhost, 127.0.0.1, and ::1 are accepted, which is what makes DNS rebinding (resolving an attacker-controlled domain to 127.0.0.1 to gain same-origin access to this app) fail. Add more with MCP_EXPLORER_ALLOWED_HOSTS=host1,host2.
  • Cross-site writes are rejected. State-changing /api/* requests must come from this app's own origin, so a page you happen to have open cannot drive the proxy on your behalf. Command-line clients are unaffected.
  • Security headers — a strict Content-Security-Policy (no inline script), X-Frame-Options: DENY, nosniff, no-referrer, and Cross-Origin-Opener-Policy: same-origin, which severs window.opener for the authorization pop-up.
  • Run as a single worker. Connection state is held in-process, so do not run under multiple uvicorn workers — each worker would have its own state.
  • Auth credentials are only saved to localStorage when you tick Remember; otherwise the token is used for the connection but not stored.
  • OAuth tokens never touch disk. They live in memory for the life of the process, so a restart means re-authorizing.

Protocol

Built on the official mcp Python SDK (v2), which speaks both protocol eras:

Mode What happens on connect
Auto (default) Probes server/discover; falls back to the initialize handshake for pre-2026 servers. Picks the newest version both sides support, either era.
2026-07-28 Pins the stateless spec with no negotiation round trip. One server/discover still runs to confirm the endpoint answers and to fill in identity.
Legacy Forces the initialize handshake (2025-11-25 and earlier).

2026-07-28 is stateless — there is no session id, no initialize, and ping no longer exists, so liveness is probed with server/discover instead. Legacy connections still terminate the session (DELETE /mcp) on disconnect. The negotiated version and transport are reported by /api/status and shown in the UI.

License

MIT © Ventz Petkov

About

Powerful MCP Explorer - because the official NXP Inspector (@modelcontextprotocol/inspector) is terrible! Also this has an API layer!

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages