Showing preview only (1,169K chars total). Download the full file or copy to clipboard to get everything.
Repository: modelcontextprotocol/inspector
Branch: main
Commit: 7c8b031ffac6
Files: 185
Total size: 1.1 MB
Directory structure:
gitextract_mkor3vr7/
├── .dockerignore
├── .git-blame-ignore-revs
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ └── bug_report.md
│ ├── dependabot.yml
│ ├── pull_request_template.md
│ └── workflows/
│ ├── claude.yml
│ ├── cli_tests.yml
│ ├── e2e_tests.yml
│ └── main.yml
├── .gitignore
├── .husky/
│ └── pre-commit
├── .mcp.json
├── .node-version
├── .npmrc
├── .prettierignore
├── .prettierrc
├── AGENTS.md
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── SECURITY.md
├── cli/
│ ├── LICENSE
│ ├── __tests__/
│ │ ├── README.md
│ │ ├── cli.test.ts
│ │ ├── headers.test.ts
│ │ ├── helpers/
│ │ │ ├── assertions.ts
│ │ │ ├── cli-runner.ts
│ │ │ ├── fixtures.ts
│ │ │ ├── test-fixtures.ts
│ │ │ ├── test-server-http.ts
│ │ │ └── test-server-stdio.ts
│ │ ├── metadata.test.ts
│ │ └── tools.test.ts
│ ├── package.json
│ ├── scripts/
│ │ └── make-executable.js
│ ├── src/
│ │ ├── cli.ts
│ │ ├── client/
│ │ │ ├── connection.ts
│ │ │ ├── index.ts
│ │ │ ├── prompts.ts
│ │ │ ├── resources.ts
│ │ │ ├── tools.ts
│ │ │ └── types.ts
│ │ ├── error-handler.ts
│ │ ├── index.ts
│ │ ├── transport.ts
│ │ └── utils/
│ │ └── awaitable-log.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── client/
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── bin/
│ │ ├── client.js
│ │ └── start.js
│ ├── components.json
│ ├── e2e/
│ │ ├── cli-arguments.spec.ts
│ │ ├── global-teardown.js
│ │ ├── startup-state.spec.ts
│ │ └── transport-type-dropdown.spec.ts
│ ├── eslint.config.js
│ ├── index.html
│ ├── jest.config.cjs
│ ├── package.json
│ ├── playwright.config.ts
│ ├── postcss.config.js
│ ├── src/
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── __mocks__/
│ │ │ └── styleMock.js
│ │ ├── __tests__/
│ │ │ ├── App.config.test.tsx
│ │ │ ├── App.routing.test.tsx
│ │ │ ├── App.samplingNavigation.test.tsx
│ │ │ └── App.toolsAppsPrefill.test.tsx
│ │ ├── components/
│ │ │ ├── AppRenderer.tsx
│ │ │ ├── AppsTab.tsx
│ │ │ ├── AuthDebugger.tsx
│ │ │ ├── ConsoleTab.tsx
│ │ │ ├── CustomHeaders.tsx
│ │ │ ├── DynamicJsonForm.tsx
│ │ │ ├── ElicitationRequest.tsx
│ │ │ ├── ElicitationTab.tsx
│ │ │ ├── HistoryAndNotifications.tsx
│ │ │ ├── IconDisplay.tsx
│ │ │ ├── JsonEditor.tsx
│ │ │ ├── JsonView.tsx
│ │ │ ├── ListPane.tsx
│ │ │ ├── MetadataTab.tsx
│ │ │ ├── OAuthCallback.tsx
│ │ │ ├── OAuthDebugCallback.tsx
│ │ │ ├── OAuthFlowProgress.tsx
│ │ │ ├── PingTab.tsx
│ │ │ ├── PromptsTab.tsx
│ │ │ ├── ResourceLinkView.tsx
│ │ │ ├── ResourcesTab.tsx
│ │ │ ├── RootsTab.tsx
│ │ │ ├── SamplingRequest.tsx
│ │ │ ├── SamplingTab.tsx
│ │ │ ├── Sidebar.tsx
│ │ │ ├── TasksTab.tsx
│ │ │ ├── ToolResults.tsx
│ │ │ ├── ToolsTab.tsx
│ │ │ ├── __tests__/
│ │ │ │ ├── AppRenderer.test.tsx
│ │ │ │ ├── AppsTab.test.tsx
│ │ │ │ ├── AuthDebugger.test.tsx
│ │ │ │ ├── DynamicJsonForm.array.test.tsx
│ │ │ │ ├── DynamicJsonForm.test.tsx
│ │ │ │ ├── ElicitationRequest.test.tsx
│ │ │ │ ├── ElicitationTab.test.tsx
│ │ │ │ ├── HistoryAndNotifications.test.tsx
│ │ │ │ ├── ListPane.test.tsx
│ │ │ │ ├── MetadataTab.test.tsx
│ │ │ │ ├── ResourcesTab.test.tsx
│ │ │ │ ├── Sidebar.test.tsx
│ │ │ │ ├── ToolsTab.test.tsx
│ │ │ │ ├── samplingRequest.test.tsx
│ │ │ │ └── samplingTab.test.tsx
│ │ │ └── ui/
│ │ │ ├── alert.tsx
│ │ │ ├── button.tsx
│ │ │ ├── checkbox.tsx
│ │ │ ├── combobox.tsx
│ │ │ ├── command.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── input.tsx
│ │ │ ├── label.tsx
│ │ │ ├── popover.tsx
│ │ │ ├── select.tsx
│ │ │ ├── switch.tsx
│ │ │ ├── tabs.tsx
│ │ │ ├── textarea.tsx
│ │ │ ├── toast.tsx
│ │ │ ├── toaster.tsx
│ │ │ └── tooltip.tsx
│ │ ├── index.css
│ │ ├── lib/
│ │ │ ├── __tests__/
│ │ │ │ └── auth.test.ts
│ │ │ ├── auth-types.ts
│ │ │ ├── auth.ts
│ │ │ ├── configurationTypes.ts
│ │ │ ├── constants.ts
│ │ │ ├── hooks/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── useConnection.test.tsx
│ │ │ │ ├── useCompletionState.ts
│ │ │ │ ├── useConnection.ts
│ │ │ │ ├── useCopy.ts
│ │ │ │ ├── useDraggablePane.ts
│ │ │ │ ├── useTheme.ts
│ │ │ │ └── useToast.ts
│ │ │ ├── notificationTypes.ts
│ │ │ ├── oauth-state-machine.ts
│ │ │ ├── types/
│ │ │ │ └── customHeaders.ts
│ │ │ └── utils.ts
│ │ ├── main.tsx
│ │ ├── utils/
│ │ │ ├── __tests__/
│ │ │ │ ├── configUtils.test.ts
│ │ │ │ ├── escapeUnicode.test.ts
│ │ │ │ ├── jsonUtils.test.ts
│ │ │ │ ├── oauthUtils.test.ts
│ │ │ │ ├── paramUtils.test.ts
│ │ │ │ ├── schemaUtils.test.ts
│ │ │ │ └── urlValidation.test.ts
│ │ │ ├── configUtils.ts
│ │ │ ├── escapeUnicode.ts
│ │ │ ├── jsonUtils.ts
│ │ │ ├── metaUtils.ts
│ │ │ ├── oauthUtils.ts
│ │ │ ├── paramUtils.ts
│ │ │ ├── schemaUtils.ts
│ │ │ └── urlValidation.ts
│ │ └── vite-env.d.ts
│ ├── tailwind.config.js
│ ├── tsconfig.app.json
│ ├── tsconfig.jest.json
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── package.json
├── sample-config.json
├── scripts/
│ ├── README.md
│ ├── check-version-consistency.js
│ └── update-version.js
└── server/
├── LICENSE
├── package.json
├── src/
│ ├── index.ts
│ └── mcpProxy.ts
├── static/
│ └── sandbox_proxy.html
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
# Version control
.git
.gitignore
# Node.js
node_modules
npm-debug.log
# Build artifacts
client/dist
client/build
server/dist
server/build
# Environment variables
.env
.env.local
.env.development
.env.test
.env.production
# Editor files
.vscode
.idea
# Logs
logs
*.log
# Testing
coverage
# Docker
Dockerfile
.dockerignore
================================================
FILE: .git-blame-ignore-revs
================================================
================================================
FILE: .gitattributes
================================================
package-lock.json linguist-generated=true
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ""
labels: ""
assignees: ""
---
**Inspector Version**
- [e.g. 0.16.5)
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
**Additional context**
Add any other context about the problem here.
**Version Consideration**
Inspector V2 is under development to address architectural and UX improvements. During this time, V1 contributions should focus on **bug fixes and MCP spec compliance**. See [CONTRIBUTING.md](../../CONTRIBUTING.md) for more details.
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
================================================
FILE: .github/pull_request_template.md
================================================
## Summary
<!-- Provide a brief description of what this PR does -->
> **Note:** Inspector V2 is under development to address architectural and UX improvements. During this time, V1 contributions should focus on **bug fixes and MCP spec compliance**. See [CONTRIBUTING.md](../CONTRIBUTING.md) for more details.
## Type of Change
<!-- Mark the relevant option with an "x" -->
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Documentation update
- [ ] Refactoring (no functional changes)
- [ ] Test updates
- [ ] Build/CI improvements
## Changes Made
<!-- Describe the changes in detail. Include screenshots/recordings if applicable -->
## Related Issues
<!-- Link to related issues using #issue_number or "Fixes #issue_number" -->
## Testing
<!-- Describe how you tested these changes, where applicable -->
- [ ] Tested in UI mode
- [ ] Tested in CLI mode
- [ ] Tested with STDIO transport
- [ ] Tested with SSE transport
- [ ] Tested with Streamable HTTP transport
- [ ] Added/updated automated tests
- [ ] Manual testing performed
### Test Results and/or Instructions
<!-- Provide steps for reviewers to test your changes -->
Screenshots are encouraged to share your testing results for this change.
## Checklist
- [ ] Code follows the style guidelines (ran `npm run prettier-fix`)
- [ ] Self-review completed
- [ ] Code is commented where necessary
- [ ] Documentation updated (README, comments, etc.)
## Breaking Changes
<!-- If this is a breaking change, describe the impact and migration path -->
## Additional Context
<!-- Add any other context, screenshots, or information about the PR here -->
================================================
FILE: .github/workflows/claude.yml
================================================
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude:
if: |
(
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
)
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read
steps:
- name: Get PR details
if: |
(github.event_name == 'issue_comment' && github.event.issue.pull_request) ||
github.event_name == 'pull_request_review_comment' ||
github.event_name == 'pull_request_review'
id: pr
uses: actions/github-script@v8
with:
script: |
let prNumber;
if (context.eventName === 'issue_comment') {
prNumber = context.issue.number;
} else {
prNumber = context.payload.pull_request.number;
}
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
core.setOutput('sha', pr.data.head.sha);
core.setOutput('repo', pr.data.head.repo.full_name);
- name: Checkout PR branch
if: steps.pr.outcome == 'success'
uses: actions/checkout@v6
with:
ref: ${{ steps.pr.outputs.sha }}
repository: ${{ steps.pr.outputs.repo }}
fetch-depth: 0
- name: Checkout repository
if: steps.pr.outcome != 'success'
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# Allow Claude to read CI results on PRs
additional_permissions: |
actions: read
# Trigger when assigned to an issue
assignee_trigger: "claude"
claude_args: |
--mcp-config .mcp.json
--allowedTools "Bash,mcp__mcp-docs"
--append-system-prompt "If posting a comment to GitHub, give a concise summary of the comment at the top and put all the details in a <details> block. When working on MCP-related code or reviewing MCP-related changes, use the mcp-docs MCP server to look up the latest protocol documentation. For schema details, reference https://github.com/modelcontextprotocol/modelcontextprotocol/tree/main/schema which contains versioned schemas in JSON (schema.json) and TypeScript (schema.ts) formats."
================================================
FILE: .github/workflows/cli_tests.yml
================================================
name: CLI Tests
on:
push:
paths:
- "cli/**"
pull_request:
paths:
- "cli/**"
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./cli
steps:
- uses: actions/checkout@v6
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version-file: package.json
cache: npm
- name: Install dependencies
run: |
cd ..
npm ci --ignore-scripts
- name: Build CLI
run: npm run build
- name: Run tests
run: npm test
env:
NPM_CONFIG_YES: true
CI: true
================================================
FILE: .github/workflows/e2e_tests.yml
================================================
name: Playwright Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
# Installing Playwright dependencies can take quite awhile, and also depends on GitHub CI load.
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y libwoff1
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
id: setup_node
with:
node-version-file: package.json
cache: npm
# Cache Playwright browsers
- name: Cache Playwright browsers
id: cache-playwright
uses: actions/cache@v5
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 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'
- name: Run Playwright tests
id: playwright-tests
run: npm run test:e2e
- name: Upload Playwright Report and Screenshots
uses: actions/upload-artifact@v6
if: steps.playwright-tests.conclusion != 'skipped'
with:
name: playwright-report
path: |
client/playwright-report/
client/test-results/
client/results.json
retention-days: 2
- name: Publish Playwright Test Summary
uses: daun/playwright-report-summary@v3
if: steps.playwright-tests.conclusion != 'skipped'
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
icon-style: "emojis"
custom-info: |
**Test Environment:** Ubuntu Latest, Node.js ${{ steps.setup_node.outputs.node-version }}
**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"
================================================
FILE: .github/workflows/main.yml
================================================
on:
push:
branches:
- main
pull_request:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Check formatting
run: npx prettier --check .
- uses: actions/setup-node@v6
with:
node-version-file: package.json
cache: npm
# Working around https://github.com/npm/cli/issues/4828
# - run: npm ci
- run: npm install --no-package-lock
- name: Check version consistency
run: npm run check-version
- name: Check linting
working-directory: ./client
run: npm run lint
- name: Run client tests
working-directory: ./client
run: npm test
- run: npm run build
publish:
runs-on: ubuntu-latest
if: github.event_name == 'release'
environment: release
needs: build
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: package.json
cache: npm
registry-url: "https://registry.npmjs.org"
# Working around https://github.com/npm/cli/issues/4828
# - run: npm ci
- run: npm install --no-package-lock
# TODO: Add --provenance once the repo is public
- run: npm run publish-all
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
publish-github-container-registry:
runs-on: ubuntu-latest
if: github.event_name == 'release'
environment: release
needs: build
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- uses: actions/checkout@v6
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push Docker image
id: push
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v3
with:
subject-name: ghcr.io/${{ github.repository }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
================================================
FILE: .gitignore
================================================
.DS_Store
.vscode
.idea
node_modules/
*-workspace/
server/build
client/dist
client/tsconfig.app.tsbuildinfo
client/tsconfig.node.tsbuildinfo
cli/build
test-output
tool-test-output
metadata-test-output
# symlinked by `npm run link:sdk`:
sdk
client/playwright-report/
client/results.json
client/test-results/
client/e2e/test-results/
mcp.json
.claude/settings.local.json
================================================
FILE: .husky/pre-commit
================================================
npx lint-staged
git update-index --again
================================================
FILE: .mcp.json
================================================
{
"mcpServers": {
"mcp-docs": {
"type": "http",
"url": "https://modelcontextprotocol.io/mcp"
}
}
}
================================================
FILE: .node-version
================================================
22.x.x
================================================
FILE: .npmrc
================================================
registry="https://registry.npmjs.org/"
@modelcontextprotocol:registry="https://registry.npmjs.org/"
================================================
FILE: .prettierignore
================================================
packages
server/build
CODE_OF_CONDUCT.md
SECURITY.md
mcp.json
.claude/settings.local.json
================================================
FILE: .prettierrc
================================================
================================================
FILE: AGENTS.md
================================================
# MCP Inspector Development Guide
> **Note:** Inspector V2 is under development to address architectural and UX improvements. During this time, V1 contributions should focus on **bug fixes and MCP spec compliance**. See [CONTRIBUTING.md](CONTRIBUTING.md) for more details.
## Build Commands
- Build all: `npm run build`
- Build client: `npm run build-client`
- Build server: `npm run build-server`
- Development mode: `npm run dev` (use `npm run dev:windows` on Windows)
- Format code: `npm run prettier-fix`
- Client lint: `cd client && npm run lint`
## Code Style Guidelines
- Use TypeScript with proper type annotations
- Follow React functional component patterns with hooks
- Use ES modules (import/export) not CommonJS
- Use Prettier for formatting (auto-formatted on commit)
- Follow existing naming conventions:
- camelCase for variables and functions
- PascalCase for component names and types
- kebab-case for file names
- Use async/await for asynchronous operations
- Implement proper error handling with try/catch blocks
- Use Tailwind CSS for styling in the client
- Keep components small and focused on a single responsibility
## Project Organization
The project is organized as a monorepo with workspaces:
- `client/`: React frontend with Vite, TypeScript and Tailwind
- `server/`: Express backend with TypeScript
- `cli/`: Command-line interface for testing and invoking MCP server methods directly
================================================
FILE: CLAUDE.md
================================================
@./AGENTS.md
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
mcp-coc@anthropic.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Model Context Protocol Inspector
Thanks for your interest in contributing! This guide explains how to get involved.
## Getting Started
1. Fork the repository and clone it locally
2. Install dependencies with `npm install`
3. Run `npm run dev` to start both client and server in development mode
4. Use the web UI at http://localhost:6274 to interact with the inspector
## Inspector V2 Development
We're actively developing **Inspector V2** to address architectural and UX improvements. We invite you to follow progress and participate in the Inspector V2 Working Group in [Discord](https://modelcontextprotocol.io/community/communication), [weekly meetings](https://meet.modelcontextprotocol.io/tag/inspector-v2-wg), and [GitHub Discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/categories/meeting-notes-other) (where notes are posted after meetings).
**Current version (V1) contribution scope:**
- Bug fixes and MCP spec compliance are actively maintained
- Documentation updates are always appreciated
- Major changes will be directed to V2 development
## Development Process & Pull Requests
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` 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
7. Submit a pull request
8. PRs will be reviewed by maintainers
## Code of Conduct
This project follows our [Code of Conduct](CODE_OF_CONDUCT.md). Please read it before contributing.
## Security
If you find a security vulnerability, please refer to our [Security Policy](SECURITY.md) for reporting instructions.
## Questions?
Feel free to [open an issue](https://github.com/modelcontextprotocol/inspector/issues) for questions or join the MCP Contributor [Discord server](https://modelcontextprotocol.io/community/communication). Also, please see notes above on Inspector V2 Development.
## License
By contributing, you agree that your contributions will be licensed under the MIT license.
================================================
FILE: Dockerfile
================================================
# Build stage
FROM node:current-alpine3.22 AS builder
# Set working directory
WORKDIR /app
# Copy package files for installation
COPY package*.json ./
COPY .npmrc ./
COPY client/package*.json ./client/
COPY server/package*.json ./server/
COPY cli/package*.json ./cli/
# Install dependencies
RUN npm ci --ignore-scripts
# Copy source files
COPY . .
# Build the application
RUN npm run build
# Production stage
FROM node:24-slim
WORKDIR /app
# Copy package files for production
COPY package*.json ./
COPY .npmrc ./
COPY client/package*.json ./client/
COPY server/package*.json ./server/
COPY cli/package*.json ./cli/
# Install only production dependencies
RUN npm ci --omit=dev --ignore-scripts
# Copy built files from builder stage
COPY --from=builder /app/client/dist ./client/dist
COPY --from=builder /app/client/bin ./client/bin
COPY --from=builder /app/server/build ./server/build
COPY --from=builder /app/cli/build ./cli/build
# Set default port values as environment variables
ENV CLIENT_PORT=6274
ENV SERVER_PORT=6277
# Document which ports the application uses internally
EXPOSE ${CLIENT_PORT} ${SERVER_PORT}
# Use ENTRYPOINT with CMD for arguments
ENTRYPOINT ["npm", "start"]
================================================
FILE: LICENSE
================================================
The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 ("Apache-2.0"). All new code and specification contributions to the project are licensed under Apache-2.0. Documentation contributions (excluding specifications) are licensed under CC-BY-4.0.
Contributions for which relicensing consent has been obtained are licensed under Apache-2.0. Contributions made by authors who originally licensed their work under the MIT License and who have not yet granted explicit permission to relicense remain licensed under the MIT License.
No rights beyond those granted by the applicable original license are conveyed for such contributions.
---
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright
owner or by an individual or Legal Entity authorized to submit on behalf
of the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
---
MIT License
Copyright (c) 2024-2025 Model Context Protocol a Series of LF Projects, LLC.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
Creative Commons Attribution 4.0 International (CC-BY-4.0)
Documentation in this project (excluding specifications) is licensed under
CC-BY-4.0. See https://creativecommons.org/licenses/by/4.0/legalcode for
the full license text.
================================================
FILE: README.md
================================================
# MCP Inspector
The MCP inspector is a developer tool for testing and debugging MCP servers.

## Architecture Overview
The MCP Inspector consists of two main components that work together:
- **MCP Inspector Client (MCPI)**: A React-based web UI that provides an interactive interface for testing and debugging MCP servers
- **MCP Proxy (MCPP)**: A Node.js server that acts as a protocol bridge, connecting the web UI to MCP servers via various transport methods (stdio, SSE, streamable-http)
Note that the proxy is not a network proxy for intercepting traffic. Instead, it functions as both an MCP client (connecting to your MCP server) and an HTTP server (serving the web UI), enabling browser-based interaction with MCP servers that use different transport protocols.
## Running the Inspector
### Requirements
- Node.js: ^22.7.5
### Quick Start (UI mode)
To get up and running right away with the UI, just execute the following:
```bash
npx @modelcontextprotocol/inspector
```
The server will start up and the UI will be accessible at `http://localhost:6274`.
### Docker Container
You can also start it in a Docker container with the following command:
```bash
docker run --rm \
-p 127.0.0.1:6274:6274 \
-p 127.0.0.1:6277:6277 \
-e HOST=0.0.0.0 \
-e MCP_AUTO_OPEN_ENABLED=false \
ghcr.io/modelcontextprotocol/inspector:latest
```
### From an MCP server repository
To inspect an MCP server implementation, there's no need to clone this repo. Instead, use `npx`. For example, if your server is built at `build/index.js`:
```bash
npx @modelcontextprotocol/inspector node build/index.js
```
You can pass both arguments and environment variables to your MCP server. Arguments are passed directly to your server, while environment variables can be set using the `-e` flag:
```bash
# Pass arguments only
npx @modelcontextprotocol/inspector node build/index.js arg1 arg2
# Pass environment variables only
npx @modelcontextprotocol/inspector -e key=value -e key2=$VALUE2 node build/index.js
# Pass both environment variables and arguments
npx @modelcontextprotocol/inspector -e key=value -e key2=$VALUE2 node build/index.js arg1 arg2
# Use -- to separate inspector flags from server arguments
npx @modelcontextprotocol/inspector -e key=$VALUE -- node build/index.js -e server-flag
```
The inspector runs both an MCP Inspector (MCPI) client UI (default port 6274) and an MCP Proxy (MCPP) server (default port 6277). Open the MCPI client UI in your browser to use the inspector. (These ports are derived from the T9 dialpad mapping of MCPI and MCPP respectively, as a mnemonic). You can customize the ports if needed:
```bash
CLIENT_PORT=8080 SERVER_PORT=9000 npx @modelcontextprotocol/inspector node build/index.js
```
For more details on ways to use the inspector, see the [Inspector section of the MCP docs site](https://modelcontextprotocol.io/docs/tools/inspector). For help with debugging, see the [Debugging guide](https://modelcontextprotocol.io/docs/tools/debugging).
### Servers File Export
The MCP Inspector provides convenient buttons to export server launch configurations for use in clients such as Cursor, Claude Code, or the Inspector's CLI. The file is usually called `mcp.json`.
- **Server Entry** - Copies a single server configuration entry to your clipboard. This can be added to your `mcp.json` file inside the `mcpServers` object with your preferred server name.
**STDIO transport example:**
```json
{
"command": "node",
"args": ["build/index.js", "--debug"],
"env": {
"API_KEY": "your-api-key",
"DEBUG": "true"
}
}
```
**SSE transport example:**
```json
{
"type": "sse",
"url": "http://localhost:3000/events",
"note": "For SSE connections, add this URL directly in Client"
}
```
**Streamable HTTP transport example:**
```json
{
"type": "streamable-http",
"url": "http://localhost:3000/mcp",
"note": "For Streamable HTTP connections, add this URL directly in your MCP Client"
}
```
- **Servers File** - Copies a complete MCP configuration file structure to your clipboard, with your current server configuration added as `default-server`. This can be saved directly as `mcp.json`.
**STDIO transport example:**
```json
{
"mcpServers": {
"default-server": {
"command": "node",
"args": ["build/index.js", "--debug"],
"env": {
"API_KEY": "your-api-key",
"DEBUG": "true"
}
}
}
}
```
**SSE transport example:**
```json
{
"mcpServers": {
"default-server": {
"type": "sse",
"url": "http://localhost:3000/events",
"note": "For SSE connections, add this URL directly in Client"
}
}
}
```
**Streamable HTTP transport example:**
```json
{
"mcpServers": {
"default-server": {
"type": "streamable-http",
"url": "http://localhost:3000/mcp",
"note": "For Streamable HTTP connections, add this URL directly in your MCP Client"
}
}
}
```
These buttons appear in the Inspector UI after you've configured your server settings, making it easy to save and reuse your configurations.
For SSE and Streamable HTTP transport connections, the Inspector provides similar functionality for both buttons. The "Server Entry" button copies the configuration that can be added to your existing configuration file, while the "Servers File" button creates a complete configuration file containing the URL for direct use in clients.
You can paste the Server Entry into your existing `mcp.json` file under your chosen server name, or use the complete Servers File payload to create a new configuration file.
### Authentication
The inspector supports bearer token authentication for SSE connections. Enter your token in the UI when connecting to an MCP server, and it will be sent in the Authorization header. You can override the header name using the input field in the sidebar.
### Security Considerations
The MCP Inspector includes a proxy server that can run and communicate with local MCP processes. The proxy server should not be exposed to untrusted networks as it has permissions to spawn local processes and can connect to any specified MCP server.
#### Authentication
The MCP Inspector proxy server requires authentication by default. When starting the server, a random session token is generated and printed to the console:
```
🔑 Session token: 3a1c267fad21f7150b7d624c160b7f09b0b8c4f623c7107bbf13378f051538d4
🔗 Open inspector with token pre-filled:
http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=3a1c267fad21f7150b7d624c160b7f09b0b8c4f623c7107bbf13378f051538d4
```
This token must be included as a Bearer token in the Authorization header for all requests to the server. The inspector will automatically open your browser with the token pre-filled in the URL.
**Automatic browser opening** - The inspector now automatically opens your browser with the token pre-filled in the URL when authentication is enabled.
**Alternative: Manual configuration** - If you already have the inspector open:
1. Click the "Configuration" button in the sidebar
2. Find "Proxy Session Token" and enter the token displayed in the proxy console
3. Click "Save" to apply the configuration
The token will be saved in your browser's local storage for future use.
If you need to disable authentication (NOT RECOMMENDED), you can set the `DANGEROUSLY_OMIT_AUTH` environment variable:
```bash
DANGEROUSLY_OMIT_AUTH=true npm start
```
---
**🚨 WARNING 🚨**
Disabling authentication with `DANGEROUSLY_OMIT_AUTH` is incredibly dangerous! Disabling auth leaves your machine open to attack not just when exposed to the public internet, but also **via your web browser**. Meaning, visiting a malicious website OR viewing a malicious advertizement could allow an attacker to remotely compromise your computer. Do not disable this feature unless you truly understand the risks.
Read more about the risks of this vulnerability on Oligo's blog: [Critical RCE Vulnerability in Anthropic MCP Inspector - CVE-2025-49596](https://www.oligo.security/blog/critical-rce-vulnerability-in-anthropic-mcp-inspector-cve-2025-49596)
---
You can also set the token via the `MCP_PROXY_AUTH_TOKEN` environment variable when starting the server:
```bash
MCP_PROXY_AUTH_TOKEN=$(openssl rand -hex 32) npm start
```
#### Local-only Binding
By default, both the MCP Inspector proxy server and client bind only to `localhost` to prevent network access. This ensures they are not accessible from other devices on the network. If you need to bind to all interfaces for development purposes, you can override this with the `HOST` environment variable:
```bash
HOST=0.0.0.0 npm start
```
**Warning:** Only bind to all interfaces in trusted network environments, as this exposes the proxy server's ability to execute local processes and both services to network access.
#### DNS Rebinding Protection
To prevent DNS rebinding attacks, the MCP Inspector validates the `Origin` header on incoming requests. By default, only requests from the client origin are allowed (respects `CLIENT_PORT` if set, defaulting to port 6274). You can configure additional allowed origins by setting the `ALLOWED_ORIGINS` environment variable (comma-separated list):
```bash
ALLOWED_ORIGINS=http://localhost:6274,http://localhost:8000 npm start
```
### Configuration
The MCP Inspector supports the following configuration settings. To change them, click on the `Configuration` button in the MCP Inspector UI:
| Setting | Description | Default |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `MCP_SERVER_REQUEST_TIMEOUT` | Client-side timeout (ms) - Inspector will cancel the request if no response is received within this time. Note: servers may have their own timeouts | 300000 |
| `MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS` | Reset timeout on progress notifications | true |
| `MCP_REQUEST_MAX_TOTAL_TIMEOUT` | Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications) | 60000 |
| `MCP_PROXY_FULL_ADDRESS` | Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577 | "" |
| `MCP_AUTO_OPEN_ENABLED` | Enable automatic browser opening when inspector starts (works with authentication enabled). Only as environment var, not configurable in browser. | true |
**Note on Timeouts:** The timeout settings above control when the Inspector (as an MCP client) will cancel requests. These are independent of any server-side timeouts. For example, if a server tool has a 10-minute timeout but the Inspector's timeout is set to 30 seconds, the Inspector will cancel the request after 30 seconds. Conversely, if the Inspector's timeout is 10 minutes but the server times out after 30 seconds, you'll receive the server's timeout error. For tools that require user interaction (like elicitation) or long-running operations, ensure the Inspector's timeout is set appropriately.
These settings can be adjusted in real-time through the UI and will persist across sessions.
The inspector also supports configuration files to store settings for different MCP servers. This is useful when working with multiple servers or complex configurations:
```bash
npx @modelcontextprotocol/inspector --config path/to/config.json --server everything
```
Example server configuration file:
```json
{
"mcpServers": {
"everything": {
"command": "npx",
"args": ["@modelcontextprotocol/server-everything"],
"env": {
"hello": "Hello MCP!"
}
},
"my-server": {
"command": "node",
"args": ["build/index.js", "arg1", "arg2"],
"env": {
"key": "value",
"key2": "value2"
}
}
}
}
```
#### Transport Types in Config Files
The inspector automatically detects the transport type from your config file. You can specify different transport types:
**STDIO (default):**
```json
{
"mcpServers": {
"my-stdio-server": {
"type": "stdio",
"command": "npx",
"args": ["@modelcontextprotocol/server-everything"]
}
}
}
```
**SSE (Server-Sent Events):**
```json
{
"mcpServers": {
"my-sse-server": {
"type": "sse",
"url": "http://localhost:3000/sse"
}
}
}
```
**Streamable HTTP:**
```json
{
"mcpServers": {
"my-http-server": {
"type": "streamable-http",
"url": "http://localhost:3000/mcp"
}
}
}
```
#### Default Server Selection
You can launch the inspector without specifying a server name if your config has:
1. **A single server** - automatically selected:
```bash
# Automatically uses "my-server" if it's the only one
npx @modelcontextprotocol/inspector --config mcp.json
```
2. **A server named "default-server"** - automatically selected:
```json
{
"mcpServers": {
"default-server": {
"command": "npx",
"args": ["@modelcontextprotocol/server-everything"]
},
"other-server": {
"command": "node",
"args": ["other.js"]
}
}
}
```
> **Tip:** You can easily generate this configuration format using the **Server Entry** and **Servers File** buttons in the Inspector UI, as described in the Servers File Export section above.
You can also set the initial `transport` type, `serverUrl`, `serverCommand`, and `serverArgs` via query params, for example:
```
http://localhost:6274/?transport=sse&serverUrl=http://localhost:8787/sse
http://localhost:6274/?transport=streamable-http&serverUrl=http://localhost:8787/mcp
http://localhost:6274/?transport=stdio&serverCommand=npx&serverArgs=arg1%20arg2
```
You can also set initial config settings via query params, for example:
```
http://localhost:6274/?MCP_SERVER_REQUEST_TIMEOUT=60000&MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS=false&MCP_PROXY_FULL_ADDRESS=http://10.1.1.22:5577
```
Note that if both the query param and the corresponding localStorage item are set, the query param will take precedence.
### From this repository
If you're working on the inspector itself:
Development mode:
```bash
npm run dev
# To co-develop with the typescript-sdk package (assuming it's cloned in ../typescript-sdk; set MCP_SDK otherwise):
npm run dev:sdk "cd sdk && npm run examples:simple-server:w"
# then open http://localhost:3000/mcp as SHTTP in the inspector.
# To go back to the deployed SDK version:
# npm run unlink:sdk && npm i
```
> **Note for Windows users:**
> On Windows, use the following command instead:
>
> ```bash
> npm run dev:windows
> ```
Production mode:
```bash
npm run build
npm start
```
### CLI Mode
CLI mode enables programmatic interaction with MCP servers from the command line, ideal for scripting, automation, and integration with coding assistants. This creates an efficient feedback loop for MCP server development.
```bash
npx @modelcontextprotocol/inspector --cli node build/index.js
```
The CLI mode supports most operations across tools, resources, and prompts. A few examples:
```bash
# Basic usage
npx @modelcontextprotocol/inspector --cli node build/index.js
# With config file
npx @modelcontextprotocol/inspector --cli --config path/to/config.json --server myserver
# List available tools
npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list
# Call a specific tool
npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name mytool --tool-arg key=value --tool-arg another=value2
# Call a tool with JSON arguments
npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name mytool --tool-arg 'options={"format": "json", "max_tokens": 100}'
# List available resources
npx @modelcontextprotocol/inspector --cli node build/index.js --method resources/list
# List available prompts
npx @modelcontextprotocol/inspector --cli node build/index.js --method prompts/list
# 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 --method tools/list
# Connect to a remote MCP server (with custom headers)
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --transport http --method tools/list --header "X-API-Key: your-api-key"
# 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
# List resources from a remote server
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --method resources/list
```
### UI Mode vs CLI Mode: When to Use Each
| Use Case | UI Mode | CLI Mode |
| ------------------------ | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Server Development** | Visual interface for interactive testing and debugging during development | Scriptable commands for quick testing and continuous integration; creates feedback loops with AI coding assistants like Cursor for rapid development |
| **Resource Exploration** | Interactive browser with hierarchical navigation and JSON visualization | Programmatic listing and reading for automation and scripting |
| **Tool Testing** | Form-based parameter input with real-time response visualization | Command-line tool execution with JSON output for scripting |
| **Prompt Engineering** | Interactive sampling with streaming responses and visual comparison | Batch processing of prompts with machine-readable output |
| **Debugging** | Request history, visualized errors, and real-time notifications | Direct JSON output for log analysis and integration with other tools |
| **Automation** | N/A | Ideal for CI/CD pipelines, batch processing, and integration with coding assistants |
| **Learning MCP** | Rich visual interface helps new users understand server capabilities | Simplified commands for focused learning of specific endpoints |
## Tool Input Validation Guidelines
When implementing or modifying tool input parameter handling in the Inspector:
- **Omit optional fields with empty values** - When processing form inputs, omit empty strings or null values for optional parameters, UNLESS the field has an explicit default value in the schema that matches the current value
- **Preserve explicit default values** - If a field schema contains an explicit default (e.g., `default: null`), and the current value matches that default, include it in the request. This is a meaningful value the tool expects
- **Always include required fields** - Preserve required field values even when empty, allowing the MCP server to validate and return appropriate error messages
- **Defer deep validation to the server** - Implement basic field presence checking in the Inspector client, but rely on the MCP server for parameter validation according to its schema
These guidelines maintain clean parameter passing and proper separation of concerns between the Inspector client and MCP servers.
## License
This project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details.
================================================
FILE: SECURITY.md
================================================
# Security Policy
Thank you for helping keep the Model Context Protocol and its ecosystem secure.
## Reporting Security Issues
If you discover a security vulnerability in this repository, please report it through
the [GitHub Security Advisory process](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability)
for this repository.
Please **do not** report security vulnerabilities through public GitHub issues, discussions,
or pull requests.
## What to Include
To help us triage and respond quickly, please include:
- A description of the vulnerability
- Steps to reproduce the issue
- The potential impact
- Any suggested fixes (optional)
================================================
FILE: cli/LICENSE
================================================
The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 ("Apache-2.0"). All new code and specification contributions to the project are licensed under Apache-2.0. Documentation contributions (excluding specifications) are licensed under CC-BY-4.0.
Contributions for which relicensing consent has been obtained are licensed under Apache-2.0. Contributions made by authors who originally licensed their work under the MIT License and who have not yet granted explicit permission to relicense remain licensed under the MIT License.
No rights beyond those granted by the applicable original license are conveyed for such contributions.
---
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright
owner or by an individual or Legal Entity authorized to submit on behalf
of the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
---
MIT License
Copyright (c) 2024-2025 Model Context Protocol a Series of LF Projects, LLC.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
Creative Commons Attribution 4.0 International (CC-BY-4.0)
Documentation in this project (excluding specifications) is licensed under
CC-BY-4.0. See https://creativecommons.org/licenses/by/4.0/legalcode for
the full license text.
================================================
FILE: cli/__tests__/README.md
================================================
# CLI Tests
## Running Tests
```bash
# Run all tests
npm test
# Run in watch mode (useful for test file changes; won't work on CLI source changes without rebuild)
npm run test:watch
# Run specific test file
npm run test:cli # cli.test.ts
npm run test:cli-tools # tools.test.ts
npm run test:cli-headers # headers.test.ts
npm run test:cli-metadata # metadata.test.ts
```
## Test Files
- `cli.test.ts` - Basic CLI functionality: CLI mode, environment variables, config files, resources, prompts, logging, transport types
- `tools.test.ts` - Tool-related tests: Tool discovery, JSON argument parsing, error handling, prompts
- `headers.test.ts` - Header parsing and validation
- `metadata.test.ts` - Metadata functionality: General metadata, tool-specific metadata, parsing, merging, validation
## Helpers
The `helpers/` directory contains shared utilities:
- `cli-runner.ts` - Spawns CLI as subprocess and captures output
- `test-mcp-server.ts` - Standalone stdio MCP server script for stdio transport testing
- `instrumented-server.ts` - In-process MCP test server for HTTP/SSE transports with request recording
- `assertions.ts` - Custom assertion helpers for CLI output validation
- `fixtures.ts` - Test config file generators and temporary directory management
## Notes
- Tests run in parallel across files (Vitest default)
- Tests within a file run sequentially (we have isolated config files and ports, so we could get more aggressive if desired)
- Config files use `crypto.randomUUID()` for uniqueness in parallel execution
- HTTP/SSE servers use dynamic port allocation to avoid conflicts
- Coverage is not used because much of the code that we want to measure is run by a spawned process, so it can't be tracked by Vitest
- /sample-config.json is no longer used by tests - not clear if this file serves some other purpose so leaving it for now
- All tests now use built-in MCP test servers, there are no external dependencies on servers from a registry
================================================
FILE: cli/__tests__/cli.test.ts
================================================
import { describe, it, beforeAll, afterAll, expect } from "vitest";
import { runCli } from "./helpers/cli-runner.js";
import {
expectCliSuccess,
expectCliFailure,
expectValidJson,
} from "./helpers/assertions.js";
import {
NO_SERVER_SENTINEL,
createSampleTestConfig,
createTestConfig,
createInvalidConfig,
deleteConfigFile,
} from "./helpers/fixtures.js";
import { getTestMcpServerCommand } from "./helpers/test-server-stdio.js";
import { createTestServerHttp } from "./helpers/test-server-http.js";
import {
createEchoTool,
createTestServerInfo,
} from "./helpers/test-fixtures.js";
describe("CLI Tests", () => {
describe("Basic CLI Mode", () => {
it("should execute tools/list successfully", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/list",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("tools");
expect(Array.isArray(json.tools)).toBe(true);
// Validate expected tools from test-mcp-server
const toolNames = json.tools.map((tool: any) => tool.name);
expect(toolNames).toContain("echo");
expect(toolNames).toContain("get-sum");
expect(toolNames).toContain("get-annotated-message");
});
it("should fail with nonexistent method", async () => {
const result = await runCli([
NO_SERVER_SENTINEL,
"--cli",
"--method",
"nonexistent/method",
]);
expectCliFailure(result);
});
it("should fail without method", async () => {
const result = await runCli([NO_SERVER_SENTINEL, "--cli"]);
expectCliFailure(result);
});
});
describe("Environment Variables", () => {
it("should accept environment variables", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"-e",
"KEY1=value1",
"-e",
"KEY2=value2",
"--cli",
"--method",
"resources/read",
"--uri",
"test://env",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("contents");
expect(Array.isArray(json.contents)).toBe(true);
expect(json.contents.length).toBeGreaterThan(0);
// Parse the env vars from the resource
const envVars = JSON.parse(json.contents[0].text);
expect(envVars.KEY1).toBe("value1");
expect(envVars.KEY2).toBe("value2");
});
it("should reject invalid environment variable format", async () => {
const result = await runCli([
NO_SERVER_SENTINEL,
"-e",
"INVALID_FORMAT",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
});
it("should handle environment variable with equals sign in value", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"-e",
"API_KEY=abc123=xyz789==",
"--cli",
"--method",
"resources/read",
"--uri",
"test://env",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
const envVars = JSON.parse(json.contents[0].text);
expect(envVars.API_KEY).toBe("abc123=xyz789==");
});
it("should handle environment variable with base64-encoded value", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"-e",
"JWT_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0=",
"--cli",
"--method",
"resources/read",
"--uri",
"test://env",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
const envVars = JSON.parse(json.contents[0].text);
expect(envVars.JWT_TOKEN).toBe(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0=",
);
});
});
describe("Config File", () => {
it("should use config file with CLI mode", async () => {
const configPath = createSampleTestConfig();
try {
const result = await runCli([
"--config",
configPath,
"--server",
"test-stdio",
"--cli",
"--method",
"tools/list",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("tools");
expect(Array.isArray(json.tools)).toBe(true);
expect(json.tools.length).toBeGreaterThan(0);
} finally {
deleteConfigFile(configPath);
}
});
it("should fail when using config file without server name", async () => {
const configPath = createSampleTestConfig();
try {
const result = await runCli([
"--config",
configPath,
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
} finally {
deleteConfigFile(configPath);
}
});
it("should fail when using server name without config file", async () => {
const result = await runCli([
"--server",
"test-stdio",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
});
it("should fail with nonexistent config file", async () => {
const result = await runCli([
"--config",
"./nonexistent-config.json",
"--server",
"test-stdio",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
});
it("should fail with invalid config file format", async () => {
// Create invalid config temporarily
const invalidConfigPath = createInvalidConfig();
try {
const result = await runCli([
"--config",
invalidConfigPath,
"--server",
"test-stdio",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
} finally {
deleteConfigFile(invalidConfigPath);
}
});
it("should fail with nonexistent server in config", async () => {
const configPath = createSampleTestConfig();
try {
const result = await runCli([
"--config",
configPath,
"--server",
"nonexistent",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
} finally {
deleteConfigFile(configPath);
}
});
});
describe("Resource Options", () => {
it("should read resource with URI", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"resources/read",
"--uri",
"demo://resource/static/document/architecture.md",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("contents");
expect(Array.isArray(json.contents)).toBe(true);
expect(json.contents.length).toBeGreaterThan(0);
expect(json.contents[0]).toHaveProperty(
"uri",
"demo://resource/static/document/architecture.md",
);
expect(json.contents[0]).toHaveProperty("mimeType", "text/markdown");
expect(json.contents[0]).toHaveProperty("text");
expect(json.contents[0].text).toContain("Architecture Documentation");
});
it("should fail when reading resource without URI", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"resources/read",
]);
expectCliFailure(result);
});
});
describe("Prompt Options", () => {
it("should get prompt by name", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"prompts/get",
"--prompt-name",
"simple-prompt",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("messages");
expect(Array.isArray(json.messages)).toBe(true);
expect(json.messages.length).toBeGreaterThan(0);
expect(json.messages[0]).toHaveProperty("role", "user");
expect(json.messages[0]).toHaveProperty("content");
expect(json.messages[0].content).toHaveProperty("type", "text");
expect(json.messages[0].content.text).toBe(
"This is a simple prompt for testing purposes.",
);
});
it("should get prompt with arguments", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"prompts/get",
"--prompt-name",
"args-prompt",
"--prompt-args",
"city=New York",
"state=NY",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("messages");
expect(Array.isArray(json.messages)).toBe(true);
expect(json.messages.length).toBeGreaterThan(0);
expect(json.messages[0]).toHaveProperty("role", "user");
expect(json.messages[0]).toHaveProperty("content");
expect(json.messages[0].content).toHaveProperty("type", "text");
// Verify that the arguments were actually used in the response
expect(json.messages[0].content.text).toContain("city=New York");
expect(json.messages[0].content.text).toContain("state=NY");
});
it("should fail when getting prompt without name", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"prompts/get",
]);
expectCliFailure(result);
});
});
describe("Logging Options", () => {
it("should set log level", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
logging: true,
});
try {
const port = await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"logging/setLevel",
"--log-level",
"debug",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate the response - logging/setLevel should return an empty result
const json = expectValidJson(result);
expect(json).toEqual({});
// Validate that the server actually received and recorded the log level
expect(server.getCurrentLogLevel()).toBe("debug");
} finally {
await server.stop();
}
});
it("should reject invalid log level", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"logging/setLevel",
"--log-level",
"invalid",
]);
expectCliFailure(result);
});
});
describe("Combined Options", () => {
it("should handle config file with environment variables", async () => {
const configPath = createSampleTestConfig();
try {
const result = await runCli([
"--config",
configPath,
"--server",
"test-stdio",
"-e",
"CLI_ENV_VAR=cli_value",
"--cli",
"--method",
"resources/read",
"--uri",
"test://env",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("contents");
expect(Array.isArray(json.contents)).toBe(true);
expect(json.contents.length).toBeGreaterThan(0);
// Parse the env vars from the resource
const envVars = JSON.parse(json.contents[0].text);
expect(envVars).toHaveProperty("CLI_ENV_VAR");
expect(envVars.CLI_ENV_VAR).toBe("cli_value");
} finally {
deleteConfigFile(configPath);
}
});
it("should handle all options together", async () => {
const configPath = createSampleTestConfig();
try {
const result = await runCli([
"--config",
configPath,
"--server",
"test-stdio",
"-e",
"CLI_ENV_VAR=cli_value",
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=Hello",
"--log-level",
"debug",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content.length).toBeGreaterThan(0);
expect(json.content[0]).toHaveProperty("type", "text");
expect(json.content[0].text).toBe("Echo: Hello");
} finally {
deleteConfigFile(configPath);
}
});
});
describe("Config Transport Types", () => {
it("should work with stdio transport type", async () => {
const { command, args } = getTestMcpServerCommand();
const configPath = createTestConfig({
mcpServers: {
"test-stdio": {
type: "stdio",
command,
args,
env: {
TEST_ENV: "test-value",
},
},
},
});
try {
// First validate tools/list works
const toolsResult = await runCli([
"--config",
configPath,
"--server",
"test-stdio",
"--cli",
"--method",
"tools/list",
]);
expectCliSuccess(toolsResult);
const toolsJson = expectValidJson(toolsResult);
expect(toolsJson).toHaveProperty("tools");
expect(Array.isArray(toolsJson.tools)).toBe(true);
expect(toolsJson.tools.length).toBeGreaterThan(0);
// Then validate env vars from config are passed to server
const envResult = await runCli([
"--config",
configPath,
"--server",
"test-stdio",
"--cli",
"--method",
"resources/read",
"--uri",
"test://env",
]);
expectCliSuccess(envResult);
const envJson = expectValidJson(envResult);
const envVars = JSON.parse(envJson.contents[0].text);
expect(envVars).toHaveProperty("TEST_ENV");
expect(envVars.TEST_ENV).toBe("test-value");
} finally {
deleteConfigFile(configPath);
}
});
it("should fail with SSE transport type in CLI mode (connection error)", async () => {
const configPath = createTestConfig({
mcpServers: {
"test-sse": {
type: "sse",
url: "http://localhost:3000/sse",
note: "Test SSE server",
},
},
});
try {
const result = await runCli([
"--config",
configPath,
"--server",
"test-sse",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
} finally {
deleteConfigFile(configPath);
}
});
it("should fail with HTTP transport type in CLI mode (connection error)", async () => {
const configPath = createTestConfig({
mcpServers: {
"test-http": {
type: "streamable-http",
url: "http://localhost:3001/mcp",
note: "Test HTTP server",
},
},
});
try {
const result = await runCli([
"--config",
configPath,
"--server",
"test-http",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
} finally {
deleteConfigFile(configPath);
}
});
it("should work with legacy config without type field", async () => {
const { command, args } = getTestMcpServerCommand();
const configPath = createTestConfig({
mcpServers: {
"test-legacy": {
command,
args,
env: {
LEGACY_ENV: "legacy-value",
},
},
},
});
try {
// First validate tools/list works
const toolsResult = await runCli([
"--config",
configPath,
"--server",
"test-legacy",
"--cli",
"--method",
"tools/list",
]);
expectCliSuccess(toolsResult);
const toolsJson = expectValidJson(toolsResult);
expect(toolsJson).toHaveProperty("tools");
expect(Array.isArray(toolsJson.tools)).toBe(true);
expect(toolsJson.tools.length).toBeGreaterThan(0);
// Then validate env vars from config are passed to server
const envResult = await runCli([
"--config",
configPath,
"--server",
"test-legacy",
"--cli",
"--method",
"resources/read",
"--uri",
"test://env",
]);
expectCliSuccess(envResult);
const envJson = expectValidJson(envResult);
const envVars = JSON.parse(envJson.contents[0].text);
expect(envVars).toHaveProperty("LEGACY_ENV");
expect(envVars.LEGACY_ENV).toBe("legacy-value");
} finally {
deleteConfigFile(configPath);
}
});
});
describe("Default Server Selection", () => {
it("should auto-select single server", async () => {
const { command, args } = getTestMcpServerCommand();
const configPath = createTestConfig({
mcpServers: {
"only-server": {
command,
args,
},
},
});
try {
const result = await runCli([
"--config",
configPath,
"--cli",
"--method",
"tools/list",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("tools");
expect(Array.isArray(json.tools)).toBe(true);
expect(json.tools.length).toBeGreaterThan(0);
} finally {
deleteConfigFile(configPath);
}
});
it("should require explicit server selection even with default-server key (multiple servers)", async () => {
const { command, args } = getTestMcpServerCommand();
const configPath = createTestConfig({
mcpServers: {
"default-server": {
command,
args,
},
"other-server": {
command: "node",
args: ["other.js"],
},
},
});
try {
const result = await runCli([
"--config",
configPath,
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
} finally {
deleteConfigFile(configPath);
}
});
it("should require explicit server selection with multiple servers", async () => {
const { command, args } = getTestMcpServerCommand();
const configPath = createTestConfig({
mcpServers: {
server1: {
command,
args,
},
server2: {
command: "node",
args: ["other.js"],
},
},
});
try {
const result = await runCli([
"--config",
configPath,
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
} finally {
deleteConfigFile(configPath);
}
});
});
describe("HTTP Transport", () => {
it("should infer HTTP transport from URL ending with /mcp", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/list",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("tools");
expect(Array.isArray(json.tools)).toBe(true);
expect(json.tools.length).toBeGreaterThan(0);
} finally {
await server.stop();
}
});
it("should work with explicit --transport http flag", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--transport",
"http",
"--cli",
"--method",
"tools/list",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("tools");
expect(Array.isArray(json.tools)).toBe(true);
expect(json.tools.length).toBeGreaterThan(0);
} finally {
await server.stop();
}
});
it("should work with explicit transport flag and URL suffix", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--transport",
"http",
"--cli",
"--method",
"tools/list",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("tools");
expect(Array.isArray(json.tools)).toBe(true);
expect(json.tools.length).toBeGreaterThan(0);
} finally {
await server.stop();
}
});
it("should fail when SSE transport is given to HTTP server", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--transport",
"sse",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
} finally {
await server.stop();
}
});
it("should fail when HTTP transport is specified without URL", async () => {
const result = await runCli([
"--transport",
"http",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
});
it("should fail when SSE transport is specified without URL", async () => {
const result = await runCli([
"--transport",
"sse",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
});
});
});
================================================
FILE: cli/__tests__/headers.test.ts
================================================
import { describe, it, expect } from "vitest";
import { runCli } from "./helpers/cli-runner.js";
import {
expectCliFailure,
expectOutputContains,
expectCliSuccess,
} from "./helpers/assertions.js";
import { createTestServerHttp } from "./helpers/test-server-http.js";
import {
createEchoTool,
createTestServerInfo,
} from "./helpers/test-fixtures.js";
describe("Header Parsing and Validation", () => {
describe("Valid Headers", () => {
it("should parse valid single header and send it to server", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
const port = await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/list",
"--transport",
"http",
"--header",
"Authorization: Bearer token123",
]);
expectCliSuccess(result);
// Check that the server received the request with the correct headers
const recordedRequests = server.getRecordedRequests();
expect(recordedRequests.length).toBeGreaterThan(0);
// Find the tools/list request (should be the last one)
const toolsListRequest = recordedRequests[recordedRequests.length - 1];
expect(toolsListRequest).toBeDefined();
expect(toolsListRequest.method).toBe("tools/list");
// Express normalizes headers to lowercase
expect(toolsListRequest.headers).toHaveProperty("authorization");
expect(toolsListRequest.headers?.authorization).toBe("Bearer token123");
} finally {
await server.stop();
}
});
it("should parse multiple headers", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
const port = await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/list",
"--transport",
"http",
"--header",
"Authorization: Bearer token123",
"--header",
"X-API-Key: secret123",
]);
expectCliSuccess(result);
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests[recordedRequests.length - 1];
expect(toolsListRequest.method).toBe("tools/list");
expect(toolsListRequest.headers?.authorization).toBe("Bearer token123");
expect(toolsListRequest.headers?.["x-api-key"]).toBe("secret123");
} finally {
await server.stop();
}
});
it("should handle header with colons in value", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
const port = await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/list",
"--transport",
"http",
"--header",
"X-Time: 2023:12:25:10:30:45",
]);
expectCliSuccess(result);
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests[recordedRequests.length - 1];
expect(toolsListRequest.method).toBe("tools/list");
expect(toolsListRequest.headers?.["x-time"]).toBe(
"2023:12:25:10:30:45",
);
} finally {
await server.stop();
}
});
it("should handle whitespace in headers", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
const port = await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/list",
"--transport",
"http",
"--header",
" X-Header : value with spaces ",
]);
expectCliSuccess(result);
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests[recordedRequests.length - 1];
expect(toolsListRequest.method).toBe("tools/list");
// Header values should be trimmed by the CLI parser
expect(toolsListRequest.headers?.["x-header"]).toBe(
"value with spaces",
);
} finally {
await server.stop();
}
});
});
describe("Invalid Header Formats", () => {
it("should reject header format without colon", async () => {
const result = await runCli([
"https://example.com",
"--cli",
"--method",
"tools/list",
"--transport",
"http",
"--header",
"InvalidHeader",
]);
expectCliFailure(result);
expectOutputContains(result, "Invalid header format");
});
it("should reject header format with empty name", async () => {
const result = await runCli([
"https://example.com",
"--cli",
"--method",
"tools/list",
"--transport",
"http",
"--header",
": value",
]);
expectCliFailure(result);
expectOutputContains(result, "Invalid header format");
});
it("should reject header format with empty value", async () => {
const result = await runCli([
"https://example.com",
"--cli",
"--method",
"tools/list",
"--transport",
"http",
"--header",
"Header:",
]);
expectCliFailure(result);
expectOutputContains(result, "Invalid header format");
});
});
});
================================================
FILE: cli/__tests__/helpers/assertions.ts
================================================
import { expect } from "vitest";
import type { CliResult } from "./cli-runner.js";
function formatCliOutput(result: CliResult): string {
const out = result.stdout?.trim() || "(empty)";
const err = result.stderr?.trim() || "(empty)";
return `stdout: ${out}\nstderr: ${err}`;
}
/**
* Assert that CLI command succeeded (exit code 0)
*/
export function expectCliSuccess(result: CliResult) {
expect(
result.exitCode,
`CLI exited with code ${result.exitCode}. ${formatCliOutput(result)}`,
).toBe(0);
}
/**
* Assert that CLI command failed (non-zero exit code)
*/
export function expectCliFailure(result: CliResult) {
expect(
result.exitCode,
`CLI unexpectedly exited with code ${result.exitCode}. ${formatCliOutput(result)}`,
).not.toBe(0);
}
/**
* Assert that output contains expected text
*/
export function expectOutputContains(result: CliResult, text: string) {
expect(result.output).toContain(text);
}
/**
* Assert that output contains valid JSON
* Uses stdout (not stderr) since JSON is written to stdout and warnings go to stderr
*/
export function expectValidJson(result: CliResult) {
expect(() => JSON.parse(result.stdout)).not.toThrow();
return JSON.parse(result.stdout);
}
/**
* Assert that output contains JSON with error flag
*/
export function expectJsonError(result: CliResult) {
const json = expectValidJson(result);
expect(json.isError).toBe(true);
return json;
}
/**
* Assert that output contains expected JSON structure
*/
export function expectJsonStructure(result: CliResult, expectedKeys: string[]) {
const json = expectValidJson(result);
expectedKeys.forEach((key) => {
expect(json).toHaveProperty(key);
});
return json;
}
================================================
FILE: cli/__tests__/helpers/cli-runner.ts
================================================
import { spawn } from "child_process";
import { resolve } from "path";
import { fileURLToPath } from "url";
import { dirname } from "path";
const __dirname = dirname(fileURLToPath(import.meta.url));
const CLI_PATH = resolve(__dirname, "../../build/cli.js");
export interface CliResult {
exitCode: number | null;
stdout: string;
stderr: string;
output: string; // Combined stdout + stderr
}
export interface CliOptions {
timeout?: number;
cwd?: string;
env?: Record<string, string>;
signal?: AbortSignal;
}
/**
* Run the CLI with given arguments and capture output
*/
export async function runCli(
args: string[],
options: CliOptions = {},
): Promise<CliResult> {
return new Promise((resolve, reject) => {
const child = spawn("node", [CLI_PATH, ...args], {
stdio: ["pipe", "pipe", "pipe"],
cwd: options.cwd,
env: { ...process.env, ...options.env },
signal: options.signal,
// Kill child process tree on exit
detached: false,
});
let stdout = "";
let stderr = "";
let resolved = false;
// Default timeout of 10 seconds (less than vitest's 15s)
const timeoutMs = options.timeout ?? 10000;
const timeout = setTimeout(() => {
if (!resolved) {
resolved = true;
// Kill the process and all its children
try {
if (process.platform === "win32") {
child.kill("SIGTERM");
} else {
// On Unix, kill the process group
process.kill(-child.pid!, "SIGTERM");
}
} catch (e) {
// Process might already be dead, try direct kill
try {
child.kill("SIGKILL");
} catch (e2) {
// Process is definitely dead
}
}
reject(new Error(`CLI command timed out after ${timeoutMs}ms`));
}
}, timeoutMs);
child.stdout.on("data", (data) => {
stdout += data.toString();
});
child.stderr.on("data", (data) => {
stderr += data.toString();
});
child.on("close", (code) => {
if (!resolved) {
resolved = true;
clearTimeout(timeout);
resolve({
exitCode: code,
stdout,
stderr,
output: stdout + stderr,
});
}
});
child.on("error", (error) => {
if (!resolved) {
resolved = true;
clearTimeout(timeout);
reject(error);
}
});
});
}
================================================
FILE: cli/__tests__/helpers/fixtures.ts
================================================
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
import * as crypto from "crypto";
import { getTestMcpServerCommand } from "./test-server-stdio.js";
/**
* Sentinel value for tests that don't need a real server
* (tests that expect failure before connecting)
*/
export const NO_SERVER_SENTINEL = "invalid-command-that-does-not-exist";
/**
* Create a sample test config with test-stdio and test-http servers
* Returns a temporary config file path that should be cleaned up with deleteConfigFile()
* @param httpUrl - Optional full URL (including /mcp path) for test-http server.
* If not provided, uses a placeholder URL. The test-http server exists
* to test server selection logic and may not actually be used.
*/
export function createSampleTestConfig(httpUrl?: string): string {
const { command, args } = getTestMcpServerCommand();
return createTestConfig({
mcpServers: {
"test-stdio": {
type: "stdio",
command,
args,
env: {
HELLO: "Hello MCP!",
},
},
"test-http": {
type: "streamable-http",
url: httpUrl || "http://localhost:3001/mcp",
},
},
});
}
/**
* Create a temporary directory for test files
* Uses crypto.randomUUID() to ensure uniqueness even when called in parallel
*/
function createTempDir(prefix: string = "mcp-inspector-test-"): string {
const uniqueId = crypto.randomUUID();
const tempDir = path.join(os.tmpdir(), `${prefix}${uniqueId}`);
fs.mkdirSync(tempDir, { recursive: true });
return tempDir;
}
/**
* Clean up temporary directory
*/
function cleanupTempDir(dir: string) {
try {
fs.rmSync(dir, { recursive: true, force: true });
} catch (err) {
// Ignore cleanup errors
}
}
/**
* Create a test config file
*/
export function createTestConfig(config: {
mcpServers: Record<string, any>;
}): string {
const tempDir = createTempDir("mcp-inspector-config-");
const configPath = path.join(tempDir, "config.json");
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
return configPath;
}
/**
* Create an invalid config file (malformed JSON)
*/
export function createInvalidConfig(): string {
const tempDir = createTempDir("mcp-inspector-config-");
const configPath = path.join(tempDir, "invalid-config.json");
fs.writeFileSync(configPath, '{\n "mcpServers": {\n "invalid": {');
return configPath;
}
/**
* Delete a config file and its containing directory
*/
export function deleteConfigFile(configPath: string): void {
cleanupTempDir(path.dirname(configPath));
}
================================================
FILE: cli/__tests__/helpers/test-fixtures.ts
================================================
/**
* Shared types and test fixtures for composable MCP test servers
*/
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { Implementation } from "@modelcontextprotocol/sdk/types.js";
import * as z from "zod/v4";
import { ZodRawShapeCompat } from "@modelcontextprotocol/sdk/server/zod-compat.js";
type ToolInputSchema = ZodRawShapeCompat;
export interface ToolDefinition {
name: string;
description: string;
inputSchema?: ToolInputSchema;
handler: (params: Record<string, any>) => Promise<any>;
}
export interface ResourceDefinition {
uri: string;
name: string;
description?: string;
mimeType?: string;
text?: string;
}
type PromptArgsSchema = ZodRawShapeCompat;
export interface PromptDefinition {
name: string;
description?: string;
argsSchema?: PromptArgsSchema;
}
// This allows us to compose tests servers using the metadata and features we want in a given scenario
export interface ServerConfig {
serverInfo: Implementation; // Server metadata (name, version, etc.) - required
tools?: ToolDefinition[]; // Tools to register (optional, empty array means no tools, but tools capability is still advertised)
resources?: ResourceDefinition[]; // Resources to register (optional, empty array means no resources, but resources capability is still advertised)
prompts?: PromptDefinition[]; // Prompts to register (optional, empty array means no prompts, but prompts capability is still advertised)
logging?: boolean; // Whether to advertise logging capability (default: false)
}
/**
* Create an "echo" tool that echoes back the input message
*/
export function createEchoTool(): ToolDefinition {
return {
name: "echo",
description: "Echo back the input message",
inputSchema: {
message: z.string().describe("Message to echo back"),
},
handler: async (params: Record<string, any>) => {
return { message: `Echo: ${params.message as string}` };
},
};
}
/**
* Create an "add" tool that adds two numbers together
*/
export function createAddTool(): ToolDefinition {
return {
name: "add",
description: "Add two numbers together",
inputSchema: {
a: z.number().describe("First number"),
b: z.number().describe("Second number"),
},
handler: async (params: Record<string, any>) => {
const a = params.a as number;
const b = params.b as number;
return { result: a + b };
},
};
}
/**
* Create a "get-sum" tool that returns the sum of two numbers (alias for add)
*/
export function createGetSumTool(): ToolDefinition {
return {
name: "get-sum",
description: "Get the sum of two numbers",
inputSchema: {
a: z.number().describe("First number"),
b: z.number().describe("Second number"),
},
handler: async (params: Record<string, any>) => {
const a = params.a as number;
const b = params.b as number;
return { result: a + b };
},
};
}
/**
* Create a "get-annotated-message" tool that returns a message with optional image
*/
export function createGetAnnotatedMessageTool(): ToolDefinition {
return {
name: "get-annotated-message",
description: "Get an annotated message",
inputSchema: {
messageType: z
.enum(["success", "error", "warning", "info"])
.describe("Type of message"),
includeImage: z
.boolean()
.optional()
.describe("Whether to include an image"),
},
handler: async (params: Record<string, any>) => {
const messageType = params.messageType as string;
const includeImage = params.includeImage as boolean | undefined;
const message = `This is a ${messageType} message`;
const content: Array<
| { type: "text"; text: string }
| { type: "image"; data: string; mimeType: string }
> = [
{
type: "text",
text: message,
},
];
if (includeImage) {
content.push({
type: "image",
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", // 1x1 transparent PNG
mimeType: "image/png",
});
}
return { content };
},
};
}
/**
* Create a "simple-prompt" prompt definition
*/
export function createSimplePrompt(): PromptDefinition {
return {
name: "simple-prompt",
description: "A simple prompt for testing",
};
}
/**
* Create an "args-prompt" prompt that accepts arguments
*/
export function createArgsPrompt(): PromptDefinition {
return {
name: "args-prompt",
description: "A prompt that accepts arguments for testing",
argsSchema: {
city: z.string().describe("City name"),
state: z.string().describe("State name"),
},
};
}
/**
* Create an "architecture" resource definition
*/
export function createArchitectureResource(): ResourceDefinition {
return {
name: "architecture",
uri: "demo://resource/static/document/architecture.md",
description: "Architecture documentation",
mimeType: "text/markdown",
text: `# Architecture Documentation
This is a test resource for the MCP test server.
## Overview
This resource is used for testing resource reading functionality in the CLI.
## Sections
- Introduction
- Design
- Implementation
- Testing
## Notes
This is a static resource provided by the test MCP server.
`,
};
}
/**
* Create a "test-cwd" resource that exposes the current working directory (generally useful when testing with the stdio test server)
*/
export function createTestCwdResource(): ResourceDefinition {
return {
name: "test-cwd",
uri: "test://cwd",
description: "Current working directory of the test server",
mimeType: "text/plain",
text: process.cwd(),
};
}
/**
* Create a "test-env" resource that exposes environment variables (generally useful when testing with the stdio test server)
*/
export function createTestEnvResource(): ResourceDefinition {
return {
name: "test-env",
uri: "test://env",
description: "Environment variables available to the test server",
mimeType: "application/json",
text: JSON.stringify(process.env, null, 2),
};
}
/**
* Create a "test-argv" resource that exposes command-line arguments (generally useful when testing with the stdio test server)
*/
export function createTestArgvResource(): ResourceDefinition {
return {
name: "test-argv",
uri: "test://argv",
description: "Command-line arguments the test server was started with",
mimeType: "application/json",
text: JSON.stringify(process.argv, null, 2),
};
}
/**
* Create minimal server info for test servers
*/
export function createTestServerInfo(
name: string = "test-server",
version: string = "1.0.0",
): Implementation {
return {
name,
version,
};
}
/**
* Get default server config with common test tools, prompts, and resources
*/
export function getDefaultServerConfig(): ServerConfig {
return {
serverInfo: createTestServerInfo("test-mcp-server", "1.0.0"),
tools: [
createEchoTool(),
createGetSumTool(),
createGetAnnotatedMessageTool(),
],
prompts: [createSimplePrompt(), createArgsPrompt()],
resources: [
createArchitectureResource(),
createTestCwdResource(),
createTestEnvResource(),
createTestArgvResource(),
],
};
}
================================================
FILE: cli/__tests__/helpers/test-server-http.ts
================================================
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { SetLevelRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import type { Request, Response } from "express";
import express from "express";
import { createServer as createHttpServer, Server as HttpServer } from "http";
import { createServer as createNetServer } from "net";
import { randomUUID } from "crypto";
import * as z from "zod/v4";
import type { ServerConfig } from "./test-fixtures.js";
export interface RecordedRequest {
method: string;
params?: any;
headers?: Record<string, string>;
metadata?: Record<string, string>;
response: any;
timestamp: number;
}
/**
* Find an available port starting from the given port
*/
async function findAvailablePort(startPort: number): Promise<number> {
return new Promise((resolve, reject) => {
const server = createNetServer();
server.listen(startPort, () => {
const port = (server.address() as { port: number })?.port;
server.close(() => resolve(port || startPort));
});
server.on("error", (err: NodeJS.ErrnoException) => {
if (err.code === "EADDRINUSE") {
// Try next port
findAvailablePort(startPort + 1)
.then(resolve)
.catch(reject);
} else {
reject(err);
}
});
});
}
/**
* Extract headers from Express request
*/
function extractHeaders(req: Request): Record<string, string> {
const headers: Record<string, string> = {};
for (const [key, value] of Object.entries(req.headers)) {
if (typeof value === "string") {
headers[key] = value;
} else if (Array.isArray(value) && value.length > 0) {
headers[key] = value[value.length - 1];
}
}
return headers;
}
// With this test server, your test can hold an instance and you can get the server's recorded message history at any time.
//
export class TestServerHttp {
private mcpServer: McpServer;
private config: ServerConfig;
private recordedRequests: RecordedRequest[] = [];
private httpServer?: HttpServer;
private transport?: StreamableHTTPServerTransport | SSEServerTransport;
private url?: string;
private currentRequestHeaders?: Record<string, string>;
private currentLogLevel: string | null = null;
constructor(config: ServerConfig) {
this.config = config;
const capabilities: {
tools?: {};
resources?: {};
prompts?: {};
logging?: {};
} = {};
// Only include capabilities for features that are present in config
if (config.tools !== undefined) {
capabilities.tools = {};
}
if (config.resources !== undefined) {
capabilities.resources = {};
}
if (config.prompts !== undefined) {
capabilities.prompts = {};
}
if (config.logging === true) {
capabilities.logging = {};
}
this.mcpServer = new McpServer(config.serverInfo, {
capabilities,
});
this.setupHandlers();
if (config.logging === true) {
this.setupLoggingHandler();
}
}
private setupHandlers() {
// Set up tools
if (this.config.tools && this.config.tools.length > 0) {
for (const tool of this.config.tools) {
this.mcpServer.registerTool(
tool.name,
{
description: tool.description,
inputSchema: tool.inputSchema,
},
async (args) => {
const result = await tool.handler(args as Record<string, any>);
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
},
);
}
}
// Set up resources
if (this.config.resources && this.config.resources.length > 0) {
for (const resource of this.config.resources) {
this.mcpServer.registerResource(
resource.name,
resource.uri,
{
description: resource.description,
mimeType: resource.mimeType,
},
async () => {
return {
contents: [
{
uri: resource.uri,
mimeType: resource.mimeType || "text/plain",
text: resource.text || "",
},
],
};
},
);
}
}
// Set up prompts
if (this.config.prompts && this.config.prompts.length > 0) {
for (const prompt of this.config.prompts) {
this.mcpServer.registerPrompt(
prompt.name,
{
description: prompt.description,
argsSchema: prompt.argsSchema,
},
async (args) => {
// Return a simple prompt response
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Prompt: ${prompt.name}${args ? ` with args: ${JSON.stringify(args)}` : ""}`,
},
},
],
};
},
);
}
}
}
private setupLoggingHandler() {
// Intercept logging/setLevel requests to track the level
this.mcpServer.server.setRequestHandler(
SetLevelRequestSchema,
async (request) => {
this.currentLogLevel = request.params.level;
// Return empty result as per MCP spec
return {};
},
);
}
/**
* Start the server with the specified transport.
* When requestedPort is omitted, uses port 0 so the OS assigns a unique port (avoids EADDRINUSE when tests run in parallel).
*/
async start(
transport: "http" | "sse",
requestedPort?: number,
): Promise<number> {
const port =
requestedPort !== undefined ? await findAvailablePort(requestedPort) : 0;
if (transport === "http") {
const actualPort = await this.startHttp(port);
this.url = `http://localhost:${actualPort}`;
return actualPort;
} else {
const actualPort = await this.startSse(port);
this.url = `http://localhost:${actualPort}`;
return actualPort;
}
}
private async startHttp(port: number): Promise<number> {
const app = express();
app.use(express.json());
// Create HTTP server
this.httpServer = createHttpServer(app);
// Create StreamableHTTP transport (stateful so it can handle multiple requests per session)
this.transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
});
// Set up Express route to handle MCP requests
app.post("/mcp", async (req: Request, res: Response) => {
// Capture headers for this request
this.currentRequestHeaders = extractHeaders(req);
try {
await (this.transport as StreamableHTTPServerTransport).handleRequest(
req,
res,
req.body,
);
} catch (error) {
res.status(500).json({
error: error instanceof Error ? error.message : String(error),
});
}
});
// Intercept messages to record them
const originalOnMessage = this.transport.onmessage;
this.transport.onmessage = async (message) => {
const timestamp = Date.now();
const method =
"method" in message && typeof message.method === "string"
? message.method
: "unknown";
const params = "params" in message ? message.params : undefined;
try {
// Extract metadata from params if present
const metadata =
params && typeof params === "object" && "_meta" in params
? ((params as any)._meta as Record<string, string>)
: undefined;
// Let the server handle the message
if (originalOnMessage) {
await originalOnMessage.call(this.transport, message);
}
// Record successful request (response will be sent by transport)
// Note: We can't easily capture the response here, so we'll record
// that the request was processed
this.recordedRequests.push({
method,
params,
headers: { ...this.currentRequestHeaders },
metadata: metadata ? { ...metadata } : undefined,
response: { processed: true },
timestamp,
});
} catch (error) {
// Extract metadata from params if present
const metadata =
params && typeof params === "object" && "_meta" in params
? ((params as any)._meta as Record<string, string>)
: undefined;
// Record error
this.recordedRequests.push({
method,
params,
headers: { ...this.currentRequestHeaders },
metadata: metadata ? { ...metadata } : undefined,
response: {
error: error instanceof Error ? error.message : String(error),
},
timestamp,
});
throw error;
}
};
// Connect transport to server
await this.mcpServer.connect(this.transport);
// Start listening (port 0 = OS assigns a unique port)
return new Promise((resolve, reject) => {
this.httpServer!.listen(port, () => {
const assignedPort = (this.httpServer!.address() as { port: number })
?.port;
resolve(assignedPort ?? port);
});
this.httpServer!.on("error", reject);
});
}
private async startSse(port: number): Promise<number> {
const app = express();
app.use(express.json());
// Create HTTP server
this.httpServer = createHttpServer(app);
// For SSE, we need to set up an Express route that creates the transport per request
// This is a simplified version - SSE transport is created per connection
app.get("/mcp", async (req: Request, res: Response) => {
this.currentRequestHeaders = extractHeaders(req);
const sseTransport = new SSEServerTransport("/mcp", res);
// Intercept messages
const originalOnMessage = sseTransport.onmessage;
sseTransport.onmessage = async (message) => {
const timestamp = Date.now();
const method =
"method" in message && typeof message.method === "string"
? message.method
: "unknown";
const params = "params" in message ? message.params : undefined;
try {
// Extract metadata from params if present
const metadata =
params && typeof params === "object" && "_meta" in params
? ((params as any)._meta as Record<string, string>)
: undefined;
if (originalOnMessage) {
await originalOnMessage.call(sseTransport, message);
}
this.recordedRequests.push({
method,
params,
headers: { ...this.currentRequestHeaders },
metadata: metadata ? { ...metadata } : undefined,
response: { processed: true },
timestamp,
});
} catch (error) {
// Extract metadata from params if present
const metadata =
params && typeof params === "object" && "_meta" in params
? ((params as any)._meta as Record<string, string>)
: undefined;
this.recordedRequests.push({
method,
params,
headers: { ...this.currentRequestHeaders },
metadata: metadata ? { ...metadata } : undefined,
response: {
error: error instanceof Error ? error.message : String(error),
},
timestamp,
});
throw error;
}
};
await this.mcpServer.connect(sseTransport);
await sseTransport.start();
});
// Note: SSE transport is created per request, so we don't store a single instance
this.transport = undefined;
// Start listening (port 0 = OS assigns a unique port)
return new Promise((resolve, reject) => {
this.httpServer!.listen(port, () => {
const assignedPort = (this.httpServer!.address() as { port: number })
?.port;
resolve(assignedPort ?? port);
});
this.httpServer!.on("error", reject);
});
}
/**
* Stop the server
*/
async stop(): Promise<void> {
await this.mcpServer.close();
if (this.transport) {
await this.transport.close();
this.transport = undefined;
}
if (this.httpServer) {
return new Promise((resolve) => {
// Force close all connections
this.httpServer!.closeAllConnections?.();
this.httpServer!.close(() => {
this.httpServer = undefined;
resolve();
});
});
}
}
/**
* Get all recorded requests
*/
getRecordedRequests(): RecordedRequest[] {
return [...this.recordedRequests];
}
/**
* Clear recorded requests
*/
clearRecordings(): void {
this.recordedRequests = [];
}
/**
* Get the server URL
*/
getUrl(): string {
if (!this.url) {
throw new Error("Server not started");
}
return this.url;
}
/**
* Get the most recent log level that was set
*/
getCurrentLogLevel(): string | null {
return this.currentLogLevel;
}
}
/**
* Create an HTTP/SSE MCP test server
*/
export function createTestServerHttp(config: ServerConfig): TestServerHttp {
return new TestServerHttp(config);
}
================================================
FILE: cli/__tests__/helpers/test-server-stdio.ts
================================================
#!/usr/bin/env node
/**
* Test MCP server for stdio transport testing
* Can be used programmatically or run as a standalone executable
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import * as z from "zod/v4";
import path from "path";
import { fileURLToPath } from "url";
import { dirname } from "path";
import type {
ServerConfig,
ToolDefinition,
PromptDefinition,
ResourceDefinition,
} from "./test-fixtures.js";
import { getDefaultServerConfig } from "./test-fixtures.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
export class TestServerStdio {
private mcpServer: McpServer;
private config: ServerConfig;
private transport?: StdioServerTransport;
constructor(config: ServerConfig) {
this.config = config;
const capabilities: {
tools?: {};
resources?: {};
prompts?: {};
logging?: {};
} = {};
// Only include capabilities for features that are present in config
if (config.tools !== undefined) {
capabilities.tools = {};
}
if (config.resources !== undefined) {
capabilities.resources = {};
}
if (config.prompts !== undefined) {
capabilities.prompts = {};
}
if (config.logging === true) {
capabilities.logging = {};
}
this.mcpServer = new McpServer(config.serverInfo, {
capabilities,
});
this.setupHandlers();
}
private setupHandlers() {
// Set up tools
if (this.config.tools && this.config.tools.length > 0) {
for (const tool of this.config.tools) {
this.mcpServer.registerTool(
tool.name,
{
description: tool.description,
inputSchema: tool.inputSchema,
},
async (args) => {
const result = await tool.handler(args as Record<string, any>);
// If handler returns content array directly (like get-annotated-message), use it
if (result && Array.isArray(result.content)) {
return { content: result.content };
}
// If handler returns message (like echo), format it
if (result && typeof result.message === "string") {
return {
content: [
{
type: "text",
text: result.message,
},
],
};
}
// Otherwise, stringify the result
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
}
}
// Set up resources
if (this.config.resources && this.config.resources.length > 0) {
for (const resource of this.config.resources) {
this.mcpServer.registerResource(
resource.name,
resource.uri,
{
description: resource.description,
mimeType: resource.mimeType,
},
async () => {
// For dynamic resources, get fresh text
let text = resource.text;
if (resource.name === "test-cwd") {
text = process.cwd();
} else if (resource.name === "test-env") {
text = JSON.stringify(process.env, null, 2);
} else if (resource.name === "test-argv") {
text = JSON.stringify(process.argv, null, 2);
}
return {
contents: [
{
uri: resource.uri,
mimeType: resource.mimeType || "text/plain",
text: text || "",
},
],
};
},
);
}
}
// Set up prompts
if (this.config.prompts && this.config.prompts.length > 0) {
for (const prompt of this.config.prompts) {
this.mcpServer.registerPrompt(
prompt.name,
{
description: prompt.description,
argsSchema: prompt.argsSchema,
},
async (args) => {
if (prompt.name === "args-prompt" && args) {
const city = (args as any).city as string;
const state = (args as any).state as string;
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `This is a prompt with arguments: city=${city}, state=${state}`,
},
},
],
};
} else {
return {
messages: [
{
role: "user",
content: {
type: "text",
text: "This is a simple prompt for testing purposes.",
},
},
],
};
}
},
);
}
}
}
/**
* Start the server with stdio transport
*/
async start(): Promise<void> {
this.transport = new StdioServerTransport();
await this.mcpServer.connect(this.transport);
}
/**
* Stop the server
*/
async stop(): Promise<void> {
await this.mcpServer.close();
if (this.transport) {
await this.transport.close();
this.transport = undefined;
}
}
}
/**
* Create a stdio MCP test server
*/
export function createTestServerStdio(config: ServerConfig): TestServerStdio {
return new TestServerStdio(config);
}
/**
* Get the path to the test MCP server script
*/
export function getTestMcpServerPath(): string {
return path.resolve(__dirname, "test-server-stdio.ts");
}
/**
* Get the command and args to run the test MCP server
*/
export function getTestMcpServerCommand(): { command: string; args: string[] } {
return {
command: "tsx",
args: [getTestMcpServerPath()],
};
}
// If run as a standalone script, start with default config
// Check if this file is being executed directly (not imported)
const isMainModule =
import.meta.url.endsWith(process.argv[1]) ||
process.argv[1]?.endsWith("test-server-stdio.ts") ||
process.argv[1]?.endsWith("test-server-stdio.js");
if (isMainModule) {
const server = new TestServerStdio(getDefaultServerConfig());
server
.start()
.then(() => {
// Server is now running and listening on stdio
// Keep the process alive
})
.catch((error) => {
console.error("Failed to start test MCP server:", error);
process.exit(1);
});
}
================================================
FILE: cli/__tests__/metadata.test.ts
================================================
import { describe, it, expect } from "vitest";
import { runCli } from "./helpers/cli-runner.js";
import {
expectCliSuccess,
expectCliFailure,
expectValidJson,
} from "./helpers/assertions.js";
import { createTestServerHttp } from "./helpers/test-server-http.js";
import {
createEchoTool,
createAddTool,
createTestServerInfo,
} from "./helpers/test-fixtures.js";
import { NO_SERVER_SENTINEL } from "./helpers/fixtures.js";
describe("Metadata Tests", () => {
describe("General Metadata", () => {
it("should work with tools/list", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/list",
"--metadata",
"client=test-client",
"--transport",
"http",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("tools");
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests.find(
(r) => r.method === "tools/list",
);
expect(toolsListRequest).toBeDefined();
expect(toolsListRequest?.metadata).toEqual({ client: "test-client" });
} finally {
await server.stop();
}
});
it("should work with resources/list", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
resources: [
{
uri: "test://resource",
name: "test-resource",
text: "test content",
},
],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"resources/list",
"--metadata",
"client=test-client",
"--transport",
"http",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("resources");
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const resourcesListRequest = recordedRequests.find(
(r) => r.method === "resources/list",
);
expect(resourcesListRequest).toBeDefined();
expect(resourcesListRequest?.metadata).toEqual({
client: "test-client",
});
} finally {
await server.stop();
}
});
it("should work with prompts/list", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
prompts: [
{
name: "test-prompt",
description: "A test prompt",
},
],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"prompts/list",
"--metadata",
"client=test-client",
"--transport",
"http",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("prompts");
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const promptsListRequest = recordedRequests.find(
(r) => r.method === "prompts/list",
);
expect(promptsListRequest).toBeDefined();
expect(promptsListRequest?.metadata).toEqual({
client: "test-client",
});
} finally {
await server.stop();
}
});
it("should work with resources/read", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
resources: [
{
uri: "test://resource",
name: "test-resource",
text: "test content",
},
],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"resources/read",
"--uri",
"test://resource",
"--metadata",
"client=test-client",
"--transport",
"http",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("contents");
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const readRequest = recordedRequests.find(
(r) => r.method === "resources/read",
);
expect(readRequest).toBeDefined();
expect(readRequest?.metadata).toEqual({ client: "test-client" });
} finally {
await server.stop();
}
});
it("should work with prompts/get", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
prompts: [
{
name: "test-prompt",
description: "A test prompt",
},
],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"prompts/get",
"--prompt-name",
"test-prompt",
"--metadata",
"client=test-client",
"--transport",
"http",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("messages");
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const getPromptRequest = recordedRequests.find(
(r) => r.method === "prompts/get",
);
expect(getPromptRequest).toBeDefined();
expect(getPromptRequest?.metadata).toEqual({ client: "test-client" });
} finally {
await server.stop();
}
});
});
describe("Tool-Specific Metadata", () => {
it("should work with tools/call", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=hello world",
"--tool-metadata",
"client=test-client",
"--transport",
"http",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const toolCallRequest = recordedRequests.find(
(r) => r.method === "tools/call",
);
expect(toolCallRequest).toBeDefined();
expect(toolCallRequest?.metadata).toEqual({ client: "test-client" });
} finally {
await server.stop();
}
});
it("should work with complex tool", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createAddTool()],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/call",
"--tool-name",
"add",
"--tool-arg",
"a=10",
"b=20",
"--tool-metadata",
"client=test-client",
"--transport",
"http",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const toolCallRequest = recordedRequests.find(
(r) => r.method === "tools/call",
);
expect(toolCallRequest).toBeDefined();
expect(toolCallRequest?.metadata).toEqual({ client: "test-client" });
} finally {
await server.stop();
}
});
});
describe("Metadata Merging", () => {
it("should merge general and tool-specific metadata (tool-specific overrides)", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=hello world",
"--metadata",
"client=general-client",
"shared_key=shared_value",
"--tool-metadata",
"client=tool-specific-client",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate metadata was merged correctly (tool-specific overrides general)
const recordedRequests = server.getRecordedRequests();
const toolCallRequest = recordedRequests.find(
(r) => r.method === "tools/call",
);
expect(toolCallRequest).toBeDefined();
expect(toolCallRequest?.metadata).toEqual({
client: "tool-specific-client", // Tool-specific overrides general
shared_key: "shared_value", // General metadata is preserved
});
} finally {
await server.stop();
}
});
});
describe("Metadata Parsing", () => {
it("should handle numeric values", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/list",
"--metadata",
"integer_value=42",
"decimal_value=3.14159",
"negative_value=-10",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate metadata values are sent as strings
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests.find(
(r) => r.method === "tools/list",
);
expect(toolsListRequest).toBeDefined();
expect(toolsListRequest?.metadata).toEqual({
integer_value: "42",
decimal_value: "3.14159",
negative_value: "-10",
});
} finally {
await server.stop();
}
});
it("should handle JSON values", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/list",
"--metadata",
'json_object="{\\"key\\":\\"value\\"}"',
'json_array="[1,2,3]"',
'json_string="\\"quoted\\""',
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate JSON values are sent as strings
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests.find(
(r) => r.method === "tools/list",
);
expect(toolsListRequest).toBeDefined();
expect(toolsListRequest?.metadata).toEqual({
json_object: '{"key":"value"}',
json_array: "[1,2,3]",
json_string: '"quoted"',
});
} finally {
await server.stop();
}
});
it("should handle special characters", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/list",
"--metadata",
"unicode=🚀🎉✨",
"special_chars=!@#$%^&*()",
"spaces=hello world with spaces",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate special characters are preserved
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests.find(
(r) => r.method === "tools/list",
);
expect(toolsListRequest).toBeDefined();
expect(toolsListRequest?.metadata).toEqual({
unicode: "🚀🎉✨",
special_chars: "!@#$%^&*()",
spaces: "hello world with spaces",
});
} finally {
await server.stop();
}
});
});
describe("Metadata Edge Cases", () => {
it("should handle single metadata entry", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/list",
"--metadata",
"single_key=single_value",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate single metadata entry
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests.find(
(r) => r.method === "tools/list",
);
expect(toolsListRequest).toBeDefined();
expect(toolsListRequest?.metadata).toEqual({
single_key: "single_value",
});
} finally {
await server.stop();
}
});
it("should handle many metadata entries", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/list",
"--metadata",
"key1=value1",
"key2=value2",
"key3=value3",
"key4=value4",
"key5=value5",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate all metadata entries
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests.find(
(r) => r.method === "tools/list",
);
expect(toolsListRequest).toBeDefined();
expect(toolsListRequest?.metadata).toEqual({
key1: "value1",
key2: "value2",
key3: "value3",
key4: "value4",
key5: "value5",
});
} finally {
await server.stop();
}
});
});
describe("Metadata Error Cases", () => {
it("should fail with invalid metadata format (missing equals)", async () => {
const result = await runCli([
NO_SERVER_SENTINEL,
"--cli",
"--method",
"tools/list",
"--metadata",
"invalid_format_no_equals",
]);
expectCliFailure(result);
});
it("should fail with invalid tool-metadata format (missing equals)", async () => {
const result = await runCli([
NO_SERVER_SENTINEL,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=test",
"--tool-metadata",
"invalid_format_no_equals",
]);
expectCliFailure(result);
});
});
describe("Metadata Impact", () => {
it("should handle tool-specific metadata precedence over general", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=precedence test",
"--metadata",
"client=general-client",
"--tool-metadata",
"client=tool-specific-client",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate tool-specific metadata overrides general
const recordedRequests = server.getRecordedRequests();
const toolCallRequest = recordedRequests.find(
(r) => r.method === "tools/call",
);
expect(toolCallRequest).toBeDefined();
expect(toolCallRequest?.metadata).toEqual({
client: "tool-specific-client",
});
} finally {
await server.stop();
}
});
it("should work with resources methods", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
resources: [
{
uri: "test://resource",
name: "test-resource",
text: "test content",
},
],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"resources/list",
"--metadata",
"resource_client=test-resource-client",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const resourcesListRequest = recordedRequests.find(
(r) => r.method === "resources/list",
);
expect(resourcesListRequest).toBeDefined();
expect(resourcesListRequest?.metadata).toEqual({
resource_client: "test-resource-client",
});
} finally {
await server.stop();
}
});
it("should work with prompts methods", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
prompts: [
{
name: "test-prompt",
description: "A test prompt",
},
],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"prompts/get",
"--prompt-name",
"test-prompt",
"--metadata",
"prompt_client=test-prompt-client",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const getPromptRequest = recordedRequests.find(
(r) => r.method === "prompts/get",
);
expect(getPromptRequest).toBeDefined();
expect(getPromptRequest?.metadata).toEqual({
prompt_client: "test-prompt-client",
});
} finally {
await server.stop();
}
});
});
describe("Metadata Validation", () => {
it("should handle special characters in keys", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=special keys test",
"--metadata",
"key-with-dashes=value1",
"key_with_underscores=value2",
"key.with.dots=value3",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate special characters in keys are preserved
const recordedRequests = server.getRecordedRequests();
const toolCallRequest = recordedRequests.find(
(r) => r.method === "tools/call",
);
expect(toolCallRequest).toBeDefined();
expect(toolCallRequest?.metadata).toEqual({
"key-with-dashes": "value1",
key_with_underscores: "value2",
"key.with.dots": "value3",
});
} finally {
await server.stop();
}
});
});
describe("Metadata Integration", () => {
it("should work with all MCP methods", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/list",
"--metadata",
"integration_test=true",
"test_phase=all_methods",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests.find(
(r) => r.method === "tools/list",
);
expect(toolsListRequest).toBeDefined();
expect(toolsListRequest?.metadata).toEqual({
integration_test: "true",
test_phase: "all_methods",
});
} finally {
await server.stop();
}
});
it("should handle complex metadata scenario", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=complex test",
"--metadata",
"session_id=12345",
"user_id=67890",
"timestamp=2024-01-01T00:00:00Z",
"request_id=req-abc-123",
"--tool-metadata",
"tool_session=session-xyz-789",
"execution_context=test",
"priority=high",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate complex metadata merging
const recordedRequests = server.getRecordedRequests();
const toolCallRequest = recordedRequests.find(
(r) => r.method === "tools/call",
);
expect(toolCallRequest).toBeDefined();
expect(toolCallRequest?.metadata).toEqual({
session_id: "12345",
user_id: "67890",
timestamp: "2024-01-01T00:00:00Z",
request_id: "req-abc-123",
tool_session: "session-xyz-789",
execution_context: "test",
priority: "high",
});
} finally {
await server.stop();
}
});
it("should handle metadata parsing validation", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start("http");
const serverUrl = `${server.getUrl()}/mcp`;
const result = await runCli([
serverUrl,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=parsing validation test",
"--metadata",
"valid_key=valid_value",
"numeric_key=123",
"boolean_key=true",
'json_key=\'{"test":"value"}\'',
"special_key=!@#$%^&*()",
"unicode_key=🚀🎉✨",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate all value types are sent as strings
// Note: The CLI parses metadata values, so single-quoted JSON strings
// are preserved with their quotes
const recordedRequests = server.getRecordedRequests();
const toolCallRequest = recordedRequests.find(
(r) => r.method === "tools/call",
);
expect(toolCallRequest).toBeDefined();
expect(toolCallRequest?.metadata).toEqual({
valid_key: "valid_value",
numeric_key: "123",
boolean_key: "true",
json_key: '\'{"test":"value"}\'', // Single quotes are preserved
special_key: "!@#$%^&*()",
unicode_key: "🚀🎉✨",
});
} finally {
await server.stop();
}
});
});
});
================================================
FILE: cli/__tests__/tools.test.ts
================================================
import { describe, it, expect } from "vitest";
import { runCli } from "./helpers/cli-runner.js";
import {
expectCliSuccess,
expectCliFailure,
expectValidJson,
expectJsonError,
} from "./helpers/assertions.js";
import { getTestMcpServerCommand } from "./helpers/test-server-stdio.js";
describe("Tool Tests", () => {
describe("Tool Discovery", () => {
it("should list available tools", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/list",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("tools");
expect(Array.isArray(json.tools)).toBe(true);
expect(json.tools.length).toBeGreaterThan(0);
// Validate that tools have required properties
expect(json.tools[0]).toHaveProperty("name");
expect(json.tools[0]).toHaveProperty("description");
// Validate expected tools from test-mcp-server
const toolNames = json.tools.map((tool: any) => tool.name);
expect(toolNames).toContain("echo");
expect(toolNames).toContain("get-sum");
expect(toolNames).toContain("get-annotated-message");
});
});
describe("JSON Argument Parsing", () => {
it("should handle string arguments (backward compatibility)", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=hello world",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content.length).toBeGreaterThan(0);
expect(json.content[0]).toHaveProperty("type", "text");
expect(json.content[0].text).toBe("Echo: hello world");
});
it("should handle integer number arguments", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"get-sum",
"--tool-arg",
"a=42",
"b=58",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content.length).toBeGreaterThan(0);
expect(json.content[0]).toHaveProperty("type", "text");
// test-mcp-server returns JSON with {result: a+b}
const resultData = JSON.parse(json.content[0].text);
expect(resultData.result).toBe(100);
});
it("should handle decimal number arguments", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"get-sum",
"--tool-arg",
"a=19.99",
"b=20.01",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content.length).toBeGreaterThan(0);
expect(json.content[0]).toHaveProperty("type", "text");
// test-mcp-server returns JSON with {result: a+b}
const resultData = JSON.parse(json.content[0].text);
expect(resultData.result).toBeCloseTo(40.0, 2);
});
it("should handle boolean arguments - true", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"get-annotated-message",
"--tool-arg",
"messageType=success",
"includeImage=true",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
// Should have both text and image content
expect(json.content.length).toBeGreaterThan(1);
const hasImage = json.content.some((item: any) => item.type === "image");
expect(hasImage).toBe(true);
});
it("should handle boolean arguments - false", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"get-annotated-message",
"--tool-arg",
"messageType=error",
"includeImage=false",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
// Should only have text content, no image
const hasImage = json.content.some((item: any) => item.type === "image");
expect(hasImage).toBe(false);
// test-mcp-server returns "This is a {messageType} message"
expect(json.content[0].text.toLowerCase()).toContain("error");
});
it("should handle null arguments", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
'message="null"',
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content[0]).toHaveProperty("type", "text");
// The string "null" should be passed through
expect(json.content[0].text).toBe("Echo: null");
});
it("should handle multiple arguments with mixed types", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"get-sum",
"--tool-arg",
"a=42.5",
"b=57.5",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content.length).toBeGreaterThan(0);
expect(json.content[0]).toHaveProperty("type", "text");
// test-mcp-server returns JSON with {result: a+b}
const resultData = JSON.parse(json.content[0].text);
expect(resultData.result).toBeCloseTo(100.0, 1);
});
});
describe("JSON Parsing Edge Cases", () => {
it("should fall back to string for invalid JSON", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message={invalid json}",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content[0]).toHaveProperty("type", "text");
// Should treat invalid JSON as a string
expect(json.content[0].text).toBe("Echo: {invalid json}");
});
it("should handle empty string value", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
'message=""',
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content[0]).toHaveProperty("type", "text");
// Empty string should be preserved
expect(json.content[0].text).toBe("Echo: ");
});
it("should handle special characters in strings", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
'message="C:\\\\Users\\\\test"',
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content[0]).toHaveProperty("type", "text");
// Special characters should be preserved
expect(json.content[0].text).toContain("C:");
expect(json.content[0].text).toContain("Users");
expect(json.content[0].text).toContain("test");
});
it("should handle unicode characters", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
'message="🚀🎉✨"',
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content[0]).toHaveProperty("type", "text");
// Unicode characters should be preserved
expect(json.content[0].text).toContain("🚀");
expect(json.content[0].text).toContain("🎉");
expect(json.content[0].text).toContain("✨");
});
it("should handle arguments with equals signs in values", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=2+2=4",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content[0]).toHaveProperty("type", "text");
// Equals signs in values should be preserved
expect(json.content[0].text).toBe("Echo: 2+2=4");
});
it("should handle base64-like strings", async () => {
const { command, args } = getTestMcpServerCommand();
const base64String =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0=";
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
`message=${base64String}`,
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content[0]).toHaveProperty("type", "text");
// Base64-like strings should be preserved
expect(json.content[0].text).toBe(`Echo: ${base64String}`);
});
});
describe("Tool Error Handling", () => {
it("should fail with nonexistent tool", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"nonexistent_tool",
"--tool-arg",
"message=test",
]);
// CLI returns exit code 0 but includes isError: true in JSON
expectJsonError(result);
});
it("should fail when tool name is missing", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-arg",
"message=test",
]);
expectCliFailure(result);
});
it("should fail with invalid tool argument format", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"invalid_format_no_equals",
]);
expectCliFailure(result);
});
});
describe("Prompt JSON Arguments", () => {
it("should handle prompt with JSON arguments", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"prompts/get",
"--prompt-name",
"args-prompt",
"--prompt-args",
"city=New York",
"state=NY",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("messages");
expect(Array.isArray(json.messages)).toBe(true);
expect(json.messages.length).toBeGreaterThan(0);
expect(json.messages[0]).toHaveProperty("content");
expect(json.messages[0].content).toHaveProperty("type", "text");
// Validate that the arguments were actually used in the response
// test-mcp-server formats it as "This is a prompt with arguments: city={city}, state={state}"
expect(json.messages[0].content.text).toContain("city=New York");
expect(json.messages[0].content.text).toContain("state=NY");
});
it("should handle prompt with simple arguments", async () => {
// Note: simple-prompt doesn't accept arguments, but the CLI should still
// accept the command and the server should ignore the arguments
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"prompts/get",
"--prompt-name",
"simple-prompt",
"--prompt-args",
"name=test",
"count=5",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("messages");
expect(Array.isArray(json.messages)).toBe(true);
expect(json.messages.length).toBeGreaterThan(0);
expect(json.messages[0]).toHaveProperty("content");
expect(json.messages[0].content).toHaveProperty("type", "text");
// test-mcp-server's simple-prompt returns standard message (ignoring args)
expect(json.messages[0].content.text).toBe(
"This is a simple prompt for testing purposes.",
);
});
});
describe("Backward Compatibility", () => {
it("should support existing string-only usage", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=hello",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content[0]).toHaveProperty("type", "text");
expect(json.content[0].text).toBe("Echo: hello");
});
it("should support multiple string arguments", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"get-sum",
"--tool-arg",
"a=10",
"b=20",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content.length).toBeGreaterThan(0);
expect(json.content[0]).toHaveProperty("type", "text");
// test-mcp-server returns JSON with {result: a+b}
const resultData = JSON.parse(json.content[0].text);
expect(resultData.result).toBe(30);
});
});
});
================================================
FILE: cli/package.json
================================================
{
"name": "@modelcontextprotocol/inspector-cli",
"version": "0.21.1",
"description": "CLI for the Model Context Protocol inspector",
"license": "SEE LICENSE IN LICENSE",
"author": "Model Context Protocol a Series of LF Projects, LLC.",
"homepage": "https://modelcontextprotocol.io",
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
"main": "build/cli.js",
"type": "module",
"bin": {
"mcp-inspector-cli": "build/cli.js"
},
"files": [
"build",
"LICENSE"
],
"scripts": {
"build": "tsc",
"postbuild": "node scripts/make-executable.js",
"test": "vitest run",
"test:watch": "vitest",
"test:cli": "vitest run cli.test.ts",
"test:cli-tools": "vitest run tools.test.ts",
"test:cli-headers": "vitest run headers.test.ts",
"test:cli-metadata": "vitest run metadata.test.ts"
},
"devDependencies": {
"@types/express": "^5.0.0",
"tsx": "^4.7.0",
"vitest": "^4.0.17"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.25.2",
"commander": "^13.1.0",
"express": "^5.2.1",
"spawn-rx": "^5.1.2"
}
}
================================================
FILE: cli/scripts/make-executable.js
================================================
/**
* Cross-platform script to make a file executable
*/
import { promises as fs } from "fs";
import { platform } from "os";
import { execSync } from "child_process";
import path from "path";
const TARGET_FILE = path.resolve("build/cli.js");
async function makeExecutable() {
try {
// On Unix-like systems (Linux, macOS), use chmod
if (platform() !== "win32") {
execSync(`chmod +x "${TARGET_FILE}"`);
console.log("Made file executable with chmod");
} else {
// On Windows, no need to make files "executable" in the Unix sense
// Just ensure the file exists
await fs.access(TARGET_FILE);
console.log("File exists and is accessible on Windows");
}
} catch (error) {
console.error("Error making file executable:", error);
process.exit(1);
}
}
makeExecutable();
================================================
FILE: cli/src/cli.ts
================================================
#!/usr/bin/env node
import { Command } from "commander";
import fs from "node:fs";
import path from "node:path";
import { dirname, resolve } from "path";
import { spawnPromise } from "spawn-rx";
import { fileURLToPath } from "url";
const __dirname = dirname(fileURLToPath(import.meta.url));
type Args = {
command: string;
args: string[];
envArgs: Record<string, string>;
cli: boolean;
transport?: "stdio" | "sse" | "streamable-http";
serverUrl?: string;
headers?: Record<string, string>;
};
type CliOptions = {
e?: Record<string, string>;
config?: string;
server?: string;
cli?: boolean;
transport?: string;
serverUrl?: string;
header?: Record<string, string>;
};
type ServerConfig =
| {
type: "stdio";
command: string;
args?: string[];
env?: Record<string, string>;
}
| {
type: "sse" | "streamable-http";
url: string;
note?: string;
};
function handleError(error: unknown): never {
let message: string;
if (error instanceof Error) {
message = error.message;
} else if (typeof error === "string") {
message = error;
} else {
message = "Unknown error";
}
console.error(message);
process.exit(1);
}
function delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms, true));
}
async function runWebClient(args: Args): Promise<void> {
// Path to the client entry point
const inspectorClientPath = resolve(
__dirname,
"../../",
"client",
"bin",
"start.js",
);
const abort = new AbortController();
let cancelled: boolean = false;
process.on("SIGINT", () => {
cancelled = true;
abort.abort();
});
// Build arguments to pass to start.js
const startArgs: string[] = [];
// Pass environment variables
for (const [key, value] of Object.entries(args.envArgs)) {
startArgs.push("-e", `${key}=${value}`);
}
// Pass transport type if specified
if (args.transport) {
startArgs.push("--transport", args.transport);
}
// Pass server URL if specified
if (args.serverUrl) {
startArgs.push("--server-url", args.serverUrl);
}
// Pass command and args (using -- to separate them)
if (args.command) {
startArgs.push("--", args.command, ...args.args);
}
try {
await spawnPromise("node", [inspectorClientPath, ...startArgs], {
signal: abort.signal,
echoOutput: true,
// pipe the stdout through here, prevents issues with buffering and
// dropping the end of console.out after 8192 chars due to node
// closing the stdout pipe before the output has finished flushing
stdio: "inherit",
});
} catch (e) {
if (!cancelled || process.env.DEBUG) throw e;
}
}
async function runCli(args: Args): Promise<void> {
const projectRoot = resolve(__dirname, "..");
const cliPath = resolve(projectRoot, "build", "index.js");
const abort = new AbortController();
let cancelled = false;
process.on("SIGINT", () => {
cancelled = true;
abort.abort();
});
try {
// Build CLI arguments
const cliArgs = [cliPath];
// Add target URL/command first
cliArgs.push(args.command, ...args.args);
// Add transport flag if specified
if (args.transport && args.transport !== "stdio") {
// Convert streamable-http back to http for CLI mode
const cliTransport =
args.transport === "streamable-http" ? "http" : args.transport;
cliArgs.push("--transport", cliTransport);
}
// Add headers if specified
if (args.headers) {
for (const [key, value] of Object.entries(args.headers)) {
cliArgs.push("--header", `${key}: ${value}`);
}
}
await spawnPromise("node", cliArgs, {
env: { ...process.env, ...args.envArgs },
signal: abort.signal,
echoOutput: true,
// pipe the stdout through here, prevents issues with buffering and
// dropping the end of console.out after 8192 chars due to node
// closing the stdout pipe before the output has finished flushing
stdio: "inherit",
});
} catch (e) {
if (!cancelled || process.env.DEBUG) {
throw e;
}
}
}
function loadConfigFile(configPath: string, serverName: string): ServerConfig {
try {
const resolvedConfigPath = path.isAbsolute(configPath)
? configPath
: path.resolve(process.cwd(), configPath);
if (!fs.existsSync(resolvedConfigPath)) {
throw new Error(`Config file not found: ${resolvedConfigPath}`);
}
const configContent = fs.readFileSync(resolvedConfigPath, "utf8");
const parsedConfig = JSON.parse(configContent);
if (!parsedConfig.mcpServers || !parsedConfig.mcpServers[serverName]) {
const availableServers = Object.keys(parsedConfig.mcpServers || {}).join(
", ",
);
throw new Error(
`Server '${serverName}' not found in config file. Available servers: ${availableServers}`,
);
}
const serverConfig = parsedConfig.mcpServers[serverName];
return serverConfig;
} catch (err: unknown) {
if (err instanceof SyntaxError) {
throw new Error(`Invalid JSON in config file: ${err.message}`);
}
throw err;
}
}
function parseKeyValuePair(
value: string,
previous: Record<string, string> = {},
): Record<string, string> {
const parts = value.split("=");
const key = parts[0];
const val = parts.slice(1).join("=");
if (val === undefined || val === "") {
throw new Error(
`Invalid parameter format: ${value}. Use key=value format.`,
);
}
return { ...previous, [key as string]: val };
}
function parseHeaderPair(
value: string,
previous: Record<string, string> = {},
): Record<string, string> {
const colonIndex = value.indexOf(":");
if (colonIndex === -1) {
throw new Error(
`Invalid header format: ${value}. Use "HeaderName: Value" format.`,
);
}
const key = value.slice(0, colonIndex).trim();
const val = value.slice(colonIndex + 1).trim();
if (key === "" || val === "") {
throw new Error(
`Invalid header format: ${value}. Use "HeaderName: Value" format.`,
);
}
return { ...previous, [key]: val };
}
function parseArgs(): Args {
const program = new Command();
const argSeparatorIndex = process.argv.indexOf("--");
let preArgs = process.argv;
let postArgs: string[] = [];
if (argSeparatorIndex !== -1) {
preArgs = process.argv.slice(0, argSeparatorIndex);
postArgs = process.argv.slice(argSeparatorIndex + 1);
}
program
.name("inspector-bin")
.allowExcessArguments()
.allowUnknownOption()
.option(
"-e <env>",
"environment variables in KEY=VALUE format",
parseKeyValuePair,
{},
)
.option("--config <path>", "config file path")
.option("--server <n>", "server name from config file")
.option("--cli", "enable CLI mode")
.option("--transport <type>", "transport type (stdio, sse, http)")
.option("--server-url <url>", "server URL for SSE/HTTP transport")
.option(
"--header <headers...>",
'HTTP headers as "HeaderName: Value" pairs (for HTTP/SSE transports)',
parseHeaderPair,
{},
);
// Parse only the arguments before --
program.parse(preArgs);
const options = program.opts() as CliOptions;
const remainingArgs = program.args;
// Add back any arguments that came after --
const finalArgs = [...remainingArgs, ...postArgs];
// Validate config and server options
if (!options.config && options.server) {
throw new Error("--server requires --config to be specified");
}
// If config is provided without server, try to auto-select
if (options.config && !options.server) {
const configContent = fs.readFileSync(
path.isAbsolute(options.config)
? options.config
: path.resolve(process.cwd(), options.config),
"utf8",
);
const parsedConfig = JSON.parse(configContent);
const servers = Object.keys(parsedConfig.mcpServers || {});
if (servers.length === 1) {
// Use the only server if there's just one
options.server = servers[0];
} else if (servers.length === 0) {
throw new Error("No servers found in config file");
} else {
// Multiple servers, require explicit selection
throw new Error(
`Multiple servers found in config file. Please specify one with --server.\nAvailable servers: ${servers.join(", ")}`,
);
}
}
// If config file is specified, load and use the options from the file. We must merge the args
// from the command line and the file together, or we will miss the method options (--method,
// etc.)
if (options.config && options.server) {
const config = loadConfigFile(options.config, options.server);
if (config.type === "stdio") {
return {
command: config.command,
args: [...(config.args || []), ...finalArgs],
envArgs: { ...(config.env || {}), ...(options.e || {}) },
cli: options.cli || false,
transport: "stdio",
headers: options.header,
};
} else if (config.type === "sse" || config.type === "streamable-http") {
return {
command: config.url,
args: finalArgs,
envArgs: options.e || {},
cli: options.cli || false,
transport: config.type,
serverUrl: config.url,
headers: options.header,
};
} else {
// Backwards compatibility: if no type field, assume stdio
return {
command: (config as any).command || "",
args: [...((config as any).args || []), ...finalArgs],
envArgs: { ...((config as any).env || {}), ...(options.e || {}) },
cli: options.cli || false,
transport: "stdio",
headers: options.header,
};
}
}
// Otherwise use command line arguments
const command = finalArgs[0] || "";
const args = finalArgs.slice(1);
// Map "http" shorthand to "streamable-http"
let transport = options.transport;
if (transport === "http") {
transport = "streamable-http";
}
return {
command,
args,
envArgs: options.e || {},
cli: options.cli || false,
transport: transport as "stdio" | "sse" | "streamable-http" | undefined,
serverUrl: options.serverUrl,
headers: options.header,
};
}
async function main(): Promise<void> {
process.on("uncaughtException", (error) => {
handleError(error);
});
try {
const args = parseArgs();
if (args.cli) {
await runCli(args);
} else {
await runWebClient(args);
}
} catch (error) {
handleError(error);
}
}
main();
================================================
FILE: cli/src/client/connection.ts
================================================
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
import { McpResponse } from "./types.js";
export const validLogLevels = [
"trace",
"debug",
"info",
"warn",
"error",
] as const;
export type LogLevel = (typeof validLogLevels)[number];
export async function connect(
client: Client,
transport: Transport,
): Promise<void> {
try {
await client.connect(transport);
if (client.getServerCapabilities()?.logging) {
// default logging level is undefined in the spec, but the user of the
// inspector most likely wants debug.
await client.setLoggingLevel("debug");
}
} catch (error) {
throw new Error(
`Failed to connect to MCP server: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
export async function disconnect(transport: Transport): Promise<void> {
try {
await transport.close();
} catch (error) {
throw new Error(
`Failed to disconnect from MCP server: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
// Set logging level
export async function setLoggingLevel(
client: Client,
level: LogLevel,
): Promise<McpResponse> {
try {
const response = await client.setLoggingLevel(level as any);
return response;
} catch (error) {
throw new Error(
`Failed to set logging level: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
================================================
FILE: cli/src/client/index.ts
================================================
// Re-export everything from the client modules
export * from "./connection.js";
export * from "./prompts.js";
export * from "./resources.js";
export * from "./tools.js";
export * from "./types.js";
================================================
FILE: cli/src/client/prompts.ts
================================================
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { McpResponse } from "./types.js";
// JSON value type matching the client utils
type JsonValue =
| string
| number
| boolean
| null
| undefined
| JsonValue[]
| { [key: string]: JsonValue };
// List available prompts
export async function listPrompts(
client: Client,
metadata?: Record<string, string>,
): Promise<McpResponse> {
try {
const params =
metadata && Object.keys(metadata).length > 0 ? { _meta: metadata } : {};
const response = await client.listPrompts(params);
return response;
} catch (error) {
throw new Error(
`Failed to list prompts: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
// Get a prompt
export async function getPrompt(
client: Client,
name: string,
args?: Record<string, JsonValue>,
metadata?: Record<string, string>,
): Promise<McpResponse> {
try {
// Convert all arguments to strings for prompt arguments
const stringArgs: Record<string, string> = {};
if (args) {
for (const [key, value] of Object.entries(args)) {
if (typeof value === "string") {
stringArgs[key] = value;
} else if (value === null || value === undefined) {
stringArgs[key] = String(value);
} else {
stringArgs[key] = JSON.stringify(value);
}
}
}
const params: any = {
name,
arguments: stringArgs,
};
if (metadata && Object.keys(metadata).length > 0) {
params._meta = metadata;
}
const response = await client.getPrompt(params);
return response;
} catch (error) {
throw new Error(
`Failed to get prompt: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
================================================
FILE: cli/src/client/resources.ts
================================================
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { McpResponse } from "./types.js";
// List available resources
export async function listResources(
client: Client,
metadata?: Record<string, string>,
): Promise<McpResponse> {
try {
const params =
metadata && Object.keys(metadata).length > 0 ? { _meta: metadata } : {};
const response = await client.listR
gitextract_mkor3vr7/
├── .dockerignore
├── .git-blame-ignore-revs
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ └── bug_report.md
│ ├── dependabot.yml
│ ├── pull_request_template.md
│ └── workflows/
│ ├── claude.yml
│ ├── cli_tests.yml
│ ├── e2e_tests.yml
│ └── main.yml
├── .gitignore
├── .husky/
│ └── pre-commit
├── .mcp.json
├── .node-version
├── .npmrc
├── .prettierignore
├── .prettierrc
├── AGENTS.md
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── SECURITY.md
├── cli/
│ ├── LICENSE
│ ├── __tests__/
│ │ ├── README.md
│ │ ├── cli.test.ts
│ │ ├── headers.test.ts
│ │ ├── helpers/
│ │ │ ├── assertions.ts
│ │ │ ├── cli-runner.ts
│ │ │ ├── fixtures.ts
│ │ │ ├── test-fixtures.ts
│ │ │ ├── test-server-http.ts
│ │ │ └── test-server-stdio.ts
│ │ ├── metadata.test.ts
│ │ └── tools.test.ts
│ ├── package.json
│ ├── scripts/
│ │ └── make-executable.js
│ ├── src/
│ │ ├── cli.ts
│ │ ├── client/
│ │ │ ├── connection.ts
│ │ │ ├── index.ts
│ │ │ ├── prompts.ts
│ │ │ ├── resources.ts
│ │ │ ├── tools.ts
│ │ │ └── types.ts
│ │ ├── error-handler.ts
│ │ ├── index.ts
│ │ ├── transport.ts
│ │ └── utils/
│ │ └── awaitable-log.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── client/
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── bin/
│ │ ├── client.js
│ │ └── start.js
│ ├── components.json
│ ├── e2e/
│ │ ├── cli-arguments.spec.ts
│ │ ├── global-teardown.js
│ │ ├── startup-state.spec.ts
│ │ └── transport-type-dropdown.spec.ts
│ ├── eslint.config.js
│ ├── index.html
│ ├── jest.config.cjs
│ ├── package.json
│ ├── playwright.config.ts
│ ├── postcss.config.js
│ ├── src/
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── __mocks__/
│ │ │ └── styleMock.js
│ │ ├── __tests__/
│ │ │ ├── App.config.test.tsx
│ │ │ ├── App.routing.test.tsx
│ │ │ ├── App.samplingNavigation.test.tsx
│ │ │ └── App.toolsAppsPrefill.test.tsx
│ │ ├── components/
│ │ │ ├── AppRenderer.tsx
│ │ │ ├── AppsTab.tsx
│ │ │ ├── AuthDebugger.tsx
│ │ │ ├── ConsoleTab.tsx
│ │ │ ├── CustomHeaders.tsx
│ │ │ ├── DynamicJsonForm.tsx
│ │ │ ├── ElicitationRequest.tsx
│ │ │ ├── ElicitationTab.tsx
│ │ │ ├── HistoryAndNotifications.tsx
│ │ │ ├── IconDisplay.tsx
│ │ │ ├── JsonEditor.tsx
│ │ │ ├── JsonView.tsx
│ │ │ ├── ListPane.tsx
│ │ │ ├── MetadataTab.tsx
│ │ │ ├── OAuthCallback.tsx
│ │ │ ├── OAuthDebugCallback.tsx
│ │ │ ├── OAuthFlowProgress.tsx
│ │ │ ├── PingTab.tsx
│ │ │ ├── PromptsTab.tsx
│ │ │ ├── ResourceLinkView.tsx
│ │ │ ├── ResourcesTab.tsx
│ │ │ ├── RootsTab.tsx
│ │ │ ├── SamplingRequest.tsx
│ │ │ ├── SamplingTab.tsx
│ │ │ ├── Sidebar.tsx
│ │ │ ├── TasksTab.tsx
│ │ │ ├── ToolResults.tsx
│ │ │ ├── ToolsTab.tsx
│ │ │ ├── __tests__/
│ │ │ │ ├── AppRenderer.test.tsx
│ │ │ │ ├── AppsTab.test.tsx
│ │ │ │ ├── AuthDebugger.test.tsx
│ │ │ │ ├── DynamicJsonForm.array.test.tsx
│ │ │ │ ├── DynamicJsonForm.test.tsx
│ │ │ │ ├── ElicitationRequest.test.tsx
│ │ │ │ ├── ElicitationTab.test.tsx
│ │ │ │ ├── HistoryAndNotifications.test.tsx
│ │ │ │ ├── ListPane.test.tsx
│ │ │ │ ├── MetadataTab.test.tsx
│ │ │ │ ├── ResourcesTab.test.tsx
│ │ │ │ ├── Sidebar.test.tsx
│ │ │ │ ├── ToolsTab.test.tsx
│ │ │ │ ├── samplingRequest.test.tsx
│ │ │ │ └── samplingTab.test.tsx
│ │ │ └── ui/
│ │ │ ├── alert.tsx
│ │ │ ├── button.tsx
│ │ │ ├── checkbox.tsx
│ │ │ ├── combobox.tsx
│ │ │ ├── command.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── input.tsx
│ │ │ ├── label.tsx
│ │ │ ├── popover.tsx
│ │ │ ├── select.tsx
│ │ │ ├── switch.tsx
│ │ │ ├── tabs.tsx
│ │ │ ├── textarea.tsx
│ │ │ ├── toast.tsx
│ │ │ ├── toaster.tsx
│ │ │ └── tooltip.tsx
│ │ ├── index.css
│ │ ├── lib/
│ │ │ ├── __tests__/
│ │ │ │ └── auth.test.ts
│ │ │ ├── auth-types.ts
│ │ │ ├── auth.ts
│ │ │ ├── configurationTypes.ts
│ │ │ ├── constants.ts
│ │ │ ├── hooks/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── useConnection.test.tsx
│ │ │ │ ├── useCompletionState.ts
│ │ │ │ ├── useConnection.ts
│ │ │ │ ├── useCopy.ts
│ │ │ │ ├── useDraggablePane.ts
│ │ │ │ ├── useTheme.ts
│ │ │ │ └── useToast.ts
│ │ │ ├── notificationTypes.ts
│ │ │ ├── oauth-state-machine.ts
│ │ │ ├── types/
│ │ │ │ └── customHeaders.ts
│ │ │ └── utils.ts
│ │ ├── main.tsx
│ │ ├── utils/
│ │ │ ├── __tests__/
│ │ │ │ ├── configUtils.test.ts
│ │ │ │ ├── escapeUnicode.test.ts
│ │ │ │ ├── jsonUtils.test.ts
│ │ │ │ ├── oauthUtils.test.ts
│ │ │ │ ├── paramUtils.test.ts
│ │ │ │ ├── schemaUtils.test.ts
│ │ │ │ └── urlValidation.test.ts
│ │ │ ├── configUtils.ts
│ │ │ ├── escapeUnicode.ts
│ │ │ ├── jsonUtils.ts
│ │ │ ├── metaUtils.ts
│ │ │ ├── oauthUtils.ts
│ │ │ ├── paramUtils.ts
│ │ │ ├── schemaUtils.ts
│ │ │ └── urlValidation.ts
│ │ └── vite-env.d.ts
│ ├── tailwind.config.js
│ ├── tsconfig.app.json
│ ├── tsconfig.jest.json
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── package.json
├── sample-config.json
├── scripts/
│ ├── README.md
│ ├── check-version-consistency.js
│ └── update-version.js
└── server/
├── LICENSE
├── package.json
├── src/
│ ├── index.ts
│ └── mcpProxy.ts
├── static/
│ └── sandbox_proxy.html
└── tsconfig.json
SYMBOL INDEX (270 symbols across 77 files)
FILE: cli/__tests__/helpers/assertions.ts
function formatCliOutput (line 4) | function formatCliOutput(result: CliResult): string {
function expectCliSuccess (line 13) | function expectCliSuccess(result: CliResult) {
function expectCliFailure (line 23) | function expectCliFailure(result: CliResult) {
function expectOutputContains (line 33) | function expectOutputContains(result: CliResult, text: string) {
function expectValidJson (line 41) | function expectValidJson(result: CliResult) {
function expectJsonError (line 49) | function expectJsonError(result: CliResult) {
function expectJsonStructure (line 58) | function expectJsonStructure(result: CliResult, expectedKeys: string[]) {
FILE: cli/__tests__/helpers/cli-runner.ts
constant CLI_PATH (line 7) | const CLI_PATH = resolve(__dirname, "../../build/cli.js");
type CliResult (line 9) | interface CliResult {
type CliOptions (line 16) | interface CliOptions {
function runCli (line 26) | async function runCli(
FILE: cli/__tests__/helpers/fixtures.ts
constant NO_SERVER_SENTINEL (line 11) | const NO_SERVER_SENTINEL = "invalid-command-that-does-not-exist";
function createSampleTestConfig (line 20) | function createSampleTestConfig(httpUrl?: string): string {
function createTempDir (line 44) | function createTempDir(prefix: string = "mcp-inspector-test-"): string {
function cleanupTempDir (line 54) | function cleanupTempDir(dir: string) {
function createTestConfig (line 65) | function createTestConfig(config: {
function createInvalidConfig (line 77) | function createInvalidConfig(): string {
function deleteConfigFile (line 87) | function deleteConfigFile(configPath: string): void {
FILE: cli/__tests__/helpers/test-fixtures.ts
type ToolInputSchema (line 10) | type ToolInputSchema = ZodRawShapeCompat;
type ToolDefinition (line 12) | interface ToolDefinition {
type ResourceDefinition (line 19) | interface ResourceDefinition {
type PromptArgsSchema (line 27) | type PromptArgsSchema = ZodRawShapeCompat;
type PromptDefinition (line 29) | interface PromptDefinition {
type ServerConfig (line 36) | interface ServerConfig {
function createEchoTool (line 47) | function createEchoTool(): ToolDefinition {
function createAddTool (line 63) | function createAddTool(): ToolDefinition {
function createGetSumTool (line 82) | function createGetSumTool(): ToolDefinition {
function createGetAnnotatedMessageTool (line 101) | function createGetAnnotatedMessageTool(): ToolDefinition {
function createSimplePrompt (line 144) | function createSimplePrompt(): PromptDefinition {
function createArgsPrompt (line 154) | function createArgsPrompt(): PromptDefinition {
function createArchitectureResource (line 168) | function createArchitectureResource(): ResourceDefinition {
function createTestCwdResource (line 199) | function createTestCwdResource(): ResourceDefinition {
function createTestEnvResource (line 212) | function createTestEnvResource(): ResourceDefinition {
function createTestArgvResource (line 225) | function createTestArgvResource(): ResourceDefinition {
function createTestServerInfo (line 238) | function createTestServerInfo(
function getDefaultServerConfig (line 251) | function getDefaultServerConfig(): ServerConfig {
FILE: cli/__tests__/helpers/test-server-http.ts
type RecordedRequest (line 13) | interface RecordedRequest {
function findAvailablePort (line 25) | async function findAvailablePort(startPort: number): Promise<number> {
function extractHeaders (line 48) | function extractHeaders(req: Request): Record<string, string> {
class TestServerHttp (line 62) | class TestServerHttp {
method constructor (line 72) | constructor(config: ServerConfig) {
method setupHandlers (line 105) | private setupHandlers() {
method setupLoggingHandler (line 178) | private setupLoggingHandler() {
method start (line 194) | async start(
method startHttp (line 212) | private async startHttp(port: number): Promise<number> {
method startSse (line 311) | private async startSse(port: number): Promise<number> {
method stop (line 395) | async stop(): Promise<void> {
method getRecordedRequests (line 418) | getRecordedRequests(): RecordedRequest[] {
method clearRecordings (line 425) | clearRecordings(): void {
method getUrl (line 432) | getUrl(): string {
method getCurrentLogLevel (line 442) | getCurrentLogLevel(): string | null {
function createTestServerHttp (line 450) | function createTestServerHttp(config: ServerConfig): TestServerHttp {
FILE: cli/__tests__/helpers/test-server-stdio.ts
class TestServerStdio (line 24) | class TestServerStdio {
method constructor (line 29) | constructor(config: ServerConfig) {
method setupHandlers (line 59) | private setupHandlers() {
method start (line 181) | async start(): Promise<void> {
method stop (line 189) | async stop(): Promise<void> {
function createTestServerStdio (line 201) | function createTestServerStdio(config: ServerConfig): TestServerStdio {
function getTestMcpServerPath (line 208) | function getTestMcpServerPath(): string {
function getTestMcpServerCommand (line 215) | function getTestMcpServerCommand(): { command: string; args: string[] } {
FILE: cli/scripts/make-executable.js
constant TARGET_FILE (line 9) | const TARGET_FILE = path.resolve("build/cli.js");
function makeExecutable (line 11) | async function makeExecutable() {
FILE: cli/src/cli.ts
type Args (line 12) | type Args = {
type CliOptions (line 22) | type CliOptions = {
type ServerConfig (line 32) | type ServerConfig =
function handleError (line 45) | function handleError(error: unknown): never {
function delay (line 61) | function delay(ms: number): Promise<void> {
function runWebClient (line 65) | async function runWebClient(args: Args): Promise<void> {
function runCli (line 119) | async function runCli(args: Args): Promise<void> {
function loadConfigFile (line 170) | function loadConfigFile(configPath: string, serverName: string): ServerC...
function parseKeyValuePair (line 204) | function parseKeyValuePair(
function parseHeaderPair (line 221) | function parseHeaderPair(
function parseArgs (line 245) | function parseArgs(): Args {
function main (line 376) | async function main(): Promise<void> {
FILE: cli/src/client/connection.ts
type LogLevel (line 13) | type LogLevel = (typeof validLogLevels)[number];
function connect (line 15) | async function connect(
function disconnect (line 34) | async function disconnect(transport: Transport): Promise<void> {
function setLoggingLevel (line 45) | async function setLoggingLevel(
FILE: cli/src/client/prompts.ts
type JsonValue (line 5) | type JsonValue =
function listPrompts (line 15) | async function listPrompts(
function getPrompt (line 32) | async function getPrompt(
FILE: cli/src/client/resources.ts
function listResources (line 5) | async function listResources(
function readResource (line 22) | async function readResource(
function listResourceTemplates (line 42) | async function listResourceTemplates(
FILE: cli/src/client/tools.ts
type JsonValue (line 6) | type JsonValue =
type JsonSchemaType (line 15) | type JsonSchemaType = {
function listTools (line 22) | async function listTools(
function convertParameterValue (line 38) | function convertParameterValue(
function convertParameters (line 65) | function convertParameters(
function callTool (line 86) | async function callTool(
FILE: cli/src/client/types.ts
type McpResponse (line 1) | type McpResponse = Record<string, unknown>;
FILE: cli/src/error-handler.ts
function formatError (line 1) | function formatError(error: unknown): string {
function handleError (line 15) | function handleError(error: unknown): never {
FILE: cli/src/index.ts
type JsonValue (line 26) | type JsonValue =
type Args (line 35) | type Args = {
function createTransportOptions (line 50) | function createTransportOptions(
function callMethod (line 104) | async function callMethod(args: Args): Promise<void> {
function parseKeyValuePair (line 206) | function parseKeyValuePair(
function parseHeaderPair (line 232) | function parseHeaderPair(
function parseArgs (line 256) | function parseArgs(): Args {
function main (line 404) | async function main(): Promise<void> {
FILE: cli/src/transport.ts
type TransportOptions (line 10) | type TransportOptions = {
function createStdioTransport (line 18) | function createStdioTransport(options: TransportOptions): Transport {
function createTransport (line 53) | function createTransport(options: TransportOptions): Transport {
FILE: cli/src/utils/awaitable-log.ts
function awaitableLog (line 1) | function awaitableLog(logValue: string): Promise<void> {
FILE: client/bin/start.js
constant DEFAULT_MCP_PROXY_LISTEN_PORT (line 10) | const DEFAULT_MCP_PROXY_LISTEN_PORT = "6277";
function delay (line 12) | function delay(ms) {
function getClientUrl (line 16) | function getClientUrl(port, authDisabled, sessionToken, serverPort) {
function startDevServer (line 30) | async function startDevServer(serverOptions) {
function startProdServer (line 83) | async function startProdServer(serverOptions) {
function startDevClient (line 133) | async function startDevClient(clientOptions) {
function startProdClient (line 191) | async function startProdClient(clientOptions) {
function main (line 226) | async function main() {
FILE: client/e2e/global-teardown.js
function globalTeardown (line 3) | async function globalTeardown() {
FILE: client/e2e/startup-state.spec.ts
constant APP_URL (line 4) | const APP_URL = "http://localhost:6274/";
FILE: client/e2e/transport-type-dropdown.spec.ts
constant APP_URL (line 4) | const APP_URL = "http://localhost:6274/";
FILE: client/src/App.tsx
constant CONFIG_LOCAL_STORAGE_KEY (line 103) | const CONFIG_LOCAL_STORAGE_KEY = "inspectorConfig_v1";
type PrefilledAppsToolCall (line 105) | type PrefilledAppsToolCall = {
FILE: client/src/__tests__/App.samplingNavigation.test.tsx
type OnPendingRequestHandler (line 16) | type OnPendingRequestHandler = (
type SamplingRequestMockProps (line 22) | type SamplingRequestMockProps = {
type UseConnectionReturn (line 28) | type UseConnectionReturn = ReturnType<typeof useConnection>;
FILE: client/src/__tests__/App.toolsAppsPrefill.test.tsx
type ToolListEntry (line 7) | type ToolListEntry = {
type AppsTabProps (line 16) | type AppsTabProps = {
FILE: client/src/components/AppRenderer.tsx
type AppRendererProps (line 25) | interface AppRendererProps {
FILE: client/src/components/AppsTab.tsx
type AppsTabProps (line 44) | interface AppsTabProps {
FILE: client/src/components/AuthDebugger.tsx
type AuthDebuggerProps (line 11) | interface AuthDebuggerProps {
type StatusMessageProps (line 18) | interface StatusMessageProps {
FILE: client/src/components/CustomHeaders.tsx
type CustomHeadersProps (line 13) | interface CustomHeadersProps {
FILE: client/src/components/DynamicJsonForm.tsx
type DynamicJsonFormProps (line 22) | interface DynamicJsonFormProps {
type DynamicJsonFormRef (line 29) | interface DynamicJsonFormRef {
FILE: client/src/components/ElicitationRequest.tsx
type ElicitationRequestProps (line 13) | type ElicitationRequestProps = {
FILE: client/src/components/ElicitationTab.tsx
type ElicitationRequestData (line 6) | interface ElicitationRequestData {
type ElicitationResponse (line 12) | interface ElicitationResponse {
type PendingElicitationRequest (line 17) | type PendingElicitationRequest = {
type Props (line 23) | type Props = {
FILE: client/src/components/IconDisplay.tsx
type Icon (line 2) | interface Icon {
type WithIcons (line 10) | interface WithIcons {
type IconDisplayProps (line 14) | interface IconDisplayProps {
FILE: client/src/components/JsonEditor.tsx
type JsonEditorProps (line 7) | interface JsonEditorProps {
FILE: client/src/components/JsonView.tsx
type JsonViewProps (line 11) | interface JsonViewProps {
type JsonNodeProps (line 89) | interface JsonNodeProps {
FILE: client/src/components/ListPane.tsx
type ListPaneProps (line 6) | type ListPaneProps<T> = {
FILE: client/src/components/MetadataTab.tsx
type MetadataEntry (line 17) | interface MetadataEntry {
type MetadataTabProps (line 22) | interface MetadataTabProps {
FILE: client/src/components/OAuthCallback.tsx
type OAuthCallbackProps (line 11) | interface OAuthCallbackProps {
FILE: client/src/components/OAuthDebugCallback.tsx
type OAuthCallbackProps (line 9) | interface OAuthCallbackProps {
FILE: client/src/components/OAuthFlowProgress.tsx
type OAuthStepProps (line 11) | interface OAuthStepProps {
type OAuthFlowProgressProps (line 55) | interface OAuthFlowProgressProps {
FILE: client/src/components/PromptsTab.tsx
type Prompt (line 19) | type Prompt = {
FILE: client/src/components/ResourceLinkView.tsx
type ResourceLinkViewProps (line 4) | interface ResourceLinkViewProps {
FILE: client/src/components/SamplingRequest.tsx
type SamplingRequestProps (line 13) | type SamplingRequestProps = {
FILE: client/src/components/SamplingTab.tsx
type PendingRequest (line 9) | type PendingRequest = {
type Props (line 15) | type Props = {
FILE: client/src/components/Sidebar.tsx
type SidebarProps (line 46) | interface SidebarProps {
FILE: client/src/components/ToolResults.tsx
type ToolResultsProps (line 10) | interface ToolResultsProps {
FILE: client/src/components/ToolsTab.tsx
type ExtendedTool (line 59) | interface ExtendedTool extends Tool, WithIcons {
FILE: client/src/components/__tests__/AppRenderer.test.tsx
type BridgeEvent (line 13) | type BridgeEvent = {
type MockMcpUiRendererProps (line 19) | type MockMcpUiRendererProps = {
FILE: client/src/components/ui/button.tsx
type ButtonProps (line 37) | interface ButtonProps
FILE: client/src/components/ui/combobox.tsx
type ComboboxProps (line 18) | interface ComboboxProps {
function Combobox (line 29) | function Combobox({
FILE: client/src/components/ui/input.tsx
type InputProps (line 5) | type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
FILE: client/src/components/ui/textarea.tsx
type TextareaProps (line 5) | type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
FILE: client/src/components/ui/toast.tsx
type ToastProps (line 112) | type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement (line 114) | type ToastActionElement = React.ReactElement<typeof ToastAction>;
FILE: client/src/components/ui/toaster.tsx
function Toaster (line 11) | function Toaster() {
FILE: client/src/lib/auth-types.ts
type OAuthStep (line 10) | type OAuthStep =
type MessageType (line 19) | type MessageType = "success" | "error" | "info";
type StatusMessage (line 21) | interface StatusMessage {
type AuthDebuggerState (line 27) | interface AuthDebuggerState {
constant EMPTY_DEBUGGER_STATE (line 44) | const EMPTY_DEBUGGER_STATE: AuthDebuggerState = {
FILE: client/src/lib/auth.ts
class InspectorOAuthClientProvider (line 130) | class InspectorOAuthClientProvider implements OAuthClientProvider {
method constructor (line 131) | constructor(protected serverUrl: string) {
method scope (line 136) | get scope(): string | undefined {
method redirectUrl (line 140) | get redirectUrl() {
method debugRedirectUrl (line 144) | get debugRedirectUrl() {
method redirect_uris (line 148) | get redirect_uris() {
method clientMetadata (line 155) | get clientMetadata(): OAuthClientMetadata {
method state (line 174) | state(): string | Promise<string> {
method clientInformation (line 178) | async clientInformation() {
method saveClientInformation (line 196) | saveClientInformation(clientInformation: OAuthClientInformation) {
method tokens (line 205) | async tokens() {
method saveTokens (line 215) | saveTokens(tokens: OAuthTokens) {
method redirectToAuthorization (line 220) | redirectToAuthorization(authorizationUrl: URL) {
method saveCodeVerifier (line 226) | saveCodeVerifier(codeVerifier: string) {
method codeVerifier (line 234) | codeVerifier() {
method clear (line 247) | clear() {
class DebugInspectorOAuthClientProvider (line 263) | class DebugInspectorOAuthClientProvider extends InspectorOAuthClientProv...
method redirectUrl (line 264) | get redirectUrl(): string {
method saveServerMetadata (line 270) | saveServerMetadata(metadata: OAuthMetadata) {
method getServerMetadata (line 278) | getServerMetadata(): OAuthMetadata | null {
method clear (line 290) | clear() {
FILE: client/src/lib/configurationTypes.ts
type ConfigItem (line 1) | type ConfigItem = {
type InspectorConfig (line 15) | type InspectorConfig = {
FILE: client/src/lib/constants.ts
constant CLIENT_IDENTITY (line 5) | const CLIENT_IDENTITY = (() => {
constant SESSION_KEYS (line 12) | const SESSION_KEYS = {
type ConnectionStatus (line 32) | type ConnectionStatus =
constant DEFAULT_MCP_PROXY_LISTEN_PORT (line 38) | const DEFAULT_MCP_PROXY_LISTEN_PORT = "6277";
constant DEFAULT_INSPECTOR_CONFIG (line 44) | const DEFAULT_INSPECTOR_CONFIG: InspectorConfig = {
FILE: client/src/lib/hooks/__tests__/useConnection.test.tsx
class SseError (line 77) | class SseError extends Error {
method constructor (line 80) | constructor(code: number, message: string, event: ErrorEvent) {
FILE: client/src/lib/hooks/useCompletionState.ts
type CompletionState (line 7) | interface CompletionState {
function debounce (line 13) | function debounce<T extends (...args: any[]) => PromiseLike<void>>(
function useCompletionState (line 26) | function useCompletionState(
FILE: client/src/lib/hooks/useConnection.ts
type UseConnectionOptions (line 80) | interface UseConnectionOptions {
function useConnection (line 106) | function useConnection({
FILE: client/src/lib/hooks/useCopy.ts
type UseCopyProps (line 5) | type UseCopyProps = {
function useCopy (line 9) | function useCopy({ timeout = 500 }: UseCopyProps = {}) {
FILE: client/src/lib/hooks/useDraggablePane.ts
function useDraggablePane (line 3) | function useDraggablePane(initialHeight: number) {
function useDraggableSidebar (line 55) | function useDraggableSidebar(initialWidth: number) {
FILE: client/src/lib/hooks/useTheme.ts
type Theme (line 3) | type Theme = "light" | "dark" | "system";
FILE: client/src/lib/hooks/useToast.ts
constant TOAST_LIMIT (line 8) | const TOAST_LIMIT = 1;
constant TOAST_REMOVE_DELAY (line 9) | const TOAST_REMOVE_DELAY = 1000000;
type ToasterToast (line 11) | type ToasterToast = ToastProps & {
function genId (line 20) | function genId() {
type ActionType (line 25) | const enum ActionType {
type Action (line 32) | type Action =
type State (line 50) | interface State {
function dispatch (line 131) | function dispatch(action: Action) {
type Toast (line 138) | type Toast = Omit<ToasterToast, "id">;
function toast (line 140) | function toast({ ...props }: Toast) {
function useToast (line 170) | function useToast() {
FILE: client/src/lib/notificationTypes.ts
type Notification (line 12) | type Notification = SchemaOutput<typeof NotificationSchema>;
FILE: client/src/lib/oauth-state-machine.ts
type StateMachineContext (line 17) | interface StateMachineContext {
type StateTransition (line 24) | interface StateTransition {
class OAuthStateMachine (line 210) | class OAuthStateMachine {
method constructor (line 211) | constructor(
method executeStep (line 216) | async executeStep(state: AuthDebuggerState): Promise<void> {
FILE: client/src/lib/types/customHeaders.ts
type CustomHeader (line 1) | interface CustomHeader {
type CustomHeaders (line 7) | type CustomHeaders = CustomHeader[];
FILE: client/src/lib/utils.ts
function cn (line 4) | function cn(...inputs: ClassValue[]) {
FILE: client/src/utils/escapeUnicode.ts
function escapeUnicode (line 2) | function escapeUnicode(obj: unknown): string {
FILE: client/src/utils/jsonUtils.ts
type JsonValue (line 1) | type JsonValue =
type JsonSchemaConst (line 10) | type JsonSchemaConst = {
type JsonSchemaType (line 16) | type JsonSchemaType = {
type JsonObject (line 59) | type JsonObject = { [key: string]: JsonValue };
type DataType (line 61) | type DataType =
function getDataType (line 78) | function getDataType(value: JsonValue): DataType {
function tryParseJson (line 89) | function tryParseJson(str: string): {
function updateValueAtPath (line 115) | function updateValueAtPath(
function updateArray (line 142) | function updateArray(
function updateObject (line 189) | function updateObject(
function getValueAtPath (line 223) | function getValueAtPath(
FILE: client/src/utils/metaUtils.ts
constant META_PREFIX_LABEL_REGEX (line 7) | const META_PREFIX_LABEL_REGEX = /^[a-z](?:[a-z\d-]*[a-z\d])?$/i;
constant META_NAME_REGEX (line 8) | const META_NAME_REGEX = /^[a-z\d](?:[a-z\d._-]*[a-z\d])?$/i;
constant RESERVED_NAMESPACE_LABELS (line 9) | const RESERVED_NAMESPACE_LABELS = ["modelcontextprotocol", "mcp"];
constant RESERVED_NAMESPACE_MESSAGE (line 11) | const RESERVED_NAMESPACE_MESSAGE =
constant META_NAME_RULES_MESSAGE (line 14) | const META_NAME_RULES_MESSAGE =
constant META_PREFIX_RULES_MESSAGE (line 17) | const META_PREFIX_RULES_MESSAGE =
FILE: client/src/utils/oauthUtils.ts
type CallbackParams (line 4) | type CallbackParams =
FILE: client/src/utils/paramUtils.ts
function cleanParams (line 11) | function cleanParams(
FILE: client/src/utils/schemaUtils.ts
function cacheToolOutputSchemas (line 17) | function cacheToolOutputSchemas(tools: Tool[]): void {
function getToolOutputValidator (line 40) | function getToolOutputValidator(
function validateToolOutput (line 53) | function validateToolOutput(
function hasOutputSchema (line 78) | function hasOutputSchema(toolName: string): boolean {
function generateDefaultValue (line 89) | function generateDefaultValue(
function isPropertyRequired (line 151) | function isPropertyRequired(
function resolveRef (line 165) | function resolveRef(
function normalizeUnionType (line 237) | function normalizeUnionType(schema: JsonSchemaType): JsonSchemaType {
function formatFieldLabel (line 310) | function formatFieldLabel(key: string): string {
function resolveRefsInMessage (line 322) | function resolveRefsInMessage(message: JSONRPCMessage): JSONRPCMessage {
FILE: client/src/utils/urlValidation.ts
function validateRedirectUrl (line 8) | function validateRedirectUrl(url: string | URL): void {
FILE: server/src/index.ts
constant DEFAULT_MCP_PROXY_LISTEN_PORT (line 36) | const DEFAULT_MCP_PROXY_LISTEN_PORT = "6277";
method start (line 274) | start(controller) {
method cancel (line 293) | cancel() {
constant PORT (line 819) | const PORT = parseInt(
constant HOST (line 823) | const HOST = process.env.HOST || "localhost";
FILE: server/src/mcpProxy.ts
function onClientError (line 4) | function onClientError(error: Error) {
function onServerError (line 8) | function onServerError(error: Error) {
function mcpProxy (line 18) | function mcpProxy({
Condensed preview — 185 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,203K chars).
[
{
"path": ".dockerignore",
"chars": 328,
"preview": "# Version control\n.git\n.gitignore\n\n# Node.js\nnode_modules\nnpm-debug.log\n\n# Build artifacts\nclient/dist\nclient/build\nserv"
},
{
"path": ".git-blame-ignore-revs",
"chars": 0,
"preview": ""
},
{
"path": ".gitattributes",
"chars": 42,
"preview": "package-lock.json linguist-generated=true\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 948,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"\"\nlabels: \"\"\nassignees: \"\"\n---\n\n**Inspector Versi"
},
{
"path": ".github/dependabot.yml",
"chars": 118,
"preview": "version: 2\nupdates:\n - package-ecosystem: \"github-actions\"\n directory: \"/\"\n schedule:\n interval: \"weekly\"\n"
},
{
"path": ".github/pull_request_template.md",
"chars": 1701,
"preview": "## Summary\n\n<!-- Provide a brief description of what this PR does -->\n\n> **Note:** Inspector V2 is under development to "
},
{
"path": ".github/workflows/claude.yml",
"chars": 3107,
"preview": "name: Claude Code\n\non:\n issue_comment:\n types: [created]\n pull_request_review_comment:\n types: [created]\n issue"
},
{
"path": ".github/workflows/cli_tests.yml",
"chars": 652,
"preview": "name: CLI Tests\n\non:\n push:\n paths:\n - \"cli/**\"\n pull_request:\n paths:\n - \"cli/**\"\n\njobs:\n test:\n "
},
{
"path": ".github/workflows/e2e_tests.yml",
"chars": 2533,
"preview": "name: Playwright Tests\n\non:\n push:\n branches: [main]\n pull_request:\n branches: [main]\n\njobs:\n test:\n # Insta"
},
{
"path": ".github/workflows/main.yml",
"chars": 2850,
"preview": "on:\n push:\n branches:\n - main\n\n pull_request:\n release:\n types: [published]\n\njobs:\n build:\n runs-on: u"
},
{
"path": ".gitignore",
"chars": 369,
"preview": ".DS_Store\n.vscode\n.idea\nnode_modules/\n*-workspace/\nserver/build\nclient/dist\nclient/tsconfig.app.tsbuildinfo\nclient/tscon"
},
{
"path": ".husky/pre-commit",
"chars": 41,
"preview": "npx lint-staged\ngit update-index --again\n"
},
{
"path": ".mcp.json",
"chars": 123,
"preview": "{\n \"mcpServers\": {\n \"mcp-docs\": {\n \"type\": \"http\",\n \"url\": \"https://modelcontextprotocol.io/mcp\"\n }\n }"
},
{
"path": ".node-version",
"chars": 7,
"preview": "22.x.x\n"
},
{
"path": ".npmrc",
"chars": 100,
"preview": "registry=\"https://registry.npmjs.org/\"\n@modelcontextprotocol:registry=\"https://registry.npmjs.org/\"\n"
},
{
"path": ".prettierignore",
"chars": 89,
"preview": "packages\nserver/build\nCODE_OF_CONDUCT.md\nSECURITY.md\nmcp.json\n.claude/settings.local.json"
},
{
"path": ".prettierrc",
"chars": 0,
"preview": ""
},
{
"path": "AGENTS.md",
"chars": 1429,
"preview": "# MCP Inspector Development Guide\n\n> **Note:** Inspector V2 is under development to address architectural and UX improve"
},
{
"path": "CLAUDE.md",
"chars": 13,
"preview": "@./AGENTS.md\n"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 5223,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
},
{
"path": "CONTRIBUTING.md",
"chars": 2237,
"preview": "# Contributing to Model Context Protocol Inspector\n\nThanks for your interest in contributing! This guide explains how to"
},
{
"path": "Dockerfile",
"chars": 1197,
"preview": "# Build stage\nFROM node:current-alpine3.22 AS builder\n\n# Set working directory\nWORKDIR /app\n\n# Copy package files for in"
},
{
"path": "LICENSE",
"chars": 12227,
"preview": "The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 (\"Apache-2."
},
{
"path": "README.md",
"chars": 20924,
"preview": "# MCP Inspector\n\nThe MCP inspector is a developer tool for testing and debugging MCP servers.\n\n![MCP Inspector Screensho"
},
{
"path": "SECURITY.md",
"chars": 764,
"preview": "# Security Policy\n\nThank you for helping keep the Model Context Protocol and its ecosystem secure.\n\n## Reporting Securit"
},
{
"path": "cli/LICENSE",
"chars": 12227,
"preview": "The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 (\"Apache-2."
},
{
"path": "cli/__tests__/README.md",
"chars": 1981,
"preview": "# CLI Tests\n\n## Running Tests\n\n```bash\n# Run all tests\nnpm test\n\n# Run in watch mode (useful for test file changes; won'"
},
{
"path": "cli/__tests__/cli.test.ts",
"chars": 23796,
"preview": "import { describe, it, beforeAll, afterAll, expect } from \"vitest\";\nimport { runCli } from \"./helpers/cli-runner.js\";\nim"
},
{
"path": "cli/__tests__/headers.test.ts",
"chars": 6051,
"preview": "import { describe, it, expect } from \"vitest\";\nimport { runCli } from \"./helpers/cli-runner.js\";\nimport {\n expectCliFai"
},
{
"path": "cli/__tests__/helpers/assertions.ts",
"chars": 1714,
"preview": "import { expect } from \"vitest\";\nimport type { CliResult } from \"./cli-runner.js\";\n\nfunction formatCliOutput(result: Cli"
},
{
"path": "cli/__tests__/helpers/cli-runner.ts",
"chars": 2444,
"preview": "import { spawn } from \"child_process\";\nimport { resolve } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { dir"
},
{
"path": "cli/__tests__/helpers/fixtures.ts",
"chars": 2632,
"preview": "import * as fs from \"fs\";\nimport * as path from \"path\";\nimport * as os from \"os\";\nimport * as crypto from \"crypto\";\nimpo"
},
{
"path": "cli/__tests__/helpers/test-fixtures.ts",
"chars": 7390,
"preview": "/**\n * Shared types and test fixtures for composable MCP test servers\n */\n\nimport type { McpServer } from \"@modelcontext"
},
{
"path": "cli/__tests__/helpers/test-server-http.ts",
"chars": 13485,
"preview": "import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StreamableHTTPServerTransport } from \"@mod"
},
{
"path": "cli/__tests__/helpers/test-server-stdio.ts",
"chars": 6688,
"preview": "#!/usr/bin/env node\n\n/**\n * Test MCP server for stdio transport testing\n * Can be used programmatically or run as a stan"
},
{
"path": "cli/__tests__/metadata.test.ts",
"chars": 25925,
"preview": "import { describe, it, expect } from \"vitest\";\nimport { runCli } from \"./helpers/cli-runner.js\";\nimport {\n expectCliSuc"
},
{
"path": "cli/__tests__/tools.test.ts",
"chars": 16561,
"preview": "import { describe, it, expect } from \"vitest\";\nimport { runCli } from \"./helpers/cli-runner.js\";\nimport {\n expectCliSuc"
},
{
"path": "cli/package.json",
"chars": 1112,
"preview": "{\n \"name\": \"@modelcontextprotocol/inspector-cli\",\n \"version\": \"0.21.1\",\n \"description\": \"CLI for the Model Context Pr"
},
{
"path": "cli/scripts/make-executable.js",
"chars": 829,
"preview": "/**\n * Cross-platform script to make a file executable\n */\nimport { promises as fs } from \"fs\";\nimport { platform } from"
},
{
"path": "cli/src/cli.ts",
"chars": 10597,
"preview": "#!/usr/bin/env node\n\nimport { Command } from \"commander\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport"
},
{
"path": "cli/src/client/connection.ts",
"chars": 1498,
"preview": "import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport type { Transport } from \"@modelcontextprotoco"
},
{
"path": "cli/src/client/index.ts",
"chars": 199,
"preview": "// Re-export everything from the client modules\nexport * from \"./connection.js\";\nexport * from \"./prompts.js\";\nexport * "
},
{
"path": "cli/src/client/prompts.ts",
"chars": 1785,
"preview": "import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { McpResponse } from \"./types.js\";\n\n// JSON v"
},
{
"path": "cli/src/client/resources.ts",
"chars": 1606,
"preview": "import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { McpResponse } from \"./types.js\";\n\n// List a"
},
{
"path": "cli/src/client/tools.ts",
"chars": 3812,
"preview": "import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { Tool } from \"@modelcontextprotocol/sdk/type"
},
{
"path": "cli/src/client/types.ts",
"chars": 51,
"preview": "export type McpResponse = Record<string, unknown>;\n"
},
{
"path": "cli/src/error-handler.ts",
"chars": 412,
"preview": "function formatError(error: unknown): string {\n let message: string;\n\n if (error instanceof Error) {\n message = err"
},
{
"path": "cli/src/index.ts",
"chars": 11157,
"preview": "#!/usr/bin/env node\n\nimport * as fs from \"fs\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimpor"
},
{
"path": "cli/src/transport.ts",
"chars": 2501,
"preview": "import { SSEClientTransport } from \"@modelcontextprotocol/sdk/client/sse.js\";\nimport {\n getDefaultEnvironment,\n StdioC"
},
{
"path": "cli/src/utils/awaitable-log.ts",
"chars": 182,
"preview": "export function awaitableLog(logValue: string): Promise<void> {\n return new Promise<void>((resolve) => {\n process.st"
},
{
"path": "cli/tsconfig.json",
"chars": 450,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"NodeNext\",\n \"moduleResolution\": \"NodeNext\",\n \"outD"
},
{
"path": "cli/vitest.config.ts",
"chars": 268,
"preview": "import { defineConfig } from \"vitest/config\";\n\nexport default defineConfig({\n test: {\n globals: true,\n environmen"
},
{
"path": "client/.gitignore",
"chars": 253,
"preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndis"
},
{
"path": "client/LICENSE",
"chars": 12227,
"preview": "The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 (\"Apache-2."
},
{
"path": "client/README.md",
"chars": 1610,
"preview": "# React + TypeScript + Vite\n\nThis template provides a minimal setup to get React working in Vite with HMR and some ESLin"
},
{
"path": "client/bin/client.js",
"chars": 1653,
"preview": "#!/usr/bin/env node\n\nimport open from \"open\";\nimport { join, dirname } from \"path\";\nimport { fileURLToPath } from \"url\";"
},
{
"path": "client/bin/start.js",
"chars": 8220,
"preview": "#!/usr/bin/env node\n\nimport open from \"open\";\nimport { resolve, dirname } from \"path\";\nimport { spawnPromise, spawn } fr"
},
{
"path": "client/components.json",
"chars": 415,
"preview": "{\n \"$schema\": \"https://ui.shadcn.com/schema.json\",\n \"style\": \"new-york\",\n \"rsc\": false,\n \"tsx\": true,\n \"tailwind\": "
},
{
"path": "client/e2e/cli-arguments.spec.ts",
"chars": 2442,
"preview": "import { test, expect } from \"@playwright/test\";\n\n// These tests verify that CLI arguments correctly set URL parameters\n"
},
{
"path": "client/e2e/global-teardown.js",
"chars": 567,
"preview": "import { rimraf } from \"rimraf\";\n\nasync function globalTeardown() {\n if (!process.env.CI) {\n console.log(\"Cleaning u"
},
{
"path": "client/e2e/startup-state.spec.ts",
"chars": 449,
"preview": "import { test, expect } from \"@playwright/test\";\n\n// Adjust the URL if your dev server runs on a different port\nconst AP"
},
{
"path": "client/e2e/transport-type-dropdown.spec.ts",
"chars": 4108,
"preview": "import { test, expect } from \"@playwright/test\";\n\n// Adjust the URL if your dev server runs on a different port\nconst AP"
},
{
"path": "client/eslint.config.js",
"chars": 740,
"preview": "import js from \"@eslint/js\";\nimport globals from \"globals\";\nimport reactHooks from \"eslint-plugin-react-hooks\";\nimport r"
},
{
"path": "client/index.html",
"chars": 376,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <link rel=\"icon\" type=\"image/svg+xml\" href=\"/"
},
{
"path": "client/jest.config.cjs",
"chars": 1035,
"preview": "module.exports = {\n preset: \"ts-jest\",\n testEnvironment: \"jest-fixed-jsdom\",\n moduleNameMapper: {\n \"^@/(.*)$\": \"<r"
},
{
"path": "client/package.json",
"chars": 2737,
"preview": "{\n \"name\": \"@modelcontextprotocol/inspector-client\",\n \"version\": \"0.21.1\",\n \"description\": \"Client-side application f"
},
{
"path": "client/playwright.config.ts",
"chars": 1950,
"preview": "import { defineConfig, devices } from \"@playwright/test\";\n\n/**\n * @see https://playwright.dev/docs/test-configuration\n *"
},
{
"path": "client/postcss.config.js",
"chars": 81,
"preview": "export default {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};\n"
},
{
"path": "client/src/App.css",
"chars": 517,
"preview": ".logo {\n height: 6em;\n padding: 1.5em;\n will-change: filter;\n transition: filter 300ms;\n}\n.logo:hover {\n filter: dr"
},
{
"path": "client/src/App.tsx",
"chars": 55914,
"preview": "import {\n ClientRequest,\n CompatibilityCallToolResult,\n CompatibilityCallToolResultSchema,\n CreateMessageResult,\n E"
},
{
"path": "client/src/__mocks__/styleMock.js",
"chars": 21,
"preview": "module.exports = {};\n"
},
{
"path": "client/src/__tests__/App.config.test.tsx",
"chars": 6657,
"preview": "import { render, waitFor } from \"@testing-library/react\";\nimport App from \"../App\";\nimport { DEFAULT_INSPECTOR_CONFIG } "
},
{
"path": "client/src/__tests__/App.routing.test.tsx",
"chars": 4575,
"preview": "import { render, waitFor } from \"@testing-library/react\";\nimport App from \"../App\";\nimport { useConnection } from \"../li"
},
{
"path": "client/src/__tests__/App.samplingNavigation.test.tsx",
"chars": 6500,
"preview": "import {\n act,\n fireEvent,\n render,\n screen,\n waitFor,\n} from \"@testing-library/react\";\nimport App from \"../App\";\ni"
},
{
"path": "client/src/__tests__/App.toolsAppsPrefill.test.tsx",
"chars": 7573,
"preview": "import { fireEvent, render, screen, waitFor } from \"@testing-library/react\";\nimport \"@testing-library/jest-dom\";\nimport "
},
{
"path": "client/src/components/AppRenderer.tsx",
"chars": 4032,
"preview": "import { useMemo, useState } from \"react\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport {\n"
},
{
"path": "client/src/components/AppsTab.tsx",
"chars": 26863,
"preview": "import { useEffect, useState, useCallback, useRef } from \"react\";\nimport { TabsContent } from \"@/components/ui/tabs\";\nim"
},
{
"path": "client/src/components/AuthDebugger.tsx",
"chars": 10227,
"preview": "import { useCallback, useMemo, useEffect } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Debug"
},
{
"path": "client/src/components/ConsoleTab.tsx",
"chars": 403,
"preview": "import { TabsContent } from \"@/components/ui/tabs\";\n\nconst ConsoleTab = () => (\n <TabsContent value=\"console\" className"
},
{
"path": "client/src/components/CustomHeaders.tsx",
"chars": 7547,
"preview": "import { useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/u"
},
{
"path": "client/src/components/DynamicJsonForm.tsx",
"chars": 28682,
"preview": "import {\n useState,\n useEffect,\n useCallback,\n useRef,\n forwardRef,\n useImperativeHandle,\n} from \"react\";\nimport {"
},
{
"path": "client/src/components/ElicitationRequest.tsx",
"chars": 5237,
"preview": "import { useState, useEffect } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport DynamicJsonForm fro"
},
{
"path": "client/src/components/ElicitationTab.tsx",
"chars": 1603,
"preview": "import { Alert, AlertDescription } from \"@/components/ui/alert\";\nimport { TabsContent } from \"@/components/ui/tabs\";\nimp"
},
{
"path": "client/src/components/HistoryAndNotifications.tsx",
"chars": 6403,
"preview": "import { ServerNotification } from \"@modelcontextprotocol/sdk/types.js\";\nimport { useState } from \"react\";\nimport JsonVi"
},
{
"path": "client/src/components/IconDisplay.tsx",
"chars": 1145,
"preview": "// Define Icon type locally since it might not be exported yet\ninterface Icon {\n src: string;\n mimeType?: string;\n si"
},
{
"path": "client/src/components/JsonEditor.tsx",
"chars": 1753,
"preview": "import { useState, useEffect } from \"react\";\nimport Editor from \"react-simple-code-editor\";\nimport Prism from \"prismjs\";"
},
{
"path": "client/src/components/JsonView.tsx",
"chars": 11899,
"preview": "import { useState, memo, useMemo, useCallback, useEffect } from \"react\";\nimport type React from \"react\";\nimport type { J"
},
{
"path": "client/src/components/ListPane.tsx",
"chars": 4250,
"preview": "import { Search } from \"lucide-react\";\nimport { Button } from \"./ui/button\";\nimport { Input } from \"./ui/input\";\nimport "
},
{
"path": "client/src/components/MetadataTab.tsx",
"chars": 5521,
"preview": "import React, { useState } from \"react\";\nimport { TabsContent } from \"@/components/ui/tabs\";\nimport { Button } from \"@/c"
},
{
"path": "client/src/components/OAuthCallback.tsx",
"chars": 2489,
"preview": "import { useEffect, useRef } from \"react\";\nimport { InspectorOAuthClientProvider } from \"../lib/auth\";\nimport { SESSION_"
},
{
"path": "client/src/components/OAuthDebugCallback.tsx",
"chars": 3823,
"preview": "import { useEffect } from \"react\";\nimport { SESSION_KEYS } from \"../lib/constants\";\nimport {\n generateOAuthErrorDescrip"
},
{
"path": "client/src/components/OAuthFlowProgress.tsx",
"chars": 14428,
"preview": "import { AuthDebuggerState, OAuthStep } from \"@/lib/auth-types\";\nimport { CheckCircle2, Circle, ExternalLink } from \"luc"
},
{
"path": "client/src/components/PingTab.tsx",
"chars": 575,
"preview": "import { TabsContent } from \"@/components/ui/tabs\";\nimport { Button } from \"@/components/ui/button\";\n\nconst PingTab = ({"
},
{
"path": "client/src/components/PromptsTab.tsx",
"chars": 6830,
"preview": "import { Alert, AlertDescription, AlertTitle } from \"@/components/ui/alert\";\nimport { Button } from \"@/components/ui/but"
},
{
"path": "client/src/components/ResourceLinkView.tsx",
"chars": 3863,
"preview": "import { useState, useCallback, useMemo, memo } from \"react\";\nimport JsonView from \"./JsonView\";\n\ninterface ResourceLink"
},
{
"path": "client/src/components/ResourcesTab.tsx",
"chars": 10660,
"preview": "import { Alert, AlertDescription, AlertTitle } from \"@/components/ui/alert\";\nimport { Button } from \"@/components/ui/but"
},
{
"path": "client/src/components/RootsTab.tsx",
"chars": 2196,
"preview": "import { Alert, AlertDescription } from \"@/components/ui/alert\";\nimport { Button } from \"@/components/ui/button\";\nimport"
},
{
"path": "client/src/components/SamplingRequest.tsx",
"chars": 4369,
"preview": "import { Button } from \"@/components/ui/button\";\nimport JsonView from \"./JsonView\";\nimport { useMemo, useState } from \"r"
},
{
"path": "client/src/components/SamplingTab.tsx",
"chars": 1437,
"preview": "import { Alert, AlertDescription } from \"@/components/ui/alert\";\nimport { TabsContent } from \"@/components/ui/tabs\";\nimp"
},
{
"path": "client/src/components/Sidebar.tsx",
"chars": 34020,
"preview": "import { useState, useCallback } from \"react\";\nimport {\n Play,\n ChevronDown,\n ChevronRight,\n CircleHelp,\n Bug,\n Gi"
},
{
"path": "client/src/components/TasksTab.tsx",
"chars": 8356,
"preview": "import { Alert, AlertDescription, AlertTitle } from \"@/components/ui/alert\";\nimport { Button } from \"@/components/ui/but"
},
{
"path": "client/src/components/ToolResults.tsx",
"chars": 8346,
"preview": "import JsonView from \"./JsonView\";\nimport ResourceLinkView from \"./ResourceLinkView\";\nimport {\n CallToolResultSchema,\n "
},
{
"path": "client/src/components/ToolsTab.tsx",
"chars": 36838,
"preview": "import { Alert, AlertDescription, AlertTitle } from \"@/components/ui/alert\";\nimport { Button } from \"@/components/ui/but"
},
{
"path": "client/src/components/__tests__/AppRenderer.test.tsx",
"chars": 7356,
"preview": "import { render, screen, fireEvent, waitFor } from \"@testing-library/react\";\nimport \"@testing-library/jest-dom\";\nimport "
},
{
"path": "client/src/components/__tests__/AppsTab.test.tsx",
"chars": 21977,
"preview": "import {\n render,\n screen,\n fireEvent,\n waitFor,\n act,\n} from \"@testing-library/react\";\nimport \"@testing-library/je"
},
{
"path": "client/src/components/__tests__/AuthDebugger.test.tsx",
"chars": 26022,
"preview": "import {\n render,\n screen,\n fireEvent,\n waitFor,\n act,\n} from \"@testing-library/react\";\nimport \"@testing-library/je"
},
{
"path": "client/src/components/__tests__/DynamicJsonForm.array.test.tsx",
"chars": 11363,
"preview": "import { render, screen, fireEvent } from \"@testing-library/react\";\nimport \"@testing-library/jest-dom\";\nimport { describ"
},
{
"path": "client/src/components/__tests__/DynamicJsonForm.test.tsx",
"chars": 31755,
"preview": "import { render, screen, fireEvent, waitFor } from \"@testing-library/react\";\nimport \"@testing-library/jest-dom\";\nimport "
},
{
"path": "client/src/components/__tests__/ElicitationRequest.test.tsx",
"chars": 5729,
"preview": "import { render, screen, fireEvent, act } from \"@testing-library/react\";\nimport \"@testing-library/jest-dom\";\nimport { de"
},
{
"path": "client/src/components/__tests__/ElicitationTab.test.tsx",
"chars": 1490,
"preview": "import { render, screen } from \"@testing-library/react\";\nimport { Tabs } from \"@/components/ui/tabs\";\nimport Elicitation"
},
{
"path": "client/src/components/__tests__/HistoryAndNotifications.test.tsx",
"chars": 9294,
"preview": "import { render, screen, fireEvent, within } from \"@testing-library/react\";\nimport { useState } from \"react\";\nimport { d"
},
{
"path": "client/src/components/__tests__/ListPane.test.tsx",
"chars": 6896,
"preview": "import { render, screen, fireEvent, act } from \"@testing-library/react\";\nimport \"@testing-library/jest-dom\";\nimport { de"
},
{
"path": "client/src/components/__tests__/MetadataTab.test.tsx",
"chars": 20581,
"preview": "import { render, screen, fireEvent } from \"@testing-library/react\";\nimport \"@testing-library/jest-dom\";\nimport MetadataT"
},
{
"path": "client/src/components/__tests__/ResourcesTab.test.tsx",
"chars": 9707,
"preview": "import { render, screen, fireEvent } from \"@testing-library/react\";\nimport \"@testing-library/jest-dom\";\nimport { Tabs } "
},
{
"path": "client/src/components/__tests__/Sidebar.test.tsx",
"chars": 31801,
"preview": "import { render, screen, fireEvent, act } from \"@testing-library/react\";\nimport \"@testing-library/jest-dom\";\nimport { de"
},
{
"path": "client/src/components/__tests__/ToolsTab.test.tsx",
"chars": 36566,
"preview": "import { render, screen, fireEvent, act } from \"@testing-library/react\";\nimport \"@testing-library/jest-dom\";\nimport { de"
},
{
"path": "client/src/components/__tests__/samplingRequest.test.tsx",
"chars": 1911,
"preview": "import { render, screen, fireEvent } from \"@testing-library/react\";\nimport SamplingRequest from \"../SamplingRequest\";\nim"
},
{
"path": "client/src/components/__tests__/samplingTab.test.tsx",
"chars": 1633,
"preview": "import { render, screen } from \"@testing-library/react\";\nimport { Tabs } from \"@/components/ui/tabs\";\nimport SamplingTab"
},
{
"path": "client/src/components/ui/alert.tsx",
"chars": 1610,
"preview": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \""
},
{
"path": "client/src/components/ui/button.tsx",
"chars": 1893,
"preview": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"cla"
},
{
"path": "client/src/components/ui/checkbox.tsx",
"chars": 1079,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\";\nimport { C"
},
{
"path": "client/src/components/ui/combobox.tsx",
"chars": 2627,
"preview": "import React from \"react\";\nimport { Check, ChevronsUpDown } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimpor"
},
{
"path": "client/src/components/ui/command.tsx",
"chars": 4938,
"preview": "import * as React from \"react\";\nimport { type DialogProps } from \"@radix-ui/react-dialog\";\nimport { Command as CommandPr"
},
{
"path": "client/src/components/ui/dialog.tsx",
"chars": 3902,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { cn } "
},
{
"path": "client/src/components/ui/input.tsx",
"chars": 815,
"preview": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type InputProps = React.InputHTMLAttributes<H"
},
{
"path": "client/src/components/ui/label.tsx",
"chars": 719,
"preview": "import * as React from \"react\";\nimport * as LabelPrimitive from \"@radix-ui/react-label\";\nimport { cva, type VariantProps"
},
{
"path": "client/src/components/ui/popover.tsx",
"chars": 1302,
"preview": "import * as React from \"react\";\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\";\n\nimport { cn } from \"@/lib/"
},
{
"path": "client/src/components/ui/select.tsx",
"chars": 5703,
"preview": "import * as React from \"react\";\nimport {\n CaretSortIcon,\n CheckIcon,\n ChevronDownIcon,\n ChevronUpIcon,\n} from \"@radi"
},
{
"path": "client/src/components/ui/switch.tsx",
"chars": 1156,
"preview": "import * as React from \"react\";\nimport * as SwitchPrimitives from \"@radix-ui/react-switch\";\n\nimport { cn } from \"@/lib/u"
},
{
"path": "client/src/components/ui/tabs.tsx",
"chars": 1900,
"preview": "import * as React from \"react\";\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\";\n\nimport { cn } from \"@/lib/utils\""
},
{
"path": "client/src/components/ui/textarea.tsx",
"chars": 725,
"preview": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type TextareaProps = React.TextareaHTMLAttrib"
},
{
"path": "client/src/components/ui/toast.tsx",
"chars": 4871,
"preview": "import * as React from \"react\";\nimport * as ToastPrimitives from \"@radix-ui/react-toast\";\nimport { cva, type VariantProp"
},
{
"path": "client/src/components/ui/toaster.tsx",
"chars": 780,
"preview": "import { useToast } from \"@/lib/hooks/useToast\";\nimport {\n Toast,\n ToastClose,\n ToastDescription,\n ToastProvider,\n "
},
{
"path": "client/src/components/ui/tooltip.tsx",
"chars": 1220,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\";\n\nimport { cn"
},
{
"path": "client/src/index.css",
"chars": 2564,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n:root {\n font-family: Inter, system-ui, Avenir, Helvetica, "
},
{
"path": "client/src/lib/__tests__/auth.test.ts",
"chars": 4454,
"preview": "import { discoverScopes } from \"../auth\";\nimport { discoverAuthorizationServerMetadata } from \"@modelcontextprotocol/sdk"
},
{
"path": "client/src/lib/auth-types.ts",
"chars": 1550,
"preview": "import {\n OAuthMetadata,\n OAuthClientInformationFull,\n OAuthClientInformation,\n OAuthTokens,\n OAuthProtectedResourc"
},
{
"path": "client/src/lib/auth.ts",
"chars": 8664,
"preview": "import { OAuthClientProvider } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport {\n OAuthClientInformationSchema,"
},
{
"path": "client/src/lib/configurationTypes.ts",
"chars": 1828,
"preview": "export type ConfigItem = {\n label: string;\n description: string;\n value: string | number | boolean;\n is_session_item"
},
{
"path": "client/src/lib/constants.ts",
"chars": 2790,
"preview": "import { InspectorConfig } from \"./configurationTypes\";\nimport packageJson from \"../../package.json\";\n\n// Client identit"
},
{
"path": "client/src/lib/hooks/__tests__/useConnection.test.tsx",
"chars": 50191,
"preview": "import { renderHook, act } from \"@testing-library/react\";\nimport { useConnection } from \"../useConnection\";\nimport { z }"
},
{
"path": "client/src/lib/hooks/useCompletionState.ts",
"chars": 3449,
"preview": "import { useState, useCallback, useEffect, useRef, useMemo } from \"react\";\nimport {\n ResourceReference,\n PromptReferen"
},
{
"path": "client/src/lib/hooks/useConnection.ts",
"chars": 38589,
"preview": "import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport {\n SSEClientTransport,\n SseError,\n SSEClie"
},
{
"path": "client/src/lib/hooks/useCopy.ts",
"chars": 548,
"preview": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\ntype UseCopyProps = {\n timeout?: number;\n};\n\nfunction useC"
},
{
"path": "client/src/lib/hooks/useDraggablePane.ts",
"chars": 2828,
"preview": "import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport function useDraggablePane(initialHeight: numbe"
},
{
"path": "client/src/lib/hooks/useTheme.ts",
"chars": 1557,
"preview": "import { useCallback, useEffect, useMemo, useState } from \"react\";\n\ntype Theme = \"light\" | \"dark\" | \"system\";\n\nconst use"
},
{
"path": "client/src/lib/hooks/useToast.ts",
"chars": 4043,
"preview": "\"use client\";\n\n// Inspired by react-hot-toast library\nimport * as React from \"react\";\n\nimport type { ToastActionElement,"
},
{
"path": "client/src/lib/notificationTypes.ts",
"chars": 433,
"preview": "import {\n NotificationSchema as BaseNotificationSchema,\n ClientNotificationSchema,\n ServerNotificationSchema,\n} from "
},
{
"path": "client/src/lib/oauth-state-machine.ts",
"chars": 7366,
"preview": "import { OAuthStep, AuthDebuggerState } from \"./auth-types\";\nimport { DebugInspectorOAuthClientProvider, discoverScopes "
},
{
"path": "client/src/lib/types/customHeaders.ts",
"chars": 1484,
"preview": "export interface CustomHeader {\n name: string;\n value: string;\n enabled: boolean;\n}\n\nexport type CustomHeaders = Cust"
},
{
"path": "client/src/lib/utils.ts",
"chars": 169,
"preview": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: C"
},
{
"path": "client/src/main.tsx",
"chars": 416,
"preview": "import { StrictMode } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport { Toaster } from \"@/components"
},
{
"path": "client/src/utils/__tests__/configUtils.test.ts",
"chars": 2005,
"preview": "import { getMCPProxyAuthToken } from \"../configUtils\";\nimport { DEFAULT_INSPECTOR_CONFIG } from \"../../lib/constants\";\ni"
},
{
"path": "client/src/utils/__tests__/escapeUnicode.test.ts",
"chars": 927,
"preview": "import { escapeUnicode } from \"../escapeUnicode\";\n\ndescribe(\"escapeUnicode\", () => {\n it(\"should escape Unicode charact"
},
{
"path": "client/src/utils/__tests__/jsonUtils.test.ts",
"chars": 16484,
"preview": "import {\n getDataType,\n tryParseJson,\n updateValueAtPath,\n getValueAtPath,\n} from \"../jsonUtils\";\nimport type { Json"
},
{
"path": "client/src/utils/__tests__/oauthUtils.test.ts",
"chars": 3602,
"preview": "import {\n generateOAuthErrorDescription,\n parseOAuthCallbackParams,\n generateOAuthState,\n getAuthorizationServerMeta"
},
{
"path": "client/src/utils/__tests__/paramUtils.test.ts",
"chars": 8541,
"preview": "import { cleanParams } from \"../paramUtils\";\nimport type { JsonSchemaType } from \"../jsonUtils\";\n\ndescribe(\"cleanParams\""
},
{
"path": "client/src/utils/__tests__/schemaUtils.test.ts",
"chars": 17898,
"preview": "import {\n generateDefaultValue,\n formatFieldLabel,\n normalizeUnionType,\n cacheToolOutputSchemas,\n getToolOutputVali"
},
{
"path": "client/src/utils/__tests__/urlValidation.test.ts",
"chars": 4240,
"preview": "import { validateRedirectUrl } from \"../urlValidation\";\n\ndescribe(\"validateRedirectUrl\", () => {\n describe(\"valid URLs\""
},
{
"path": "client/src/utils/configUtils.ts",
"chars": 5805,
"preview": "import { InspectorConfig } from \"@/lib/configurationTypes\";\nimport {\n DEFAULT_MCP_PROXY_LISTEN_PORT,\n DEFAULT_INSPECTO"
},
{
"path": "client/src/utils/escapeUnicode.ts",
"chars": 479,
"preview": "// Utility function to escape Unicode characters\nexport function escapeUnicode(obj: unknown): string {\n return JSON.str"
},
{
"path": "client/src/utils/jsonUtils.ts",
"chars": 5833,
"preview": "export type JsonValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | JsonValue[]\n | { [key: string]: Js"
},
{
"path": "client/src/utils/metaUtils.ts",
"chars": 5251,
"preview": "/**\n * Metadata helpers aligned with the official MCP specification.\n *\n * @see https://modelcontextprotocol.io/specific"
},
{
"path": "client/src/utils/oauthUtils.ts",
"chars": 3790,
"preview": "// The parsed query parameters returned by the Authorization Server\n// representing either a valid authorization_code or"
},
{
"path": "client/src/utils/paramUtils.ts",
"chars": 1709,
"preview": "import type { JsonSchemaType } from \"./jsonUtils\";\n\n/**\n * Cleans parameters by removing undefined, null, and empty stri"
},
{
"path": "client/src/utils/schemaUtils.ts",
"chars": 10689,
"preview": "import type { JsonValue, JsonSchemaType, JsonObject } from \"./jsonUtils\";\nimport Ajv from \"ajv\";\nimport type { ValidateF"
},
{
"path": "client/src/utils/urlValidation.ts",
"chars": 731,
"preview": "/**\n * Validates that a URL is safe for redirection.\n * Only allows HTTP and HTTPS protocols to prevent XSS attacks.\n *\n"
},
{
"path": "client/src/vite-env.d.ts",
"chars": 38,
"preview": "/// <reference types=\"vite/client\" />\n"
},
{
"path": "client/tailwind.config.js",
"chars": 1719,
"preview": "/** @type {import('tailwindcss').Config} */\nimport animate from \"tailwindcss-animate\";\nexport default {\n darkMode: [\"cl"
},
{
"path": "client/tsconfig.app.json",
"chars": 790,
"preview": "{\n \"compilerOptions\": {\n \"baseUrl\": \".\",\n \"paths\": {\n \"@/*\": [\"./src/*\"]\n },\n\n \"target\": \"ES2020\",\n "
},
{
"path": "client/tsconfig.jest.json",
"chars": 197,
"preview": "{\n \"extends\": \"./tsconfig.app.json\",\n \"compilerOptions\": {\n \"jsx\": \"react-jsx\",\n \"esModuleInterop\": true,\n \"m"
},
{
"path": "client/tsconfig.json",
"chars": 213,
"preview": "{\n \"files\": [],\n \"references\": [\n { \"path\": \"./tsconfig.app.json\" },\n { \"path\": \"./tsconfig.node.json\" }\n ],\n "
},
{
"path": "client/tsconfig.node.json",
"chars": 479,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"lib\": [\"ES2023\"],\n \"module\": \"ESNext\",\n \"skipLibCheck\": true"
},
{
"path": "client/vite.config.ts",
"chars": 428,
"preview": "import react from \"@vitejs/plugin-react\";\nimport path from \"path\";\nimport { defineConfig } from \"vite\";\n\n// https://vite"
},
{
"path": "package.json",
"chars": 3181,
"preview": "{\n \"name\": \"@modelcontextprotocol/inspector\",\n \"version\": \"0.21.1\",\n \"description\": \"Model Context Protocol inspector"
},
{
"path": "sample-config.json",
"chars": 360,
"preview": "{\n \"mcpServers\": {\n \"everything\": {\n \"command\": \"npx\",\n \"args\": [\"@modelcontextprotocol/server-everything\""
},
{
"path": "scripts/README.md",
"chars": 1675,
"preview": "# Version Management Scripts\n\nThis directory contains scripts for managing version consistency across the monorepo.\n\n## "
},
{
"path": "scripts/check-version-consistency.js",
"chars": 5542,
"preview": "#!/usr/bin/env node\n\nimport fs from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\n\nconst __filenam"
},
{
"path": "scripts/update-version.js",
"chars": 3133,
"preview": "#!/usr/bin/env node\n\nimport fs from \"fs\";\nimport path from \"path\";\nimport { execSync } from \"child_process\";\nimport { fi"
},
{
"path": "server/LICENSE",
"chars": 12227,
"preview": "The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 (\"Apache-2."
},
{
"path": "server/package.json",
"chars": 1234,
"preview": "{\n \"name\": \"@modelcontextprotocol/inspector-server\",\n \"version\": \"0.21.1\",\n \"description\": \"Server-side application f"
},
{
"path": "server/src/index.ts",
"chars": 27022,
"preview": "#!/usr/bin/env node\n\nimport cors from \"cors\";\nimport { parseArgs } from \"node:util\";\nimport { parse as shellParseArgs } "
},
{
"path": "server/src/mcpProxy.ts",
"chars": 2417,
"preview": "import { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { isJSONRPCRequest } from \"@modelconte"
},
{
"path": "server/static/sandbox_proxy.html",
"chars": 7472,
"preview": "<!doctype html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"color-scheme\" content=\"light dark\" />\n <"
},
{
"path": "server/tsconfig.json",
"chars": 399,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"Node16\",\n \"moduleResolution\": \"Node16\",\n \"outDir\":"
}
]
About this extraction
This page contains the full source code of the modelcontextprotocol/inspector GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 185 files (1.1 MB), approximately 256.2k tokens, and a symbol index with 270 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.