diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 00000000..6f59a975 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,48 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) + +## Quick Start + +### Creating a Changeset + +```bash +pnpm changeset +``` + +This will prompt you to: +1. Select which packages have changes +2. Choose the version bump type (major/minor/patch) +3. Write a summary of the changes + +### Applying Changesets (Versioning) + +```bash +pnpm version +``` + +This will: +- Apply all pending changesets +- Update package versions +- Generate/update CHANGELOG.md files +- Update dependencies + +### Publishing to npm + +```bash +pnpm release +``` + +This will: +- Build all packages +- Publish updated packages to npm +- Create git tags + +--- + +For detailed documentation, see [VERSIONING.md](../VERSIONING.md) in the project root. diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 00000000..2be13d43 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.changeset/every-roses-sell.md b/.changeset/every-roses-sell.md new file mode 100644 index 00000000..6ff6b7e2 --- /dev/null +++ b/.changeset/every-roses-sell.md @@ -0,0 +1,8 @@ +--- +'create-mcp-use-app': patch +'@mcp-use/inspector': patch +'mcp-use': patch +'@mcp-use/cli': patch +--- + +add MCP server feature to mcp-use + add mcp-use inspector + add mcp-use cli build and deployment tool + add create-mcp-use-app for scaffolding mcp-use apps diff --git a/.github/BETA_RELEASES.md b/.github/BETA_RELEASES.md new file mode 100644 index 00000000..59e4948a --- /dev/null +++ b/.github/BETA_RELEASES.md @@ -0,0 +1,252 @@ +# Beta Release Workflow + +This guide explains how to release beta versions using the `beta` branch and automated GitHub Actions. + +## 🎯 Overview + +- **`main` branch** → Stable releases (automated via `release.yml`) +- **`beta` branch** → Beta/prerelease versions (automated via `release-beta.yml`) +- Feature branches → Work in progress (no releases) + +## 🚀 Quick Start: Releasing a Beta + +### 1. Create and Push to Beta Branch + +```bash +# From your feature branch (e.g., feat/mcp-ui-apps) +git checkout -b beta + +# Push to trigger the beta release workflow +git push origin beta +``` + +The GitHub Action will automatically: +- Enter prerelease mode (creates `.changeset/pre.json`) +- Create a "Version Packages (beta)" PR +- Publish beta versions when the PR is merged + +### 2. Make Changes and Create Changesets + +```bash +# Make your changes +# ... edit files ... + +# Create a changeset +pnpm changeset + +# Commit and push +git add . +git commit -m "feat: add new feature" +git push origin beta +``` + +### 3. The Automated Flow + +When you push to `beta`: + +1. **If no changesets exist**: Nothing happens (waiting for changesets) +2. **If changesets exist**: + - A PR is created with version bumps (e.g., `0.2.1-beta.0`) + - Review and merge the PR + - On merge, packages are automatically published to npm with `@beta` tag + +## 📝 Manual Beta Release (Alternative) + +If you prefer manual control: + +```bash +# On beta branch +git checkout beta + +# Enter prerelease mode (first time only) +pnpm changeset pre enter beta + +# Create changesets +pnpm changeset + +# Version packages +pnpm version + +# Commit version changes +git add . +git commit -m "chore: version packages (beta)" +git push + +# Publish to npm +pnpm release +``` + +## 🔄 Continuous Beta Releases + +While on the `beta` branch, you can continue making changes: + +```bash +# Make more changes +# ... edit code ... + +# Create another changeset +pnpm changeset + +# Push to trigger versioning +git push origin beta +``` + +Each release will increment the beta number: `0.2.1-beta.0` → `0.2.1-beta.1` → `0.2.1-beta.2`, etc. + +## ✅ Promoting Beta to Stable + +When beta testing is complete and you're ready for a stable release: + +### Option 1: Merge Beta to Main (Recommended) + +```bash +# Switch to main and merge beta +git checkout main +git pull origin main +git merge beta + +# Exit prerelease mode +pnpm changeset pre exit + +# Commit the pre.json removal +git add .changeset/pre.json +git commit -m "chore: exit prerelease mode" + +# Push to main (triggers stable release workflow) +git push origin main +``` + +The stable release workflow will: +- Version packages as stable (e.g., `0.2.1`) +- Publish to npm with `@latest` tag + +### Option 2: Cherry-pick Changes + +If you only want specific changes from beta: + +```bash +git checkout main +git cherry-pick +# ... resolve any conflicts ... +git push origin main +``` + +## 📦 Installing Beta Versions + +Users can install beta versions: + +```bash +# Install latest beta +npm install mcp-use@beta +npm install @mcp-use/cli@beta + +# Install specific beta version +npm install mcp-use@0.2.1-beta.0 +``` + +## 🔍 Checking Beta Releases + +View published versions and tags: + +```bash +# See all versions +npm view mcp-use versions + +# See dist-tags +npm view mcp-use dist-tags +# { +# latest: '0.2.0', +# beta: '0.2.1-beta.0' +# } +``` + +## 🛠️ Workflow Features + +The `release-beta.yml` workflow includes: + +- ✅ Automatic prerelease mode entry +- ✅ Version PR creation +- ✅ Automatic publishing on merge +- ✅ Comment on commits with published versions +- ✅ Manual trigger via GitHub UI (workflow_dispatch) + +## 🔧 Manual Trigger + +You can manually trigger a beta release from GitHub: + +1. Go to **Actions** tab +2. Select **Release Beta** workflow +3. Click **Run workflow** +4. Select `beta` branch +5. Click **Run workflow** button + +## 📋 Best Practices + +1. **Keep beta branch up to date with main** + ```bash + git checkout beta + git merge main + git push + ``` + +2. **Create meaningful changesets** + - Describe what changed from a user's perspective + - Mark breaking changes clearly + +3. **Test beta versions thoroughly** + - Install beta versions in test projects + - Verify all packages work together + - Check for breaking changes + +4. **Clean up after stable release** + ```bash + # After merging to main and releasing stable + git checkout beta + git merge main # Sync beta with main + git push + ``` + +## 🐛 Troubleshooting + +### "Already in prerelease mode" Error + +The workflow handles this automatically, but if you see this message, it means `.changeset/pre.json` already exists. This is normal and expected. + +### Beta Branch Out of Sync + +```bash +# Reset beta branch to match a starting point +git checkout beta +git reset --hard main # or feat/your-feature +git push --force origin beta +``` + +### Want to Start Fresh + +```bash +# Exit prerelease mode +pnpm changeset pre exit + +# Remove all pending changesets +rm -rf .changeset/*.md + +# Commit changes +git add . +git commit -m "chore: reset changesets" +git push +``` + +### Workflow Not Triggering + +Check: +1. Branch name is exactly `beta` +2. You have changesets in `.changeset/*.md` +3. GitHub Actions is enabled in your repository +4. `NPM_TOKEN` secret is configured in GitHub Settings + +## 📚 Resources + +- [Changesets Prerelease Documentation](https://github.com/changesets/changesets/blob/main/docs/prereleases.md) +- [Main Release Workflow](./VERSIONING.md) +- [Changeset Workflow Guide](./CHANGESET_WORKFLOW.md) + diff --git a/.github/CHANGESET_WORKFLOW.md b/.github/CHANGESET_WORKFLOW.md new file mode 100644 index 00000000..7d0021f7 --- /dev/null +++ b/.github/CHANGESET_WORKFLOW.md @@ -0,0 +1,251 @@ +# Changeset Workflow Quick Reference + +## 📦 Publishable Packages + +- `mcp-use` - Main MCP integration library +- `@mcp-use/cli` - CLI tool for building MCP widgets +- `@mcp-use/inspector` - MCP Inspector UI +- `create-mcp-use-app` - Project scaffolding tool + +## 🚀 Common Commands + +### Development Workflow + +```bash +# 1. Make your changes to the codebase +# ... edit files ... + +# 2. Build to verify everything works +pnpm build + +# 3. Create a changeset +pnpm changeset +# Follow prompts: +# - Select affected packages +# - Choose version bump (patch/minor/major) +# - Write a summary + +# 4. Commit your changes + changeset +git add . +git commit -m "feat: your feature description" + +# 5. Push and create PR +git push +``` + +### Release Workflow (Maintainers) + +```bash +# After PRs with changesets are merged to main: + +# 1. Check what will be released +pnpm version:check + +# 2. Apply changesets (bump versions, update CHANGELOGs) +pnpm version + +# 3. Review the changes +git diff + +# 4. Commit version bumps +git add . +git commit -m "chore: version packages" +git push + +# 5. Publish to npm (requires npm auth) +pnpm release +``` + +## 📝 Changeset Types + +### Patch (0.0.X) - Bug Fixes + +```bash +pnpm changeset +``` + +```md +--- +"mcp-use": patch +--- + +Fixed memory leak in MCPSession cleanup +``` + +### Minor (0.X.0) - New Features + +```bash +pnpm changeset +``` + +```md +--- +"mcp-use": minor +"@mcp-use/cli": minor +--- + +Added support for custom headers in HTTP connections +``` + +### Major (X.0.0) - Breaking Changes + +```bash +pnpm changeset +``` + +```md +--- +"mcp-use": major +--- + +BREAKING: Renamed `createSession()` to `connect()` for consistency +``` + +### Multiple Packages + +```md +--- +"mcp-use": minor +"@mcp-use/cli": patch +"@mcp-use/inspector": patch +"create-mcp-use-app": patch +--- + +- Added React hooks for MCP connections (mcp-use) +- Fixed build output paths (cli) +- Updated dependencies (inspector, create-mcp-use-app) +``` + +### Empty Changeset (No Release) + +For changes that don't need a release (docs, tests, internal): + +```bash +pnpm changeset --empty +``` + +## 🔍 Useful Commands + +```bash +# Check status of pending changesets +pnpm version:check + +# Create a changeset interactively +pnpm changeset + +# Create an empty changeset +pnpm changeset --empty + +# Apply changesets (version bump) +pnpm version + +# Build and publish +pnpm release + +# Check what would be published +pnpm changeset status --verbose +``` + +## 📋 Checklist for Contributors + +Before submitting a PR: + +- [ ] Code changes are complete +- [ ] Tests pass (`pnpm test`) +- [ ] Linting passes (`pnpm lint`) +- [ ] Build succeeds (`pnpm build`) +- [ ] **Changeset created** (`pnpm changeset`) - if changes affect public API +- [ ] Changeset committed with PR + +## 📋 Checklist for Maintainers + +Before releasing: + +- [ ] All PRs with changesets are merged +- [ ] Check pending changesets (`pnpm version:check`) +- [ ] Apply versions (`pnpm version`) +- [ ] Review generated CHANGELOGs +- [ ] Commit version changes +- [ ] Push to main +- [ ] Publish packages (`pnpm release`) +- [ ] Verify packages on npm +- [ ] Create GitHub release (optional) + +## 🎯 Best Practices + +1. **Create changesets with your PRs** + - Always add a changeset for user-facing changes + - Skip changesets for internal-only changes (add `--empty` if needed) + +2. **Write clear summaries** + - Focus on what changed from a user's perspective + - Include migration steps for breaking changes + - Link to relevant issues/PRs if applicable + +3. **Use appropriate version bumps** + - **Patch**: Bug fixes, performance improvements, internal changes + - **Minor**: New features, new exports, enhancements + - **Major**: Breaking API changes, removed features + +4. **Review before publishing** + - Always review the generated CHANGELOGs + - Verify version numbers make sense + - Test packages locally before publishing + +## 🔄 Automated Releases (GitHub Actions) + +The repository includes automated release workflows: + +### `.github/workflows/release.yml` + +- Runs on push to `main` +- Creates a "Version Packages" PR automatically +- Publishes packages when the Version PR is merged +- Requires `NPM_TOKEN` secret in GitHub + +### `.github/workflows/ci.yml` + +- Runs on all PRs +- Checks for lint errors +- Runs tests +- Verifies builds +- Reminds to add changesets + +## 🐛 Troubleshooting + +### "No changesets present" + +You need to create a changeset first: +```bash +pnpm changeset +``` + +### "Package X is not found in the project" + +The package name in ignore list doesn't match. Check: +1. Package name in `package.json` +2. Name in `.changeset/config.json` ignore list + +### "Published packages are missing" + +Make sure packages have: +1. `"private": false` (or omit it) +2. `"publishConfig": { "access": "public" }` +3. Not in the `ignore` list + +### Dry Run + +To test versioning without actually changing files: + +```bash +# Preview what changesets would do +pnpm changeset status --verbose --since=main +``` + +## 📚 Resources + +- [Changesets Documentation](https://github.com/changesets/changesets) +- [Semantic Versioning](https://semver.org/) +- [Project VERSIONING.md](./VERSIONING.md) - Detailed guide +- [Changesets Tutorial](https://github.com/changesets/changesets/blob/main/docs/intro-to-using-changesets.md) + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 97daa34c..968a6578 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -12,11 +12,56 @@ Fixes # (issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Documentation update +- [ ] Internal change (refactoring, tests, tooling) + +## 📦 Changeset Required? + + + +**Does this PR need a changeset?** +- [ ] ✅ Yes - I have created a changeset (`pnpm changeset`) +- [ ] ❌ No - This is an internal-only change (docs, tests, tooling) + +**Quick reminder:** +```bash +pnpm changeset # Creates a changeset interactively +``` + +**📚 Full guide:** [Changeset Workflow Guide](.github/CHANGESET_WORKFLOW.md) +**Need help?** See [when to create changesets](.github/CHANGESET_WORKFLOW.md#-changeset-types) and [examples](.github/CHANGESET_WORKFLOW.md#-changeset-types) + +## 🚀 Release Target + +- [ ] `main` branch → Stable release (default) +- [ ] `beta` branch → Beta/prerelease version +- [ ] Other feature branch → No release + +**Releasing a beta?** See the [Beta Release Workflow Guide](.github/BETA_RELEASES.md) for instructions. ## Checklist +### Code Quality + - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] Existing tests pass locally (`pnpm test`) +- [ ] Linting passes (`pnpm lint`) +- [ ] Build succeeds (`pnpm build`) + +### Documentation + - [ ] The title of my pull request follows the [conventional commits](https://www.conventionalcommits.org/) standard - [ ] Changes have been documented in the README/documentation (if applicable) +- [ ] Breaking changes are clearly documented with migration steps + +### Release Preparation + +- [ ] **I have created a changeset** (`pnpm changeset`) - if this affects published packages +- [ ] The changeset has a clear, user-focused description +- [ ] Version bump type is appropriate (patch/minor/major) +- [ ] All affected packages are included in the changeset + +### Additional Notes + + diff --git a/.github/VERSIONING.md b/.github/VERSIONING.md new file mode 100644 index 00000000..6ee3a7c4 --- /dev/null +++ b/.github/VERSIONING.md @@ -0,0 +1,341 @@ +# Version Management with Changesets + +This monorepo uses [Changesets](https://github.com/changesets/changesets) for coordinated version management and automatic changelog generation across all packages. + +## Packages + +The following packages are published to npm: + +- **mcp-use** - Main library for MCP integration +- **@mcp-use/cli** - CLI tool for building MCP widgets +- **@mcp-use/inspector** - MCP Inspector UI +- **create-mcp-use-app** - Project scaffolding tool + +## Workflow + +### 1. Making Changes + +When you make changes that should be published: + +```bash +# After making your changes, create a changeset +pnpm changeset +``` + +This will: +1. Ask which packages were affected +2. Ask whether it's a **major**, **minor**, or **patch** change +3. Ask you to write a summary of the changes + +The changeset file will be created in `.changeset/` directory. + +### 2. Version Types + +Follow [Semantic Versioning](https://semver.org/): + +- **Major** (x.0.0) - Breaking changes +- **Minor** (0.x.0) - New features (backward compatible) +- **Patch** (0.0.x) - Bug fixes (backward compatible) + +### 3. Creating a Changeset + +```bash +# Interactive mode (recommended) +pnpm changeset + +# Example prompts: +# ? Which packages would you like to include? +# ✔ mcp-use +# ✔ @mcp-use/cli +# +# ? What kind of change is this for mcp-use? +# ○ major (breaking) +# ● minor (feature) +# ○ patch (fix) +# +# ? Please enter a summary for this change: +# Added support for custom headers in HTTP connections +``` + +### 4. Versioning Packages + +When you're ready to release: + +```bash +# Consume all changesets and update package versions +pnpm version + +# This will: +# - Update version numbers in package.json files +# - Update CHANGELOG.md files +# - Delete consumed changeset files +# - Update pnpm-lock.yaml +``` + +### 5. Publishing to npm + +```bash +# Build and publish all updated packages +pnpm release + +# This will: +# - Build all packages +# - Publish updated packages to npm +# - Create git tags +``` + +### 6. Complete Release Flow + +```bash +# 1. Make your changes +git checkout -b feat/my-new-feature + +# 2. Create a changeset +pnpm changeset + +# 3. Commit the changeset +git add .changeset +git commit -m "feat: add my new feature" + +# 4. Push and create PR +git push origin feat/my-new-feature + +# 5. After PR is merged to main, on main branch: +git checkout main +git pull + +# 6. Version packages +pnpm version + +# 7. Commit version changes +git add . +git commit -m "chore: version packages" +git push + +# 8. Publish to npm +pnpm release +``` + +## Changeset Examples + +### Adding a Feature + +```bash +pnpm changeset +``` + +``` +--- +"mcp-use": minor +--- + +Added support for WebSocket reconnection with exponential backoff +``` + +### Fixing a Bug + +```bash +pnpm changeset +``` + +``` +--- +"@mcp-use/cli": patch +--- + +Fixed TypeScript compilation errors in widget builder +``` + +### Breaking Change + +```bash +pnpm changeset +``` + +``` +--- +"mcp-use": major +--- + +BREAKING: Changed `createMCPServer` API to require explicit configuration object +``` + +### Multiple Packages + +```bash +pnpm changeset +``` + +``` +--- +"mcp-use": minor +"@mcp-use/cli": patch +"@mcp-use/inspector": patch +--- + +- Added new React hooks for MCP connections +- Fixed CLI build output paths +- Updated inspector UI dependencies +``` + +## Commands Reference + +| Command | Description | +|---------|-------------| +| `pnpm changeset` | Create a new changeset (interactive) | +| `pnpm version` | Apply changesets and update versions | +| `pnpm release` | Build and publish packages to npm | +| `pnpm version:check` | Check which packages have changesets | + +## Advanced Configuration + +### Linked Packages + +If you want packages to always be versioned together: + +```json +{ + "linked": [ + ["mcp-use", "@mcp-use/cli"] + ] +} +``` + +### Fixed Packages + +If you want packages to always have the same version: + +```json +{ + "fixed": [ + ["@mcp-use/*"] + ] +} +``` + +### Ignore Packages + +Packages already ignored (in `.changeset/config.json`): +- add the package in `"ignore": []` + +## CI/CD Integration + +### GitHub Actions Example + +Create `.github/workflows/release.yml`: + +```yaml +name: Release + +on: + push: + branches: + - main + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 10.6.1 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build packages + run: pnpm build + + - name: Create Release Pull Request or Publish + id: changesets + uses: changesets/action@v1 + with: + publish: pnpm release + version: pnpm version + commit: 'chore: version packages' + title: 'chore: version packages' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} +``` + +## Best Practices + +1. **Create changesets with your PRs** - Add a changeset file with every PR that changes package behavior +2. **Be descriptive** - Write clear, user-focused changeset summaries +3. **Version appropriately** - Follow semver strictly to avoid breaking users +4. **Batch releases** - Accumulate multiple changesets before versioning +5. **Review CHANGELOGs** - Check generated changelogs before publishing + +## Troubleshooting + +### "No changesets present" + +If you run `pnpm version` and see this message, you need to create changesets first: + +```bash +pnpm changeset +``` + +### "Unable to find a workspace package" + +Make sure all packages are in the workspace config (`pnpm-workspace.yaml`): + +```yaml +packages: + - 'packages/*' +``` + +### "Package is not published" + +Ensure package has `publishConfig.access` set to `"public"` in its `package.json`. + +## Examples of Generated CHANGELOGs + +After running `pnpm version`, your CHANGELOG.md files will look like: + +```markdown +# mcp-use + +## 0.3.0 + +### Minor Changes + +- abc1234: Added support for WebSocket reconnection with exponential backoff + +### Patch Changes + +- def5678: Fixed memory leak in session cleanup + +## 0.2.0 + +### Minor Changes + +- ghi9012: Added React hooks for MCP connections +``` + +## Package Dependencies + +When updating internal dependencies (workspace packages), Changesets will automatically: + +- Update version ranges in dependent packages +- Create appropriate changeset entries +- Maintain workspace protocol (`workspace:*`) in development + +Example: If you update `mcp-use` with a minor change, and `@mcp-use/cli` depends on it, `@mcp-use/cli` will get a **patch** version bump (configured by `updateInternalDependencies: "patch"`). + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..a40413ee --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,122 @@ +name: CI + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 10.6.1 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + + - name: Install Dependencies + run: pnpm install --no-frozen-lockfile + + - name: Run Linter + run: pnpm lint + + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 10.6.1 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + + - name: Install Dependencies + run: pnpm install --no-frozen-lockfile + + - name: Build Packages + run: pnpm build + + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 10.6.1 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + + - name: Install Dependencies + run: pnpm install --no-frozen-lockfile + + - name: Build Packages + run: pnpm build + + - name: Run Tests + run: pnpm test + + changeset-check: + name: Check Changesets + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 10.6.1 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + + - name: Install Dependencies + run: pnpm install --no-frozen-lockfile + + - name: Check for Changesets + run: | + if [ -n "$(ls -A .changeset/*.md 2>/dev/null | grep -v README.md)" ]; then + echo "✅ Changeset found" + pnpm changeset status --since=origin/main + else + echo "⚠️ No changeset found. If this PR includes changes that should be published, please add a changeset:" + echo " pnpm changeset" + echo "" + echo "If this PR doesn't require a changeset (docs, tests, internal changes), you can ignore this message." + fi + diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index b106529f..00000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Lint - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - lint: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '22' - - - name: Install dependencies - run: npm install - - - name: Run linter - run: npm run lint - - - name: TypeScript check - run: npm run build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a91d7945..f0eb863e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,108 +2,48 @@ name: Release on: push: - tags: - - 'v*' + branches: + - main -permissions: - contents: write - packages: write +concurrency: ${{ github.workflow }}-${{ github.ref }} jobs: - verify: + release: + name: Release runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + id-token: write steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Checkout Repo + uses: actions/checkout@v4 with: - node-version: 22.x - - name: Get package info - id: package - uses: codex-team/action-nodejs-package-info@v1 - - - name: Check version match - run: | - TAG_VERSION="${GITHUB_REF#refs/tags/v}" - PACKAGE_VERSION="${{ steps.package.outputs.version }}" - - if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then - echo "Error: Tag version ($TAG_VERSION) does not match package.json version ($PACKAGE_VERSION)" - exit 1 - fi - - echo "✅ Version in package.json matches GitHub tag" + fetch-depth: 0 - test: - runs-on: ubuntu-latest - needs: verify - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v3 with: - node-version: 22.x + version: 9.14.2 - - name: Install dependencies - run: npm install - - - name: Lint - run: npm run lint - - - name: Build - run: npm run build - - - name: Run tests - run: npm test || echo "No tests found, skipping test step" - - publish: - runs-on: ubuntu-latest - needs: test - steps: - - uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 with: - fetch-depth: 0 - - - uses: actions/setup-node@v4 - with: - node-version: 22.x + node-version: 23 + cache: 'pnpm' registry-url: 'https://registry.npmjs.org' - - name: Install dependencies - run: npm install + - name: Install Dependencies + run: pnpm install - - name: Build - run: npm run build - - - name: Generate changelog - id: changelog - uses: metcalfc/changelog-generator@v4.1.0 + - name: Create Release Pull Request or Publish to npm + id: changesets + uses: changesets/action@v1 with: - myToken: ${{ secrets.GITHUB_TOKEN }} - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 + publish: pnpm release + version: pnpm version + commit: 'chore(release): version packages' + title: 'chore(release): version packages' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref_name }} - body: | - ${{ steps.changelog.outputs.changelog }} - - **Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ github.ref_name }}...HEAD - draft: false - prerelease: false - - - name: Publish to NPM - run: npm publish - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - - name: Notify success - uses: rjstone/discord-webhook-notify@v1 - if: success() - with: - severity: info - details: | - Version ${{ github.ref_name }} has been successfully published to NPM! 🎉 - See the release here: https://github.com/${{ github.repository }}/releases/tag/${{ github.ref_name }} - webhookUrl: ${{ secrets.DISCORD_WEBHOOK }} - continue-on-error: true + NPM_TOKEN: ${{ secrets.NPM_TOKEN_ORG }} diff --git a/.github/workflows/release_beta.yml b/.github/workflows/release_beta.yml new file mode 100644 index 00000000..42fc80d8 --- /dev/null +++ b/.github/workflows/release_beta.yml @@ -0,0 +1,50 @@ +name: Release Beta + +on: + push: + branches: + - beta + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + release: + name: Release Beta + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + id-token: write + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 9.14.2 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 23 + cache: 'pnpm' + registry-url: 'https://registry.npmjs.org' + + - name: Install Dependencies + run: pnpm install --no-frozen-lockfile + + - name: Create Release Pull Request or Publish to npm + id: changesets + uses: changesets/action@v1 + with: + publish: pnpm release + version: pnpm version + commit: 'chore(release): version packages (beta)' + title: 'chore(release): version packages (beta)' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN_ORG }} + diff --git a/.gitignore b/.gitignore index 510fbe0e..3750d9b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # Dependencies +*.tsbuildinfo +dist/ node_modules .pnp .pnp.js @@ -21,7 +23,7 @@ coverage # Vercel .vercel -# Build Outputs +# Build Oututs .next/ out/ build diff --git a/.husky/pre-commit b/.husky/pre-commit index d0a77842..dca06839 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1 @@ -npx lint-staged \ No newline at end of file +cd packages/mcp-use && npx lint-staged \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..7bab172f --- /dev/null +++ b/.npmrc @@ -0,0 +1,12 @@ +auto-install-peers=true # Automatically installs peer dependencies +strict-peer-dependencies=false # Allows flexibility with peer dep versions +link-workspace-packages=true # Ensures workspace packages are linked +prefer-workspace-packages=true # Prefers workspace packages over registry +resolve-workspace-protocol=true # Properly handles workspace: protocol +# Hoisting patterns +# Hoisting in pnpm means moving certain dependencies to the root node_modules to avoid duplication and resolve conflicts. +# This is useful for packages like @types/*, eslint, and typescript that are often required at the top level. +public-hoist-pattern[]=*@types* +public-hoist-pattern[]=*eslint* +public-hoist-pattern[]=typescript +shamefully-hoist=false # Maintains proper dependency isolation \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..73321b80 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,15 @@ +node_modules +dist +build +coverage +.next +.turbo +.vercel +pnpm-lock.yaml +package-lock.json +yarn.lock +*.min.js +*.min.css +packages/*/dist +packages/*/build +packages/*/coverage diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..02782c35 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "semi": false, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5" +} + diff --git a/INSPECTOR_INTEGRATION.md b/INSPECTOR_INTEGRATION.md new file mode 100644 index 00000000..4038f635 --- /dev/null +++ b/INSPECTOR_INTEGRATION.md @@ -0,0 +1,124 @@ +# MCP Inspector Integration + +## Overview + +The MCP Inspector is now automatically mounted at `/inspector` for all MCP servers created with `createMCPServer`, similar to how FastAPI provides automatic Swagger documentation at `/docs`. + +## Key Changes + +### 1. Inspector Package (`@mcp-use/inspector`) + +- **New Middleware Function**: Created `mountInspector()` function that can mount the inspector UI on any Express app +- **Package Configuration**: + - Added `main` and `exports` fields to make it importable + - Added Express as a peer dependency + - Built server components are available in `dist/server/` + +### 2. MCP Server (`mcp-use`) + +- **Automatic Mounting**: Modified `createMCPServer()` to automatically mount the inspector at `/inspector` +- **Optional Dependency**: Added `@mcp-use/inspector` as an optional peer dependency +- **Graceful Degradation**: Server works fine if inspector package is not installed + +### 3. Templates (`create-mcp-use-app`) + +- **Automatic Setup**: All new projects created with `create-mcp-use-app` include `@mcp-use/inspector` as a dependency +- **No Manual Configuration**: Developers don't need to manually call `mountInspector()` anymore +- **Console Message**: Server startup logs include the inspector URL + +## Usage + +### For New Projects + +When developers create a new MCP server: + +```typescript +import { createMCPServer } from 'mcp-use' + +const server = createMCPServer('my-server', { + version: '1.0.0', + description: 'My awesome MCP server' +}) + +// Define tools, resources, prompts... + +server.listen(3000) +// Inspector automatically available at http://localhost:3000/inspector +``` + +### For Existing Projects + +1. Install the inspector package: + ```bash + pnpm add @mcp-use/inspector + ``` + +2. Build the inspector: + ```bash + cd packages/inspector && pnpm build + ``` + +3. The inspector will automatically be available at `/inspector` when you start your server + +### Manual Mounting (Advanced) + +If you need custom mounting: + +```typescript +import { mountInspector } from '@mcp-use/inspector' + +// Mount at custom path +mountInspector(server, '/my-custom-path') +``` + +## Implementation Details + +### How It Works + +1. When `createMCPServer()` is called, it attempts to dynamically import `@mcp-use/inspector` +2. If the package is installed, `mountInspector()` is called automatically with the Express app instance +3. The inspector middleware: + - Serves the built React UI from `dist/client/` + - Handles static assets (JS, CSS) + - Serves the HTML for all inspector routes (client-side routing) + +### Workspace Setup + +For local development in the monorepo: + +```json +{ + "dependencies": { + "@mcp-use/inspector": "workspace:*" + } +} +``` + +For published packages: + +```json +{ + "dependencies": { + "@mcp-use/inspector": "^0.1.0" + } +} +``` + +## Benefits + +1. **Zero Configuration**: Works out of the box, just like FastAPI's `/docs` +2. **Developer Experience**: Instant visual debugging and testing of MCP servers +3. **Optional**: Doesn't break existing servers if inspector is not installed +4. **Consistent**: All MCP servers have the same inspector experience + +## Files Modified + +- `packages/inspector/src/server/middleware.ts` - New middleware function +- `packages/inspector/src/server/index.ts` - Export mountInspector +- `packages/inspector/package.json` - Added exports and peer dependencies +- `packages/inspector/tsconfig.server.json` - Fixed TypeScript config +- `packages/mcp-use/src/server/mcp-server.ts` - Auto-mount inspector +- `packages/mcp-use/package.json` - Added inspector as optional peer dependency +- `packages/create-mcp-use-app/src/templates/ui/package.json` - Added inspector dependency +- `packages/create-mcp-use-app/src/templates/ui/src/server.ts` - Updated comments + diff --git a/README.md b/README.md index 83e83017..2fbcb68f 100644 --- a/README.md +++ b/README.md @@ -1,490 +1,520 @@
- - - mcp use logo + + + mcp use logo
-

Unified MCP Client Library

+

MCP-Use: The Complete TypeScript Framework for Model Context Protocol

- - - - - - - - - - - - + +

-🌐 **MCP Client** is the open-source way to connect **any LLM to any MCP server** in TypeScript/Node.js, letting you build custom agents with tool access without closed-source dependencies. - -💡 Let developers easily connect any LLM via LangChain.js to tools like web browsing, file operations, 3D modeling, and more. +

+ Build powerful AI agents, create MCP servers with UI widgets, and debug with built-in inspector - all in TypeScript +

--- -## ✨ Key Features +## 🎯 What is MCP-Use? -| Feature | Description | -| ------------------------------- | -------------------------------------------------------------------------- | -| 🔄 **Ease of use** | Create an MCP-capable agent in just a few lines of TypeScript. | -| 🤖 **LLM Flexibility** | Works with any LangChain.js-supported LLM that supports tool calling. | -| 🌐 **HTTP Support** | Direct SSE/HTTP connection to MCP servers. | -| ⚙️ **Dynamic Server Selection** | Agents select the right MCP server from a pool on the fly. | -| 🧩 **Multi-Server Support** | Use multiple MCP servers in one agent. | -| 🛡️ **Tool Restrictions** | Restrict unsafe tools like filesystem or network. | -| 🔧 **Custom Agents** | Build your own agents with LangChain.js adapter or implement new adapters. | -| 📊 **Observability** | Built-in support for Langfuse with dynamic metadata and tag handling. | +MCP-Use is a comprehensive TypeScript framework for building and using [Model Context Protocol (MCP)](https://modelcontextprotocol.io) applications. It provides everything you need to create AI agents that can use tools, build MCP servers with rich UI interfaces, and debug your applications with powerful developer tools. ---- +## 📦 Packages Overview -## 🚀 Quick Start +| Package | Description | Version | Downloads | +|---------|-------------|---------|-----------| +| **[mcp-use](#mcp-use-core-framework)** | Core framework for MCP clients and servers | [![npm](https://img.shields.io/npm/v/mcp-use.svg)](https://www.npmjs.com/package/mcp-use) | [![npm](https://img.shields.io/npm/dw/mcp-use.svg)](https://www.npmjs.com/package/mcp-use) | +| **[@mcp-use/cli](#mcp-use-cli)** | Build tool with hot reload and auto-inspector | [![npm](https://img.shields.io/npm/v/@mcp-use/cli.svg)](https://www.npmjs.com/package/@mcp-use/cli) | [![npm](https://img.shields.io/npm/dw/@mcp-use/cli.svg)](https://www.npmjs.com/package/@mcp-use/cli) | +| **[@mcp-use/inspector](#mcp-use-inspector)** | Web-based debugger for MCP servers | [![npm](https://img.shields.io/npm/v/@mcp-use/inspector.svg)](https://www.npmjs.com/package/@mcp-use/inspector) | [![npm](https://img.shields.io/npm/dw/@mcp-use/inspector.svg)](https://www.npmjs.com/package/@mcp-use/inspector) | +| **[create-mcp-use-app](#create-mcp-use-app)** | Project scaffolding tool | [![npm](https://img.shields.io/npm/v/create-mcp-use-app.svg)](https://www.npmjs.com/package/create-mcp-use-app) | [![npm](https://img.shields.io/npm/dw/create-mcp-use-app.svg)](https://www.npmjs.com/package/create-mcp-use-app) | -### Requirements +--- -- Node.js 22.0.0 or higher -- npm, yarn, or pnpm (examples use pnpm) +## 🚀 Quick Start -### Installation +Get started with MCP-Use in under a minute: ```bash -# Install from npm -npm install mcp-use -# LangChain.js and your LLM provider (e.g., OpenAI) -npm install langchain @langchain/openai dotenv +# Create a new MCP application +npx create-mcp-use-app my-mcp-app + +# Navigate to your project +cd my-mcp-app -# Optional: Install observability packages for monitoring -npm install langfuse langfuse-langchain # For Langfuse observability +# Start development with hot reload and auto-inspector +npm run dev ``` -Create a `.env`: +Your MCP server is now running at `http://localhost:3000` with the inspector automatically opened in your browser! -```ini -OPENAI_API_KEY=your_api_key -``` +--- + +## 📚 Package Documentation -### Basic Usage +### mcp-use: Core Framework -```ts +The heart of the MCP-Use ecosystem - a powerful framework for building both MCP clients and servers. + +#### As an MCP Client + +Connect any LLM to any MCP server and build intelligent agents: + +```typescript +import { MCPClient, MCPAgent } from 'mcp-use' import { ChatOpenAI } from '@langchain/openai' -import { MCPAgent, MCPClient } from 'mcp-use' -import 'dotenv/config' -async function main() { - // 1. Configure MCP servers - const config = { - mcpServers: { - playwright: { command: 'npx', args: ['@playwright/mcp@latest'] } +// Configure MCP servers +const client = MCPClient.fromDict({ + mcpServers: { + filesystem: { + command: 'npx', + args: ['@modelcontextprotocol/server-filesystem'] + }, + github: { + command: 'npx', + args: ['@modelcontextprotocol/server-github'], + env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN } } } - const client = MCPClient.fromDict(config) - - // 2. Create LLM - const llm = new ChatOpenAI({ modelName: 'gpt-4o' }) - - // 3. Instantiate agent - const agent = new MCPAgent({ llm, client, maxSteps: 20 }) +}) - // 4. Run query - const result = await agent.run('Find the best restaurant in Tokyo using Google Search') - console.log('Result:', result) -} +// Create an AI agent +const agent = new MCPAgent({ + llm: new ChatOpenAI({ model: 'gpt-4' }), + client, + maxSteps: 10 +}) -main().catch(console.error) +// Use the agent with natural language +const result = await agent.run( + 'Search for TypeScript files in the project and create a summary' +) ``` ---- +**Key Client Features:** +- 🤖 **LLM Agnostic**: Works with OpenAI, Anthropic, Google, or any LangChain-supported LLM +- 🔄 **Streaming Support**: Real-time streaming with `stream()` and `streamEvents()` methods +- 🌐 **Multi-Server**: Connect to multiple MCP servers simultaneously +- 🔒 **Tool Control**: Restrict access to specific tools for safety +- 📊 **Observability**: Built-in Langfuse integration for monitoring +- 🎯 **Server Manager**: Automatic server selection based on available tools -## 🔧 API Methods +#### As an MCP Server Framework -### MCPAgent Methods +Build your own MCP servers with automatic inspector and UI capabilities: -The `MCPAgent` class provides several methods for executing queries with different output formats: +```typescript +import { createMCPServer } from 'mcp-use/server' +import { z } from 'zod' -#### `run(query: string, maxSteps?: number): Promise` +// Create your MCP server +const server = createMCPServer('weather-server', { + version: '1.0.0', + description: 'Weather information MCP server' +}) -Executes a query and returns the final result as a string. +// Define tools with Zod schemas +server.tool('get_weather', { + description: 'Get current weather for a city', + parameters: z.object({ + city: z.string().describe('City name'), + units: z.enum(['celsius', 'fahrenheit']).optional() + }), + execute: async ({ city, units = 'celsius' }) => { + const weather = await fetchWeather(city, units) + return { + temperature: weather.temp, + condition: weather.condition, + humidity: weather.humidity + } + } +}) + +// Define resources +server.resource('weather_map', { + description: 'Interactive weather map', + uri: 'weather://map', + mimeType: 'text/html', + fetch: async () => { + return generateWeatherMapHTML() + } +}) -```ts -const result = await agent.run('What tools are available?') -console.log(result) +// Start the server +server.listen(3000) +// 🎉 Inspector automatically available at http://localhost:3000/inspector +// 🚀 MCP endpoint at http://localhost:3000/mcp ``` -#### `stream(query: string, maxSteps?: number): AsyncGenerator` +**Key Server Features:** +- 🔍 **Auto Inspector**: Debugging UI automatically mounts at `/inspector` +- 🎨 **UI Widgets**: Build React components served alongside MCP tools +- 🔐 **OAuth Support**: Built-in authentication flow handling +- 📡 **Multiple Transports**: HTTP/SSE and WebSocket support +- 🛠️ **TypeScript First**: Full type safety and inference +- ♻️ **Hot Reload**: Development mode with auto-restart -Yields intermediate steps during execution, providing visibility into the agent's reasoning process. +#### Advanced Features + +**Streaming with AI SDK Integration:** + +```typescript +import { streamEventsToAISDKWithTools } from 'mcp-use' +import { LangChainAdapter } from 'ai' -```ts -const stream = agent.stream('Search for restaurants in Tokyo') -for await (const step of stream) { - console.log(`Tool: ${step.action.tool}, Input: ${step.action.toolInput}`) - console.log(`Result: ${step.observation}`) +// In your Next.js API route +export async function POST(req: Request) { + const { prompt } = await req.json() + + const streamEvents = agent.streamEvents(prompt) + const enhancedStream = streamEventsToAISDKWithTools(streamEvents) + const readableStream = createReadableStreamFromGenerator(enhancedStream) + + return LangChainAdapter.toDataStreamResponse(readableStream) } ``` -#### `streamEvents(query: string, maxSteps?: number): AsyncGenerator` +**Custom UI Widgets:** + +```tsx +// resources/analytics-dashboard.tsx +import { useMcp } from 'mcp-use/react' -Yields fine-grained LangChain StreamEvent objects, enabling token-by-token streaming and detailed event tracking. +export default function AnalyticsDashboard() { + const { callTool, status } = useMcp() + const [data, setData] = useState(null) -```ts -const eventStream = agent.streamEvents('What is the weather today?') -for await (const event of eventStream) { - // Handle different event types - switch (event.event) { - case 'on_chat_model_stream': - // Token-by-token streaming from the LLM - if (event.data?.chunk?.content) { - process.stdout.write(event.data.chunk.content) - } - break - case 'on_tool_start': - console.log(`\nTool started: ${event.name}`) - break - case 'on_tool_end': - console.log(`Tool completed: ${event.name}`) - break - } + useEffect(() => { + callTool('get_analytics', { period: '7d' }) + .then(setData) + }, []) + + return ( +
+

Analytics Dashboard

+ {/* Your dashboard UI */} +
+ ) } ``` -### Key Differences +[**Full mcp-use Documentation →**](./packages/mcp-use) + +--- + +### @mcp-use/cli + +Powerful build and development tool for MCP applications with integrated inspector. + +```bash +# Development with hot reload +mcp-use dev -- **`run()`**: Best for simple queries where you only need the final result -- **`stream()`**: Best for debugging and understanding the agent's tool usage -- **`streamEvents()`**: Best for real-time UI updates with token-level streaming +# Production build +mcp-use build -## 🔄 AI SDK Integration +# Start production server +mcp-use start +``` -The library provides built-in utilities for integrating with [Vercel AI SDK](https://sdk.vercel.ai/), making it easy to build streaming UIs with React hooks like `useCompletion` and `useChat`. +**What it does:** +- 🚀 Auto-opens inspector in development mode +- ♻️ Hot reload for both server and UI widgets +- 📦 Bundles React widgets into standalone HTML pages +- 🏗️ Optimized production builds with asset hashing +- 🛠️ TypeScript compilation with watch mode -### Installation +**Example workflow:** ```bash -npm install ai @langchain/anthropic +# Start development +mcp-use dev +# Server running at http://localhost:3000 +# Inspector opened at http://localhost:3000/inspector +# Watching for changes... + +# Make changes to your code +# Server automatically restarts +# UI widgets hot reload +# Inspector updates in real-time ``` -### Basic Usage +[**Full CLI Documentation →**](./packages/cli) -```ts -import { ChatAnthropic } from '@langchain/anthropic' -import { LangChainAdapter } from 'ai' -import { createReadableStreamFromGenerator, MCPAgent, MCPClient, streamEventsToAISDK } from 'mcp-use' +--- -async function createApiHandler() { - const config = { - mcpServers: { - everything: { command: 'npx', args: ['-y', '@modelcontextprotocol/server-everything'] } - } - } +### @mcp-use/inspector - const client = new MCPClient(config) - const llm = new ChatAnthropic({ model: 'claude-sonnet-4-20250514' }) - const agent = new MCPAgent({ llm, client, maxSteps: 5 }) +Web-based debugging tool for MCP servers - like Swagger UI but for MCP. - return async (request: { prompt: string }) => { - const streamEvents = agent.streamEvents(request.prompt) - const aiSDKStream = streamEventsToAISDK(streamEvents) - const readableStream = createReadableStreamFromGenerator(aiSDKStream) +**Features:** +- 🔍 Test tools interactively with live execution +- 📊 Monitor connection status and server health +- 🔐 Handle OAuth flows automatically +- 💾 Persistent sessions with localStorage +- 🎨 Beautiful, responsive UI - return LangChainAdapter.toDataStreamResponse(readableStream) - } -} +**Three ways to use:** + +1. **Automatic** (with mcp-use server): +```typescript +server.listen(3000) +// Inspector at http://localhost:3000/inspector ``` -### Enhanced Usage with Tool Visibility +2. **Standalone CLI**: +```bash +npx mcp-inspect --url https://mcp.example.com/sse +``` -```ts -import { streamEventsToAISDKWithTools } from 'mcp-use' +3. **Custom mounting**: +```typescript +import { mountInspector } from '@mcp-use/inspector' +mountInspector(app, '/debug') +``` -async function createEnhancedApiHandler() { - const config = { - mcpServers: { - everything: { command: 'npx', args: ['-y', '@modelcontextprotocol/server-everything'] } - } - } +[**Full Inspector Documentation →**](./packages/inspector) - const client = new MCPClient(config) - const llm = new ChatAnthropic({ model: 'claude-sonnet-4-20250514' }) - const agent = new MCPAgent({ llm, client, maxSteps: 8 }) +--- - return async (request: { prompt: string }) => { - const streamEvents = agent.streamEvents(request.prompt) - // Enhanced stream includes tool usage notifications - const enhancedStream = streamEventsToAISDKWithTools(streamEvents) - const readableStream = createReadableStreamFromGenerator(enhancedStream) +### create-mcp-use-app - return LangChainAdapter.toDataStreamResponse(readableStream) - } -} +Zero-configuration project scaffolding for MCP applications. + +```bash +# Interactive mode +npx create-mcp-use-app + +# Direct mode +npx create-mcp-use-app my-app --template advanced ``` -### Next.js API Route Example +**What you get:** +- ✅ Complete TypeScript setup +- ✅ Pre-configured build scripts +- ✅ Example tools and widgets +- ✅ Development environment ready +- ✅ Docker and CI/CD configs (advanced template) -```ts -// pages/api/chat.ts or app/api/chat/route.ts -import { ChatAnthropic } from '@langchain/anthropic' -import { LangChainAdapter } from 'ai' -import { createReadableStreamFromGenerator, MCPAgent, MCPClient, streamEventsToAISDK } from 'mcp-use' +[**Full create-mcp-use-app Documentation →**](./packages/create-mcp-use-app) -export async function POST(req: Request) { - const { prompt } = await req.json() +--- + +## 💡 Real-World Examples - const config = { +### Example 1: AI-Powered File Manager + +```typescript +// Create an agent that can manage files +const agent = new MCPAgent({ + llm: new ChatOpenAI(), + client: MCPClient.fromDict({ mcpServers: { - everything: { command: 'npx', args: ['-y', '@modelcontextprotocol/server-everything'] } + filesystem: { + command: 'npx', + args: ['@modelcontextprotocol/server-filesystem', '/Users/me/documents'] + } } - } + }) +}) - const client = new MCPClient(config) - const llm = new ChatAnthropic({ model: 'claude-sonnet-4-20250514' }) - const agent = new MCPAgent({ llm, client, maxSteps: 10 }) +// Natural language file operations +await agent.run('Organize all PDF files into a "PDFs" folder sorted by date') +await agent.run('Find all TypeScript files and create a project summary') +await agent.run('Delete all temporary files older than 30 days') +``` - try { - const streamEvents = agent.streamEvents(prompt) - const aiSDKStream = streamEventsToAISDK(streamEvents) - const readableStream = createReadableStreamFromGenerator(aiSDKStream) +### Example 2: Multi-Tool Research Assistant - return LangChainAdapter.toDataStreamResponse(readableStream) - } - finally { - await client.closeAllSessions() +```typescript +// Connect multiple MCP servers +const client = MCPClient.fromDict({ + mcpServers: { + browser: { command: 'npx', args: ['@playwright/mcp'] }, + search: { command: 'npx', args: ['@mcp/server-search'] }, + memory: { command: 'npx', args: ['@mcp/server-memory'] } } -} -``` +}) -### Frontend Integration +const researcher = new MCPAgent({ + llm: new ChatAnthropic(), + client, + useServerManager: true // Auto-select appropriate server +}) -```tsx -// components/Chat.tsx -import { useCompletion } from 'ai/react' +// Complex research task +const report = await researcher.run(` + Research the latest developments in quantum computing. + Search for recent papers, visit official websites, + and create a comprehensive summary with sources. +`) +``` -export function Chat() { - const { completion, input, handleInputChange, handleSubmit } = useCompletion({ - api: '/api/chat', - }) +### Example 3: Database Admin Assistant - return ( -
-
{completion}
-
- -
-
- ) -} -``` +```typescript +const server = createMCPServer('db-admin', { + version: '1.0.0' +}) -### Available AI SDK Utilities +server.tool('execute_query', { + description: 'Execute SQL query safely', + parameters: z.object({ + query: z.string(), + database: z.string() + }), + execute: async ({ query, database }) => { + // Validate and execute query + const results = await db.query(query, { database }) + return { rows: results, count: results.length } + } +}) -- **`streamEventsToAISDK()`**: Converts streamEvents to basic text stream -- **`streamEventsToAISDKWithTools()`**: Enhanced stream with tool usage notifications -- **`createReadableStreamFromGenerator()`**: Converts async generator to ReadableStream +// Create an AI-powered DBA +const dba = new MCPAgent({ + llm: new ChatOpenAI({ model: 'gpt-4' }), + client: new MCPClient({ url: 'http://localhost:3000/mcp' }) +}) ---- +await dba.run('Show me all users who signed up this week') +await dba.run('Optimize the slow queries in the performance log') +``` -## 📊 Observability & Monitoring +--- -mcp-use-ts provides built-in observability support through the `ObservabilityManager`, with integration for Langfuse and other observability platforms. +## 🏗️ Project Structure -#### To enable observability simply configure Environment Variables +A typical MCP-Use project structure: -```ini -# .env -LANGFUSE_PUBLIC_KEY=pk-lf-your-public-key -LANGFUSE_SECRET_KEY=sk-lf-your-secret-key -LANGFUSE_HOST=https://cloud.langfuse.com # or your self-hosted instance +``` +my-mcp-app/ +├── src/ +│ └── index.ts # MCP server definition +├── resources/ # UI widgets (React components) +│ ├── dashboard.tsx # Main dashboard widget +│ └── settings.tsx # Settings panel widget +├── package.json # Dependencies and scripts +├── tsconfig.json # TypeScript configuration +├── .env # Environment variables +└── dist/ # Build output + ├── index.js # Compiled server + └── resources/ # Compiled widgets ``` -### Advanced Observability Features +--- -#### Dynamic Metadata and Tags +## 🛠️ Development Workflow -```ts -// Set custom metadata for the current execution -agent.setMetadata({ - userId: 'user123', - sessionId: 'session456', - environment: 'production' -}) +### Local Development -// Set tags for better organization -agent.setTags(['production', 'user-query', 'tool-discovery']) +```bash +# 1. Create your project +npx create-mcp-use-app my-project -// Run query with metadata and tags -const result = await agent.run('Search for restaurants in Tokyo') -``` +# 2. Start development +cd my-project +npm run dev -#### Monitoring Agent Performance - -```ts -// Stream events for detailed monitoring -const eventStream = agent.streamEvents('Complex multi-step query') - -for await (const event of eventStream) { - // Monitor different event types - switch (event.event) { - case 'on_llm_start': - console.log('LLM call started:', event.data) - break - case 'on_tool_start': - console.log('Tool execution started:', event.name, event.data) - break - case 'on_tool_end': - console.log('Tool execution completed:', event.name, event.data) - break - case 'on_chain_end': - console.log('Agent execution completed:', event.data) - break - } -} +# 3. Make changes - hot reload handles the rest +# 4. Test with the auto-opened inspector ``` -### Disabling Observability +### Production Deployment -To disable observability, either remove langfuse env variables or +```bash +# Build for production +npm run build -```ts -const agent = new MCPAgent({ - llm, - client, - observe: false -}) +# Deploy with Docker +docker build -t my-mcp-server . +docker run -p 3000:3000 my-mcp-server + +# Or deploy to any Node.js host +npm run start ``` --- -## 📂 Configuration File +## 🤝 Community & Support -You can store servers in a JSON file: +- **Discord**: [Join our community](https://discord.gg/XkNkSkMz3V) +- **GitHub Issues**: [Report bugs or request features](https://github.com/mcp-use/mcp-use-ts/issues) +- **Documentation**: [Full docs](https://github.com/mcp-use/mcp-use-ts) -```json -{ - "mcpServers": { - "playwright": { - "command": "npx", - "args": ["@playwright/mcp@latest"] - } - } -} -``` +--- -Load it: +## 📊 Publishing & Version Management -```ts -import { MCPClient } from 'mcp-use' +This monorepo uses modern tooling for package management: -const client = MCPClient.fromConfigFile('./mcp-config.json') -``` +### Using Changesets (Recommended) ---- +```bash +# Create a changeset for your changes +pnpm changeset -## 📚 Examples +# Version packages based on changesets +pnpm changeset version -We provide a comprehensive set of examples demonstrating various use cases. All examples are located in the `examples/` directory with a dedicated README. +# Publish all changed packages +pnpm changeset publish +``` -### Running Examples +### Manual Publishing ```bash -# Install dependencies -npm install - -# Run any example -npm run example:airbnb # Search accommodations with Airbnb -npm run example:browser # Browser automation with Playwright -npm run example:chat # Interactive chat with memory -npm run example:stream # Demonstrate streaming methods (stream & streamEvents) -npm run example:stream_events # Comprehensive streamEvents() examples -npm run example:ai_sdk # AI SDK integration with streaming -npm run example:filesystem # File system operations -npm run example:http # HTTP server connection -npm run example:everything # Test MCP functionalities -npm run example:multi # Multiple servers in one session +# Publish individual packages +pnpm --filter mcp-use publish --access public +pnpm --filter @mcp-use/cli publish --access public +pnpm --filter @mcp-use/inspector publish --access public +pnpm --filter create-mcp-use-app publish --access public + +# Or publish all at once +pnpm -r publish --access public ``` -### Example Highlights +--- -- **Browser Automation**: Control browsers to navigate websites and extract information -- **File Operations**: Read, write, and manipulate files through MCP -- **Multi-Server**: Combine multiple MCP servers (Airbnb + Browser) in a single task -- **Sandboxed Execution**: Run MCP servers in isolated E2B containers -- **OAuth Flows**: Authenticate with services like Linear using OAuth2 -- **Streaming Methods**: Demonstrate both step-by-step and token-level streaming -- **AI SDK Integration**: Build streaming UIs with Vercel AI SDK and React hooks +## 🧑‍💻 Contributing -See the [examples README](./examples/README.md) for detailed documentation and prerequisites. +We welcome contributions! Check out our [Contributing Guide](CONTRIBUTING.md) to get started. ---- +### Development Setup -## 🔄 Multi-Server Example +```bash +# Clone the repository +git clone https://github.com/mcp-use/mcp-use-ts.git +cd mcp-use-ts -```ts -const config = { - mcpServers: { - airbnb: { command: 'npx', args: ['@openbnb/mcp-server-airbnb'] }, - playwright: { command: 'npx', args: ['@playwright/mcp@latest'] } - } -} -const client = MCPClient.fromDict(config) -const agent = new MCPAgent({ llm, client, useServerManager: true }) -await agent.run('Search Airbnb in Barcelona, then Google restaurants nearby') -``` +# Install dependencies +pnpm install ---- +# Build all packages +pnpm build -## 🔒 Tool Access Control +# Run tests +pnpm test -```ts -const agent = new MCPAgent({ - llm, - client, - disallowedTools: ['file_system', 'network'] -}) +# Start development +pnpm dev ``` -## 👥 Contributors - - - - - - - -
- - Pietro -
- Pietro Zullo -
-
- - Zane/ -
- Zane -
-
- - Luigi -
- Luigi Pederzani -
-
- - +--- ## 📜 License -MIT © [Zane](https://github.com/zandko) +MIT © [MCP-Use](https://github.com/mcp-use) + +--- + +

+ Built with ❤️ by the MCP-Use team +

\ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js index 1e36a289..39e5e5a2 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,15 +1,323 @@ -import antfu from '@antfu/eslint-config' +import eslint from '@eslint/js'; +import tseslint from '@typescript-eslint/eslint-plugin'; +import tsparser from '@typescript-eslint/parser'; +import importPlugin from 'eslint-plugin-import'; + +export default [ + { + ignores: [ + '**/node_modules/**', + '**/dist/**', + '**/build/**', + '**/.next/**', + '**/coverage/**', + '**/*.min.js', + '**/.turbo/**', + '**/.vercel/**', + '**/.tsup/**', + 'packages/*/dist/**', + 'packages/*/build/**', + 'packages/*/node_modules/**', + ], + }, + { + files: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx', '**/*.mts'], + languageOptions: { + parser: tsparser, + parserOptions: { + ecmaVersion: 2022, + sourceType: 'module', + }, + globals: { + // Node.js globals + __dirname: 'readonly', + __filename: 'readonly', + exports: 'writable', + module: 'readonly', + require: 'readonly', + process: 'readonly', + Buffer: 'readonly', + console: 'readonly', + setTimeout: 'readonly', + clearTimeout: 'readonly', + setInterval: 'readonly', + clearInterval: 'readonly', + setImmediate: 'readonly', + clearImmediate: 'readonly', + NodeJS: 'readonly', + // ES6+ globals + Promise: 'readonly', + Map: 'readonly', + Set: 'readonly', + WeakMap: 'readonly', + WeakSet: 'readonly', + Symbol: 'readonly', + Proxy: 'readonly', + Reflect: 'readonly', + // Web APIs available in both Node.js and browsers + fetch: 'readonly', + URL: 'readonly', + URLSearchParams: 'readonly', + AbortController: 'readonly', + AbortSignal: 'readonly', + ReadableStream: 'readonly', + TextDecoder: 'readonly', + TextEncoder: 'readonly', + WebSocket: 'readonly', + Response: 'readonly', + Request: 'readonly', + Headers: 'readonly', + btoa: 'readonly', + atob: 'readonly', + // Browser globals + window: 'readonly', + document: 'readonly', + navigator: 'readonly', + location: 'readonly', + localStorage: 'readonly', + sessionStorage: 'readonly', + // HTML Element types + HTMLInputElement: 'readonly', + HTMLButtonElement: 'readonly', + HTMLDivElement: 'readonly', + HTMLSpanElement: 'readonly', + HTMLTextAreaElement: 'readonly', + HTMLElement: 'readonly', + // Browser APIs + MutationObserver: 'readonly', + ResizeObserver: 'readonly', + queueMicrotask: 'readonly', + FileReader: 'readonly', + // React + React: 'readonly', + }, + }, + plugins: { + '@typescript-eslint': tseslint, + import: importPlugin, + }, + settings: { + 'import/resolver': { + typescript: true, + }, + }, + rules: { + 'array-callback-return': 'error', + 'default-case': ['error', { commentPattern: '^no default$' }], + 'dot-location': ['error', 'property'], + eqeqeq: ['error', 'smart'], + 'new-parens': 'error', + 'no-array-constructor': 'error', + 'no-caller': 'error', + 'no-cond-assign': ['error', 'except-parens'], + 'no-const-assign': 'error', + 'no-control-regex': 'error', + 'no-delete-var': 'error', + 'no-dupe-args': 'error', + 'no-dupe-class-members': 'error', + 'no-dupe-keys': 'error', + 'no-duplicate-case': 'error', + 'no-empty-character-class': 'error', + 'no-empty-pattern': 'error', + 'no-eval': 'error', + 'no-ex-assign': 'error', + 'no-extend-native': 'error', + 'no-extra-bind': 'error', + 'no-extra-label': 'error', + 'no-fallthrough': 'error', + 'no-func-assign': 'error', + 'no-implied-eval': 'error', + 'no-invalid-regexp': 'error', + 'no-iterator': 'error', + 'no-label-var': 'error', + 'no-labels': ['error', { allowLoop: true, allowSwitch: false }], + 'no-lone-blocks': 'error', + 'no-loop-func': 'error', + 'no-multi-str': 'error', + 'no-new-func': 'error', + 'no-new-object': 'error', + 'no-new-symbol': 'error', + 'no-new-wrappers': 'error', + 'no-obj-calls': 'error', + 'no-octal': 'error', + 'no-octal-escape': 'error', + 'no-regex-spaces': 'error', + 'no-restricted-syntax': [ + 'error', + 'WithStatement', + { + message: "substr() is deprecated, use slice() or substring() instead", + selector: "MemberExpression > Identifier[name='substr']", + }, + ], + 'no-script-url': 'error', + 'no-self-assign': 'error', + 'no-self-compare': 'error', + 'no-sequences': 'error', + 'no-shadow': 'warn', + 'no-shadow-restricted-names': 'error', + 'no-sparse-arrays': 'error', + 'no-template-curly-in-string': 'error', + 'no-this-before-super': 'error', + 'no-throw-literal': 'error', + 'no-undef': 'error', + 'no-unexpected-multiline': 'error', + 'no-unreachable': 'error', + 'no-unused-expressions': [ + 'error', + { + allowShortCircuit: true, + allowTernary: true, + allowTaggedTemplates: true, + }, + ], + 'no-unused-labels': 'error', + 'no-unused-vars': 'off', + 'no-useless-computed-key': 'error', + 'no-useless-concat': 'error', + 'no-useless-constructor': 'off', + 'no-useless-escape': 'error', + 'no-useless-rename': [ + 'error', + { + ignoreDestructuring: false, + ignoreImport: false, + ignoreExport: false, + }, + ], + 'no-with': 'error', + 'no-whitespace-before-property': 'error', + 'require-yield': 'error', + 'rest-spread-spacing': ['error', 'never'], + strict: ['error', 'never'], + 'unicode-bom': ['error', 'never'], + 'use-isnan': 'error', + 'valid-typeof': 'error', + 'getter-return': 'error', + 'prefer-const': 'error', + '@typescript-eslint/prefer-as-const': 'error', + '@typescript-eslint/no-redeclare': [ + 'error', + { builtinGlobals: false, ignoreDeclarationMerge: true }, + ], + }, + }, + // TypeScript files + { + files: ['**/*.ts', '**/*.tsx', '**/*.mts'], + languageOptions: { + parser: tsparser, + parserOptions: { + ecmaVersion: 2022, + sourceType: 'module', + }, + }, + rules: { + ...eslint.configs.recommended.rules, + ...tseslint.configs.recommended.rules, + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + args: 'none', + ignoreRestSiblings: true, + argsIgnorePattern: '^_', + caughtErrors: 'none', + caughtErrorsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^_', + varsIgnorePattern: '^_', + }, + ], + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/consistent-type-imports': [ + 'error', + { + disallowTypeAnnotations: false, + }, + ], + '@typescript-eslint/no-import-type-side-effects': 'error', + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': 'off', + 'no-case-declarations': 'warn', + 'no-constant-condition': 'warn', + }, + }, + // CLI packages + { + files: [ + 'packages/cli/**/*.ts', + 'packages/create-mcp-use-app/**/*.ts', + ], + rules: { + 'no-console': 'off', + 'no-process-exit': 'off', + }, + }, + // mcp-use-ts package + { + files: ['packages/mcp-use-ts/**/*.ts'], + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { devDependencies: false }, + ], + }, + }, + // Test files + { + files: [ + '**/*.test.ts', + '**/*.test.tsx', + '**/*.spec.ts', + '**/*.spec.tsx', + 'tests/**/*.ts', + '**/vitest.config.ts', + '**/vitest.config.mts', + ], + languageOptions: { + globals: { + describe: 'readonly', + it: 'readonly', + test: 'readonly', + expect: 'readonly', + beforeEach: 'readonly', + afterEach: 'readonly', + beforeAll: 'readonly', + afterAll: 'readonly', + jest: 'readonly', + vi: 'readonly', + }, + }, + rules: { + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': 'off', + 'no-unreachable-loop': 'off', + 'no-console': 'off', + 'import/no-extraneous-dependencies': 'off', + '@typescript-eslint/no-explicit-any': 'off', + 'no-shadow': 'off', + '@typescript-eslint/no-shadow': 'off', + 'no-constant-condition': 'off', + 'require-yield': 'off', + }, + }, + // Examples + { + files: ['examples/**/*', 'packages/*/examples/**/*'], + rules: { + 'import/no-extraneous-dependencies': 'off', + 'no-console': 'off', + '@typescript-eslint/no-unused-vars': 'off', + 'no-constant-condition': 'off', + 'default-case': 'off', + 'no-new-func': 'off', + 'no-useless-escape': 'off', + 'no-case-declarations': 'off', + 'require-yield': 'off', + }, + }, +]; -export default antfu({ - formatters: true, - typescript: true, - rules: { - 'node/prefer-global/process': 'off', - 'no-console': 'off', - }, -}, { - files: ['examples/**/*.ts'], - rules: { - 'no-console': 'off', - }, -}) diff --git a/examples/react/index.tsx b/examples/react/index.tsx deleted file mode 100644 index 5c10a5e3..00000000 --- a/examples/react/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react' -import { createRoot } from 'react-dom/client' -import ReactExample from './react_example' - -const container = document.getElementById('root') -if (container) { - const root = createRoot(container) - root.render() -} -else { - console.error('Root element not found') -} diff --git a/examples/react/react_example.tsx b/examples/react/react_example.tsx deleted file mode 100644 index 97f98972..00000000 --- a/examples/react/react_example.tsx +++ /dev/null @@ -1,421 +0,0 @@ -import { createOAuthMCPConfig, LINEAR_OAUTH_CONFIG, MCPClient, OAuthHelper } from 'mcp-use/browser' -import React, { useEffect, useState } from 'react' - -interface Tool { - name: string - description?: string - inputSchema?: any -} - -interface MCPToolsProps { - config?: Record -} - -const MCPTools: React.FC = ({ config }) => { - const [client, setClient] = useState(null) - const [tools, setTools] = useState([]) - const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) - const [connectedServers, setConnectedServers] = useState([]) - const [oauthHelper] = useState(() => new OAuthHelper(LINEAR_OAUTH_CONFIG)) - const [oauthState, setOAuthState] = useState(oauthHelper.getState()) - const [serverUrl] = useState('https://mcp.linear.app') - - useEffect(() => { - if (config) { - const mcpClient = new MCPClient(config) - setClient(mcpClient) - } - }, [config]) - - // Handle OAuth callback on component mount - useEffect(() => { - const handleOAuthCallback = async () => { - const callback = oauthHelper.handleCallback() - if (callback) { - try { - const tokenResult = await oauthHelper.completeOAuthFlow(serverUrl, callback.code) - setOAuthState(oauthHelper.getState()) - - // Create new config with the access token - const oauthConfig = createOAuthMCPConfig(`${serverUrl}/sse`, tokenResult.access_token) - const mcpClient = new MCPClient(oauthConfig) - setClient(mcpClient) - } - catch (err) { - setError(`OAuth authentication failed: ${err instanceof Error ? err.message : 'Unknown error'}`) - setOAuthState(oauthHelper.getState()) - } - } - } - - handleOAuthCallback() - }, [oauthHelper, serverUrl]) - - // Check if authentication is required on mount - useEffect(() => { - const checkAuthRequirement = async () => { - try { - const requiresAuth = await oauthHelper.checkAuthRequired(`${serverUrl}/sse`) - if (requiresAuth) { - setOAuthState(oauthHelper.getState()) - } - } - catch (err) { - console.warn('Could not check auth requirement:', err) - } - } - - checkAuthRequirement() - }, [oauthHelper, serverUrl]) - - const loadTools = async () => { - if (!client) { - setError('MCP Client not initialized') - return - } - - setLoading(true) - setError(null) - - try { - // Get all server names from config - const serverNames = client.getServerNames() - - if (serverNames.length === 0) { - setError('No MCP servers configured') - setLoading(false) - return - } - - // Create sessions for all servers - const sessions = await client.createAllSessions() - setConnectedServers(Object.keys(sessions)) - - // Collect tools from all sessions - const allTools: Tool[] = [] - - for (const [serverName, session] of Object.entries(sessions)) { - try { - const sessionTools = session.connector.tools - const toolsWithServer = sessionTools.map(tool => ({ - ...tool, - server: serverName, - })) - allTools.push(...toolsWithServer) - } - catch (err) { - console.warn(`Failed to get tools from server ${serverName}:`, err) - } - } - - setTools(allTools) - } - catch (err) { - setError(`Failed to load tools: ${err instanceof Error ? err.message : 'Unknown error'}`) - } - finally { - setLoading(false) - } - } - - const disconnect = async () => { - if (client) { - await client.closeAllSessions() - setConnectedServers([]) - setTools([]) - } - } - - const startOAuthFlow = async () => { - setError(null) - try { - await oauthHelper.startOAuthFlow(serverUrl) - setOAuthState(oauthHelper.getState()) - } - catch (err) { - setError(`Failed to start OAuth flow: ${err instanceof Error ? err.message : 'Unknown error'}`) - setOAuthState(oauthHelper.getState()) - } - } - - const clearAuth = () => { - oauthHelper.resetAuth() - setOAuthState(oauthHelper.getState()) - setClient(null) - setConnectedServers([]) - setTools([]) - setError(null) - } - - return ( -
-

MCP Tools Explorer

- -
- {!oauthState.isAuthenticated - ? ( - - ) - : ( - <> - - - - - - - )} -
- - {error && ( -
- Error: - {' '} - {error} -
- )} - - {oauthState.isAuthenticated && oauthState.oauthTokens && ( -
-

✓ Authenticated with Linear

-

- Access token: - {' '} - {oauthState.oauthTokens.access_token.substring(0, 20)} - ... - {oauthState.oauthTokens.expires_at && ( - - {' '} - (expires: - {new Date(oauthState.oauthTokens.expires_at * 1000).toLocaleString()} - ) - - )} -

-
- )} - - {oauthState.authError && ( -
- OAuth Error: - {' '} - {oauthState.authError} -
- )} - - {connectedServers.length > 0 && ( -
-

Connected Servers:

-
    - {connectedServers.map(server => ( -
  • - ✓ - {server} -
  • - ))} -
-
- )} - -
-

- Available Tools ( - {tools.length} - ) -

- {tools.length === 0 && !loading && ( -

No tools loaded. Click "Load Tools" to get started.

- )} - - {tools.map((tool, index) => ( -
-

- {tool.name} - {tool.server && ( - - from - {' '} - {tool.server} - - )} -

- - {tool.description && ( -

- {tool.description} -

- )} - - {tool.inputSchema && ( -
- - Input Schema - -
-                  {JSON.stringify(tool.inputSchema, null, 2)}
-                
-
- )} -
- ))} -
-
- ) -} - -// Example usage component -const ReactExample: React.FC = () => { - return ( -
- - -
-

🔐 OAuth Authentication Required

-

- This example demonstrates OAuth authentication with Linear's MCP server. - Click "Authenticate with Linear" to start the OAuth flow. -

-

- Note: - {' '} - You'll need to register this application with Linear to get a proper client ID. - For this demo, we're using a placeholder client ID. -

-

- Browser limitations: - {' '} - Stdio connections (using - command - {' '} - and - args - ) - are not supported in the browser. Use - ws_url - {' '} - for WebSocket or - url - {' '} - for HTTP/SSE connections. -

-
-
- ) -} - -export default ReactExample diff --git a/package.json b/package.json index cb92d9d4..e9278a44 100644 --- a/package.json +++ b/package.json @@ -1,137 +1,59 @@ { - "name": "mcp-use", + "name": "mcp-use-monorepo", + "version": "1.0.0", + "private": true, "type": "module", - "version": "0.2.0", - "packageManager": "pnpm@10.6.1", - "description": "A utility library for integrating Model Context Protocol (MCP) with LangChain, Zod, and related tools. Provides helpers for schema conversion, event streaming, and SDK usage.", - "author": "mcp-use, Inc.", - "license": "MIT", - "homepage": "https://github.com/mcp-use/mcp-use-ts#readme", - "repository": { - "type": "git", - "url": "git+https://github.com/mcp-use/mcp-use-ts.git" - }, - "bugs": { - "url": "https://github.com/mcp-use/mcp-use-ts/issues" - }, - "keywords": [ - "MCP", - "Model Context Protocol", - "LangChain", - "Zod", - "schema", - "SDK", - "eventsource", - "AI", - "utility", - "typescript" + "workspaces": [ + "packages/*" ], - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js" - }, - "./browser": { - "types": "./dist/src/browser.d.ts", - "import": "./dist/src/browser.js" - } - }, - "main": "./dist/index.js", - "module": "./dist/index.js", - "types": "./dist/index.d.ts", - "files": [ - "dist" - ], - "engines": { - "node": ">=22.0.0" - }, - "publishConfig": { - "registry": "https://registry.npmjs.org" - }, "scripts": { - "build": "rm -rf dist && tsc", - "lint": "eslint", - "lint:fix": "eslint --fix", - "test": "vitest", - "test:run": "vitest run", - "test:simple": "vitest run tests/stream_events_simple.test.ts", - "test:integration": "vitest run tests/stream_events.test.ts", - "watch": "tsc --watch", - "start": "node dist/index.js", - "prepublishOnly": "npm run build", - "fmt": "eslint --fix", - "release": "npm version patch --tag-version-prefix=v && git push --follow-tags", - "release:minor": "npm version minor --tag-version-prefix=v && git push --follow-tags", - "release:major": "npm version major --tag-version-prefix=v && git push --follow-tags", - "prepare": "husky", - "example:airbnb": "npm run build && node dist/examples/airbnb_use.js", - "example:browser": "npm run build && node dist/examples/browser_use.js", - "example:chat": "npm run build && node dist/examples/chat_example.js", - "example:stream": "npm run build && node dist/examples/stream_example.js", - "example:stream_events": "npm run build && node dist/examples/stream_events_example.js", - "example:ai_sdk": "npm run build && node dist/examples/ai_sdk_example.js", - "example:filesystem": "npm run build && node dist/examples/filesystem_use.js", - "example:http": "npm run build && node dist/examples/http_example.js", - "example:everything": "npm run build && node dist/examples/mcp_everything.js", - "example:multi": "npm run build && node dist/examples/multi_server_example.js", - "example:sandbox": "npm run build && node dist/examples/sandbox_everything.js", - "example:oauth": "npm run build && node dist/examples/simple_oauth_example.js", - "example:blender": "npm run build && node dist/examples/blender_use.js", - "example:add_server": "npm run build && node dist/examples/add_server_tool.js", - "example:structured": "npm run build && node dist/examples/structured_output.js", - "example:observability": "npm run build && node dist/examples/observability.js" - }, - "peerDependencies": { - "langfuse": "^3.32.0", - "langfuse-langchain": "^3.38.4" - }, - "peerDependenciesMeta": { - "langfuse": { - "optional": true - }, - "langfuse-langchain": { - "optional": true - } - }, - "dependencies": { - "@dmitryrechkin/json-schema-to-zod": "^1.0.1", - "@langchain/anthropic": "^0.3.26", - "@langchain/core": "^0.3.72", - "@langchain/openai": "^0.6.9", - "@modelcontextprotocol/sdk": "1.12.1", - "@scarf/scarf": "^1.4.0", - "ai": "^4.3.19", - "dotenv": "^16.5.0", - "langchain": "^0.3.27", - "lodash-es": "^4.17.21", - "posthog-node": "^5.1.1", - "uuid": "^11.1.0", - "winston": "^3.17.0", - "winston-transport-browserconsole": "^1.0.5", - "ws": "^8.18.2", - "zod": "^3.25.48", - "zod-to-json-schema": "^3.24.6" + "build": "pnpm run build:mcp-use && pnpm run build:other", + "build:mcp-use": "pnpm --filter mcp-use build", + "build:other": "pnpm --filter '{packages/*}' --filter '!mcp-use' build", + "test": "pnpm run -r test", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "lint:strict": "eslint . --max-warnings 0", + "dev": "pnpm run -r --parallel dev", + "changeset": "changeset", + "version": "changeset version && pnpm install --no-frozen-lockfile", + "release": "pnpm build && changeset publish", + "version:check": "changeset status" }, "devDependencies": { - "@antfu/eslint-config": "^4.13.2", - "@types/lodash-es": "^4.17.12", - "@types/node": "^20.19.8", - "@types/ws": "^8.18.1", + "@changesets/cli": "^2.29.7", + "@types/node": "^20.0.0", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", "eslint": "^9.28.0", - "eslint-plugin-format": "^1.0.1", + "eslint-import-resolver-typescript": "^4.4.4", + "eslint-plugin-import": "^2.32.0", "husky": "^9.1.7", "lint-staged": "^15.2.11", - "typescript": "^5.8.3", - "vitest": "^2.1.9" + "tsup": "^8.5.0", + "typescript": "^5.0.0", + "typescript-eslint": "^8.20.0" }, "lint-staged": { - "*.{js,ts}": [ - "eslint --fix", - "eslint" - ], - "*.md": [ - "eslint --fix", - "eslint" + "*.{js,jsx,ts,tsx}": [ + "eslint --fix" ] + }, + "packageManager": "pnpm@10.6.1", + "pnpm": { + "patchedDependencies": { + "@mcp-ui/server@5.11.0": "patches/@mcp-ui__server@5.11.0.patch" + }, + "overrides": { + "mcp-use": "workspace:*", + "@mcp-use/inspector": "workspace:*", + "@mcp-use/cli": "workspace:*", + "create-mcp-use-app": "workspace:*", + "react": "^19.2.0", + "react-dom": "^19.2.0" + } + }, + "dependencies": { + "@eslint/js": "^9.37.0" } } diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md new file mode 100644 index 00000000..f87f8f9f --- /dev/null +++ b/packages/cli/CHANGELOG.md @@ -0,0 +1,39 @@ +# @mcp-use/cli + +## 2.1.2 + +### Patch Changes + +- 6fa0026: Fix cli dist +- Updated dependencies [6fa0026] + - @mcp-use/inspector@0.3.2 + +## 2.1.1 + +### Patch Changes + +- 04b9f14: Update versions +- Updated dependencies [04b9f14] + - @mcp-use/inspector@0.3.1 + +## 2.1.0 + +### Minor Changes + +- Update dependecies versions + +### Patch Changes + +- Updated dependencies + - @mcp-use/inspector@0.3.0 + - mcp-use@1.0.0 + +## 2.0.2 + +### Patch Changes + +- db54528: Migrated build system from tsc to tsup for faster builds (10-100x improvement) with dual CJS/ESM output support. This is an internal change that improves build performance without affecting the public API. +- Updated dependencies [db54528] +- Updated dependencies [db54528] + - mcp-use@0.3.0 + - @mcp-use/inspector@0.2.1 diff --git a/packages/cli/LICENSE b/packages/cli/LICENSE new file mode 100644 index 00000000..8059ea5d --- /dev/null +++ b/packages/cli/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2025 mcp-use, Inc. + +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. + diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 00000000..3f0ccf98 --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1,403 @@ +
+ + + + mcp use logo + +
+ +

MCP-Use CLI

+ +

+ + + + + + + + + + +

+ +🛠️ **MCP-Use CLI** is a powerful build and development tool for creating MCP (Model Context Protocol) applications with integrated UI widgets. It enables developers to build MCP servers with custom React components that can be served alongside their MCP tools, providing rich visual interfaces for AI interactions. + +## 📦 Related Packages + +| Package | Description | Version | +|---------|-------------|---------| +| [mcp-use](https://github.com/mcp-use/mcp-use-ts/tree/main/packages/mcp-use) | Core MCP framework | [![npm](https://img.shields.io/npm/v/mcp-use.svg)](https://www.npmjs.com/package/mcp-use) | +| [@mcp-use/inspector](https://github.com/mcp-use/mcp-use-ts/tree/main/packages/inspector) | Web-based MCP inspector | [![npm](https://img.shields.io/npm/v/@mcp-use/inspector.svg)](https://www.npmjs.com/package/@mcp-use/inspector) | +| [create-mcp-use-app](https://github.com/mcp-use/mcp-use-ts/tree/main/packages/create-mcp-use-app) | Create MCP apps | [![npm](https://img.shields.io/npm/v/create-mcp-use-app.svg)](https://www.npmjs.com/package/create-mcp-use-app) | + +--- + +## ✨ Key Features + +| Feature | Description | +|---------|-------------| +| **🚀 Auto Inspector** | Automatically opens the MCP Inspector in your browser when development server starts | +| **♻️ Hot Reload** | Development mode with automatic reloading for both server code and UI widgets | +| **🎨 Widget Builder** | Compiles React components into standalone HTML pages with all dependencies bundled | +| **📦 Production Ready** | Optimized production builds with hashed assets for caching | +| **🛠️ TypeScript First** | Full TypeScript support with watch mode compilation | +| **🖥️ Multi-Environment** | Separate commands for development, build, and production deployment | + +--- + +## 🚀 Installation + +```bash +# Install globally +npm install -g @mcp-use/cli + +# Or use with npx (no installation needed) +npx @mcp-use/cli dev + +# Install as a project dependency +npm install --save-dev @mcp-use/cli +``` + +--- + +## 📖 Usage + +### Development Mode + +Start a development server with hot reload, automatic TypeScript compilation, and auto-opening inspector: + +```bash +mcp-use dev [options] +``` + +**What happens in dev mode:** +1. TypeScript files are compiled in watch mode +2. UI widgets are built with hot reload +3. Server runs with auto-restart on changes (via tsx) +4. Inspector automatically opens in your browser at `http://localhost:3000/inspector` +5. MCP endpoint is available at `http://localhost:3000/mcp` + +**Options:** +- `-p, --path ` - Project directory (default: current directory) +- `--port ` - Server port (default: 3000) +- `--no-open` - Don't auto-open inspector in browser + +### Production Build + +Build your MCP application for production deployment: + +```bash +mcp-use build [options] +``` + +**What happens during build:** +1. TypeScript is compiled to JavaScript +2. All `.tsx` files in `resources/` are bundled as standalone HTML pages +3. Assets are hashed for optimal caching +4. Output is optimized and minified + +**Options:** +- `-p, --path ` - Project directory (default: current directory) + +### Production Server + +Start the production server from built files: + +```bash +mcp-use start [options] +``` + +**Options:** +- `-p, --path ` - Project directory (default: current directory) +- `--port ` - Server port (default: 3000) + +--- + +## 💡 Examples + +### Basic Development Workflow + +```bash +# Create a new MCP app (using create-mcp-use-app) +npx create-mcp-use-app my-mcp-server + +# Navigate to project +cd my-mcp-server + +# Start development with auto-reload and inspector +mcp-use dev + +# Browser automatically opens to http://localhost:3000/inspector +``` + +### Custom Port Configuration + +```bash +# Development on port 8080 +mcp-use dev --port 8080 + +# Production on port 8080 +mcp-use build +mcp-use start --port 8080 +``` + +### Working with Different Project Structures + +```bash +# Specify custom project path +mcp-use dev -p ./my-app +mcp-use build -p ./my-app +mcp-use start -p ./my-app + +# Development without auto-opening browser +mcp-use dev --no-open +``` + +### CI/CD Pipeline Example + +```bash +# In your CI/CD pipeline +npm install +npm run build # Uses mcp-use build +npm run start # Uses mcp-use start + +# Or with PM2 for production +npm run build +pm2 start "mcp-use start" --name my-mcp-server +``` + +--- + +## 📁 Project Structure + +The CLI expects the following project structure: + +``` +my-mcp-app/ +├── package.json +├── tsconfig.json +├── src/ +│ └── index.ts # Your MCP server entry point +├── resources/ # UI widgets (React components) +│ ├── todo-list.tsx # Becomes /widgets/todo-list/index.html +│ └── dashboard.tsx # Becomes /widgets/dashboard/index.html +└── dist/ # Build output + ├── index.js # Compiled server + └── resources/ + └── mcp-use/ + └── widgets/ + ├── todo-list/ + │ ├── index.html + │ └── assets/ + │ └── index-[hash].js + └── dashboard/ + ├── index.html + └── assets/ + └── index-[hash].js +``` + +--- + +## 🎨 Creating UI Widgets + +UI widgets are React components that get compiled into standalone HTML pages. They can interact with your MCP server tools using the `useMcp` hook: + +```tsx +// resources/task-manager.tsx +import React, { useState, useEffect } from 'react' +import { useMcp } from 'mcp-use/react' + +export default function TaskManager() { + const { callTool, status, error } = useMcp() + const [tasks, setTasks] = useState([]) + const [newTask, setNewTask] = useState('') + + useEffect(() => { + loadTasks() + }, []) + + const loadTasks = async () => { + const result = await callTool('list_tasks') + setTasks(result.tasks) + } + + const addTask = async () => { + if (!newTask.trim()) return + + await callTool('create_task', { + title: newTask, + status: 'pending' + }) + + setNewTask('') + await loadTasks() + } + + if (status === 'connecting') return
Connecting...
+ if (error) return
Error: {error.message}
+ + return ( +
+

Task Manager

+ +
+ setNewTask(e.target.value)} + placeholder="Add a new task..." + className="flex-1 p-2 border rounded" + /> + +
+ +
    + {tasks.map(task => ( +
  • + {task.title} +
  • + ))} +
+
+ ) +} +``` + +This widget will be available at `http://localhost:3000/widgets/task-manager` after building. + +--- + +## 🔧 Configuration + +### TypeScript Configuration + +Ensure your `tsconfig.json` includes: + +```json +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "jsx": "react-jsx", + "esModuleInterop": true, + "skipLibCheck": true, + "strict": true, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*", "resources/**/*"] +} +``` + +### Package.json Scripts + +Add these scripts for convenience: + +```json +{ + "scripts": { + "dev": "mcp-use dev", + "build": "mcp-use build", + "start": "mcp-use start", + "serve": "npm run build && npm run start" + } +} +``` + +--- + +## 🚀 Advanced Usage + +### Environment Variables + +```bash +# Custom port via environment variable +PORT=8080 mcp-use dev + +# Production build with custom output +BUILD_DIR=./build mcp-use build +``` + +### Docker Deployment + +```dockerfile +# Dockerfile +FROM node:20-alpine +WORKDIR /app +COPY package*.json ./ +RUN npm ci --production +COPY . . +RUN npm run build +EXPOSE 3000 +CMD ["npm", "start"] +``` + +### Integration with Existing Express Apps + +If you have an existing Express app, you can mount the built widgets: + +```ts +import express from 'express' +import path from 'path' + +const app = express() + +// Serve MCP widgets +app.use('/widgets', express.static( + path.join(__dirname, '../dist/resources/mcp-use/widgets') +)) + +// Your other routes... +``` + +--- + +## 🐛 Troubleshooting + +### Common Issues + +**Port already in use:** +```bash +# Use a different port +mcp-use dev --port 3001 +``` + +**TypeScript compilation errors:** +```bash +# Check your tsconfig.json +# Ensure all dependencies are installed +npm install +``` + +**Widgets not loading:** +- Ensure `.tsx` files are in the `resources/` directory +- Check that React dependencies are installed +- Verify the build output in `dist/resources/mcp-use/widgets/` + +**Inspector not opening:** +```bash +# Manually open http://localhost:3000/inspector +# Or disable auto-open +mcp-use dev --no-open +``` + +--- + +## 📚 Learn More + +- [MCP-Use Documentation](https://github.com/mcp-use/mcp-use-ts) +- [Model Context Protocol](https://modelcontextprotocol.io) +- [Creating MCP Servers](https://github.com/mcp-use/mcp-use-ts/tree/main/packages/mcp-use#-mcp-server-framework) +- [MCP Inspector Guide](https://github.com/mcp-use/mcp-use-ts/tree/main/packages/inspector) + +--- + +## 📜 License + +MIT © [MCP-Use](https://github.com/mcp-use) \ No newline at end of file diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 00000000..5b257716 --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,54 @@ +{ + "name": "@mcp-use/cli", + "version": "2.1.2", + "description": "Build tool for MCP UI widgets - bundles React components into standalone HTML pages for Model Context Protocol servers", + "author": "mcp-use, Inc.", + "license": "MIT", + "homepage": "https://github.com/mcp-use/mcp-use-ts#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/mcp-use/mcp-use-ts.git", + "directory": "packages/cli" + }, + "bugs": { + "url": "https://github.com/mcp-use/mcp-use-ts/issues" + }, + "keywords": [ + "mcp", + "model-context-protocol", + "cli", + "build-tool", + "widget", + "ui", + "react", + "esbuild", + "bundler", + "typescript" + ], + "bin": { + "mcp-use": "./dist/index.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsup src/index.ts --format cjs,esm && tsc --emitDeclarationOnly --declaration", + "dev": "tsc --watch" + }, + "dependencies": { + "mcp-use": "workspace:*", + "@mcp-use/inspector": "workspace:*", + "commander": "^11.0.0", + "esbuild": "^0.19.0", + "globby": "^14.0.0", + "open": "^10.0.0", + "tsx": "^4.0.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/cli/src/build.ts b/packages/cli/src/build.ts new file mode 100644 index 00000000..cfa68cef --- /dev/null +++ b/packages/cli/src/build.ts @@ -0,0 +1,194 @@ +import { promises as fs } from 'node:fs' +import path from 'node:path' +import { build, context } from 'esbuild' +import { globby } from 'globby' + +const ROUTE_PREFIX = '/mcp-use/widgets' +const SRC_DIR = 'resources' +const OUT_DIR = 'dist/resources' + +function toRoute(file: string) { + const rel = file.replace(new RegExp(`^${SRC_DIR}/`), '').replace(/\.tsx?$/, '') + return `${ROUTE_PREFIX}/${rel}` +} + +function outDirForRoute(route: string) { + return path.join(OUT_DIR, route.replace(/^\//, '')) +} + +function htmlTemplate({ title, scriptPath }: { title: string, scriptPath: string }) { + return ` + + + + + ${title} Widget + + + +
+ + +` +} + +async function buildWidget(entry: string, projectPath: string, minify = true) { + const relativePath = path.relative(projectPath, entry) + const route = toRoute(relativePath) + const pageOutDir = path.join(projectPath, outDirForRoute(route)) + const baseName = path.parse(entry).name + + // Build JS/CSS chunks for this page + await build({ + entryPoints: [entry], + bundle: true, + splitting: true, + format: 'esm', + platform: 'browser', + target: 'es2018', + sourcemap: !minify, + minify, + outdir: path.join(pageOutDir, 'assets'), + logLevel: 'silent', + loader: { + '.svg': 'file', + '.png': 'file', + '.jpg': 'file', + '.jpeg': 'file', + '.gif': 'file', + '.css': 'css', + }, + entryNames: `[name]-[hash]`, + chunkNames: `chunk-[hash]`, + assetNames: `asset-[hash]`, + define: { + 'process.env.NODE_ENV': minify ? '"production"' : '"development"', + }, + }) + + // Find the main entry file name + const files = await fs.readdir(path.join(pageOutDir, 'assets')) + const mainJs = files.find(f => f.startsWith(`${baseName}-`) && f.endsWith('.js')) + if (!mainJs) + throw new Error(`Failed to locate entry JS for ${entry}`) + + // Write an index.html that points to the entry + await fs.mkdir(pageOutDir, { recursive: true }) + await fs.writeFile( + path.join(pageOutDir, 'index.html'), + htmlTemplate({ + title: baseName, + scriptPath: `./assets/${mainJs}`, + }), + 'utf8', + ) + + return { baseName, route } +} + +export async function buildWidgets(projectPath: string, watch = false) { + const srcDir = path.join(projectPath, SRC_DIR) + const outDir = path.join(projectPath, OUT_DIR) + + // Clean dist + await fs.rm(outDir, { recursive: true, force: true }) + + // Find all TSX entries + const entries = await globby([`${srcDir}/**/*.tsx`]) + + if (!watch) { + console.log(`Building ${entries.length} widget files...`) + } + + if (watch) { + // Watch mode - create contexts for each entry but don't log individual watching messages + const contexts = [] + + for (const entry of entries) { + const relativePath = path.relative(projectPath, entry) + const route = toRoute(relativePath) + const pageOutDir = path.join(projectPath, outDirForRoute(route)) + const baseName = path.parse(entry).name + + const ctx = await context({ + entryPoints: [entry], + bundle: true, + splitting: true, + format: 'esm', + platform: 'browser', + target: 'es2018', + sourcemap: true, + minify: false, + outdir: path.join(pageOutDir, 'assets'), + logLevel: 'silent', + loader: { + '.svg': 'file', + '.png': 'file', + '.jpg': 'file', + '.jpeg': 'file', + '.gif': 'file', + '.css': 'css', + }, + entryNames: `[name]-[hash]`, + chunkNames: `chunk-[hash]`, + assetNames: `asset-[hash]`, + define: { + 'process.env.NODE_ENV': '"development"', + }, + plugins: [{ + name: 'html-writer', + setup(buildPlugin) { + buildPlugin.onEnd(async () => { + try { + const files = await fs.readdir(path.join(pageOutDir, 'assets')) + const mainJs = files.find(f => f.startsWith(`${baseName}-`) && f.endsWith('.js')) + if (mainJs) { + await fs.mkdir(pageOutDir, { recursive: true }) + await fs.writeFile( + path.join(pageOutDir, 'index.html'), + htmlTemplate({ + title: baseName, + scriptPath: `./assets/${mainJs}`, + }), + 'utf8', + ) + } + } catch (err) { + console.error(`Error writing HTML for ${baseName}:`, err) + } + }) + }, + }], + }) + + contexts.push(ctx) + } + + // Start watching all contexts + for (const ctx of contexts) { + await ctx.watch() + } + } + else { + // Build once + for (const entry of entries) { + const { baseName, route } = await buildWidget(entry, projectPath) + console.log(`\x1b[32m✓\x1b[0m Built ${baseName} -> ${route}`) + } + + console.log('Build complete!') + } +} + + diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts new file mode 100644 index 00000000..a4a61b55 --- /dev/null +++ b/packages/cli/src/index.ts @@ -0,0 +1,246 @@ +#!/usr/bin/env node +import { Command } from 'commander'; +import { buildWidgets } from './build'; +import { spawn } from 'node:child_process'; +import { readFileSync } from 'node:fs'; +import { access } from 'node:fs/promises'; +import path from 'node:path'; +import open from 'open'; +const program = new Command(); + + +const packageContent = readFileSync(path.join(__dirname, '../package.json'), 'utf-8') +const packageJson = JSON.parse(packageContent) +const packageVersion = packageJson.version || 'unknown' + + +program + .name('mcp-use') + .description('MCP CLI tool') + .version(packageVersion); + +// Helper to check if port is available +async function isPortAvailable(port: number): Promise { + try { + await fetch(`http://localhost:${port}`); + return false; // Port is in use + } catch { + return true; // Port is available + } +} + +// Helper to find an available port +async function findAvailablePort(startPort: number): Promise { + for (let port = startPort; port < startPort + 100; port++) { + if (await isPortAvailable(port)) { + return port; + } + } + throw new Error('No available ports found'); +} + +// Helper to check if server is ready +async function waitForServer(port: number, maxAttempts = 30): Promise { + for (let i = 0; i < maxAttempts; i++) { + try { + const response = await fetch(`http://localhost:${port}/inspector`); + if (response.ok) { + return true; + } + } catch { + // Server not ready yet + } + await new Promise(resolve => setTimeout(resolve, 1000)); + } + return false; +} + +// Helper to run a command +function runCommand(command: string, args: string[], cwd: string): Promise { + return new Promise((resolve, reject) => { + const proc = spawn(command, args, { + cwd, + stdio: 'inherit', + shell: false, + }); + + proc.on('error', reject); + proc.on('exit', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`Command failed with exit code ${code}`)); + } + }); + }); +} + +program + .command('build') + .description('Build TypeScript and MCP UI widgets') + .option('-p, --path ', 'Path to project directory', process.cwd()) + .action(async (options) => { + try { + const projectPath = path.resolve(options.path); + + console.log(`\x1b[36m\x1b[1mmcp-use\x1b[0m \x1b[90mVersion: ${packageJson.version}\x1b[0m\n`); + + // Run tsc first + console.log('Building TypeScript...'); + await runCommand('npx', ['tsc'], projectPath); + console.log('\x1b[32m✓\x1b[0m TypeScript build complete!'); + + // Then build widgets + await buildWidgets(projectPath, false); + } catch (error) { + console.error('Build failed:', error); + process.exit(1); + } + }); + +program + .command('dev') + .description('Run development server with auto-reload and inspector') + .option('-p, --path ', 'Path to project directory', process.cwd()) + .option('--port ', 'Server port', '3000') + .option('--no-open', 'Do not auto-open inspector') + .action(async (options) => { + try { + const projectPath = path.resolve(options.path); + let port = parseInt(options.port, 10); + + console.log(`\x1b[36m\x1b[1mmcp-use\x1b[0m \x1b[90mVersion: ${packageJson.version}\x1b[0m\n`); + + // Check if port is available, find alternative if needed + if (!(await isPortAvailable(port))) { + console.log(`\x1b[33m⚠️ Port ${port} is already in use\x1b[0m`); + const availablePort = await findAvailablePort(port); + console.log(`\x1b[32m✓\x1b[0m Using port ${availablePort} instead`); + port = availablePort; + } + + // Find the main source file + let serverFile = 'index.ts'; + try { + await access(path.join(projectPath, serverFile)); + } catch { + serverFile = 'src/server.ts'; + } + + // Start all processes concurrently + const processes: any[] = []; + + // 1. TypeScript watch + const tscProc = spawn('npx', ['tsc', '--watch'], { + cwd: projectPath, + stdio: 'pipe', + shell: false, + }); + tscProc.stdout?.on('data', (data) => { + const output = data.toString(); + if (output.includes('Watching for file changes')) { + console.log('\x1b[32m✓\x1b[0m TypeScript compiler watching...'); + } + }); + processes.push(tscProc); + + // 2. Widget builder watch - run in background + buildWidgets(projectPath, true).catch((error) => { + console.error('Widget builder failed:', error); + }); + + // Wait a bit for initial builds + await new Promise(resolve => setTimeout(resolve, 2000)); + + // 3. Server with tsx + const serverProc = spawn('npx', ['tsx', 'watch', serverFile], { + cwd: projectPath, + stdio: 'inherit', + shell: false, + env: { ...process.env, PORT: String(port) }, + }); + + processes.push(serverProc); + + // Auto-open inspector if enabled + if (options.open !== false) { + const startTime = Date.now(); + const ready = await waitForServer(port); + if (ready) { + const mcpUrl = `http://localhost:${port}/mcp`; + const inspectorUrl = `http://localhost:${port}/inspector?autoConnect=${encodeURIComponent(mcpUrl)}`; + const readyTime = Date.now() - startTime; + console.log(`\n\x1b[32m✓\x1b[0m Ready in ${readyTime}ms`); + console.log(`Local: http://localhost:${port}`); + console.log(`Network: http://localhost:${port}`); + console.log(`MCP: ${mcpUrl}`); + console.log(`Inspector: ${inspectorUrl}\n`); + await open(inspectorUrl); + } + } + + // Handle cleanup + const cleanup = () => { + console.log('\n\nShutting down...'); + processes.forEach(proc => proc.kill()); + process.exit(0); + }; + + process.on('SIGINT', cleanup); + process.on('SIGTERM', cleanup); + + // Keep the process running + await new Promise(() => {}); + } catch (error) { + console.error('Dev mode failed:', error); + process.exit(1); + } + }); + +program + .command('start') + .description('Start production server') + .option('-p, --path ', 'Path to project directory', process.cwd()) + .option('--port ', 'Server port', '3000') + .action(async (options) => { + try { + const projectPath = path.resolve(options.path); + const port = parseInt(options.port, 10); + + console.log(`\x1b[36m\x1b[1mmcp-use\x1b[0m \x1b[90mVersion: ${packageJson.version}\x1b[0m\n`); + + // Find the built server file + let serverFile = 'dist/index.js'; + try { + await access(path.join(projectPath, serverFile)); + } catch { + serverFile = 'dist/server.js'; + } + + console.log('Starting production server...'); + const serverProc = spawn('node', [serverFile], { + cwd: projectPath, + stdio: 'inherit', + env: { ...process.env, PORT: String(port) }, + }); + + // Handle cleanup + const cleanup = () => { + console.log('\n\nShutting down...'); + serverProc.kill(); + process.exit(0); + }; + + process.on('SIGINT', cleanup); + process.on('SIGTERM', cleanup); + + serverProc.on('exit', (code) => { + process.exit(code || 0); + }); + } catch (error) { + console.error('Start failed:', error); + process.exit(1); + } + }); + +program.parse(); diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 00000000..72805bd0 --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "composite": true, + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +} diff --git a/packages/create-mcp-use-app/.gitignore b/packages/create-mcp-use-app/.gitignore new file mode 100644 index 00000000..8e731286 --- /dev/null +++ b/packages/create-mcp-use-app/.gitignore @@ -0,0 +1,63 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# Dependencies +*.tsbuildinfo +dist/ +node_modules/ +.pnp +.pnp.js + +# Local env files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Testing +coverage + +# Turbo +.turbo + +# Vercel +.vercel + +# Build Oututs +.next/ +out/ +build +dist + + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Misc +.DS_Store +*.pem + + +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info +.coverage +agent_history.gif +static/browser_history/*.gif + +# Virtual environments +.venv + +# user conf +conf.yaml + +# Agents +.cursor +.cursorrules +.claude \ No newline at end of file diff --git a/packages/create-mcp-use-app/CHANGELOG.md b/packages/create-mcp-use-app/CHANGELOG.md new file mode 100644 index 00000000..ab0f728b --- /dev/null +++ b/packages/create-mcp-use-app/CHANGELOG.md @@ -0,0 +1,19 @@ +# create-mcp-use-app + +## 0.3.1 + +### Patch Changes + +- 04b9f14: Update versions + +## 0.3.0 + +### Minor Changes + +- Update dependecies versions + +## 0.2.1 + +### Patch Changes + +- db54528: Migrated build system from tsc to tsup for faster builds (10-100x improvement) with dual CJS/ESM output support. This is an internal change that improves build performance without affecting the public API. diff --git a/packages/create-mcp-use-app/README.md b/packages/create-mcp-use-app/README.md new file mode 100644 index 00000000..a2eb01bf --- /dev/null +++ b/packages/create-mcp-use-app/README.md @@ -0,0 +1,444 @@ +
+ + + + mcp use logo + +
+ +

Create MCP-Use App

+ +

+ + + + + + + + + + +

+ +🚀 **Create MCP-Use App** is the fastest way to scaffold a new MCP (Model Context Protocol) application. With just one command, you get a fully configured TypeScript project with hot reload, automatic inspector, and UI widget support - everything you need to build powerful MCP servers. + +## 📦 Related Packages + +| Package | Description | Version | +|---------|-------------|---------| +| [mcp-use](https://github.com/mcp-use/mcp-use-ts/tree/main/packages/mcp-use) | Core MCP framework | [![npm](https://img.shields.io/npm/v/mcp-use.svg)](https://www.npmjs.com/package/mcp-use) | +| [@mcp-use/cli](https://github.com/mcp-use/mcp-use-ts/tree/main/packages/cli) | Build tool for MCP apps | [![npm](https://img.shields.io/npm/v/@mcp-use/cli.svg)](https://www.npmjs.com/package/@mcp-use/cli) | +| [@mcp-use/inspector](https://github.com/mcp-use/mcp-use-ts/tree/main/packages/inspector) | Web-based MCP inspector | [![npm](https://img.shields.io/npm/v/@mcp-use/inspector.svg)](https://www.npmjs.com/package/@mcp-use/inspector) | + +--- + +## ⚡ Quick Start + +Create a new MCP application in seconds: + +```bash +npx create-mcp-use-app my-mcp-server +cd my-mcp-server +npm run dev +``` + +That's it! Your MCP server is running at `http://localhost:3000` with the inspector automatically opened in your browser. + +--- + +## 🎯 What It Creates + +Running `create-mcp-use-app` sets up a complete MCP development environment: + +### Project Structure + +``` +my-mcp-server/ +├── package.json # Pre-configured with all scripts +├── tsconfig.json # TypeScript configuration +├── .env.example # Environment variables template +├── .gitignore # Git ignore rules +├── README.md # Project documentation +├── src/ +│ └── index.ts # MCP server entry point with example tools +├── resources/ # UI widgets directory +│ └── example-widget.tsx # Example React widget +└── dist/ # Build output (generated) +``` + +### Pre-configured Features + +| Feature | Description | +|---------|-------------| +| **📝 TypeScript** | Full TypeScript setup with proper types | +| **🔥 Hot Reload** | Auto-restart on code changes during development | +| **🔍 Auto Inspector** | Inspector UI opens automatically in dev mode | +| **🎨 UI Widgets** | React components that compile to standalone pages | +| **🛠️ Example Tools** | Sample MCP tools, resources, and prompts | +| **📦 Build Scripts** | Ready-to-use development and production scripts | +| **🚀 Production Ready** | Optimized build configuration | + +--- + +## 📖 Usage Options + +### Interactive Mode + +Run without any arguments to enter interactive mode: + +```bash +npx create-mcp-use-app +``` + +You'll be prompted for: +- Project name +- Project template +- Package manager preference + +### Direct Mode + +Specify the project name directly: + +```bash +npx create-mcp-use-app my-project +``` + +### With Options + +```bash +# Use a specific template +npx create-mcp-use-app my-project --template advanced + +# Use a specific package manager +npx create-mcp-use-app my-project --use-npm +npx create-mcp-use-app my-project --use-yarn +npx create-mcp-use-app my-project --use-pnpm + +# Skip dependency installation +npx create-mcp-use-app my-project --skip-install +``` + +--- + +## 🎨 Available Templates + +### Basic Template (Default) + +The basic template includes: +- Simple MCP server setup +- Example tool, resource, and prompt +- Basic UI widget example +- Essential configuration files + +Perfect for getting started quickly or building simple MCP servers. + +### Advanced Template + +The advanced template includes everything from basic plus: +- Multiple tools with complex schemas +- OAuth authentication example +- Database integration patterns +- Advanced UI widgets with state management +- Observability setup with Langfuse +- Docker configuration +- CI/CD workflows + +Ideal for production applications or complex integrations. + +### Minimal Template + +The minimal template includes: +- Bare-bones MCP server +- No example tools or widgets +- Essential configuration only + +Best for experienced developers who want full control. + +--- + +## 🏗️ What Gets Installed + +The scaffolded project includes these dependencies: + +### Core Dependencies +- `mcp-use` - The MCP framework +- `@mcp-use/cli` - Build and development tool +- `@mcp-use/inspector` - Web-based debugger + +### Development Dependencies +- `typescript` - TypeScript compiler +- `tsx` - TypeScript executor for development +- `@types/node` - Node.js type definitions + +### Optional Dependencies (Advanced Template) +- Database drivers (PostgreSQL, SQLite) +- Authentication libraries +- Monitoring tools + +--- + +## 🚀 After Installation + +Once your project is created, you can: + +### Start Development + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +``` + +This will: +1. Start the MCP server on port 3000 +2. Open the inspector in your browser +3. Watch for file changes and auto-reload + +### Build for Production + +```bash +npm run build +``` + +Creates an optimized build in the `dist/` directory. + +### Start Production Server + +```bash +npm run start +``` + +Runs the production build. + +--- + +## 💡 First Steps + +After creating your app, here's what to do next: + +### 1. Explore the Example Server + +Open `src/index.ts` to see how to: +- Define MCP tools with Zod schemas +- Create resources for data access +- Set up prompts for AI interactions + +### 2. Try the Inspector + +The inspector automatically opens at `http://localhost:3000/inspector` where you can: +- Test your tools interactively +- View available resources +- Debug tool executions +- Monitor server status + +### 3. Create a UI Widget + +Edit `resources/example-widget.tsx` or create new widgets: + +```tsx +import React from 'react' +import { useMcp } from 'mcp-use/react' + +export default function MyWidget() { + const { callTool } = useMcp() + + const handleClick = async () => { + const result = await callTool('my_tool', { + param: 'value' + }) + console.log(result) + } + + return ( +
+ +
+ ) +} +``` + +### 4. Connect to AI + +Use the MCP server with any MCP-compatible client: + +```typescript +import { MCPClient, MCPAgent } from 'mcp-use' +import { ChatOpenAI } from '@langchain/openai' + +const client = new MCPClient({ + url: 'http://localhost:3000/mcp' +}) + +const agent = new MCPAgent({ + llm: new ChatOpenAI(), + client +}) + +const result = await agent.run('Use my MCP tools') +``` + +--- + +## 🔧 Configuration + +### Environment Variables + +The created project includes a `.env.example` file: + +```bash +# Server Configuration +PORT=3000 +NODE_ENV=development + +# OAuth (if using authentication) +OAUTH_CLIENT_ID=your_client_id +OAUTH_CLIENT_SECRET=your_client_secret + +# Database (if using database) +DATABASE_URL=postgresql://localhost/myapp + +# Observability (optional) +LANGFUSE_PUBLIC_KEY=your_public_key +LANGFUSE_SECRET_KEY=your_secret_key +``` + +Copy to `.env` and configure as needed: + +```bash +cp .env.example .env +``` + +### TypeScript Configuration + +The `tsconfig.json` is pre-configured for MCP development: + +```json +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + } +} +``` + +--- + +## 📚 Examples + +### Creating a Tool + +```typescript +server.tool('search_database', { + description: 'Search for records in the database', + parameters: z.object({ + query: z.string().describe('Search query'), + limit: z.number().optional().default(10) + }), + execute: async ({ query, limit }) => { + // Your tool logic here + const results = await db.search(query, limit) + return { results } + } +}) +``` + +### Creating a Resource + +```typescript +server.resource('user_profile', { + description: 'Current user profile data', + uri: 'user://profile', + mimeType: 'application/json', + fetch: async () => { + const profile = await getUserProfile() + return JSON.stringify(profile) + } +}) +``` + +### Creating a Prompt + +```typescript +server.prompt('code_review', { + description: 'Review code for best practices', + arguments: [ + { name: 'code', description: 'Code to review', required: true }, + { name: 'language', description: 'Programming language', required: false } + ], + render: async ({ code, language }) => { + return `Please review this ${language || ''} code for best practices:\n\n${code}` + } +}) +``` + +--- + +## 🐛 Troubleshooting + +### Common Issues + +**Command not found:** +```bash +# Make sure you have Node.js 18+ installed +node --version + +# Try with npx +npx create-mcp-use-app@latest +``` + +**Permission denied:** +```bash +# On macOS/Linux, you might need sudo +sudo npx create-mcp-use-app my-app +``` + +**Network issues:** +```bash +# Use a different registry +npm config set registry https://registry.npmjs.org/ +``` + +**Port already in use:** +```bash +# Change the port in your .env file +PORT=3001 +``` + +--- + +## 🤝 Contributing + +We welcome contributions! To contribute: + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Submit a pull request + +See our [contributing guide](https://github.com/mcp-use/mcp-use-ts/blob/main/CONTRIBUTING.md) for more details. + +--- + +## 📚 Learn More + +- [MCP-Use Documentation](https://github.com/mcp-use/mcp-use-ts) +- [Model Context Protocol Spec](https://modelcontextprotocol.io) +- [Creating MCP Tools](https://github.com/mcp-use/mcp-use-ts/tree/main/packages/mcp-use#-mcp-server-framework) +- [Building UI Widgets](https://github.com/mcp-use/mcp-use-ts/tree/main/packages/cli#-creating-ui-widgets) +- [Using the Inspector](https://github.com/mcp-use/mcp-use-ts/tree/main/packages/inspector) + +--- + +## 📜 License + +MIT © [MCP-Use](https://github.com/mcp-use) \ No newline at end of file diff --git a/packages/create-mcp-use-app/package.json b/packages/create-mcp-use-app/package.json new file mode 100644 index 00000000..9ebdbb50 --- /dev/null +++ b/packages/create-mcp-use-app/package.json @@ -0,0 +1,61 @@ +{ + "name": "create-mcp-use-app", + "version": "0.3.1", + "type": "module", + "description": "Create MCP-Use apps with one command", + "author": "mcp-use, Inc.", + "license": "MIT", + "homepage": "https://github.com/mcp-use/mcp-use-ts#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/mcp-use/mcp-use-ts.git", + "directory": "packages/create-mcp-use-app" + }, + "bugs": { + "url": "https://github.com/mcp-use/mcp-use-ts/issues" + }, + "keywords": [ + "mcp", + "model-context-protocol", + "create", + "scaffold", + "cli", + "init", + "starter", + "template", + "typescript", + "react" + ], + "bin": { + "create-mcp-use-app": "./dist/index.js" + }, + "scripts": { + "build": "npm run clean && tsup src/index.ts --format esm && tsc --emitDeclarationOnly --declaration && npm run copy-templates", + "clean": "rm -rf dist tsconfig.tsbuildinfo", + "copy-templates": "node scripts/copy-templates.js", + "dev": "tsc --build --watch", + "test": "vitest --run --passWithNoTests", + "lint": "eslint ." + }, + "files": [ + "dist", + "README.md" + ], + "engines": { + "node": ">=18.0.0" + }, + "dependencies": { + "commander": "^11.0.0", + "chalk": "^5.3.0", + "fs-extra": "^11.2.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "@types/fs-extra": "^11.0.4", + "typescript": "^5.0.0", + "vitest": "^1.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/create-mcp-use-app/scripts/copy-templates.js b/packages/create-mcp-use-app/scripts/copy-templates.js new file mode 100644 index 00000000..98a7d4fa --- /dev/null +++ b/packages/create-mcp-use-app/scripts/copy-templates.js @@ -0,0 +1,47 @@ +#!/usr/bin/env node + +import { promises as fs } from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const sourceDir = path.join(__dirname, '..', 'src', 'templates'); +const targetDir = path.join(__dirname, '..', 'dist', 'templates'); + +async function copyDir(src, dest, excludeDirs = ['node_modules']) { + try { + await fs.mkdir(dest, { recursive: true }); + + const entries = await fs.readdir(src, { withFileTypes: true }); + + for (const entry of entries) { + const srcPath = path.join(src, entry.name); + const destPath = path.join(dest, entry.name); + + // Skip excluded directories + if (entry.isDirectory() && excludeDirs.includes(entry.name)) { + console.log(`Skipping ${entry.name}/`); + continue; + } + + if (entry.isDirectory()) { + await copyDir(srcPath, destPath, excludeDirs); + } else { + await fs.copyFile(srcPath, destPath); + console.log(`Copied ${entry.name}`); + } + } + } catch (error) { + console.error('Error copying templates:', error); + process.exit(1); + } +} + +// Ensure target directory exists +await fs.mkdir(targetDir, { recursive: true }); + +console.log('Copying templates...'); +await copyDir(sourceDir, targetDir); +console.log('Templates copied successfully!'); diff --git a/packages/create-mcp-use-app/src/index.ts b/packages/create-mcp-use-app/src/index.ts new file mode 100644 index 00000000..3f9f58d8 --- /dev/null +++ b/packages/create-mcp-use-app/src/index.ts @@ -0,0 +1,292 @@ +#!/usr/bin/env node + +import { execSync } from 'node:child_process' +import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs' +import { dirname, join, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' +import { createInterface } from 'node:readline' +import { Command } from 'commander' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +const program = new Command() + +const packageJson = JSON.parse( + readFileSync(join(__dirname, '../package.json'), 'utf-8') +) + +// Read current package versions from workspace +function getCurrentPackageVersions() { + const versions: Record = {} + + try { + // Try multiple possible workspace root locations + const possibleRoots = [ + resolve(__dirname, '../../../..'), // From dist/templates + resolve(__dirname, '../../../../..'), // From dist + resolve(process.cwd(), '.'), // Current working directory + resolve(process.cwd(), '..'), // Parent of current directory + ] + + let workspaceRoot = null + for (const root of possibleRoots) { + if (existsSync(join(root, 'packages/mcp-use/package.json'))) { + workspaceRoot = root + break + } + } + + if (!workspaceRoot) { + throw new Error('Workspace root not found') + } + + // Read mcp-use version + const mcpUsePackage = JSON.parse( + readFileSync(join(workspaceRoot, 'packages/mcp-use/package.json'), 'utf-8') + ) + versions['mcp-use'] = mcpUsePackage.version + + // Read cli version + const cliPackage = JSON.parse( + readFileSync(join(workspaceRoot, 'packages/cli/package.json'), 'utf-8') + ) + versions['@mcp-use/cli'] = cliPackage.version + + // Read inspector version + const inspectorPackage = JSON.parse( + readFileSync(join(workspaceRoot, 'packages/inspector/package.json'), 'utf-8') + ) + versions['@mcp-use/inspector'] = inspectorPackage.version + } catch (error) { + console.warn('⚠️ Could not read workspace package versions, using defaults') + console.warn(` Error: ${error}`) + } + + return versions +} + +// Process template files to replace version placeholders +function processTemplateFile(filePath: string, versions: Record, isDevelopment: boolean = false) { + const content = readFileSync(filePath, 'utf-8') + let processedContent = content + + // Replace version placeholders with current versions + for (const [packageName, version] of Object.entries(versions)) { + const placeholder = `{{${packageName}_version}}` + const versionPrefix = isDevelopment ? 'workspace:*' : `^${version}` + processedContent = processedContent.replace(new RegExp(placeholder, 'g'), versionPrefix) + } + + // Handle workspace dependencies based on mode + if (isDevelopment) { + // Keep workspace dependencies for development + processedContent = processedContent.replace(/"mcp-use": "\^[^"]+"/, '"mcp-use": "workspace:*"') + processedContent = processedContent.replace(/"@mcp-use\/cli": "\^[^"]+"/, '"@mcp-use/cli": "workspace:*"') + processedContent = processedContent.replace(/"@mcp-use\/inspector": "\^[^"]+"/, '"@mcp-use/inspector": "workspace:*"') + } else { + // Replace workspace dependencies with specific versions for production + processedContent = processedContent.replace(/"mcp-use": "workspace:\*"/, `"mcp-use": "^${versions['mcp-use'] || '1.0.0'}"`) + processedContent = processedContent.replace(/"@mcp-use\/cli": "workspace:\*"/, `"@mcp-use/cli": "^${versions['@mcp-use/cli'] || '2.0.0'}"`) + processedContent = processedContent.replace(/"@mcp-use\/inspector": "workspace:\*"/, `"@mcp-use/inspector": "^${versions['@mcp-use/inspector'] || '0.3.0'}"`) + } + + return processedContent +} + +program + .name('create-mcp-use-app') + .description('Create a new MCP server project') + .version(packageJson.version) + .argument('[project-name]', 'Name of the MCP server project') + .option('-t, --template