We welcome contributions to the MCP Apps SDK! This document outlines the process for contributing to the project.
- Fork the repository
- Clone your fork:
git clone https://github.com/YOUR-USERNAME/ext-apps.git - Install dependencies:
npm install - Build the project:
npm run build - Run tests:
npm test
- Create a new branch for your changes
- Make your changes
- Run
npm run prettierto ensure code style compliance - Run
npm testto verify all tests pass - Submit a pull request
- Follow the existing code style
- Include tests for new functionality
- Update documentation as needed
- Keep changes focused and atomic
- Provide a clear description of changes
- Keep "Allow edits by maintainers" checked when opening your PR — this lets maintainers rebase your branch and lets the
/update-snapshotsworkflow push updated screenshots to it
The examples in examples/ are maintained by the core team. We do not accept pull requests that add new example servers from outside contributors. Each example carries an ongoing maintenance cost — keeping it building, tested, and up to date with SDK changes — so we are deliberate about what we add.
If you have an idea for an example that would provide significant educational value, please open an issue describing what the example would demonstrate and why it would be useful, and maintainers will evaluate the request.
PRs that add new examples without a prior approved issue may be closed.
Start the development environment with hot reloading:
npm run examples:devOr build and run examples:
npm run examples:startTo use these examples with MCP clients that support the stdio transport (such as Claude Desktop or VS Code), add this MCP server configuration to your client's settings:
MCP client configuration for all examples (using stdio)
{
"mcpServers": {
"basic-react": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-basic-react",
"--stdio"
]
},
"basic-vanillajs": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-basic-vanillajs",
"--stdio"
]
},
"basic-vue": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-basic-vue",
"--stdio"
]
},
"basic-svelte": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-basic-svelte",
"--stdio"
]
},
"basic-preact": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-basic-preact",
"--stdio"
]
},
"basic-solid": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-basic-solid",
"--stdio"
]
},
"budget-allocator": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-budget-allocator",
"--stdio"
]
},
"cohort-heatmap": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-cohort-heatmap",
"--stdio"
]
},
"customer-segmentation": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-customer-segmentation",
"--stdio"
]
},
"map": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-map",
"--stdio"
]
},
"pdf": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-pdf",
"--stdio"
]
},
"scenario-modeler": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-scenario-modeler",
"--stdio"
]
},
"shadertoy": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-shadertoy",
"--stdio"
]
},
"sheet-music": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-sheet-music",
"--stdio"
]
},
"system-monitor": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-system-monitor",
"--stdio"
]
},
"threejs": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-threejs",
"--stdio"
]
},
"transcript": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-transcript",
"--stdio"
]
},
"video-resource": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-video-resource",
"--stdio"
]
},
"wiki-explorer": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-wiki-explorer",
"--stdio"
]
},
"qr": {
"command": "uv",
"args": [
"run",
"/path/to/ext-apps/examples/qr-server/server.py",
"--stdio"
]
},
"say": {
"command": "uv",
"args": [
"run",
"--default-index",
"https://pypi.org/simple",
"https://raw.githubusercontent.com/modelcontextprotocol/ext-apps/refs/heads/main/examples/say-server/server.py",
"--stdio"
]
}
}
}Note
The qr server requires cloning the repository first. See qr-server README for details.
To test local modifications with MCP clients, first clone and install the repository:
git clone https://github.com/modelcontextprotocol/ext-apps.git
cd ext-apps
npm installThen configure your MCP client to build and run the local server. Replace ~/src/ext-apps with your actual clone path.
Most example servers have a start:stdio script that builds and launches in stdio mode:
MCP client configuration for local development (all examples)
{
"mcpServers": {
"basic-react": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/basic-server-react && npm --silent run start:stdio"
]
},
"basic-vanillajs": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/basic-server-vanillajs && npm --silent run start:stdio"
]
},
"basic-vue": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/basic-server-vue && npm --silent run start:stdio"
]
},
"basic-svelte": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/basic-server-svelte && npm --silent run start:stdio"
]
},
"basic-preact": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/basic-server-preact && npm --silent run start:stdio"
]
},
"basic-solid": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/basic-server-solid && npm --silent run start:stdio"
]
},
"budget-allocator": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/budget-allocator-server && npm --silent run start:stdio"
]
},
"cohort-heatmap": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/cohort-heatmap-server && npm --silent run start:stdio"
]
},
"customer-segmentation": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/customer-segmentation-server && npm --silent run start:stdio"
]
},
"map": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/map-server && npm --silent run start:stdio"
]
},
"pdf": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/pdf-server && npm --silent run start:stdio"
]
},
"scenario-modeler": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/scenario-modeler-server && npm --silent run start:stdio"
]
},
"shadertoy": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/shadertoy-server && npm --silent run start:stdio"
]
},
"sheet-music": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/sheet-music-server && npm --silent run start:stdio"
]
},
"system-monitor": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/system-monitor-server && npm --silent run start:stdio"
]
},
"threejs": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/threejs-server && npm --silent run start:stdio"
]
},
"transcript": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/transcript-server && npm --silent run start:stdio"
]
},
"video-resource": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/video-resource-server && npm --silent run start:stdio"
]
},
"wiki-explorer": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/wiki-explorer-server && npm --silent run start:stdio"
]
},
"qr": {
"command": "bash",
"args": [
"-c",
"uv run ~/src/ext-apps/examples/qr-server/server.py --stdio"
]
},
"say": {
"command": "bash",
"args": [
"-c",
"uv run --index https://pypi.org/simple ~/src/ext-apps/examples/say-server/server.py --stdio"
]
}
}
}This configuration rebuilds each server on launch, ensuring your local changes are picked up.
Run unit tests with Bun:
npm testE2E tests use Playwright to verify all example servers work correctly with screenshot comparisons.
# Run all E2E tests
npm run test:e2e
# Run a specific server's tests
npm run test:e2e -- --grep "Budget Allocator"
# Run tests in interactive UI mode
npm run test:e2e:uiWhen UI changes are intentional, update the golden screenshots:
# Update all screenshots
npm run test:e2e:update
# Update screenshots for a specific server
npm run test:e2e:update -- --grep "Three.js"Note: Golden screenshots are platform-agnostic. Tests use canvas masking and tolerance thresholds to handle minor cross-platform rendering differences.
If E2E tests fail in CI due to screenshot mismatches, you can update snapshots directly from your PR:
- Comment
/update-snapshotson the PR - The workflow will update snapshots and push to your branch
- A comment will confirm when complete
Alternatively, use the workflow dispatch to manually trigger updates for any branch.
This project follows our Code of Conduct. Please review it before contributing.
- Use the GitHub issue tracker
- Search existing issues before creating a new one
- Provide clear reproduction steps
Please review our Security Policy for reporting security vulnerabilities.
This repository uses npm trusted publishing with OIDC - no secrets required.
Before publishing releases, ensure the following are configured:
-
Trusted publisher on npm: Configure the package to trust this GitHub repository
- Go to https://www.npmjs.com/package/@modelcontextprotocol/ext-apps/access
- Under "Trusted Publishers", click "Add trusted publisher"
- Select "GitHub Actions"
- Repository:
modelcontextprotocol/ext-apps - Workflow filename:
npm-publish.yml - Environment:
Release(optional, for additional protection)
-
Releaseenvironment (optional): Create a protected environment for additional safeguards- Go to Settings > Environments > New environment
- Name it
Release - Add required reviewers or other protection rules as needed
Releases are published automatically via GitHub Actions when a GitHub Release is created.
-
Update the version in
package.json:# For a regular release npm version patch # or minor, or major # For a beta release npm version prerelease --preid=beta
-
Commit the version bump (if not done by
npm version):git add package.json git commit -m "Bump version to X.Y.Z" git push origin main -
Create a GitHub Release:
- Go to Releases
- Click "Draft a new release"
- Create a new tag matching the version (e.g.,
v0.1.0) - Set the target branch (usually
main) - Write release notes describing the changes
- Click "Publish release"
-
Monitor the workflow:
- The npm-publish workflow will trigger automatically
- It runs build and test jobs before publishing
- On success, the package is published to npm with provenance
The workflow automatically determines the npm dist-tag:
| Version Pattern | npm Tag | Install Command |
|---|---|---|
X.Y.Z (from main) |
latest |
npm install @modelcontextprotocol/ext-apps |
X.Y.Z-beta.N |
beta |
npm install @modelcontextprotocol/ext-apps@beta |
X.Y.Z (from release branch) |
release-X.Y |
npm install @modelcontextprotocol/ext-apps@release-X.Y |
To release a patch for an older version:
- Create a release branch from the tag:
git checkout -b release-0.1 v0.1.0 - Cherry-pick or apply fixes
- Bump the patch version
- Create a GitHub Release targeting the release branch
- The package will be published with tag
release-0.1
Every commit and PR automatically publishes a preview package via pkg-pr-new. Check the PR comments or workflow logs for the install command.
By contributing, you agree that your contributions will be licensed under the MIT License.