[
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing to MCP-UI\n\nFirst of all, thank you for your interest in contributing to MCP-UI! We appreciate the time and effort you're willing to invest in improving the project. This document provides guidelines and information to make the contribution process as smooth as possible.\n\n## Table of Contents\n\n- [Getting Started](#getting-started)\n- [Local Development](#local-development)\n  - [Prerequisites](#prerequisites)\n  - [Setting Up Your Development Environment](#setting-up-your-development-environment)\n  - [Running the Project Locally](#running-the-project-locally)\n  - [Testing](#testing)\n  - [Code Formatting](#code-formatting)\n  - [Development Workflow](#development-workflow)\n  - [Project Structure](#project-structure)\n- [How to Contribute](#how-to-contribute)\n  - [Reporting Bugs](#reporting-bugs)\n  - [Suggesting Enhancements](#suggesting-enhancements)\n  - [Submitting Pull Requests](#submitting-pull-requests)\n- [Style Guidelines](#style-guidelines)\n  - [Code Style](#code-style)\n  - [Commit Messages](#commit-messages)\n- [Additional Resources](#additional-resources)\n\n## Getting Started\n\n1. Fork the repository and clone it to your local machine.\n2. Set up the development environment.\n3. Explore the codebase, run tests, and verify that everything works as expected.\n\n## Local Development\n\n### Prerequisites\n\nBefore you start working on MCP-UI, make sure you have the following installed:\n\n- [Node.js](https://nodejs.org/) (version 18 or higher recommended)\n- [pnpm](https://pnpm.io/) (version 8.15.7 or higher)\n- Git\n\n### Setting Up Your Development Environment\n\n1. Clone your forked repository:\n\n   ```bash\n   git clone https://github.com/your-username/mcp-ui.git\n   cd mcp-ui\n   ```\n\n2. Install dependencies:\n\n   ```bash\n   pnpm install\n   ```\n\n3. Set up environment variables:\n   - Create a `.env.local` file in the root directory\n   - Add any necessary environment variables (ask project maintainers if you need access to specific API keys)\n\n### Running the Project Locally\n\nTo start the development server:\n\n```bash\npnpm vercel dev\n```\n\nThis will start the Next.js development server, typically at http://localhost:3000.\n\nFor running with the MCP Inspector (useful for debugging MCP endpoints):\n\n```bash\npnpm run inspector\n```\n\n### Testing\n\nTo run tests:\n\n```bash\npnpm test\n```\n\nMCP-UI uses Vitest as the testing framework. When adding new features, please include appropriate tests.\n\n### Code Formatting\n\nMCP-UI uses Prettier for code formatting and lint-staged to ensure code is properly formatted before committing. Pre-commit hooks are set up with Husky to run these checks automatically.\n\nTo manually format your code:\n\n```bash\npnpm prettier --write .\n```\n\n### Development Workflow\n\n1. Create a new branch for your feature/bugfix\n2. Make your changes\n3. Add tests for your changes when applicable\n4. Run the tests to ensure they pass\n5. Commit your changes following the commit message guidelines\n6. Push your branch and open a pull request\n\n### Project Structure\n\n- `api/`: Contains the server-side code and MCP implementation\n  - `tools/`: MCP tools implementation\n  - `utils/`: Utility functions for the API\n- `app/`: Next.js app directory with React components\n- `pages/`: Additional Next.js pages\n- `public/`: Static assets\n- `shared/`: Shared utilities used across the codebase\n\n## How to Contribute\n\n### Reporting Bugs\n\nIf you encounter a bug or issue while using MCP-UI, please open a new issue on the [GitHub Issues](https://github.com/idosal/mcp-ui/issues) page. Provide a clear and concise description of the problem, steps to reproduce it, and any relevant error messages or logs.\n\n### Suggesting Enhancements\n\nWe welcome ideas for improvements and new features. To suggest an enhancement, open a new issue on the [GitHub Issues](https://github.com/idosal/mcp-ui/issues) page. Describe the enhancement in detail, explain the use case, and outline the benefits it would bring to the project.\n\n### Submitting Pull Requests\n\n1. Create a new branch for your feature or bugfix. Use a descriptive name like `feature/your-feature-name` or `fix/your-bugfix-name`.\n2. Make your changes, following the [Style Guidelines](#style-guidelines) below.\n3. Test your changes and ensure that they don't introduce new issues or break existing functionality.\n4. Commit your changes, following the [commit message guidelines](#commit-messages).\n5. Push your branch to your fork on GitHub.\n6. Open a new pull request against the `main` branch of the `mcp-ui` repository. Include a clear and concise description of your changes, referencing any related issues.\n\n## Style Guidelines\n\n### Code Style\n\nMCP-UI uses [ESLint](https://eslint.org/) as its code style guide. Please ensure that your code follows these guidelines.\n\n### Commit Messages\n\nWrite clear and concise commit messages that briefly describe the changes made in each commit. Use the imperative mood and start with a capitalized verb, e.g., \"Add new feature\" or \"Fix bug in function\".\n\n## Additional Resources\n\n- [GitHub Help](https://help.github.com/)\n- [GitHub Pull Request Documentation](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests)\n- [ESLint Style Guide](https://eslint.org/)\n\nThank you once again for your interest in contributing to MCP UI. We look forward to collaborating with you and creating an even better project together!\n"
  },
  {
    "path": ".github/copilot-instructions.md",
    "content": "# MCP-UI Development Instructions\n\n**ALWAYS follow these instructions first and fallback to additional search and context gathering only if the information here is incomplete or found to be in error.**\n\nMCP-UI is a Model Context Protocol UI SDK monorepo providing TypeScript and Ruby SDKs for building MCP enabled applications with interactive UI components. The repository includes client SDKs, server SDKs, documentation, and examples.\n\n## Working Effectively\n\n### Bootstrap and Build (CRITICAL - Set 60+ minute timeouts)\n- Install Node.js 22.x and pnpm 9+: `npm install -g pnpm`\n- Install Ruby 3.x and bundler: `sudo apt-get install -y ruby ruby-dev build-essential && sudo gem install bundler`\n- Clone repository: `git clone https://github.com/idosal/mcp-ui.git && cd mcp-ui`\n- Install dependencies: `pnpm install` -- takes ~60 seconds. NEVER CANCEL.\n- Build all packages: `pnpm build` -- takes ~15 seconds. NEVER CANCEL. Set timeout to 120+ seconds.\n- Build documentation: `pnpm docs:build` -- takes ~17 seconds. NEVER CANCEL. Set timeout to 180+ seconds.\n\n### Testing (CRITICAL - Set 30+ minute timeouts)\n- Run TypeScript tests: `pnpm test:ts` -- takes ~6 seconds. NEVER CANCEL. Set timeout to 300+ seconds.\n- Run Ruby tests: `pnpm test:ruby` -- takes ~1 second. NEVER CANCEL. Set timeout to 300+ seconds.\n- Run all tests: `pnpm test` -- combines TypeScript and Ruby tests. NEVER CANCEL. Set timeout to 300+ seconds.\n- Run with coverage: `pnpm coverage` -- NEVER CANCEL. Set timeout to 600+ seconds.\n\n### Code Quality (Always run before committing)\n- Lint code: `pnpm lint` -- takes ~2.4 seconds. Uses ESLint with TypeScript parser.\n- Fix linting issues: `pnpm lint:fix`\n- Format code: `pnpm format` -- Uses Prettier with single quotes, trailing commas, 100 char width.\n\n### Development Workflow\n- Run TypeScript SDKs in development: `pnpm dev` -- starts all TypeScript package dev servers in parallel.\n- Run docs in development: `pnpm docs:dev` -- starts VitePress dev server.\n- Preview builds: `pnpm preview` -- preview built packages.\n\n## Validation (CRITICAL - Always perform these after changes)\n\n### Mandatory End-to-End Validation Scenarios\n1. **Always build and test after making changes:** Run `pnpm build && pnpm test` to ensure nothing is broken.\n2. **Validate examples work:** Test working examples by running:\n   - `cd examples/remote-dom-demo && npm run build` -- takes ~1 second. Always works.\n   - `cd examples/wc-demo && npm run build` -- takes ~1 second. Always works.\n   - NOTE: `typescript-server-demo` may have import issues and should be tested separately after SDK changes.\n3. **Test UI components:** When changing client components, manually verify React rendering works by running example applications.\n4. **Validate SDK functionality:** When changing server SDKs, test resource creation with both TypeScript and Ruby implementations.\n5. **Documentation validation:** When updating docs, run `pnpm docs:build` and verify no broken links or build errors.\n\n### Ruby SDK Specific Validation\n- Ruby gems must be installed with `sudo bundle install` in `sdks/ruby/` directory\n- Run Ruby linting: `cd sdks/ruby && sudo bundle exec rubocop`\n- Ruby tests validate resource creation: `cd sdks/ruby && sudo bundle exec rake spec`\n\n## Project Structure\n\n### Key Directories\n- `sdks/typescript/` - TypeScript SDKs (client, server, shared)\n  - `client/` - React components for rendering MCP-UI resources\n  - `server/` - Utilities for creating UI resources on MCP servers\n  - `shared/` - Common types and utilities\n- `sdks/ruby/` - Ruby SDK (`mcp_ui_server` gem)\n- `examples/` - Demo applications showcasing SDK usage\n  - `remote-dom-demo/` - Interactive UI script testing\n  - `typescript-server-demo/` - Complete TypeScript server example\n  - `ruby-server-demo/` - Complete Ruby server example\n- `docs/` - VitePress documentation site\n\n### Critical Files\n- `package.json` - Root monorepo configuration with pnpm workspaces\n- `pnpm-workspace.yaml` - Workspace configuration for TypeScript packages and examples\n- `.github/workflows/ci.yml` - CI pipeline with build, test, and release steps\n- `vitest.config.ts` - Test configuration for TypeScript packages\n- `tsconfig.base.json` - Base TypeScript configuration\n\n## Build System Details\n\n### Package Management\n- Uses pnpm workspaces for monorepo management\n- TypeScript packages use Vite for building with dual ESM/CJS output\n- Ruby uses standard gem building with bundler\n\n### Dependencies and Versions\n- Node.js 22.x (required for TypeScript SDKs)\n- pnpm 9+ (required for workspace management)\n- Ruby 3.x (required for Ruby SDK)\n- React 18+ (peer dependency for client SDK)\n\n### Build Outputs\n- TypeScript client: Builds to `dist/` with ESM, CJS, and Web Component builds\n- TypeScript server: Builds to `dist/` with ESM and CJS formats\n- Ruby gem: Standard gem structure in `lib/`\n- Documentation: Static site built to `docs/src/.vitepress/dist/`\n\n## Common Development Tasks\n\n### Adding New Features\n1. Determine if change affects TypeScript SDKs, Ruby SDK, or both\n2. Make changes following existing patterns\n3. Add appropriate tests (Vitest for TypeScript, RSpec for Ruby)\n4. Update documentation if needed\n5. Run full validation workflow: `pnpm build && pnpm test && pnpm lint`\n\n### Working with Examples\n- Examples use workspace dependencies and build independently\n- External URL demo shows partial externalUrl content type functionality\n- Remote DOM demo shows partial remote-dom content type functionality\n- The Server demo shows a full MCP server implementation with Cloudflare\n- The Ruby and Typescript server demos show basic MCP server implementations\n- WC-demo showcases the web components client implementation\n- Always test example builds after making SDK changes\n\n### Documentation Updates\n- Documentation uses VitePress with enhanced styling\n- Edit files in `docs/src/` directory\n- Test changes with `pnpm docs:dev` before building\n- Always build docs to check for errors: `pnpm docs:build`\n\n## Troubleshooting\n\n### Common Issues\n- **TypeScript version warnings:** Current setup uses TypeScript 5.8.3 with ESLint plugins that support <5.4.0. This is expected and working.\n- **Ruby permission errors:** Use `sudo bundle install` and `sudo bundle exec` for Ruby commands in CI environment.\n- **Build failures:** Always run `pnpm install` first, then check individual package builds.\n- **Test failures:** Check if all dependencies are installed and built before running tests.\n\n### CI/CD Pipeline\n- CI runs on Node.js 22.x with pnpm 10\n- Separate jobs for TypeScript and Ruby testing\n- Path filtering prevents unnecessary builds\n- Semantic release handles versioning and publishing\n\n### Performance Notes\n- TypeScript builds are fast (~15 seconds total)\n- Ruby tests run very quickly (~1 second)\n- Documentation builds may take longer (~17 seconds)\n- Full CI pipeline completes in under 5 minutes\n\n## CRITICAL REMINDERS\n- **NEVER CANCEL builds or long-running commands** - Always set appropriate timeouts\n- **Always validate changes** with complete build and test cycle\n- **Test examples** after SDK changes to ensure compatibility\n- **Run linting and formatting** before committing changes\n- **Update documentation** when adding or changing features\n- **Use workspace commands** from root directory for consistency\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - main\n      - alpha\n  pull_request:\n    branches:\n      - main\n      - alpha\n  release:\n    types: [published]\n\njobs:\n  filter_changed_paths:\n    runs-on: ubuntu-latest\n    outputs:\n      ts_client_files: ${{ steps.filter.outputs.ts_client_files }}\n      ts_server_files: ${{ steps.filter.outputs.ts_server_files }}\n      ruby_sdk_files: ${{ steps.filter.outputs.ruby_sdk_files }}\n      python_sdk_files: ${{ steps.filter.outputs.python_sdk_files }}\n      example_files: ${{ steps.filter.outputs.example_files }}\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - uses: dorny/paths-filter@v2\n        id: filter\n        with:\n          base: ${{ github.event_name == 'push' && github.event.before || github.base_ref }}\n          filters: |\n            ts_client_files:\n              - 'sdks/typescript/client/**'\n            ts_server_files:\n              - 'sdks/typescript/server/**'\n            ruby_sdk_files:\n              - 'sdks/ruby/**'\n            python_sdk_files:\n              - 'sdks/python/**'\n            example_files:\n              - 'examples/**'\n\n  js_build_and_test:\n    needs: filter_changed_paths\n    if: needs.filter_changed_paths.outputs.ts_client_files == 'true' || needs.filter_changed_paths.outputs.ts_server_files == 'true'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup pnpm\n        uses: pnpm/action-setup@v2\n        with:\n          version: 10\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: 22.x\n          cache: 'pnpm'\n\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile\n\n      - name: Lint\n        run: pnpm lint\n\n      - name: Test\n        run: pnpm test:ts\n\n      - name: Build\n        run: pnpm build\n\n  ruby_sdk_test:\n    needs: filter_changed_paths\n    if: needs.filter_changed_paths.outputs.ruby_sdk_files == 'true'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup pnpm\n        uses: pnpm/action-setup@v2\n        with:\n          version: 10\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: 22.x\n          cache: 'pnpm'\n\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile\n\n      - name: Set up Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: '3.2'\n          bundler: latest\n          bundler-cache: true\n          working-directory: 'sdks/ruby'\n\n      - name: Lint\n        run: bundle exec rubocop\n        working-directory: 'sdks/ruby'\n\n      - name: Run tests\n        run: pnpm test:ruby\n\n  python_sdk_test:\n    needs: filter_changed_paths\n    if: needs.filter_changed_paths.outputs.python_sdk_files == 'true'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up Python\n        uses: actions/setup-python@v4\n        with:\n          python-version: '3.11'\n\n      - name: Set up uv\n        uses: astral-sh/setup-uv@v3\n        with:\n          enable-cache: true\n\n      - name: Install dependencies\n        run: uv sync --dev\n        working-directory: sdks/python/server\n\n      - name: Lint with ruff\n        run: uv run ruff check\n        working-directory: sdks/python/server\n\n      - name: Type check with pyright\n        run: uv run pyright\n        working-directory: sdks/python/server\n\n      - name: Run tests\n        run: uv run pytest\n        working-directory: sdks/python/server\n\n      - name: Build package\n        run: uv build\n        working-directory: sdks/python/server\n\n  release_ts_client:\n    needs: [js_build_and_test, filter_changed_paths]\n    if: (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/alpha') && needs.filter_changed_paths.outputs.ts_client_files == 'true'\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      pull-requests: write # to be able to comment on released pull requests\n      issues: write\n      id-token: write\n    steps:\n      - uses: actions/checkout@v4\n        with: { fetch-depth: 0 }\n      - name: Fetch all tags\n        run: git fetch --tags --force\n      - name: Setup pnpm\n        uses: pnpm/action-setup@v2\n        with: { version: 10 }\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with: { node-version: 22.x, cache: 'pnpm' }\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile\n      - name: Release\n        working-directory: sdks/typescript/client\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}\n        run: npx semantic-release\n\n  release_ts_server:\n    needs: [js_build_and_test, release_ts_client, filter_changed_paths]\n    if: >\n      always() &&\n      (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/alpha') &&\n      needs.filter_changed_paths.outputs.ts_server_files == 'true' &&\n      needs.js_build_and_test.result == 'success'\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      issues: write\n      pull-requests: write\n      id-token: write\n    steps:\n      - uses: actions/checkout@v4\n        with: { fetch-depth: 0 }\n      - name: Pull latest changes\n        run: git pull --rebase origin ${{ github.ref_name }}\n      - name: Fetch all tags\n        run: git fetch --tags --force\n      - name: Setup pnpm\n        uses: pnpm/action-setup@v2\n        with: { version: 10 }\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with: { node-version: 22.x, cache: 'pnpm' }\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile\n      - name: Release\n        working-directory: sdks/typescript/server\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}\n        run: npx semantic-release\n\n  release_ruby_sdk:\n    name: Release Ruby SDK\n    needs: [ruby_sdk_test, filter_changed_paths, release_ts_server]\n    if: >\n      always() &&\n      (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/alpha') &&\n      needs.filter_changed_paths.outputs.ruby_sdk_files == 'true' &&\n      needs.ruby_sdk_test.result == 'success'\n    runs-on: ubuntu-latest\n    environment: release\n    permissions:\n      contents: write # to push commits and tags\n      id-token: write # for trusted publishing\n      issues: write # to comment on issues\n      pull-requests: write # to comment on pull requests\n    steps:\n      - uses: actions/checkout@v4\n        with: { fetch-depth: 0 }\n      - name: Pull latest changes\n        run: git pull --rebase origin ${{ github.ref_name }}\n      - name: Setup pnpm\n        uses: pnpm/action-setup@v2\n        with: { version: 10 }\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with: { node-version: 22.x, cache: 'pnpm' }\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile\n      - name: Set up Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: '3.2'\n          bundler: latest\n          bundler-cache: true\n          working-directory: sdks/ruby\n      - name: Configure RubyGems Credentials\n        uses: rubygems/configure-rubygems-credentials@main\n      - name: Release\n        working-directory: sdks/ruby\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: npx semantic-release\n\n  release_python_sdk:\n    name: Release Python SDK\n    needs: [python_sdk_test, filter_changed_paths, release_ruby_sdk]\n    if: >\n      always() &&\n      (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/alpha') &&\n      needs.filter_changed_paths.outputs.python_sdk_files == 'true' &&\n      needs.python_sdk_test.result == 'success'\n    runs-on: ubuntu-latest\n    environment: release\n    permissions:\n      contents: write # to push commits and tags\n      id-token: write # for trusted publishing to PyPI\n      issues: write # to comment on issues\n      pull-requests: write # to comment on pull requests\n    steps:\n      - uses: actions/checkout@v4\n        with: { fetch-depth: 0 }\n      - name: Pull latest changes\n        run: git pull --rebase origin ${{ github.ref_name }}\n      - name: Fetch all tags\n        run: git fetch --tags --force\n      - name: Setup pnpm\n        uses: pnpm/action-setup@v2\n        with: { version: 10 }\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with: { node-version: 22.x, cache: 'pnpm' }\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile\n      - name: Set up Python\n        uses: actions/setup-python@v4\n        with:\n          python-version: '3.11'\n      - name: Set up uv\n        uses: astral-sh/setup-uv@v3\n        with:\n          enable-cache: true\n      - name: Install Python dependencies\n        run: uv sync --dev\n        working-directory: sdks/python/server\n      - name: Release\n        working-directory: sdks/python/server\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: npx semantic-release\n"
  },
  {
    "path": ".github/workflows/deploy-docs.yml",
    "content": "#\nname: Deploy VitePress site to Pages\n\non:\n  # Runs on pushes targeting the `main` branch. Change this to `master` if you\\'re\n  # using the `master` branch as the default branch.\n  push:\n    branches: [main]\n\n  # Allows you to run this workflow manually from the Actions tab\n  workflow_dispatch:\n\n# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\n# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.\n# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.\nconcurrency:\n  group: pages\n  cancel-in-progress: false\n\njobs:\n  # Build job\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0 # Not needed if lastUpdated is not enabled\n      - name: Use pnpm\n        uses: pnpm/action-setup@v3\n        with:\n          version: 10 # Specify pnpm version from package.json\n      - name: Setup Node\n        uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: 'pnpm'\n      - name: Setup Pages\n        uses: actions/configure-pages@v4\n      - name: Install dependencies\n        run: pnpm install\n      - name: Build with VitePress\n        run: pnpm docs:build # This script is \\'vitepress build docs\\'\n      - name: Upload artifact\n        uses: actions/upload-pages-artifact@v3\n        with:\n          # Path to the output directory of 'vitepress build docs/src'\n          path: docs/src/.vitepress/dist\n\n  # Deployment job\n  deploy:\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    needs: build\n    runs-on: ubuntu-latest\n    name: Deploy\n    steps:\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\nexamples/server/build\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# PNPM\n.pnpm-store/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarnclean\n\n# dotenv environment variables file\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\nDynamoDBLocal_lib/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*\n\n# Parcel cache files\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build output\n.nuxt\n\n# Svelte build output\n.svelte-kit\n\n# Docusaurus build output\n.docusaurus\n\n# Gatsby cache\n.cache/\n\n\n# Vite build output\ndist\n\n# VitePress\ndocs/src/.vitepress/dist\ndocs/.vitepress/cache\ndocs/src/.vitepress/cache\n\n# Monorepo specific\n/sdks/**/dist\n/sdks/**/coverage\n/examples/**/dist\n/examples/**/coverage\n\n# OS generated files #\n######################\n.DS_Store\n.DS_Store?\n._*\n.Spotlight-V100\n.Trashes\nehthumbs.db\nThumbs.db\n\n# IDEs and Editors #\n####################\n\n# VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n*.code-workspace\n\n# JetBrains\n.idea/\n*.iws\n*.iml\n*.ipr\n\n# Sublime Text\n*.sublime-project\n*.sublime-workspace\n\n# Compilation artifacts\n#######################\n*.o\n*.obj\n*.exe\n*.dll\n*.so\n*.dylib\n\n# C/C++ specific\n*.gch\n*.pch\n\n# Python specific\n__pycache__/\n*.py[cod]\n*$py.class\n\n# Environment variables\n.env*\n!.env.example\n\n# Husky\n.husky/_/\n.husky/.gitignore\n\n# Auto-generated files\nadapter-runtime.bundled.ts"
  },
  {
    "path": ".prettierignore",
    "content": "# Ignore artifacts:\nnode_modules\ndist\nbuild\ncoverage\nvenv\n\n# Ignore package manager files (if you commit them)\npackage-lock.json\nyarn.lock\npnpm-lock.yaml\n\n# Ignore specific configuration files if managed elsewhere or intentionally unformatted\n# .vscode/\n# .idea/\n\n# Ignore generated/bundled files\n*.generated.*\n*.bundled.ts\ndocs/src/.vitepress/cache\nextensions/vscode/out\n\n*.log\n*.md "
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n  \"semi\": true,\n  \"trailingComma\": \"all\",\n  \"singleQuote\": true,\n  \"printWidth\": 100,\n  \"tabWidth\": 2,\n  \"plugins\": [\"prettier-plugin-tailwindcss\"]\n}\n"
  },
  {
    "path": ".releaserc.json",
    "content": "{\n  \"branches\": [\n    \"main\",\n    {\n      \"name\": \"alpha\",\n      \"prerelease\": true\n    }\n  ],\n  \"plugins\": [\n    \"@semantic-release/commit-analyzer\",\n    \"@semantic-release/release-notes-generator\",\n    \"@semantic-release/changelog\",\n    [\n      \"@semantic-release/npm\",\n      {\n        \"pkgRoot\": \"sdks/typescript/client\",\n        \"npmPublish\": true\n      }\n    ],\n    [\n      \"@semantic-release/npm\",\n      {\n        \"pkgRoot\": \"sdks/typescript/server\",\n        \"npmPublish\": true\n      }\n    ],\n    [\n      \"@semantic-release/npm\",\n      {\n        \"npmPublish\": false\n      }\n    ],\n    [\n      \"@semantic-release/exec\",\n      {\n        \"prepareCmd\": \"pnpm install --lockfile-only --ignore-scripts\"\n      }\n    ],\n    [\n      \"@semantic-release/git\",\n      {\n        \"assets\": [\n          \"CHANGELOG.md\",\n          \"package.json\",\n          \"sdks/typescript/client/package.json\",\n          \"sdks/typescript/server/package.json\",\n          \"pnpm-lock.yaml\"\n        ],\n        \"message\": \"chore(release): ${nextRelease.version} [skip ci]\\n\\n${nextRelease.notes}\"\n      }\n    ],\n    \"@semantic-release/github\"\n  ]\n}\n"
  },
  {
    "path": ".ruby-version",
    "content": "3.2.2\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# [5.2.0](https://github.com/idosal/mcp-ui/compare/v5.1.2...v5.2.0) (2025-07-18)\n\n\n### Features\n\n* support generic messages response ([#35](https://github.com/idosal/mcp-ui/issues/35)) ([10b407b](https://github.com/idosal/mcp-ui/commit/10b407b279b3ee9608ef077445f4d714f88343c5))\n\n## [5.1.2](https://github.com/idosal/mcp-ui/compare/v5.1.1...v5.1.2) (2025-07-18)\n\n\n### Bug Fixes\n\n* use targetOrigin in the proxy message relay ([#40](https://github.com/idosal/mcp-ui/issues/40)) ([b3fb54e](https://github.com/idosal/mcp-ui/commit/b3fb54e28ca7b8eeda896b5bcf478b6343dbba47))\n\n## [5.1.1](https://github.com/idosal/mcp-ui/compare/v5.1.0...v5.1.1) (2025-07-18)\n\n\n### Bug Fixes\n\n* add a bridge to pass messages in and out of the proxy ([#38](https://github.com/idosal/mcp-ui/issues/38)) ([30ccac0](https://github.com/idosal/mcp-ui/commit/30ccac0706ad8e02ebcd8960924ed1d58ddedf85))\n\n# [5.1.0](https://github.com/idosal/mcp-ui/compare/v5.0.0...v5.1.0) (2025-07-18)\n\n\n### Features\n\n* add proxy option to externalUrl ([#37](https://github.com/idosal/mcp-ui/issues/37)) ([7b95cd0](https://github.com/idosal/mcp-ui/commit/7b95cd0b3873fc1cde28748ec463e81c6ff1c494))\n\n# [5.0.0](https://github.com/idosal/mcp-ui/compare/v4.1.4...v5.0.0) (2025-07-17)\n\n\n### Bug Fixes\n\n* rename delivery -> encoding and flavor -> framework ([#36](https://github.com/idosal/mcp-ui/issues/36)) ([9a509ed](https://github.com/idosal/mcp-ui/commit/9a509ed80d051b0a8042b36958b401a0a7c1e138))\n\n\n### BREAKING CHANGES\n\n* The existing naming is ambiguous. Renaming delivery to encoding and flavor to framework should clarify the intent.\n\n## [4.1.4](https://github.com/idosal/mcp-ui/compare/v4.1.3...v4.1.4) (2025-07-16)\n\n\n### Bug Fixes\n\n* pass ref explicitly using iframeProps ([#33](https://github.com/idosal/mcp-ui/issues/33)) ([d01b5d1](https://github.com/idosal/mcp-ui/commit/d01b5d1e4cdaedc436ba2fa8984d866d93d59087))\n\n## [4.1.3](https://github.com/idosal/mcp-ui/compare/v4.1.2...v4.1.3) (2025-07-15)\n\n\n### Bug Fixes\n\n* ref passing to UIResourceRenderer ([#32](https://github.com/idosal/mcp-ui/issues/32)) ([d28c23f](https://github.com/idosal/mcp-ui/commit/d28c23f9b8ee320f4e361200ae02a23f0d2a1c0c))\n\n## [4.1.2](https://github.com/idosal/mcp-ui/compare/v4.1.1...v4.1.2) (2025-07-10)\n\n\n### Bug Fixes\n\n* validate URL ([b7c994d](https://github.com/idosal/mcp-ui/commit/b7c994dfdd947b3dfbb903fc8cb896d61004c8d8))\n\n## [4.1.1](https://github.com/idosal/mcp-ui/compare/v4.1.0...v4.1.1) (2025-07-06)\n\n\n### Bug Fixes\n\n* text and blob support in RemoteDOM resources ([ec68eb9](https://github.com/idosal/mcp-ui/commit/ec68eb90df984da8b492cc25eafdafdeda79f299))\n\n# [4.1.0](https://github.com/idosal/mcp-ui/compare/v4.0.0...v4.1.0) (2025-07-05)\n\n\n### Features\n\n* separate html and remote-dom props ([#24](https://github.com/idosal/mcp-ui/issues/24)) ([a7f0529](https://github.com/idosal/mcp-ui/commit/a7f05299dc9cc40184f9ab25c5b648ee7077be64))\n\n# [4.0.0](https://github.com/idosal/mcp-ui/compare/v3.0.0...v4.0.0) (2025-07-05)\n\n\n### Bug Fixes\n\n* rename components and methods to fit new scope ([#22](https://github.com/idosal/mcp-ui/issues/22)) ([6bab1fe](https://github.com/idosal/mcp-ui/commit/6bab1fe3a168a18e7ba4762e23478abf4e0cc84c))\n\n\n### BREAKING CHANGES\n\n* exported names have changed\n\n# [3.0.0](https://github.com/idosal/mcp-ui/compare/v2.5.1...v3.0.0) (2025-07-04)\n\n\n### Features\n\n* switch to UiResourceRenderer ([#21](https://github.com/idosal/mcp-ui/issues/21)) ([6fe3166](https://github.com/idosal/mcp-ui/commit/6fe316682675e27db914d60696754677e3783448))\n\n\n### BREAKING CHANGES\n\n* removed deprecated client API\n\n## [2.5.1](https://github.com/idosal/mcp-ui/compare/v2.5.0...v2.5.1) (2025-06-28)\n\n\n### Bug Fixes\n\n* export RemoteDomResource ([2b86f2d](https://github.com/idosal/mcp-ui/commit/2b86f2dd4506de49c69908e23d84a2a323170446))\n* export UiResourceRenderer and HtmlResource ([2b841a5](https://github.com/idosal/mcp-ui/commit/2b841a556c1111ed70ccb3d3987afd21fe7df897))\n\n# [2.5.0](https://github.com/idosal/mcp-ui/compare/v2.4.0...v2.5.0) (2025-06-27)\n\n\n### Features\n\n* add remote-dom content type ([#18](https://github.com/idosal/mcp-ui/issues/18)) ([5dacf37](https://github.com/idosal/mcp-ui/commit/5dacf37c22b5ee6ae795049a8d573fc073b8a1f5))\n\n# [2.4.0](https://github.com/idosal/mcp-ui/compare/v2.3.3...v2.4.0) (2025-06-20)\n\n\n### Features\n\n* **client:** allow setting supportedContentTypes for HtmlResource ([#17](https://github.com/idosal/mcp-ui/issues/17)) ([e009ef1](https://github.com/idosal/mcp-ui/commit/e009ef10010134ba3d9893314cc4d8e1274f1f07))\n\n## [2.3.3](https://github.com/idosal/mcp-ui/compare/v2.3.2...v2.3.3) (2025-06-19)\n\n\n### Bug Fixes\n\n* typescript types to be compatible with MCP SDK ([#10](https://github.com/idosal/mcp-ui/issues/10)) ([74365d7](https://github.com/idosal/mcp-ui/commit/74365d7ed6422beef6cd9ee0f5a97c847bd9827b))\n\n## [2.3.2](https://github.com/idosal/mcp-ui/compare/v2.3.1...v2.3.2) (2025-06-14)\n\n\n### Bug Fixes\n\n* trigger release ([aaca831](https://github.com/idosal/mcp-ui/commit/aaca83125c3f7825ccdebf0f04f8553e953c5249))\n\n## [2.3.1](https://github.com/idosal/mcp-ui/compare/v2.3.0...v2.3.1) (2025-06-14)\n\n\n### Bug Fixes\n\n* iframe handle ([#15](https://github.com/idosal/mcp-ui/issues/15)) ([66bd4fd](https://github.com/idosal/mcp-ui/commit/66bd4fd3d04f82e3e4557f064e701b68e1d8af11))\n\n# [2.3.0](https://github.com/idosal/mcp-ui/compare/v2.2.0...v2.3.0) (2025-06-13)\n\n\n### Features\n\n* pass iframe props down ([#14](https://github.com/idosal/mcp-ui/issues/14)) ([112539d](https://github.com/idosal/mcp-ui/commit/112539d28640a96e8375a6b416f2ba559370b312))\n\n# [2.2.0](https://github.com/idosal/mcp-ui/compare/v2.1.0...v2.2.0) (2025-06-03)\n\n\n### Features\n\n* support ui action result types ([#6](https://github.com/idosal/mcp-ui/issues/6)) ([899d152](https://github.com/idosal/mcp-ui/commit/899d1527286a281a23fbb8f3a207d435dfc3fe96))\n\n# [2.1.0](https://github.com/idosal/mcp-ui/compare/v2.0.0...v2.1.0) (2025-05-31)\n\n\n### Features\n\n* consolidate ui:// and ui-app:// ([#8](https://github.com/idosal/mcp-ui/issues/8)) ([2e08035](https://github.com/idosal/mcp-ui/commit/2e08035676bb6a46ef3c94dba916bc895f1fa3cc))\n\n# [2.0.0](https://github.com/idosal/mcp-ui/compare/v1.1.0...v2.0.0) (2025-05-23)\n\n\n### Documentation\n\n* bump ([#4](https://github.com/idosal/mcp-ui/issues/4)) ([ad4d163](https://github.com/idosal/mcp-ui/commit/ad4d1632cc1f9c99072349a8f0cdaac343236132))\n\n\n### BREAKING CHANGES\n\n* (previous one didn't take due to semantic-release misalignment)\n\n# [1.1.0](https://github.com/idosal/mcp-ui/compare/v1.0.7...v1.1.0) (2025-05-16)\n\n\n### Bug Fixes\n\n* update deps ([4091ef4](https://github.com/idosal/mcp-ui/commit/4091ef47da048fab3c4feb002f5287b2ff295744))\n\n\n### Features\n\n* change onGenericMcpAction to optional onUiAction ([1913b59](https://github.com/idosal/mcp-ui/commit/1913b5977c30811f9e67659949e2d961f2eda983))\n\n## [1.0.7](https://github.com/idosal/mcp-ui/compare/v1.0.6...v1.0.7) (2025-05-16)\n\n\n### Bug Fixes\n\n* **client:** specify iframe ([fd0b70a](https://github.com/idosal/mcp-ui/commit/fd0b70a84948d3aa5d7a79269ff7c3bcd0946689))\n\n## [1.0.6](https://github.com/idosal/mcp-ui/compare/v1.0.5...v1.0.6) (2025-05-16)\n\n\n### Bug Fixes\n\n* support react-router ([21ffb95](https://github.com/idosal/mcp-ui/commit/21ffb95fe6d77a348b95b38dbf3741ba6442894e))\n\n## [1.0.5](https://github.com/idosal/mcp-ui/compare/v1.0.4...v1.0.5) (2025-05-16)\n\n\n### Bug Fixes\n\n* **client:** styling ([6ff9b68](https://github.com/idosal/mcp-ui/commit/6ff9b685fd1be770fd103943e45275e9ec86905c))\n\n## [1.0.4](https://github.com/idosal/mcp-ui/compare/v1.0.3...v1.0.4) (2025-05-16)\n\n\n### Bug Fixes\n\n* packaging ([9e6babd](https://github.com/idosal/mcp-ui/commit/9e6babd3a587213452ea7aec4cc9ae3a50fa1965))\n\n## [1.0.3](https://github.com/idosal/mcp-ui/compare/v1.0.2...v1.0.3) (2025-05-16)\n\n\n### Bug Fixes\n\n* exports ([3a93a16](https://github.com/idosal/mcp-ui/commit/3a93a16e1b7438ba7b2ef49ca854479f755abcc6))\n\n## [1.0.2](https://github.com/idosal/mcp-ui/compare/v1.0.1...v1.0.2) (2025-05-16)\n\n\n### Bug Fixes\n\n* remove shared dependency ([e66e8f4](https://github.com/idosal/mcp-ui/commit/e66e8f49b1ba46090db6e4682060488566f4fe41))\n\n## [1.0.1](https://github.com/idosal/mcp-ui/compare/v1.0.0...v1.0.1) (2025-05-16)\n\n\n### Bug Fixes\n\n* publish ([0943e7a](https://github.com/idosal/mcp-ui/commit/0943e7acaf17f32aae085c2313bfbec47bc59f1f))\n\n# 1.0.0 (2025-05-16)\n\n\n### Bug Fixes\n\n* dependencies ([887f61f](https://github.com/idosal/mcp-ui/commit/887f61f827b4585c17493d4fa2dfb251ea598587))\n* lint ([4487820](https://github.com/idosal/mcp-ui/commit/44878203a71c3c9173d463b809be36769e996ba9))\n* lint ([d0a91f9](https://github.com/idosal/mcp-ui/commit/d0a91f9a07ec0042690240c3d8d0bad620f8c765))\n* package config ([8dc1e53](https://github.com/idosal/mcp-ui/commit/8dc1e5358c3c8e641206a5e6851427d360cc1955))\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\nidosalomon@gmail.com.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2025 Ido Salomon\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "## 📦 Model Context Protocol UI SDK\n\n<p align=\"center\">\n  <img width=\"250\" alt=\"image\" src=\"https://github.com/user-attachments/assets/65b9698f-990f-4846-9b2d-88de91d53d4d\" />\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@mcp-ui/server\"><img src=\"https://img.shields.io/npm/v/@mcp-ui/server?label=server&color=green\" alt=\"Server Version\"></a>\n  <a href=\"https://www.npmjs.com/package/@mcp-ui/client\"><img src=\"https://img.shields.io/npm/v/@mcp-ui/client?label=client&color=blue\" alt=\"Client Version\"></a>\n  <a href=\"https://rubygems.org/gems/mcp_ui_server\"><img src=\"https://img.shields.io/gem/v/mcp_ui_server\" alt=\"Ruby Server SDK Version\"></a>\n  <a href=\"https://pypi.org/project/mcp-ui-server/\"><img src=\"https://img.shields.io/pypi/v/mcp-ui-server?label=python&color=yellow\" alt=\"Python Server SDK Version\"></a>\n  <a href=\"https://discord.gg/CEAG4KW7ZH\"><img src=\"https://img.shields.io/discord/1401195140436983879?logo=discord&label=discord\" alt=\"Discord\"></a>\n  <a href=\"https://gitmcp.io/idosal/mcp-ui\"><img src=\"https://img.shields.io/endpoint?url=https://gitmcp.io/badge/idosal/mcp-ui\" alt=\"MCP Documentation\"></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"#-whats-mcp-ui\">What's mcp-ui?</a> •\n  <a href=\"#-core-concepts\">Core Concepts</a> •\n  <a href=\"#-installation\">Installation</a> •\n  <a href=\"#-getting-started\">Getting Started</a> •\n  <a href=\"#-walkthrough\">Walkthrough</a> •\n  <a href=\"#-examples\">Examples</a> •\n  <a href=\"#-supported-hosts\">Supported Hosts</a> •\n  <a href=\"#-security\">Security</a> •\n  <a href=\"#-roadmap\">Roadmap</a> •\n  <a href=\"#-contributing\">Contributing</a> •\n  <a href=\"#-license\">License</a>\n</p>\n\n----\n\n**`mcp-ui`** pioneered the concept of interactive UI over [MCP](https://modelcontextprotocol.io/introduction), enabling rich web interfaces for AI tools. Alongside Apps SDK, the patterns developed here directly influenced the [MCP Apps](https://github.com/modelcontextprotocol/ext-apps) specification, which standardized UI delivery over the protocol.\n\nThe `@mcp-ui/*` packages implement the MCP Apps standard. `@mcp-ui/client` is the recommended SDK for MCP Apps Hosts.\n\n> *The @mcp-ui/* packages are fully compliant with the MCP Apps specification and ready for production use.*\n\n<p align=\"center\">\n  <video src=\"https://github.com/user-attachments/assets/7180c822-2dd9-4f38-9d3e-b67679509483\"></video>\n</p>\n\n## 💡 What's `mcp-ui`?\n\n`mcp-ui` is an SDK implementing the [MCP Apps](https://github.com/modelcontextprotocol/ext-apps) standard for UI over MCP. It provides:\n\n* **`@mcp-ui/server` (TypeScript)**: Create UI resources with `createUIResource`. Works with `registerAppTool` and `registerAppResource` from `@modelcontextprotocol/ext-apps/server`.\n* **`@mcp-ui/client` (TypeScript)**: Render tool UIs with `AppRenderer` (MCP Apps) or `UIResourceRenderer` (legacy MCP-UI hosts).\n* **`mcp_ui_server` (Ruby)**: Create UI resources in Ruby.\n* **`mcp-ui-server` (Python)**: Create UI resources in Python.\n\nThe MCP Apps pattern links tools to their UIs via `_meta.ui.resourceUri`. Hosts fetch and render the UI alongside tool results.\n\n## ✨ Core Concepts\n\n### MCP Apps Pattern (Recommended)\n\nThe MCP Apps standard links tools to their UIs via `_meta.ui.resourceUri`:\n\n```ts\nimport { registerAppTool, registerAppResource } from '@modelcontextprotocol/ext-apps/server';\nimport { createUIResource } from '@mcp-ui/server';\n\n// 1. Create UI resource\nconst widgetUI = await createUIResource({\n  uri: 'ui://my-server/widget',\n  content: { type: 'rawHtml', htmlString: '<h1>Widget</h1>' },\n  encoding: 'text',\n});\n\n// 2. Register resource handler\nregisterAppResource(server, 'widget_ui', widgetUI.resource.uri, {}, async () => ({\n  contents: [widgetUI.resource]\n}));\n\n// 3. Register tool with _meta linking\nregisterAppTool(server, 'show_widget', {\n  description: 'Show widget',\n  inputSchema: { query: z.string() },\n  _meta: { ui: { resourceUri: widgetUI.resource.uri } }  // Links tool → UI\n}, async ({ query }) => {\n  return { content: [{ type: 'text', text: `Query: ${query}` }] };\n});\n```\n\nHosts detect `_meta.ui.resourceUri`, fetch the UI via `resources/read`, and render it with `AppRenderer`.\n\n### UIResource (Wire Format)\n\nThe underlying payload for UI content:\n\n```ts\ninterface UIResource {\n  type: 'resource';\n  resource: {\n    uri: string;       // e.g., ui://component/id\n    mimeType: 'text/html;profile=mcp-app';\n    text?: string;      // HTML content\n    blob?: string;      // Base64-encoded HTML content\n  };\n}\n```\n\n* **`uri`**: Unique identifier using `ui://` scheme\n* **`mimeType`**: `text/html;profile=mcp-app` — the MCP Apps standard MIME type\n* **`text` vs. `blob`**: Plain text or Base64-encoded content\n\n### Client Components\n\n#### AppRenderer (MCP Apps)\n\nFor MCP Apps hosts, use `AppRenderer` to render tool UIs:\n\n```tsx\nimport { AppRenderer } from '@mcp-ui/client';\n\nfunction ToolUI({ client, toolName, toolInput, toolResult }) {\n  return (\n    <AppRenderer\n      client={client}\n      toolName={toolName}\n      sandbox={{ url: sandboxUrl }}\n      toolInput={toolInput}\n      toolResult={toolResult}\n      onOpenLink={async ({ url }) => window.open(url)}\n      onMessage={async (params) => console.log('Message:', params)}\n    />\n  );\n}\n```\n\nKey props:\n- **`client`**: Optional MCP client for automatic resource fetching\n- **`toolName`**: Tool name to render UI for\n- **`sandbox`**: Sandbox configuration with proxy URL\n- **`toolInput`** / **`toolResult`**: Tool arguments and results\n- **`onOpenLink`** / **`onMessage`**: Handlers for UI requests\n\n#### UIResourceRenderer (Legacy MCP-UI)\n\nFor legacy hosts that embed resources in tool responses:\n\n```tsx\nimport { UIResourceRenderer } from '@mcp-ui/client';\n\n<UIResourceRenderer\n  resource={mcpResource.resource}\n  onUIAction={(action) => console.log('Action:', action)}\n/>\n```\n\nProps:\n- **`resource`**: Resource object with `uri`, `mimeType`, and content (`text`/`blob`)\n- **`onUIAction`**: Callback for handling tool, prompt, link, notify, and intent actions\n\nAlso available as a Web Component:\n```html\n<ui-resource-renderer\n  resource='{ \"mimeType\": \"text/html\", \"text\": \"<h2>Hello!</h2>\" }'\n></ui-resource-renderer>\n```\n\n### Supported Resource Types\n\n#### HTML (`text/html;profile=mcp-app`)\n\nRendered using the internal `<HTMLResourceRenderer />` component, which displays content inside an `<iframe>`. This is suitable for self-contained HTML.\n\n*   **`mimeType`**: `text/html;profile=mcp-app` (MCP Apps standard)\n\n### UI Action\n\nUI snippets must be able to interact with the agent. In `mcp-ui`, this is done by hooking into events sent from the UI snippet and reacting to them in the host (see `onUIAction` prop). For example, an HTML may trigger a tool call when a button is clicked by sending an event which will be caught handled by the client.\n\n\n### Platform Adapters\n\nMCP-UI SDKs includes adapter support for host-specific implementations, enabling your open MCP-UI widgets to work seamlessly regardless of host. Adapters automatically translate between MCP-UI's `postMessage` protocol and host-specific APIs. Over time, as hosts become compatible with the open spec, these adapters wouldn't be needed.\n\n#### Available Adapters\n\n##### Apps SDK Adapter\n\nFor Apps SDK environments (e.g., ChatGPT), this adapter translates MCP-UI protocol to Apps SDK API calls (e.g., `window.openai`).\n\n**How it Works:**\n- Intercepts MCP-UI `postMessage` calls from your widgets\n- Translates them to appropriate Apps SDK API calls\n- Handles bidirectional communication (tools, prompts, state management)\n- Works transparently - your existing MCP-UI code continues to work without changes\n\n**Usage:**\n\n```ts\nimport { createUIResource } from '@mcp-ui/server';\n\nconst htmlResource = await createUIResource({\n  uri: 'ui://greeting/1',\n  content: {\n    type: 'rawHtml',\n    htmlString: `\n      <button onclick=\"window.parent.postMessage({ type: 'tool', payload: { toolName: 'myTool', params: {} } }, '*')\">\n        Call Tool\n      </button>\n    `\n  },\n  encoding: 'text',\n});\n```\n\n\n## 🏗️ Installation\n\n### TypeScript\n\n```bash\n# using npm\nnpm install @mcp-ui/server @mcp-ui/client\n\n# or pnpm\npnpm add @mcp-ui/server @mcp-ui/client\n\n# or yarn\nyarn add @mcp-ui/server @mcp-ui/client\n```\n\n### Ruby\n\n```bash\ngem install mcp_ui_server\n```\n\n### Python\n\n```bash\n# using pip\npip install mcp-ui-server\n\n# or uv\nuv add mcp-ui-server\n```\n\n## 🚀 Getting Started\n\nYou can use [GitMCP](https://gitmcp.io/idosal/mcp-ui) to give your IDE access to `mcp-ui`'s latest documentation!\n\n### TypeScript (MCP Apps Pattern)\n\n1. **Server-side**: Create a tool with UI using `_meta.ui.resourceUri`\n\n   ```ts\n   import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n   import { registerAppTool, registerAppResource } from '@modelcontextprotocol/ext-apps/server';\n   import { createUIResource } from '@mcp-ui/server';\n   import { z } from 'zod';\n\n   const server = new McpServer({ name: 'my-server', version: '1.0.0' });\n\n   // Create UI resource\n   const widgetUI = await createUIResource({\n     uri: 'ui://my-server/widget',\n     content: { type: 'rawHtml', htmlString: '<h1>Interactive Widget</h1>' },\n     encoding: 'text',\n   });\n\n   // Register resource handler\n   registerAppResource(server, 'widget_ui', widgetUI.resource.uri, {}, async () => ({\n     contents: [widgetUI.resource]\n   }));\n\n   // Register tool with _meta linking\n   registerAppTool(server, 'show_widget', {\n     description: 'Show widget',\n     inputSchema: { query: z.string() },\n     _meta: { ui: { resourceUri: widgetUI.resource.uri } }\n   }, async ({ query }) => {\n     return { content: [{ type: 'text', text: `Query: ${query}` }] };\n   });\n   ```\n\n2. **Client-side**: Render tool UIs with `AppRenderer`\n\n   ```tsx\n   import { AppRenderer } from '@mcp-ui/client';\n\n   function ToolUI({ client, toolName, toolInput, toolResult }) {\n     return (\n       <AppRenderer\n         client={client}\n         toolName={toolName}\n         sandbox={{ url: sandboxUrl }}\n         toolInput={toolInput}\n         toolResult={toolResult}\n         onOpenLink={async ({ url }) => window.open(url)}\n         onMessage={async (params) => console.log('Message:', params)}\n       />\n     );\n   }\n   ```\n\n### Legacy MCP-UI Pattern\n\nFor hosts that don't support MCP Apps yet:\n\n   ```tsx\n   import { UIResourceRenderer } from '@mcp-ui/client';\n\n   <UIResourceRenderer\n     resource={mcpResource.resource}\n     onUIAction={(action) => console.log('Action:', action)}\n   />\n   ```\n\n### Python\n\n**Server-side**: Build your UI resources\n\n   ```python\n   from mcp_ui_server import create_ui_resource\n\n   # Inline HTML\n   html_resource = create_ui_resource({\n     \"uri\": \"ui://greeting/1\",\n     \"content\": { \"type\": \"rawHtml\", \"htmlString\": \"<p>Hello, from Python!</p>\" },\n     \"encoding\": \"text\",\n   })\n\n   # External URL\n   external_url_resource = create_ui_resource({\n     \"uri\": \"ui://greeting/2\",\n     \"content\": { \"type\": \"externalUrl\", \"iframeUrl\": \"https://example.com\" },\n     \"encoding\": \"text\",\n   })\n   ```\n\n### Ruby\n\n**Server-side**: Build your UI resources\n\n   ```ruby\n   require 'mcp_ui_server'\n\n   # Inline HTML\n   html_resource = McpUiServer.create_ui_resource(\n     uri: 'ui://greeting/1',\n     content: { type: :raw_html, htmlString: '<p>Hello, from Ruby!</p>' },\n     encoding: :text\n   )\n\n   # External URL\n   external_url_resource = McpUiServer.create_ui_resource(\n     uri: 'ui://greeting/2',\n     content: { type: :external_url, iframeUrl: 'https://example.com' },\n     encoding: :text\n   )\n\n   # remote-dom\n   remote_dom_resource = McpUiServer.create_ui_resource(\n     uri: 'ui://remote-component/action-button',\n     content: {\n       type: :remote_dom,\n       script: \"\n        const button = document.createElement('ui-button');\n        button.setAttribute('label', 'Click me from Ruby!');\n        button.addEventListener('press', () => {\n          window.parent.postMessage({ type: 'tool', payload: { toolName: 'uiInteraction', params: { action: 'button-click', from: 'ruby-remote-dom' } } }, '*');\n        });\n        root.appendChild(button);\n        \",\n       framework: :react,\n     },\n     encoding: :text\n   )\n   ```\n\n## 🚶 Walkthrough\n\nFor a detailed, simple, step-by-step guide on how to integrate `mcp-ui` into your own server, check out the full server walkthroughs on the [mcp-ui documentation site](https://mcpui.dev):\n\n- **[TypeScript Server Walkthrough](https://mcpui.dev/guide/server/typescript/walkthrough)**\n- **[Ruby Server Walkthrough](https://mcpui.dev/guide/server/ruby/walkthrough)**\n- **[Python Server Walkthrough](https://mcpui.dev/guide/server/python/walkthrough)**\n\nThese guides will show you how to add a `mcp-ui` endpoint to an existing server, create tools that return UI resources, and test your setup with the `ui-inspector`!\n\n## 🌍 Examples\n\n**Client Examples**\n* [Goose](https://github.com/block/goose) - open source AI agent that supports `mcp-ui`.\n* [LibreChat](https://github.com/danny-avila/LibreChat) - enhanced ChatGPT clone that supports `mcp-ui`.\n* [ui-inspector](https://github.com/idosal/ui-inspector) - inspect local `mcp-ui`-enabled servers.\n* [MCP-UI Chat](https://github.com/idosal/scira-mcp-ui-chat) - interactive chat built with the `mcp-ui` client. Check out the [hosted version](https://scira-mcp-chat-git-main-idosals-projects.vercel.app/)!\n* MCP-UI RemoteDOM Playground (`examples/remote-dom-demo`) - local demo app to test RemoteDOM resources\n* MCP-UI Web Component Demo (`examples/wc-demo`) - local demo app to test the Web Component integration in hosts\n\n**Server Examples**\n* **TypeScript**: A [full-featured server](examples/server) that is deployed to a hosted environment for easy testing.\n  * **[`typescript-server-demo`](./examples/typescript-server-demo)**: A simple Typescript server that demonstrates how to generate UI resources.\n  * **server**: A [full-featured Typescript server](examples/server) that is deployed to a hosted Cloudflare environment for easy testing.\n    * **HTTP Streaming**: `https://remote-mcp-server-authless.idosalomon.workers.dev/mcp`\n    * **SSE**: `https://remote-mcp-server-authless.idosalomon.workers.dev/sse`\n* **Ruby**: A barebones [demo server](/examples/ruby-server-demo) that shows how to use `mcp_ui_server` and `mcp` gems together.\n* **Python**: A simple [demo server](/examples/python-server-demo) that shows how to use the `mcp-ui-server` Python package.\n* [XMCP](https://github.com/basementstudio/xmcp/tree/main/examples/mcp-ui) - Typescript MCP framework with `mcp-ui` starter example.\n\nDrop those URLs into any MCP-compatible host to see `mcp-ui` in action. For a supported local inspector, see the [ui-inspector](https://github.com/idosal/ui-inspector).\n\n## 💻 Supported Hosts\n\nThe `@mcp-ui/*` packages work with both MCP Apps hosts and legacy MCP-UI hosts.\n\n### MCP Apps Hosts\n\nThese hosts implement the [MCP Apps specification](https://github.com/modelcontextprotocol/ext-apps) and support tools with `_meta.ui.resourceUri`:\n\n| Host | Notes |\n| :--- | :---- |\n| [Claude](https://www.claude.ai/) | ✅ | ✅ |\n| [VSCode](https://github.com/microsoft/vscode/issues/260218) | |\n| [Postman](https://www.postman.com/) | |\n| [Goose](https://block.github.io/goose/) | |\n| [MCPJam](https://www.mcpjam.com/) | |\n| [LibreChat](https://www.librechat.ai/) | |\n| [mcp-use](https://mcp-use.com/) | |\n| [Smithery](https://smithery.ai/playground) | |\n\n### Legacy MCP-UI Hosts\n\nThese hosts expect UI resources embedded directly in tool responses:\n\n| Host | Rendering | UI Actions | Notes |\n| :--- | :-------: | :--------: | :---- |\n| [Nanobot](https://www.nanobot.ai/) | ✅ | ✅ |\n| [MCPJam](https://www.mcpjam.com/) | ✅ | ✅ |\n| [Postman](https://www.postman.com/) | ✅ | ⚠️ | |\n| [Goose](https://block.github.io/goose/) | ✅ | ⚠️ | |\n| [LibreChat](https://www.librechat.ai/) | ✅ | ⚠️ | |\n| [Smithery](https://smithery.ai/playground) | ✅ | ❌ | |\n| [fast-agent](https://fast-agent.ai/mcp/mcp-ui/) | ✅ | ❌ | |\n\n### Hosts Requiring Adapters\n\n| Host | Protocol | Notes |\n| :--- | :------: | :---- |\n| [ChatGPT](https://chatgpt.com/) | Apps SDK | [Guide](https://mcpui.dev/guide/apps-sdk) |\n\n**Legend:** ✅ Supported · ⚠️ Partial · ❌ Not yet supported\n\n## 🔒 Security\nHost and user security is one of `mcp-ui`'s primary concerns. In all content types, the remote code is executed in a sandboxed iframe.\n\n## 🛣️ Roadmap\n\n- [X] Add online playground\n- [X] Expand UI Action API (beyond tool calls)\n- [X] Support Web Components\n- [X] Support Remote-DOM\n- [ ] Add component libraries (in progress)\n- [ ] Add SDKs for additional programming languages (in progress; Ruby, Python available)\n- [ ] Support additional frontend frameworks\n- [ ] Explore providing a UI SDK (in addition to the client and server one)\n- [ ] Add declarative UI content type\n- [ ] Support generative UI?\n      \n## Core Team\n`mcp-ui` is a project by [Ido Salomon](https://x.com/idosal1), in collaboration with [Liad Yosef](https://x.com/liadyosef).\n\n## 🤝 Contributing\n\nContributions, ideas, and bug reports are welcome! See the [contribution guidelines](https://github.com/idosal/mcp-ui/blob/main/.github/CONTRIBUTING.md) to get started.\n\n## 📄 License\n\nApache License 2.0 © [The MCP-UI Authors](LICENSE)\n\n## Disclaimer\n\nThis project is provided \"as is\", without warranty of any kind. The `mcp-ui` authors and contributors shall not be held liable for any damages, losses, or issues arising from the use of this software. Use at your own risk.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\nMCP-UI is committed to maintaining the highest security standards, and actively works with web security experts to improve the spec and implementations. We welcome disclosures, suggestions, and feedback that can improve it.\n\n## Disclosing an Issue\n\nPlease report any issues using this private [form](https://forms.gle/6WbAJU7m2LSxfY6K6). All disclosures will be handled as soon as possible.\n"
  },
  {
    "path": "docs/README.md",
    "content": "# MCP-UI Documentation\n\nThis directory contains the enhanced documentation for MCP UI, built with VitePress and featuring a professional, modern design.\n\n## 🎨 Design Enhancements\n\nThe documentation has been significantly improved with:\n\n### Visual Design\n- **Modern Color Scheme**: Blue to green gradient branding with excellent contrast\n- **Professional Typography**: Inter font family with proper font weights and spacing\n- **Enhanced Shadows**: Subtle depth and visual hierarchy\n- **Smooth Animations**: Fade-in effects and hover transitions\n- **Responsive Design**: Mobile-first approach with breakpoint optimizations\n\n### Content Improvements\n- **Video Integration**: Demo video from README prominently featured on landing page\n- **Rich Feature Cards**: Six detailed feature cards with icons and descriptions\n- **Code Examples**: Syntax-highlighted examples with proper formatting\n- **Call-to-Action Buttons**: Professional buttons with hover effects\n- **Better Navigation**: Improved sidebar with collapsible sections\n\n### Technical Features\n- **Local Search**: Built-in search functionality\n- **Dark Mode**: Full dark mode support with proper color schemes\n- **SEO Optimization**: Meta tags, OpenGraph, and Twitter cards\n- **Performance**: Optimized loading and rendering\n- **Accessibility**: Proper ARIA labels and keyboard navigation\n\n## 🚀 Getting Started\n\nTo run the documentation locally:\n\n```bash\ncd docs\nnpm install\nnpm run dev\n```\n\nTo build for production:\n\n```bash\nnpm run build\n```\n\n## 📁 Structure\n\n```\ndocs/\n├── src/\n│   ├── .vitepress/\n│   │   ├── config.ts          # VitePress configuration\n│   │   └── theme/\n│   │       ├── index.ts       # Custom theme setup\n│   │       └── custom.css     # Enhanced styling\n│   ├── guide/                 # Documentation pages\n│   ├── public/                # Static assets\n│   │   ├── logo.svg          # Brand logo\n│   │   └── favicon.png       # Site favicon\n│   └── index.md              # Enhanced landing page\n└── README.md                 # This file\n```\n\n## 🎯 Key Features\n\n### Landing Page\n- Hero section with gradient text and compelling tagline\n- Demo video integration from the main README\n- Six feature cards highlighting key capabilities\n- Code examples showing quick usage\n- Call-to-action buttons linking to guides and demos\n\n### Navigation\n- Clean, organized sidebar with collapsible sections\n- Breadcrumb navigation\n- \"Edit on GitHub\" links\n- Social media links (GitHub, npm)\n\n### Content\n- Professional typography with proper hierarchy\n- Enhanced code blocks with syntax highlighting\n- Improved tables with hover effects\n- Styled blockquotes and badges\n- Responsive images and media\n\n### Performance\n- Optimized bundle size\n- Fast loading times\n- Efficient caching\n- Mobile-optimized assets\n\n## 🔧 Customization\n\nThe documentation uses CSS custom properties for easy theming:\n\n```css\n:root {\n  --vp-c-brand-1: #3c82f6;      /* Primary brand color */\n  --vp-c-brand-2: #2563eb;      /* Secondary brand color */\n  --vp-c-accent-1: #10b981;     /* Accent color */\n  /* ... more variables */\n}\n```\n\n## 📝 Content Guidelines\n\nWhen adding new content:\n\n1. Use proper markdown headers (##, ###) for structure\n2. Include code examples with language tags\n3. Add screenshots or diagrams where helpful\n4. Keep paragraphs concise and scannable\n5. Use callouts for important information\n\n## 🤝 Contributing\n\nTo contribute to the documentation:\n\n1. Edit files in the `src/` directory\n2. Test locally with `npm run dev`\n3. Build with `npm run build` to verify\n4. Submit a pull request\n\nThe documentation automatically rebuilds on changes to the main branch. "
  },
  {
    "path": "docs/package.json",
    "content": "{\n  \"name\": \"@mcp-ui/docs\",\n  \"version\": \"0.1.0\",\n  \"private\": false,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vitepress dev src\",\n    \"build\": \"vitepress build src\",\n    \"preview\": \"vitepress preview src\"\n  },\n  \"devDependencies\": {\n    \"mermaid\": \"^11.9.0\",\n    \"vitepress\": \"^1.0.0-rc.44\",\n    \"vitepress-plugin-mermaid\": \"^2.0.17\",\n    \"vue\": \"^3.3.0\"\n  }\n}\n"
  },
  {
    "path": "docs/src/.vitepress/config.ts",
    "content": "import { defineConfig } from 'vitepress';\nimport { withMermaid } from 'vitepress-plugin-mermaid';\n\nexport default withMermaid(\n  defineConfig({\n    lang: 'en-US',\n    title: 'MCP-UI',\n    description: 'Interactive UI for MCP - Build rich, dynamic interfaces with MCP-UI',\n    base: '/',\n    cleanUrls: true,\n\n    head: [\n      ['meta', { name: 'theme-color', content: '#3c82f6' }],\n      ['meta', { name: 'og:type', content: 'website' }],\n      ['meta', { name: 'og:locale', content: 'en' }],\n      [\n        'meta',\n        {\n          name: 'og:title',\n          content: 'MCP-UI | Interactive UI for MCP',\n        },\n      ],\n      ['meta', { name: 'og:site_name', content: 'MCP-UI' }],\n      ['meta', { name: 'og:image', content: 'https://mcpui.dev/og-image.png' }],\n      ['meta', { name: 'og:url', content: 'https://mcpui.dev/' }],\n      ['meta', { name: 'twitter:card', content: 'summary_large_image' }],\n      ['meta', { name: 'twitter:site', content: '@idosal1' }],\n      ['meta', { name: 'twitter:url', content: 'https://mcpui.dev/' }],\n      ['meta', { name: 'twitter:domain', content: 'mcpui.dev' }],\n      ['meta', { name: 'twitter:image', content: 'https://mcpui.dev/og-image.png' }],\n      [\n        'meta',\n        {\n          name: 'twitter:description',\n          content: 'Interactive UI for MCP - Build rich, dynamic interfaces with MCP-UI',\n        },\n      ],\n      ['link', { rel: 'icon', type: 'image/png', href: '/logo.png' }],\n      ['link', { rel: 'icon', type: 'image/png', href: '/favicon.png' }],\n      [\n        'style',\n        {},\n        `.VPNavBar .VPNavBarSocialLinks a[href*=\"npmjs.com/package/@mcp-ui/server\"] { border-left: 1px solid var(--vp-c-divider); margin-left: 8px; padding-left: 12px; }`,\n      ],\n    ],\n\n    vite: {\n      plugins: [],\n      optimizeDeps: {\n        include: ['vue', '@vue/shared', 'dayjs', 'mermaid'],\n      },\n    },\n\n    themeConfig: {\n      logo: {\n        light: '/logo-black.png',\n        dark: '/logo.png',\n        alt: 'MCP-UI Logo',\n      },\n\n      nav: [\n        { text: 'Home', link: '/' },\n        { text: 'Guide', link: '/guide/introduction' },\n        { text: 'Team', link: '/team' },\n        {\n          text: 'Examples',\n          items: [\n            {\n              text: 'UI Inspector',\n              link: 'https://github.com/idosal/ui-inspector',\n            },\n            {\n              text: 'Server Examples',\n              items: [\n                {\n                  text: 'TypeScript',\n                  link: '/guide/server/typescript/usage-examples',\n                },\n                { text: 'Ruby', link: '/guide/server/ruby/usage-examples' },\n                { text: 'Python', link: '/guide/server/python/usage-examples' },\n              ],\n            },\n            {\n              text: 'Client Examples',\n              items: [\n                { text: 'Overview', link: '/guide/client/overview' },\n              ],\n            },\n          ],\n        },\n        {\n          text: 'Packages',\n          items: [\n            {\n              text: '@mcp-ui/client',\n              link: 'https://www.npmjs.com/package/@mcp-ui/client',\n            },\n            {\n              text: '@mcp-ui/server',\n              link: 'https://www.npmjs.com/package/@mcp-ui/server',\n            },\n            {\n              text: 'mcp_ui_server Gem',\n              link: 'https://rubygems.org/gems/mcp_ui_server',\n            },\n            {\n              text: 'mcp-ui-server (PyPI)',\n              link: 'https://pypi.org/project/mcp-ui-server/',\n            },\n          ],\n        },\n      ],\n\n      sidebar: {\n        '/guide/': [\n          {\n            text: 'Getting Started',\n            items: [\n              { text: 'Introduction', link: '/guide/introduction' },\n              { text: 'Installation', link: '/guide/getting-started' },\n              { text: 'Core Concepts', link: '/guide/protocol-details' },\n              { text: 'Embeddable UI', link: '/guide/embeddable-ui' },\n              { text: 'Supported Hosts', link: '/guide/supported-hosts' },\n            ],\n          },\n          {\n            text: 'Protocol Integrations',\n            collapsed: false,\n            items: [\n              { text: 'MCP Apps', link: '/guide/mcp-apps' },\n              { text: 'Apps SDK (ChatGPT)', link: '/guide/apps-sdk' },\n            ],\n          },\n          {\n            text: 'Server SDKs',\n            collapsed: false,\n            items: [\n              {\n                text: 'TypeScript',\n                collapsed: false,\n                items: [\n                  {\n                    text: 'Overview',\n                    link: '/guide/server/typescript/overview',\n                  },\n                  {\n                    text: 'Walkthrough',\n                    link: '/guide/server/typescript/walkthrough',\n                  },\n                  {\n                    text: 'Usage & Examples',\n                    link: '/guide/server/typescript/usage-examples',\n                  },\n                ],\n              },\n              {\n                text: 'Ruby',\n                collapsed: false,\n                items: [\n                  { text: 'Overview', link: '/guide/server/ruby/overview' },\n                  {\n                    text: 'Walkthrough',\n                    link: '/guide/server/ruby/walkthrough',\n                  },\n                  {\n                    text: 'Usage & Examples',\n                    link: '/guide/server/ruby/usage-examples',\n                  },\n                ],\n              },\n              {\n                text: 'Python',\n                collapsed: false,\n                items: [\n                  { text: 'Overview', link: '/guide/server/python/overview' },\n                  { text: 'Walkthrough', link: '/guide/server/python/walkthrough' },\n                  { text: 'Usage & Examples', link: '/guide/server/python/usage-examples' },\n                ],\n              },\n            ],\n          },\n          {\n            text: 'Client SDK',\n            collapsed: false,\n            items: [\n              { text: 'Overview', link: '/guide/client/overview' },\n              { text: 'Walkthrough', link: '/guide/client/walkthrough' },\n              {\n                text: 'AppRenderer',\n                link: '/guide/client/app-renderer',\n              },\n            ],\n          },\n        ],\n      },\n\n      editLink: {\n        pattern: 'https://github.com/idosal/mcp-ui/edit/main/docs/src/:path',\n        text: 'Edit this page on GitHub',\n      },\n\n      search: {\n        provider: 'local',\n        options: {\n          locales: {\n            root: {\n              translations: {\n                button: {\n                  buttonText: 'Search',\n                  buttonAriaLabel: 'Search',\n                },\n                modal: {\n                  displayDetails: 'Display detailed list',\n                  resetButtonTitle: 'Reset search',\n                  backButtonTitle: 'Close search',\n                  noResultsText: 'No results for',\n                  footer: {\n                    selectText: 'to select',\n                    navigateText: 'to navigate',\n                    closeText: 'to close',\n                  },\n                },\n              },\n            },\n          },\n        },\n      },\n\n      socialLinks: [\n        { icon: 'github', link: 'https://github.com/idosal/mcp-ui' },\n        { icon: 'discord', link: 'https://discord.gg/CEAG4KW7ZH' },\n        {\n          icon: 'npm',\n          link: 'https://www.npmjs.com/package/@mcp-ui/server',\n        },\n        {\n          icon: {\n            svg: '<svg viewBox=\"0 0 256 293\" xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMinYMin meet\"><g fill=\"currentColor\"><path d=\"M76.748 97.434l-.163-.163-36.11 36.11 87.674 87.512 36.11-35.948 51.564-51.563-36.11-36.11v-.164H76.584l.163.326z\"/><path d=\"M127.823.976L.135 74.173v146.395l127.688 73.197 127.689-73.197V74.173L127.823.976zm103.29 205.603l-103.29 59.534-103.29-59.534V87.837l103.29-59.534 103.29 59.534v118.742z\"/></g></svg>',\n          },\n          link: 'https://rubygems.org/gems/mcp_ui_server',\n        },\n        {\n          icon: 'pypi',\n          link: 'https://pypi.org/project/mcp-ui-server/',\n        },\n      ],\n\n      footer: {\n        message:\n          'Released under the <a href=\"https://github.com/idosal/mcp-ui/blob/main/LICENSE\">Apache 2.0 License</a>.',\n        copyright: 'Copyright © 2025-present <a href=\"https://github.com/idosal\">Ido Salomon</a>',\n      },\n\n      lastUpdated: {\n        text: 'Last updated',\n        formatOptions: {\n          dateStyle: 'short',\n          timeStyle: 'medium',\n        },\n      },\n\n      outline: {\n        level: [2, 3],\n        label: 'On this page',\n      },\n\n      docFooter: {\n        prev: 'Previous page',\n        next: 'Next page',\n      },\n\n      darkModeSwitchLabel: 'Appearance',\n      lightModeSwitchTitle: 'Switch to light theme',\n      darkModeSwitchTitle: 'Switch to dark theme',\n      sidebarMenuLabel: 'Menu',\n      returnToTopLabel: 'Return to top',\n      langMenuLabel: 'Change language',\n\n      externalLinkIcon: true,\n    },\n\n    markdown: {\n      theme: {\n        light: 'github-light',\n        dark: 'github-dark',\n      },\n      lineNumbers: true,\n      config: (md) => {\n        // Add any markdown-it plugins here\n      },\n    },\n\n    sitemap: {\n      hostname: 'https://mcpui.dev',\n    },\n\n    // Mermaid configuration\n    mermaid: {\n      // Refer to https://mermaid.js.org/config/setup/modules/mermaidAPI.html#mermaidapi-configuration-defaults for options\n      theme: 'default',\n    },\n    // Optional plugin configuration\n    mermaidPlugin: {\n      class: 'mermaid', // Set additional CSS classes for parent container\n    },\n  }),\n);\n"
  },
  {
    "path": "docs/src/.vitepress/theme/custom.css",
    "content": "/**\n * Custom styles for MCP UI documentation\n * These styles enhance the default VitePress theme\n */\n\n/* CSS Variables for consistent theming */\n:root {\n  --vp-c-brand-1: #3c82f6;\n  --vp-c-brand-2: #2563eb;\n  --vp-c-brand-3: #1d4ed8;\n  --vp-c-brand-soft: rgba(60, 130, 246, 0.14);\n  --vp-c-brand-softer: rgba(60, 130, 246, 0.08);\n\n  /* Custom accent colors */\n  --vp-c-accent-1: #10b981;\n  --vp-c-accent-2: #059669;\n  --vp-c-accent-soft: rgba(16, 185, 129, 0.14);\n\n  /* Enhanced typography */\n  --vp-font-family-base:\n    'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',\n    'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n  --vp-font-family-mono: 'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;\n\n  /* Custom shadows */\n  --vp-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.04);\n  --vp-shadow-2: 0 3px 12px rgba(0, 0, 0, 0.07);\n  --vp-shadow-3: 0 12px 32px rgba(0, 0, 0, 0.1);\n  --vp-shadow-4: 0 14px 44px rgba(0, 0, 0, 0.12);\n  --vp-shadow-5: 0 18px 56px rgba(0, 0, 0, 0.16);\n\n  /* Custom sidebar spacing */\n  --vp-sidebar-width: 310px;\n  --active-item-left-padding: 8px;\n  --items-left-padding: 18px;\n}\n\n/* Dark mode adjustments */\n.dark {\n  --vp-c-brand-1: #60a5fa;\n  --vp-c-brand-2: #3b82f6;\n  --vp-c-brand-3: #2563eb;\n  --vp-c-brand-soft: rgba(96, 165, 250, 0.16);\n  --vp-c-brand-softer: rgba(96, 165, 250, 0.1);\n\n  --vp-c-accent-1: #34d399;\n  --vp-c-accent-2: #10b981;\n  --vp-c-accent-soft: rgba(52, 211, 153, 0.16);\n}\n\n/* Fix top bar overlap - Enhanced hero section */\n.VPHero {\n  padding-top: 120px !important;\n  padding-bottom: 64px !important;\n}\n\n.VPHome {\n  margin-bottom: 16px !important;\n}\n\n.VPHero .container {\n  margin-top: 0 !important;\n}\n\n.VPHero .name {\n  background: linear-gradient(\n    -45deg,\n    #3b82f6 0%,\n    #10b981 25%,\n    #8b5cf6 50%,\n    #3b82f6 75%,\n    #10b981 100%\n  );\n  background-size: 400% 400%;\n  -webkit-background-clip: text;\n  -webkit-text-fill-color: transparent;\n  background-clip: text;\n  font-weight: 800;\n  letter-spacing: -0.02em;\n  animation: gradientShift 10s ease-in-out infinite;\n}\n\n.VPHero .text {\n  font-weight: 600;\n  letter-spacing: -0.01em;\n}\n\n.VPHero .tagline {\n  font-size: 1.25rem;\n  line-height: 1.6;\n  color: var(--vp-c-text-2);\n  max-width: 600px;\n  margin: 0 auto;\n}\n\n/* Animated gradient keyframes */\n@keyframes gradientShift {\n  0%,\n  100% {\n    background-position: 0% 50%;\n  }\n  50% {\n    background-position: 100% 50%;\n  }\n}\n\n/* Animated gradient text utility class */\n.animated-gradient-text {\n  background: linear-gradient(\n    -45deg,\n    #3b82f6 0%,\n    #10b981 25%,\n    #8b5cf6 50%,\n    #3b82f6 75%,\n    #10b981 100%\n  );\n  background-size: 400% 400%;\n  -webkit-background-clip: text;\n  -webkit-text-fill-color: transparent;\n  background-clip: text;\n  animation: gradientShift 10s ease-in-out infinite;\n}\n\n/* Enhanced feature cards */\n.VPFeatures {\n  padding-top: 48px;\n  padding-bottom: 48px;\n}\n\n.VPFeature {\n  background: var(--vp-c-bg-soft);\n  border: 1px solid var(--vp-c-divider);\n  border-radius: 12px;\n  padding: 28px;\n  height: 100%;\n  transition: all 0.3s ease;\n  position: relative;\n  overflow: hidden;\n}\n\n.VPFeature::before {\n  content: '';\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  height: 3px;\n  background: linear-gradient(90deg, var(--vp-c-brand-1), var(--vp-c-accent-1));\n  opacity: 0;\n  transition: opacity 0.3s ease;\n}\n\n.VPFeature:hover {\n  border-color: var(--vp-c-brand-1);\n  box-shadow: var(--vp-shadow-3);\n  transform: translateY(-2px);\n}\n\n.VPFeature:hover::before {\n  opacity: 1;\n}\n\n/* Fix feature card icons - target the correct VitePress classes */\n.VPFeature .icon,\n.VPFeature .VPImage,\n.VPFeature .box .icon {\n  font-size: 2.5rem !important;\n  line-height: 1 !important;\n  margin-bottom: 16px !important;\n  display: block !important;\n  text-align: center !important;\n  width: 100% !important;\n  height: auto !important;\n}\n\n/* Ensure emoji icons are properly sized */\n.VPFeature .box .icon {\n  font-size: 2.5rem !important;\n  line-height: 1.2 !important;\n  margin: 0 0 16px 0 !important;\n  padding: 0 !important;\n  position: static !important;\n  top: auto !important;\n  left: auto !important;\n  transform: none !important;\n}\n\n.VPFeature .title {\n  font-size: 1.25rem;\n  font-weight: 600;\n  margin-bottom: 12px;\n  color: var(--vp-c-text-1);\n}\n\n.VPFeature .details {\n  color: var(--vp-c-text-2);\n  line-height: 1.6;\n  font-size: 0.95rem;\n}\n\n/* Enhanced code blocks */\n.vp-code-group {\n  margin: 24px 0;\n  border-radius: 12px;\n  overflow: hidden;\n  box-shadow: var(--vp-shadow-2);\n}\n\ndiv[class*='language-'] {\n  border-radius: 8px;\n  margin: 16px 0;\n  overflow: hidden;\n  box-shadow: var(--vp-shadow-2);\n}\n\ndiv[class*='language-'] pre {\n  padding: 20px 24px;\n  background: var(--vp-code-bg);\n  overflow-x: auto;\n}\n\ndiv[class*='language-'] code {\n  font-family: var(--vp-font-family-mono);\n  font-size: 0.9em;\n  line-height: 1.7;\n}\n\n/* Enhanced buttons and links */\n.VPButton {\n  border-radius: 8px;\n  font-weight: 600;\n  transition: all 0.3s ease;\n  box-shadow: var(--vp-shadow-1);\n}\n\n.VPButton:hover {\n  transform: translateY(-1px);\n  box-shadow: var(--vp-shadow-2);\n}\n\n.VPButton.brand {\n  background: linear-gradient(135deg, var(--vp-c-brand-1), var(--vp-c-brand-2));\n  border: none;\n}\n\n.VPButton.brand:hover {\n  background: linear-gradient(135deg, var(--vp-c-brand-2), var(--vp-c-brand-3));\n}\n\n/* Enhanced navigation */\n.VPNavBar {\n  backdrop-filter: blur(12px);\n  background: rgba(255, 255, 255, 0.85);\n  border-bottom: 1px solid var(--vp-c-divider);\n  box-shadow: var(--vp-shadow-1);\n}\n\n.dark .VPNavBar {\n  background: rgba(26, 26, 26, 0.85);\n}\n\n/* Enhanced sidebar */\n.VPSidebar {\n  background: var(--vp-c-bg-soft);\n  border-right: 1px solid var(--vp-c-divider);\n}\n\n.VPSidebarItem[class*='level-']:not(.level-0) .items {\n  padding-left: var(--items-left-padding) !important;\n}\n\n.VPSidebarItem.level-0 > .item > .link {\n  font-weight: 600;\n  color: var(--vp-c-text-1);\n}\n\n.VPSidebarItem.is-active > .item > .indicator {\n  left: calc(calc(var(--items-left-padding) * -1) - 1px);\n}\n\n.VPSidebarItem.is-active > .item > .link {\n  background: var(--vp-c-brand-soft);\n  color: var(--vp-c-brand-1);\n  border-radius: 6px;\n  padding-left: var(--active-item-left-padding);\n  margin-left: calc(var(--active-item-left-padding) * -1);\n}\n\n/* Enhanced content area */\n.vp-doc h1 {\n  font-size: 2.5rem;\n  font-weight: 800;\n  letter-spacing: -0.02em;\n  line-height: 1.2;\n  margin-bottom: 24px;\n  margin-top: 24px;\n  background: linear-gradient(135deg, var(--vp-c-text-1) 0%, var(--vp-c-brand-1) 100%);\n  -webkit-background-clip: text;\n  -webkit-text-fill-color: transparent;\n  background-clip: text;\n}\n\n.vp-doc h2 {\n  font-size: 1.875rem;\n  font-weight: 700;\n  margin-top: 12px;\n  margin-bottom: 16px;\n  padding-bottom: 8px;\n  border-bottom: 2px solid var(--vp-c-divider);\n}\n\n.vp-doc h3 {\n  font-size: 1.5rem;\n  font-weight: 600;\n  margin-top: 32px;\n  margin-bottom: 12px;\n  color: var(--vp-c-brand-1);\n}\n\n.vp-doc p {\n  line-height: 1.7;\n  margin: 16px 0;\n}\n\n.vp-doc a {\n  color: var(--vp-c-brand-1);\n  text-decoration: none;\n  font-weight: 500;\n  transition: all 0.2s ease;\n}\n\n.vp-doc a:hover {\n  color: var(--vp-c-brand-2);\n  text-decoration: underline;\n}\n\n/* Enhanced tables */\n.vp-doc table {\n  border-collapse: collapse;\n  margin: 24px 0;\n  width: 100%;\n  border-radius: 8px;\n  overflow: hidden;\n  box-shadow: var(--vp-shadow-2);\n}\n\n.vp-doc th {\n  background: var(--vp-c-bg-soft);\n  font-weight: 600;\n  padding: 16px;\n  text-align: left;\n  border-bottom: 2px solid var(--vp-c-divider);\n}\n\n.vp-doc td {\n  padding: 12px 16px;\n  border-bottom: 1px solid var(--vp-c-divider-light);\n}\n\n.vp-doc tr:hover {\n  background: var(--vp-c-bg-softer);\n}\n\n/* Enhanced blockquotes */\n.vp-doc blockquote {\n  border-left: 4px solid var(--vp-c-brand-1);\n  background: var(--vp-c-brand-softer);\n  padding: 16px 20px;\n  margin: 24px 0;\n  border-radius: 0 8px 8px 0;\n}\n\n.vp-doc blockquote p {\n  margin: 0;\n  color: var(--vp-c-text-2);\n  font-style: italic;\n}\n\n/* Enhanced badges */\n.vp-doc .badge {\n  display: inline-block;\n  background: var(--vp-c-brand-soft);\n  color: var(--vp-c-brand-1);\n  padding: 4px 8px;\n  border-radius: 4px;\n  font-size: 0.8rem;\n  font-weight: 600;\n  margin-left: 8px;\n}\n\n/* Custom utility classes */\n.text-gradient {\n  background: linear-gradient(135deg, var(--vp-c-brand-1), var(--vp-c-accent-1));\n  -webkit-background-clip: text;\n  -webkit-text-fill-color: transparent;\n  background-clip: text;\n}\n\n.card {\n  background: var(--vp-c-bg-soft);\n  border: 1px solid var(--vp-c-divider);\n  border-radius: 12px;\n  padding: 24px;\n  margin: 16px 0;\n  transition: all 0.3s ease;\n}\n\n.card:hover {\n  border-color: var(--vp-c-brand-1);\n  box-shadow: var(--vp-shadow-2);\n}\n\n/* Responsive improvements */\n@media (max-width: 768px) {\n  .VPHero {\n    padding-top: 60px !important;\n    padding-bottom: 48px !important;\n  }\n\n  .VPHero .name {\n    font-size: 2.5rem;\n  }\n\n  .VPHero .text {\n    font-size: 1.5rem;\n  }\n\n  .VPHero .tagline {\n    font-size: 1.125rem;\n  }\n\n  .vp-doc h1 {\n    font-size: 2rem;\n  }\n\n  .vp-doc h2 {\n    font-size: 1.5rem;\n  }\n\n  .VPFeature {\n    padding: 20px;\n  }\n\n  .VPFeature .icon,\n  .VPFeature .VPImage,\n  .VPFeature .box .icon {\n    font-size: 2rem !important;\n  }\n\n  /* Responsive animated gradient text */\n  .animated-gradient-text {\n    font-size: 2.5rem !important;\n    line-height: 1.1 !important;\n    margin-bottom: 1.5rem !important;\n  }\n}\n\n/* Animation enhancements */\n@keyframes fadeInUp {\n  from {\n    opacity: 0;\n    transform: translateY(20px);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n.VPFeature {\n  animation: fadeInUp 0.6s ease-out;\n}\n\n.VPFeature:nth-child(1) {\n  animation-delay: 0.1s;\n}\n.VPFeature:nth-child(2) {\n  animation-delay: 0.2s;\n}\n.VPFeature:nth-child(3) {\n  animation-delay: 0.3s;\n}\n.VPFeature:nth-child(4) {\n  animation-delay: 0.4s;\n}\n.VPFeature:nth-child(5) {\n  animation-delay: 0.5s;\n}\n.VPFeature:nth-child(6) {\n  animation-delay: 0.6s;\n}\n\n/* Announcement Banner */\n.announcement-banner {\n  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n  color: white;\n  text-align: center;\n  padding: 12px 24px;\n  font-size: 14px;\n  font-weight: 500;\n  line-height: 1.5;\n  position: relative;\n  z-index: 1;\n  margin-bottom: 0;\n  border-radius: 0 0 8px 8px; /* Rounded bottom corners just for style */\n  width: 100%;\n  max-width: 1152px; /* Match typical container width */\n  margin-left: auto;\n  margin-right: auto;\n}\n\n.announcement-banner a:hover {\n  text-decoration: none !important;\n  opacity: 0.9;\n}\n\n/* Adjust VPHero top padding when banner is present (Home page) */\n.VPHome .VPHero {\n  padding-top: 80px !important;\n}\n\n@media (max-width: 960px) {\n  .VPHome .VPHero {\n    padding-top: 64px !important;\n  }\n\n  /* Fix overlap by resetting layout shifts on image */\n  .VPHome .VPHero .image,\n  .VPHome .VPHero .image-container {\n    margin-top: 0 !important;\n    transform: none !important;\n  }\n}\n\n@media (max-width: 768px) {\n  .VPHome .VPHero {\n    padding-top: 32px !important;\n  }\n\n  .announcement-banner {\n    margin-bottom: 0;\n  }\n}\n\n/* Fix hero image overlap on tablet/mobile by removing negative margin */\n@media (max-width: 960px) {\n  .VPHome .VPHero .image-container {\n    margin-top: 0 !important;\n  }\n}\n"
  },
  {
    "path": "docs/src/.vitepress/theme/index.ts",
    "content": "import DefaultTheme from 'vitepress/theme';\nimport { h } from 'vue';\nimport type { Theme } from 'vitepress';\nimport './custom.css';\n\nexport default {\n  extends: DefaultTheme,\n  Layout() {\n    return h(DefaultTheme.Layout, null, {\n      'home-hero-before': () =>\n        h('div', { class: 'announcement-banner' }, [\n          h('span', '🎉 '),\n          h('span', { style: 'font-weight: bold;' }, 'MCP-UI is now standardized into MCP Apps!'),\n          h('span', ' '),\n          h(\n            'a',\n            {\n              href: 'https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1865',\n              style: 'text-decoration: underline; color: inherit;',\n            },\n            'Learn more →',\n          ),\n        ]),\n    });\n  },\n  enhanceApp({ app, router, siteData }) {\n    // Custom app enhancements can go here\n  },\n} satisfies Theme;\n"
  },
  {
    "path": "docs/src/about.md",
    "content": "# About\n\n## About\n\n`mcp-ui` pioneered the concept of interactive UI over the [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP). When MCP was first introduced, tools could only return plain text responses. MCP-UI took the opportunity to transform how users interact with AI tools by enabling rich, dynamic interfaces delivered seamlessly through the protocol.\n\nWhat started as an experimental project to bring web UIs to MCP tools has helped shape the ecosystem. The patterns we developed - embedding HTML resources in tool responses, secure sandboxed rendering, and bidirectional communication between UIs and hosts - directly influenced the creation of the [MCP Apps specification](https://github.com/modelcontextprotocol/ext-apps).\n\nToday, `mcp-ui` implements the MCP Apps standard while continuing to push the boundaries of what's possible with UI over MCP. The `@mcp-ui/*` packages provide production-ready SDKs for both servers and clients, making it easy for developers to build interactive experiences for their MCP tools.\n\n## What's Included\n\n- **Client SDK** with React components for rendering tool UIs securely\n- **Server SDKs** for TypeScript, Python, and Ruby to create UI resources\n- **Documentation and examples** to help you get started quickly\n- **Active community** pushing the MCP Apps standard forward\n\n## Team\n\n`mcp-ui` is a project by [Ido Salomon](https://x.com/idosal1), in collaboration with [Liad Yosef](https://x.com/liadyosef).\n\n## Get Involved\n\n- [GitHub Repository](https://github.com/idosal/mcp-ui)\n- [Discord Community](https://discord.gg/CEAG4KW7ZH)\n"
  },
  {
    "path": "docs/src/guide/apps-sdk.md",
    "content": "# OpenAI Apps SDK Integration\n\n::: warning ChatGPT-Specific\nThis page covers the **OpenAI Apps SDK** adapter for **ChatGPT** integration. This is separate from the **MCP Apps** standard.\n\n- **MCP Apps**: The open standard for tool UIs (`_meta.ui.resourceUri`) - see [Getting Started](./getting-started)\n- **Apps SDK**: ChatGPT's proprietary protocol (`openai/outputTemplate`) - covered on this page\n:::\n\nThe Apps SDK adapter in `@mcp-ui/server` enables your MCP-UI HTML widget to run inside ChatGPT. However, for now, you still need to manually serve the resource according to the Apps SDK spec. This guide walks through the manual flow the adapter expects today to support both MCP-UI hosts and ChatGPT.\n\n## Why two resources?\n\n- **Static template for Apps SDK** – referenced from your tool descriptor via `_meta[\"openai/outputTemplate\"]`. This version must enable the Apps SDK adapter so ChatGPT injects the bridge script and uses the `text/html+skybridge` MIME type.\n- **Embedded resource in tool results** – returned each time your tool runs. This version should *not* enable the adapter so MCP-native hosts continue to receive standard MCP-UI HTML.\n\n## Step-by-step walkthrough\n\n### 1. Register the Apps SDK template\n\nUse `createUIResource` with `adapters.appsSdk.enabled: true` and expose it through the MCP Resources API so both Apps SDK and traditional MCP hosts can fetch it.\n\n```ts\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { createUIResource } from '@mcp-ui/server';\n\nconst server = new McpServer({ name: 'weather-bot', version: '1.0.0' });\nconst TEMPLATE_URI = 'ui://widgets/weather';\n\nconst appsSdkTemplate = await createUIResource({\n  uri: TEMPLATE_URI,\n  encoding: 'text',\n  content: {\n    type: 'rawHtml',\n    htmlString: renderForecastWidget(),\n  },\n  metadata: {\n    'openai/widgetDescription': widget.description,\n    'openai/widgetPrefersBorder': true,\n  },\n});\n\nserver.registerResource(TEMPLATE_URI, async () => appsSdkTemplate.resource);\n```\n\n> **Note:** The adapter switches the MIME type to `text/html+skybridge` and injects the Apps bridge script automatically. The bridge translates MCP-UI primitives to Apps SDK compatible code so no HTML changes are required.\n\n### Add Apps SDK widget metadata\n\nApps SDK surfaces a handful of `_meta` keys on the resource itself (description, CSP, borders, etc.). Provide them via the `metadata` option when you build the template so ChatGPT can present the widget correctly. For example:\n\n```ts\nconst appsSdkTemplate = await createUIResource({\n  uri: TEMPLATE_URI,\n  encoding: 'text',\n  content: {\n    type: 'rawHtml',\n    htmlString: renderForecastWidget(),\n  },\n  metadata: {\n    'openai/widgetDescription': 'Interactive calculator',\n    'openai/widgetCSP': {\n      connect_domains: [],\n      resource_domains: [],\n    },\n    'openai/widgetPrefersBorder': true,\n  },\n});\n```\n\n### 2. Reference the template in your tool descriptor\n\nThe Apps SDK looks for `_meta[\"openai/outputTemplate\"]` to know which resource to render. Mirror the rest of the Apps-specific metadata you need (status text, accessibility hints, security schemes, etc.).\n\n```ts\n// Partial example (see Step 3 for complete example)\nserver.registerTool(\n  'forecast',\n  {\n    title: 'Get the forecast',\n    description: 'Returns a UI that displays the current weather.',\n    inputSchema: {\n      type: 'object',\n      properties: { city: { type: 'string' } },\n      required: ['city'],\n    },\n    _meta: {\n      'openai/outputTemplate': TEMPLATE_URI,\n      'openai/toolInvocation/invoking': 'Fetching forecast…',\n      'openai/toolInvocation/invoked': 'Forecast ready',\n      'openai/widgetAccessible': true,\n    },\n  },\n  async ({ city }) => {\n    const forecast = await fetchForecast(city);\n\n    return {\n      content: [\n        {\n          type: 'text',\n          text: `Forecast prepared for ${city}.`,\n        },\n      ],\n      structuredContent: {\n        forecast,\n      },\n    };\n  },\n);\n```\n\n### 3. Add the MCP-UI embedded resource to the tool response\n\nTo support MCP-UI hosts, also return a standard `createUIResource` result (without the Apps adapter) alongside the Apps SDK payloads.\n\n```ts\nserver.registerTool(\n  'forecast',\n  {\n    title: 'Get the forecast',\n    description: 'Returns a UI that displays the current weather.',\n    inputSchema: {\n      type: 'object',\n      properties: { city: { type: 'string' } },\n      required: ['city'],\n    },\n    _meta: {\n      'openai/outputTemplate': TEMPLATE_URI,\n      'openai/toolInvocation/invoking': 'Fetching forecast…',\n      'openai/toolInvocation/invoked': 'Forecast ready',\n      'openai/widgetAccessible': true,\n    },\n  },\n  async ({ city }) => {\n    const forecast = await fetchForecast(city);\n\n    // MCP-UI embedded UI resource\n    const uiResource = await createUIResource({\n        uri: `ui://widgets/weather/${city}`,\n        encoding: 'text',\n        content: {\n        type: 'rawHtml',\n        htmlString: renderForecastWidget(forecast),\n        },\n    });\n\n    return {\n      content: [\n        {\n          type: 'text',\n          text: `Forecast prepared for ${city}.`,\n        },\n        uiResource\n      ],\n      structuredContent: {\n        forecast,\n      },\n    };\n  },\n);\n```\n\n> **Important:** The MCP-UI resource should **not** enable the Apps SDK adapter. It is for hosts that expect embedded resources. ChatGPT will ignore it and use the template registered in step 1 instead.\n\nFor the complete list of supported metadata fields, refer to the official documentation. [Apps SDK Reference](https://developers.openai.com/apps-sdk/reference)\n\n"
  },
  {
    "path": "docs/src/guide/client/app-renderer.md",
    "content": "# AppRenderer Component\n\n`AppRenderer` is the recommended component for rendering MCP tool UIs in your host application. It implements the [MCP Apps](../mcp-apps) standard, handling the complete lifecycle: resource fetching, sandbox setup, JSON-RPC communication, and tool input/result delivery.\n\nFor lower-level control or when you already have HTML and an `AppBridge` instance, use [`AppFrame`](../mcp-apps#appframe-component) instead.\n\n## Quick Example\n\n```tsx\nimport { AppRenderer, type AppRendererHandle } from '@mcp-ui/client';\n\nfunction ToolUI({ client, toolName, toolInput, toolResult }) {\n  const appRef = useRef<AppRendererHandle>(null);\n\n  return (\n    <AppRenderer\n      ref={appRef}\n      client={client}\n      toolName={toolName}\n      sandbox={{ url: new URL('http://localhost:8765/sandbox_proxy.html') }}\n      toolInput={toolInput}\n      toolResult={toolResult}\n      hostContext={{ theme: 'dark' }}\n      onOpenLink={async ({ url }) => {\n        window.open(url, '_blank');\n        return {};\n      }}\n      onMessage={async (params) => {\n        console.log('Message from tool UI:', params);\n        return {};\n      }}\n      onError={(error) => console.error('Tool UI error:', error)}\n    />\n  );\n}\n```\n\n## Props Reference\n\n### Core Props\n\n| Prop | Type | Description |\n|------|------|-------------|\n| `client` | `Client` | Optional MCP client for automatic resource fetching and MCP request forwarding. Omit to use custom handlers instead. |\n| `toolName` | `string` | Name of the MCP tool to render UI for. |\n| `sandbox` | `SandboxConfig` | Sandbox configuration with the proxy URL and optional CSP. |\n| `html` | `string` | Optional pre-fetched HTML. If provided, skips all resource fetching. |\n| `toolResourceUri` | `string` | Optional pre-fetched resource URI. If not provided, fetched via the client. |\n| `toolInput` | `Record<string, unknown>` | Tool arguments to pass to the guest UI once it initializes. |\n| `toolResult` | `CallToolResult` | Tool execution result to pass to the guest UI. |\n| `toolInputPartial` | `object` | Partial/streaming tool input to send progressively. |\n| `toolCancelled` | `boolean` | Set to `true` to notify the guest UI that tool execution was cancelled. |\n| `hostContext` | `McpUiHostContext` | Host context (theme, locale, viewport, etc.) to pass to the guest UI. |\n\n### Event Handlers\n\n| Prop | Type | Description |\n|------|------|-------------|\n| `onOpenLink` | `(params, extra) => Promise<McpUiOpenLinkResult>` | Handler for open-link requests from the guest UI. |\n| `onMessage` | `(params, extra) => Promise<McpUiMessageResult>` | Handler for message requests from the guest UI. |\n| `onLoggingMessage` | `(params) => void` | Handler for logging messages from the guest UI. |\n| `onSizeChanged` | `(params) => void` | Handler for size change notifications from the guest UI. |\n| `onError` | `(error: Error) => void` | Callback invoked when an error occurs during setup or message handling. |\n| `onFallbackRequest` | `(request, extra) => Promise<Record<string, unknown>>` | Catch-all for JSON-RPC requests not handled by built-in handlers. See [Handling Custom Requests](#handling-custom-requests). |\n\n### MCP Request Handlers\n\nThese override the automatic forwarding to the MCP client when provided:\n\n| Prop | Type | Description |\n|------|------|-------------|\n| `onCallTool` | `(params, extra) => Promise<CallToolResult>` | Handler for `tools/call` requests. |\n| `onListResources` | `(params, extra) => Promise<ListResourcesResult>` | Handler for `resources/list` requests. |\n| `onListResourceTemplates` | `(params, extra) => Promise<ListResourceTemplatesResult>` | Handler for `resources/templates/list` requests. |\n| `onReadResource` | `(params, extra) => Promise<ReadResourceResult>` | Handler for `resources/read` requests. |\n| `onListPrompts` | `(params, extra) => Promise<ListPromptsResult>` | Handler for `prompts/list` requests. |\n\n### Ref Methods\n\nAccess via `useRef<AppRendererHandle>`:\n\n| Method | Description |\n|--------|-------------|\n| `sendToolListChanged()` | Notify guest UI that the server's tool list has changed. |\n| `sendResourceListChanged()` | Notify guest UI that the server's resource list has changed. |\n| `sendPromptListChanged()` | Notify guest UI that the server's prompt list has changed. |\n| `teardownResource()` | Notify the guest UI before unmounting (graceful shutdown). |\n\n## Using Without an MCP Client\n\nYou can use `AppRenderer` without a full MCP client by providing custom handlers:\n\n```tsx\n<AppRenderer\n  // No client - use callbacks instead\n  toolName=\"my-tool\"\n  toolResourceUri=\"ui://my-server/my-tool\"\n  sandbox={{ url: sandboxUrl }}\n  onReadResource={async ({ uri }) => {\n    return myMcpProxy.readResource({ uri });\n  }}\n  onCallTool={async (params) => {\n    return myMcpProxy.callTool(params);\n  }}\n/>\n```\n\nOr provide pre-fetched HTML directly:\n\n```tsx\n<AppRenderer\n  toolName=\"my-tool\"\n  sandbox={{ url: sandboxUrl }}\n  html={preloadedHtml}  // Skip all resource fetching\n  toolInput={args}\n/>\n```\n\n## Handling Custom Requests\n\nAppRenderer includes built-in handlers for standard MCP Apps methods (`tools/call`, `ui/message`, `ui/open-link`, etc.). The `onFallbackRequest` prop lets you handle **any JSON-RPC request that doesn't match a built-in handler**. This is useful for:\n\n- **Experimental methods** -- prototype new capabilities (e.g., `x/clipboard/write`, `x/analytics/track`)\n- **MCP methods not yet in the Apps spec** -- support standard MCP methods like `sampling/createMessage` before they're officially added to MCP Apps\n\nUnder the hood, this is wired to `AppBridge`'s `fallbackRequestHandler` from the MCP SDK `Protocol` class. The guest UI sends a standard JSON-RPC request via `postMessage`, and if AppBridge has no registered handler for the method, it delegates to `onFallbackRequest`.\n\n### Host-side handler\n\n```tsx\nimport { AppRenderer, type JSONRPCRequest } from '@mcp-ui/client';\nimport { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';\n\n<AppRenderer\n  client={client}\n  toolName=\"my-tool\"\n  sandbox={sandboxConfig}\n  onFallbackRequest={async (request, extra) => {\n    switch (request.method) {\n      case 'x/clipboard/write':\n        await navigator.clipboard.writeText(request.params?.text as string);\n        return { success: true };\n      case 'sampling/createMessage':\n        // Forward to MCP server\n        return client.createMessage(request.params);\n      default:\n        throw new McpError(ErrorCode.MethodNotFound, `Unknown method: ${request.method}`);\n    }\n  }}\n/>\n```\n\n### Guest-side (inside tool UI HTML)\n\n```ts\nimport { sendExperimentalRequest } from '@mcp-ui/server';\n\n// Send a custom request to the host -- returns a Promise with the response\nconst result = await sendExperimentalRequest('x/clipboard/write', { text: 'hello' });\n```\n\nThe `sendExperimentalRequest` helper sends a properly formatted JSON-RPC request via `window.parent.postMessage`. The full request/response cycle flows through `PostMessageTransport` and the sandbox proxy, just like built-in methods.\n\n::: tip Method Naming Convention\nUse the `x/<namespace>/<action>` prefix for experimental methods (e.g., `x/clipboard/write`). Standard MCP methods not yet in the Apps spec (e.g., `sampling/createMessage`) should use their canonical method names. When an experimental method proves useful, it can be promoted to a standard method in the [ext-apps spec](https://github.com/modelcontextprotocol/ext-apps).\n:::\n\n## Sandbox Proxy\n\nAppRenderer requires a sandbox proxy HTML file to be served. This provides security isolation for the guest UI by running it inside a double-iframe architecture. The sandbox proxy URL should point to a page that loads the MCP Apps sandbox proxy script.\n\nSee the [Client SDK Walkthrough](./walkthrough#_3-set-up-a-sandbox-proxy) for setup instructions.\n\n## Related\n\n- [Client SDK Walkthrough](./walkthrough) -- Step-by-step guide to building an MCP Apps client\n- [MCP Apps Overview](../mcp-apps) -- Protocol details and server-side setup\n- [Protocol Details](../protocol-details) -- Wire format reference\n- [AppFrame Component](../mcp-apps#appframe-component) -- Lower-level rendering component\n"
  },
  {
    "path": "docs/src/guide/client/overview.md",
    "content": "# @mcp-ui/client Overview\n\nThe `@mcp-ui/client` package provides components for rendering MCP tool UIs in your host application using the MCP Apps standard.\n\n## What's Included?\n\n### MCP Apps Components\n- **`<AppRenderer />`**: High-level component for MCP Apps hosts. Fetches resources, handles lifecycle, renders tool UIs.\n- **`<AppFrame />`**: Lower-level component for when you have pre-fetched HTML and an AppBridge instance.\n- **`AppBridge`**: Handles JSON-RPC communication between host and guest UI.\n\n### Utility Functions\n- **`getResourceMetadata(resource)`**: Extracts the resource's `_meta` content (standard MCP metadata)\n- **`getUIResourceMetadata(resource)`**: Extracts only the MCP-UI specific metadata keys (prefixed with `mcpui.dev/ui-`) from the resource's `_meta` content\n- **`isUIResource()`**: Utility function to check if content is a UI resource\n- **`UI_EXTENSION_CAPABILITIES`**: Declares UI extension support for your MCP client\n\n## Purpose\n- **MCP Apps Compliance**: Implements the MCP Apps standard for UI over MCP\n- **Simplified Rendering**: AppRenderer handles resource fetching, lifecycle, and rendering automatically\n- **Security**: All UIs render in sandboxed iframes\n- **Interactivity**: JSON-RPC communication between host and guest UI\n\n## Quick Example: AppRenderer\n\n```tsx\nimport { AppRenderer } from '@mcp-ui/client';\n\nfunction ToolUI({ client, toolName, toolInput, toolResult }) {\n  return (\n    <AppRenderer\n      client={client}\n      toolName={toolName}\n      sandbox={{ url: sandboxUrl }}\n      toolInput={toolInput}\n      toolResult={toolResult}\n      onOpenLink={async ({ url }) => {\n        if (url.startsWith('https://') || url.startsWith('http://')) {\n          window.open(url);\n        }\n      }}\n      onMessage={async (params) => console.log('Message:', params)}\n    />\n  );\n}\n```\n\n## Building\n\nThis package uses Vite in library mode. It outputs ESM (`.mjs`) and UMD (`.js`) formats, plus TypeScript declarations (`.d.ts`). `react` is externalized.\n\nTo build just this package from the monorepo root:\n\n```bash\npnpm build --filter @mcp-ui/client\n```\n\n## Utility Functions Reference\n\n### `getResourceMetadata(resource)`\n\nExtracts the standard MCP metadata from a resource's `_meta` property.\n\n```typescript\nimport { getResourceMetadata } from '@mcp-ui/client';\n\nconst resource = {\n  uri: 'ui://example/demo',\n  mimeType: 'text/html',\n  text: '<div>Hello</div>',\n  _meta: {\n    title: 'Demo Component',\n    version: '1.0.0',\n    'mcpui.dev/ui-preferred-frame-size': ['800px', '600px'],\n    'mcpui.dev/ui-initial-render-data': { theme: 'dark' },\n    author: 'Development Team'\n  }\n};\n\nconst metadata = getResourceMetadata(resource);\nconsole.log(metadata);\n// Output: {\n//   title: 'Demo Component',\n//   version: '1.0.0',\n//   'mcpui.dev/ui-preferred-frame-size': ['800px', '600px'],\n//   'mcpui.dev/ui-initial-render-data': { theme: 'dark' },\n//   author: 'Development Team'\n// }\n```\n\n### `getUIResourceMetadata(resource)`\n\nExtracts only the MCP-UI specific metadata keys (those prefixed with `mcpui.dev/ui-`) from a resource's `_meta` property, with the prefixes removed for easier access.\n\n```typescript\nimport { getUIResourceMetadata } from '@mcp-ui/client';\n\nconst resource = {\n  uri: 'ui://example/demo',\n  mimeType: 'text/html',\n  text: '<div>Hello</div>',\n  _meta: {\n    title: 'Demo Component',\n    version: '1.0.0',\n    'mcpui.dev/ui-preferred-frame-size': ['800px', '600px'],\n    'mcpui.dev/ui-initial-render-data': { theme: 'dark' },\n    author: 'Development Team'\n  }\n};\n\nconst uiMetadata = getUIResourceMetadata(resource);\nconsole.log(uiMetadata);\n// Output: {\n//   'preferred-frame-size': ['800px', '600px'],\n//   'initial-render-data': { theme: 'dark' },\n// }\n```\n\n## See More\n\nSee the following pages for more details:\n\n- [Client SDK Walkthrough](./walkthrough.md) - **Step-by-step guide to building an MCP Apps client**\n- [AppRenderer Component](./app-renderer.md) - **Full API reference for the MCP Apps renderer**\n"
  },
  {
    "path": "docs/src/guide/client/walkthrough.md",
    "content": "# Client SDK Walkthrough\n\nThis guide provides a step-by-step walkthrough for building an MCP Apps client that can render tool UIs using the `@mcp-ui/client` package.\n\nFor a complete example, see the [`mcp-apps-demo`](https://github.com/idosal/mcp-ui/tree/main/examples/mcp-apps-demo) (server) and test it with the [ui-inspector](https://github.com/idosal/ui-inspector) (client).\n\n## Prerequisites\n\n- Node.js (v18+)\n- An MCP server with tools that have `_meta.ui.resourceUri` (see [Server Walkthrough](../server/typescript/walkthrough))\n- A React project (this guide uses Vite)\n\n## 1. Set up a React Project\n\nIf you don't have an existing React project, create one with Vite:\n\n```bash\nnpm create vite@latest my-mcp-client -- --template react-ts\ncd my-mcp-client\nnpm install\n```\n\n## 2. Install Dependencies\n\nInstall the MCP SDK, client package, and ext-apps:\n\n```bash\nnpm install @mcp-ui/client @modelcontextprotocol/sdk @modelcontextprotocol/ext-apps\n```\n\n## 3. Set Up a Sandbox Proxy\n\nMCP Apps renders tool UIs in sandboxed iframes for security. You need a sandbox proxy HTML file that will host the guest content. Create `public/sandbox_proxy.html`:\n\n```html\n<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Sandbox Proxy</title>\n  <style>\n    html, body { margin: 0; padding: 0; width: 100%; height: 100%; }\n  </style>\n</head>\n<body>\n  <script>\n    // Sandbox proxy implementation for MCP Apps\n    // This receives HTML content from the host and renders it securely\n\n    // Listen for messages from the host\n    window.addEventListener('message', (event) => {\n      const data = event.data;\n      if (!data || typeof data !== 'object') return;\n\n      // Handle resource ready notification (HTML content to render)\n      if (data.method === 'ui/notifications/sandbox-resource-ready') {\n        const { html } = data.params || {};\n        if (html) {\n          // Replace the entire document with the received HTML\n          document.open();\n          document.write(html);\n          document.close();\n        }\n      }\n    });\n\n    // Signal that the sandbox proxy is ready\n    window.parent.postMessage({\n      method: 'ui/notifications/sandbox-proxy-ready',\n      params: {}\n    }, '*');\n  </script>\n</body>\n</html>\n```\n\n::: tip Production Setup\nFor production, consider implementing Content Security Policy (CSP) headers and additional security measures. See [@modelcontextprotocol/ext-apps](https://github.com/modelcontextprotocol/ext-apps) for more details on secure sandbox proxy implementation.\n:::\n\n## 4. Create an MCP Client\n\nCreate a file `src/mcp-client.ts` to handle the MCP connection:\n\n```typescript\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';\nimport {\n  type ClientCapabilitiesWithExtensions,\n  UI_EXTENSION_CAPABILITIES,\n} from '@mcp-ui/client';\n\nexport async function createMcpClient(serverUrl: string): Promise<Client> {\n  // Create the client with UI extension capabilities\n  const capabilities: ClientCapabilitiesWithExtensions = {\n    roots: { listChanged: true },\n    extensions: UI_EXTENSION_CAPABILITIES,\n  };\n\n  const client = new Client(\n    { name: 'my-mcp-client', version: '1.0.0' },\n    { capabilities }\n  );\n\n  // Connect to the MCP server\n  const transport = new StreamableHTTPClientTransport(new URL(serverUrl));\n  await client.connect(transport);\n\n  console.log('Connected to MCP server');\n  return client;\n}\n```\n\n## 5. Create the Tool UI Component\n\nCreate a component that uses `AppRenderer` to render tool UIs. Create `src/ToolUI.tsx`:\n\n```tsx\nimport { useState, useEffect, useRef } from 'react';\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport { AppRenderer, type AppRendererHandle } from '@mcp-ui/client';\n\ninterface ToolUIProps {\n  client: Client;\n  toolName: string;\n  toolInput?: Record<string, unknown>;\n}\n\nexport function ToolUI({ client, toolName, toolInput }: ToolUIProps) {\n  const [toolResult, setToolResult] = useState<any>(null);\n  const [error, setError] = useState<string | null>(null);\n  const appRef = useRef<AppRendererHandle>(null);\n\n  // Call the tool when input changes\n  useEffect(() => {\n    if (!toolInput) return;\n\n    const callTool = async () => {\n      try {\n        const result = await client.callTool({\n          name: toolName,\n          arguments: toolInput,\n        });\n        setToolResult(result);\n      } catch (err) {\n        setError(err instanceof Error ? err.message : String(err));\n      }\n    };\n\n    callTool();\n  }, [client, toolName, toolInput]);\n\n  // Get the sandbox URL\n  const sandboxUrl = new URL('/sandbox_proxy.html', window.location.origin);\n\n  if (error) {\n    return <div style={{ color: 'red' }}>Error: {error}</div>;\n  }\n\n  return (\n    <div style={{ width: '100%', height: '600px' }}>\n      <AppRenderer\n        ref={appRef}\n        client={client}\n        toolName={toolName}\n        sandbox={{ url: sandboxUrl }}\n        toolInput={toolInput}\n        toolResult={toolResult}\n        onOpenLink={async ({ url }) => {\n          // Handle link requests from the UI\n          window.open(url, '_blank');\n          return { isError: false };\n        }}\n        onMessage={async (params) => {\n          // Handle message requests from the UI (e.g., follow-up prompts)\n          console.log('Message from UI:', params);\n          return { isError: false };\n        }}\n        onSizeChanged={(params) => {\n          // Handle size change notifications\n          console.log('Size changed:', params);\n        }}\n        onError={(error) => {\n          console.error('UI Error:', error);\n          setError(error.message);\n        }}\n      />\n    </div>\n  );\n}\n```\n\n## 6. Create the Main App\n\nUpdate `src/App.tsx` to connect to the MCP server and render tool UIs:\n\n```tsx\nimport { useState, useEffect } from 'react';\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport { createMcpClient } from './mcp-client';\nimport { ToolUI } from './ToolUI';\nimport './App.css';\n\nfunction App() {\n  const [client, setClient] = useState<Client | null>(null);\n  const [tools, setTools] = useState<any[]>([]);\n  const [selectedTool, setSelectedTool] = useState<string | null>(null);\n  const [toolInput, setToolInput] = useState<Record<string, unknown>>({});\n  const [error, setError] = useState<string | null>(null);\n\n  // Connect to MCP server on mount\n  useEffect(() => {\n    const connect = async () => {\n      try {\n        // Replace with your MCP server URL\n        const mcpClient = await createMcpClient('http://localhost:3001/mcp');\n        setClient(mcpClient);\n\n        // List available tools\n        const toolsResult = await mcpClient.listTools({});\n        setTools(toolsResult.tools);\n      } catch (err) {\n        setError(err instanceof Error ? err.message : String(err));\n      }\n    };\n\n    connect();\n  }, []);\n\n  // Filter tools that have UI resources\n  const toolsWithUI = tools.filter((tool) =>\n    tool._meta?.ui?.resourceUri\n  );\n\n  if (error) {\n    return <div style={{ color: 'red', padding: '20px' }}>Error: {error}</div>;\n  }\n\n  if (!client) {\n    return <div style={{ padding: '20px' }}>Connecting to MCP server...</div>;\n  }\n\n  return (\n    <div style={{ padding: '20px' }}>\n      <h1>MCP Apps Client Demo</h1>\n\n      <div style={{ marginBottom: '20px' }}>\n        <h2>Available Tools with UI</h2>\n        {toolsWithUI.length === 0 ? (\n          <p>No tools with UI found. Make sure your server has tools with _meta.ui.resourceUri.</p>\n        ) : (\n          <ul>\n            {toolsWithUI.map((tool) => (\n              <li key={tool.name}>\n                <button\n                  onClick={() => {\n                    setSelectedTool(tool.name);\n                    setToolInput({ query: 'Hello from client!' });\n                  }}\n                  style={{\n                    fontWeight: selectedTool === tool.name ? 'bold' : 'normal',\n                  }}\n                >\n                  {tool.name}\n                </button>\n                <span style={{ marginLeft: '10px', color: '#666' }}>\n                  {tool.description}\n                </span>\n              </li>\n            ))}\n          </ul>\n        )}\n      </div>\n\n      {selectedTool && client && (\n        <div style={{ border: '1px solid #ccc', padding: '20px', borderRadius: '8px' }}>\n          <h2>Tool UI: {selectedTool}</h2>\n          <ToolUI\n            client={client}\n            toolName={selectedTool}\n            toolInput={toolInput}\n          />\n        </div>\n      )}\n    </div>\n  );\n}\n\nexport default App;\n```\n\n## 7. Run the Application\n\nStart your React development server:\n\n```bash\nnpm run dev\n```\n\nMake sure your MCP server is running (e.g., the `mcp-apps-demo` example on port 3001):\n\n```bash\n# In the mcp-apps-demo directory\nnpm run build && npm start\n```\n\nOpen your browser to `http://localhost:5173` (or the Vite dev server URL). You should see:\n1. A list of tools with UI from the connected MCP server\n2. Click a tool to render its UI in the sandboxed iframe\n3. The UI can send messages back to your client via the `onMessage` callback\n\n## 8. Handle Custom Tool Calls from UI\n\nTool UIs can request to call other tools. Add a custom handler:\n\n```tsx\n<AppRenderer\n  client={client}\n  toolName={selectedTool}\n  sandbox={{ url: sandboxUrl }}\n  // ... other props\n  onCallTool={async (params) => {\n    // Custom handling for tool calls from the UI\n    console.log('UI requested tool call:', params);\n\n    // You can filter, modify, or intercept tool calls here\n    const result = await client.callTool(params);\n    return result;\n  }}\n/>\n```\n\n## 9. Using AppRenderer Without a Client\n\nIf you don't have direct access to an MCP client (e.g., the MCP connection is managed by a backend), you can use callbacks instead:\n\n```tsx\n<AppRenderer\n  toolName=\"my-tool\"\n  toolResourceUri=\"ui://my-server/widget\"\n  sandbox={{ url: sandboxUrl }}\n  onReadResource={async ({ uri }) => {\n    // Fetch the resource from your backend\n    const response = await fetch(`/api/mcp/resources?uri=${encodeURIComponent(uri)}`);\n    return response.json();\n  }}\n  onCallTool={async (params) => {\n    // Proxy tool calls through your backend\n    const response = await fetch('/api/mcp/tools/call', {\n      method: 'POST',\n      body: JSON.stringify(params),\n    });\n    return response.json();\n  }}\n  toolInput={{ query: 'hello' }}\n/>\n```\n\nOr provide pre-fetched HTML directly:\n\n```tsx\n<AppRenderer\n  toolName=\"my-tool\"\n  sandbox={{ url: sandboxUrl }}\n  html={preloadedHtml}  // Skip resource fetching entirely\n  toolInput={{ query: 'hello' }}\n/>\n```\n\n## Next Steps\n\n- [AppRenderer Reference](./app-renderer.md) - Complete API documentation for AppRenderer\n- [Protocol Details](../protocol-details.md) - Understanding the MCP Apps protocol\n- [MCP Apps Overview](../mcp-apps.md) - Protocol details and server-side setup\n- [Supported Hosts](../supported-hosts.md) - See which hosts support MCP Apps\n"
  },
  {
    "path": "docs/src/guide/embeddable-ui.md",
    "content": "# Embeddable UI\n\n::: tip MCP Apps Standard\nThis page documents the **legacy MCP-UI `postMessage` protocol** for embedded iframes. For MCP Apps hosts, see the [MCP Apps JSON-RPC protocol](./protocol-details#mcp-apps-protocol) instead.\n\nFor new apps, we recommend using the MCP Apps pattern with `_meta.ui.resourceUri`. See [Getting Started](./getting-started).\n:::\n\n> This document describes the general communication protocol for any embeddable UIs.\n> This is implemented by the mcp-ui iframe based solution, in the context of UI over MCP.\n\n# Concepts\n\n- Embedded iframes communicate with the parent window via `postMessage`.\n- The parent window can send messages to the iframe.\n- The iframe can send messages to the parent window.\n\n# Communication Protocol\n\n## Message Structure\n\n```typescript\ntype Message = {\n  type: string;\n  messageId?: string; // optional, used for tracking the message\n  payload: Record<string, unknown>;\n};\n```\n\n## Message Types\n\n- [`intent`](#intent) - the user has interacted with the UI and expressed an intent, and the host should act on it\n- [`notify`](#notify) - the iframe already acted upon the user interaction, and is notifying the host to trigger any side effects\n- [`prompt`](#prompt) - the iframe asks the host to run a prompt\n- [`tool`](#tool) - the iframe asks the host to run a tool call\n- [`link`](#link) - the iframe asks the host to navigate to a link\n\n### `intent`\n\n- indicates that the user has interacted with the UI and expressed an intent, and the host should act on it\n- the payload is an object with the following properties:\n  - `intent` - the intent that the user expressed\n  - `params` - the parameters to pass to the intent\n\n**Example:**\n\n```typescript\nwindow.parent.postMessage(\n  {\n    type: \"intent\",\n    payload: {\n      intent: \"create-task\",\n      params: {\n        title: \"Buy groceries\",\n        description: \"Buy groceries for the week\",\n      },\n    },\n  },\n  \"*\"\n);\n```\n\n### `notify`\n\n- indicates that the iframe already acted upon the user interaction, and is notifying the host to trigger any side effects\n- the payload is an object with the following properties:\n  - `message` - the message to notify the host with\n\n**Example:**\n\n```typescript\nwindow.parent.postMessage(\n  {\n    type: \"notify\",\n    payload: {\n      message: \"cart-updated\",\n    },\n  },\n  \"*\"\n);\n```\n\n### `prompt`\n\n- indicates that the iframe asks the host to run a prompt\n- the payload is an object with the following properties:\n  - `prompt` - the prompt to run\n\n**Example:**\n\n```typescript\nwindow.parent.postMessage(\n  {\n    type: \"prompt\",\n    payload: {\n      prompt: \"What is the weather in Tokyo?\",\n    },\n  },\n  \"*\"\n);\n```\n\n### `tool`\n\n- indicates that the iframe asks the host to run a tool call\n- the payload is an object with the following properties:\n  - `toolName` - the name of the tool to run\n  - `params` - the parameters to pass to the tool\n\n**Example:**\n\n```typescript\nwindow.parent.postMessage(\n  {\n    type: \"tool\",\n    payload: {\n      toolName: \"get-weather\",\n      params: {\n        city: \"Tokyo\",\n      },\n    },\n  },\n  \"*\"\n);\n```\n\n### `link`\n\n- indicates that the iframe asks the host to navigate to a link\n- the payload is an object with the following properties:\n  - `url` - the URL to navigate to\n\n**Example:**\n\n```typescript\nwindow.parent.postMessage(\n  {\n    type: \"link\",\n    payload: {\n      url: \"https://www.google.com\",\n    },\n  },\n  \"*\"\n);\n```\n\n## Reserved Message Types (iframe to host)\n\n- [`ui-lifecycle-iframe-ready`](#ui-lifecycle-iframe-ready) - the iframe is ready to receive messages\n- [`ui-size-change`](#ui-size-change) - the iframe's size has changed and the host should adjust the iframe's size\n- [`ui-request-data`](#ui-request-data) - the iframe sends a request to the host to request data\n- [`ui-request-render-data`](#ui-request-render-data) - the iframe requests render data from the host\n\n### `ui-lifecycle-iframe-ready`\n\n- indicates that the iframe is ready to receive messages\n\n**Example:**\nSee [Render Data](#passing-render-data-to-the-iframe)\n\n### `ui-size-change`\n\n- indicates that the iframe's size has changed and the host should adjust the iframe's size\n- the payload is an object with the following properties:\n  - `width` - the new width of the iframe\n  - `height` - the new height of the iframe\n\n**Example:**\n\n```typescript\nconst resizeObserver = new ResizeObserver((entries) => {\n  entries.forEach((entry) => {\n    window.parent.postMessage(\n      {\n        type: \"ui-size-change\",\n        payload: {\n          height: entry.contentRect.height,\n        },\n      },\n      \"*\"\n    );\n  });\n});\n\nresizeObserver.observe(document.documentElement);\n```\n\n### `ui-request-data`\n\n- a message that the iframe sends to the host to request data. The message must include a `messageId` to allow the iframe to track the response.\n- the payload is an object with the following properties:\n  - `requestType` - the type of the request\n  - `params` - the parameters to pass to the request\n\n**Example:**\n\n```typescript\nwindow.parent.postMessage(\n  {\n    type: \"ui-request-data\",\n    messageId: \"123\",\n    payload: {\n      requestType: \"get-payment-methods\",\n      params: {\n        // any params needed for the request\n      },\n    },\n  },\n  \"*\"\n);\n```\n\nSee also [Asynchronous Data Requests with Message IDs](#asynchronous-data-requests-with-message-ids)\n\n### `ui-request-render-data`\n\n- a message that the iframe sends to the host to request render data. The message can optionally include a `messageId` to allow the iframe to track the response.\n- this message has no payload\n- the host responds with a [`ui-lifecycle-iframe-render-data`](#ui-lifecycle-iframe-render-data) message containing the render data\n\n**Example:**\n\n```typescript\nwindow.parent.postMessage(\n  {\n    type: \"ui-request-render-data\",\n    messageId: \"render-data-123\", // optional\n  },\n  \"*\"\n);\n```\n\n## Reserved Message Types (host to iframe)\n\n- [`ui-lifecycle-iframe-render-data`](#ui-lifecycle-iframe-render-data) - the host sends the iframe render data\n- [`ui-message-received`](#ui-message-received) - the host sends the iframe to indicate that the action has been received\n- [`ui-message-response`](#ui-message-response) - the iframe sends the host to indicate that the action has been processed or failed\n\n### `ui-lifecycle-iframe-render-data`\n\n- a message that the host sends to the iframe to pass any relevant render data\n- the payload is an object with the following properties:\n  - `renderData` - the render data to pass to the iframe\n\n**Example:**\nSee [Render Data](#render-data)\n\n### `ui-message-received`\n\n- a message that the host sends to the iframe to indicate that the action has been received. The original messageId is passed back to the host to allow the host to track the action. This is useful for `request-data` messages, but is not limited to this type.\n  **Example:**\n  See [Asynchronous Data Requests with Message IDs](#asynchronous-data-requests-with-message-ids)\n\n### `ui-message-response`\n\n- a message that the iframe sends to the host to indicate that the action has been processed. The original messageId is passed back to the host to allow the host to track the action. This is useful for `request-data` messages, but is not limited to this type.\n- the payload is an object with the following properties:\n  - `response` - the response to the action\n  - `error` - the error, if any, that occurred\n\n**Example:**\nSee [Asynchronous Data Requests with Message IDs](#asynchronous-data-requests-with-message-ids)\n\n## Query Parameters\n\n### `waitForRenderData`\n\n- a query parameter that can be passed to the iframe to indicate that the iframe should wait for the render data to be passed before sending any messages\n- the value of the query parameter is a boolean\n- if the query parameter is present, the iframe will wait for the render data to be passed before sending any messages\n\n**Example:**\nSee [Render Data](#render-data)\n\n# Usage Examples\n\n## Passing Render Data to the Iframe\n\n### In the host:\n\n```typescript\niframeSrc = \"https://my-embeddable-ui.com?waitForRenderData=true\";\niframe = document.createElement(\"iframe\");\niframe.src = iframeSrc; // the iframe will wait for the render data to be passed before rendering\ndocument.body.appendChild(iframe);\n\nwindow.addEventListener(\"message\", (event) => {\n  if (event.data.type === \"ui-lifecycle-iframe-ready\") {\n    iframe.contentWindow.postMessage(\n      {\n        type: \"ui-lifecycle-iframe-render-data\",\n        payload: { renderData: { theme: \"dark\" } },\n      },\n      \"*\"\n    );\n  }\n});\n```\n\n### In the iframe:\n\n```typescript\n// In the iframe's script\nconst urlParams = new URLSearchParams(window.location.search);\nif (urlParams.get(\"waitForRenderData\") === \"true\") {\n  let customRenderData = null;\n\n  // The parent will send this message on load or when we notify it we're ready\n  window.addEventListener(\"message\", (event) => {\n    // Add origin checks for security\n    if (event.data.type === \"ui-lifecycle-iframe-render-data\") {\n      // If the iframe has already received data, we don't need to do anything\n      if (customRenderData) {\n        return;\n      } else {\n        customRenderData = event.data.payload.renderData;\n        // Now you can render the UI with the received data\n        renderUI(customRenderData);\n      }\n    }\n  });\n  // We can let the parent know we're ready to receive data\n  window.parent.postMessage({ type: \"ui-lifecycle-iframe-ready\" }, \"*\");\n} else {\n  // If the iframe doesn't need to wait for data, we can render the default UI immediately\n  renderUI();\n}\n```\n\n### Alternative: Requesting Render Data On-Demand\n\nInstead of relying on the `ui-lifecycle-iframe-ready` lifecycle event, you can explicitly request render data when needed using `ui-request-render-data`:\n\n#### In the iframe:\n\n```typescript\n// Request render data when ready\nasync function requestRenderData() {\n  return new Promise((resolve, reject) => {\n    const messageId = crypto.randomUUID();\n    \n    window.parent.postMessage(\n      { type: \"ui-request-render-data\", messageId },\n      \"*\"\n    );\n\n    function handleMessage(event) {\n      if (event.data?.type !== \"ui-lifecycle-iframe-render-data\") return;\n      if (event.data.messageId !== messageId) return;\n      \n      window.removeEventListener(\"message\", handleMessage);\n      \n      const { renderData, error } = event.data.payload;\n      if (error) return reject(error);\n      return resolve(renderData);\n    }\n\n    window.addEventListener(\"message\", handleMessage);\n  });\n}\n\n// Use it when your iframe is ready\nconst renderData = await requestRenderData();\nrenderUI(renderData);\n```\n\n## Asynchronous Data Requests with Message IDs\n\nActions initiated from the iframe are handled by the host asynchronously (e.g., data requests, tool calls, etc.). It's useful for the iframe to get feedback on the status of the request and its result. This is achieved using a `messageId` to track the request through its lifecycle. Example use cases include fetching additional information, displaying a progress bar in the iframe, signaling success or failure, and more.\n\nThe primary message types are:\n- `ui-request-data`: Sent from the iframe to the host to request some data or action.\n- `ui-message-received`: Sent from the host to the iframe to acknowledge that the request is being processed.\n- `ui-message-response`: Sent from the host to the iframe with the final result (success or error).\n\nWhile this example uses `ui-request-data`, any message from the iframe can include a `messageId` to leverage this asynchronous flow (e.g., `tool`, `intent`).\n\n```mermaid\nsequenceDiagram\n    participant Iframe\n    participant Host\n\n    Iframe->>Host: postMessage({ type: 'ui-request-data', messageId: '123', ... })\n    Note right of Iframe: 1. Iframe initiates request<br>with a unique messageId.\n\n    Host->>Iframe: postMessage({ type: 'ui-message-received', messageId: '123' })\n    Note left of Host: 2. Host acknowledges receipt (optional).\n\n    Note over Host: Process request...\n\n    alt Request succeeds\n        Host->>Iframe: postMessage({ type: 'ui-message-response', messageId: '123', payload: { response: ... } })\n    else Request fails\n        Host->>Iframe: postMessage({ type: 'ui-message-response', messageId: '123', payload: { error: ... } })\n    end\n    Note left of Host: 3. Host sends final response.\n\n    Note right of Iframe: 4. Iframe uses messageId to handle<br>the specific response.\n```\n"
  },
  {
    "path": "docs/src/guide/getting-started.md",
    "content": "# Getting Started\n\nThis guide will help you get started with building MCP Apps using the `@mcp-ui/*` packages.\n\n## Prerequisites\n\n- Node.js (v22.x recommended for the TypeScript SDK)\n- pnpm (v9 or later recommended for the TypeScript SDK)\n- Ruby (v3.x recommended for the Ruby SDK)\n- Python (v3.10+ recommended for the Python SDK)\n\n## Installation\n\n### For TypeScript\n\n```bash\n# Server SDK\nnpm install @mcp-ui/server @modelcontextprotocol/ext-apps\n\n# Client SDK\nnpm install @mcp-ui/client\n```\n\n### For Ruby\n\n```bash\ngem install mcp_ui_server\n```\n\n### For Python\n\n```bash\npip install mcp-ui-server\n```\n\n## Quick Start: MCP Apps Pattern\n\n### Server Side\n\nCreate a tool with an interactive UI using `registerAppTool` and `_meta.ui.resourceUri`:\n\n```typescript\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { registerAppTool, registerAppResource } from '@modelcontextprotocol/ext-apps/server';\nimport { createUIResource } from '@mcp-ui/server';\nimport { z } from 'zod';\n\n// 1. Create your MCP server\nconst server = new McpServer({ name: 'my-server', version: '1.0.0' });\n\n// 2. Create the UI resource with interactive HTML\nconst widgetUI = await createUIResource({\n  uri: 'ui://my-server/widget',\n  content: {\n    type: 'rawHtml',\n    htmlString: `\n      <html>\n        <body>\n          <h1>Interactive Widget</h1>\n          <button onclick=\"sendMessage()\">Send Message</button>\n          <div id=\"status\">Ready</div>\n          <script type=\"module\">\n            import { App } from 'https://esm.sh/@modelcontextprotocol/ext-apps@0.4.1';\n\n            // Initialize the MCP Apps client\n            const app = new App({ name: 'widget', version: '1.0.0' });\n\n            // Listen for tool input\n            app.ontoolinput = (params) => {\n              document.getElementById('status').textContent =\n                'Received: ' + JSON.stringify(params.input);\n            };\n\n            // Send a message to the conversation\n            window.sendMessage = async () => {\n              await app.sendMessage({\n                role: 'user',\n                content: [{ type: 'text', text: 'Tell me more about this widget' }]\n              });\n            };\n\n            // Connect to the host\n            await app.connect();\n          </script>\n        </body>\n      </html>\n    `,\n  },\n  encoding: 'text',\n});\n\n// 3. Register the resource handler\nregisterAppResource(\n  server,\n  'widget_ui',\n  widgetUI.resource.uri,\n  {},\n  async () => ({\n    contents: [widgetUI.resource]\n  })\n);\n\n// 4. Register the tool with _meta.ui.resourceUri\nregisterAppTool(\n  server,\n  'show_widget',\n  {\n    description: 'Show an interactive widget',\n    inputSchema: {\n      query: z.string().describe('User query'),\n    },\n    _meta: {\n      ui: {\n        resourceUri: widgetUI.resource.uri  // Links tool to UI\n      }\n    }\n  },\n  async ({ query }) => {\n    return {\n      content: [{ type: 'text', text: `Processing: ${query}` }]\n    };\n  }\n);\n```\n\n::: tip MCP Apps Protocol\nThe example above uses the [`@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps) `App` class for communication. See [Protocol Details](./protocol-details) for the full JSON-RPC API.\n:::\n\n### Client Side\n\nRender tool UIs with `AppRenderer`:\n\n```tsx\nimport { AppRenderer } from '@mcp-ui/client';\n\nfunction ToolUI({ client, toolName, toolInput, toolResult }) {\n  return (\n    <AppRenderer\n      client={client}\n      toolName={toolName}\n      sandbox={{ url: new URL('/sandbox_proxy.html', window.location.origin) }}\n      toolInput={toolInput}\n      toolResult={toolResult}\n      onOpenLink={async ({ url }) => {\n        // Validate URL scheme before opening\n        if (url.startsWith('https://') || url.startsWith('http://')) {\n          window.open(url);\n        }\n        return { isError: false };\n      }}\n      onMessage={async (params) => {\n        console.log('Message from UI:', params);\n        // Handle the message (e.g., send to AI conversation)\n        return { isError: false };\n      }}\n      onError={(error) => console.error('UI error:', error)}\n    />\n  );\n}\n```\n\n### Using Without an MCP Client\n\nYou can use `AppRenderer` without a full MCP client by providing callbacks:\n\n```tsx\n<AppRenderer\n  toolName=\"show_widget\"\n  toolResourceUri=\"ui://my-server/widget\"\n  sandbox={{ url: sandboxUrl }}\n  onReadResource={async ({ uri }) => {\n    // Fetch resource from your backend\n    return myBackend.readResource({ uri });\n  }}\n  onCallTool={async (params) => {\n    return myBackend.callTool(params);\n  }}\n  toolInput={{ query: 'hello' }}\n/>\n```\n\nOr provide pre-fetched HTML directly:\n\n```tsx\n<AppRenderer\n  toolName=\"show_widget\"\n  sandbox={{ url: sandboxUrl }}\n  html={preloadedHtml}  // Skip resource fetching\n  toolInput={{ query: 'hello' }}\n/>\n```\n\n## Resource Types\n\nMCP Apps supports several UI content types:\n\n### 1. HTML Resources\n\nDirect HTML content rendered in a sandboxed iframe:\n\n```typescript\nconst htmlResource = await createUIResource({\n  uri: 'ui://my-tool/widget',\n  content: { type: 'rawHtml', htmlString: '<h1>Hello World</h1>' },\n  encoding: 'text',\n});\n```\n\n### 2. External URLs\n\nFetch an external page's HTML and serve it as a UI resource. The SDK fetches the URL's contents server-side and injects a `<base>` tag so relative paths (CSS, JS, images) resolve correctly:\n\n```typescript\nconst urlResource = await createUIResource({\n  uri: 'ui://my-tool/external',\n  content: { type: 'externalUrl', iframeUrl: 'https://example.com' },\n  encoding: 'text',\n});\n// The resource now contains the fetched HTML with a <base href=\"https://example.com\"> tag\n```\n\n## Declaring UI Extension Support\n\nWhen creating your MCP client, declare UI extension support:\n\n```typescript\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport {\n  type ClientCapabilitiesWithExtensions,\n  UI_EXTENSION_CAPABILITIES,\n} from '@mcp-ui/client';\n\nconst capabilities: ClientCapabilitiesWithExtensions = {\n  roots: { listChanged: true },\n  extensions: UI_EXTENSION_CAPABILITIES,\n};\n\nconst client = new Client(\n  { name: 'my-app', version: '1.0.0' },\n  { capabilities }\n);\n```\n\n## Building from Source\n\n### Clone and Install\n\n```bash\ngit clone https://github.com/idosal/mcp-ui.git\ncd mcp-ui\npnpm install\n```\n\n### Build All Packages\n\n```bash\npnpm --filter=!@mcp-ui/docs build\n```\n\n### Run Tests\n\n```bash\npnpm test\n```\n\n## Next Steps\n\n- **Server SDKs**: Learn how to create resources with our server-side packages.\n  - [TypeScript SDK Usage & Examples](./server/typescript/usage-examples.md)\n  - [Ruby SDK Usage & Examples](./server/ruby/usage-examples.md)\n  - [Python SDK Usage & Examples](./server/python/usage-examples.md)\n- **Client SDK**: Learn how to render resources.\n  - [Client Overview](./client/overview.md)\n- **Protocol & Components**:\n  - [Protocol Details](./protocol-details.md)\n"
  },
  {
    "path": "docs/src/guide/introduction.md",
    "content": "# Introduction\n\nWelcome to the MCP Apps SDK documentation!\n\nThe `@mcp-ui/*` packages provide tools for building [MCP Apps](https://github.com/modelcontextprotocol/ext-apps) - interactive UI components for Model Context Protocol (MCP) tools. This SDK implements the MCP Apps standard, enabling rich HTML interfaces within AI applications.\n\nYou can use [GitMCP](https://gitmcp.io/idosal/mcp-ui) to give your IDE access to `mcp-ui`'s latest documentation!\n<a href=\"https://gitmcp.io/idosal/mcp-ui\"><img src=\"https://img.shields.io/endpoint?url=https://gitmcp.io/badge/idosal/mcp-ui\" alt=\"MCP Documentation\"></a>\n\n## Background\n\nMCP-UI pioneered the concept of interactive UI over the Model Context Protocol. Before MCP Apps existed as a standard, this project demonstrated how MCP tools could return rich, interactive HTML interfaces instead of plain text, enabling UI components within AI applications.\n\nThe patterns and ideas explored in MCP-UI directly influenced the development of the [MCP Apps specification](https://github.com/modelcontextprotocol/ext-apps), which standardized UI delivery over MCP. Today, the `@mcp-ui/*` packages implement this standard while maintaining the project's original vision: making it simple to build beautiful, interactive experiences for AI tools.\n\n## What are MCP Apps?\n\nMCP Apps is a standard for attaching interactive UIs to MCP tools. When a tool has an associated UI, hosts can render it alongside the tool's results, enabling rich user experiences like forms, charts, and interactive widgets.\n\n### The Core Pattern\n\nThe MCP Apps pattern uses three key concepts:\n\n1. **Tool with `_meta.ui.resourceUri`** - Links a tool to its UI resource\n2. **Resource Handler** - Serves the UI content when the host requests it\n3. **AppRenderer** - Client component that fetches and renders the UI\n\n```typescript\n// 1. Create UI content\nconst widgetUI = await createUIResource({\n  uri: 'ui://my-server/widget',\n  content: { type: 'rawHtml', htmlString: '<h1>Widget</h1>' },\n  encoding: 'text',\n});\n\n// 2. Register resource handler\nregisterAppResource(server, 'widget_ui', widgetUI.resource.uri, {}, async () => ({\n  contents: [widgetUI.resource]\n}));\n\n// 3. Register tool with _meta linking\nregisterAppTool(server, 'show_widget', {\n  description: 'Show interactive widget',\n  inputSchema: { query: z.string() },\n  _meta: { ui: { resourceUri: widgetUI.resource.uri } }  // This links tool → UI\n}, async ({ query }) => {\n  return { content: [{ type: 'text', text: `Result: ${query}` }] };\n});\n```\n\nWhen a host calls `show_widget`, it sees the `_meta.ui.resourceUri` and fetches the UI from that resource URI to render alongside the tool result.\n\n## SDK Packages\n\nThe `@mcp-ui/*` packages provide everything needed to build and render MCP Apps:\n\n### Server SDK (`@mcp-ui/server`)\n- **`createUIResource`**: Creates UI resource objects with HTML content or fetched external URLs\n- Works with `registerAppTool` and `registerAppResource` from `@modelcontextprotocol/ext-apps/server`\n\n### Client SDK (`@mcp-ui/client`)\n- **`AppRenderer`**: High-level component for rendering tool UIs (fetches resources, handles lifecycle)\n- **`AppFrame`**: Lower-level component for when you have pre-fetched HTML\n\n### Additional Language SDKs\n- **`mcp_ui_server`** (Ruby): Helper methods for creating UI resources\n- **`mcp-ui-server`** (Python): Helper methods for creating UI resources\n\n## How It Works\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│                          MCP Host                                │\n│  1. Calls tool                                                  │\n│  2. Sees _meta.ui.resourceUri in tool definition               │\n│  3. Fetches resource via resources/read                        │\n│  4. Renders UI in sandboxed iframe (AppRenderer)               │\n└─────────────────────────────────────────────────────────────────┘\n         │                                    ▲\n         ▼                                    │\n┌─────────────────────────────────────────────────────────────────┐\n│                         MCP Server                               │\n│  - registerAppTool with _meta.ui.resourceUri                    │\n│  - registerAppResource to serve UI content                      │\n│  - createUIResource to build UI payloads                        │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n### Example Flow\n\n**Server (MCP Tool):**\n::: code-group\n\n```typescript [TypeScript]\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { registerAppTool, registerAppResource } from '@modelcontextprotocol/ext-apps/server';\nimport { createUIResource } from '@mcp-ui/server';\nimport { z } from 'zod';\n\nconst server = new McpServer({ name: 'my-server', version: '1.0.0' });\n\nconst dashboardUI = await createUIResource({\n  uri: 'ui://my-tool/dashboard',\n  content: { type: 'rawHtml', htmlString: '<h1>Dashboard</h1>' },\n  encoding: 'text'\n});\n\nregisterAppResource(server, 'dashboard_ui', dashboardUI.resource.uri, {}, async () => ({\n  contents: [dashboardUI.resource]\n}));\n\nregisterAppTool(server, 'show_dashboard', {\n  description: 'Show dashboard',\n  inputSchema: {},\n  _meta: { ui: { resourceUri: dashboardUI.resource.uri } }\n}, async () => {\n  return { content: [{ type: 'text', text: 'Dashboard loaded' }] };\n});\n```\n\n```ruby [Ruby]\nrequire 'mcp_ui_server'\n\nresource = McpUiServer.create_ui_resource(\n  uri: 'ui://my-tool/dashboard',\n  content: { type: :raw_html, htmlString: '<h1>Dashboard</h1>' },\n  encoding: :text\n)\n\n# Return in MCP response\n{ content: [resource] }\n```\n\n:::\n\n**Client (Frontend App):**\n```tsx\nimport { AppRenderer } from '@mcp-ui/client';\n\nfunction ToolUI({ client, toolName, toolInput, toolResult }) {\n  return (\n    <AppRenderer\n      client={client}\n      toolName={toolName}\n      sandbox={{ url: new URL('http://localhost:8765/sandbox_proxy.html') }}\n      toolInput={toolInput}\n      toolResult={toolResult}\n      onOpenLink={async ({ url }) => {\n        if (url.startsWith('https://') || url.startsWith('http://')) {\n          window.open(url);\n        }\n      }}\n      onMessage={async (params) => {\n        console.log('Message from UI:', params);\n        return { isError: false };\n      }}\n    />\n  );\n}\n```\n\n## Key Benefits\n\n- **Standardized**: Implements the MCP Apps specification for consistent behavior across hosts\n- **Secure**: Sandboxed iframe execution prevents malicious code from affecting the host\n- **Interactive**: Two-way communication between UI and host via JSON-RPC\n- **Flexible**: Supports HTML content with the MCP Apps standard MIME type\n\n## Wire Format: UIResource\n\nThe underlying data format for UI content is the `UIResource` object:\n\n```typescript\ninterface UIResource {\n  type: 'resource';\n  resource: {\n    uri: string;       // ui://component/id\n    mimeType: 'text/html;profile=mcp-app';  // MCP Apps standard\n    text?: string;      // Inline HTML content\n    blob?: string;      // Base64-encoded content\n  };\n}\n```\n\nThe MIME type `text/html;profile=mcp-app` is the MCP Apps standard for UI resources.\n\n### Key Field Details:\n\n- **`uri`**: Unique identifier using `ui://` scheme (e.g., `ui://my-tool/widget-01`)\n- **`mimeType`**: `text/html;profile=mcp-app` — MCP Apps-compliant HTML\n- **`text` or `blob`**: The actual content, either as plain text or Base64 encoded\n\n## Next Steps\n\n- [Getting Started](./getting-started.md) - Set up your development environment\n- [Server Walkthroughs](./server/typescript/walkthrough.md) - Step-by-step guides\n- [Client SDK](./client/overview.md) - Learn to render tool UIs with AppRenderer\n- [TypeScript Server SDK](./server/typescript/overview.md) - Create tools with UI\n- [Ruby Server SDK](./server/ruby/overview.md) - Ruby implementation\n- [Protocol Details](./protocol-details.md) - Understand the underlying protocol\n"
  },
  {
    "path": "docs/src/guide/mcp-apps.md",
    "content": "# Legacy MCP-UI Adapter\n\n::: tip For New Apps\n**Building a new app?** Use the MCP Apps patterns directly - see [Getting Started](./getting-started) for the recommended approach with `registerAppTool`, `_meta.ui.resourceUri`, and `AppRenderer`.\n\nThis page is for **migrating existing MCP-UI widgets** to work in MCP Apps hosts.\n:::\n\nThe MCP Apps adapter in `@mcp-ui/server` enables **existing MCP-UI HTML widgets** to run inside MCP Apps-compliant hosts. This is a backward-compatibility layer for apps that were built using the legacy MCP-UI `postMessage` protocol.\n\n## When to Use This Adapter\n\n- **Existing MCP-UI widgets**: You have HTML widgets using the `ui-lifecycle-*` message format\n- **Gradual migration**: You want your existing widgets to work in both legacy MCP-UI hosts and new MCP Apps hosts\n- **Protocol translation**: Your widget uses `postMessage` calls that need to be translated to JSON-RPC\n\n## Overview\n\nThe adapter automatically translates between the MCP-UI `postMessage` protocol and MCP Apps JSON-RPC, allowing your existing widgets to work in MCP Apps hosts without code changes.\n\n## How It Works\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│                        MCP Apps Host                            │\n│  ┌───────────────────────────────────────────────────────────┐  │\n│  │                     Sandbox Iframe                        │  │\n│  │  ┌─────────────────────────────────────────────────────┐  │  │\n│  │  │                  Tool UI Iframe                     │  │  │\n│  │  │  ┌───────────────┐    ┌──────────────────────────┐  │  │  │\n│  │  │  │  MCP-UI       │───▶│  MCP Apps Adapter        │  │  │  │\n│  │  │  │  Widget       │◀───│  (injected script)       │  │  │  │\n│  │  │  └───────────────┘    └──────────────────────────┘  │  │  │\n│  │  │         │                        │                  │  │  │\n│  │  │         │ MCP-UI Protocol        │ JSON-RPC         │  │  │\n│  │  │         ▼                        ▼                  │  │  │\n│  │  │   postMessage              postMessage              │  │  │\n│  │  └─────────────────────────────────────────────────────┘  │  │\n│  └───────────────────────────────────────────────────────────┘  │\n│                              │                                   │\n│                              ▼                                   │\n│                    MCP Apps SEP Protocol                         │\n└─────────────────────────────────────────────────────────────────┘\n```\n\nThe adapter:\n1. Intercepts MCP-UI messages from your widget\n2. Translates them to MCP Apps SEP JSON-RPC format\n3. Sends them to the host via postMessage\n4. Receives host responses and translates them back to MCP-UI format\n\n## Quick Start\n\n### 1. Create a UI Resource with the MCP Apps Adapter\n\n```typescript\nimport { createUIResource } from '@mcp-ui/server';\n\nconst widgetUI = await createUIResource({\n  uri: 'ui://my-server/widget',\n  encoding: 'text',\n  content: {\n    type: 'rawHtml',\n    htmlString: `\n      <html>\n        <body>\n          <div id=\"app\">Loading...</div>\n          <script>\n            // Listen for render data from the adapter\n            window.addEventListener('message', (event) => {\n              if (event.data.type === 'ui-lifecycle-iframe-render-data') {\n                const { toolInput, toolOutput } = event.data.payload.renderData;\n                document.getElementById('app').textContent =\n                  JSON.stringify({ toolInput, toolOutput }, null, 2);\n              }\n            });\n\n            // Signal that the widget is ready\n            window.parent.postMessage({ type: 'ui-lifecycle-iframe-ready' }, '*');\n          </script>\n        </body>\n      </html>\n    `,\n  },\n});\n```\n\n### 2. Register the Resource and Tool\n\n```typescript\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { createUIResource } from '@mcp-ui/server';\nimport { registerAppTool, registerAppResource } from '@modelcontextprotocol/ext-apps/server';\nimport { z } from 'zod';\n\nconst server = new McpServer({ name: 'my-server', version: '1.0.0' });\n\n// Create the UI resource (from step 1)\nconst widgetUI = await createUIResource({\n  uri: 'ui://my-server/widget',\n  // ... (same as above)\n});\n\n// Register the resource so the host can fetch it\nregisterAppResource(\n  server,\n  'widget_ui',           // Resource name\n  widgetUI.resource.uri, // Resource URI\n  {},                    // Resource metadata\n  async () => ({\n    contents: [widgetUI.resource]\n  })\n);\n\n// Register the tool with _meta linking to the UI resource\nregisterAppTool(\n  server,\n  'my_widget',\n  {\n    description: 'An interactive widget',\n    inputSchema: {\n      query: z.string().describe('User query'),\n    },\n    // This tells MCP Apps hosts where to find the UI\n    _meta: {\n      ui: {\n        resourceUri: widgetUI.resource.uri\n      }\n    }\n  },\n  async ({ query }) => {\n    return {\n      content: [{ type: 'text', text: `Processing: ${query}` }],\n    };\n  }\n);\n```\n\nThe key requirement for MCP Apps hosts is that the tool's `_meta.ui.resourceUri` points to the UI resource URI. This tells the host where to fetch the widget HTML.\n\n### 3. Add the MCP-UI Embedded Resource to Tool Responses\n\nTo support **MCP-UI hosts** (which expect embedded resources in tool responses), also return a `createUIResource` result:\n\n```typescript\nregisterAppTool(\n  server,\n  'my_widget',\n  {\n    description: 'An interactive widget',\n    inputSchema: {\n      query: z.string().describe('User query'),\n    },\n    // For MCP Apps hosts - points to the registered resource\n    _meta: {\n      ui: {\n        resourceUri: widgetUI.resource.uri\n      }\n    }\n  },\n  async ({ query }) => {\n    // Create an embedded UI resource for MCP-UI hosts\n    const embeddedResource = await createUIResource({\n      uri: `ui://my-server/widget/${query}`,\n      encoding: 'text',\n      content: {\n        type: 'rawHtml',\n        htmlString: renderWidget(query),  // Your widget HTML\n      },\n    });\n\n    return {\n      content: [\n        { type: 'text', text: `Processing: ${query}` },\n        embeddedResource  // Include for MCP-UI hosts\n      ],\n    };\n  }\n);\n```\n\n> **Important:** The embedded MCP-UI resource should **not** enable the MCP Apps adapter. It is for hosts that expect embedded resources in tool responses. MCP Apps hosts will ignore the embedded resource and instead fetch the UI from the registered resource URI in `_meta`.\n\n## Protocol Translation Reference\n\n### Widget → Host (Outgoing)\n\n| MCP-UI Action | MCP Apps Method | Description |\n|--------------|-----------------|-------------|\n| `tool` | `tools/call` | Call another tool |\n| `prompt` | `ui/message` | Send a follow-up message to the conversation |\n| `link` | `ui/open-link` | Open a URL in a new tab |\n| `notify` | `notifications/message` | Log a message to the host |\n| `intent` | `ui/message` | Send an intent (translated to message) |\n| `ui-size-change` | `ui/notifications/size-changed` | Request widget resize |\n\n### Host → Widget (Incoming)\n\n| MCP Apps Notification | MCP-UI Message | Description |\n|----------------------|----------------|-------------|\n| `ui/notifications/tool-input` | `ui-lifecycle-iframe-render-data` | Complete tool arguments |\n| `ui/notifications/tool-input-partial` | `ui-lifecycle-iframe-render-data` | Streaming partial arguments |\n| `ui/notifications/tool-result` | `ui-lifecycle-iframe-render-data` | Tool execution result |\n| `ui/notifications/host-context-changed` | `ui-lifecycle-iframe-render-data` | Theme, locale, viewport changes |\n| `ui/notifications/size-changed` | `ui-lifecycle-iframe-render-data` | Host informs of size constraints |\n| `ui/notifications/tool-cancelled` | `ui-lifecycle-tool-cancelled` | Tool execution was cancelled |\n| `ui/resource-teardown` | `ui-lifecycle-teardown` | Host notifies UI before teardown |\n\n## Configuration Options\n\n```typescript\ncreateUIResource({\n  // ...\n  adapters: {\n    mcpApps: {\n      enabled: true,\n      config: {\n        // Timeout for async operations (default: 30000ms)\n        timeout: 60000,\n      },\n    },\n  },\n});\n```\n\n## MIME Type\n\nWhen the MCP Apps adapter is enabled, the resource MIME type is automatically set to `text/html;profile=mcp-app`, the MCP Apps equivalent to `text/html`.\n\n## Receiving Data in Your Widget\n\nThe adapter sends data to your widget via the standard MCP-UI `ui-lifecycle-iframe-render-data` message:\n\n```typescript\nwindow.addEventListener('message', (event) => {\n  if (event.data.type === 'ui-lifecycle-iframe-render-data') {\n    const { renderData } = event.data.payload;\n    \n    // Tool input arguments\n    const toolInput = renderData.toolInput;\n    \n    // Tool execution result (if available)\n    const toolOutput = renderData.toolOutput;\n    \n    // Widget state (if supported by host)\n    const widgetState = renderData.widgetState;\n    \n    // Host context\n    const theme = renderData.theme;      // 'light' | 'dark' | 'system'\n    const locale = renderData.locale;    // e.g., 'en-US'\n    const displayMode = renderData.displayMode; // 'inline' | 'fullscreen' | 'pip'\n    const maxHeight = renderData.maxHeight;\n    \n    // Update your UI with the data\n    updateWidget(renderData);\n  }\n});\n```\n\n## Sending Actions from Your Widget\n\nUse standard MCP-UI postMessage calls - the adapter translates them automatically:\n\n```typescript\n// Send a prompt to the conversation\nwindow.parent.postMessage({\n  type: 'prompt',\n  payload: { prompt: 'What is the weather like today?' }\n}, '*');\n\n// Open a link\nwindow.parent.postMessage({\n  type: 'link',\n  payload: { url: 'https://example.com' }\n}, '*');\n\n// Call another tool\nwindow.parent.postMessage({\n  type: 'tool',\n  payload: { \n    toolName: 'get_weather',\n    params: { city: 'San Francisco' }\n  }\n}, '*');\n\n// Send a notification\nwindow.parent.postMessage({\n  type: 'notify',\n  payload: { message: 'Widget loaded successfully' }\n}, '*');\n\n// Request resize\nwindow.parent.postMessage({\n  type: 'ui-size-change',\n  payload: { width: 500, height: 400 }\n}, '*');\n```\n\n## Mutual Exclusivity with Apps SDK Adapter\n\nOnly one adapter can be enabled at a time. The TypeScript types enforce this:\n\n```typescript\n// ✅ Valid: MCP Apps adapter only\nadapters: { mcpApps: { enabled: true } }\n\n// ✅ Valid: Apps SDK adapter only (for ChatGPT)\nadapters: { appsSdk: { enabled: true } }\n\n// ❌ TypeScript error: Cannot enable both\nadapters: { mcpApps: { enabled: true }, appsSdk: { enabled: true } }\n```\n\nIf you need to support both MCP Apps hosts and ChatGPT, create separate resources:\n\n```typescript\n// For MCP Apps hosts\nconst mcpAppsResource = await createUIResource({\n  uri: 'ui://my-server/widget-mcp-apps',\n  content: { type: 'rawHtml', htmlString: widgetHtml },\n});\n\n// For ChatGPT/Apps SDK hosts\nconst appsSdkResource = await createUIResource({\n  uri: 'ui://my-server/widget-apps-sdk',\n  content: { type: 'rawHtml', htmlString: widgetHtml },\n});\n```\n\n## Complete Example\n\nSee the [mcp-apps-demo](https://github.com/idosal/mcp-ui/tree/main/examples/mcp-apps-demo) example for a complete working implementation.\n\n```typescript\nimport express from 'express';\nimport cors from 'cors';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport { createUIResource } from '@mcp-ui/server';\nimport { registerAppTool, registerAppResource } from '@modelcontextprotocol/ext-apps/server';\nimport { z } from 'zod';\n\nconst app = express();\napp.use(cors({ origin: '*', exposedHeaders: ['Mcp-Session-Id'] }));\napp.use(express.json());\n\n// ... (transport setup)\n\nconst server = new McpServer({ name: 'demo', version: '1.0.0' });\n\nconst graphUI = await createUIResource({\n  uri: 'ui://demo/graph',\n  encoding: 'text',\n  content: {\n    type: 'rawHtml',\n    htmlString: `\n      <!DOCTYPE html>\n      <html>\n      <head>\n        <style>\n          body { font-family: system-ui; padding: 20px; }\n          .data { background: #f5f5f5; padding: 10px; border-radius: 8px; }\n        </style>\n      </head>\n      <body>\n        <h1>graph</h1>\n        <div class=\"data\" id=\"data\">Waiting for data...</div>\n        <button onclick=\"sendPrompt()\">Ask Follow-up</button>\n\n        <script>\n          window.addEventListener('message', (e) => {\n            if (e.data.type === 'ui-lifecycle-iframe-render-data') {\n              document.getElementById('data').textContent =\n                JSON.stringify(e.data.payload.renderData, null, 2);\n            }\n          });\n\n          function sendPrompt() {\n            window.parent.postMessage({\n              type: 'prompt',\n              payload: { prompt: 'Tell me more about this data' }\n            }, '*');\n          }\n\n          window.parent.postMessage({ type: 'ui-lifecycle-iframe-ready' }, '*');\n        </script>\n      </body>\n      </html>\n    `,\n  },\n});\n\n// Register the UI resource\nregisterAppResource(\n  server,\n  'graph_ui',\n  graphUI.resource.uri,\n  {},\n  async () => ({\n    contents: [graphUI.resource]\n  })\n);\n\n// Register the tool with _meta linking to the UI resource\nregisterAppTool(\n  server,\n  'show_graph',\n  {\n    description: 'Display an interactive graph',\n    inputSchema: {\n      title: z.string().describe('Graph title'),\n    },\n    // For MCP Apps hosts - points to the registered resource\n    _meta: {\n      ui: {\n        resourceUri: graphUI.resource.uri\n      }\n    }\n  },\n  async ({ title }) => {\n    // Create embedded resource for MCP-UI hosts\n    const embeddedResource = await createUIResource({\n      uri: `ui://demo/graph/${encodeURIComponent(title)}`,\n      encoding: 'text',\n      content: {\n        type: 'rawHtml',\n        htmlString: `<html><body><h1>Graph: ${title}</h1></body></html>`,\n      },\n    });\n\n    return {\n      content: [\n        { type: 'text', text: `Graph: ${title}` },\n        embeddedResource  // Included for MCP-UI hosts\n      ],\n    };\n  }\n);\n\n// ... (server setup)\n```\n\n## Debugging\n\nThe adapter logs debug information to the browser console. Look for messages prefixed with `[MCP Apps Adapter]`:\n\n```\n[MCP Apps Adapter] Initializing adapter...\n[MCP Apps Adapter] Sending ui/initialize request\n[MCP Apps Adapter] Received JSON-RPC message: {...}\n[MCP Apps Adapter] Intercepted MCP-UI message: prompt\n```\n\n## Host-Side Rendering (Client SDK)\n\nThe `@mcp-ui/client` package provides React components for rendering MCP Apps tool UIs in your host application.\n\n### AppRenderer Component\n\n`AppRenderer` is the high-level component that handles the complete lifecycle of rendering an MCP tool's UI:\n\n```tsx\nimport { AppRenderer, type AppRendererHandle } from '@mcp-ui/client';\n\nfunction ToolUI({ client, toolName, toolInput, toolResult }) {\n  const appRef = useRef<AppRendererHandle>(null);\n\n  return (\n    <AppRenderer\n      ref={appRef}\n      client={client}\n      toolName={toolName}\n      sandbox={{ url: new URL('http://localhost:8765/sandbox_proxy.html') }}\n      toolInput={toolInput}\n      toolResult={toolResult}\n      hostContext={{ theme: 'dark' }}\n      onOpenLink={async ({ url }) => {\n        if (url.startsWith('https://') || url.startsWith('http://')) {\n          window.open(url);\n        }\n      }}\n      onMessage={async (params) => {\n        console.log('Message from tool UI:', params);\n        return { isError: false };\n      }}\n      onError={(error) => console.error('Tool UI error:', error)}\n    />\n  );\n}\n```\n\n**Key Props:**\n- `client` - Optional MCP client for automatic resource fetching and MCP request forwarding\n- `toolName` - Name of the tool to render UI for\n- `sandbox` - Sandbox configuration with the sandbox proxy URL\n- `html` - Optional pre-fetched HTML (skips resource fetching)\n- `toolResourceUri` - Optional pre-fetched resource URI\n- `toolInput` / `toolResult` - Tool arguments and results to pass to the UI\n- `hostContext` - Theme, locale, viewport info for the guest UI\n- `onOpenLink` / `onMessage` / `onLoggingMessage` - Handlers for guest UI requests\n- `onFallbackRequest` - Catch-all for JSON-RPC requests not handled by the built-in handlers (see [Handling Custom Requests](#handling-custom-requests-onfallbackrequest))\n\n**Ref Methods:**\n- `sendToolListChanged()` - Notify guest when tools change\n- `sendResourceListChanged()` - Notify guest when resources change\n- `sendPromptListChanged()` - Notify guest when prompts change\n- `teardownResource()` - Clean up before unmounting\n\n### Handling Custom Requests (`onFallbackRequest`)\n\nAppRenderer includes built-in handlers for standard MCP Apps methods (`tools/call`, `ui/message`, `ui/open-link`, etc.). The `onFallbackRequest` prop lets you handle **any JSON-RPC request that doesn't match a built-in handler**. This is useful for:\n\n- **Experimental methods** — prototype new capabilities (e.g., `x/clipboard/write`, `x/analytics/track`)\n- **MCP methods not yet in the Apps spec** — support standard MCP methods like `sampling/createMessage` before they're officially added to MCP Apps\n\nUnder the hood, this is wired to `AppBridge`'s `fallbackRequestHandler` from the MCP SDK `Protocol` class. The guest UI sends a standard JSON-RPC request via `postMessage`, and if AppBridge has no registered handler for the method, it delegates to `onFallbackRequest`.\n\n**Host-side handler:**\n\n```tsx\nimport { AppRenderer, type JSONRPCRequest } from '@mcp-ui/client';\nimport { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';\n\n<AppRenderer\n  client={client}\n  toolName=\"my-tool\"\n  sandbox={sandboxConfig}\n  onFallbackRequest={async (request, extra) => {\n    switch (request.method) {\n      case 'x/clipboard/write':\n        await navigator.clipboard.writeText(request.params?.text as string);\n        return { success: true };\n      case 'sampling/createMessage':\n        // Forward to MCP server\n        return client.createMessage(request.params);\n      default:\n        throw new McpError(ErrorCode.MethodNotFound, `Unknown method: ${request.method}`);\n    }\n  }}\n/>\n```\n\n**Guest-side (inside tool UI HTML):**\n\n```ts\nimport { sendExperimentalRequest } from '@mcp-ui/server';\n\n// Send a custom request to the host — returns a Promise with the response\nconst result = await sendExperimentalRequest('x/clipboard/write', { text: 'hello' });\n```\n\nThe `sendExperimentalRequest` helper sends a properly formatted JSON-RPC request via `window.parent.postMessage`. The full request/response cycle flows through `PostMessageTransport` and the sandbox proxy, just like built-in methods.\n\n::: tip Method Naming Convention\nUse the `x/<namespace>/<action>` prefix for experimental methods (e.g., `x/clipboard/write`). Standard MCP methods not yet in the Apps spec (e.g., `sampling/createMessage`) should use their canonical method names. When an experimental method proves useful, it can be promoted to a standard method in the [ext-apps spec](https://github.com/modelcontextprotocol/ext-apps).\n:::\n\n### Using Without an MCP Client\n\nYou can use `AppRenderer` without a full MCP client by providing custom handlers:\n\n```tsx\n<AppRenderer\n  // No client - use callbacks instead\n  toolName=\"my-tool\"\n  toolResourceUri=\"ui://my-server/my-tool\"\n  sandbox={{ url: sandboxUrl }}\n  onReadResource={async ({ uri }) => {\n    // Proxy to your MCP client in a different context\n    return myMcpProxy.readResource({ uri });\n  }}\n  onCallTool={async (params) => {\n    return myMcpProxy.callTool(params);\n  }}\n/>\n```\n\nOr provide pre-fetched HTML directly:\n\n```tsx\n<AppRenderer\n  toolName=\"my-tool\"\n  sandbox={{ url: sandboxUrl }}\n  html={preloadedHtml}  // Skip all resource fetching\n  toolInput={args}\n/>\n```\n\n### AppFrame Component\n\n`AppFrame` is the lower-level component for when you already have the HTML content and an `AppBridge` instance:\n\n```tsx\nimport { AppFrame, AppBridge } from '@mcp-ui/client';\n\nfunction LowLevelToolUI({ html, client }) {\n  const bridge = useMemo(() => new AppBridge(client, hostInfo, capabilities), [client]);\n\n  return (\n    <AppFrame\n      html={html}\n      sandbox={{ url: sandboxUrl }}\n      appBridge={bridge}\n      toolInput={{ query: 'test' }}\n      onSizeChanged={(size) => console.log('Size changed:', size)}\n    />\n  );\n}\n```\n\n### Sandbox Proxy\n\nBoth components require a sandbox proxy HTML file to be served. This provides security isolation for the guest UI. The sandbox proxy URL should point to a page that loads the MCP Apps sandbox proxy script.\n\n## Declaring UI Extension Support\n\nWhen creating your MCP client, declare UI extension support using the provided type and capabilities:\n\n```typescript\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport {\n  type ClientCapabilitiesWithExtensions,\n  UI_EXTENSION_CAPABILITIES,\n} from '@mcp-ui/client';\n\nconst capabilities: ClientCapabilitiesWithExtensions = {\n  // Standard capabilities\n  roots: { listChanged: true },\n  // UI extension support (SEP-1724 pattern)\n  extensions: UI_EXTENSION_CAPABILITIES,\n};\n\nconst client = new Client(\n  { name: 'my-app', version: '1.0.0' },\n  { capabilities }\n);\n```\n\nThis tells MCP servers that your client can render UI resources with MIME type `text/html;profile=mcp-app`.\n\n> **Note:** This uses the `extensions` field pattern from [SEP-1724](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1724), which is not yet part of the official MCP protocol.\n\n## Related Resources\n\n- [Getting Started](./getting-started) - Recommended patterns for new MCP Apps\n- [MCP Apps SEP Specification](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\n- [@modelcontextprotocol/ext-apps](https://github.com/modelcontextprotocol/ext-apps)\n- [Apps SDK Integration](./apps-sdk.md) - For ChatGPT integration (separate from MCP Apps)\n- [Protocol Details](./protocol-details.md) - MCP-UI wire format reference\n\n"
  },
  {
    "path": "docs/src/guide/protocol-details.md",
    "content": "# Protocol Details\n\nThis section covers the wire protocols for MCP Apps and legacy MCP-UI.\n\n## MCP Apps Protocol\n\nMCP Apps uses JSON-RPC over `postMessage` for communication between host and guest UI.\n\n### Tool → UI Linking\n\nTools declare their associated UI via `_meta.ui.resourceUri`:\n\n```typescript\n// Tool definition\n{\n  name: 'show_widget',\n  description: 'Show an interactive widget',\n  inputSchema: { ... },\n  _meta: {\n    ui: {\n      resourceUri: 'ui://my-server/widget'  // Points to registered resource\n    }\n  }\n}\n```\n\n### Host → Guest Communication\n\nThe host sends JSON-RPC notifications to the guest UI:\n\n| Notification | Description |\n|-------------|-------------|\n| `ui/notifications/tool-input` | Complete tool arguments |\n| `ui/notifications/tool-input-partial` | Streaming partial arguments |\n| `ui/notifications/tool-result` | Tool execution result |\n| `ui/notifications/host-context-changed` | Theme, locale, viewport changes |\n| `ui/notifications/size-changed` | Host informs of size constraints |\n| `ui/notifications/tool-cancelled` | Tool execution was cancelled |\n| `ui/resource-teardown` | Host notifies UI before teardown |\n\n### Guest → Host Communication\n\nThe guest UI sends JSON-RPC requests to the host:\n\n| Method | Description |\n|--------|-------------|\n| `tools/call` | Call another MCP tool |\n| `ui/message` | Send a follow-up message to the conversation |\n| `ui/open-link` | Open a URL in a new tab |\n| `notifications/message` | Log a message to the host |\n| `ui/notifications/size-changed` | Request widget resize |\n\n### MIME Type\n\nMCP Apps resources use `text/html;profile=mcp-app` to indicate MCP Apps compliance.\n\n## UIResource Wire Format\n\n```typescript\nexport interface UIResource {\n  type: 'resource';\n  resource: {\n    uri: string;\n    mimeType: 'text/html;profile=mcp-app';\n    text?: string;\n    blob?: string;\n  };\n}\n```\n\n## URI Schemes\n\n- **`ui://<component-name>/<instance-id>`**\n\n  - **Purpose**: For all UI resources.\n  - **Content**: `text` or `blob` contains HTML content.\n  - **Client Action**: Render in a sandboxed iframe\n  - **Examples**: A custom button, a small form, a data visualization snippet, a fetched external page\n\n## Content encoding: `text` vs. `blob`\n\n- **`text`**: Simple, direct string. Good for smaller, less complex content.\n- **`blob`**: Base64 encoded string.\n  - **Pros**: Handles special characters robustly, can be better for larger payloads, ensures integrity during JSON transport.\n  - **Cons**: Requires Base64 decoding on the client, slightly increases payload size.\n\n## External URL Handling\n\nWhen using `createUIResource` with `content.type: 'externalUrl'`, the behavior depends on the SDK:\n\n- **TypeScript SDK**: Fetches the URL's HTML content server-side, injects a `<base>` tag so relative paths (CSS, JS, images) resolve against the original URL, and returns the resulting HTML as the resource content. It also validates the URL (http/https only, blocks private/localhost addresses) and enforces a timeout and response size limit. The SDK automatically populates `_meta.csp.baseUriDomains` with the external URL's origin, so the host's sandbox iframe can set appropriate CSP headers.\n- **Python and Ruby SDKs**: Store the URL string directly as the resource content without fetching it. The host client is responsible for fetching and rendering the external page.\n\n> **Note:** Not all hosts support `baseUriDomains`. Those that don't will ignore this field, which may cause the `<base>` tag to be blocked by the sandbox CSP.\n>\n> **Security:** The TypeScript SDK's server-side fetch introduces SSRF risk if the URL is derived from untrusted user input. The SDK blocks private IP ranges and localhost by default, but server developers should apply additional validation (e.g., URL allowlists) when the URL originates from user input. DNS rebinding attacks are not mitigated at the SDK level.\n\n## Recommended Client-Side Pattern\n\nClient-side hosts should check for the `ui://` URI scheme to identify MCP-UI resources:\n\n```tsx\nif (\n  mcpResource.type === 'resource' &&\n  mcpResource.resource.uri?.startsWith('ui://')\n) {\n  return <AppRenderer client={client} toolName={toolName} ... />;\n}\n```\n\n## Communication (Client <-> Iframe)\n\nFor `ui://` resources, you can use `window.parent.postMessage` to send data or actions from the iframe back to the host client application. The client application should set up an event listener for `message` events.\n\n### Basic Communication\n\n**Iframe Script Example:**\n\n```html\n<button onclick=\"handleAction()\">Submit Data</button>\n<script>\n  function handleAction() {\n    const data = { action: 'formData', value: 'someValue' };\n    // IMPORTANT: Always specify the targetOrigin for security!\n    // Use '*' only if the parent origin is unknown or variable and security implications are understood.\n    window.parent.postMessage(\n      { type: 'tool', payload: { toolName: 'myCustomTool', params: data } },\n      '*',\n    );\n  }\n</script>\n```\n\n**Client-Side Handler:**\n\n```typescript\nwindow.addEventListener('message', (event) => {\n  // Add origin check for security: if (event.origin !== \"expectedOrigin\") return;\n  if (event.data && event.data.tool) {\n    // Call the onUIAction prop of UIResourceRenderer\n  }\n});\n```\n\n### Asynchronous Communication with Message IDs\n\nFor iframe content that needs to handle asynchronous responses, you can include a `messageId` field in your UI action messages. When the host provides an `onUIAction` callback, the iframe will receive acknowledgment and response messages.\n\n**Message Flow:**\n\n1. **Iframe sends message with `messageId`:**\n   ```javascript\n   window.parent.postMessage({\n     type: 'tool',\n     messageId: 'unique-request-id-123',\n     payload: { toolName: 'myAsyncTool', params: { data: 'some data' } }\n   }, '*');\n   ```\n\n2. **Host responds with acknowledgment:**\n   ```javascript\n   // The iframe receives this message back\n   {\n     type: 'ui-message-received',\n     messageId: 'unique-request-id-123',\n   }\n   ```\n\n3. **When `onUIAction` completes successfully:**\n   ```javascript\n   // The iframe receives the actual response\n   {\n     type: 'ui-message-response',\n     messageId: 'unique-request-id-123',\n     payload: {\n       response: { /* the result from onUIAction */ }\n     }\n   }\n   ```\n\n4. **If `onUIAction` encounters an error:**\n   ```javascript\n   // The iframe receives the error\n   {\n     type: 'ui-message-response',\n     messageId: 'unique-request-id-123',\n     payload: {\n       error: { /* the error object */ }\n     }\n   }\n   ```\n\n**Complete Iframe Example with Async Handling:**\n\n```html\n<button onclick=\"handleAsyncAction()\">Async Action</button>\n<div id=\"status\">Ready</div>\n<div id=\"result\"></div>\n\n<script>\n  let messageCounter = 0;\n  const pendingRequests = new Map();\n\n  function generateMessageId() {\n    return `msg-${Date.now()}-${++messageCounter}`;\n  }\n\n  function handleAsyncAction() {\n    const messageId = generateMessageId();\n    const statusEl = document.getElementById('status');\n    const resultEl = document.getElementById('result');\n    \n    statusEl.textContent = 'Sending request...';\n    \n    // Store the request context\n    pendingRequests.set(messageId, { \n      startTime: Date.now(),\n      action: 'async-tool-call'\n    });\n    \n    // Send the message with messageId\n    window.parent.postMessage({\n      type: 'tool',\n      messageId: messageId,\n      payload: { \n        toolName: 'processData', \n        params: { data: 'example data', timestamp: Date.now() }\n      }\n    }, '*');\n  }\n\n  // Listen for responses from the host\n  window.addEventListener('message', (event) => {\n    const message = event.data;\n    \n    if (!message.messageId || !pendingRequests.has(message.messageId)) {\n      return; // Not for us or unknown request\n    }\n    \n    const statusEl = document.getElementById('status');\n    const resultEl = document.getElementById('result');\n    const request = pendingRequests.get(message.messageId);\n    \n    switch (message.type) {\n      case 'ui-message-received':\n        statusEl.textContent = 'Request acknowledged, processing...';\n        break;\n        \n      case 'ui-message-response':\n        if (message.payload.error) {\n          statusEl.textContent = 'Error occurred!';\n          resultEl.innerHTML = `<div style=\"color: red;\">Error: ${JSON.stringify(message.payload.error)}</div>`;\n          pendingRequests.delete(message.messageId);\n          break;\n        }\n        statusEl.textContent = 'Completed successfully!';\n        resultEl.innerHTML = `<pre>${JSON.stringify(message.payload.response, null, 2)}</pre>`;\n        pendingRequests.delete(message.messageId);\n        break;\n    }\n  });\n</script>\n```\n\n### Message Types\n\nThe following internal message types are available as constants:\n\n- `InternalMessageType.UI_MESSAGE_RECEIVED` (`'ui-message-received'`)\n- `InternalMessageType.UI_MESSAGE_RESPONSE` (`'ui-message-response'`)\n\nThese types are exported from both `@mcp-ui/client` and `@mcp-ui/server` packages.\n\n**Important Notes:**\n\n- **Message ID is optional**: If you don't provide a `messageId`, the iframe will not receive response messages.\n- **Only with `onUIAction`**: Response messages are only sent when the host provides an `onUIAction` callback.\n- **Unique IDs**: Ensure `messageId` values are unique to avoid conflicts between multiple pending requests.\n- **Cleanup**: Always clean up pending request tracking when you receive responses to avoid memory leaks.\n"
  },
  {
    "path": "docs/src/guide/server/python/overview.md",
    "content": "# mcp-ui-server Overview\n\nThe `mcp-ui-server` package provides utilities to generate UI resources (`UIResource`) on your MCP server. It allows you to define UI snippets on the server-side, which can then be seamlessly and securely rendered on the client.\n\n::: tip MCP Apps Compatibility\nThe `create_ui_resource` function creates UIResource objects that work with both MCP Apps hosts (via `_meta.ui.resourceUri`) and legacy MCP-UI hosts.\n\nFor the recommended MCP Apps pattern with TypeScript, see [Getting Started](../../getting-started).\n:::\n\nFor a complete example, see the [`python-server-demo`](https://github.com/idosal/mcp-ui/tree/main/examples/python-server-demo).\n\n## Key Exports\n\n- **`create_ui_resource(options_dict: dict[str, Any]) -> UIResource`**:\n  The primary function for creating UI snippets. It takes a dictionary of options to define the URI, content (direct HTML or external URL), and encoding method (text or blob).\n\n## Purpose\n\n- **Ease of Use**: Simplifies the creation of valid `UIResource` objects.\n- **Validation**: Includes basic validation (e.g., URI prefixes matching content type).\n- **Encoding**: Handles Base64 encoding when `encoding: 'blob'` is specified.\n- **MCP Integration**: Proper integration with the MCP Python SDK using `EmbeddedResource`.\n\n## Installation\n\nInstall the package using pip or your preferred package manager:\n\n```bash\npip install mcp-ui-server\n```\n\nOr with uv:\n\n```bash\nuv add mcp-ui-server\n```\n\n## Building\n\nThis package is built using Python's standard build tools and distributed via PyPI. It includes full type annotations and is compatible with Python 3.10+.\n\nTo build the package from source:\n\n```bash\nuv build\n```\n\nSee the [Server SDK Usage & Examples](./usage-examples.md) page for practical examples."
  },
  {
    "path": "docs/src/guide/server/python/usage-examples.md",
    "content": "# mcp-ui-server Usage & Examples\n\nThis page provides practical examples for using the `mcp-ui-server` package.\n\nFor a complete example, see the [`python-server-demo`](https://github.com/idosal/mcp-ui/tree/main/examples/python-server-demo).\n\n## Basic Setup\n\nFirst, ensure you have `mcp-ui-server` available in your project:\n\n```bash\npip install mcp-ui-server\n```\n\nOr with uv:\n\n```bash\nuv add mcp-ui-server\n```\n\n## Basic Usage\n\nThe core function is `create_ui_resource`.\n\n```python\nfrom mcp_ui_server import create_ui_resource\n\n# Example 1: Direct HTML, delivered as text\nresource1 = create_ui_resource({\n    \"uri\": \"ui://my-component/instance-1\",\n    \"content\": {\n        \"type\": \"rawHtml\", \n        \"htmlString\": \"<p>Hello World</p>\"\n    },\n    \"encoding\": \"text\"\n})\n\nprint(\"Resource 1:\", resource1.model_dump_json(indent=2))\n# Output for Resource 1:\n# {\n#   \"type\": \"resource\",\n#   \"resource\": {\n#     \"uri\": \"ui://my-component/instance-1\",\n#     \"mimeType\": \"text/html;profile=mcp-app\",\n#     \"text\": \"<p>Hello World</p>\"\n#   }\n# }\n\n# Example 2: Direct HTML, delivered as a Base64 blob\nresource2 = create_ui_resource({\n    \"uri\": \"ui://my-component/instance-2\",\n    \"content\": {\n        \"type\": \"rawHtml\", \n        \"htmlString\": \"<h1>Complex HTML</h1>\"\n    },\n    \"encoding\": \"blob\"\n})\n\nprint(\"Resource 2 (blob will be Base64):\", resource2.model_dump_json(indent=2))\n# Output for Resource 2:\n# {\n#   \"type\": \"resource\",\n#   \"resource\": {\n#     \"uri\": \"ui://my-component/instance-2\",\n#     \"mimeType\": \"text/html;profile=mcp-app\",\n#     \"blob\": \"PGgxPkNvbXBsZXggSFRNTDwvaDE+\"\n#   }\n# }\n\n# Example 3: External URL, text encoding\ndashboard_url = \"https://my.analytics.com/dashboard/123\"\nresource3 = create_ui_resource({\n    \"uri\": \"ui://analytics-dashboard/main\",\n    \"content\": {\n        \"type\": \"externalUrl\", \n        \"iframeUrl\": dashboard_url\n    },\n    \"encoding\": \"text\"\n})\n\nprint(\"Resource 3:\", resource3.model_dump_json(indent=2))\n# Output for Resource 3:\n# {\n#   \"type\": \"resource\",\n#   \"resource\": {\n#     \"uri\": \"ui://analytics-dashboard/main\",\n#     \"mimeType\": \"text/html;profile=mcp-app\",\n#     \"text\": \"https://my.analytics.com/dashboard/123\"\n#   }\n# }\n\n# Example 4: External URL, blob encoding (URL is Base64 encoded)\nchart_api_url = \"https://charts.example.com/api?type=pie&data=1,2,3\"\nresource4 = create_ui_resource({\n    \"uri\": \"ui://live-chart/session-xyz\",\n    \"content\": {\n        \"type\": \"externalUrl\", \n        \"iframeUrl\": chart_api_url\n    },\n    \"encoding\": \"blob\"\n})\n\nprint(\"Resource 4 (blob will be Base64 of URL):\", resource4.model_dump_json(indent=2))\n# Output for Resource 4:\n# {\n#   \"type\": \"resource\",\n#   \"resource\": {\n#     \"uri\": \"ui://live-chart/session-xyz\",\n#     \"mimeType\": \"text/html;profile=mcp-app\",\n#     \"blob\": \"aHR0cHM6Ly9jaGFydHMuZXhhbXBsZS5jb20vYXBpP3R5cGU9cGllJmRhdGE9MSwyLDM=\"\n#   }\n# }\n\n# These resource objects would then be included in the 'content' array\n# of a toolResult in an MCP interaction.\n```\n\n## Using with FastMCP\n\nHere's how to use `create_ui_resource` with the FastMCP framework:\n\n```python\nimport argparse\nfrom mcp.server.fastmcp import FastMCP\nfrom mcp_ui_server import create_ui_resource\nfrom mcp_ui_server.core import UIResource\n\n# Create FastMCP instance\nmcp = FastMCP(\"my-server\")\n\n@mcp.tool()\ndef show_dashboard() -> list[UIResource]:\n    \"\"\"Display an analytics dashboard.\"\"\"\n    ui_resource = create_ui_resource({\n        \"uri\": \"ui://dashboard/analytics\",\n        \"content\": {\n            \"type\": \"externalUrl\",\n            \"iframeUrl\": \"https://my.analytics.com/dashboard\"\n        },\n        \"encoding\": \"text\"\n    })\n    return [ui_resource]\n\n@mcp.tool()\ndef show_welcome() -> list[UIResource]:\n    \"\"\"Display a welcome message.\"\"\"\n    ui_resource = create_ui_resource({\n        \"uri\": \"ui://welcome/main\",\n        \"content\": {\n            \"type\": \"rawHtml\",\n            \"htmlString\": \"<h1>Welcome to My MCP Server!</h1><p>How can I help you today?</p>\"\n        },\n        \"encoding\": \"text\"\n    })\n    return [ui_resource]\n\nif __name__ == \"__main__\":\n    mcp.run()\n```\n\n## Error Handling\n\nThe `create_ui_resource` function will raise exceptions if invalid combinations are provided, for example:\n\n- URI not starting with `ui://` for any content type\n- Invalid content type specified\n\n```python\nfrom mcp_ui_server.exceptions import InvalidURIError\n\ntry:\n    create_ui_resource({\n        \"uri\": \"invalid://should-be-ui\",\n        \"content\": {\n            \"type\": \"externalUrl\", \n            \"iframeUrl\": \"https://example.com\"\n        },\n        \"encoding\": \"text\"\n    })\nexcept InvalidURIError as e:\n    print(f\"Caught expected error: {e}\")\n    # URI must start with 'ui://' but got: invalid://should-be-ui\n```\n"
  },
  {
    "path": "docs/src/guide/server/python/walkthrough.md",
    "content": "# Python Server Walkthrough\n\nThis guide provides a step-by-step walkthrough for creating an MCP server with UI resources using `mcp-ui-server` and FastMCP.\n\nFor a complete, runnable example, see the [`python-server-demo`](https://github.com/idosal/mcp-ui/tree/main/examples/python-server-demo).\n\n## 1. Set up Your Python Environment\n\nFirst, create a new Python project and set up your dependencies:\n\n```bash\n# Create a new directory\nmkdir my-mcp-server\ncd my-mcp-server\n\n# Initialize with uv (recommended) or pip\nuv init\n# or: python -m venv venv && source venv/bin/activate\n```\n\n## 2. Install Dependencies\n\nInstall the necessary packages:\n\n```bash\nuv add mcp mcp-ui-server\n```\n\nOr with pip:\n\n```bash\npip install mcp mcp-ui-server\n```\n\nThe `mcp` package provides FastMCP and core MCP functionality, while `mcp-ui-server` includes helpers for creating UI resources.\n\n## 3. Create Your MCP Server\n\nCreate a file called `server.py`:\n\n```python\nimport argparse\nfrom mcp.server.fastmcp import FastMCP\nfrom mcp_ui_server import create_ui_resource\nfrom mcp_ui_server.core import UIResource\n\n# Create FastMCP instance\nmcp = FastMCP(\"my-mcp-server\")\n\n@mcp.tool()\ndef greet() -> list[UIResource]:\n    \"\"\"A simple greeting tool that returns a UI resource.\"\"\"\n    ui_resource = create_ui_resource({\n        \"uri\": \"ui://greeting/simple\",\n        \"content\": {\n            \"type\": \"rawHtml\",\n            \"htmlString\": \"\"\"\n                <div style=\"padding: 20px; text-align: center; font-family: Arial, sans-serif;\">\n                    <h1 style=\"color: #2563eb;\">Hello from Python MCP Server!</h1>\n                    <p>This UI resource was generated server-side using mcp-ui-server.</p>\n                </div>\n            \"\"\"\n        },\n        \"encoding\": \"text\"\n    })\n    return [ui_resource]\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(description=\"My MCP Server\")\n    parser.add_argument(\"--http\", action=\"store_true\", help=\"Use HTTP transport instead of stdio\")\n    parser.add_argument(\"--port\", type=int, default=3000, help=\"Port for HTTP transport (default: 3000)\")\n    args = parser.parse_args()\n\n    if args.http:\n        print(\"🚀 Starting MCP server on HTTP (SSE transport)\")\n        mcp.settings.port = args.port\n        mcp.run(transport=\"sse\")\n    else:\n        print(\"🚀 Starting MCP server with stdio transport\")\n        mcp.run()\n```\n\n## 4. Add More UI Tools\n\nLet's add more sophisticated tools with different types of UI resources:\n\n```python\n@mcp.tool()\ndef show_dashboard() -> list[UIResource]:\n    \"\"\"Display a sample dashboard with metrics.\"\"\"\n    dashboard_html = \"\"\"\n    <div style=\"padding: 20px; font-family: Arial, sans-serif;\">\n        <h1>Server Dashboard</h1>\n        <div style=\"display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-top: 20px;\">\n            <div style=\"background: #f0f9ff; border: 1px solid #0ea5e9; border-radius: 8px; padding: 16px;\">\n                <h3 style=\"margin-top: 0; color: #0369a1;\">Active Connections</h3>\n                <p style=\"font-size: 24px; font-weight: bold; margin: 0; color: #0c4a6e;\">42</p>\n            </div>\n            <div style=\"background: #f0fdf4; border: 1px solid #22c55e; border-radius: 8px; padding: 16px;\">\n                <h3 style=\"margin-top: 0; color: #15803d;\">CPU Usage</h3>\n                <p style=\"font-size: 24px; font-weight: bold; margin: 0; color: #14532d;\">23%</p>\n            </div>\n            <div style=\"background: #fefce8; border: 1px solid #eab308; border-radius: 8px; padding: 16px;\">\n                <h3 style=\"margin-top: 0; color: #a16207;\">Memory Usage</h3>\n                <p style=\"font-size: 24px; font-weight: bold; margin: 0; color: #713f12;\">67%</p>\n            </div>\n        </div>\n    </div>\n    \"\"\"\n    \n    ui_resource = create_ui_resource({\n        \"uri\": \"ui://dashboard/main\",\n        \"content\": {\n            \"type\": \"rawHtml\",\n            \"htmlString\": dashboard_html\n        },\n        \"encoding\": \"text\"\n    })\n    return [ui_resource]\n\n@mcp.tool()\ndef show_external_site() -> list[UIResource]:\n    \"\"\"Display an external website in an iframe.\"\"\"\n    ui_resource = create_ui_resource({\n        \"uri\": \"ui://external/example\",\n        \"content\": {\n            \"type\": \"externalUrl\",\n            \"iframeUrl\": \"https://example.com\"\n        },\n        \"encoding\": \"text\"\n    })\n    return [ui_resource]\n\n@mcp.tool()\ndef show_interactive_demo() -> list[UIResource]:\n    \"\"\"Show an interactive demo with buttons that send intents.\"\"\"\n    interactive_html = \"\"\"\n    <div style=\"padding: 20px; font-family: Arial, sans-serif;\">\n        <h2>Interactive Demo</h2>\n        <p>Click the buttons below to send different types of actions back to the parent:</p>\n        \n        <div style=\"margin: 10px 0;\">\n            <button onclick=\"sendIntent('user_action', {type: 'button_click', id: 'demo'})\" \n                    style=\"background: #2563eb; color: white; padding: 8px 16px; border: none; border-radius: 4px; margin: 5px; cursor: pointer;\">\n                Send Intent\n            </button>\n            <button onclick=\"sendToolCall('get_data', {source: 'ui'})\" \n                    style=\"background: #059669; color: white; padding: 8px 16px; border: none; border-radius: 4px; margin: 5px; cursor: pointer;\">\n                Call Tool\n            </button>\n        </div>\n        \n        <div id=\"status\" style=\"margin-top: 20px; padding: 10px; background: #f3f4f6; border-radius: 4px;\">\n            Ready - click a button to see the action\n        </div>\n    </div>\n    \n    <script>\n        function sendIntent(intent, params) {\n            const status = document.getElementById('status');\n            status.innerHTML = `<strong>Intent sent:</strong> ${intent}<br><strong>Params:</strong> ${JSON.stringify(params)}`;\n            \n            if (window.parent) {\n                window.parent.postMessage({\n                    type: 'intent',\n                    payload: { intent: intent, params: params }\n                }, '*');\n            }\n        }\n        \n        function sendToolCall(toolName, params) {\n            const status = document.getElementById('status');\n            status.innerHTML = `<strong>Tool call:</strong> ${toolName}<br><strong>Params:</strong> ${JSON.stringify(params)}`;\n            \n            if (window.parent) {\n                window.parent.postMessage({\n                    type: 'tool',\n                    payload: { toolName: toolName, params: params }\n                }, '*');\n            }\n        }\n    </script>\n    \"\"\"\n    \n    ui_resource = create_ui_resource({\n        \"uri\": \"ui://demo/interactive\",\n        \"content\": {\n            \"type\": \"rawHtml\",\n            \"htmlString\": interactive_html\n        },\n        \"encoding\": \"text\"\n    })\n    return [ui_resource]\n```\n\n## 5. Complete Server Example\n\nHere's your complete `server.py` file:\n\n```python\nimport argparse\nfrom mcp.server.fastmcp import FastMCP\nfrom mcp_ui_server import create_ui_resource\nfrom mcp_ui_server.core import UIResource\n\n# Create FastMCP instance\nmcp = FastMCP(\"my-mcp-server\")\n\n@mcp.tool()\ndef greet() -> list[UIResource]:\n    \"\"\"A simple greeting tool that returns a UI resource.\"\"\"\n    ui_resource = create_ui_resource({\n        \"uri\": \"ui://greeting/simple\",\n        \"content\": {\n            \"type\": \"rawHtml\",\n            \"htmlString\": \"\"\"\n                <div style=\"padding: 20px; text-align: center; font-family: Arial, sans-serif;\">\n                    <h1 style=\"color: #2563eb;\">Hello from Python MCP Server!</h1>\n                    <p>This UI resource was generated server-side using mcp-ui-server.</p>\n                </div>\n            \"\"\"\n        },\n        \"encoding\": \"text\"\n    })\n    return [ui_resource]\n\n@mcp.tool()\ndef show_dashboard() -> list[UIResource]:\n    \"\"\"Display a sample dashboard with metrics.\"\"\"\n    dashboard_html = \"\"\"\n    <div style=\"padding: 20px; font-family: Arial, sans-serif;\">\n        <h1>Server Dashboard</h1>\n        <div style=\"display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-top: 20px;\">\n            <div style=\"background: #f0f9ff; border: 1px solid #0ea5e9; border-radius: 8px; padding: 16px;\">\n                <h3 style=\"margin-top: 0; color: #0369a1;\">Active Connections</h3>\n                <p style=\"font-size: 24px; font-weight: bold; margin: 0; color: #0c4a6e;\">42</p>\n            </div>\n            <div style=\"background: #f0fdf4; border: 1px solid #22c55e; border-radius: 8px; padding: 16px;\">\n                <h3 style=\"margin-top: 0; color: #15803d;\">CPU Usage</h3>\n                <p style=\"font-size: 24px; font-weight: bold; margin: 0; color: #14532d;\">23%</p>\n            </div>\n        </div>\n    </div>\n    \"\"\"\n    \n    ui_resource = create_ui_resource({\n        \"uri\": \"ui://dashboard/main\",\n        \"content\": {\n            \"type\": \"rawHtml\",\n            \"htmlString\": dashboard_html\n        },\n        \"encoding\": \"text\"\n    })\n    return [ui_resource]\n\n@mcp.tool()\ndef show_external_site() -> list[UIResource]:\n    \"\"\"Display an external website in an iframe.\"\"\"\n    ui_resource = create_ui_resource({\n        \"uri\": \"ui://external/example\",\n        \"content\": {\n            \"type\": \"externalUrl\",\n            \"iframeUrl\": \"https://example.com\"\n        },\n        \"encoding\": \"text\"\n    })\n    return [ui_resource]\n\n@mcp.tool()\ndef show_interactive_demo() -> list[UIResource]:\n    \"\"\"Show an interactive demo with buttons that send intents.\"\"\"\n    interactive_html = \"\"\"\n    <div style=\"padding: 20px; font-family: Arial, sans-serif;\">\n        <h2>Interactive Demo</h2>\n        <p>Click the button below to send an intent back to the parent:</p>\n        \n        <button onclick=\"sendIntent()\" \n                style=\"background: #2563eb; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer;\">\n            Send Intent\n        </button>\n        \n        <div id=\"status\" style=\"margin-top: 20px; padding: 10px; background: #f3f4f6; border-radius: 4px;\">\n            Ready\n        </div>\n    </div>\n    \n    <script>\n        function sendIntent() {\n            const status = document.getElementById('status');\n            status.innerHTML = 'Intent sent!';\n            \n            if (window.parent) {\n                window.parent.postMessage({\n                    type: 'intent',\n                    payload: {\n                        intent: 'demo_interaction',\n                        params: { source: 'python-server', timestamp: new Date().toISOString() }\n                    }\n                }, '*');\n            }\n        }\n    </script>\n    \"\"\"\n    \n    ui_resource = create_ui_resource({\n        \"uri\": \"ui://demo/interactive\",\n        \"content\": {\n            \"type\": \"rawHtml\",\n            \"htmlString\": interactive_html\n        },\n        \"encoding\": \"text\"\n    })\n    return [ui_resource]\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(description=\"My MCP Server\")\n    parser.add_argument(\"--http\", action=\"store_true\", help=\"Use HTTP transport instead of stdio\")\n    parser.add_argument(\"--port\", type=int, default=3000, help=\"Port for HTTP transport (default: 3000)\")\n    args = parser.parse_args()\n\n    if args.http:\n        print(\"🚀 Starting MCP server on HTTP (SSE transport)\")\n        print(\"📡 Server will use SSE transport settings\")\n        mcp.settings.port = args.port\n        mcp.run(transport=\"sse\")\n    else:\n        print(\"🚀 Starting MCP server with stdio transport\")\n        mcp.run()\n```\n\n## 6. Run Your Server\n\nYou can run your server in two modes:\n\n**Stdio Mode (for command-line clients):**\n```bash\npython server.py\n```\n\n**HTTP Mode (for web clients):**\n```bash\npython server.py --http --port 3000\n```\n\n## 7. Test with UI Inspector\n\nTo test your server with a visual interface:\n\n1. Go to the [ui-inspector repository](https://github.com/idosal/ui-inspector) and run it locally\n2. Open the inspector in your browser (usually `http://localhost:6274`)\n3. Configure the connection:\n   - **Transport Type**: \"SSE\" (for HTTP mode) or \"Stdio\" (for stdio mode)\n   - **Server URL**: `http://localhost:3000/sse` (for HTTP mode)\n4. Click \"Connect\"\n\nThe inspector will show your tools:\n- **greet**: Simple HTML greeting\n- **show_dashboard**: Dashboard with metrics\n- **show_external_site**: External website iframe\n- **show_interactive_demo**: Interactive buttons with intents\n\nWhen you call these tools, the UI resources will be rendered in the inspector's Tool Results panel.\n\n## 8. Next Steps\n\nNow that you have a working MCP server with UI resources, you can:\n\n1. **Add more tools** with different types of content\n2. **Handle user interactions** by implementing tools that respond to intents\n3. **Create dynamic content** based on tool parameters\n4. **Integrate with external APIs** to display live data\n5. **Use blob encoding** for larger or binary content\n\nFor more examples and advanced usage, see the [Usage Examples](./usage-examples.md) documentation.\n\n## Tips for Development\n\n- Use `encoding: \"text\"` for simple HTML content\n- Use `encoding: \"blob\"` for larger content or when you need Base64 encoding\n- Always prefix URIs with `ui://` followed by your component identifier\n- Test both stdio and HTTP transports depending on your use case\n- Use the ui-inspector for visual testing and debugging\n\nYou've successfully created a Python MCP server with UI resources! The FastMCP framework makes it easy to create tools that return rich, interactive UI content to MCP clients."
  },
  {
    "path": "docs/src/guide/server/ruby/overview.md",
    "content": "# mcp_ui_server Overview\n\nThe `mcp_ui_server` gem provides server-side utilities in Ruby to help construct `UIResource` objects, which can then be sent to a client as part of an MCP response.\n\n::: tip MCP Apps Compatibility\nThe `create_ui_resource` method creates UIResource objects that work with both MCP Apps hosts (via `_meta.ui.resourceUri`) and legacy MCP-UI hosts.\n\nFor the recommended MCP Apps pattern with TypeScript, see [Getting Started](../../getting-started).\n:::\n\n## Key Methods\n\n- **`McpUiServer.create_ui_resource(options)`**:\n  The primary method for creating UI snippets. It takes an options hash to define the URI, content (direct HTML or external URL), and encoding method.\n\n## Purpose\n\n- **Ease of Use**: Simplifies the creation of valid `UIResource` objects in Ruby.\n- **Validation**: Includes basic validation (e.g., URI prefixes matching content type).\n- **Encoding**: Handles Base64 encoding automatically.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'mcp_ui_server'\n```\n\nAnd then execute:\n\n```bash\n$ bundle install\n```\n\nOr install it yourself as:\n\n```bash\n$ gem install mcp_ui_server\n```\n\nSee the [Server SDK Usage & Examples](./usage-examples.md) page for practical examples.\n"
  },
  {
    "path": "docs/src/guide/server/ruby/usage-examples.md",
    "content": "# mcp_ui_server Usage & Examples\n\nThis page provides practical examples for using the `mcp_ui_server` gem.\n\nFor a complete, runnable server implementation that demonstrates how to use these resources within an MCP tool, see the Ruby server demo (`/examples/ruby-server-demo/server.rb`).\n\n## Basic Setup\n\nFirst, ensure you have `mcp_ui_server` available in your project by adding it to your Gemfile and running `bundle install`.\n\n```ruby\nrequire 'mcp_ui_server'\nrequire 'json'\n\n# Example 1: Direct HTML, delivered as text\nresource1 = McpUiServer.create_ui_resource(\n  uri: 'ui://my-component/instance-1',\n  content: { type: :raw_html, htmlString: '<p>Hello World</p>' },\n  encoding: :text\n)\nputs \"Resource 1: #{JSON.pretty_generate(resource1)}\"\n# Output for Resource 1:\n# {\n#   \"type\": \"resource\",\n#   \"resource\": {\n#     \"uri\": \"ui://my-component/instance-1\",\n#     \"mimeType\": \"text/html;profile=mcp-app\",\n#     \"text\": \"<p>Hello World</p>\"\n#   }\n# }\n\n# Example 2: Direct HTML, delivered as a Base64 blob\nresource2 = McpUiServer.create_ui_resource(\n  uri: 'ui://my-component/instance-2',\n  content: { type: :raw_html, htmlString: '<h1>Complex HTML</h1>' },\n  encoding: :blob\n)\nputs \"Resource 2 (blob will be Base64): #{JSON.pretty_generate(resource2)}\"\n# Output for Resource 2:\n# {\n#   \"type\": \"resource\",\n#   \"resource\": {\n#     \"uri\": \"ui://my-component/instance-2\",\n#     \"mimeType\": \"text/html;profile=mcp-app\",\n#     \"blob\": \"PGgxPkNvbXBsZXggSFRNTDwvaDE+\"\n#   }\n# }\n\n# Example 3: External URL, text encoding\ndashboard_url = 'https://my.analytics.com/dashboard/123'\nresource3 = McpUiServer.create_ui_resource(\n  uri: 'ui://analytics-dashboard/main',\n  content: { type: :external_url, iframeUrl: dashboard_url },\n  encoding: :text\n)\nputs \"Resource 3: #{JSON.pretty_generate(resource3)}\"\n# Output for Resource 3:\n# {\n#   \"type\": \"resource\",\n#   \"resource\": {\n#     \"uri\": \"ui://analytics-dashboard/main\",\n#     \"mimeType\": \"text/html;profile=mcp-app\",\n#     \"text\": \"https://my.analytics.com/dashboard/123\"\n#   }\n# }\n\n# Example 4: External URL, blob encoding (URL is Base64 encoded)\nchart_api_url = 'https://charts.example.com/api?type=pie&data=1,2,3'\nresource4 = McpUiServer.create_ui_resource(\n  uri: 'ui://live-chart/session-xyz',\n  content: { type: :external_url, iframeUrl: chart_api_url },\n  encoding: :blob\n)\nputs \"Resource 4 (blob will be Base64 of URL): #{JSON.pretty_generate(resource4)}\"\n# Output for Resource 4:\n# {\n#   \"type\": \"resource\",\n#   \"resource\": {\n#     \"uri\": \"ui://live-chart/session-xyz\",\n#     \"mimeType\": \"text/html;profile=mcp-app\",\n#     \"blob\": \"aHR0cHM6Ly9jaGFydHMuZXhhbXBsZS5jb20vYXBpP3R5cGU9cGllJmRhdGE9MSwyLDM=\"\n#   }\n# }\n\n# These resource objects would then be included in the 'content' array\n# of a toolResult in an MCP interaction.\n```\n\n## Error Handling\n\nThe `create_ui_resource` method will raise an `ArgumentError` if invalid combinations are provided.\n\n```ruby\nbegin\n  McpUiServer.create_ui_resource(\n    uri: 'invalid://should-be-ui',\n    content: { type: :external_url, iframeUrl: 'https://example.com' },\n    encoding: :text\n  )\nrescue ArgumentError => e\n  puts \"Caught expected error: #{e.message}\"\n  # => Caught expected error: URI must start with 'ui://' for externalUrl content type.\nend\n```\n"
  },
  {
    "path": "docs/src/guide/server/ruby/walkthrough.md",
    "content": "# Ruby Server Walkthrough\n\nThis guide provides a step-by-step walkthrough for creating a barebones MCP UI server using Ruby's built-in WEBrick library.\n\nFor a complete, runnable example, see the [`ruby-server-demo`](https://github.com/idosal/mcp-ui/tree/main/examples/ruby-server-demo).\n\n## 1. Project Setup\n\nFirst, set up your project directory and dependencies.\n\nCreate a `Gemfile` with the necessary gems:\n\n```ruby\nsource \"https://rubygems.org\"\n\ngem \"mcp\", git: \"https://github.com/modelcontextprotocol/ruby-sdk\"\ngem \"mcp_ui_server\"\n```\n\nThe `mcp` gem is the core Model Context Protocol SDK, while `mcp_ui_server` provides helpers for creating UI resources.\n\nInstall the dependencies:\n\n```sh\nbundle install\n```\n\nCreate an empty `server.rb` file. We will add code to it in the next steps.\n\n```sh\ntouch server.rb\n```\n\n## 2. Create an MCP Tool\n\nIn MCP, a \"tool\" is a class that can be invoked by a client. For this example, we'll create a tool that returns a `UIResource` containing an external URL.\n\nAdd the following to your `server.rb`:\n\n```ruby\nrequire 'mcp'\nrequire 'mcp_ui_server'\nrequire 'webrick'\nrequire 'json'\n\nclass ExternalUrlTool < MCP::Tool\n  description 'A simple tool that returns an external URL resource'\n  input_schema(\n    type: 'object',\n    properties: {}\n  )\n\n  def self.call(server_context:)\n    ui_resource_object = McpUiServer.create_ui_resource(\n      uri: 'ui://my-external-site',\n      content: { type: :external_url, iframeUrl: 'https://example.com' },\n      encoding: :text\n    )\n\n    MCP::Tool::Response.new([ui_resource_object])\n  end\nend\n```\n\nThe `require 'mcp_ui_server'` line imports the mcp-ui library. The `ExternalUrlTool` is a standard MCP `Tool`, but it uses `McpUiServer.create_ui_resource` to return a `UIResource`, which is the primary integration point with `mcp-ui`. The following sections describe how to set up a standard MCP server and expose it over HTTP.\n\n## 3. Set Up the MCP Server\n\nNext, instantiate the MCP server and register the tool with it.\n\nAdd this to `server.rb`:\n\n```ruby\n# --- MCP Server Setup ---\nmcp_server = MCP::Server.new(tools: [ExternalUrlTool])\n```\n\n## 4. Set Up an HTTP Server\n\nWe'll use WEBrick to create a simple HTTP server to handle MCP requests.\n\nAdd the following to `server.rb` to create an HTTP server that listens on port 8081 and handles requests at the `/mcp` endpoint:\n\n```ruby\n# --- WEBrick HTTP Server Setup ---\nhttp_server = WEBrick::HTTPServer.new(Port: 8081)\n\n# Create a servlet to handle requests to /mcp\nclass MCPServlet < WEBrick::HTTPServlet::AbstractServlet\n  def initialize(server, mcp_instance)\n    super(server)\n    @mcp = mcp_instance\n  end\n\n  # Handle pre-flight CORS requests\n  def do_OPTIONS(_request, response)\n    response.status = 200\n    response['Access-Control-Allow-Origin'] = '*'\n    response['Access-Control-Allow-Methods'] = 'POST, OPTIONS'\n    response['Access-Control-Allow-Headers'] = 'Content-Type, Accept'\n  end\n\n  def do_POST(request, response)\n    response['Access-control-Allow-Origin'] = '*'\n    response.status = 200\n    response['Content-Type'] = 'application/json'\n    response.body = @mcp.handle_json(request.body)\n  end\nend\n\n# Mount the servlet at the /mcp endpoint\nhttp_server.mount('/mcp', MCPServlet, mcp_server)\n```\n\nThe `MCPServlet` processes incoming `POST` requests, passes them to the MCP server instance, and returns the JSON response. It also handles CORS `OPTIONS` requests to allow cross-origin communication with a web client.\n\n## 5. Run and Test\n\nFinally, add the code to start the server.\n\n```ruby\n# Start the server and handle shutdown\ntrap('INT') { http_server.shutdown }\nputs 'MCP server running on http://localhost:8081/mcp'\nhttp_server.start\n```\n\nYour `server.rb` is now complete. You can run it with:\n\n```sh\nruby server.rb\n```\n\nThe server will be available at `http://localhost:8081/mcp`.\n\nTo test your new endpoint, you can use the [`ui-inspector`](https://github.com/idosal/ui-inspector):\n\n1. Go to the [ui-inspector repo](https://github.com/idosal/ui-inspector/) and run locally.\n2. Open the local client in a browser (usually `http://localhost:6274`)\n3. Change the Transport Type to \"Streamable HTTP\".\n4. Enter your server's MCP endpoint URL: `http://localhost:8081/mcp`.\n5. Click \"Connect\".\n\nThe inspector will show tools for the different content types. When you call them, the UI resource will be rendered in the inspector's Tool Results.\n\nYou've now successfully integrated `mcp-ui` into your Ruby server! You can now create more complex tools that return different types of UI resources. "
  },
  {
    "path": "docs/src/guide/server/typescript/overview.md",
    "content": "# @mcp-ui/server Overview\n\nThe `@mcp-ui/server` package provides utilities to build MCP Apps - tools with interactive UIs. It works with `@modelcontextprotocol/ext-apps` to implement the MCP Apps standard.\n\nFor a complete example, see the [`typescript-server-demo`](https://github.com/idosal/mcp-ui/tree/docs/ts-example/examples/typescript-server-demo).\n\n## MCP Apps Pattern (Recommended)\n\n```typescript\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { registerAppTool, registerAppResource } from '@modelcontextprotocol/ext-apps/server';\nimport { createUIResource } from '@mcp-ui/server';\nimport { z } from 'zod';\n\n// 1. Create UI content\nconst widgetUI = await createUIResource({\n  uri: 'ui://my-server/widget',\n  content: { type: 'rawHtml', htmlString: '<h1>Widget</h1>' },\n  encoding: 'text',\n});\n\n// 2. Register resource handler\nregisterAppResource(server, 'widget_ui', widgetUI.resource.uri, {}, async () => ({\n  contents: [widgetUI.resource]\n}));\n\n// 3. Register tool with _meta.ui.resourceUri\nregisterAppTool(server, 'show_widget', {\n  description: 'Show widget',\n  inputSchema: { query: z.string() },\n  _meta: { ui: { resourceUri: widgetUI.resource.uri } }\n}, async ({ query }) => {\n  return { content: [{ type: 'text', text: `Query: ${query}` }] };\n});\n```\n\n## Key Exports\n\n- **`createUIResource(options: CreateUIResourceOptions): Promise<UIResource>`**:\n  Creates UI resource objects. For `externalUrl` content, fetches the URL, injects a `<base>` tag, and auto-populates `_meta.csp.baseUriDomains` with the external origin. Use with `registerAppTool` and `registerAppResource` from `@modelcontextprotocol/ext-apps/server`.\n\n## Purpose\n\n- **MCP Apps Compliance**: Implements the MCP Apps standard for tool UIs\n- **Ease of Use**: Simplifies the creation of valid `UIResource` objects\n- **Validation**: Includes basic validation (e.g., URI prefixes matching content type)\n- **Encoding**: Handles Base64 encoding when `encoding: 'blob'` is specified\n- **Metadata Support**: Provides flexible metadata configuration for enhanced client-side rendering\n\n## Metadata Features\n\nThe `createUIResource()` function supports three types of metadata configuration to enhance resource functionality:\n\n### `metadata`\nStandard MCP resource metadata that becomes the `_meta` property on the resource. This follows the MCP specification for resource metadata.\n\n### `uiMetadata`\nMCP-UI specific configuration options. These keys are automatically prefixed with `mcpui.dev/ui-` in the resource metadata:\n\n- **`preferred-frame-size`**: Define the resource's preferred initial frame size (e.g., `{ width: 800, height: 600 }`)\n- **`initial-render-data`**: Provide data that should be passed to the iframe when rendering\n\n### `resourceProps`\nAdditional properties that are spread directly onto the actual resource definition, allowing you to add/override any MCP specification-supported properties.\n\n### `embeddedResourceProps`\nAdditional properties that are spread directly onto the embedded resource top-level definition, allowing you to add any MCP embedded resource [specification-supported](https://modelcontextprotocol.io/specification/2025-06-18/schema#embeddedresource) properties, like `annotations`.\n\n## Building\n\nThis package is built using Vite in library mode, targeting Node.js environments. It outputs ESM (`.mjs`) and CJS (`.js`) formats, along with TypeScript declaration files (`.d.ts`).\n\nTo build specifically this package from the monorepo root:\n\n```bash\npnpm build --filter @mcp-ui/server\n```\n\nSee the [Server SDK Usage & Examples](./usage-examples.md) page for practical examples.\n"
  },
  {
    "path": "docs/src/guide/server/typescript/usage-examples.md",
    "content": "# @mcp-ui/server Usage & Examples\n\nThis page provides practical examples for using the `@mcp-ui/server` package.\n\nFor a complete example, see the [`typescript-server-demo`](https://github.com/idosal/mcp-ui/tree/docs/ts-example/examples/typescript-server-demo).\n\n## Installation\n\n```bash\nnpm i @mcp-ui/server @modelcontextprotocol/ext-apps\n```\n\n## MCP Apps Pattern (Recommended)\n\nThe MCP Apps pattern uses `registerAppTool` with `_meta.ui.resourceUri` to link tools to their UIs:\n\n```typescript\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { registerAppTool, registerAppResource } from '@modelcontextprotocol/ext-apps/server';\nimport { createUIResource } from '@mcp-ui/server';\nimport { z } from 'zod';\n\nconst server = new McpServer({ name: 'my-server', version: '1.0.0' });\n\n// Create UI resource with MCP Apps protocol\nconst widgetUI = await createUIResource({\n  uri: 'ui://my-server/widget',\n  content: {\n    type: 'rawHtml',\n    htmlString: `\n      <html>\n        <body>\n          <h1>Interactive Widget</h1>\n          <button onclick=\"sendMessage()\">Send Message</button>\n          <div id=\"status\">Ready</div>\n          <script type=\"module\">\n            import { App } from 'https://esm.sh/@modelcontextprotocol/ext-apps@0.4.1';\n\n            const app = new App({ name: 'widget', version: '1.0.0' });\n\n            // Listen for tool input\n            app.ontoolinput = (params) => {\n              document.getElementById('status').textContent =\n                'Received: ' + JSON.stringify(params.input);\n            };\n\n            // Send a message to the conversation\n            window.sendMessage = async () => {\n              await app.sendMessage({\n                role: 'user',\n                content: [{ type: 'text', text: 'Tell me more' }]\n              });\n            };\n\n            await app.connect();\n          </script>\n        </body>\n      </html>\n    `,\n  },\n  encoding: 'text',\n});\n\n// Register resource handler\nregisterAppResource(server, 'widget_ui', widgetUI.resource.uri, {}, async () => ({\n  contents: [widgetUI.resource]\n}));\n\n// Register tool with _meta.ui.resourceUri\nregisterAppTool(server, 'show_widget', {\n  description: 'Show an interactive widget',\n  inputSchema: {\n    query: z.string().describe('User query'),\n  },\n  _meta: {\n    ui: {\n      resourceUri: widgetUI.resource.uri  // Links tool to UI\n    }\n  }\n}, async ({ query }) => {\n  return {\n    content: [{ type: 'text', text: `Processing: ${query}` }]\n  };\n});\n```\n\n::: tip MCP Apps Protocol\nThe HTML uses the [`@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps) `App` class for communication with the host. See [Protocol Details](/guide/protocol-details) for the full JSON-RPC API. For legacy MCP-UI hosts, see the [MCP Apps Adapter](/guide/mcp-apps).\n:::\n\n## Creating UI Resources\n\nThe core function is `createUIResource`.\n\n```typescript\nimport {\n  createUIResource,\n} from '@mcp-ui/server';\n\n// Using a shared enum value (just for demonstration)\nconsole.log('Shared Enum from server usage:', PlaceholderEnum.FOO);\n\n// Example 1: Direct HTML, delivered as text\nconst resource1 = await createUIResource({\n  uri: 'ui://my-component/instance-1',\n  content: { type: 'rawHtml', htmlString: '<p>Hello World</p>' },\n  encoding: 'text',\n});\nconsole.log('Resource 1:', JSON.stringify(resource1, null, 2));\n/* Output for Resource 1:\n{\n  \"type\": \"resource\",\n  \"resource\": {\n    \"uri\": \"ui://my-component/instance-1\",\n    \"mimeType\": \"text/html;profile=mcp-app\",\n    \"text\": \"<p>Hello World</p>\"\n  }\n}\n*/\n\n// Example 2: Direct HTML, delivered as a Base64 blob\nconst resource2 = await createUIResource({\n  uri: 'ui://my-component/instance-2',\n  content: { type: 'rawHtml', htmlString: '<h1>Complex HTML</h1>' },\n  encoding: 'blob',\n});\nconsole.log(\n  'Resource 2 (blob will be Base64):',\n  JSON.stringify(resource2, null, 2),\n);\n/* Output for Resource 2:\n{\n  \"type\": \"resource\",\n  \"resource\": {\n    \"uri\": \"ui://my-component/instance-2\",\n    \"mimeType\": \"text/html;profile=mcp-app\",\n    \"blob\": \"PGgxPkNvbXBsZXggSFRNTDwvaDE+\"\n  }\n}\n*/\n\n// Example 3: External URL — fetches the page HTML and injects a <base> tag\nconst dashboardUrl = 'https://my.analytics.com/dashboard/123';\nconst resource3 = await createUIResource({\n  uri: 'ui://analytics-dashboard/main',\n  content: { type: 'externalUrl', iframeUrl: dashboardUrl },\n  encoding: 'text',\n});\n// resource3.resource.text contains the fetched HTML with:\n//   <base href=\"https://my.analytics.com/dashboard/123\">\n// injected so relative paths resolve correctly.\n\n// Example 4: External URL, blob encoding (fetched HTML is Base64 encoded)\nconst chartApiUrl = 'https://charts.example.com/api?type=pie&data=1,2,3';\nconst resource4 = await createUIResource({\n  uri: 'ui://live-chart/session-xyz',\n  content: { type: 'externalUrl', iframeUrl: chartApiUrl },\n  encoding: 'blob',\n});\n// resource4.resource.blob contains the Base64-encoded fetched HTML.\n\n// These resource objects would then be included in the 'content' array\n// of a toolResult in an MCP interaction.\n```\n\n## Metadata Configuration Examples\n\nThe `createUIResource` function supports several types of metadata configuration to enhance your UI resources:\n\n```typescript\nimport { createUIResource } from '@mcp-ui/server';\n\n// Example 7: Using standard metadata\nconst resourceWithMetadata = await createUIResource({\n  uri: 'ui://analytics/dashboard',\n  content: { type: 'rawHtml', htmlString: '<div id=\"dashboard\">Loading...</div>' },\n  encoding: 'text',\n  metadata: {\n    title: 'Analytics Dashboard',\n    description: 'Real-time analytics and metrics',\n    created: '2024-01-15T10:00:00Z',\n    author: 'Analytics Team',\n    preferredRenderContext: 'sidebar'\n  }\n});\nconsole.log('Resource with metadata:', JSON.stringify(resourceWithMetadata, null, 2));\n/* Output includes:\n{\n  \"type\": \"resource\",\n  \"resource\": {\n    \"uri\": \"ui://analytics/dashboard\",\n    \"mimeType\": \"text/html;profile=mcp-app\",\n    \"text\": \"<div id=\\\"dashboard\\\">Loading...</div>\",\n    \"_meta\": {\n      \"title\": \"Analytics Dashboard\",\n      \"description\": \"Real-time analytics and metrics\",\n      \"created\": \"2024-01-15T10:00:00Z\",\n      \"author\": \"Analytics Team\",\n      \"preferredRenderContext\": \"sidebar\"\n    }\n  }\n}\n*/\n\n// Example 8: Using uiMetadata for client-side configuration\nconst resourceWithUIMetadata = await createUIResource({\n  uri: 'ui://chart/interactive',\n  content: { type: 'rawHtml', htmlString: '<div id=\"chart\">Loading...</div>' },\n  encoding: 'text',\n  uiMetadata: {\n    'preferred-frame-size': ['800px', '600px'],\n    'initial-render-data': {\n      theme: 'dark',\n      chartType: 'bar',\n      dataSet: 'quarterly-sales'\n    },\n  }\n});\nconsole.log('Resource with UI metadata:', JSON.stringify(resourceWithUIMetadata, null, 2));\n/* Output includes:\n{\n  \"type\": \"resource\",\n  \"resource\": {\n    \"uri\": \"ui://chart/interactive\",\n    \"mimeType\": \"text/html;profile=mcp-app\",\n    \"text\": \"<div id=\\\"chart\\\">Loading...</div>\",\n    \"_meta\": {\n      \"mcpui.dev/ui-preferred-frame-size\": [\"800px\", \"600px\"],\n      \"mcpui.dev/ui-initial-render-data\": {\n        \"theme\": \"dark\",\n        \"chartType\": \"bar\",\n        \"dataSet\": \"quarterly-sales\"\n      },\n    }\n  }\n}\n*/\n\n// Example 9: Using embeddedResourceProps for additional MCP properties\nconst resourceWithProps = await createUIResource({\n  uri: 'ui://form/user-profile',\n  content: { type: 'rawHtml', htmlString: '<form id=\"profile\">...</form>' },\n  encoding: 'text',\n  embeddedResourceProps: {\n    annotations: {\n      audience: ['user'],\n      priority: 'high'\n    }\n  }\n});\nconsole.log('Resource with additional props:', JSON.stringify(resourceWithProps, null, 2));\n/* Output includes:\n{\n  \"type\": \"resource\",\n  \"resource\": {\n    \"uri\": \"ui://form/user-profile\",\n    \"mimeType\": \"text/html;profile=mcp-app\",\n    \"text\": \"<form id=\\\"profile\\\">...</form>\",\n  },\n  \"annotations\": {\n    \"audience\": [\"user\"],\n    \"priority\": \"high\"\n  }\n}\n*/\n```\n\n### Metadata Best Practices\n\n- **Use `metadata` for standard MCP resource information** like titles, descriptions, timestamps, and authorship\n- **Use `uiMetadata` for client rendering hints** like preferred sizes, initial data, and context preferences  \n- **Use `resourceProps` for MCP specification properties**, descriptions at the resource level, and other standard fields\n- **Use `embeddedResourceProps` for MCP embedded resource properties** like annotations.\n\n## Error Handling\n\nThe `createUIResource` function will throw errors if invalid combinations are provided, for example:\n\n- URI not starting with `ui://` for any content type\n- Invalid content type specified\n\n```typescript\ntry {\n  await createUIResource({\n    uri: 'invalid://should-be-ui',\n    content: { type: 'externalUrl', iframeUrl: 'https://example.com' },\n    encoding: 'text',\n  });\n} catch (e: any) {\n  console.error('Caught expected error:', e.message);\n  // MCP-UI SDK: URI must start with 'ui://'.\n}\n```"
  },
  {
    "path": "docs/src/guide/server/typescript/walkthrough.md",
    "content": "# TypeScript Server Walkthrough\n\nThis guide provides a step-by-step walkthrough for integrating `@mcp-ui/server` into a an existing Node.js server using the [Express.js](https://expressjs.com) framework.\n\nFor a complete, runnable example, see the [`typescript-server-demo`](https://github.com/idosal/mcp-ui/tree/main/examples/typescript-server-demo).\n\n## 1. Set up an Express Server\n\nIf you don't have an existing server, you can create a simple one.\n\nFirst, install the necessary dependencies:\n\n```bash\nnpm install express cors @types/express @types/cors\n```\n\nThen, create a basic server file (e.g., `server.ts`):\n\n```typescript\nimport express from 'express';\nimport cors from 'cors';\n\nconst app = express();\nconst port = 3000;\n\napp.use(cors());\napp.use(express.json());\n\napp.get('/', (req, res) => {\n  res.send('Hello, world!');\n});\n\napp.listen(port, () => {\n  console.log(`Server listening at http://localhost:${port}`);\n});\n```\n\n## 2. Install MCP and mcp-ui Dependencies\n\nNext, add the Model Context Protocol SDK and the `mcp-ui` server package to your project:\n\n```bash\nnpm install @modelcontextprotocol/sdk @mcp-ui/server\n```\n\nThe `@modelcontextprotocol/sdk` package provides the core functionality for creating an MCP server, while `@mcp-ui/server` includes helpers specifically for creating UI resources.\n\n## 3. Set up the MCP Server Handler\n\nIn MCP, tools are functions that the client can invoke. For this example, we'll create a tool that returns a `UIResource`.\nNow, let's create an MCP server instance and an endpoint to handle MCP requests using a streaming transport. This allows for more complex, stateful interactions.\n\nModify your `server.ts` file to include the following:\n\n```typescript\nimport express from 'express';\nimport cors from 'cors';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';\nimport { createUIResource } from '@mcp-ui/server';\nimport { randomUUID } from 'crypto';\n\nconst app = express();\nconst port = 3000;\n\napp.use(cors({\n  origin: '*',\n  exposedHeaders: ['Mcp-Session-Id'],\n  allowedHeaders: ['Content-Type', 'mcp-session-id'],\n}));\napp.use(express.json());\n\n// Map to store transports by session ID\nconst transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};\n\n// Handle POST requests for client-to-server communication.\napp.post('/mcp', async (req, res) => {\n  const sessionId = req.headers['mcp-session-id'] as string | undefined;\n  let transport: StreamableHTTPServerTransport;\n\n  if (sessionId && transports[sessionId]) {\n    // A session already exists; reuse the existing transport.\n    transport = transports[sessionId];\n  } else if (!sessionId && isInitializeRequest(req.body)) {\n    // This is a new initialization request. Create a new transport.\n    transport = new StreamableHTTPServerTransport({\n      sessionIdGenerator: () => randomUUID(),\n      onsessioninitialized: (sid) => {\n        transports[sid] = transport;\n        console.log(`MCP Session initialized: ${sid}`);\n      },\n    });\n\n    // Clean up the transport from our map when the session closes.\n    transport.onclose = () => {\n      if (transport.sessionId) {\n        console.log(`MCP Session closed: ${transport.sessionId}`);\n        delete transports[transport.sessionId];\n      }\n    };\n    \n    // Create a new server instance for this specific session.\n    const server = new McpServer({\n      name: \"typescript-server-walkthrough\",\n      version: \"1.0.0\"\n    });\n\n    // Register our MCP-UI tool on the new server instance.\n    server.registerTool('greet', {\n      title: 'Greet',\n      description: 'A simple tool that returns a UI resource.',\n      inputSchema: {},\n    }, async () => {\n      // Create the UI resource to be returned to the client (this is the only part specific to MCP-UI)\n      // For externalUrl, the SDK fetches the page HTML and injects a <base> tag\n      const uiResource = await createUIResource({\n        uri: 'ui://greeting',\n        content: { type: 'externalUrl', iframeUrl: 'https://example.com' },\n        encoding: 'text',\n      });\n\n      return {\n        content: [uiResource],\n      };\n    });\n  \n    // Connect the server instance to the transport for this session.\n    await server.connect(transport);\n  } else {\n    return res.status(400).json({\n      error: { message: 'Bad Request: No valid session ID provided' },\n    });\n  }\n\n  // Handle the client's request using the session's transport.\n  await transport.handleRequest(req, res, req.body);\n});\n\n// A separate, reusable handler for GET and DELETE requests.\nconst handleSessionRequest = async (req: express.Request, res: express.Response) => {\n  const sessionId = req.headers['mcp-session-id'] as string | undefined;\n  if (!sessionId || !transports[sessionId]) {\n    return res.status(404).send('Session not found');\n  }\n  \n  const transport = transports[sessionId];\n  await transport.handleRequest(req, res);\n};\n\n// GET handles the long-lived stream for server-to-client messages.\napp.get('/mcp', handleSessionRequest);\n\n// DELETE handles explicit session termination from the client.\napp.delete('/mcp', handleSessionRequest);\n\napp.listen(port, () => {\n  console.log(`Server listening at http://localhost:${port}`);\n  console.log(`MCP endpoint available at http://localhost:${port}/mcp`);\n});\n```\n\n## 4. Run and Test\n\nYou can now run your server:\n\n```bash\nnpx ts-node server.ts\n```\n\nTo test your new endpoint, you can use the [`ui-inspector`](https://github.com/idosal/ui-inspector):\n\n1. Go to the [ui-inspector repo](https://github.com/idosal/ui-inspector/) and run locally.\n2. Open the local client in a browser (usually `http://localhost:6274`)\n3. Change the Transport Type to \"Streamable HTTP\".\n4. Enter your server's MCP endpoint URL: `http://localhost:3000/mcp`.\n5. Click \"Connect\".\n\nThe inspector will show the \"Greet\" tool. When you call it, the UI resource will be rendered in the inspector's Tool Results.\n\nYou've now successfully integrated `mcp-ui` into your TypeScript server! You can now create more complex tools that return different types of UI resources.\n"
  },
  {
    "path": "docs/src/guide/supported-hosts.md",
    "content": "# Supported Hosts\n\nThe `@mcp-ui/*` packages work with both MCP Apps hosts and legacy MCP-UI hosts.\n\n## MCP Apps Hosts\n\nThese hosts implement the [MCP Apps SEP protocol](https://github.com/modelcontextprotocol/ext-apps) and support tools with `_meta.ui.resourceUri`:\n\n| Host | Support | Notes |\n| :--- | :-------: | :---- |\n| [Claude](https://www.claude.ai/) | ✅ | ✅ |\n| [VSCode](https://github.com/microsoft/vscode/issues/260218) | ✅ | |\n| [Postman](https://www.postman.com/) | ✅ | |\n| [Goose](https://block.github.io/goose/) | ✅ | |\n| [MCPJam](https://www.mcpjam.com/) | ✅ | |\n| [LibreChat](https://www.librechat.ai/) | ✅ | |\n| [mcp-use](https://mcp-use.com/) | ✅ | |\n| [Smithery](https://smithery.ai/playground) | ✅ | |\n\nFor MCP Apps hosts, use `AppRenderer` on the client side:\n\n```tsx\nimport { AppRenderer } from '@mcp-ui/client';\n\n<AppRenderer\n  client={client}\n  toolName={toolName}\n  sandbox={{ url: sandboxUrl }}\n  toolInput={toolInput}\n  toolResult={toolResult}\n/>\n```\n\n## Other Hosts\n\n| Host | Rendering | UI Actions | Notes |\n| :--- | :-------: | :--------: | :---- |\n| [ChatGPT](https://chatgpt.com/) | ✅ | ⚠️ | [Apps SDK Guide](./apps-sdk.md) |\n| [Nanobot](https://www.nanobot.ai/) | ✅ | ✅ |\n| [fast-agent](https://fast-agent.ai/mcp/mcp-ui/) | ✅ | ❌ | |\n\n## Legend\n\n- ✅: Fully Supported\n- ⚠️: Partial Support\n- ❌: Not Supported (yet)\n"
  },
  {
    "path": "docs/src/index.md",
    "content": "---\nlayout: home\n\nhero:\n  name: MCP-UI -> MCP Apps\n  text: Interactive UI Components over MCP\n  tagline: Build rich, dynamic interfaces for AI tools using the MCP Apps standard.\n  image:\n    light: /logo-lg-black.png\n    dark: /logo-lg.png\n    alt: MCP-UI Logo\n  actions:\n    - theme: brand\n      text: Get Started\n      link: /guide/introduction\n    - theme: alt\n      text: GitHub\n      link: https://github.com/idosal/mcp-ui\n    - theme: alt\n      text: About\n      link: /about\n\nfeatures:\n  - title: 🌐 MCP Apps Standard\n    details: MCP Apps is the official standard for interactive UI in MCP. The MCP-UI packages implement the spec, and serve as a community playground for future enhancements.\n  - title: 🛠️ Client & Server SDKs\n    details: The recommended MCP Apps Client SDK, with components for seamless integration. Includes server SDK with utilities.\n  - title: 🔒 Secure\n    details: All remote code executes in sandboxed iframes, ensuring host and user security while maintaining rich interactivity.\n  - title: 🎨 Flexible\n    details: Supports HTML content. Works with MCP Apps hosts and legacy MCP-UI hosts alike.\n---\n\n<!-- ## See MCP-UI in Action -->\n<div style=\"display: flex; flex-direction: column; align-items: center; margin: 3rem 0 2rem 0;\">\n<span class=\"text animated-gradient-text\" style=\"font-size: 30px; font-family: var(--vp-font-family-base); font-weight: 600;\n    letter-spacing: -0.01em; margin-bottom: 0.5rem; text-align: center; line-height: 1.2;\">See it in action</span>\n<div class=\"video-container\" style=\"display: flex; justify-content: center; align-items: center;\">\n  <video controls width=\"100%\" style=\"max-width: 800px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\">\n    <source src=\"https://github.com/user-attachments/assets/7180c822-2dd9-4f38-9d3e-b67679509483\" type=\"video/mp4\">\n    Your browser does not support the video tag.\n  </video>\n</div>\n</div>\n\n## Quick Example\n\n**Client Side** - Render tool UIs with `AppRenderer`:\n\n```tsx\nimport { AppRenderer } from '@mcp-ui/client';\n\nfunction ToolUI({ client, toolName, toolInput, toolResult }) {\n  return (\n    <AppRenderer\n      client={client}\n      toolName={toolName}\n      sandbox={{ url: sandboxUrl }}\n      toolInput={toolInput}\n      toolResult={toolResult}\n      onOpenLink={async ({ url }) => {\n        // Validate URL scheme before opening\n        if (url.startsWith('https://') || url.startsWith('http://')) {\n          window.open(url);\n        }\n      }}\n      onMessage={async (params) => console.log('Message:', params)}\n    />\n  );\n}\n```\n\n**Server Side** - Create a tool with an interactive UI using `_meta.ui.resourceUri`:\n\n```typescript\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { registerAppTool, registerAppResource } from '@modelcontextprotocol/ext-apps/server';\nimport { createUIResource } from '@mcp-ui/server';\nimport { z } from 'zod';\n\nconst server = new McpServer({ name: 'my-server', version: '1.0.0' });\n\n// Create the UI resource\nconst widgetUI = createUIResource({\n  uri: 'ui://my-server/widget',\n  content: { type: 'rawHtml', htmlString: '<h1>Interactive Widget</h1>' },\n  encoding: 'text',\n});\n\n// Register the resource handler\nregisterAppResource(server, 'widget_ui', widgetUI.resource.uri, {}, async () => ({\n  contents: [widgetUI.resource]\n}));\n\n// Register the tool with _meta.ui.resourceUri\nregisterAppTool(server, 'show_widget', {\n  description: 'Show interactive widget',\n  inputSchema: { query: z.string() },\n  _meta: { ui: { resourceUri: widgetUI.resource.uri } }  // Links tool to UI\n}, async ({ query }) => {\n  return { content: [{ type: 'text', text: `Query: ${query}` }] };\n});\n```\n\n::: tip Legacy MCP-UI Support\nFor existing MCP-UI apps or hosts that don't support MCP Apps yet, see the [Legacy MCP-UI Adapter](./guide/mcp-apps) guide.\n:::\n\n\n<style>\n.video-container {\n  text-align: center;\n  margin: 2rem 0;\n}\n\n.action-buttons {\n  display: flex;\n  gap: 1rem;\n  justify-content: center;\n  margin: 2rem 0;\n  flex-wrap: wrap;\n}\n\n.action-button {\n  display: inline-block;\n  padding: 0.75rem 1.5rem;\n  border-radius: 6px;\n  text-decoration: none;\n  font-weight: 500;\n  transition: all 0.3s ease;\n}\n\n.action-button.primary {\n  background: var(--vp-c-brand-1);\n  color: var(--vp-c-white);\n}\n\n.action-button.primary:hover {\n  background: var(--vp-c-brand-2);\n}\n\n.action-button.secondary {\n  background: var(--vp-c-bg-soft);\n  color: var(--vp-c-text-1);\n  border: 1px solid var(--vp-c-divider);\n}\n\n.action-button.secondary:hover {\n  background: var(--vp-c-bg-mute);\n}\n\n@media (max-width: 768px) {\n  .action-buttons {\n    flex-direction: column;\n    align-items: center;\n  }\n  \n  .action-button {\n    width: 200px;\n    text-align: center;\n  }\n}\n\na.VPButton.medium[href=\"/about\"] {\n  background-color: var(--vp-c-bg-soft);\n  border: 1px solid var(--vp-c-divider);\n  color: var(--vp-c-text-1);\n}\n\na.VPButton.medium[href=\"/about\"]:hover {\n  background-color: var(--vp-c-bg-mute);\n}\n</style>\n"
  },
  {
    "path": "docs/src/team.md",
    "content": "# Team\n\n`mcp-ui` is a project by [Ido Salomon](https://x.com/idosal1), in collaboration with [Liad Yosef](https://x.com/liadyosef). "
  },
  {
    "path": "eslint.config.mjs",
    "content": "import js from '@eslint/js';\nimport tseslint from 'typescript-eslint';\nimport react from 'eslint-plugin-react';\nimport reactHooks from 'eslint-plugin-react-hooks';\nimport jsxA11y from 'eslint-plugin-jsx-a11y';\nimport prettier from 'eslint-config-prettier';\nimport globals from 'globals';\n\nexport default tseslint.config(\n  // Global ignores (replaces .eslintignore)\n  {\n    ignores: [\n      '**/node_modules/**',\n      '**/dist/**',\n      '**/coverage/**',\n      '**/*.log',\n      '**/*.js',\n      '**/*.mjs',\n      '**/*.cjs',\n      'examples/**',\n      'docs/**',\n    ],\n  },\n\n  // Base configs\n  js.configs.recommended,\n  ...tseslint.configs.recommended,\n\n  // React config\n  {\n    files: ['**/*.tsx', '**/*.jsx'],\n    ...react.configs.flat.recommended,\n    ...react.configs.flat['jsx-runtime'],\n    settings: {\n      react: {\n        version: 'detect',\n      },\n    },\n  },\n\n  // React Hooks\n  {\n    files: ['**/*.tsx', '**/*.jsx'],\n    ...reactHooks.configs['flat/recommended'],\n  },\n\n  // Accessibility\n  {\n    files: ['**/*.tsx', '**/*.jsx'],\n    ...jsxA11y.flatConfigs.recommended,\n  },\n\n  // TypeScript and general config\n  {\n    files: ['**/*.ts', '**/*.tsx'],\n    languageOptions: {\n      ecmaVersion: 'latest',\n      sourceType: 'module',\n      globals: {\n        ...globals.node,\n        ...globals.browser,\n        ...globals.es2025,\n      },\n      parserOptions: {\n        ecmaFeatures: {\n          jsx: true,\n        },\n      },\n    },\n    rules: {\n      'react/prop-types': 'off',\n      '@typescript-eslint/consistent-type-imports': [\n        'error',\n        {\n          prefer: 'type-imports',\n          fixStyle: 'inline-type-imports',\n        },\n      ],\n      '@typescript-eslint/no-unused-vars': [\n        'error',\n        {\n          argsIgnorePattern: '^_',\n          varsIgnorePattern: '^_',\n          caughtErrorsIgnorePattern: '^_',\n        },\n      ],\n    },\n  },\n\n  // Prettier must be last to override other formatting rules\n  prettier\n);\n"
  },
  {
    "path": "examples/external-url-demo/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta\n      http-equiv=\"Content-Security-Policy\"\n      content=\"frame-src 'self' https://proxy.mcpui.dev;\"\n    />\n    <title>MCP-UI Proxy Demo</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/external-url-demo/package.json",
    "content": "{\n  \"name\": \"external-url-demo\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc -b && vite build\",\n    \"lint\": \"eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0\",\n    \"preview\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"@mcp-ui/client\": \"workspace:*\",\n    \"react\": \"^18.3.1\",\n    \"react-dom\": \"^18.3.1\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"^18.3.23\",\n    \"@types/react-dom\": \"^18.3.7\",\n    \"@typescript-eslint/eslint-plugin\": \"^7.18.0\",\n    \"@typescript-eslint/parser\": \"^7.18.0\",\n    \"@vitejs/plugin-react\": \"^4.6.0\",\n    \"eslint\": \"^9.29.0\",\n    \"eslint-plugin-react-hooks\": \"^5.2.0\",\n    \"eslint-plugin-react-refresh\": \"0.4.7\",\n    \"typescript\": \"~5.8.3\",\n    \"vite\": \"^6.3.5\"\n  }\n}\n"
  },
  {
    "path": "examples/external-url-demo/src/App.css",
    "content": "body {\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n  max-width: 1200px;\n  margin: 0 auto;\n  padding: 2rem;\n  line-height: 1.6;\n}\n.demo-section {\n  margin: 2rem 0;\n  padding: 1rem;\n  border: 1px solid #ddd;\n  border-radius: 8px;\n}\n.demo-section h2 {\n  margin-top: 0;\n  color: #333;\n}\niframe {\n  width: 100%;\n  min-height: 400px;\n  border: 1px solid #ccc;\n  border-radius: 4px;\n}\n.code {\n  background: #f5f5f5;\n  padding: 1rem;\n  border-radius: 4px;\n  font-family: 'Courier New', monospace;\n  font-size: 0.9em;\n  margin: 1rem 0;\n}\n.toggle {\n  background: #007bff;\n  color: white;\n  border: none;\n  padding: 0.5rem 1rem;\n  border-radius: 4px;\n  cursor: pointer;\n  margin: 1rem 0;\n}\n.toggle:hover {\n  background: #0056b3;\n}\n"
  },
  {
    "path": "examples/external-url-demo/src/App.tsx",
    "content": "import { useState } from 'react';\nimport './App.css';\nimport { UIResourceRenderer } from '@mcp-ui/client';\n\nfunction App() {\n  const [useProxy, setUseProxy] = useState(false);\n  const resource = {\n    mimeType: 'text/uri-list',\n    text: 'https://example.com',\n  };\n  const proxy = 'https://proxy.mcpui.dev/';\n\n  return (\n    <div>\n      <h1>MCP-UI Proxy Demo</h1>\n      <p>This demo shows how the proxy functionality works for external URLs in MCP-UI.</p>\n      <p>\n        <strong>CSP Simulation:</strong> This page includes a Content Security Policy (\n        <code>frame-src 'self' https://proxy.mcpui.dev;</code>) that only allows iframes from this\n        origin and <code>https://proxy.mcpui.dev</code>. This demonstrates how the{' '}\n        <code>proxy</code> prop can be used to display external content on hosts with strict\n        security policies.\n      </p>\n      <p>\n        <code>proxy.mcpui.dev</code> hosts a simple script that renders the provided URL in a nested\n        iframe. Hosts can use this script or host their own to achieve the same result.\n      </p>\n\n      <div className=\"demo-section\">\n        <h2>Direct URL (No Proxy)</h2>\n        <p>\n          This iframe attempts to load an external URL directly.{' '}\n          <strong>It should be blocked by the browser's Content Security Policy.</strong>\n        </p>\n        <div className=\"code\">\n          Resource: {`{ mimeType: 'text/uri-list', text: 'https://example.com' }`}\n        </div>\n        <UIResourceRenderer resource={{ mimeType: 'text/uri-list', text: 'https://example.com' }} />\n      </div>\n\n      <div className=\"demo-section\">\n        <h2>Proxied URL</h2>\n        <p>This iframe loads the external URL through the proxy:</p>\n        <div className=\"code\">\n          Resource: {`{ mimeType: 'text/uri-list', text: 'https://example.com' }`}\n          <br />\n          Proxy: https://proxy.mcpui.dev/\n          <br />\n          Final URL: {`${proxy}?url=${encodeURIComponent(resource.text)}`}\n        </div>\n        <UIResourceRenderer\n          resource={resource}\n          htmlProps={{ proxy, style: { width: '500px', height: '500px' } }}\n        />\n      </div>\n\n      <div className=\"demo-section\">\n        <h2>Interactive Demo</h2>\n        <p>Toggle between direct and proxied loading:</p>\n        <div style={{ display: 'flex', alignItems: 'center', gap: '20px' }}>\n          <button className=\"toggle\" onClick={() => setUseProxy(!useProxy)}>\n            Toggle Proxy\n          </button>\n          <h1>{useProxy ? 'Proxied' : 'Direct (no proxy)'}</h1>\n        </div>\n        <div id=\"demo-container\">\n          <UIResourceRenderer resource={resource} htmlProps={useProxy ? { proxy } : {}} />\n        </div>\n        <div className=\"code\" id=\"url-display\">\n          Current URL:{' '}\n          {useProxy ? `${proxy}?url=${encodeURIComponent(resource.text)}` : resource.text}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport default App;\n"
  },
  {
    "path": "examples/external-url-demo/src/index.css",
    "content": "body {\n  margin: 0;\n  font-family:\n    -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell',\n    'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;\n}\n"
  },
  {
    "path": "examples/external-url-demo/src/main.tsx",
    "content": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport App from './App.tsx';\nimport './index.css';\n\nReactDOM.createRoot(document.getElementById('root')!).render(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>,\n);\n"
  },
  {
    "path": "examples/external-url-demo/tsconfig.app.json",
    "content": "{\n  \"compilerOptions\": {\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.app.tsbuildinfo\",\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"verbatimModuleSyntax\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "examples/external-url-demo/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"references\": [{ \"path\": \"./tsconfig.app.json\" }, { \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "examples/external-url-demo/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "examples/external-url-demo/vite.config.ts",
    "content": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [react()],\n});\n"
  },
  {
    "path": "examples/mcp-apps-demo/package.json",
    "content": "{\n  \"name\": \"mcp-apps-demo\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"start\": \"node dist/index.js\",\n    \"build\": \"tsc\"\n  },\n  \"dependencies\": {\n    \"@mcp-ui/server\": \"workspace:*\",\n    \"@modelcontextprotocol/ext-apps\": \"^0.2.2\",\n    \"@modelcontextprotocol/sdk\": \"^1.25.1\",\n    \"cors\": \"^2.8.5\",\n    \"express\": \"^4.18.2\",\n    \"zod\": \"^3.22.4\"\n  },\n  \"devDependencies\": {\n    \"@types/cors\": \"^2.8.17\",\n    \"@types/express\": \"^4.17.21\",\n    \"@types/node\": \"^20.11.24\",\n    \"ts-node\": \"^10.9.2\",\n    \"typescript\": \"^5.3.3\"\n  }\n}\n"
  },
  {
    "path": "examples/mcp-apps-demo/src/index.ts",
    "content": "import express from 'express';\nimport cors from 'cors';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';\nimport { createUIResource } from '@mcp-ui/server';\nimport { registerAppTool, registerAppResource } from '@modelcontextprotocol/ext-apps/server';\n\nimport { randomUUID } from 'crypto';\nimport { z } from 'zod';\n\nconst app = express();\nconst port = 3001;\n\napp.use(\n  cors({\n    origin: '*',\n    exposedHeaders: ['Mcp-Session-Id'],\n    allowedHeaders: ['*'],\n  }),\n);\napp.use(express.json());\n\n// Map to store transports by session ID, as shown in the documentation.\nconst transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};\n\n// Handle POST requests for client-to-server communication.\napp.post('/mcp', async (req, res) => {\n  const sessionId = req.headers['mcp-session-id'] as string | undefined;\n  let transport: StreamableHTTPServerTransport;\n\n  if (sessionId && transports[sessionId]) {\n    // A session already exists; reuse the existing transport.\n    transport = transports[sessionId];\n  } else if (!sessionId && isInitializeRequest(req.body)) {\n    // This is a new initialization request. Create a new transport.\n    transport = new StreamableHTTPServerTransport({\n      sessionIdGenerator: () => randomUUID(),\n      onsessioninitialized: (sid) => {\n        transports[sid] = transport;\n        console.log(`MCP Session initialized: ${sid}`);\n      },\n    });\n\n    // Clean up the transport from our map when the session closes.\n    transport.onclose = () => {\n      if (transport.sessionId) {\n        console.log(`MCP Session closed: ${transport.sessionId}`);\n        delete transports[transport.sessionId];\n      }\n    };\n\n    // Create a new server instance for this specific session.\n    const server = new McpServer({\n      name: 'mcp-apps-demo',\n      version: '1.0.0',\n    });\n\n    // Register a tool with a UI interface using the MCP Apps adapter\n    const weatherDashboardUI = await createUIResource({\n      uri: 'ui://weather-server/dashboard-template',\n      encoding: 'text',\n      content: {\n        type: 'rawHtml',\n        htmlString: `\n<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>MCP Apps Adapter Demo</title>\n  <style>\n    * { box-sizing: border-box; }\n    body {\n      font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n      padding: 16px;\n      margin: 0;\n      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n      min-height: 100vh;\n      color: #333;\n    }\n    .container {\n      max-width: 500px;\n      margin: 0 auto;\n    }\n    .card {\n      background: white;\n      border-radius: 16px;\n      padding: 24px;\n      box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);\n      margin-bottom: 16px;\n    }\n    h1 { \n      margin: 0 0 8px 0; \n      color: #667eea;\n      font-size: 24px;\n    }\n    h2 {\n      margin: 0 0 16px 0;\n      color: #333;\n      font-size: 18px;\n      font-weight: 600;\n    }\n    .subtitle {\n      color: #64748b;\n      font-size: 14px;\n      margin-bottom: 20px;\n    }\n    .data-display {\n      background: #f8fafc;\n      border-radius: 12px;\n      padding: 16px;\n      margin-bottom: 20px;\n    }\n    .data-row {\n      display: flex;\n      justify-content: space-between;\n      padding: 8px 0;\n      border-bottom: 1px solid #e2e8f0;\n    }\n    .data-row:last-child { border-bottom: none; }\n    .data-label { color: #64748b; font-size: 14px; }\n    .data-value { color: #1e293b; font-weight: 600; }\n    .actions-grid {\n      display: grid;\n      grid-template-columns: 1fr 1fr;\n      gap: 12px;\n    }\n    button {\n      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n      color: white;\n      border: none;\n      padding: 12px 16px;\n      border-radius: 8px;\n      cursor: pointer;\n      font-size: 14px;\n      font-weight: 500;\n      transition: transform 0.2s, box-shadow 0.2s;\n    }\n    button:hover { \n      transform: translateY(-2px);\n      box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);\n    }\n    button:active { transform: translateY(0); }\n    button.secondary {\n      background: #f1f5f9;\n      color: #475569;\n    }\n    button.secondary:hover {\n      background: #e2e8f0;\n      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n    }\n    .full-width { grid-column: 1 / -1; }\n    .log-area {\n      background: #1e293b;\n      border-radius: 8px;\n      padding: 12px;\n      font-family: 'SF Mono', Monaco, 'Courier New', monospace;\n      font-size: 12px;\n      color: #94a3b8;\n      max-height: 150px;\n      overflow-y: auto;\n    }\n    .log-entry { margin-bottom: 4px; }\n    .log-entry.success { color: #4ade80; }\n    .log-entry.info { color: #60a5fa; }\n    .log-entry.warn { color: #fbbf24; }\n    .status-badge {\n      display: inline-block;\n      padding: 4px 12px;\n      border-radius: 20px;\n      font-size: 12px;\n      font-weight: 600;\n    }\n    .status-badge.connected {\n      background: #dcfce7;\n      color: #166534;\n    }\n    .status-badge.disconnected {\n      background: #fee2e2;\n      color: #991b1b;\n    }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <div class=\"card\">\n      <h1>🔌 MCP Apps Adapter Demo</h1>\n      <p class=\"subtitle\">Testing MCP-UI actions through the MCP Apps adapter</p>\n      \n      <div class=\"data-display\">\n        <div class=\"data-row\">\n          <span class=\"data-label\">Status</span>\n          <span id=\"status\" class=\"status-badge disconnected\">Waiting...</span>\n        </div>\n        <div class=\"data-row\">\n          <span class=\"data-label\">Tool Input</span>\n          <span id=\"toolInput\" class=\"data-value\">--</span>\n        </div>\n        <div class=\"data-row\">\n          <span class=\"data-label\">Tool Output</span>\n          <span id=\"toolOutput\" class=\"data-value\">--</span>\n        </div>\n      </div>\n    </div>\n\n    <div class=\"card\">\n      <h2>📤 MCP-UI Actions</h2>\n      <div class=\"actions-grid\">\n        <button onclick=\"sendNotify()\">\n          📢 Send Notify\n        </button>\n        <button onclick=\"sendLink()\">\n          🔗 Open Link\n        </button>\n        <button onclick=\"sendPrompt()\">\n          💬 Send Prompt\n        </button>\n        <button onclick=\"sendIntent()\">\n          🎯 Send Intent\n        </button>\n        <button onclick=\"sendSizeChange()\" class=\"secondary\">\n          📐 Resize Widget\n        </button>\n        <button onclick=\"callTool()\" class=\"secondary\">\n          🔧 Call Tool\n        </button>\n        <button onclick=\"refreshData()\" class=\"full-width\">\n          🔄 Request Render Data\n        </button>\n      </div>\n    </div>\n\n    <div class=\"card\">\n      <h2>📋 Event Log</h2>\n      <div id=\"log\" class=\"log-area\">\n        <div class=\"log-entry info\">Waiting for adapter initialization...</div>\n      </div>\n    </div>\n  </div>\n\n  <script>\n    // Log helper\n    function log(message, type = 'info') {\n      const logEl = document.getElementById('log');\n      const entry = document.createElement('div');\n      entry.className = 'log-entry ' + type;\n      entry.textContent = new Date().toLocaleTimeString() + ' - ' + message;\n      logEl.appendChild(entry);\n      logEl.scrollTop = logEl.scrollHeight;\n    }\n\n    // Update status\n    function setStatus(connected) {\n      const el = document.getElementById('status');\n      el.textContent = connected ? 'Connected' : 'Disconnected';\n      el.className = 'status-badge ' + (connected ? 'connected' : 'disconnected');\n    }\n\n    // Listen for messages from adapter\n    window.addEventListener('message', (event) => {\n      const data = event.data;\n      if (!data || !data.type) return;\n\n      log('Received: ' + data.type, 'info');\n\n      switch (data.type) {\n        case 'ui-lifecycle-iframe-render-data':\n          setStatus(true);\n          const renderData = data.payload?.renderData || {};\n          \n          if (renderData.toolInput) {\n            document.getElementById('toolInput').textContent = \n              JSON.stringify(renderData.toolInput).substring(0, 30) + '...';\n            log('Tool input received: ' + JSON.stringify(renderData.toolInput), 'success');\n          }\n          \n          if (renderData.toolOutput) {\n            document.getElementById('toolOutput').textContent = \n              JSON.stringify(renderData.toolOutput).substring(0, 30) + '...';\n            log('Tool output received', 'success');\n          }\n          break;\n\n        case 'ui-message-received':\n          log('Message acknowledged: ' + data.payload?.messageId, 'info');\n          break;\n\n        case 'ui-message-response':\n          if (data.payload?.error) {\n            log('Response error: ' + JSON.stringify(data.payload.error), 'warn');\n          } else {\n            log('Response received: ' + JSON.stringify(data.payload?.response || {}), 'success');\n          }\n          break;\n      }\n    });\n\n    // Helper to send MCP-UI messages\n    function sendMessage(type, payload) {\n      const messageId = 'msg-' + Date.now();\n      log('Sending: ' + type, 'info');\n      window.parent.postMessage({ type, messageId, payload }, '*');\n      return messageId;\n    }\n\n    // Action handlers\n    function sendNotify() {\n      sendMessage('notify', { \n        message: 'Hello from MCP-UI widget! Time: ' + new Date().toLocaleTimeString() \n      });\n    }\n\n    function sendLink() {\n      sendMessage('link', { \n        url: 'https://github.com/modelcontextprotocol/ext-apps' \n      });\n    }\n\n    function sendPrompt() {\n      sendMessage('prompt', { \n        prompt: 'What is the weather like today?' \n      });\n    }\n\n    function sendIntent() {\n      sendMessage('intent', { \n        intent: 'get_forecast',\n        params: { days: 7, location: 'San Francisco' }\n      });\n    }\n\n    function sendSizeChange() {\n      const height = 300 + Math.floor(Math.random() * 200);\n      sendMessage('ui-size-change', { \n        width: 500,\n        height: height\n      });\n      log('Requested size: 500x' + height, 'info');\n    }\n\n    function callTool() {\n      sendMessage('tool', { \n        toolName: 'weather_dashboard',\n        params: { location: 'New York' }\n      });\n    }\n\n    function refreshData() {\n      sendMessage('ui-request-render-data', {});\n    }\n\n    // Signal ready state\n    log('Sending ready signal...', 'info');\n    window.parent.postMessage({ type: 'ui-lifecycle-iframe-ready' }, '*');\n  </script>\n</body>\n</html>\n        `,\n      },\n      adapters: {\n        // Enable the MCP Apps adapter\n        mcpApps: {\n          enabled: true,\n        },\n      },\n    });\n\n    // Register the UI resource so the host can fetch it\n    registerAppResource(\n      server,\n      'weather_dashboard_ui',\n      weatherDashboardUI.resource.uri,\n      {},\n      async () => ({\n        contents: [weatherDashboardUI.resource],\n      }),\n    );\n\n    // Register the tool with _meta linking to the UI resource\n    registerAppTool(\n      server,\n      'weather_dashboard',\n      {\n        description: 'Interactive weather dashboard widget',\n        inputSchema: {\n          location: z.string().describe('City name'),\n        },\n        // This tells MCP Apps hosts where to find the UI\n        _meta: {\n          ui: {\n            resourceUri: weatherDashboardUI.resource.uri,\n          },\n        },\n      },\n      async ({ location }) => {\n        // In a real app, we might fetch data here\n        return {\n          content: [{ type: 'text', text: `Weather dashboard for ${location}` }],\n        };\n      },\n    );\n\n    // Connect the server instance to the transport for this session.\n    await server.connect(transport);\n  } else {\n    return res.status(400).json({\n      error: { message: 'Bad Request: No valid session ID provided' },\n    });\n  }\n\n  // Handle the client's request using the session's transport.\n  await transport.handleRequest(req, res, req.body);\n});\n\n// A separate, reusable handler for GET and DELETE requests.\nconst handleSessionRequest = async (req: express.Request, res: express.Response) => {\n  const sessionId = req.headers['mcp-session-id'] as string | undefined;\n  console.log('sessionId', sessionId);\n  if (!sessionId || !transports[sessionId]) {\n    return res.status(404).send('Session not found');\n  }\n\n  const transport = transports[sessionId];\n  await transport.handleRequest(req, res);\n};\n\n// GET handles the long-lived stream for server-to-client messages.\napp.get('/mcp', handleSessionRequest);\n\n// DELETE handles explicit session termination from the client.\napp.delete('/mcp', handleSessionRequest);\n\napp.listen(port, () => {\n  console.log(`MCP Apps Demo Server running at http://localhost:${port}`);\n});\n"
  },
  {
    "path": "examples/mcp-apps-demo/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\",\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./src\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true\n  },\n  \"include\": [\"src/**/*\"]\n}\n"
  },
  {
    "path": "examples/python-server-demo/README.md",
    "content": "# Python MCP Server Demo\n\nA Python MCP server implementation inspired by the TypeScript server demo. This server demonstrates how to create MCP UI resources using the MCP UI Server SDK.\n\n## Features\n\nThis server provides multiple tools that demonstrate different types of UI resources and metadata capabilities:\n\n### Basic UI Resources\n- **show_external_url** - Creates a UI resource displaying an external URL (example.com) with preferred frame size metadata\n- **show_raw_html** - Creates a UI resource with raw HTML content\n- **show_remote_dom** - Creates a UI resource with remote DOM script using React framework\n- **show_action_html** - Creates a UI resource with interactive buttons demonstrating intent actions\n\n## Installation\n\nThis project uses [uv](https://github.com/astral-sh/uv) for dependency management.\n\n```bash\n# Install dependencies\nuv sync\n\n# Or install manually\nuv add mcp mcp-ui-server\n```\n\n## Running the Server\n\n```bash\n# Using uv\nuv run python_server_demo.py\n\n# Or directly with Python\npython python_server_demo.py\n```\n\nThe server uses stdio transport and communicates via stdin/stdout using the MCP protocol.\n\n## Usage with MCP Clients\n\nThis server can be connected to by any MCP client that supports stdio transport. The server will:\n\n1. Initialize the MCP connection\n2. List the three available tools\n3. Handle tool calls and return UI resources\n\n### Example Tool Calls\n\nEach tool returns an MCP resource that can be rendered by MCP UI clients:\n\n- **External URL**: Returns a resource that displays example.com in an iframe\n- **Raw HTML**: Returns a resource with HTML content `<h1>Hello from Raw HTML</h1>`\n- **Remote DOM**: Returns a resource with JavaScript that creates UI elements dynamically\n\n## UI Metadata\n\nThe SDK supports UI metadata through the `uiMetadata` parameter in `create_ui_resource()`.\n\n### Metadata Keys\n\nUse the `UIMetadataKey` constants for type-safe metadata keys:\n\n- **`UIMetadataKey.PREFERRED_FRAME_SIZE`**: CSS dimensions for the iframe\n  - Type: `list[str, str]` - [width, height] as CSS dimension strings\n  - Examples: `[\"800px\", \"600px\"]`, `[\"100%\", \"50vh\"]`, `[\"50rem\", \"80%\"]`\n  - Must include CSS units (px, %, vh, vw, rem, em, etc.)\n\n- **`UIMetadataKey.INITIAL_RENDER_DATA`**: Initial data for the UI component\n  - Type: `dict[str, Any]` - Any JSON-serializable dictionary\n\n### How It Works\n\n1. All `uiMetadata` keys are automatically prefixed with `mcpui.dev/ui-`\n2. Prefixed metadata is merged with any custom `metadata`\n3. The combined metadata is added to the resource's `_meta` field\n4. Custom metadata keys are preserved as-is (not prefixed)\n\n### Example Usage\n\n```python\nfrom mcp_ui_server import create_ui_resource, UIMetadataKey\n\nui_resource = create_ui_resource({\n    \"uri\": \"ui://my-component\",\n    \"content\": {\n        \"type\": \"rawHtml\",\n        \"htmlString\": \"<h1>Hello</h1>\"\n    },\n    \"encoding\": \"text\",\n    \"uiMetadata\": {\n        UIMetadataKey.PREFERRED_FRAME_SIZE: [\"1200px\", \"800px\"],\n        # Or use string literal: \"preferred-frame-size\": [\"1200px\", \"800px\"]\n    },\n    # Optional: custom metadata (not prefixed)\n    \"metadata\": {\n        \"custom.author\": \"My Server\",\n        \"custom.version\": \"1.0.0\"\n    }\n})\n```\n\n\n## Development\n\n```bash\n# Install dev dependencies\nuv sync --dev\n\n# Run linting\nuv run ruff check .\n\n# Run tests (if any)\nuv run pytest\n```\n\n## Architecture\n\n- **Transport**: stdio (standard input/output)\n- **Framework**: MCP Python SDK\n- **UI Resources**: Created using mcp-ui-server SDK\n- **Session Management**: Handled by MCP SDK\n\n## Comparison to TypeScript Demo\n\n| Feature | TypeScript Demo | Python Demo |\n|---------|----------------|-------------|\n| Transport | HTTP | stdio |\n| Framework | Express.js | MCP Python SDK |\n| Tools | 3 UI tools | Same 3 UI tools |\n| Session Management | Manual | SDK handled |\n| Deployment | Web server | CLI/Desktop integration |"
  },
  {
    "path": "examples/python-server-demo/pyproject.toml",
    "content": "[project]\nname = \"python-server-demo\"\nversion = \"0.1.0\"\ndescription = \"Python MCP server demo inspired by the TypeScript version\"\nreadme = \"README.md\"\nrequires-python = \">=3.10\"\ndependencies = [\n    \"mcp>=1.0.0\",\n    \"mcp-ui-server\",\n]\nauthors = [\n    {name = \"MCP UI Contributors\"}\n]\nlicense = {text = \"Apache-2.0\"}\n\n[project.urls]\nHomepage = \"https://mcpui.dev\"\nRepository = \"https://github.com/idosal/mcp-ui\"\nDocumentation = \"https://mcpui.dev\"\n\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[tool.hatch.metadata]\nallow-direct-references = true\n\n[dependency-groups]\ndev = [\n    \"pytest>=7.0.0\",\n    \"ruff>=0.1.0\"\n]\n\n[tool.uv.sources]\nmcp-ui-server = { path = \"../../sdks/python/server\" }\n\n[project.scripts]\npython-server-demo = \"python_server_demo:main\"\n"
  },
  {
    "path": "examples/python-server-demo/python_server_demo.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Python MCP server demo inspired by the TypeScript version.\n\nThis server demonstrates how to create an MCP server with UI resources\nusing FastMCP for both stdio and HTTP transports.\n\nUsage:\n    python python_server_demo.py                    # stdio transport  \n    python python_server_demo.py --http --port 3000 # HTTP transport\n\"\"\"\n\nimport argparse\nfrom mcp.server.fastmcp import FastMCP\n\nfrom mcp_ui_server import create_ui_resource, UIMetadataKey\nfrom mcp_ui_server.core import UIResource\n\n# Create FastMCP instance\nmcp = FastMCP(\"python-server-demo\")\n\n@mcp.tool()\ndef show_external_url() -> list[UIResource]:\n    \"\"\"Creates a UI resource displaying an external URL (example.com) with preferred frame size.\"\"\"\n    ui_resource = create_ui_resource({\n        \"uri\": \"ui://greeting\",\n        \"content\": {\n            \"type\": \"externalUrl\",\n            \"iframeUrl\": \"https://example.com\"\n        },\n        \"encoding\": \"text\",\n        \"uiMetadata\": {\n            UIMetadataKey.PREFERRED_FRAME_SIZE: [\"800px\", \"600px\"]  # CSS dimension strings (can be px, %, vh, etc.)\n        }\n    })\n    return [ui_resource]\n\n\n@mcp.tool()\ndef show_raw_html() -> list[UIResource]:\n    \"\"\"Creates a UI resource displaying raw HTML.\"\"\"\n    ui_resource = create_ui_resource({\n        \"uri\": \"ui://raw-html-demo\",\n        \"content\": {\n            \"type\": \"rawHtml\",\n            \"htmlString\": \"<h1>Hello from Raw HTML</h1>\"\n        },\n        \"encoding\": \"text\"\n    })\n    return [ui_resource]\n\n\n@mcp.tool()\ndef show_action_html() -> list[UIResource]:\n    \"\"\"Creates a UI resource with interactive buttons that demonstrate intent actions.\"\"\"\n    interactive_html = \"\"\"\n    <div style=\"padding: 20px; font-family: Arial, sans-serif;\">\n        <h2>Intent Action Demo</h2>\n        <p>Click the buttons below to trigger different intent actions:</p>\n        \n        <div style=\"margin: 10px 0;\">\n            <button onclick=\"sendIntent('user_profile', {userId: '123', action: 'view'})\" \n                    style=\"background: #007cba; color: white; padding: 8px 16px; border: none; border-radius: 4px; margin: 5px; cursor: pointer;\">\n                View User Profile\n            </button>\n        </div>\n        \n        <div style=\"margin: 10px 0;\">\n            <button onclick=\"sendIntent('navigation', {page: 'dashboard', section: 'analytics'})\" \n                    style=\"background: #28a745; color: white; padding: 8px 16px; border: none; border-radius: 4px; margin: 5px; cursor: pointer;\">\n                Navigate to Dashboard\n            </button>\n        </div>\n        \n        <div style=\"margin: 10px 0;\">\n            <button onclick=\"sendIntent('data_export', {format: 'csv', dateRange: '30days'})\" \n                    style=\"background: #ffc107; color: black; padding: 8px 16px; border: none; border-radius: 4px; margin: 5px; cursor: pointer;\">\n                Export Data\n            </button>\n        </div>\n        \n        <div style=\"margin: 10px 0;\">\n            <button onclick=\"sendIntent('notification_settings', {type: 'email', enabled: true})\" \n                    style=\"background: #dc3545; color: white; padding: 8px 16px; border: none; border-radius: 4px; margin: 5px; cursor: pointer;\">\n                Update Notifications\n            </button>\n        </div>\n        \n        <div id=\"status\" style=\"margin-top: 20px; padding: 10px; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px;\">\n            Click a button to see the intent action in action!\n        </div>\n    </div>\n    \n    <script>\n        function sendIntent(intent, params) {\n            const status = document.getElementById('status');\n            status.innerHTML = `<strong>Intent Sent:</strong> ${intent}<br><strong>Parameters:</strong> ${JSON.stringify(params, null, 2)}`;\n            \n            // Send the intent to the parent frame\n            if (window.parent) {\n                window.parent.postMessage({\n                    type: 'intent',\n                    payload: {\n                        intent: intent,\n                        params: params\n                    }\n                }, '*');\n            }\n        }\n    </script>\n    \"\"\"\n\n    ui_resource = create_ui_resource({\n        \"uri\": \"ui://action-html-demo\",\n        \"content\": {\n            \"type\": \"rawHtml\",\n            \"htmlString\": interactive_html\n        },\n        \"encoding\": \"text\"\n    })\n\n    return [ui_resource]\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(description=\"Python MCP Server Demo\")\n    parser.add_argument(\"--http\", action=\"store_true\", help=\"Use HTTP transport instead of stdio\")\n    parser.add_argument(\"--port\", type=int, default=3000, help=\"Port for HTTP transport (default: 3000)\")\n    args = parser.parse_args()\n\n    if args.http:\n        print(\"🚀 Starting Python MCP server on HTTP (SSE transport)\")\n        print(\"📡 Server will use SSE transport settings\")\n        mcp.settings.port=args.port\n        mcp.run(transport=\"sse\")\n    else:\n        print(\"🚀 Starting Python MCP server with stdio transport\")\n        mcp.run()\n"
  },
  {
    "path": "examples/ruby-server-demo/Gemfile",
    "content": "source \"https://rubygems.org\"\n\ngem \"mcp\", git: \"https://github.com/modelcontextprotocol/ruby-sdk\"\ngem \"mcp_ui_server\", path: \"../../sdks/ruby\"\ngem \"webrick\" "
  },
  {
    "path": "examples/ruby-server-demo/README.md",
    "content": "# ruby-server-demo\n\nThis barebones server demonstrates how to use `mcp_ui_server` to generate three types of UI resources: `externalUrl`, `rawHtml`, and `remoteDom`.\n\nEach resource type has a dedicated tool:\n- `ExternalUrlTool`: Returns a resource that renders an external webpage (`https://example.com`) in an iframe.\n- `RawHtmlTool`: Returns a resource containing a simple raw HTML string.\n- `RemoteDomTool`: Returns a resource with a JavaScript snippet that manipulates the DOM of the client application.\n\nFor a detailed explanation of how this server works, see the [Ruby Server Walkthrough](https://mcpui.dev/guide/server/ruby/walkthrough).\n\n## Running the server\n\n1. **Install dependencies:**\n   ```sh\n   bundle install\n   ```\n2. **Start the server:**\n   ```sh\n   ruby server.rb\n   ```\n   The server will be running at `http://localhost:8081/mcp`.\n\nYou can view the UI resources from this server by connecting to it with the [`ui-inspector`](https://github.com/idosal/ui-inspector) (target `http://localhost:8081/mcp`)."
  },
  {
    "path": "examples/ruby-server-demo/server.rb",
    "content": "require 'mcp'\nrequire 'mcp_ui_server'\nrequire 'webrick'\nrequire 'json'\n\n# --- Define MCP Tools ---\nclass ExternalUrlTool < MCP::Tool\n  description 'A simple tool that returns an external URL resource'\n  input_schema(\n    type: 'object',\n    properties: {}\n  )\n\n  class << self\n    def call(server_context:)\n      # create_ui_resource returns a UI Resource object of the form:\n      # { type: 'resource', resource: { ... } }\n      ui_resource_object = McpUiServer.create_ui_resource(\n        uri: 'ui://my-external-site',\n        content: { type: :external_url, iframeUrl: 'https://example.com' },\n        encoding: :text\n      )\n\n      MCP::Tool::Response.new([ui_resource_object])\n    end\n  end\nend\n\nclass RawHtmlTool < MCP::Tool\n  description 'A simple tool that returns a raw HTML resource'\n  input_schema(\n    type: 'object',\n    properties: {}\n  )\n\n  class << self\n    def call(server_context:)\n      ui_resource_object = McpUiServer.create_ui_resource(\n        uri: 'ui://my-raw-html',\n        content: { type: :raw_html, htmlString: '<p>This is some raw HTML content.</p>' },\n        encoding: :text\n      )\n\n      MCP::Tool::Response.new([ui_resource_object])\n    end\n  end\nend\n\n# --- MCP Server Setup ---\nmcp_server = MCP::Server.new(tools: [ExternalUrlTool, RawHtmlTool])\n\n# --- WEBrick HTTP Server Setup ---\nhttp_server = WEBrick::HTTPServer.new(Port: 8081)\n\n# Create a servlet to handle requests to /mcp\nclass MCPServlet < WEBrick::HTTPServlet::AbstractServlet\n  def initialize(server, mcp_instance)\n    super(server)\n    @mcp = mcp_instance\n  end\n\n  def do_OPTIONS(_request, response)\n    response.status = 200\n    response['Access-Control-Allow-Origin'] = '*'\n    response['Access-Control-Allow-Methods'] = 'POST, OPTIONS'\n    response['Access-Control-Allow-Headers'] = 'Content-Type, Accept'\n  end\n\n  def do_POST(request, response)\n    response['Access-Control-Allow-Origin'] = '*'\n    response.status = 200\n    response['Content-Type'] = 'application/json'\n    response.body = @mcp.handle_json(request.body)\n  end\nend\n\n# Mount the servlet at the /mcp endpoint\nhttp_server.mount('/mcp', MCPServlet, mcp_server)\n\n# Start the server and handle shutdown\ntrap('INT') { http_server.shutdown }\nputs 'MCP server running on http://localhost:8081/mcp'\nhttp_server.start"
  },
  {
    "path": "examples/server/.react-router/types/+future.ts",
    "content": "// Generated by React Router\n\nimport 'react-router';\n\ndeclare module 'react-router' {\n  interface Future {\n    unstable_middleware: false;\n  }\n}\n"
  },
  {
    "path": "examples/server/.react-router/types/+routes.ts",
    "content": "// Generated by React Router\n\nimport 'react-router';\n\ndeclare module 'react-router' {\n  interface Register {\n    pages: Pages;\n    routeFiles: RouteFiles;\n  }\n}\n\ntype Pages = {\n  '/': {\n    params: {};\n  };\n  '/task': {\n    params: {};\n  };\n  '/user': {\n    params: {};\n  };\n};\n\ntype RouteFiles = {\n  'root.tsx': {\n    id: 'root';\n    page: '/' | '/task' | '/user';\n  };\n  'routes/home.tsx': {\n    id: 'routes/home';\n    page: '/';\n  };\n  'routes/task.tsx': {\n    id: 'routes/task';\n    page: '/task';\n  };\n  'routes/user.tsx': {\n    id: 'routes/user';\n    page: '/user';\n  };\n};\n"
  },
  {
    "path": "examples/server/.react-router/types/+server-build.d.ts",
    "content": "// Generated by React Router\n\ndeclare module 'virtual:react-router/server-build' {\n  import { ServerBuild } from 'react-router';\n  export const assets: ServerBuild['assets'];\n  export const assetsBuildDirectory: ServerBuild['assetsBuildDirectory'];\n  export const basename: ServerBuild['basename'];\n  export const entry: ServerBuild['entry'];\n  export const future: ServerBuild['future'];\n  export const isSpaMode: ServerBuild['isSpaMode'];\n  export const prerender: ServerBuild['prerender'];\n  export const publicPath: ServerBuild['publicPath'];\n  export const routeDiscovery: ServerBuild['routeDiscovery'];\n  export const routes: ServerBuild['routes'];\n  export const ssr: ServerBuild['ssr'];\n  export const unstable_getCriticalCss: ServerBuild['unstable_getCriticalCss'];\n}\n"
  },
  {
    "path": "examples/server/.react-router/types/app/+types/root.ts",
    "content": "// Generated by React Router\n\nimport type { GetInfo, GetAnnotations } from 'react-router/internal';\n\ntype Module = typeof import('../root.js');\n\ntype Info = GetInfo<{\n  file: 'root.tsx';\n  module: Module;\n}>;\n\ntype Matches = [\n  {\n    id: 'root';\n    module: typeof import('../root.js');\n  },\n];\n\ntype Annotations = GetAnnotations<Info & { module: Module; matches: Matches }>;\n\nexport namespace Route {\n  // links\n  export type LinkDescriptors = Annotations['LinkDescriptors'];\n  export type LinksFunction = Annotations['LinksFunction'];\n\n  // meta\n  export type MetaArgs = Annotations['MetaArgs'];\n  export type MetaDescriptors = Annotations['MetaDescriptors'];\n  export type MetaFunction = Annotations['MetaFunction'];\n\n  // headers\n  export type HeadersArgs = Annotations['HeadersArgs'];\n  export type HeadersFunction = Annotations['HeadersFunction'];\n\n  // unstable_middleware\n  export type unstable_MiddlewareFunction = Annotations['unstable_MiddlewareFunction'];\n\n  // unstable_clientMiddleware\n  export type unstable_ClientMiddlewareFunction = Annotations['unstable_ClientMiddlewareFunction'];\n\n  // loader\n  export type LoaderArgs = Annotations['LoaderArgs'];\n\n  // clientLoader\n  export type ClientLoaderArgs = Annotations['ClientLoaderArgs'];\n\n  // action\n  export type ActionArgs = Annotations['ActionArgs'];\n\n  // clientAction\n  export type ClientActionArgs = Annotations['ClientActionArgs'];\n\n  // HydrateFallback\n  export type HydrateFallbackProps = Annotations['HydrateFallbackProps'];\n\n  // Component\n  export type ComponentProps = Annotations['ComponentProps'];\n\n  // ErrorBoundary\n  export type ErrorBoundaryProps = Annotations['ErrorBoundaryProps'];\n}\n"
  },
  {
    "path": "examples/server/.react-router/types/app/routes/+types/home.ts",
    "content": "// Generated by React Router\n\nimport type { GetInfo, GetAnnotations } from 'react-router/internal';\n\ntype Module = typeof import('../home.js');\n\ntype Info = GetInfo<{\n  file: 'routes/home.tsx';\n  module: Module;\n}>;\n\ntype Matches = [\n  {\n    id: 'root';\n    module: typeof import('../../root.js');\n  },\n  {\n    id: 'routes/home';\n    module: typeof import('../home.js');\n  },\n];\n\ntype Annotations = GetAnnotations<Info & { module: Module; matches: Matches }>;\n\nexport namespace Route {\n  // links\n  export type LinkDescriptors = Annotations['LinkDescriptors'];\n  export type LinksFunction = Annotations['LinksFunction'];\n\n  // meta\n  export type MetaArgs = Annotations['MetaArgs'];\n  export type MetaDescriptors = Annotations['MetaDescriptors'];\n  export type MetaFunction = Annotations['MetaFunction'];\n\n  // headers\n  export type HeadersArgs = Annotations['HeadersArgs'];\n  export type HeadersFunction = Annotations['HeadersFunction'];\n\n  // unstable_middleware\n  export type unstable_MiddlewareFunction = Annotations['unstable_MiddlewareFunction'];\n\n  // unstable_clientMiddleware\n  export type unstable_ClientMiddlewareFunction = Annotations['unstable_ClientMiddlewareFunction'];\n\n  // loader\n  export type LoaderArgs = Annotations['LoaderArgs'];\n\n  // clientLoader\n  export type ClientLoaderArgs = Annotations['ClientLoaderArgs'];\n\n  // action\n  export type ActionArgs = Annotations['ActionArgs'];\n\n  // clientAction\n  export type ClientActionArgs = Annotations['ClientActionArgs'];\n\n  // HydrateFallback\n  export type HydrateFallbackProps = Annotations['HydrateFallbackProps'];\n\n  // Component\n  export type ComponentProps = Annotations['ComponentProps'];\n\n  // ErrorBoundary\n  export type ErrorBoundaryProps = Annotations['ErrorBoundaryProps'];\n}\n"
  },
  {
    "path": "examples/server/.react-router/types/app/routes/+types/task.ts",
    "content": "// Generated by React Router\n\nimport type { GetInfo, GetAnnotations } from 'react-router/internal';\n\ntype Module = typeof import('../task.js');\n\ntype Info = GetInfo<{\n  file: 'routes/task.tsx';\n  module: Module;\n}>;\n\ntype Matches = [\n  {\n    id: 'root';\n    module: typeof import('../../root.js');\n  },\n  {\n    id: 'routes/task';\n    module: typeof import('../task.js');\n  },\n];\n\ntype Annotations = GetAnnotations<Info & { module: Module; matches: Matches }>;\n\nexport namespace Route {\n  // links\n  export type LinkDescriptors = Annotations['LinkDescriptors'];\n  export type LinksFunction = Annotations['LinksFunction'];\n\n  // meta\n  export type MetaArgs = Annotations['MetaArgs'];\n  export type MetaDescriptors = Annotations['MetaDescriptors'];\n  export type MetaFunction = Annotations['MetaFunction'];\n\n  // headers\n  export type HeadersArgs = Annotations['HeadersArgs'];\n  export type HeadersFunction = Annotations['HeadersFunction'];\n\n  // unstable_middleware\n  export type unstable_MiddlewareFunction = Annotations['unstable_MiddlewareFunction'];\n\n  // unstable_clientMiddleware\n  export type unstable_ClientMiddlewareFunction = Annotations['unstable_ClientMiddlewareFunction'];\n\n  // loader\n  export type LoaderArgs = Annotations['LoaderArgs'];\n\n  // clientLoader\n  export type ClientLoaderArgs = Annotations['ClientLoaderArgs'];\n\n  // action\n  export type ActionArgs = Annotations['ActionArgs'];\n\n  // clientAction\n  export type ClientActionArgs = Annotations['ClientActionArgs'];\n\n  // HydrateFallback\n  export type HydrateFallbackProps = Annotations['HydrateFallbackProps'];\n\n  // Component\n  export type ComponentProps = Annotations['ComponentProps'];\n\n  // ErrorBoundary\n  export type ErrorBoundaryProps = Annotations['ErrorBoundaryProps'];\n}\n"
  },
  {
    "path": "examples/server/.react-router/types/app/routes/+types/user.ts",
    "content": "// Generated by React Router\n\nimport type { GetInfo, GetAnnotations } from 'react-router/internal';\n\ntype Module = typeof import('../user.js');\n\ntype Info = GetInfo<{\n  file: 'routes/user.tsx';\n  module: Module;\n}>;\n\ntype Matches = [\n  {\n    id: 'root';\n    module: typeof import('../../root.js');\n  },\n  {\n    id: 'routes/user';\n    module: typeof import('../user.js');\n  },\n];\n\ntype Annotations = GetAnnotations<Info & { module: Module; matches: Matches }>;\n\nexport namespace Route {\n  // links\n  export type LinkDescriptors = Annotations['LinkDescriptors'];\n  export type LinksFunction = Annotations['LinksFunction'];\n\n  // meta\n  export type MetaArgs = Annotations['MetaArgs'];\n  export type MetaDescriptors = Annotations['MetaDescriptors'];\n  export type MetaFunction = Annotations['MetaFunction'];\n\n  // headers\n  export type HeadersArgs = Annotations['HeadersArgs'];\n  export type HeadersFunction = Annotations['HeadersFunction'];\n\n  // unstable_middleware\n  export type unstable_MiddlewareFunction = Annotations['unstable_MiddlewareFunction'];\n\n  // unstable_clientMiddleware\n  export type unstable_ClientMiddlewareFunction = Annotations['unstable_ClientMiddlewareFunction'];\n\n  // loader\n  export type LoaderArgs = Annotations['LoaderArgs'];\n\n  // clientLoader\n  export type ClientLoaderArgs = Annotations['ClientLoaderArgs'];\n\n  // action\n  export type ActionArgs = Annotations['ActionArgs'];\n\n  // clientAction\n  export type ClientActionArgs = Annotations['ClientActionArgs'];\n\n  // HydrateFallback\n  export type HydrateFallbackProps = Annotations['HydrateFallbackProps'];\n\n  // Component\n  export type ComponentProps = Annotations['ComponentProps'];\n\n  // ErrorBoundary\n  export type ErrorBoundaryProps = Annotations['ErrorBoundaryProps'];\n}\n"
  },
  {
    "path": "examples/server/README.md",
    "content": "This example features a complete MCP remote server hosted on Cloudflare.\n\nThe server is the standard Cloudflare auth-less boilerplate. The only part relevant to `mcp-ui` is in the tool definitions (`src/index.ts`), where we return a UI resource created using `createUIResource` instead of a string."
  },
  {
    "path": "examples/server/app/app.css",
    "content": "@import 'tailwindcss' source('.');\n\n@theme {\n  --font-sans:\n    'Inter', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',\n    'Segoe UI Symbol', 'Noto Color Emoji';\n}\n\nhtml,\nbody {\n  @apply bg-white dark:bg-gray-950;\n\n  @media (prefers-color-scheme: dark) {\n    color-scheme: dark;\n  }\n}\n"
  },
  {
    "path": "examples/server/app/entry.server.tsx",
    "content": "import type { EntryContext } from 'react-router';\nimport { ServerRouter } from 'react-router';\nimport { isbot } from 'isbot';\nimport { renderToReadableStream } from 'react-dom/server';\n\nexport default async function handleRequest(\n  request: Request,\n  responseStatusCode: number,\n  responseHeaders: Headers,\n  routerContext: EntryContext,\n) {\n  let shellRendered = false;\n  const userAgent = request.headers.get('user-agent');\n\n  const body = await renderToReadableStream(\n    <ServerRouter context={routerContext} url={request.url} />,\n    {\n      onError(error: unknown) {\n        responseStatusCode = 500;\n        // Log streaming rendering errors from inside the shell.  Don't log\n        // errors encountered during initial shell rendering since they'll\n        // reject and get logged in handleDocumentRequest.\n        if (shellRendered) {\n          console.error(error);\n        }\n      },\n    },\n  );\n  shellRendered = true;\n\n  // Ensure requests from bots and SPA Mode renders wait for all content to load before responding\n  // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation\n  if ((userAgent && isbot(userAgent)) || routerContext.isSpaMode) {\n    await body.allReady;\n  }\n\n  responseHeaders.set('Content-Type', 'text/html');\n  return new Response(body, {\n    headers: responseHeaders,\n    status: responseStatusCode,\n  });\n}\n"
  },
  {
    "path": "examples/server/app/graph/graph.tsx",
    "content": "import { useState, useMemo } from 'react';\nimport {\n  BarChart,\n  Bar,\n  XAxis,\n  YAxis,\n  CartesianGrid,\n  Tooltip,\n  ResponsiveContainer,\n  Legend,\n} from 'recharts';\nimport { postMessageToParent } from '../utils/messageUtils';\n\ninterface TaskDetails {\n  remaining: number;\n  toDo: number;\n  inProgress: number;\n  blocked: number;\n}\n\ntype TeamMemberId = 'alice' | 'bob' | 'charlie';\n\ninterface TeamMemberInfo {\n  id: TeamMemberId;\n  name: string;\n  color: string;\n  gradientId: string;\n  avatarUrl: string;\n}\n\ninterface SprintDayDataEntry {\n  date: string;\n  alice: TaskDetails;\n  bob: TaskDetails;\n  charlie: TaskDetails;\n  [key: string]: TaskDetails | string;\n}\n\ninterface ProcessedChartItemFullView {\n  date: string;\n  toDo: number;\n  inProgress: number;\n  blocked: number;\n}\n\ninterface ProcessedChartItemZoomedView {\n  teamMemberId: TeamMemberId;\n  teamMemberName: string;\n  toDo: number;\n  inProgress: number;\n  blocked: number;\n  originalColor: string;\n}\n\nconst teamMembers: TeamMemberInfo[] = [\n  {\n    id: 'alice',\n    name: 'Alice',\n    color: '#26A69A',\n    gradientId: 'gradAlice',\n    avatarUrl: '/avatar1.png',\n  },\n  {\n    id: 'bob',\n    name: 'Bob',\n    color: '#42A5F5',\n    gradientId: 'gradBob',\n    avatarUrl: '/avatar2.png',\n  },\n  {\n    id: 'charlie',\n    name: 'Charlie',\n    color: '#D32F2F',\n    gradientId: 'gradCharlie',\n    avatarUrl: '/avatar3.png',\n  },\n];\n\nconst statusMeta = {\n  toDo: { name: 'To Do', color: '#B0BEC5' }, // Light Grey/Blue - Neutral\n  inProgress: { name: 'In Progress', color: '#FFCA28' }, // Amber - Active\n  blocked: { name: 'Blocked', color: '#EF5350' }, // Soft Red - Urgent\n};\nconst statusKeys = ['toDo', 'inProgress', 'blocked'] as const;\n\n// Original full sprint data\nconst sprintDataFull: SprintDayDataEntry[] = [\n  {\n    date: '5/10',\n    alice: { remaining: 8, toDo: 3, inProgress: 3, blocked: 2 },\n    bob: { remaining: 7, toDo: 2, inProgress: 3, blocked: 2 },\n    charlie: { remaining: 9, toDo: 4, inProgress: 3, blocked: 2 },\n  },\n  {\n    date: '5/11',\n    alice: { remaining: 7, toDo: 2, inProgress: 3, blocked: 2 },\n    bob: { remaining: 6, toDo: 2, inProgress: 2, blocked: 2 },\n    charlie: { remaining: 8, toDo: 3, inProgress: 3, blocked: 2 },\n  },\n  {\n    date: '5/12',\n    alice: { remaining: 9, toDo: 3, inProgress: 4, blocked: 2 },\n    bob: { remaining: 8, toDo: 3, inProgress: 3, blocked: 2 },\n    charlie: { remaining: 10, toDo: 4, inProgress: 4, blocked: 2 },\n  },\n  {\n    date: '5/13',\n    alice: { remaining: 6, toDo: 1, inProgress: 2, blocked: 3 },\n    bob: { remaining: 9, toDo: 3, inProgress: 3, blocked: 3 },\n    charlie: { remaining: 11, toDo: 5, inProgress: 3, blocked: 3 },\n  },\n  {\n    date: '5/14',\n    alice: { remaining: 10, toDo: 4, inProgress: 3, blocked: 3 },\n    bob: { remaining: 9, toDo: 3, inProgress: 3, blocked: 3 },\n    charlie: { remaining: 12, toDo: 5, inProgress: 4, blocked: 3 },\n  },\n  {\n    date: '5/15',\n    alice: { remaining: 11, toDo: 4, inProgress: 4, blocked: 3 },\n    bob: { remaining: 10, toDo: 3, inProgress: 4, blocked: 3 },\n    charlie: { remaining: 13, toDo: 6, inProgress: 4, blocked: 3 },\n  },\n  {\n    date: '5/16',\n    alice: { remaining: 12, toDo: 5, inProgress: 4, blocked: 3 },\n    bob: { remaining: 18, toDo: 11, inProgress: 4, blocked: 3 },\n    charlie: { remaining: 14, toDo: 6, inProgress: 5, blocked: 3 },\n  },\n];\n\n// Process data for the full view (stacked by STATUS, grouped by date)\nconst originalProcessedDataFullView: ProcessedChartItemFullView[] = sprintDataFull.map((day) => {\n  let dayTotalToDo = 0;\n  let dayTotalInProgress = 0;\n  let dayTotalBlocked = 0;\n  teamMembers.forEach((member) => {\n    dayTotalToDo += day[member.id]?.toDo || 0;\n    dayTotalInProgress += day[member.id]?.inProgress || 0;\n    dayTotalBlocked += day[member.id]?.blocked || 0;\n  });\n  return {\n    date: day.date,\n    toDo: dayTotalToDo,\n    inProgress: dayTotalInProgress,\n    blocked: dayTotalBlocked,\n  };\n});\n\n// Mapping statuses to team member gradients for the full view bar colors\nconst fullViewStatusToGradientMapping: Record<(typeof statusKeys)[number], string> = {\n  toDo: teamMembers[0].gradientId, // Alice's gradient (Teal)\n  inProgress: teamMembers[1].gradientId, // Bob's gradient (Blue)\n  blocked: teamMembers[2].gradientId, // Charlie's gradient (Red)\n};\n\n// --- Custom Rounded Bar Shape (used for both views) ---\nconst RoundedBar = (props: {\n  fill: string;\n  x: number;\n  y: number;\n  width: number;\n  height: number;\n}) => {\n  // Reverted to any to match recharts expectations for now\n  const { fill, x, y, width, height } = props;\n  // Use the existing radius calculation. This radius will be applied to all four corners.\n  const radius = Math.min(Math.abs(width), Math.abs(height)) / 6;\n\n  if (height === 0) return <g />;\n\n  // Fallback for very small bars where path calculations might lead to visual glitches.\n  // This condition remains the same.\n  if (Math.abs(height) < radius * 1.5 || Math.abs(width) < radius * 1.5) {\n    return <rect x={x} y={y} width={width} height={height} fill={fill} />;\n  }\n\n  if (height < 0) {\n    // Handle negative values - path unchanged as not currently used and not specified in request\n    const absHeight = Math.abs(height);\n    // This existing path rounds the two corners at the \"bottom\" (y + absHeight) of the negative bar.\n    // For full consistency, if negative bars were used and needed all 4 corners rounded, this would also need changing.\n    const path = `\n      M ${x},${y}\n      L ${x + width},${y}\n      A ${radius},${radius} 0 0 0 ${x + width - radius},${y + absHeight}\n      L ${x + radius},${y + absHeight}\n      A ${radius},${radius} 0 0 0 ${x},${y}\n      Z\n    `;\n    return <path d={path} fill={fill} />;\n  }\n\n  const path = `\n    M ${x + radius},${y}\n    L ${x + width - radius},${y}\n    A ${radius},${radius} 0 0 1 ${x + width},${y + radius}\n    L ${x + width},${y + height - radius}\n    A ${radius},${radius} 0 0 1 ${x + width - radius},${y + height}\n    L ${x + radius},${y + height}\n    A ${radius},${radius} 0 0 1 ${x},${y + height - radius}\n    L ${x},${y + radius}\n    A ${radius},${radius} 0 0 1 ${x + radius},${y}\n    Z\n  `;\n  return <path d={path} fill={fill} />;\n};\n\n// --- Custom Tooltip ---\ninterface CustomTooltipProps {\n  active?: boolean;\n  payload?: Array<{\n    name: string; // Status name like \"To Do\", \"In Progress\", \"Blocked\"\n    value: number; // Count for that status\n    color?: string; // Bar segment color (e.g., from 'fill' prop of Bar)\n    dataKey?: (typeof statusKeys)[number]; // 'toDo', 'inProgress', 'blocked'\n    payload?: any; // The raw data item for the bar\n  }>;\n  label?: string; // Date in full view, teamMemberName in zoomed view\n  isZoomedView: boolean; // Manually passed prop\n}\n\nconst CustomTooltip = ({ active, payload, label, isZoomedView }: CustomTooltipProps) => {\n  if (active && payload && payload.length && label) {\n    const commonStyle = {\n      backgroundColor: 'rgba(40, 40, 40, 0.92)',\n      padding: '10px 14px',\n      border: 'none',\n      borderRadius: '8px',\n      boxShadow: '0 4px 12px rgba(0,0,0,0.25)',\n      fontFamily: \"'Segoe UI', Tahoma, Geneva, Verdana, sans-serif\",\n      fontSize: '13px',\n      color: '#FFFFFF',\n      minWidth: '150px',\n    };\n\n    if (isZoomedView) {\n      // Zoomed-in view: label is teamMemberName\n      // Payload contains { name: \"Status Name\", value: count, dataKey: \"statusKey\" } for that team member\n      const teamMemberName = label;\n\n      return (\n        <div style={commonStyle}>\n          <p\n            style={{\n              margin: 0,\n              fontWeight: '600',\n              opacity: 0.85,\n              fontSize: '0.9em',\n              marginBottom: '6px',\n            }}\n          >\n            {teamMemberName}\n          </p>\n          <ul\n            style={{\n              listStyleType: 'none',\n              paddingLeft: 0,\n              margin: 0,\n              fontSize: '0.9em',\n            }}\n          >\n            {payload.map((entry, index) => {\n              const statusKey = entry.dataKey;\n              if (!statusKey || !statusMeta[statusKey]) return null; // Should not happen with current setup\n\n              const fontWeight = statusKey === 'blocked' ? 'bold' : '500';\n              return (\n                <li\n                  key={`item-${index}-${entry.name}`}\n                  style={{\n                    marginBottom: '4px',\n                    display: 'flex',\n                    justifyContent: 'space-between',\n                  }}\n                >\n                  <span\n                    style={{\n                      color: statusMeta[statusKey].color,\n                      fontWeight: fontWeight,\n                    }}\n                  >\n                    {entry.name}:\n                  </span>\n                  <span style={{ fontWeight: 'bold', marginLeft: '10px' }}>{entry.value}</span>\n                </li>\n              );\n            })}\n          </ul>\n        </div>\n      );\n    } else {\n      // Original full view logic: label is date\n      const dayData = sprintDataFull.find((d) => d.date === label);\n      if (!dayData) return null;\n\n      let totalDayToDo = 0;\n      let totalDayInProgress = 0;\n      let totalDayBlocked = 0;\n\n      teamMembers.forEach((member) => {\n        totalDayToDo += dayData[member.id]?.toDo || 0;\n        totalDayInProgress += dayData[member.id]?.inProgress || 0;\n        totalDayBlocked += dayData[member.id]?.blocked || 0;\n      });\n\n      return (\n        <div style={commonStyle}>\n          <p\n            style={{\n              margin: 0,\n              fontWeight: '600',\n              opacity: 0.85,\n              fontSize: '0.9em',\n              marginBottom: '6px',\n            }}\n          >\n            {label}\n          </p>\n          <ul\n            style={{\n              listStyleType: 'none',\n              paddingLeft: 0,\n              margin: 0,\n              fontSize: '0.9em',\n            }}\n          >\n            <li\n              style={{\n                marginBottom: '4px',\n                display: 'flex',\n                justifyContent: 'space-between',\n              }}\n            >\n              <span style={{ color: statusMeta.toDo.color, fontWeight: '500' }}>\n                {statusMeta.toDo.name}:\n              </span>\n              <span style={{ fontWeight: 'bold', marginLeft: '10px' }}>{totalDayToDo}</span>\n            </li>\n            <li\n              style={{\n                marginBottom: '4px',\n                display: 'flex',\n                justifyContent: 'space-between',\n              }}\n            >\n              <span\n                style={{\n                  color: statusMeta.inProgress.color,\n                  fontWeight: '500',\n                }}\n              >\n                {statusMeta.inProgress.name}:\n              </span>\n              <span style={{ fontWeight: 'bold', marginLeft: '10px' }}>{totalDayInProgress}</span>\n            </li>\n            <li style={{ display: 'flex', justifyContent: 'space-between' }}>\n              <span style={{ color: statusMeta.blocked.color, fontWeight: 'bold' }}>\n                {statusMeta.blocked.name}:\n              </span>\n              <span style={{ fontWeight: 'bold', marginLeft: '10px' }}>{totalDayBlocked}</span>\n            </li>\n          </ul>\n        </div>\n      );\n    }\n  }\n  return null;\n};\n\nconst CustomAvatarXAxisTick = (props: { x: number; y: number; payload: { value: string } }) => {\n  const { x, y, payload } = props;\n  const teamMemberName = payload.value;\n  const memberInfo = teamMembers.find((member) => member.name === teamMemberName);\n  const [isHovered, setIsHovered] = useState(false); // State for hover effect\n\n  const handleAvatarClick = () => {\n    if (memberInfo) {\n      const message = {\n        type: 'tool',\n        payload: {\n          toolName: 'show_user_status',\n          params: {\n            id: memberInfo.id,\n            name: memberInfo.name,\n            avatarUrl: memberInfo.avatarUrl,\n          },\n        },\n      };\n      postMessageToParent(message);\n    }\n  };\n\n  if (memberInfo && memberInfo.avatarUrl) {\n    const baseAvatarSize = 24; // Diameter of the avatar\n    const yPositionOffset = 8; // Determines vertical placement relative to Recharts y\n\n    const scale = isHovered ? 1.15 : 1.0; // Scale factor for hover effect\n\n    // Transform to:\n    // 1. Translate to the intended center of the avatar (x, y + yPositionOffset).\n    // 2. Apply scaling.\n    // 3. Translate back by half of the base size, so the image (drawn at 0,0) is centered before scaling.\n    const transformValue = `translate(${x}, ${y + yPositionOffset}) scale(${scale}) translate(${-baseAvatarSize / 2}, ${-baseAvatarSize / 2})`;\n\n    return (\n      <g\n        transform={transformValue}\n        onClick={handleAvatarClick}\n        style={{ cursor: 'pointer', transition: 'transform 0.1s ease-out' }} // Added transition\n        onMouseEnter={() => setIsHovered(true)}\n        onMouseLeave={() => setIsHovered(false)}\n      >\n        <image\n          href={memberInfo.avatarUrl}\n          x={0} // Image is drawn from the (0,0) of the transformed <g>\n          y={0}\n          height={baseAvatarSize} // Image base size is constant\n          width={baseAvatarSize} // Image base size is constant\n          clipPath=\"url(#clipCircle)\" // Clip path is for a 24x24 image starting at 0,0\n        />\n        {isHovered && (\n          <g>\n            <rect\n              x={baseAvatarSize + 8} // Position 8px to the right of the avatar (increased from 4px)\n              y={(baseAvatarSize - 16) / 2} // Vertically center with the avatar\n              width={70} // Width of the tooltip background (increased from 44px)\n              height={16} // Height of the tooltip background\n              rx={3} // Rounded corners for the background\n              ry={3}\n              fill=\"rgb(50, 50, 50)\" // Solid dark background\n            />\n            <text\n              x={baseAvatarSize + 8 + 70 / 2} // Horizontally center text in the rect (increased x offset and width)\n              y={baseAvatarSize / 2} // Vertically center text with the avatar\n              fill=\"#FFFFFF\"\n              fontSize=\"10px\"\n              textAnchor=\"middle\"\n              dominantBaseline=\"middle\" // Ensure proper vertical alignment of text\n              style={{ pointerEvents: 'none' }} // Ensure text doesn't interfere with mouse events on avatar\n            >\n              Show profile\n            </text>\n          </g>\n        )}\n      </g>\n    );\n  }\n  // Fallback to text if no avatar\n  return <div>name</div>;\n};\n\n// --- Graph Component ---\nexport function Graph() {\n  const [zoomedDate, setZoomedDate] = useState<string | null>(null);\n\n  const isZoomed = !!zoomedDate;\n\n  const chartData = useMemo(() => {\n    if (isZoomed && zoomedDate) {\n      const dayEntry = sprintDataFull.find((d) => d.date === zoomedDate);\n      if (!dayEntry) return [];\n      return teamMembers.map((member) => ({\n        teamMemberId: member.id,\n        teamMemberName: member.name,\n        toDo: dayEntry[member.id]?.toDo || 0,\n        inProgress: dayEntry[member.id]?.inProgress || 0,\n        blocked: dayEntry[member.id]?.blocked || 0,\n        originalColor: member.color,\n      })) as ProcessedChartItemZoomedView[];\n    }\n    return originalProcessedDataFullView;\n  }, [isZoomed, zoomedDate]);\n\n  const handleChartClick = (clickEventData?: { activeLabel?: string }) => {\n    if (isZoomed || !clickEventData || !clickEventData.activeLabel) {\n      return;\n    }\n    const clickedDate = clickEventData.activeLabel;\n    setZoomedDate(clickedDate); // Directly set zoomedDate on single click\n  };\n\n  return (\n    <div\n      style={{\n        fontFamily:\n          \"'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, 'Helvetica Neue', sans-serif\",\n        backgroundColor: '#F8F9FA',\n        boxShadow: '0 6px 18px rgba(0,0,0,0.08)',\n        margin: 'auto',\n        position: 'relative',\n      }}\n    >\n      <div style={{ width: '100%', height: 425 }}>\n        <ResponsiveContainer width=\"100%\" height=\"100%\">\n          <BarChart\n            data={\n              chartData as {\n                date: string;\n                toDo: number;\n                inProgress: number;\n                blocked: number;\n              }[]\n            }\n            margin={{\n              top: 5,\n              right: isZoomed ? 20 : 5,\n              left: isZoomed ? 5 : 10,\n              bottom: isZoomed ? 25 : 20,\n            }} // Increased bottom margin for zoomed view avatars\n            onClick={isZoomed ? undefined : handleChartClick}\n            barCategoryGap={isZoomed ? '25%' : '30%'}\n            barGap={isZoomed ? 0 : 4}\n          >\n            <defs>\n              {/* Gradients */}\n              {teamMembers.map((member) => (\n                <linearGradient\n                  id={member.gradientId}\n                  x1=\"0\"\n                  y1=\"0\"\n                  x2=\"0\"\n                  y2=\"1\"\n                  key={member.gradientId}\n                >\n                  <stop offset=\"5%\" stopColor={member.color} stopOpacity={0.9} />\n                  <stop offset=\"95%\" stopColor={member.color} stopOpacity={0.6} />\n                </linearGradient>\n              ))}\n              {/* ClipPath for Avatar */}\n              <clipPath id=\"clipCircle\">\n                <circle r=\"12\" cx=\"12\" cy=\"12\" /> {/* Radius is half of avatarSize */}\n              </clipPath>\n            </defs>\n            <CartesianGrid strokeDasharray=\"4 4\" stroke=\"#E0E0E0\" vertical={false} />\n            <XAxis\n              dataKey={isZoomed ? 'teamMemberName' : 'date'}\n              stroke=\"#78909C\"\n              fontSize=\"11px\"\n              axisLine={false}\n              tickLine={false}\n              padding={{ left: 0, right: 0 }}\n              interval={0}\n              // @ts-expect-error - CustomAvatarXAxisTick is not typed correctly\n              {...(isZoomed && { tick: <CustomAvatarXAxisTick />, dy: 10 })}\n            />\n            <YAxis\n              stroke=\"#78909C\"\n              fontSize=\"11px\"\n              axisLine={false}\n              tickLine={false}\n              tickCount={5}\n              width={35}\n              allowDecimals={false}\n            />\n            <Tooltip\n              content={<CustomTooltip isZoomedView={isZoomed} />}\n              cursor={{ fill: 'rgba(176, 190, 197, 0.08)' }}\n            />\n\n            {isZoomed\n              ? // Zoomed View: Bars are statuses, stacked, NOW COLORED by mapped team member GRADIENTS\n                statusKeys.map((statusKey) => (\n                  <Bar\n                    key={statusKey}\n                    dataKey={statusKey}\n                    stackId=\"zoomedDayStack\"\n                    name={statusMeta[statusKey].name}\n                    fill={`url(#${fullViewStatusToGradientMapping[statusKey]})`} // Use team member GRADIENTS\n                    // @ts-expect-error - RoundedBar is not typed correctly\n                    shape={<RoundedBar />}\n                    barSize={50}\n                  />\n                ))\n              : statusKeys.map((statusKey) => (\n                  <Bar\n                    key={statusKey}\n                    dataKey={statusKey}\n                    stackId=\"fullViewStack\"\n                    name={statusMeta[statusKey].name} // Name is status (e.g. \"To Do\") for tooltip payload if it were used\n                    fill={`url(#${fullViewStatusToGradientMapping[statusKey]})`} // Color from team member gradients\n                    // @ts-expect-error - RoundedBar is not typed correctly\n                    shape={<RoundedBar />}\n                  />\n                ))}\n            {isZoomed ? (\n              <Legend\n                payload={statusKeys.map((key, index) => ({\n                  value: statusMeta[key].name,\n                  type: 'square',\n                  color: teamMembers[index % teamMembers.length].color, // Use team member colors in order\n                }))}\n                wrapperStyle={{\n                  fontSize: '11px',\n                  paddingTop: '10px',\n                  paddingBottom: '0px',\n                }}\n              />\n            ) : (\n              <Legend // Legend for Full View (zoomed-out graph) - NOW WITH UPDATED COLORS\n                payload={statusKeys.map((key, index) => ({\n                  value: statusMeta[key].name,\n                  type: 'square',\n                  color: teamMembers[index % teamMembers.length].color, // Use team member colors in order\n                }))}\n                wrapperStyle={{\n                  fontSize: '11px',\n                  paddingTop: '10px',\n                  paddingBottom: '0px',\n                }}\n                align=\"center\"\n                verticalAlign=\"bottom\"\n              />\n            )}\n          </BarChart>\n        </ResponsiveContainer>\n      </div>\n      {!isZoomed && (\n        <p\n          style={{\n            textAlign: 'center',\n            marginTop: '5px',\n            fontSize: '10px',\n            color: '#90A4AE',\n          }}\n        ></p>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "examples/server/app/images.d.ts",
    "content": "declare module '*.svg' {\n  const content: unknown;\n  export default content;\n}\n"
  },
  {
    "path": "examples/server/app/root.tsx",
    "content": "import {\n  isRouteErrorResponse,\n  Links,\n  Meta,\n  Outlet,\n  Scripts,\n  ScrollRestoration,\n} from 'react-router';\n\nimport type { Route } from './+types/root';\nimport './app.css';\n\nexport const links: Route.LinksFunction = () => [\n  { rel: 'preconnect', href: 'https://fonts.googleapis.com' },\n  {\n    rel: 'preconnect',\n    href: 'https://fonts.gstatic.com',\n    crossOrigin: 'anonymous',\n  },\n  {\n    rel: 'stylesheet',\n    href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap',\n  },\n];\n\nexport function Layout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <meta charSet=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n        <Meta />\n        <Links />\n      </head>\n      <body>\n        {children}\n        <ScrollRestoration />\n        <Scripts />\n      </body>\n    </html>\n  );\n}\n\nexport default function App() {\n  return <Outlet />;\n}\n\nexport function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {\n  let message = 'Oops!';\n  let details = 'An unexpected error occurred.';\n  let stack: string | undefined;\n\n  if (isRouteErrorResponse(error)) {\n    message = error.status === 404 ? '404' : 'Error';\n    details =\n      error.status === 404 ? 'The requested page could not be found.' : error.statusText || details;\n  } else if (import.meta.env.DEV && error && error instanceof Error) {\n    details = error.message;\n    stack = error.stack;\n  }\n\n  return (\n    <main className=\"container mx-auto p-4 pt-16\">\n      <h1>{message}</h1>\n      <p>{details}</p>\n      {stack && (\n        <pre className=\"w-full overflow-x-auto p-4\">\n          <code>{stack}</code>\n        </pre>\n      )}\n    </main>\n  );\n}\n"
  },
  {
    "path": "examples/server/app/routes/home.tsx",
    "content": "export function meta() {\n  return [\n    { title: 'New React Router App' },\n    { name: 'description', content: 'Welcome to React Router!' },\n  ];\n}\n\nexport default function Home() {\n  return <div>Welcome</div>;\n}\n"
  },
  {
    "path": "examples/server/app/routes/task.tsx",
    "content": "import { Graph } from '../graph/graph';\n\nexport function meta() {\n  return [{ title: 'Task Status' }];\n}\n\nexport const loader = async ({ request }: { request: Request }) => {\n  const url = new URL(request.url);\n  const host = url.host;\n  const pathname = url.pathname;\n\n  return { url: request.url, host, pathname };\n};\n\nexport default function Task() {\n  return <Graph />;\n}\n"
  },
  {
    "path": "examples/server/app/routes/user.tsx",
    "content": "import { User } from '../user/user';\n\nexport function meta() {\n  return [{ title: 'User' }];\n}\n\nexport const loader = async ({ request }: { request: Request }) => {\n  const url = new URL(request.url);\n  const queryParams = url.searchParams;\n\n  return {\n    user: {\n      id: queryParams.get('id') || '',\n      name: queryParams.get('name') || '',\n      avatarUrl: queryParams.get('avatarUrl') || '',\n    },\n  };\n};\n\nexport default function UserProfile({\n  loaderData,\n}: {\n  loaderData: Awaited<ReturnType<typeof loader>>;\n}) {\n  const { user } = loaderData;\n  return <User user={user} />;\n}\n"
  },
  {
    "path": "examples/server/app/routes.ts",
    "content": "import { type RouteConfig, index, route } from '@react-router/dev/routes';\n\nexport default [\n  index('routes/home.tsx'),\n  route('task', 'routes/task.tsx'),\n  route('user', 'routes/user.tsx'),\n] satisfies RouteConfig;\n"
  },
  {
    "path": "examples/server/app/user/user.tsx",
    "content": "import React from 'react';\nimport {\n  AreaChart,\n  Area,\n  XAxis,\n  YAxis,\n  CartesianGrid,\n  Tooltip,\n  ResponsiveContainer,\n} from 'recharts';\nimport { postMessageToParent } from '../utils/messageUtils';\n\ninterface UserInfo {\n  id: string;\n  name: string;\n  avatarUrl: string;\n  location?: string;\n}\n\nexport function User({ user }: { user: UserInfo }) {\n  const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'];\n  const taskCounts = [80, 140, 110, 170, 95, 155];\n  const data = months.map((m, i) => ({ month: m, tasks: taskCounts[i] }));\n\n  const avg = Math.round(taskCounts.reduce((sum, v) => sum + v, 0) / taskCounts.length);\n  const reachPct = Math.round((taskCounts[taskCounts.length - 1] / Math.max(...taskCounts)) * 100);\n\n  const handleNudge = () => {\n    if (user.id) {\n      const message = {\n        type: 'tool',\n        payload: {\n          toolName: 'nudge_team_member',\n          params: {\n            name: user.name,\n          },\n        },\n      };\n      postMessageToParent(message);\n    }\n  };\n\n  const handleAskChat = (taskTitle: string) => {\n    const message = {\n      type: 'prompt',\n      payload: {\n        prompt: `How do I ${taskTitle}?`,\n      },\n    };\n    postMessageToParent(message);\n  };\n\n  return (\n    <div style={styles.card}>\n      {/* header */}\n      <div style={styles.header}>\n        <img src={user.avatarUrl} alt={`${user.name}’s avatar`} style={styles.avatar} />\n        <div>\n          <h2 style={styles.name}>{user.name}</h2>\n          {user.location && <div style={styles.location}>{user.location}</div>}\n        </div>\n      </div>\n\n      {/* performance overview */}\n      <div style={{ padding: '0 16px', width: '100%' }}>\n        <h3 style={styles.sectionTitle}>Performance Overview</h3>\n        <div style={styles.statsRow}>\n          <div>\n            <span style={styles.statLabel}>Average&nbsp;</span>\n            <span style={styles.statValueGreen}>{avg}</span>\n          </div>\n          <div>\n            <span style={styles.statLabel}>Reach&nbsp;</span>\n            <span style={styles.statValueRed}>{reachPct}%</span>\n          </div>\n        </div>\n        <div style={{ height: 120, width: '100%' }}>\n          <ResponsiveContainer>\n            <AreaChart data={data} margin={{ top: 0, right: 0, left: 0, bottom: 0 }}>\n              <defs>\n                <linearGradient id=\"colorTasks\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n                  <stop offset=\"5%\" stopColor=\"#1976d2\" stopOpacity={0.4} />\n                  <stop offset=\"95%\" stopColor=\"#1976d2\" stopOpacity={0.05} />\n                </linearGradient>\n              </defs>\n              <CartesianGrid stroke=\"#eee\" strokeDasharray=\"3 3\" />\n              <XAxis dataKey=\"month\" tick={{ fill: '#666' }} />\n              <YAxis hide domain={[0, 'dataMax + 20']} />\n              <Tooltip\n                contentStyle={{ borderRadius: 8 }}\n                formatter={(value: number) => [`${value}`, 'Tasks']}\n              />\n              <Area\n                type=\"monotone\"\n                dataKey=\"tasks\"\n                stroke=\"#1976d2\"\n                fill=\"url(#colorTasks)\"\n                strokeWidth={2}\n                activeDot={{ r: 5 }}\n              />\n            </AreaChart>\n          </ResponsiveContainer>\n        </div>\n      </div>\n\n      {/* summary tiles */}\n      <div style={styles.tiles}>\n        {[\n          { label: 'Completed', value: 1243, gradient: ['#FF8A65', '#FF7043'] },\n          { label: 'Pending', value: 289, gradient: ['#4FC3F7', '#29B6F6'] },\n          { label: 'Blocked', value: 67, gradient: ['#E57373', '#EF5350'] },\n        ].map((tile) => (\n          <div\n            key={tile.label}\n            style={{\n              ...styles.tile,\n              background: `linear-gradient(135deg, ${tile.gradient[0]}, ${tile.gradient[1]})`,\n            }}\n          >\n            <div style={styles.tileValue}>{tile.value}</div>\n            <div style={styles.tileLabel}>{tile.label}</div>\n          </div>\n        ))}\n      </div>\n\n      {/* blocked tasks */}\n      <div style={styles.blockedSection}>\n        <h3 style={styles.sectionTitle}>Blocked Tasks</h3>\n        <div style={styles.tasksList}>\n          {[\n            {\n              id: 1,\n              title: 'Add a route to React Router app',\n              priority: 'High',\n            },\n            {\n              id: 2,\n              title: 'Fix database connection timeout issue',\n              priority: 'Medium',\n            },\n            {\n              id: 3,\n              title: 'Turn on Vision Mode in Playwright MCP',\n              priority: 'High',\n            },\n          ].map((task) => (\n            <div key={task.id} style={styles.taskItem}>\n              <div style={styles.taskContent}>\n                <div style={styles.taskPriority}>\n                  <span\n                    style={{\n                      ...styles.priorityBadge,\n                      ...(task.priority === 'High' ? styles.priorityHigh : styles.priorityMedium),\n                    }}\n                  >\n                    {task.priority}\n                  </span>\n                </div>\n                <div style={styles.taskTitle}>{task.title}</div>\n              </div>\n              <button style={styles.askChatButton} onClick={() => handleAskChat(task.title)}>\n                Ask Chat\n              </button>\n            </div>\n          ))}\n        </div>\n      </div>\n\n      {/* nudge button */}\n      <button onClick={handleNudge} style={styles.button}>\n        Nudge\n      </button>\n    </div>\n  );\n}\n\nconst styles: Record<string, React.CSSProperties> = {\n  card: {\n    // maxWidth: 500,\n    borderRadius: 8,\n    background: '#fff',\n    boxShadow: '0 4px 12px rgba(0,0,0,0.08)',\n    overflow: 'hidden',\n    fontFamily: 'system-ui, sans-serif',\n  },\n  header: {\n    display: 'flex',\n    alignItems: 'center',\n    padding: '16px',\n    background: '#E3F2FD',\n    color: '#1E88E5',\n    gap: 12,\n  },\n  avatar: {\n    width: 48,\n    height: 48,\n    borderRadius: '50%',\n    objectFit: 'cover',\n    border: '2px solid #fff',\n  },\n  name: {\n    margin: 0,\n    fontSize: '1.2rem',\n  },\n  location: {\n    fontSize: '0.85rem',\n    opacity: 0.85,\n  },\n  sectionTitle: {\n    margin: '16px 0 4px',\n    fontSize: '1rem',\n    color: '#333',\n  },\n  statsRow: {\n    display: 'flex',\n    justifyContent: 'space-between',\n    paddingBottom: 8,\n    fontSize: '0.9rem',\n  },\n  statLabel: {\n    color: '#555',\n  },\n  statValueGreen: {\n    color: '#388E3C',\n    fontWeight: 600,\n  },\n  statValueRed: {\n    color: '#D32F2F',\n    fontWeight: 600,\n  },\n  tiles: {\n    display: 'flex',\n    gap: 8,\n    padding: '16px',\n  },\n  tile: {\n    flex: 1,\n    borderRadius: 6,\n    padding: 12,\n    color: '#fff',\n    textAlign: 'center' as const,\n  },\n  tileValue: {\n    fontSize: '1.1rem',\n    fontWeight: 600,\n  },\n  tileLabel: {\n    fontSize: '0.85rem',\n    marginTop: 4,\n  },\n  button: {\n    width: '100%',\n    padding: '10px 0',\n    border: 'none',\n    background: '#BBDEFB',\n    color: '#1565C0',\n    fontSize: '1rem',\n    fontWeight: 500,\n    cursor: 'pointer',\n  },\n  blockedSection: {\n    padding: '0 16px',\n    borderTop: '1px solid #f0f0f0',\n    paddingTop: 16,\n  },\n  tasksList: {\n    display: 'flex',\n    flexDirection: 'column' as const,\n    gap: 8,\n    marginBottom: 16,\n  },\n  taskItem: {\n    display: 'flex',\n    alignItems: 'center',\n    justifyContent: 'space-between',\n    padding: 12,\n    background: '#FAFAFA',\n    borderRadius: 6,\n    border: '1px solid #E0E0E0',\n  },\n  taskContent: {\n    flex: 1,\n    display: 'flex',\n    flexDirection: 'row' as const,\n    gap: 4,\n  },\n  taskTitle: {\n    fontSize: '0.9rem',\n    color: '#333',\n    fontWeight: 500,\n  },\n  taskPriority: {\n    display: 'flex',\n    alignItems: 'center',\n  },\n  priorityBadge: {\n    fontSize: '0.75rem',\n    fontWeight: 600,\n    padding: '2px 6px',\n    borderRadius: 3,\n    textTransform: 'uppercase' as const,\n  },\n  priorityHigh: {\n    background: '#FFEBEE',\n    color: '#C62828',\n    marginRight: '30px',\n  },\n  priorityMedium: {\n    background: '#FFF3E0',\n    color: '#E65100',\n    marginRight: '10px',\n  },\n  askChatButton: {\n    padding: '6px 12px',\n    border: 'none',\n    background: '#E3F2FD',\n    color: '#1976D2',\n    fontSize: '0.8rem',\n    fontWeight: 500,\n    borderRadius: 4,\n    cursor: 'pointer',\n    marginLeft: 12,\n  },\n};\n"
  },
  {
    "path": "examples/server/app/utils/messageUtils.ts",
    "content": "export function postMessageToParent(message: any) {\n  console.log('Sending message:', message);\n  // @ts-expect-error - window is not typed correctly\n  if (window.parent) {\n    // @ts-expect-error - window is not typed correctly\n    window.parent.postMessage(message, '*');\n  }\n}\n"
  },
  {
    "path": "examples/server/biome.json",
    "content": "{\n  \"$schema\": \"https://biomejs.dev/schemas/1.6.2/schema.json\",\n  \"organizeImports\": {\n    \"enabled\": true\n  },\n  \"files\": {\n    \"ignore\": [\"worker-configuration.d.ts\"]\n  },\n  \"vcs\": {\n    \"enabled\": true,\n    \"clientKind\": \"git\",\n    \"useIgnoreFile\": true\n  },\n  \"linter\": {\n    \"enabled\": true,\n    \"rules\": {\n      \"recommended\": true,\n      \"suspicious\": {\n        \"noExplicitAny\": \"off\",\n        \"noDebugger\": \"off\",\n        \"noConsoleLog\": \"off\",\n        \"noConfusingVoidType\": \"off\"\n      },\n      \"style\": {\n        \"noNonNullAssertion\": \"off\"\n      }\n    }\n  },\n  \"formatter\": {\n    \"enabled\": true,\n    \"indentWidth\": 4,\n    \"lineWidth\": 100\n  }\n}\n"
  },
  {
    "path": "examples/server/package.json",
    "content": "{\n  \"name\": \"remote-mcp-server-authless\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"react-router build\",\n    \"cf-typegen\": \"wrangler types\",\n    \"deploy\": \"pnpm run build && wrangler deploy\",\n    \"dev\": \"react-router dev\",\n    \"preview\": \"pnpm run build && vite preview\",\n    \"typecheck\": \"npm run cf-typegen && react-router typegen && tsc -b\"\n  },\n  \"packageManager\": \"pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977\",\n  \"dependencies\": {\n    \"@mcp-ui/server\": \"^5.2.0\",\n    \"@modelcontextprotocol/sdk\": \"^1.22.0\",\n    \"@vis.gl/react-google-maps\": \"^1.5.2\",\n    \"agents\": \"^0.0.80\",\n    \"isbot\": \"^5.1.27\",\n    \"leaflet\": \"^1.9.4\",\n    \"react\": \"^19.1.0\",\n    \"react-dom\": \"^19.1.0\",\n    \"react-leaflet\": \"^5.0.0\",\n    \"react-router\": \"^7.5.3\",\n    \"recharts\": \"^2.15.3\",\n    \"zod\": \"^3.24.4\"\n  },\n  \"devDependencies\": {\n    \"@cloudflare/vite-plugin\": \"^1.0.12\",\n    \"@cloudflare/workers-types\": \"^4.20250515.0\",\n    \"@react-router/dev\": \"^7.5.3\",\n    \"@tailwindcss/vite\": \"^4.1.4\",\n    \"@types/google.maps\": \"^3.58.1\",\n    \"@types/leaflet\": \"^1.9.17\",\n    \"@types/node\": \"^20\",\n    \"@types/react\": \"^19.1.2\",\n    \"@types/react-dom\": \"^19.1.2\",\n    \"@types/recharts\": \"^2.0.1\",\n    \"prettier\": \"^3.5.3\",\n    \"tailwindcss\": \"^4.1.4\",\n    \"typescript\": \"^5.8.3\",\n    \"vite\": \"^6.3.3\",\n    \"vite-tsconfig-paths\": \"^5.1.4\",\n    \"wrangler\": \"^4.15.1\"\n  }\n}\n"
  },
  {
    "path": "examples/server/pnpm-workspace.yaml",
    "content": "packages: []\n"
  },
  {
    "path": "examples/server/react-router.config.ts",
    "content": "import type { Config } from '@react-router/dev/config';\n\nexport default {\n  ssr: true,\n  future: {\n    unstable_viteEnvironmentApi: true,\n  },\n} satisfies Config;\n"
  },
  {
    "path": "examples/server/src/index.ts",
    "content": "import { McpAgent } from 'agents/mcp';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { createRequestHandler } from 'react-router';\nimport { createUIResource } from '@mcp-ui/server';\n\ndeclare module 'react-router' {\n  export interface AppLoadContext {\n    cloudflare: {\n      env: CloudflareEnvironment;\n      ctx: ExecutionContext;\n    };\n  }\n}\n\nconst requestHandler = createRequestHandler(\n  () => import('virtual:react-router/server-build'),\n  import.meta.env.MODE,\n);\n\n// Define our MCP agent with tools\nexport class MyMCP extends McpAgent {\n  server = new McpServer({\n    name: 'MCP-UI Example',\n    version: '1.0.0',\n  });\n\n  async init() {\n    const requestUrl = this.props.requestUrl as string;\n    const url = new URL(requestUrl);\n    const requestHost = url.host;\n\n    this.server.tool(\n      'get_tasks_status',\n      'The main way to get a textual representation of the status of all tasks',\n      async () => {\n        const todayData = {\n          alice: { remaining: 12, toDo: 5, inProgress: 4, blocked: 3 },\n          bob: { remaining: 18, toDo: 11, inProgress: 4, blocked: 3 },\n          charlie: { remaining: 14, toDo: 6, inProgress: 5, blocked: 3 },\n        };\n\n        // Full sprint data for weekly summary\n        const sprintDataFull = [\n          {\n            date: '5/10',\n            alice: { remaining: 8, toDo: 3, inProgress: 3, blocked: 2 },\n            bob: { remaining: 7, toDo: 2, inProgress: 3, blocked: 2 },\n            charlie: { remaining: 9, toDo: 4, inProgress: 3, blocked: 2 },\n          },\n          {\n            date: '5/11',\n            alice: { remaining: 7, toDo: 2, inProgress: 3, blocked: 2 },\n            bob: { remaining: 6, toDo: 2, inProgress: 2, blocked: 2 },\n            charlie: { remaining: 8, toDo: 3, inProgress: 3, blocked: 2 },\n          },\n          {\n            date: '5/12',\n            alice: { remaining: 9, toDo: 3, inProgress: 4, blocked: 2 },\n            bob: { remaining: 8, toDo: 3, inProgress: 3, blocked: 2 },\n            charlie: { remaining: 10, toDo: 4, inProgress: 4, blocked: 2 },\n          },\n          {\n            date: '5/13',\n            alice: { remaining: 6, toDo: 1, inProgress: 2, blocked: 3 },\n            bob: { remaining: 9, toDo: 3, inProgress: 3, blocked: 3 },\n            charlie: { remaining: 11, toDo: 5, inProgress: 3, blocked: 3 },\n          },\n          {\n            date: '5/14',\n            alice: { remaining: 10, toDo: 4, inProgress: 3, blocked: 3 },\n            bob: { remaining: 9, toDo: 3, inProgress: 3, blocked: 3 },\n            charlie: { remaining: 12, toDo: 5, inProgress: 4, blocked: 3 },\n          },\n          {\n            date: '5/15',\n            alice: { remaining: 11, toDo: 4, inProgress: 4, blocked: 3 },\n            bob: { remaining: 10, toDo: 3, inProgress: 4, blocked: 3 },\n            charlie: { remaining: 13, toDo: 6, inProgress: 4, blocked: 3 },\n          },\n          {\n            date: '5/16',\n            alice: { remaining: 12, toDo: 5, inProgress: 4, blocked: 3 },\n            bob: { remaining: 11, toDo: 4, inProgress: 4, blocked: 3 },\n            charlie: { remaining: 14, toDo: 6, inProgress: 5, blocked: 3 },\n          },\n        ];\n        const teamMembers = ['alice', 'bob', 'charlie'];\n\n        let statusText = \"Today's Task Status:\\n\\n\";\n\n        statusText += 'Alice:\\n';\n        statusText += `  To Do: ${todayData.alice.toDo}\\n`;\n        statusText += `  In Progress: ${todayData.alice.inProgress}\\n`;\n        statusText += `  Blocked: ${todayData.alice.blocked}\\n`;\n        statusText += `  Remaining: ${todayData.alice.remaining}\\n\\n`;\n\n        statusText += 'Bob:\\n';\n        statusText += `  To Do: ${todayData.bob.toDo}\\n`;\n        statusText += `  In Progress: ${todayData.bob.inProgress}\\n`;\n        statusText += `  Blocked: ${todayData.bob.blocked}\\n`;\n        statusText += `  Remaining: ${todayData.bob.remaining}\\n\\n`;\n\n        statusText += 'Charlie:\\n';\n        statusText += `  To Do: ${todayData.charlie.toDo}\\n`;\n        statusText += `  In Progress: ${todayData.charlie.inProgress}\\n`;\n        statusText += `  Blocked: ${todayData.charlie.blocked}\\n`;\n        statusText += `  Remaining: ${todayData.charlie.remaining}\\n`;\n\n        // Calculate weekly totals\n        let weeklyTotalToDo = 0;\n        let weeklyTotalInProgress = 0;\n        let weeklyTotalBlocked = 0;\n\n        sprintDataFull.forEach((day) => {\n          teamMembers.forEach((member) => {\n            // @ts-expect-error - member is a string, but it's used as an index type for day\n            weeklyTotalToDo += day[member]?.toDo || 0;\n            // @ts-expect-error - member is a string, but it's used as an index type for day\n            weeklyTotalInProgress += day[member]?.inProgress || 0;\n            // @ts-expect-error - member is a string, but it's used as an index type for day\n            weeklyTotalBlocked += day[member]?.blocked || 0;\n          });\n        });\n\n        statusText += '\\n\\nSummary for the past week:\\n';\n        statusText += `Total tasks To Do: ${weeklyTotalToDo}\\n`;\n        statusText += `Total tasks In Progress: ${weeklyTotalInProgress}\\n`;\n        statusText += `Total tasks Blocked: ${weeklyTotalBlocked}\\n`;\n\n        return {\n          content: [{ type: 'text', text: statusText }],\n        };\n      },\n    );\n\n    this.server.tool('nudge_team_member', { name: z.string() }, async ({ name }) => ({\n      content: [{ type: 'text', text: 'Nudged ' + name + '!' }],\n    }));\n\n    this.server.tool(\n      'show_task_status',\n      'Displays a UI for the user to see the status of tasks. Use get_tasks_status unless asked to SHOW the status',\n      async () => {\n        const scheme =\n          requestHost.includes('localhost') || requestHost.includes('127.0.0.1') ? 'http' : 'https';\n\n        const pickerPageUrl = `${scheme}://${requestHost}/task`;\n\n        // Generate a unique URI for this specific invocation of the file picker UI.\n        // This URI identifies the resource block itself, not the content of the iframe.\n        const uniqueUIAppUri = `ui://task-manager/${Date.now()}` as `ui://${string}`;\n        const resourceBlock = await createUIResource({\n          uri: uniqueUIAppUri,\n          content: { type: 'externalUrl', iframeUrl: pickerPageUrl },\n          encoding: 'text',\n        });\n\n        return {\n          content: [resourceBlock],\n        };\n      },\n    );\n    this.server.tool(\n      'show_user_status',\n      'Displays a UI for the user to see the status of a user and their tasks',\n      { id: z.string(), name: z.string(), avatarUrl: z.string() },\n      async ({ id, name, avatarUrl }) => {\n        const scheme =\n          requestHost.includes('localhost') || requestHost.includes('127.0.0.1') ? 'http' : 'https';\n\n        const pickerPageUrl = `${scheme}://${requestHost}/user?id=${id}&name=${name}&avatarUrl=${avatarUrl}`;\n\n        // Generate a unique URI for this specific invocation of the file picker UI.\n        // This URI identifies the resource block itself, not the content of the iframe.\n        const uniqueUIAppUri = `ui://user-profile/${Date.now()}` as `ui://${string}`;\n        const resourceBlock = await createUIResource({\n          uri: uniqueUIAppUri,\n          content: { type: 'externalUrl', iframeUrl: pickerPageUrl },\n          encoding: 'text',\n        });\n\n        return {\n          content: [resourceBlock],\n        };\n      },\n    );\n\n  }\n}\n\nexport default {\n  fetch(request: Request, env: Env, ctx: ExecutionContext) {\n    if (request.method === 'OPTIONS') {\n      return new Response(null, {\n        headers: {\n          'Access-Control-Allow-Origin': '*',\n          'Access-Control-Allow-Methods': 'GET,HEAD,OPTIONS',\n          'Access-Control-Allow-Headers': '*',\n        },\n      });\n    }\n\n    const url = new URL(request.url);\n    ctx.props.requestUrl = request.url;\n\n    if (url.pathname === '/sse' || url.pathname === '/sse/message') {\n      return MyMCP.serveSSE('/sse').fetch(request, env, ctx);\n    }\n\n    if (url.pathname === '/mcp') {\n      return MyMCP.serve('/mcp').fetch(request, env, ctx);\n    }\n\n    return requestHandler(request, {\n      cloudflare: { env, ctx },\n    });\n    // return new Response(\"Not found\", { status: 404 });\n  },\n};\n"
  },
  {
    "path": "examples/server/tsconfig.cloudflare.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"include\": [\n    \".react-router/types/**/*\",\n    \"app/**/*\",\n    \"app/**/.server/**/*\",\n    \"app/**/.client/**/*\",\n    \"workers/**/*\",\n    \"worker-configuration.d.ts\"\n  ],\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"strict\": true,\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2022\"],\n    \"types\": [\"@cloudflare/workers-types\", \"vite/client\"],\n    \"target\": \"ES2022\",\n    \"module\": \"ES2022\",\n    \"moduleResolution\": \"bundler\",\n    \"jsx\": \"react-jsx\",\n    \"baseUrl\": \".\",\n    \"rootDirs\": [\".\", \"./.react-router/types\"],\n    \"paths\": {\n      \"~/*\": [\"./app/*\"]\n    },\n    \"esModuleInterop\": true,\n    \"resolveJsonModule\": true\n  }\n}\n"
  },
  {
    "path": "examples/server/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2021\",\n    \"lib\": [\"es2021\"],\n    \"jsx\": \"react-jsx\",\n    \"module\": \"es2022\",\n    \"moduleResolution\": \"Bundler\",\n    \"resolveJsonModule\": true,\n    \"allowJs\": true,\n    \"checkJs\": false,\n    \"noEmit\": true,\n    \"isolatedModules\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"skipLibCheck\": true\n  },\n  \"include\": [\"worker-configuration.d.ts\", \"src/**/*.ts\", \"app/**/*.ts\", \"app/**/*.tsx\"]\n}\n"
  },
  {
    "path": "examples/server/tsconfig.node.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"include\": [\"vite.config.ts\"],\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"strict\": true,\n    \"types\": [\"node\"],\n    \"lib\": [\"ES2022\"],\n    \"target\": \"ES2022\",\n    \"module\": \"ES2022\",\n    \"moduleResolution\": \"bundler\"\n  }\n}\n"
  },
  {
    "path": "examples/server/vite.config.ts",
    "content": "import { reactRouter } from '@react-router/dev/vite';\nimport { cloudflare } from '@cloudflare/vite-plugin';\nimport tailwindcss from '@tailwindcss/vite';\nimport { defineConfig } from 'vite';\nimport tsconfigPaths from 'vite-tsconfig-paths';\n\nexport default defineConfig({\n  plugins: [\n    cloudflare({ viteEnvironment: { name: 'ssr' } }),\n    tailwindcss(),\n    reactRouter(),\n    tsconfigPaths(),\n  ],\n});\n"
  },
  {
    "path": "examples/server/worker-configuration.d.ts",
    "content": "/* eslint-disable */\n// Generated by Wrangler by running `wrangler types` (hash: 15dd420dfccac8afafeb181790b9eddb)\n// Runtime types generated with workerd@1.20250508.0 2025-03-10 nodejs_compat\ndeclare namespace Cloudflare {\n  interface Env {\n    MCP_OBJECT: DurableObjectNamespace<import('./src/index').MyMCP>;\n  }\n}\ninterface Env extends Cloudflare.Env {}\n\n// Begin runtime types\n/*! *****************************************************************************\nCopyright (c) Cloudflare. All rights reserved.\nCopyright (c) Microsoft Corporation. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at http://www.apache.org/licenses/LICENSE-2.0\nTHIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\nKIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED\nWARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,\nMERCHANTABLITY OR NON-INFRINGEMENT.\nSee the Apache Version 2.0 License for specific language governing permissions\nand limitations under the License.\n***************************************************************************** */\n/* eslint-disable */\n// noinspection JSUnusedGlobalSymbols\ndeclare var onmessage: never;\n/**\n * An abnormal event (called an exception) which occurs as a result of calling a method or accessing a property of a web API.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException)\n */\ndeclare class DOMException extends Error {\n  constructor(message?: string, name?: string);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/message) */\n  readonly message: string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/name) */\n  readonly name: string;\n  /**\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/code)\n   */\n  readonly code: number;\n  static readonly INDEX_SIZE_ERR: number;\n  static readonly DOMSTRING_SIZE_ERR: number;\n  static readonly HIERARCHY_REQUEST_ERR: number;\n  static readonly WRONG_DOCUMENT_ERR: number;\n  static readonly INVALID_CHARACTER_ERR: number;\n  static readonly NO_DATA_ALLOWED_ERR: number;\n  static readonly NO_MODIFICATION_ALLOWED_ERR: number;\n  static readonly NOT_FOUND_ERR: number;\n  static readonly NOT_SUPPORTED_ERR: number;\n  static readonly INUSE_ATTRIBUTE_ERR: number;\n  static readonly INVALID_STATE_ERR: number;\n  static readonly SYNTAX_ERR: number;\n  static readonly INVALID_MODIFICATION_ERR: number;\n  static readonly NAMESPACE_ERR: number;\n  static readonly INVALID_ACCESS_ERR: number;\n  static readonly VALIDATION_ERR: number;\n  static readonly TYPE_MISMATCH_ERR: number;\n  static readonly SECURITY_ERR: number;\n  static readonly NETWORK_ERR: number;\n  static readonly ABORT_ERR: number;\n  static readonly URL_MISMATCH_ERR: number;\n  static readonly QUOTA_EXCEEDED_ERR: number;\n  static readonly TIMEOUT_ERR: number;\n  static readonly INVALID_NODE_TYPE_ERR: number;\n  static readonly DATA_CLONE_ERR: number;\n  get stack(): any;\n  set stack(value: any);\n}\ntype WorkerGlobalScopeEventMap = {\n  fetch: FetchEvent;\n  scheduled: ScheduledEvent;\n  queue: QueueEvent;\n  unhandledrejection: PromiseRejectionEvent;\n  rejectionhandled: PromiseRejectionEvent;\n};\ndeclare abstract class WorkerGlobalScope extends EventTarget<WorkerGlobalScopeEventMap> {\n  EventTarget: typeof EventTarget;\n}\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console) */\ninterface Console {\n  'assert'(condition?: boolean, ...data: any[]): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/clear_static) */\n  clear(): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/count_static) */\n  count(label?: string): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/countReset_static) */\n  countReset(label?: string): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/debug_static) */\n  debug(...data: any[]): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dir_static) */\n  dir(item?: any, options?: any): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dirxml_static) */\n  dirxml(...data: any[]): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/error_static) */\n  error(...data: any[]): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/group_static) */\n  group(...data: any[]): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupCollapsed_static) */\n  groupCollapsed(...data: any[]): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupEnd_static) */\n  groupEnd(): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/info_static) */\n  info(...data: any[]): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static) */\n  log(...data: any[]): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/table_static) */\n  table(tabularData?: any, properties?: string[]): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/time_static) */\n  time(label?: string): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeEnd_static) */\n  timeEnd(label?: string): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeLog_static) */\n  timeLog(label?: string, ...data: any[]): void;\n  timeStamp(label?: string): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/trace_static) */\n  trace(...data: any[]): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/warn_static) */\n  warn(...data: any[]): void;\n}\ndeclare const console: Console;\ntype BufferSource = ArrayBufferView | ArrayBuffer;\ntype TypedArray =\n  | Int8Array\n  | Uint8Array\n  | Uint8ClampedArray\n  | Int16Array\n  | Uint16Array\n  | Int32Array\n  | Uint32Array\n  | Float32Array\n  | Float64Array\n  | BigInt64Array\n  | BigUint64Array;\ndeclare namespace WebAssembly {\n  class CompileError extends Error {\n    constructor(message?: string);\n  }\n  class RuntimeError extends Error {\n    constructor(message?: string);\n  }\n  type ValueType = 'anyfunc' | 'externref' | 'f32' | 'f64' | 'i32' | 'i64' | 'v128';\n  interface GlobalDescriptor {\n    value: ValueType;\n    mutable?: boolean;\n  }\n  class Global {\n    constructor(descriptor: GlobalDescriptor, value?: any);\n    value: any;\n    valueOf(): any;\n  }\n  type ImportValue = ExportValue | number;\n  type ModuleImports = Record<string, ImportValue>;\n  type Imports = Record<string, ModuleImports>;\n  type ExportValue = Function | Global | Memory | Table;\n  type Exports = Record<string, ExportValue>;\n  class Instance {\n    constructor(module: Module, imports?: Imports);\n    readonly exports: Exports;\n  }\n  interface MemoryDescriptor {\n    initial: number;\n    maximum?: number;\n    shared?: boolean;\n  }\n  class Memory {\n    constructor(descriptor: MemoryDescriptor);\n    readonly buffer: ArrayBuffer;\n    grow(delta: number): number;\n  }\n  type ImportExportKind = 'function' | 'global' | 'memory' | 'table';\n  interface ModuleExportDescriptor {\n    kind: ImportExportKind;\n    name: string;\n  }\n  interface ModuleImportDescriptor {\n    kind: ImportExportKind;\n    module: string;\n    name: string;\n  }\n  abstract class Module {\n    static customSections(module: Module, sectionName: string): ArrayBuffer[];\n    static exports(module: Module): ModuleExportDescriptor[];\n    static imports(module: Module): ModuleImportDescriptor[];\n  }\n  type TableKind = 'anyfunc' | 'externref';\n  interface TableDescriptor {\n    element: TableKind;\n    initial: number;\n    maximum?: number;\n  }\n  class Table {\n    constructor(descriptor: TableDescriptor, value?: any);\n    readonly length: number;\n    get(index: number): any;\n    grow(delta: number, value?: any): number;\n    set(index: number, value?: any): void;\n  }\n  function instantiate(module: Module, imports?: Imports): Promise<Instance>;\n  function validate(bytes: BufferSource): boolean;\n}\n/**\n * This ServiceWorker API interface represents the global execution context of a service worker.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope)\n */\ninterface ServiceWorkerGlobalScope extends WorkerGlobalScope {\n  DOMException: typeof DOMException;\n  WorkerGlobalScope: typeof WorkerGlobalScope;\n  btoa(data: string): string;\n  atob(data: string): string;\n  setTimeout(callback: (...args: any[]) => void, msDelay?: number): number;\n  setTimeout<Args extends any[]>(\n    callback: (...args: Args) => void,\n    msDelay?: number,\n    ...args: Args\n  ): number;\n  clearTimeout(timeoutId: number | null): void;\n  setInterval(callback: (...args: any[]) => void, msDelay?: number): number;\n  setInterval<Args extends any[]>(\n    callback: (...args: Args) => void,\n    msDelay?: number,\n    ...args: Args\n  ): number;\n  clearInterval(timeoutId: number | null): void;\n  queueMicrotask(task: Function): void;\n  structuredClone<T>(value: T, options?: StructuredSerializeOptions): T;\n  reportError(error: any): void;\n  fetch(input: RequestInfo | URL, init?: RequestInit<RequestInitCfProperties>): Promise<Response>;\n  self: ServiceWorkerGlobalScope;\n  crypto: Crypto;\n  caches: CacheStorage;\n  scheduler: Scheduler;\n  performance: Performance;\n  Cloudflare: Cloudflare;\n  readonly origin: string;\n  Event: typeof Event;\n  ExtendableEvent: typeof ExtendableEvent;\n  CustomEvent: typeof CustomEvent;\n  PromiseRejectionEvent: typeof PromiseRejectionEvent;\n  FetchEvent: typeof FetchEvent;\n  TailEvent: typeof TailEvent;\n  TraceEvent: typeof TailEvent;\n  ScheduledEvent: typeof ScheduledEvent;\n  MessageEvent: typeof MessageEvent;\n  CloseEvent: typeof CloseEvent;\n  ReadableStreamDefaultReader: typeof ReadableStreamDefaultReader;\n  ReadableStreamBYOBReader: typeof ReadableStreamBYOBReader;\n  ReadableStream: typeof ReadableStream;\n  WritableStream: typeof WritableStream;\n  WritableStreamDefaultWriter: typeof WritableStreamDefaultWriter;\n  TransformStream: typeof TransformStream;\n  ByteLengthQueuingStrategy: typeof ByteLengthQueuingStrategy;\n  CountQueuingStrategy: typeof CountQueuingStrategy;\n  ErrorEvent: typeof ErrorEvent;\n  EventSource: typeof EventSource;\n  ReadableStreamBYOBRequest: typeof ReadableStreamBYOBRequest;\n  ReadableStreamDefaultController: typeof ReadableStreamDefaultController;\n  ReadableByteStreamController: typeof ReadableByteStreamController;\n  WritableStreamDefaultController: typeof WritableStreamDefaultController;\n  TransformStreamDefaultController: typeof TransformStreamDefaultController;\n  CompressionStream: typeof CompressionStream;\n  DecompressionStream: typeof DecompressionStream;\n  TextEncoderStream: typeof TextEncoderStream;\n  TextDecoderStream: typeof TextDecoderStream;\n  Headers: typeof Headers;\n  Body: typeof Body;\n  Request: typeof Request;\n  Response: typeof Response;\n  WebSocket: typeof WebSocket;\n  WebSocketPair: typeof WebSocketPair;\n  WebSocketRequestResponsePair: typeof WebSocketRequestResponsePair;\n  AbortController: typeof AbortController;\n  AbortSignal: typeof AbortSignal;\n  TextDecoder: typeof TextDecoder;\n  TextEncoder: typeof TextEncoder;\n  navigator: Navigator;\n  Navigator: typeof Navigator;\n  URL: typeof URL;\n  URLSearchParams: typeof URLSearchParams;\n  URLPattern: typeof URLPattern;\n  Blob: typeof Blob;\n  File: typeof File;\n  FormData: typeof FormData;\n  Crypto: typeof Crypto;\n  SubtleCrypto: typeof SubtleCrypto;\n  CryptoKey: typeof CryptoKey;\n  CacheStorage: typeof CacheStorage;\n  Cache: typeof Cache;\n  FixedLengthStream: typeof FixedLengthStream;\n  IdentityTransformStream: typeof IdentityTransformStream;\n  HTMLRewriter: typeof HTMLRewriter;\n}\ndeclare function addEventListener<Type extends keyof WorkerGlobalScopeEventMap>(\n  type: Type,\n  handler: EventListenerOrEventListenerObject<WorkerGlobalScopeEventMap[Type]>,\n  options?: EventTargetAddEventListenerOptions | boolean,\n): void;\ndeclare function removeEventListener<Type extends keyof WorkerGlobalScopeEventMap>(\n  type: Type,\n  handler: EventListenerOrEventListenerObject<WorkerGlobalScopeEventMap[Type]>,\n  options?: EventTargetEventListenerOptions | boolean,\n): void;\n/**\n * Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent)\n */\ndeclare function dispatchEvent(\n  event: WorkerGlobalScopeEventMap[keyof WorkerGlobalScopeEventMap],\n): boolean;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/btoa) */\ndeclare function btoa(data: string): string;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/atob) */\ndeclare function atob(data: string): string;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */\ndeclare function setTimeout(callback: (...args: any[]) => void, msDelay?: number): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */\ndeclare function setTimeout<Args extends any[]>(\n  callback: (...args: Args) => void,\n  msDelay?: number,\n  ...args: Args\n): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearTimeout) */\ndeclare function clearTimeout(timeoutId: number | null): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */\ndeclare function setInterval(callback: (...args: any[]) => void, msDelay?: number): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */\ndeclare function setInterval<Args extends any[]>(\n  callback: (...args: Args) => void,\n  msDelay?: number,\n  ...args: Args\n): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearInterval) */\ndeclare function clearInterval(timeoutId: number | null): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/queueMicrotask) */\ndeclare function queueMicrotask(task: Function): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/structuredClone) */\ndeclare function structuredClone<T>(value: T, options?: StructuredSerializeOptions): T;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/reportError) */\ndeclare function reportError(error: any): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch) */\ndeclare function fetch(\n  input: RequestInfo | URL,\n  init?: RequestInit<RequestInitCfProperties>,\n): Promise<Response>;\ndeclare const self: ServiceWorkerGlobalScope;\n/**\n * The Web Crypto API provides a set of low-level functions for common cryptographic tasks.\n * The Workers runtime implements the full surface of this API, but with some differences in\n * the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms)\n * compared to those implemented in most browsers.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/)\n */\ndeclare const crypto: Crypto;\n/**\n * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)\n */\ndeclare const caches: CacheStorage;\ndeclare const scheduler: Scheduler;\n/**\n * The Workers runtime supports a subset of the Performance API, used to measure timing and performance,\n * as well as timing of subrequests and other operations.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/)\n */\ndeclare const performance: Performance;\ndeclare const Cloudflare: Cloudflare;\ndeclare const origin: string;\ndeclare const navigator: Navigator;\ninterface TestController {}\ninterface ExecutionContext {\n  waitUntil(promise: Promise<any>): void;\n  passThroughOnException(): void;\n  props: any;\n}\ntype ExportedHandlerFetchHandler<Env = unknown, CfHostMetadata = unknown> = (\n  request: Request<CfHostMetadata, IncomingRequestCfProperties<CfHostMetadata>>,\n  env: Env,\n  ctx: ExecutionContext,\n) => Response | Promise<Response>;\ntype ExportedHandlerTailHandler<Env = unknown> = (\n  events: TraceItem[],\n  env: Env,\n  ctx: ExecutionContext,\n) => void | Promise<void>;\ntype ExportedHandlerTraceHandler<Env = unknown> = (\n  traces: TraceItem[],\n  env: Env,\n  ctx: ExecutionContext,\n) => void | Promise<void>;\ntype ExportedHandlerTailStreamHandler<Env = unknown> = (\n  event: TailStream.TailEvent,\n  env: Env,\n  ctx: ExecutionContext,\n) => TailStream.TailEventHandlerType | Promise<TailStream.TailEventHandlerType>;\ntype ExportedHandlerScheduledHandler<Env = unknown> = (\n  controller: ScheduledController,\n  env: Env,\n  ctx: ExecutionContext,\n) => void | Promise<void>;\ntype ExportedHandlerQueueHandler<Env = unknown, Message = unknown> = (\n  batch: MessageBatch<Message>,\n  env: Env,\n  ctx: ExecutionContext,\n) => void | Promise<void>;\ntype ExportedHandlerTestHandler<Env = unknown> = (\n  controller: TestController,\n  env: Env,\n  ctx: ExecutionContext,\n) => void | Promise<void>;\ninterface ExportedHandler<Env = unknown, QueueHandlerMessage = unknown, CfHostMetadata = unknown> {\n  fetch?: ExportedHandlerFetchHandler<Env, CfHostMetadata>;\n  tail?: ExportedHandlerTailHandler<Env>;\n  trace?: ExportedHandlerTraceHandler<Env>;\n  tailStream?: ExportedHandlerTailStreamHandler<Env>;\n  scheduled?: ExportedHandlerScheduledHandler<Env>;\n  test?: ExportedHandlerTestHandler<Env>;\n  email?: EmailExportedHandler<Env>;\n  queue?: ExportedHandlerQueueHandler<Env, QueueHandlerMessage>;\n}\ninterface StructuredSerializeOptions {\n  transfer?: any[];\n}\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent) */\ndeclare abstract class PromiseRejectionEvent extends Event {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/promise) */\n  readonly promise: Promise<any>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/reason) */\n  readonly reason: any;\n}\ndeclare abstract class Navigator {\n  sendBeacon(\n    url: string,\n    body?:\n      | ReadableStream\n      | string\n      | (ArrayBuffer | ArrayBufferView)\n      | Blob\n      | FormData\n      | URLSearchParams\n      | URLSearchParams,\n  ): boolean;\n  readonly userAgent: string;\n  readonly hardwareConcurrency: number;\n}\n/**\n * The Workers runtime supports a subset of the Performance API, used to measure timing and performance,\n * as well as timing of subrequests and other operations.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/)\n */\ninterface Performance {\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancetimeorigin) */\n  readonly timeOrigin: number;\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancenow) */\n  now(): number;\n}\ninterface AlarmInvocationInfo {\n  readonly isRetry: boolean;\n  readonly retryCount: number;\n}\ninterface Cloudflare {\n  readonly compatibilityFlags: Record<string, boolean>;\n}\ninterface DurableObject {\n  fetch(request: Request): Response | Promise<Response>;\n  alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise<void>;\n  webSocketMessage?(ws: WebSocket, message: string | ArrayBuffer): void | Promise<void>;\n  webSocketClose?(\n    ws: WebSocket,\n    code: number,\n    reason: string,\n    wasClean: boolean,\n  ): void | Promise<void>;\n  webSocketError?(ws: WebSocket, error: unknown): void | Promise<void>;\n}\ntype DurableObjectStub<T extends Rpc.DurableObjectBranded | undefined = undefined> = Fetcher<\n  T,\n  'alarm' | 'webSocketMessage' | 'webSocketClose' | 'webSocketError'\n> & {\n  readonly id: DurableObjectId;\n  readonly name?: string;\n};\ninterface DurableObjectId {\n  toString(): string;\n  equals(other: DurableObjectId): boolean;\n  readonly name?: string;\n}\ninterface DurableObjectNamespace<T extends Rpc.DurableObjectBranded | undefined = undefined> {\n  newUniqueId(options?: DurableObjectNamespaceNewUniqueIdOptions): DurableObjectId;\n  idFromName(name: string): DurableObjectId;\n  idFromString(id: string): DurableObjectId;\n  get(\n    id: DurableObjectId,\n    options?: DurableObjectNamespaceGetDurableObjectOptions,\n  ): DurableObjectStub<T>;\n  jurisdiction(jurisdiction: DurableObjectJurisdiction): DurableObjectNamespace<T>;\n}\ntype DurableObjectJurisdiction = 'eu' | 'fedramp';\ninterface DurableObjectNamespaceNewUniqueIdOptions {\n  jurisdiction?: DurableObjectJurisdiction;\n}\ntype DurableObjectLocationHint =\n  | 'wnam'\n  | 'enam'\n  | 'sam'\n  | 'weur'\n  | 'eeur'\n  | 'apac'\n  | 'oc'\n  | 'afr'\n  | 'me';\ninterface DurableObjectNamespaceGetDurableObjectOptions {\n  locationHint?: DurableObjectLocationHint;\n}\ninterface DurableObjectState {\n  waitUntil(promise: Promise<any>): void;\n  readonly id: DurableObjectId;\n  readonly storage: DurableObjectStorage;\n  container?: Container;\n  blockConcurrencyWhile<T>(callback: () => Promise<T>): Promise<T>;\n  acceptWebSocket(ws: WebSocket, tags?: string[]): void;\n  getWebSockets(tag?: string): WebSocket[];\n  setWebSocketAutoResponse(maybeReqResp?: WebSocketRequestResponsePair): void;\n  getWebSocketAutoResponse(): WebSocketRequestResponsePair | null;\n  getWebSocketAutoResponseTimestamp(ws: WebSocket): Date | null;\n  setHibernatableWebSocketEventTimeout(timeoutMs?: number): void;\n  getHibernatableWebSocketEventTimeout(): number | null;\n  getTags(ws: WebSocket): string[];\n  abort(reason?: string): void;\n}\ninterface DurableObjectTransaction {\n  get<T = unknown>(key: string, options?: DurableObjectGetOptions): Promise<T | undefined>;\n  get<T = unknown>(keys: string[], options?: DurableObjectGetOptions): Promise<Map<string, T>>;\n  list<T = unknown>(options?: DurableObjectListOptions): Promise<Map<string, T>>;\n  put<T>(key: string, value: T, options?: DurableObjectPutOptions): Promise<void>;\n  put<T>(entries: Record<string, T>, options?: DurableObjectPutOptions): Promise<void>;\n  delete(key: string, options?: DurableObjectPutOptions): Promise<boolean>;\n  delete(keys: string[], options?: DurableObjectPutOptions): Promise<number>;\n  rollback(): void;\n  getAlarm(options?: DurableObjectGetAlarmOptions): Promise<number | null>;\n  setAlarm(scheduledTime: number | Date, options?: DurableObjectSetAlarmOptions): Promise<void>;\n  deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise<void>;\n}\ninterface DurableObjectStorage {\n  get<T = unknown>(key: string, options?: DurableObjectGetOptions): Promise<T | undefined>;\n  get<T = unknown>(keys: string[], options?: DurableObjectGetOptions): Promise<Map<string, T>>;\n  list<T = unknown>(options?: DurableObjectListOptions): Promise<Map<string, T>>;\n  put<T>(key: string, value: T, options?: DurableObjectPutOptions): Promise<void>;\n  put<T>(entries: Record<string, T>, options?: DurableObjectPutOptions): Promise<void>;\n  delete(key: string, options?: DurableObjectPutOptions): Promise<boolean>;\n  delete(keys: string[], options?: DurableObjectPutOptions): Promise<number>;\n  deleteAll(options?: DurableObjectPutOptions): Promise<void>;\n  transaction<T>(closure: (txn: DurableObjectTransaction) => Promise<T>): Promise<T>;\n  getAlarm(options?: DurableObjectGetAlarmOptions): Promise<number | null>;\n  setAlarm(scheduledTime: number | Date, options?: DurableObjectSetAlarmOptions): Promise<void>;\n  deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise<void>;\n  sync(): Promise<void>;\n  sql: SqlStorage;\n  transactionSync<T>(closure: () => T): T;\n  getCurrentBookmark(): Promise<string>;\n  getBookmarkForTime(timestamp: number | Date): Promise<string>;\n  onNextSessionRestoreBookmark(bookmark: string): Promise<string>;\n}\ninterface DurableObjectListOptions {\n  start?: string;\n  startAfter?: string;\n  end?: string;\n  prefix?: string;\n  reverse?: boolean;\n  limit?: number;\n  allowConcurrency?: boolean;\n  noCache?: boolean;\n}\ninterface DurableObjectGetOptions {\n  allowConcurrency?: boolean;\n  noCache?: boolean;\n}\ninterface DurableObjectGetAlarmOptions {\n  allowConcurrency?: boolean;\n}\ninterface DurableObjectPutOptions {\n  allowConcurrency?: boolean;\n  allowUnconfirmed?: boolean;\n  noCache?: boolean;\n}\ninterface DurableObjectSetAlarmOptions {\n  allowConcurrency?: boolean;\n  allowUnconfirmed?: boolean;\n}\ndeclare class WebSocketRequestResponsePair {\n  constructor(request: string, response: string);\n  get request(): string;\n  get response(): string;\n}\ninterface AnalyticsEngineDataset {\n  writeDataPoint(event?: AnalyticsEngineDataPoint): void;\n}\ninterface AnalyticsEngineDataPoint {\n  indexes?: ((ArrayBuffer | string) | null)[];\n  doubles?: number[];\n  blobs?: ((ArrayBuffer | string) | null)[];\n}\n/**\n * An event which takes place in the DOM.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event)\n */\ndeclare class Event {\n  constructor(type: string, init?: EventInit);\n  /**\n   * Returns the type of event, e.g. \"click\", \"hashchange\", or \"submit\".\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/type)\n   */\n  get type(): string;\n  /**\n   * Returns the event's phase, which is one of NONE, CAPTURING_PHASE, AT_TARGET, and BUBBLING_PHASE.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/eventPhase)\n   */\n  get eventPhase(): number;\n  /**\n   * Returns true or false depending on how event was initialized. True if event invokes listeners past a ShadowRoot node that is the root of its target, and false otherwise.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composed)\n   */\n  get composed(): boolean;\n  /**\n   * Returns true or false depending on how event was initialized. True if event goes through its target's ancestors in reverse tree order, and false otherwise.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/bubbles)\n   */\n  get bubbles(): boolean;\n  /**\n   * Returns true or false depending on how event was initialized. Its return value does not always carry meaning, but true can indicate that part of the operation during which event was dispatched, can be canceled by invoking the preventDefault() method.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelable)\n   */\n  get cancelable(): boolean;\n  /**\n   * Returns true if preventDefault() was invoked successfully to indicate cancelation, and false otherwise.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/defaultPrevented)\n   */\n  get defaultPrevented(): boolean;\n  /**\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/returnValue)\n   */\n  get returnValue(): boolean;\n  /**\n   * Returns the object whose event listener's callback is currently being invoked.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/currentTarget)\n   */\n  get currentTarget(): EventTarget | undefined;\n  /**\n   * Returns the object to which event is dispatched (its target).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/target)\n   */\n  get target(): EventTarget | undefined;\n  /**\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/srcElement)\n   */\n  get srcElement(): EventTarget | undefined;\n  /**\n   * Returns the event's timestamp as the number of milliseconds measured relative to the time origin.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/timeStamp)\n   */\n  get timeStamp(): number;\n  /**\n   * Returns true if event was dispatched by the user agent, and false otherwise.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/isTrusted)\n   */\n  get isTrusted(): boolean;\n  /**\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble)\n   */\n  get cancelBubble(): boolean;\n  /**\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble)\n   */\n  set cancelBubble(value: boolean);\n  /**\n   * Invoking this method prevents event from reaching any registered event listeners after the current one finishes running and, when dispatched in a tree, also prevents event from reaching any other objects.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopImmediatePropagation)\n   */\n  stopImmediatePropagation(): void;\n  /**\n   * If invoked when the cancelable attribute value is true, and while executing a listener for the event with passive set to false, signals to the operation that caused event to be dispatched that it needs to be canceled.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/preventDefault)\n   */\n  preventDefault(): void;\n  /**\n   * When dispatched in a tree, invoking this method prevents event from reaching any objects other than the current object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopPropagation)\n   */\n  stopPropagation(): void;\n  /**\n   * Returns the invocation target objects of event's path (objects on which listeners will be invoked), except for any nodes in shadow trees of which the shadow root's mode is \"closed\" that are not reachable from event's currentTarget.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composedPath)\n   */\n  composedPath(): EventTarget[];\n  static readonly NONE: number;\n  static readonly CAPTURING_PHASE: number;\n  static readonly AT_TARGET: number;\n  static readonly BUBBLING_PHASE: number;\n}\ninterface EventInit {\n  bubbles?: boolean;\n  cancelable?: boolean;\n  composed?: boolean;\n}\ntype EventListener<EventType extends Event = Event> = (event: EventType) => void;\ninterface EventListenerObject<EventType extends Event = Event> {\n  handleEvent(event: EventType): void;\n}\ntype EventListenerOrEventListenerObject<EventType extends Event = Event> =\n  | EventListener<EventType>\n  | EventListenerObject<EventType>;\n/**\n * EventTarget is a DOM interface implemented by objects that can receive events and may have listeners for them.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget)\n */\ndeclare class EventTarget<EventMap extends Record<string, Event> = Record<string, Event>> {\n  constructor();\n  /**\n   * Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched.\n   *\n   * The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options's capture.\n   *\n   * When set to true, options's capture prevents callback from being invoked when the event's eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event's eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event's eventPhase attribute value is AT_TARGET.\n   *\n   * When set to true, options's passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in § 2.8 Observing event listeners.\n   *\n   * When set to true, options's once indicates that the callback will only be invoked once after which the event listener will be removed.\n   *\n   * If an AbortSignal is passed for options's signal, then the event listener will be removed when signal is aborted.\n   *\n   * The event listener is appended to target's event listener list and is not appended if it has the same type, callback, and capture.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)\n   */\n  addEventListener<Type extends keyof EventMap>(\n    type: Type,\n    handler: EventListenerOrEventListenerObject<EventMap[Type]>,\n    options?: EventTargetAddEventListenerOptions | boolean,\n  ): void;\n  /**\n   * Removes the event listener in target's event listener list with the same type, callback, and options.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener)\n   */\n  removeEventListener<Type extends keyof EventMap>(\n    type: Type,\n    handler: EventListenerOrEventListenerObject<EventMap[Type]>,\n    options?: EventTargetEventListenerOptions | boolean,\n  ): void;\n  /**\n   * Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent)\n   */\n  dispatchEvent(event: EventMap[keyof EventMap]): boolean;\n}\ninterface EventTargetEventListenerOptions {\n  capture?: boolean;\n}\ninterface EventTargetAddEventListenerOptions {\n  capture?: boolean;\n  passive?: boolean;\n  once?: boolean;\n  signal?: AbortSignal;\n}\ninterface EventTargetHandlerObject {\n  handleEvent: (event: Event) => any | undefined;\n}\n/**\n * A controller object that allows you to abort one or more DOM requests as and when desired.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController)\n */\ndeclare class AbortController {\n  constructor();\n  /**\n   * Returns the AbortSignal object associated with this object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/signal)\n   */\n  get signal(): AbortSignal;\n  /**\n   * Invoking this method will set this object's AbortSignal's aborted flag and signal to any observers that the associated activity is to be aborted.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/abort)\n   */\n  abort(reason?: any): void;\n}\n/**\n * A signal object that allows you to communicate with a DOM request (such as a Fetch) and abort it if required via an AbortController object.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal)\n */\ndeclare abstract class AbortSignal extends EventTarget {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_static) */\n  static abort(reason?: any): AbortSignal;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/timeout_static) */\n  static timeout(delay: number): AbortSignal;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/any_static) */\n  static any(signals: AbortSignal[]): AbortSignal;\n  /**\n   * Returns true if this AbortSignal's AbortController has signaled to abort, and false otherwise.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/aborted)\n   */\n  get aborted(): boolean;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/reason) */\n  get reason(): any;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */\n  get onabort(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */\n  set onabort(value: any | null);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/throwIfAborted) */\n  throwIfAborted(): void;\n}\ninterface Scheduler {\n  wait(delay: number, maybeOptions?: SchedulerWaitOptions): Promise<void>;\n}\ninterface SchedulerWaitOptions {\n  signal?: AbortSignal;\n}\n/**\n * Extends the lifetime of the install and activate events dispatched on the global scope as part of the service worker lifecycle. This ensures that any functional events (like FetchEvent) are not dispatched until it upgrades database schemas and deletes the outdated cache entries.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent)\n */\ndeclare abstract class ExtendableEvent extends Event {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent/waitUntil) */\n  waitUntil(promise: Promise<any>): void;\n}\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent) */\ndeclare class CustomEvent<T = any> extends Event {\n  constructor(type: string, init?: CustomEventCustomEventInit);\n  /**\n   * Returns any custom data event was created with. Typically used for synthetic events.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent/detail)\n   */\n  get detail(): T;\n}\ninterface CustomEventCustomEventInit {\n  bubbles?: boolean;\n  cancelable?: boolean;\n  composed?: boolean;\n  detail?: any;\n}\n/**\n * A file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob)\n */\ndeclare class Blob {\n  constructor(type?: ((ArrayBuffer | ArrayBufferView) | string | Blob)[], options?: BlobOptions);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */\n  get size(): number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */\n  get type(): string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */\n  slice(start?: number, end?: number, type?: string): Blob;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/arrayBuffer) */\n  arrayBuffer(): Promise<ArrayBuffer>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/bytes) */\n  bytes(): Promise<Uint8Array>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */\n  text(): Promise<string>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/stream) */\n  stream(): ReadableStream;\n}\ninterface BlobOptions {\n  type?: string;\n}\n/**\n * Provides information about files and allows JavaScript in a web page to access their content.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File)\n */\ndeclare class File extends Blob {\n  constructor(\n    bits: ((ArrayBuffer | ArrayBufferView) | string | Blob)[] | undefined,\n    name: string,\n    options?: FileOptions,\n  );\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */\n  get name(): string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */\n  get lastModified(): number;\n}\ninterface FileOptions {\n  type?: string;\n  lastModified?: number;\n}\n/**\n * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)\n */\ndeclare abstract class CacheStorage {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CacheStorage/open) */\n  open(cacheName: string): Promise<Cache>;\n  readonly default: Cache;\n}\n/**\n * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)\n */\ndeclare abstract class Cache {\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#delete) */\n  delete(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<boolean>;\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#match) */\n  match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>;\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#put) */\n  put(request: RequestInfo | URL, response: Response): Promise<void>;\n}\ninterface CacheQueryOptions {\n  ignoreMethod?: boolean;\n}\n/**\n * The Web Crypto API provides a set of low-level functions for common cryptographic tasks.\n * The Workers runtime implements the full surface of this API, but with some differences in\n * the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms)\n * compared to those implemented in most browsers.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/)\n */\ndeclare abstract class Crypto {\n  /**\n   * Available only in secure contexts.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/subtle)\n   */\n  get subtle(): SubtleCrypto;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/getRandomValues) */\n  getRandomValues<\n    T extends\n      | Int8Array\n      | Uint8Array\n      | Int16Array\n      | Uint16Array\n      | Int32Array\n      | Uint32Array\n      | BigInt64Array\n      | BigUint64Array,\n  >(buffer: T): T;\n  /**\n   * Available only in secure contexts.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/randomUUID)\n   */\n  randomUUID(): string;\n  DigestStream: typeof DigestStream;\n}\n/**\n * This Web Crypto API interface provides a number of low-level cryptographic functions. It is accessed via the Crypto.subtle properties available in a window context (via Window.crypto).\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto)\n */\ndeclare abstract class SubtleCrypto {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/encrypt) */\n  encrypt(\n    algorithm: string | SubtleCryptoEncryptAlgorithm,\n    key: CryptoKey,\n    plainText: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/decrypt) */\n  decrypt(\n    algorithm: string | SubtleCryptoEncryptAlgorithm,\n    key: CryptoKey,\n    cipherText: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/sign) */\n  sign(\n    algorithm: string | SubtleCryptoSignAlgorithm,\n    key: CryptoKey,\n    data: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/verify) */\n  verify(\n    algorithm: string | SubtleCryptoSignAlgorithm,\n    key: CryptoKey,\n    signature: ArrayBuffer | ArrayBufferView,\n    data: ArrayBuffer | ArrayBufferView,\n  ): Promise<boolean>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/digest) */\n  digest(\n    algorithm: string | SubtleCryptoHashAlgorithm,\n    data: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/generateKey) */\n  generateKey(\n    algorithm: string | SubtleCryptoGenerateKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey | CryptoKeyPair>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveKey) */\n  deriveKey(\n    algorithm: string | SubtleCryptoDeriveKeyAlgorithm,\n    baseKey: CryptoKey,\n    derivedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveBits) */\n  deriveBits(\n    algorithm: string | SubtleCryptoDeriveKeyAlgorithm,\n    baseKey: CryptoKey,\n    length?: number | null,\n  ): Promise<ArrayBuffer>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/importKey) */\n  importKey(\n    format: string,\n    keyData: (ArrayBuffer | ArrayBufferView) | JsonWebKey,\n    algorithm: string | SubtleCryptoImportKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/exportKey) */\n  exportKey(format: string, key: CryptoKey): Promise<ArrayBuffer | JsonWebKey>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/wrapKey) */\n  wrapKey(\n    format: string,\n    key: CryptoKey,\n    wrappingKey: CryptoKey,\n    wrapAlgorithm: string | SubtleCryptoEncryptAlgorithm,\n  ): Promise<ArrayBuffer>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/unwrapKey) */\n  unwrapKey(\n    format: string,\n    wrappedKey: ArrayBuffer | ArrayBufferView,\n    unwrappingKey: CryptoKey,\n    unwrapAlgorithm: string | SubtleCryptoEncryptAlgorithm,\n    unwrappedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey>;\n  timingSafeEqual(a: ArrayBuffer | ArrayBufferView, b: ArrayBuffer | ArrayBufferView): boolean;\n}\n/**\n * The CryptoKey dictionary of the Web Crypto API represents a cryptographic key.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey)\n */\ndeclare abstract class CryptoKey {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/type) */\n  readonly type: string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/extractable) */\n  readonly extractable: boolean;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/algorithm) */\n  readonly algorithm:\n    | CryptoKeyKeyAlgorithm\n    | CryptoKeyAesKeyAlgorithm\n    | CryptoKeyHmacKeyAlgorithm\n    | CryptoKeyRsaKeyAlgorithm\n    | CryptoKeyEllipticKeyAlgorithm\n    | CryptoKeyArbitraryKeyAlgorithm;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/usages) */\n  readonly usages: string[];\n}\ninterface CryptoKeyPair {\n  publicKey: CryptoKey;\n  privateKey: CryptoKey;\n}\ninterface JsonWebKey {\n  kty: string;\n  use?: string;\n  key_ops?: string[];\n  alg?: string;\n  ext?: boolean;\n  crv?: string;\n  x?: string;\n  y?: string;\n  d?: string;\n  n?: string;\n  e?: string;\n  p?: string;\n  q?: string;\n  dp?: string;\n  dq?: string;\n  qi?: string;\n  oth?: RsaOtherPrimesInfo[];\n  k?: string;\n}\ninterface RsaOtherPrimesInfo {\n  r?: string;\n  d?: string;\n  t?: string;\n}\ninterface SubtleCryptoDeriveKeyAlgorithm {\n  name: string;\n  salt?: ArrayBuffer | ArrayBufferView;\n  iterations?: number;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  $public?: CryptoKey;\n  info?: ArrayBuffer | ArrayBufferView;\n}\ninterface SubtleCryptoEncryptAlgorithm {\n  name: string;\n  iv?: ArrayBuffer | ArrayBufferView;\n  additionalData?: ArrayBuffer | ArrayBufferView;\n  tagLength?: number;\n  counter?: ArrayBuffer | ArrayBufferView;\n  length?: number;\n  label?: ArrayBuffer | ArrayBufferView;\n}\ninterface SubtleCryptoGenerateKeyAlgorithm {\n  name: string;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  modulusLength?: number;\n  publicExponent?: ArrayBuffer | ArrayBufferView;\n  length?: number;\n  namedCurve?: string;\n}\ninterface SubtleCryptoHashAlgorithm {\n  name: string;\n}\ninterface SubtleCryptoImportKeyAlgorithm {\n  name: string;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  length?: number;\n  namedCurve?: string;\n  compressed?: boolean;\n}\ninterface SubtleCryptoSignAlgorithm {\n  name: string;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  dataLength?: number;\n  saltLength?: number;\n}\ninterface CryptoKeyKeyAlgorithm {\n  name: string;\n}\ninterface CryptoKeyAesKeyAlgorithm {\n  name: string;\n  length: number;\n}\ninterface CryptoKeyHmacKeyAlgorithm {\n  name: string;\n  hash: CryptoKeyKeyAlgorithm;\n  length: number;\n}\ninterface CryptoKeyRsaKeyAlgorithm {\n  name: string;\n  modulusLength: number;\n  publicExponent: ArrayBuffer | ArrayBufferView;\n  hash?: CryptoKeyKeyAlgorithm;\n}\ninterface CryptoKeyEllipticKeyAlgorithm {\n  name: string;\n  namedCurve: string;\n}\ninterface CryptoKeyArbitraryKeyAlgorithm {\n  name: string;\n  hash?: CryptoKeyKeyAlgorithm;\n  namedCurve?: string;\n  length?: number;\n}\ndeclare class DigestStream extends WritableStream<ArrayBuffer | ArrayBufferView> {\n  constructor(algorithm: string | SubtleCryptoHashAlgorithm);\n  readonly digest: Promise<ArrayBuffer>;\n  get bytesWritten(): number | bigint;\n}\n/**\n * A decoder for a specific method, that is a specific character encoding, like utf-8, iso-8859-2, koi8, cp1261, gbk, etc. A decoder takes a stream of bytes as input and emits a stream of code points. For a more scalable, non-native library, see StringView – a C-like representation of strings based on typed arrays.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder)\n */\ndeclare class TextDecoder {\n  constructor(label?: string, options?: TextDecoderConstructorOptions);\n  /**\n   * Returns the result of running encoding's decoder. The method can be invoked zero or more times with options's stream set to true, and then once without options's stream (or set to false), to process a fragmented input. If the invocation without options's stream (or set to false) has no input, it's clearest to omit both arguments.\n   *\n   * ```\n   * var string = \"\", decoder = new TextDecoder(encoding), buffer;\n   * while(buffer = next_chunk()) {\n   *   string += decoder.decode(buffer, {stream:true});\n   * }\n   * string += decoder.decode(); // end-of-queue\n   * ```\n   *\n   * If the error mode is \"fatal\" and encoding's decoder returns error, throws a TypeError.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/decode)\n   */\n  decode(input?: ArrayBuffer | ArrayBufferView, options?: TextDecoderDecodeOptions): string;\n  get encoding(): string;\n  get fatal(): boolean;\n  get ignoreBOM(): boolean;\n}\n/**\n * TextEncoder takes a stream of code points as input and emits a stream of bytes. For a more scalable, non-native library, see StringView – a C-like representation of strings based on typed arrays.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder)\n */\ndeclare class TextEncoder {\n  constructor();\n  /**\n   * Returns the result of running UTF-8's encoder.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encode)\n   */\n  encode(input?: string): Uint8Array;\n  /**\n   * Runs the UTF-8 encoder on source, stores the result of that operation into destination, and returns the progress made as an object wherein read is the number of converted code units of source and written is the number of bytes modified in destination.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encodeInto)\n   */\n  encodeInto(input: string, buffer: ArrayBuffer | ArrayBufferView): TextEncoderEncodeIntoResult;\n  get encoding(): string;\n}\ninterface TextDecoderConstructorOptions {\n  fatal: boolean;\n  ignoreBOM: boolean;\n}\ninterface TextDecoderDecodeOptions {\n  stream: boolean;\n}\ninterface TextEncoderEncodeIntoResult {\n  read: number;\n  written: number;\n}\n/**\n * Events providing information related to errors in scripts or in files.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent)\n */\ndeclare class ErrorEvent extends Event {\n  constructor(type: string, init?: ErrorEventErrorEventInit);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/filename) */\n  get filename(): string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/message) */\n  get message(): string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/lineno) */\n  get lineno(): number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/colno) */\n  get colno(): number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/error) */\n  get error(): any;\n}\ninterface ErrorEventErrorEventInit {\n  message?: string;\n  filename?: string;\n  lineno?: number;\n  colno?: number;\n  error?: any;\n}\n/**\n * Provides a way to easily construct a set of key/value pairs representing form fields and their values, which can then be easily sent using the XMLHttpRequest.send() method. It uses the same format a form would use if the encoding type were set to \"multipart/form-data\".\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData)\n */\ndeclare class FormData {\n  constructor();\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append) */\n  append(name: string, value: string): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append) */\n  append(name: string, value: Blob, filename?: string): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/delete) */\n  delete(name: string): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/get) */\n  get(name: string): (File | string) | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/getAll) */\n  getAll(name: string): (File | string)[];\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/has) */\n  has(name: string): boolean;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set) */\n  set(name: string, value: string): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set) */\n  set(name: string, value: Blob, filename?: string): void;\n  /* Returns an array of key, value pairs for every entry in the list. */\n  entries(): IterableIterator<[key: string, value: File | string]>;\n  /* Returns a list of keys in the list. */\n  keys(): IterableIterator<string>;\n  /* Returns a list of values in the list. */\n  values(): IterableIterator<File | string>;\n  forEach<This = unknown>(\n    callback: (this: This, value: File | string, key: string, parent: FormData) => void,\n    thisArg?: This,\n  ): void;\n  [Symbol.iterator](): IterableIterator<[key: string, value: File | string]>;\n}\ninterface ContentOptions {\n  html?: boolean;\n}\ndeclare class HTMLRewriter {\n  constructor();\n  on(selector: string, handlers: HTMLRewriterElementContentHandlers): HTMLRewriter;\n  onDocument(handlers: HTMLRewriterDocumentContentHandlers): HTMLRewriter;\n  transform(response: Response): Response;\n}\ninterface HTMLRewriterElementContentHandlers {\n  element?(element: Element): void | Promise<void>;\n  comments?(comment: Comment): void | Promise<void>;\n  text?(element: Text): void | Promise<void>;\n}\ninterface HTMLRewriterDocumentContentHandlers {\n  doctype?(doctype: Doctype): void | Promise<void>;\n  comments?(comment: Comment): void | Promise<void>;\n  text?(text: Text): void | Promise<void>;\n  end?(end: DocumentEnd): void | Promise<void>;\n}\ninterface Doctype {\n  readonly name: string | null;\n  readonly publicId: string | null;\n  readonly systemId: string | null;\n}\ninterface Element {\n  tagName: string;\n  readonly attributes: IterableIterator<string[]>;\n  readonly removed: boolean;\n  readonly namespaceURI: string;\n  getAttribute(name: string): string | null;\n  hasAttribute(name: string): boolean;\n  setAttribute(name: string, value: string): Element;\n  removeAttribute(name: string): Element;\n  before(content: string | ReadableStream | Response, options?: ContentOptions): Element;\n  after(content: string | ReadableStream | Response, options?: ContentOptions): Element;\n  prepend(content: string | ReadableStream | Response, options?: ContentOptions): Element;\n  append(content: string | ReadableStream | Response, options?: ContentOptions): Element;\n  replace(content: string | ReadableStream | Response, options?: ContentOptions): Element;\n  remove(): Element;\n  removeAndKeepContent(): Element;\n  setInnerContent(content: string | ReadableStream | Response, options?: ContentOptions): Element;\n  onEndTag(handler: (tag: EndTag) => void | Promise<void>): void;\n}\ninterface EndTag {\n  name: string;\n  before(content: string | ReadableStream | Response, options?: ContentOptions): EndTag;\n  after(content: string | ReadableStream | Response, options?: ContentOptions): EndTag;\n  remove(): EndTag;\n}\ninterface Comment {\n  text: string;\n  readonly removed: boolean;\n  before(content: string, options?: ContentOptions): Comment;\n  after(content: string, options?: ContentOptions): Comment;\n  replace(content: string, options?: ContentOptions): Comment;\n  remove(): Comment;\n}\ninterface Text {\n  readonly text: string;\n  readonly lastInTextNode: boolean;\n  readonly removed: boolean;\n  before(content: string | ReadableStream | Response, options?: ContentOptions): Text;\n  after(content: string | ReadableStream | Response, options?: ContentOptions): Text;\n  replace(content: string | ReadableStream | Response, options?: ContentOptions): Text;\n  remove(): Text;\n}\ninterface DocumentEnd {\n  append(content: string, options?: ContentOptions): DocumentEnd;\n}\n/**\n * This is the event type for fetch events dispatched on the service worker global scope. It contains information about the fetch, including the request and how the receiver will treat the response. It provides the event.respondWith() method, which allows us to provide a response to this fetch.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent)\n */\ndeclare abstract class FetchEvent extends ExtendableEvent {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/request) */\n  readonly request: Request;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/respondWith) */\n  respondWith(promise: Response | Promise<Response>): void;\n  passThroughOnException(): void;\n}\ntype HeadersInit = Headers | Iterable<Iterable<string>> | Record<string, string>;\n/**\n * This Fetch API interface allows you to perform various actions on HTTP request and response headers. These actions include retrieving, setting, adding to, and removing. A Headers object has an associated header list, which is initially empty and consists of zero or more name and value pairs.  You can add to this using methods like append() (see Examples.) In all methods of this interface, header names are matched by case-insensitive byte sequence.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers)\n */\ndeclare class Headers {\n  constructor(init?: HeadersInit);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/get) */\n  get(name: string): string | null;\n  getAll(name: string): string[];\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/getSetCookie) */\n  getSetCookie(): string[];\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/has) */\n  has(name: string): boolean;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/set) */\n  set(name: string, value: string): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/append) */\n  append(name: string, value: string): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/delete) */\n  delete(name: string): void;\n  forEach<This = unknown>(\n    callback: (this: This, value: string, key: string, parent: Headers) => void,\n    thisArg?: This,\n  ): void;\n  /* Returns an iterator allowing to go through all key/value pairs contained in this object. */\n  entries(): IterableIterator<[key: string, value: string]>;\n  /* Returns an iterator allowing to go through all keys of the key/value pairs contained in this object. */\n  keys(): IterableIterator<string>;\n  /* Returns an iterator allowing to go through all values of the key/value pairs contained in this object. */\n  values(): IterableIterator<string>;\n  [Symbol.iterator](): IterableIterator<[key: string, value: string]>;\n}\ntype BodyInit =\n  | ReadableStream<Uint8Array>\n  | string\n  | ArrayBuffer\n  | ArrayBufferView\n  | Blob\n  | URLSearchParams\n  | FormData;\ndeclare abstract class Body {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body) */\n  get body(): ReadableStream | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */\n  get bodyUsed(): boolean;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/arrayBuffer) */\n  arrayBuffer(): Promise<ArrayBuffer>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bytes) */\n  bytes(): Promise<Uint8Array>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/text) */\n  text(): Promise<string>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */\n  json<T>(): Promise<T>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/formData) */\n  formData(): Promise<FormData>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/blob) */\n  blob(): Promise<Blob>;\n}\n/**\n * This Fetch API interface represents the response to a request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)\n */\ndeclare var Response: {\n  prototype: Response;\n  new (body?: BodyInit | null, init?: ResponseInit): Response;\n  error(): Response;\n  redirect(url: string, status?: number): Response;\n  json(any: any, maybeInit?: ResponseInit | Response): Response;\n};\n/**\n * This Fetch API interface represents the response to a request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)\n */\ninterface Response extends Body {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/clone) */\n  clone(): Response;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/status) */\n  status: number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/statusText) */\n  statusText: string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/headers) */\n  headers: Headers;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/ok) */\n  ok: boolean;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/redirected) */\n  redirected: boolean;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/url) */\n  url: string;\n  webSocket: WebSocket | null;\n  cf: any | undefined;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/type) */\n  type: 'default' | 'error';\n}\ninterface ResponseInit {\n  status?: number;\n  statusText?: string;\n  headers?: HeadersInit;\n  cf?: any;\n  webSocket?: WebSocket | null;\n  encodeBody?: 'automatic' | 'manual';\n}\ntype RequestInfo<CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>> =\n  | Request<CfHostMetadata, Cf>\n  | string;\n/**\n * This Fetch API interface represents a resource request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request)\n */\ndeclare var Request: {\n  prototype: Request;\n  new <CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>>(\n    input: RequestInfo<CfProperties> | URL,\n    init?: RequestInit<Cf>,\n  ): Request<CfHostMetadata, Cf>;\n};\n/**\n * This Fetch API interface represents a resource request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request)\n */\ninterface Request<CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>> extends Body {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/clone) */\n  clone(): Request<CfHostMetadata, Cf>;\n  /**\n   * Returns request's HTTP method, which is \"GET\" by default.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/method)\n   */\n  method: string;\n  /**\n   * Returns the URL of request as a string.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/url)\n   */\n  url: string;\n  /**\n   * Returns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the \"Host\" header.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/headers)\n   */\n  headers: Headers;\n  /**\n   * Returns the redirect mode associated with request, which is a string indicating how redirects for the request will be handled during fetching. A request will follow redirects by default.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/redirect)\n   */\n  redirect: string;\n  fetcher: Fetcher | null;\n  /**\n   * Returns the signal associated with request, which is an AbortSignal object indicating whether or not request has been aborted, and its abort event handler.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/signal)\n   */\n  signal: AbortSignal;\n  cf: Cf | undefined;\n  /**\n   * Returns request's subresource integrity metadata, which is a cryptographic hash of the resource being fetched. Its value consists of multiple hashes separated by whitespace. [SRI]\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/integrity)\n   */\n  integrity: string;\n  /**\n   * Returns a boolean indicating whether or not request can outlive the global in which it was created.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/keepalive)\n   */\n  keepalive: boolean;\n  /**\n   * Returns the cache mode associated with request, which is a string indicating how the request will interact with the browser's cache when fetching.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/cache)\n   */\n  cache?: 'no-store';\n}\ninterface RequestInit<Cf = CfProperties> {\n  /* A string to set request's method. */\n  method?: string;\n  /* A Headers object, an object literal, or an array of two-item arrays to set request's headers. */\n  headers?: HeadersInit;\n  /* A BodyInit object or null to set request's body. */\n  body?: BodyInit | null;\n  /* A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */\n  redirect?: string;\n  fetcher?: Fetcher | null;\n  cf?: Cf;\n  /* A string indicating how the request will interact with the browser's cache to set request's cache. */\n  cache?: 'no-store';\n  /* A cryptographic hash of the resource to be fetched by request. Sets request's integrity. */\n  integrity?: string;\n  /* An AbortSignal to set request's signal. */\n  signal?: AbortSignal | null;\n  encodeResponseBody?: 'automatic' | 'manual';\n}\ntype Service<T extends Rpc.WorkerEntrypointBranded | undefined = undefined> = Fetcher<T>;\ntype Fetcher<\n  T extends Rpc.EntrypointBranded | undefined = undefined,\n  Reserved extends string = never,\n> = (T extends Rpc.EntrypointBranded\n  ? Rpc.Provider<T, Reserved | 'fetch' | 'connect'>\n  : unknown) & {\n  fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;\n  connect(address: SocketAddress | string, options?: SocketOptions): Socket;\n};\ninterface KVNamespaceListKey<Metadata, Key extends string = string> {\n  name: Key;\n  expiration?: number;\n  metadata?: Metadata;\n}\ntype KVNamespaceListResult<Metadata, Key extends string = string> =\n  | {\n      list_complete: false;\n      keys: KVNamespaceListKey<Metadata, Key>[];\n      cursor: string;\n      cacheStatus: string | null;\n    }\n  | {\n      list_complete: true;\n      keys: KVNamespaceListKey<Metadata, Key>[];\n      cacheStatus: string | null;\n    };\ninterface KVNamespace<Key extends string = string> {\n  get(key: Key, options?: Partial<KVNamespaceGetOptions<undefined>>): Promise<string | null>;\n  get(key: Key, type: 'text'): Promise<string | null>;\n  get<ExpectedValue = unknown>(key: Key, type: 'json'): Promise<ExpectedValue | null>;\n  get(key: Key, type: 'arrayBuffer'): Promise<ArrayBuffer | null>;\n  get(key: Key, type: 'stream'): Promise<ReadableStream | null>;\n  get(key: Key, options?: KVNamespaceGetOptions<'text'>): Promise<string | null>;\n  get<ExpectedValue = unknown>(\n    key: Key,\n    options?: KVNamespaceGetOptions<'json'>,\n  ): Promise<ExpectedValue | null>;\n  get(key: Key, options?: KVNamespaceGetOptions<'arrayBuffer'>): Promise<ArrayBuffer | null>;\n  get(key: Key, options?: KVNamespaceGetOptions<'stream'>): Promise<ReadableStream | null>;\n  get(key: Array<Key>, type: 'text'): Promise<Map<string, string | null>>;\n  get<ExpectedValue = unknown>(\n    key: Array<Key>,\n    type: 'json',\n  ): Promise<Map<string, ExpectedValue | null>>;\n  get(\n    key: Array<Key>,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<Map<string, string | null>>;\n  get(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<'text'>,\n  ): Promise<Map<string, string | null>>;\n  get<ExpectedValue = unknown>(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<'json'>,\n  ): Promise<Map<string, ExpectedValue | null>>;\n  list<Metadata = unknown>(\n    options?: KVNamespaceListOptions,\n  ): Promise<KVNamespaceListResult<Metadata, Key>>;\n  put(\n    key: Key,\n    value: string | ArrayBuffer | ArrayBufferView | ReadableStream,\n    options?: KVNamespacePutOptions,\n  ): Promise<void>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    type: 'text',\n  ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Key,\n    type: 'json',\n  ): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    type: 'arrayBuffer',\n  ): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    type: 'stream',\n  ): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<'text'>,\n  ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<'json'>,\n  ): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<'arrayBuffer'>,\n  ): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<'stream'>,\n  ): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Array<Key>,\n    type: 'text',\n  ): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Array<Key>,\n    type: 'json',\n  ): Promise<Map<string, KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Array<Key>,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<'text'>,\n  ): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<'json'>,\n  ): Promise<Map<string, KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>>;\n  delete(key: Key): Promise<void>;\n}\ninterface KVNamespaceListOptions {\n  limit?: number;\n  prefix?: string | null;\n  cursor?: string | null;\n}\ninterface KVNamespaceGetOptions<Type> {\n  type: Type;\n  cacheTtl?: number;\n}\ninterface KVNamespacePutOptions {\n  expiration?: number;\n  expirationTtl?: number;\n  metadata?: any | null;\n}\ninterface KVNamespaceGetWithMetadataResult<Value, Metadata> {\n  value: Value | null;\n  metadata: Metadata | null;\n  cacheStatus: string | null;\n}\ntype QueueContentType = 'text' | 'bytes' | 'json' | 'v8';\ninterface Queue<Body = unknown> {\n  send(message: Body, options?: QueueSendOptions): Promise<void>;\n  sendBatch(\n    messages: Iterable<MessageSendRequest<Body>>,\n    options?: QueueSendBatchOptions,\n  ): Promise<void>;\n}\ninterface QueueSendOptions {\n  contentType?: QueueContentType;\n  delaySeconds?: number;\n}\ninterface QueueSendBatchOptions {\n  delaySeconds?: number;\n}\ninterface MessageSendRequest<Body = unknown> {\n  body: Body;\n  contentType?: QueueContentType;\n  delaySeconds?: number;\n}\ninterface QueueRetryOptions {\n  delaySeconds?: number;\n}\ninterface Message<Body = unknown> {\n  readonly id: string;\n  readonly timestamp: Date;\n  readonly body: Body;\n  readonly attempts: number;\n  retry(options?: QueueRetryOptions): void;\n  ack(): void;\n}\ninterface QueueEvent<Body = unknown> extends ExtendableEvent {\n  readonly messages: readonly Message<Body>[];\n  readonly queue: string;\n  retryAll(options?: QueueRetryOptions): void;\n  ackAll(): void;\n}\ninterface MessageBatch<Body = unknown> {\n  readonly messages: readonly Message<Body>[];\n  readonly queue: string;\n  retryAll(options?: QueueRetryOptions): void;\n  ackAll(): void;\n}\ninterface R2Error extends Error {\n  readonly name: string;\n  readonly code: number;\n  readonly message: string;\n  readonly action: string;\n  readonly stack: any;\n}\ninterface R2ListOptions {\n  limit?: number;\n  prefix?: string;\n  cursor?: string;\n  delimiter?: string;\n  startAfter?: string;\n  include?: ('httpMetadata' | 'customMetadata')[];\n}\ndeclare abstract class R2Bucket {\n  head(key: string): Promise<R2Object | null>;\n  get(\n    key: string,\n    options: R2GetOptions & {\n      onlyIf: R2Conditional | Headers;\n    },\n  ): Promise<R2ObjectBody | R2Object | null>;\n  get(key: string, options?: R2GetOptions): Promise<R2ObjectBody | null>;\n  put(\n    key: string,\n    value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob,\n    options?: R2PutOptions & {\n      onlyIf: R2Conditional | Headers;\n    },\n  ): Promise<R2Object | null>;\n  put(\n    key: string,\n    value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob,\n    options?: R2PutOptions,\n  ): Promise<R2Object>;\n  createMultipartUpload(key: string, options?: R2MultipartOptions): Promise<R2MultipartUpload>;\n  resumeMultipartUpload(key: string, uploadId: string): R2MultipartUpload;\n  delete(keys: string | string[]): Promise<void>;\n  list(options?: R2ListOptions): Promise<R2Objects>;\n}\ninterface R2MultipartUpload {\n  readonly key: string;\n  readonly uploadId: string;\n  uploadPart(\n    partNumber: number,\n    value: ReadableStream | (ArrayBuffer | ArrayBufferView) | string | Blob,\n    options?: R2UploadPartOptions,\n  ): Promise<R2UploadedPart>;\n  abort(): Promise<void>;\n  complete(uploadedParts: R2UploadedPart[]): Promise<R2Object>;\n}\ninterface R2UploadedPart {\n  partNumber: number;\n  etag: string;\n}\ndeclare abstract class R2Object {\n  readonly key: string;\n  readonly version: string;\n  readonly size: number;\n  readonly etag: string;\n  readonly httpEtag: string;\n  readonly checksums: R2Checksums;\n  readonly uploaded: Date;\n  readonly httpMetadata?: R2HTTPMetadata;\n  readonly customMetadata?: Record<string, string>;\n  readonly range?: R2Range;\n  readonly storageClass: string;\n  readonly ssecKeyMd5?: string;\n  writeHttpMetadata(headers: Headers): void;\n}\ninterface R2ObjectBody extends R2Object {\n  get body(): ReadableStream;\n  get bodyUsed(): boolean;\n  arrayBuffer(): Promise<ArrayBuffer>;\n  text(): Promise<string>;\n  json<T>(): Promise<T>;\n  blob(): Promise<Blob>;\n}\ntype R2Range =\n  | {\n      offset: number;\n      length?: number;\n    }\n  | {\n      offset?: number;\n      length: number;\n    }\n  | {\n      suffix: number;\n    };\ninterface R2Conditional {\n  etagMatches?: string;\n  etagDoesNotMatch?: string;\n  uploadedBefore?: Date;\n  uploadedAfter?: Date;\n  secondsGranularity?: boolean;\n}\ninterface R2GetOptions {\n  onlyIf?: R2Conditional | Headers;\n  range?: R2Range | Headers;\n  ssecKey?: ArrayBuffer | string;\n}\ninterface R2PutOptions {\n  onlyIf?: R2Conditional | Headers;\n  httpMetadata?: R2HTTPMetadata | Headers;\n  customMetadata?: Record<string, string>;\n  md5?: (ArrayBuffer | ArrayBufferView) | string;\n  sha1?: (ArrayBuffer | ArrayBufferView) | string;\n  sha256?: (ArrayBuffer | ArrayBufferView) | string;\n  sha384?: (ArrayBuffer | ArrayBufferView) | string;\n  sha512?: (ArrayBuffer | ArrayBufferView) | string;\n  storageClass?: string;\n  ssecKey?: ArrayBuffer | string;\n}\ninterface R2MultipartOptions {\n  httpMetadata?: R2HTTPMetadata | Headers;\n  customMetadata?: Record<string, string>;\n  storageClass?: string;\n  ssecKey?: ArrayBuffer | string;\n}\ninterface R2Checksums {\n  readonly md5?: ArrayBuffer;\n  readonly sha1?: ArrayBuffer;\n  readonly sha256?: ArrayBuffer;\n  readonly sha384?: ArrayBuffer;\n  readonly sha512?: ArrayBuffer;\n  toJSON(): R2StringChecksums;\n}\ninterface R2StringChecksums {\n  md5?: string;\n  sha1?: string;\n  sha256?: string;\n  sha384?: string;\n  sha512?: string;\n}\ninterface R2HTTPMetadata {\n  contentType?: string;\n  contentLanguage?: string;\n  contentDisposition?: string;\n  contentEncoding?: string;\n  cacheControl?: string;\n  cacheExpiry?: Date;\n}\ntype R2Objects = {\n  objects: R2Object[];\n  delimitedPrefixes: string[];\n} & (\n  | {\n      truncated: true;\n      cursor: string;\n    }\n  | {\n      truncated: false;\n    }\n);\ninterface R2UploadPartOptions {\n  ssecKey?: ArrayBuffer | string;\n}\ndeclare abstract class ScheduledEvent extends ExtendableEvent {\n  readonly scheduledTime: number;\n  readonly cron: string;\n  noRetry(): void;\n}\ninterface ScheduledController {\n  readonly scheduledTime: number;\n  readonly cron: string;\n  noRetry(): void;\n}\ninterface QueuingStrategy<T = any> {\n  highWaterMark?: number | bigint;\n  size?: (chunk: T) => number | bigint;\n}\ninterface UnderlyingSink<W = any> {\n  type?: string;\n  start?: (controller: WritableStreamDefaultController) => void | Promise<void>;\n  write?: (chunk: W, controller: WritableStreamDefaultController) => void | Promise<void>;\n  abort?: (reason: any) => void | Promise<void>;\n  close?: () => void | Promise<void>;\n}\ninterface UnderlyingByteSource {\n  type: 'bytes';\n  autoAllocateChunkSize?: number;\n  start?: (controller: ReadableByteStreamController) => void | Promise<void>;\n  pull?: (controller: ReadableByteStreamController) => void | Promise<void>;\n  cancel?: (reason: any) => void | Promise<void>;\n}\ninterface UnderlyingSource<R = any> {\n  type?: '' | undefined;\n  start?: (controller: ReadableStreamDefaultController<R>) => void | Promise<void>;\n  pull?: (controller: ReadableStreamDefaultController<R>) => void | Promise<void>;\n  cancel?: (reason: any) => void | Promise<void>;\n  expectedLength?: number | bigint;\n}\ninterface Transformer<I = any, O = any> {\n  readableType?: string;\n  writableType?: string;\n  start?: (controller: TransformStreamDefaultController<O>) => void | Promise<void>;\n  transform?: (chunk: I, controller: TransformStreamDefaultController<O>) => void | Promise<void>;\n  flush?: (controller: TransformStreamDefaultController<O>) => void | Promise<void>;\n  cancel?: (reason: any) => void | Promise<void>;\n  expectedLength?: number;\n}\ninterface StreamPipeOptions {\n  /**\n   * Pipes this readable stream to a given writable stream destination. The way in which the piping process behaves under various error conditions can be customized with a number of passed options. It returns a promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered.\n   *\n   * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader.\n   *\n   * Errors and closures of the source and destination streams propagate as follows:\n   *\n   * An error in this source readable stream will abort destination, unless preventAbort is truthy. The returned promise will be rejected with the source's error, or with any error that occurs during aborting the destination.\n   *\n   * An error in destination will cancel this source readable stream, unless preventCancel is truthy. The returned promise will be rejected with the destination's error, or with any error that occurs during canceling the source.\n   *\n   * When this source readable stream closes, destination will be closed, unless preventClose is truthy. The returned promise will be fulfilled once this process completes, unless an error is encountered while closing the destination, in which case it will be rejected with that error.\n   *\n   * If destination starts out closed or closing, this source readable stream will be canceled, unless preventCancel is true. The returned promise will be rejected with an error indicating piping to a closed stream failed, or with any error that occurs during canceling the source.\n   *\n   * The signal option can be set to an AbortSignal to allow aborting an ongoing pipe operation via the corresponding AbortController. In this case, this source readable stream will be canceled, and destination aborted, unless the respective options preventCancel or preventAbort are set.\n   */\n  preventClose?: boolean;\n  preventAbort?: boolean;\n  preventCancel?: boolean;\n  signal?: AbortSignal;\n}\ntype ReadableStreamReadResult<R = any> =\n  | {\n      done: false;\n      value: R;\n    }\n  | {\n      done: true;\n      value?: undefined;\n    };\n/**\n * This Streams API interface represents a readable stream of byte data. The Fetch API offers a concrete instance of a ReadableStream through the body property of a Response object.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream)\n */\ninterface ReadableStream<R = any> {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/locked) */\n  get locked(): boolean;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/cancel) */\n  cancel(reason?: any): Promise<void>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader) */\n  getReader(): ReadableStreamDefaultReader<R>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader) */\n  getReader(options: ReadableStreamGetReaderOptions): ReadableStreamBYOBReader;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeThrough) */\n  pipeThrough<T>(\n    transform: ReadableWritablePair<T, R>,\n    options?: StreamPipeOptions,\n  ): ReadableStream<T>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeTo) */\n  pipeTo(destination: WritableStream<R>, options?: StreamPipeOptions): Promise<void>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/tee) */\n  tee(): [ReadableStream<R>, ReadableStream<R>];\n  values(options?: ReadableStreamValuesOptions): AsyncIterableIterator<R>;\n  [Symbol.asyncIterator](options?: ReadableStreamValuesOptions): AsyncIterableIterator<R>;\n}\n/**\n * This Streams API interface represents a readable stream of byte data. The Fetch API offers a concrete instance of a ReadableStream through the body property of a Response object.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream)\n */\ndeclare const ReadableStream: {\n  prototype: ReadableStream;\n  new (\n    underlyingSource: UnderlyingByteSource,\n    strategy?: QueuingStrategy<Uint8Array>,\n  ): ReadableStream<Uint8Array>;\n  new <R = any>(\n    underlyingSource?: UnderlyingSource<R>,\n    strategy?: QueuingStrategy<R>,\n  ): ReadableStream<R>;\n};\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader) */\ndeclare class ReadableStreamDefaultReader<R = any> {\n  constructor(stream: ReadableStream);\n  get closed(): Promise<void>;\n  cancel(reason?: any): Promise<void>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/read) */\n  read(): Promise<ReadableStreamReadResult<R>>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/releaseLock) */\n  releaseLock(): void;\n}\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader) */\ndeclare class ReadableStreamBYOBReader {\n  constructor(stream: ReadableStream);\n  get closed(): Promise<void>;\n  cancel(reason?: any): Promise<void>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/read) */\n  read<T extends ArrayBufferView>(view: T): Promise<ReadableStreamReadResult<T>>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/releaseLock) */\n  releaseLock(): void;\n  readAtLeast<T extends ArrayBufferView>(\n    minElements: number,\n    view: T,\n  ): Promise<ReadableStreamReadResult<T>>;\n}\ninterface ReadableStreamBYOBReaderReadableStreamBYOBReaderReadOptions {\n  min?: number;\n}\ninterface ReadableStreamGetReaderOptions {\n  /**\n   * Creates a ReadableStreamBYOBReader and locks the stream to the new reader.\n   *\n   * This call behaves the same way as the no-argument variant, except that it only works on readable byte streams, i.e. streams which were constructed specifically with the ability to handle \"bring your own buffer\" reading. The returned BYOB reader provides the ability to directly read individual chunks from the stream via its read() method, into developer-supplied buffers, allowing more precise control over allocation.\n   */\n  mode: 'byob';\n}\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest) */\ndeclare abstract class ReadableStreamBYOBRequest {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/view) */\n  get view(): Uint8Array | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respond) */\n  respond(bytesWritten: number): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respondWithNewView) */\n  respondWithNewView(view: ArrayBuffer | ArrayBufferView): void;\n  get atLeast(): number | null;\n}\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController) */\ndeclare abstract class ReadableStreamDefaultController<R = any> {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/desiredSize) */\n  get desiredSize(): number | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/close) */\n  close(): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/enqueue) */\n  enqueue(chunk?: R): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/error) */\n  error(reason: any): void;\n}\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController) */\ndeclare abstract class ReadableByteStreamController {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/byobRequest) */\n  get byobRequest(): ReadableStreamBYOBRequest | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/desiredSize) */\n  get desiredSize(): number | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/close) */\n  close(): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/enqueue) */\n  enqueue(chunk: ArrayBuffer | ArrayBufferView): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/error) */\n  error(reason: any): void;\n}\n/**\n * This Streams API interface represents a controller allowing control of a WritableStream's state. When constructing a WritableStream, the underlying sink is given a corresponding WritableStreamDefaultController instance to manipulate.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController)\n */\ndeclare abstract class WritableStreamDefaultController {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/signal) */\n  get signal(): AbortSignal;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/error) */\n  error(reason?: any): void;\n}\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController) */\ndeclare abstract class TransformStreamDefaultController<O = any> {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/desiredSize) */\n  get desiredSize(): number | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/enqueue) */\n  enqueue(chunk?: O): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/error) */\n  error(reason: any): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/terminate) */\n  terminate(): void;\n}\ninterface ReadableWritablePair<R = any, W = any> {\n  /**\n   * Provides a convenient, chainable way of piping this readable stream through a transform stream (or any other { writable, readable } pair). It simply pipes the stream into the writable side of the supplied pair, and returns the readable side for further use.\n   *\n   * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader.\n   */\n  writable: WritableStream<W>;\n  readable: ReadableStream<R>;\n}\n/**\n * This Streams API interface provides a standard abstraction for writing streaming data to a destination, known as a sink. This object comes with built-in backpressure and queuing.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream)\n */\ndeclare class WritableStream<W = any> {\n  constructor(underlyingSink?: UnderlyingSink, queuingStrategy?: QueuingStrategy);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/locked) */\n  get locked(): boolean;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/abort) */\n  abort(reason?: any): Promise<void>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/close) */\n  close(): Promise<void>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/getWriter) */\n  getWriter(): WritableStreamDefaultWriter<W>;\n}\n/**\n * This Streams API interface is the object returned by WritableStream.getWriter() and once created locks the < writer to the WritableStream ensuring that no other streams can write to the underlying sink.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter)\n */\ndeclare class WritableStreamDefaultWriter<W = any> {\n  constructor(stream: WritableStream);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/closed) */\n  get closed(): Promise<void>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/ready) */\n  get ready(): Promise<void>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/desiredSize) */\n  get desiredSize(): number | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/abort) */\n  abort(reason?: any): Promise<void>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/close) */\n  close(): Promise<void>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/write) */\n  write(chunk?: W): Promise<void>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/releaseLock) */\n  releaseLock(): void;\n}\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream) */\ndeclare class TransformStream<I = any, O = any> {\n  constructor(\n    transformer?: Transformer<I, O>,\n    writableStrategy?: QueuingStrategy<I>,\n    readableStrategy?: QueuingStrategy<O>,\n  );\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/readable) */\n  get readable(): ReadableStream<O>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/writable) */\n  get writable(): WritableStream<I>;\n}\ndeclare class FixedLengthStream extends IdentityTransformStream {\n  constructor(\n    expectedLength: number | bigint,\n    queuingStrategy?: IdentityTransformStreamQueuingStrategy,\n  );\n}\ndeclare class IdentityTransformStream extends TransformStream<\n  ArrayBuffer | ArrayBufferView,\n  Uint8Array\n> {\n  constructor(queuingStrategy?: IdentityTransformStreamQueuingStrategy);\n}\ninterface IdentityTransformStreamQueuingStrategy {\n  highWaterMark?: number | bigint;\n}\ninterface ReadableStreamValuesOptions {\n  preventCancel?: boolean;\n}\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CompressionStream) */\ndeclare class CompressionStream extends TransformStream<ArrayBuffer | ArrayBufferView, Uint8Array> {\n  constructor(format: 'gzip' | 'deflate' | 'deflate-raw');\n}\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DecompressionStream) */\ndeclare class DecompressionStream extends TransformStream<\n  ArrayBuffer | ArrayBufferView,\n  Uint8Array\n> {\n  constructor(format: 'gzip' | 'deflate' | 'deflate-raw');\n}\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoderStream) */\ndeclare class TextEncoderStream extends TransformStream<string, Uint8Array> {\n  constructor();\n  get encoding(): string;\n}\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoderStream) */\ndeclare class TextDecoderStream extends TransformStream<ArrayBuffer | ArrayBufferView, string> {\n  constructor(label?: string, options?: TextDecoderStreamTextDecoderStreamInit);\n  get encoding(): string;\n  get fatal(): boolean;\n  get ignoreBOM(): boolean;\n}\ninterface TextDecoderStreamTextDecoderStreamInit {\n  fatal?: boolean;\n  ignoreBOM?: boolean;\n}\n/**\n * This Streams API interface provides a built-in byte length queuing strategy that can be used when constructing streams.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy)\n */\ndeclare class ByteLengthQueuingStrategy implements QueuingStrategy<ArrayBufferView> {\n  constructor(init: QueuingStrategyInit);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/highWaterMark) */\n  get highWaterMark(): number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/size) */\n  get size(): (chunk?: any) => number;\n}\n/**\n * This Streams API interface provides a built-in byte length queuing strategy that can be used when constructing streams.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy)\n */\ndeclare class CountQueuingStrategy implements QueuingStrategy {\n  constructor(init: QueuingStrategyInit);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/highWaterMark) */\n  get highWaterMark(): number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/size) */\n  get size(): (chunk?: any) => number;\n}\ninterface QueuingStrategyInit {\n  /**\n   * Creates a new ByteLengthQueuingStrategy with the provided high water mark.\n   *\n   * Note that the provided high water mark will not be validated ahead of time. Instead, if it is negative, NaN, or not a number, the resulting ByteLengthQueuingStrategy will cause the corresponding stream constructor to throw.\n   */\n  highWaterMark: number;\n}\ninterface ScriptVersion {\n  id?: string;\n  tag?: string;\n  message?: string;\n}\ndeclare abstract class TailEvent extends ExtendableEvent {\n  readonly events: TraceItem[];\n  readonly traces: TraceItem[];\n}\ninterface TraceItem {\n  readonly event:\n    | (\n        | TraceItemFetchEventInfo\n        | TraceItemJsRpcEventInfo\n        | TraceItemScheduledEventInfo\n        | TraceItemAlarmEventInfo\n        | TraceItemQueueEventInfo\n        | TraceItemEmailEventInfo\n        | TraceItemTailEventInfo\n        | TraceItemCustomEventInfo\n        | TraceItemHibernatableWebSocketEventInfo\n      )\n    | null;\n  readonly eventTimestamp: number | null;\n  readonly logs: TraceLog[];\n  readonly exceptions: TraceException[];\n  readonly diagnosticsChannelEvents: TraceDiagnosticChannelEvent[];\n  readonly scriptName: string | null;\n  readonly entrypoint?: string;\n  readonly scriptVersion?: ScriptVersion;\n  readonly dispatchNamespace?: string;\n  readonly scriptTags?: string[];\n  readonly outcome: string;\n  readonly executionModel: string;\n  readonly truncated: boolean;\n  readonly cpuTime: number;\n  readonly wallTime: number;\n}\ninterface TraceItemAlarmEventInfo {\n  readonly scheduledTime: Date;\n}\ninterface TraceItemCustomEventInfo {}\ninterface TraceItemScheduledEventInfo {\n  readonly scheduledTime: number;\n  readonly cron: string;\n}\ninterface TraceItemQueueEventInfo {\n  readonly queue: string;\n  readonly batchSize: number;\n}\ninterface TraceItemEmailEventInfo {\n  readonly mailFrom: string;\n  readonly rcptTo: string;\n  readonly rawSize: number;\n}\ninterface TraceItemTailEventInfo {\n  readonly consumedEvents: TraceItemTailEventInfoTailItem[];\n}\ninterface TraceItemTailEventInfoTailItem {\n  readonly scriptName: string | null;\n}\ninterface TraceItemFetchEventInfo {\n  readonly response?: TraceItemFetchEventInfoResponse;\n  readonly request: TraceItemFetchEventInfoRequest;\n}\ninterface TraceItemFetchEventInfoRequest {\n  readonly cf?: any;\n  readonly headers: Record<string, string>;\n  readonly method: string;\n  readonly url: string;\n  getUnredacted(): TraceItemFetchEventInfoRequest;\n}\ninterface TraceItemFetchEventInfoResponse {\n  readonly status: number;\n}\ninterface TraceItemJsRpcEventInfo {\n  readonly rpcMethod: string;\n}\ninterface TraceItemHibernatableWebSocketEventInfo {\n  readonly getWebSocketEvent:\n    | TraceItemHibernatableWebSocketEventInfoMessage\n    | TraceItemHibernatableWebSocketEventInfoClose\n    | TraceItemHibernatableWebSocketEventInfoError;\n}\ninterface TraceItemHibernatableWebSocketEventInfoMessage {\n  readonly webSocketEventType: string;\n}\ninterface TraceItemHibernatableWebSocketEventInfoClose {\n  readonly webSocketEventType: string;\n  readonly code: number;\n  readonly wasClean: boolean;\n}\ninterface TraceItemHibernatableWebSocketEventInfoError {\n  readonly webSocketEventType: string;\n}\ninterface TraceLog {\n  readonly timestamp: number;\n  readonly level: string;\n  readonly message: any;\n}\ninterface TraceException {\n  readonly timestamp: number;\n  readonly message: string;\n  readonly name: string;\n  readonly stack?: string;\n}\ninterface TraceDiagnosticChannelEvent {\n  readonly timestamp: number;\n  readonly channel: string;\n  readonly message: any;\n}\ninterface TraceMetrics {\n  readonly cpuTime: number;\n  readonly wallTime: number;\n}\ninterface UnsafeTraceMetrics {\n  fromTrace(item: TraceItem): TraceMetrics;\n}\n/**\n * The URL interface represents an object providing static methods used for creating object URLs.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL)\n */\ndeclare class URL {\n  constructor(url: string | URL, base?: string | URL);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/origin) */\n  get origin(): string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href) */\n  get href(): string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href) */\n  set href(value: string);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol) */\n  get protocol(): string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol) */\n  set protocol(value: string);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username) */\n  get username(): string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username) */\n  set username(value: string);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password) */\n  get password(): string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password) */\n  set password(value: string);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host) */\n  get host(): string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host) */\n  set host(value: string);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname) */\n  get hostname(): string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname) */\n  set hostname(value: string);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port) */\n  get port(): string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port) */\n  set port(value: string);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname) */\n  get pathname(): string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname) */\n  set pathname(value: string);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search) */\n  get search(): string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search) */\n  set search(value: string);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash) */\n  get hash(): string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash) */\n  set hash(value: string);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/searchParams) */\n  get searchParams(): URLSearchParams;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/toJSON) */\n  toJSON(): string;\n  /*function toString() { [native code] }*/\n  toString(): string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/canParse_static) */\n  static canParse(url: string, base?: string): boolean;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/parse_static) */\n  static parse(url: string, base?: string): URL | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/createObjectURL_static) */\n  static createObjectURL(object: File | Blob): string;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/revokeObjectURL_static) */\n  static revokeObjectURL(object_url: string): void;\n}\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams) */\ndeclare class URLSearchParams {\n  constructor(init?: Iterable<Iterable<string>> | Record<string, string> | string);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/size) */\n  get size(): number;\n  /**\n   * Appends a specified key/value pair as a new search parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/append)\n   */\n  append(name: string, value: string): void;\n  /**\n   * Deletes the given search parameter, and its associated value, from the list of all search parameters.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/delete)\n   */\n  delete(name: string, value?: string): void;\n  /**\n   * Returns the first value associated to the given search parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/get)\n   */\n  get(name: string): string | null;\n  /**\n   * Returns all the values association with a given search parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/getAll)\n   */\n  getAll(name: string): string[];\n  /**\n   * Returns a Boolean indicating if such a search parameter exists.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/has)\n   */\n  has(name: string, value?: string): boolean;\n  /**\n   * Sets the value associated to a given search parameter to the given value. If there were several values, delete the others.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/set)\n   */\n  set(name: string, value: string): void;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/sort) */\n  sort(): void;\n  /* Returns an array of key, value pairs for every entry in the search params. */\n  entries(): IterableIterator<[key: string, value: string]>;\n  /* Returns a list of keys in the search params. */\n  keys(): IterableIterator<string>;\n  /* Returns a list of values in the search params. */\n  values(): IterableIterator<string>;\n  forEach<This = unknown>(\n    callback: (this: This, value: string, key: string, parent: URLSearchParams) => void,\n    thisArg?: This,\n  ): void;\n  /*function toString() { [native code] } Returns a string containing a query string suitable for use in a URL. Does not include the question mark. */\n  toString(): string;\n  [Symbol.iterator](): IterableIterator<[key: string, value: string]>;\n}\ndeclare class URLPattern {\n  constructor(\n    input?: string | URLPatternInit,\n    baseURL?: string | URLPatternOptions,\n    patternOptions?: URLPatternOptions,\n  );\n  get protocol(): string;\n  get username(): string;\n  get password(): string;\n  get hostname(): string;\n  get port(): string;\n  get pathname(): string;\n  get search(): string;\n  get hash(): string;\n  test(input?: string | URLPatternInit, baseURL?: string): boolean;\n  exec(input?: string | URLPatternInit, baseURL?: string): URLPatternResult | null;\n}\ninterface URLPatternInit {\n  protocol?: string;\n  username?: string;\n  password?: string;\n  hostname?: string;\n  port?: string;\n  pathname?: string;\n  search?: string;\n  hash?: string;\n  baseURL?: string;\n}\ninterface URLPatternComponentResult {\n  input: string;\n  groups: Record<string, string>;\n}\ninterface URLPatternResult {\n  inputs: (string | URLPatternInit)[];\n  protocol: URLPatternComponentResult;\n  username: URLPatternComponentResult;\n  password: URLPatternComponentResult;\n  hostname: URLPatternComponentResult;\n  port: URLPatternComponentResult;\n  pathname: URLPatternComponentResult;\n  search: URLPatternComponentResult;\n  hash: URLPatternComponentResult;\n}\ninterface URLPatternOptions {\n  ignoreCase?: boolean;\n}\n/**\n * A CloseEvent is sent to clients using WebSockets when the connection is closed. This is delivered to the listener indicated by the WebSocket object's onclose attribute.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent)\n */\ndeclare class CloseEvent extends Event {\n  constructor(type: string, initializer?: CloseEventInit);\n  /**\n   * Returns the WebSocket connection close code provided by the server.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/code)\n   */\n  readonly code: number;\n  /**\n   * Returns the WebSocket connection close reason provided by the server.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/reason)\n   */\n  readonly reason: string;\n  /**\n   * Returns true if the connection closed cleanly; false otherwise.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/wasClean)\n   */\n  readonly wasClean: boolean;\n}\ninterface CloseEventInit {\n  code?: number;\n  reason?: string;\n  wasClean?: boolean;\n}\n/**\n * A message received by a target object.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent)\n */\ndeclare class MessageEvent extends Event {\n  constructor(type: string, initializer: MessageEventInit);\n  /**\n   * Returns the data of the message.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/data)\n   */\n  readonly data: ArrayBuffer | string;\n}\ninterface MessageEventInit {\n  data: ArrayBuffer | string;\n}\ntype WebSocketEventMap = {\n  close: CloseEvent;\n  message: MessageEvent;\n  open: Event;\n  error: ErrorEvent;\n};\n/**\n * Provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket)\n */\ndeclare var WebSocket: {\n  prototype: WebSocket;\n  new (url: string, protocols?: string[] | string): WebSocket;\n  readonly READY_STATE_CONNECTING: number;\n  readonly CONNECTING: number;\n  readonly READY_STATE_OPEN: number;\n  readonly OPEN: number;\n  readonly READY_STATE_CLOSING: number;\n  readonly CLOSING: number;\n  readonly READY_STATE_CLOSED: number;\n  readonly CLOSED: number;\n};\n/**\n * Provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket)\n */\ninterface WebSocket extends EventTarget<WebSocketEventMap> {\n  accept(): void;\n  /**\n   * Transmits data using the WebSocket connection. data can be a string, a Blob, an ArrayBuffer, or an ArrayBufferView.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/send)\n   */\n  send(message: (ArrayBuffer | ArrayBufferView) | string): void;\n  /**\n   * Closes the WebSocket connection, optionally using code as the the WebSocket connection close code and reason as the the WebSocket connection close reason.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/close)\n   */\n  close(code?: number, reason?: string): void;\n  serializeAttachment(attachment: any): void;\n  deserializeAttachment(): any | null;\n  /**\n   * Returns the state of the WebSocket object's connection. It can have the values described below.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/readyState)\n   */\n  readyState: number;\n  /**\n   * Returns the URL that was used to establish the WebSocket connection.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/url)\n   */\n  url: string | null;\n  /**\n   * Returns the subprotocol selected by the server, if any. It can be used in conjunction with the array form of the constructor's second argument to perform subprotocol negotiation.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/protocol)\n   */\n  protocol: string | null;\n  /**\n   * Returns the extensions selected by the server, if any.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/extensions)\n   */\n  extensions: string | null;\n}\ndeclare const WebSocketPair: {\n  new (): {\n    0: WebSocket;\n    1: WebSocket;\n  };\n};\ninterface SqlStorage {\n  exec<T extends Record<string, SqlStorageValue>>(\n    query: string,\n    ...bindings: any[]\n  ): SqlStorageCursor<T>;\n  get databaseSize(): number;\n  Cursor: typeof SqlStorageCursor;\n  Statement: typeof SqlStorageStatement;\n}\ndeclare abstract class SqlStorageStatement {}\ntype SqlStorageValue = ArrayBuffer | string | number | null;\ndeclare abstract class SqlStorageCursor<T extends Record<string, SqlStorageValue>> {\n  next():\n    | {\n        done?: false;\n        value: T;\n      }\n    | {\n        done: true;\n        value?: never;\n      };\n  toArray(): T[];\n  one(): T;\n  raw<U extends SqlStorageValue[]>(): IterableIterator<U>;\n  columnNames: string[];\n  get rowsRead(): number;\n  get rowsWritten(): number;\n  [Symbol.iterator](): IterableIterator<T>;\n}\ninterface Socket {\n  get readable(): ReadableStream;\n  get writable(): WritableStream;\n  get closed(): Promise<void>;\n  get opened(): Promise<SocketInfo>;\n  get upgraded(): boolean;\n  get secureTransport(): 'on' | 'off' | 'starttls';\n  close(): Promise<void>;\n  startTls(options?: TlsOptions): Socket;\n}\ninterface SocketOptions {\n  secureTransport?: string;\n  allowHalfOpen: boolean;\n  highWaterMark?: number | bigint;\n}\ninterface SocketAddress {\n  hostname: string;\n  port: number;\n}\ninterface TlsOptions {\n  expectedServerHostname?: string;\n}\ninterface SocketInfo {\n  remoteAddress?: string;\n  localAddress?: string;\n}\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource) */\ndeclare class EventSource extends EventTarget {\n  constructor(url: string, init?: EventSourceEventSourceInit);\n  /**\n   * Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close)\n   */\n  close(): void;\n  /**\n   * Returns the URL providing the event stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url)\n   */\n  get url(): string;\n  /**\n   * Returns true if the credentials mode for connection requests to the URL providing the event stream is set to \"include\", and false otherwise.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials)\n   */\n  get withCredentials(): boolean;\n  /**\n   * Returns the state of this EventSource object's connection. It can have the values described below.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState)\n   */\n  get readyState(): number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */\n  get onopen(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */\n  set onopen(value: any | null);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */\n  get onmessage(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */\n  set onmessage(value: any | null);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */\n  get onerror(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */\n  set onerror(value: any | null);\n  static readonly CONNECTING: number;\n  static readonly OPEN: number;\n  static readonly CLOSED: number;\n  static from(stream: ReadableStream): EventSource;\n}\ninterface EventSourceEventSourceInit {\n  withCredentials?: boolean;\n  fetcher?: Fetcher;\n}\ninterface Container {\n  get running(): boolean;\n  start(options?: ContainerStartupOptions): void;\n  monitor(): Promise<void>;\n  destroy(error?: any): Promise<void>;\n  signal(signo: number): void;\n  getTcpPort(port: number): Fetcher;\n}\ninterface ContainerStartupOptions {\n  entrypoint?: string[];\n  enableInternet: boolean;\n  env?: Record<string, string>;\n}\ntype AiImageClassificationInput = {\n  image: number[];\n};\ntype AiImageClassificationOutput = {\n  score?: number;\n  label?: string;\n}[];\ndeclare abstract class BaseAiImageClassification {\n  inputs: AiImageClassificationInput;\n  postProcessedOutputs: AiImageClassificationOutput;\n}\ntype AiImageToTextInput = {\n  image: number[];\n  prompt?: string;\n  max_tokens?: number;\n  temperature?: number;\n  top_p?: number;\n  top_k?: number;\n  seed?: number;\n  repetition_penalty?: number;\n  frequency_penalty?: number;\n  presence_penalty?: number;\n  raw?: boolean;\n  messages?: RoleScopedChatInput[];\n};\ntype AiImageToTextOutput = {\n  description: string;\n};\ndeclare abstract class BaseAiImageToText {\n  inputs: AiImageToTextInput;\n  postProcessedOutputs: AiImageToTextOutput;\n}\ntype AiImageTextToTextInput = {\n  image: string;\n  prompt?: string;\n  max_tokens?: number;\n  temperature?: number;\n  ignore_eos?: boolean;\n  top_p?: number;\n  top_k?: number;\n  seed?: number;\n  repetition_penalty?: number;\n  frequency_penalty?: number;\n  presence_penalty?: number;\n  raw?: boolean;\n  messages?: RoleScopedChatInput[];\n};\ntype AiImageTextToTextOutput = {\n  description: string;\n};\ndeclare abstract class BaseAiImageTextToText {\n  inputs: AiImageTextToTextInput;\n  postProcessedOutputs: AiImageTextToTextOutput;\n}\ntype AiObjectDetectionInput = {\n  image: number[];\n};\ntype AiObjectDetectionOutput = {\n  score?: number;\n  label?: string;\n}[];\ndeclare abstract class BaseAiObjectDetection {\n  inputs: AiObjectDetectionInput;\n  postProcessedOutputs: AiObjectDetectionOutput;\n}\ntype AiSentenceSimilarityInput = {\n  source: string;\n  sentences: string[];\n};\ntype AiSentenceSimilarityOutput = number[];\ndeclare abstract class BaseAiSentenceSimilarity {\n  inputs: AiSentenceSimilarityInput;\n  postProcessedOutputs: AiSentenceSimilarityOutput;\n}\ntype AiAutomaticSpeechRecognitionInput = {\n  audio: number[];\n};\ntype AiAutomaticSpeechRecognitionOutput = {\n  text?: string;\n  words?: {\n    word: string;\n    start: number;\n    end: number;\n  }[];\n  vtt?: string;\n};\ndeclare abstract class BaseAiAutomaticSpeechRecognition {\n  inputs: AiAutomaticSpeechRecognitionInput;\n  postProcessedOutputs: AiAutomaticSpeechRecognitionOutput;\n}\ntype AiSummarizationInput = {\n  input_text: string;\n  max_length?: number;\n};\ntype AiSummarizationOutput = {\n  summary: string;\n};\ndeclare abstract class BaseAiSummarization {\n  inputs: AiSummarizationInput;\n  postProcessedOutputs: AiSummarizationOutput;\n}\ntype AiTextClassificationInput = {\n  text: string;\n};\ntype AiTextClassificationOutput = {\n  score?: number;\n  label?: string;\n}[];\ndeclare abstract class BaseAiTextClassification {\n  inputs: AiTextClassificationInput;\n  postProcessedOutputs: AiTextClassificationOutput;\n}\ntype AiTextEmbeddingsInput = {\n  text: string | string[];\n};\ntype AiTextEmbeddingsOutput = {\n  shape: number[];\n  data: number[][];\n};\ndeclare abstract class BaseAiTextEmbeddings {\n  inputs: AiTextEmbeddingsInput;\n  postProcessedOutputs: AiTextEmbeddingsOutput;\n}\ntype RoleScopedChatInput = {\n  role: 'user' | 'assistant' | 'system' | 'tool' | (string & NonNullable<unknown>);\n  content: string;\n  name?: string;\n};\ntype AiTextGenerationToolLegacyInput = {\n  name: string;\n  description: string;\n  parameters?: {\n    type: 'object' | (string & NonNullable<unknown>);\n    properties: {\n      [key: string]: {\n        type: string;\n        description?: string;\n      };\n    };\n    required: string[];\n  };\n};\ntype AiTextGenerationToolInput = {\n  type: 'function' | (string & NonNullable<unknown>);\n  function: {\n    name: string;\n    description: string;\n    parameters?: {\n      type: 'object' | (string & NonNullable<unknown>);\n      properties: {\n        [key: string]: {\n          type: string;\n          description?: string;\n        };\n      };\n      required: string[];\n    };\n  };\n};\ntype AiTextGenerationFunctionsInput = {\n  name: string;\n  code: string;\n};\ntype AiTextGenerationResponseFormat = {\n  type: string;\n  json_schema?: any;\n};\ntype AiTextGenerationInput = {\n  prompt?: string;\n  raw?: boolean;\n  stream?: boolean;\n  max_tokens?: number;\n  temperature?: number;\n  top_p?: number;\n  top_k?: number;\n  seed?: number;\n  repetition_penalty?: number;\n  frequency_penalty?: number;\n  presence_penalty?: number;\n  messages?: RoleScopedChatInput[];\n  response_format?: AiTextGenerationResponseFormat;\n  tools?:\n    | AiTextGenerationToolInput[]\n    | AiTextGenerationToolLegacyInput[]\n    | (object & NonNullable<unknown>);\n  functions?: AiTextGenerationFunctionsInput[];\n};\ntype AiTextGenerationOutput =\n  | {\n      response?: string;\n      tool_calls?: {\n        name: string;\n        arguments: unknown;\n      }[];\n    }\n  | ReadableStream;\ndeclare abstract class BaseAiTextGeneration {\n  inputs: AiTextGenerationInput;\n  postProcessedOutputs: AiTextGenerationOutput;\n}\ntype AiTextToSpeechInput = {\n  prompt: string;\n  lang?: string;\n};\ntype AiTextToSpeechOutput =\n  | Uint8Array\n  | {\n      audio: string;\n    };\ndeclare abstract class BaseAiTextToSpeech {\n  inputs: AiTextToSpeechInput;\n  postProcessedOutputs: AiTextToSpeechOutput;\n}\ntype AiTextToImageInput = {\n  prompt: string;\n  negative_prompt?: string;\n  height?: number;\n  width?: number;\n  image?: number[];\n  image_b64?: string;\n  mask?: number[];\n  num_steps?: number;\n  strength?: number;\n  guidance?: number;\n  seed?: number;\n};\ntype AiTextToImageOutput = ReadableStream<Uint8Array>;\ndeclare abstract class BaseAiTextToImage {\n  inputs: AiTextToImageInput;\n  postProcessedOutputs: AiTextToImageOutput;\n}\ntype AiTranslationInput = {\n  text: string;\n  target_lang: string;\n  source_lang?: string;\n};\ntype AiTranslationOutput = {\n  translated_text?: string;\n};\ndeclare abstract class BaseAiTranslation {\n  inputs: AiTranslationInput;\n  postProcessedOutputs: AiTranslationOutput;\n}\ntype Ai_Cf_Openai_Whisper_Input =\n  | string\n  | {\n      /**\n       * An array of integers that represent the audio data constrained to 8-bit unsigned integer values\n       */\n      audio: number[];\n    };\ninterface Ai_Cf_Openai_Whisper_Output {\n  /**\n   * The transcription\n   */\n  text: string;\n  word_count?: number;\n  words?: {\n    word?: string;\n    /**\n     * The second this word begins in the recording\n     */\n    start?: number;\n    /**\n     * The ending second when the word completes\n     */\n    end?: number;\n  }[];\n  vtt?: string;\n}\ndeclare abstract class Base_Ai_Cf_Openai_Whisper {\n  inputs: Ai_Cf_Openai_Whisper_Input;\n  postProcessedOutputs: Ai_Cf_Openai_Whisper_Output;\n}\ntype Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input =\n  | string\n  | {\n      /**\n       * The input text prompt for the model to generate a response.\n       */\n      prompt?: string;\n      /**\n       * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n       */\n      raw?: boolean;\n      /**\n       * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n       */\n      top_p?: number;\n      /**\n       * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n       */\n      top_k?: number;\n      /**\n       * Random seed for reproducibility of the generation.\n       */\n      seed?: number;\n      /**\n       * Penalty for repeated tokens; higher values discourage repetition.\n       */\n      repetition_penalty?: number;\n      /**\n       * Decreases the likelihood of the model repeating the same lines verbatim.\n       */\n      frequency_penalty?: number;\n      /**\n       * Increases the likelihood of the model introducing new topics.\n       */\n      presence_penalty?: number;\n      image: number[] | (string & NonNullable<unknown>);\n      /**\n       * The maximum number of tokens to generate in the response.\n       */\n      max_tokens?: number;\n    };\ninterface Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output {\n  description?: string;\n}\ndeclare abstract class Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M {\n  inputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input;\n  postProcessedOutputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output;\n}\ntype Ai_Cf_Openai_Whisper_Tiny_En_Input =\n  | string\n  | {\n      /**\n       * An array of integers that represent the audio data constrained to 8-bit unsigned integer values\n       */\n      audio: number[];\n    };\ninterface Ai_Cf_Openai_Whisper_Tiny_En_Output {\n  /**\n   * The transcription\n   */\n  text: string;\n  word_count?: number;\n  words?: {\n    word?: string;\n    /**\n     * The second this word begins in the recording\n     */\n    start?: number;\n    /**\n     * The ending second when the word completes\n     */\n    end?: number;\n  }[];\n  vtt?: string;\n}\ndeclare abstract class Base_Ai_Cf_Openai_Whisper_Tiny_En {\n  inputs: Ai_Cf_Openai_Whisper_Tiny_En_Input;\n  postProcessedOutputs: Ai_Cf_Openai_Whisper_Tiny_En_Output;\n}\ninterface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input {\n  /**\n   * Base64 encoded value of the audio data.\n   */\n  audio: string;\n  /**\n   * Supported tasks are 'translate' or 'transcribe'.\n   */\n  task?: string;\n  /**\n   * The language of the audio being transcribed or translated.\n   */\n  language?: string;\n  /**\n   * Preprocess the audio with a voice activity detection model.\n   */\n  vad_filter?: string;\n  /**\n   * A text prompt to help provide context to the model on the contents of the audio.\n   */\n  initial_prompt?: string;\n  /**\n   * The prefix it appended the the beginning of the output of the transcription and can guide the transcription result.\n   */\n  prefix?: string;\n}\ninterface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output {\n  transcription_info?: {\n    /**\n     * The language of the audio being transcribed or translated.\n     */\n    language?: string;\n    /**\n     * The confidence level or probability of the detected language being accurate, represented as a decimal between 0 and 1.\n     */\n    language_probability?: number;\n    /**\n     * The total duration of the original audio file, in seconds.\n     */\n    duration?: number;\n    /**\n     * The duration of the audio after applying Voice Activity Detection (VAD) to remove silent or irrelevant sections, in seconds.\n     */\n    duration_after_vad?: number;\n  };\n  /**\n   * The complete transcription of the audio.\n   */\n  text: string;\n  /**\n   * The total number of words in the transcription.\n   */\n  word_count?: number;\n  segments?: {\n    /**\n     * The starting time of the segment within the audio, in seconds.\n     */\n    start?: number;\n    /**\n     * The ending time of the segment within the audio, in seconds.\n     */\n    end?: number;\n    /**\n     * The transcription of the segment.\n     */\n    text?: string;\n    /**\n     * The temperature used in the decoding process, controlling randomness in predictions. Lower values result in more deterministic outputs.\n     */\n    temperature?: number;\n    /**\n     * The average log probability of the predictions for the words in this segment, indicating overall confidence.\n     */\n    avg_logprob?: number;\n    /**\n     * The compression ratio of the input to the output, measuring how much the text was compressed during the transcription process.\n     */\n    compression_ratio?: number;\n    /**\n     * The probability that the segment contains no speech, represented as a decimal between 0 and 1.\n     */\n    no_speech_prob?: number;\n    words?: {\n      /**\n       * The individual word transcribed from the audio.\n       */\n      word?: string;\n      /**\n       * The starting time of the word within the audio, in seconds.\n       */\n      start?: number;\n      /**\n       * The ending time of the word within the audio, in seconds.\n       */\n      end?: number;\n    }[];\n  }[];\n  /**\n   * The transcription in WebVTT format, which includes timing and text information for use in subtitles.\n   */\n  vtt?: string;\n}\ndeclare abstract class Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo {\n  inputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input;\n  postProcessedOutputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output;\n}\ntype Ai_Cf_Baai_Bge_M3_Input = BGEM3InputQueryAndContexts | BGEM3InputEmbedding;\ninterface BGEM3InputQueryAndContexts {\n  /**\n   * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts\n   */\n  query?: string;\n  /**\n   * List of provided contexts. Note that the index in this array is important, as the response will refer to it.\n   */\n  contexts: {\n    /**\n     * One of the provided context content\n     */\n    text?: string;\n  }[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\ninterface BGEM3InputEmbedding {\n  text: string | string[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\ntype Ai_Cf_Baai_Bge_M3_Output =\n  | BGEM3OuputQuery\n  | BGEM3OutputEmbeddingForContexts\n  | BGEM3OuputEmbedding;\ninterface BGEM3OuputQuery {\n  response?: {\n    /**\n     * Index of the context in the request\n     */\n    id?: number;\n    /**\n     * Score of the context under the index.\n     */\n    score?: number;\n  }[];\n}\ninterface BGEM3OutputEmbeddingForContexts {\n  response?: number[][];\n  shape?: number[];\n  /**\n   * The pooling method used in the embedding process.\n   */\n  pooling?: 'mean' | 'cls';\n}\ninterface BGEM3OuputEmbedding {\n  shape?: number[];\n  /**\n   * Embeddings of the requested text values\n   */\n  data?: number[][];\n  /**\n   * The pooling method used in the embedding process.\n   */\n  pooling?: 'mean' | 'cls';\n}\ndeclare abstract class Base_Ai_Cf_Baai_Bge_M3 {\n  inputs: Ai_Cf_Baai_Bge_M3_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_M3_Output;\n}\ninterface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input {\n  /**\n   * A text description of the image you want to generate.\n   */\n  prompt: string;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer.\n   */\n  steps?: number;\n}\ninterface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output {\n  /**\n   * The generated image in Base64 format.\n   */\n  image?: string;\n}\ndeclare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output;\n}\ntype Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input = Prompt | Messages;\ninterface Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  image?: number[] | (string & NonNullable<unknown>);\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n}\ninterface Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    /**\n     * The content of the message as a string.\n     */\n    content: string;\n  }[];\n  image?: number[] | string;\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * If true, the response will be streamed back incrementally.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ntype Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output =\n  | {\n      /**\n       * The generated text response from the model\n       */\n      response?: string;\n      /**\n       * An array of tool calls requests made during the response generation\n       */\n      tool_calls?: {\n        /**\n         * The arguments passed to be passed to the tool call request\n         */\n        arguments?: object;\n        /**\n         * The name of the tool to be called\n         */\n        name?: string;\n      }[];\n    }\n  | ReadableStream;\ndeclare abstract class Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct {\n  inputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output;\n}\ninterface Ai_Cf_Meta_Llama_Guard_3_8B_Input {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender must alternate between 'user' and 'assistant'.\n     */\n    role: 'user' | 'assistant';\n    /**\n     * The content of the message as a string.\n     */\n    content: string;\n  }[];\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Dictate the output format of the generated response.\n   */\n  response_format?: {\n    /**\n     * Set to json_object to process and output generated text as JSON.\n     */\n    type?: string;\n  };\n}\ninterface Ai_Cf_Meta_Llama_Guard_3_8B_Output {\n  response?:\n    | string\n    | {\n        /**\n         * Whether the conversation is safe or not.\n         */\n        safe?: boolean;\n        /**\n         * A list of what hazard categories predicted for the conversation, if the conversation is deemed unsafe.\n         */\n        categories?: string[];\n      };\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n}\ndeclare abstract class Base_Ai_Cf_Meta_Llama_Guard_3_8B {\n  inputs: Ai_Cf_Meta_Llama_Guard_3_8B_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_Guard_3_8B_Output;\n}\ninterface Ai_Cf_Baai_Bge_Reranker_Base_Input {\n  /**\n   * A query you wish to perform against the provided contexts.\n   */\n  /**\n   * Number of returned results starting with the best score.\n   */\n  top_k?: number;\n  /**\n   * List of provided contexts. Note that the index in this array is important, as the response will refer to it.\n   */\n  contexts: {\n    /**\n     * One of the provided context content\n     */\n    text?: string;\n  }[];\n}\ninterface Ai_Cf_Baai_Bge_Reranker_Base_Output {\n  response?: {\n    /**\n     * Index of the context in the request\n     */\n    id?: number;\n    /**\n     * Score of the context under the index.\n     */\n    score?: number;\n  }[];\n}\ndeclare abstract class Base_Ai_Cf_Baai_Bge_Reranker_Base {\n  inputs: Ai_Cf_Baai_Bge_Reranker_Base_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Reranker_Base_Output;\n}\ntype Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input =\n  | Ai_Cf_Meta_Llama_4_Prompt\n  | Ai_Cf_Meta_Llama_4_Messages;\ninterface Ai_Cf_Meta_Llama_4_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Meta_Llama_4_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ntype Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output =\n  | {\n      /**\n       * The generated text response from the model\n       */\n      response: string;\n      /**\n       * Usage statistics for the inference request\n       */\n      usage?: {\n        /**\n         * Total number of tokens in input\n         */\n        prompt_tokens?: number;\n        /**\n         * Total number of tokens in output\n         */\n        completion_tokens?: number;\n        /**\n         * Total number of input and output tokens\n         */\n        total_tokens?: number;\n      };\n      /**\n       * An array of tool calls requests made during the response generation\n       */\n      tool_calls?: {\n        /**\n         * The arguments passed to be passed to the tool call request\n         */\n        arguments?: object;\n        /**\n         * The name of the tool to be called\n         */\n        name?: string;\n      }[];\n    }\n  | string;\ndeclare abstract class Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct {\n  inputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output;\n}\ninterface AiModels {\n  '@cf/huggingface/distilbert-sst-2-int8': BaseAiTextClassification;\n  '@cf/stabilityai/stable-diffusion-xl-base-1.0': BaseAiTextToImage;\n  '@cf/runwayml/stable-diffusion-v1-5-inpainting': BaseAiTextToImage;\n  '@cf/runwayml/stable-diffusion-v1-5-img2img': BaseAiTextToImage;\n  '@cf/lykon/dreamshaper-8-lcm': BaseAiTextToImage;\n  '@cf/bytedance/stable-diffusion-xl-lightning': BaseAiTextToImage;\n  '@cf/myshell-ai/melotts': BaseAiTextToSpeech;\n  '@cf/baai/bge-base-en-v1.5': BaseAiTextEmbeddings;\n  '@cf/baai/bge-small-en-v1.5': BaseAiTextEmbeddings;\n  '@cf/baai/bge-large-en-v1.5': BaseAiTextEmbeddings;\n  '@cf/microsoft/resnet-50': BaseAiImageClassification;\n  '@cf/facebook/detr-resnet-50': BaseAiObjectDetection;\n  '@cf/meta/llama-2-7b-chat-int8': BaseAiTextGeneration;\n  '@cf/mistral/mistral-7b-instruct-v0.1': BaseAiTextGeneration;\n  '@cf/meta/llama-2-7b-chat-fp16': BaseAiTextGeneration;\n  '@hf/thebloke/llama-2-13b-chat-awq': BaseAiTextGeneration;\n  '@hf/thebloke/mistral-7b-instruct-v0.1-awq': BaseAiTextGeneration;\n  '@hf/thebloke/zephyr-7b-beta-awq': BaseAiTextGeneration;\n  '@hf/thebloke/openhermes-2.5-mistral-7b-awq': BaseAiTextGeneration;\n  '@hf/thebloke/neural-chat-7b-v3-1-awq': BaseAiTextGeneration;\n  '@hf/thebloke/llamaguard-7b-awq': BaseAiTextGeneration;\n  '@hf/thebloke/deepseek-coder-6.7b-base-awq': BaseAiTextGeneration;\n  '@hf/thebloke/deepseek-coder-6.7b-instruct-awq': BaseAiTextGeneration;\n  '@cf/deepseek-ai/deepseek-math-7b-instruct': BaseAiTextGeneration;\n  '@cf/defog/sqlcoder-7b-2': BaseAiTextGeneration;\n  '@cf/openchat/openchat-3.5-0106': BaseAiTextGeneration;\n  '@cf/tiiuae/falcon-7b-instruct': BaseAiTextGeneration;\n  '@cf/thebloke/discolm-german-7b-v1-awq': BaseAiTextGeneration;\n  '@cf/qwen/qwen1.5-0.5b-chat': BaseAiTextGeneration;\n  '@cf/qwen/qwen1.5-7b-chat-awq': BaseAiTextGeneration;\n  '@cf/qwen/qwen1.5-14b-chat-awq': BaseAiTextGeneration;\n  '@cf/tinyllama/tinyllama-1.1b-chat-v1.0': BaseAiTextGeneration;\n  '@cf/microsoft/phi-2': BaseAiTextGeneration;\n  '@cf/qwen/qwen1.5-1.8b-chat': BaseAiTextGeneration;\n  '@cf/mistral/mistral-7b-instruct-v0.2-lora': BaseAiTextGeneration;\n  '@hf/nousresearch/hermes-2-pro-mistral-7b': BaseAiTextGeneration;\n  '@hf/nexusflow/starling-lm-7b-beta': BaseAiTextGeneration;\n  '@hf/google/gemma-7b-it': BaseAiTextGeneration;\n  '@cf/meta-llama/llama-2-7b-chat-hf-lora': BaseAiTextGeneration;\n  '@cf/google/gemma-2b-it-lora': BaseAiTextGeneration;\n  '@cf/google/gemma-7b-it-lora': BaseAiTextGeneration;\n  '@hf/mistral/mistral-7b-instruct-v0.2': BaseAiTextGeneration;\n  '@cf/meta/llama-3-8b-instruct': BaseAiTextGeneration;\n  '@cf/fblgit/una-cybertron-7b-v2-bf16': BaseAiTextGeneration;\n  '@cf/meta/llama-3-8b-instruct-awq': BaseAiTextGeneration;\n  '@hf/meta-llama/meta-llama-3-8b-instruct': BaseAiTextGeneration;\n  '@cf/meta/llama-3.1-8b-instruct': BaseAiTextGeneration;\n  '@cf/meta/llama-3.1-8b-instruct-fp8': BaseAiTextGeneration;\n  '@cf/meta/llama-3.1-8b-instruct-awq': BaseAiTextGeneration;\n  '@cf/meta/llama-3.2-3b-instruct': BaseAiTextGeneration;\n  '@cf/meta/llama-3.2-1b-instruct': BaseAiTextGeneration;\n  '@cf/meta/llama-3.3-70b-instruct-fp8-fast': BaseAiTextGeneration;\n  '@cf/deepseek-ai/deepseek-r1-distill-qwen-32b': BaseAiTextGeneration;\n  '@cf/meta/m2m100-1.2b': BaseAiTranslation;\n  '@cf/facebook/bart-large-cnn': BaseAiSummarization;\n  '@cf/llava-hf/llava-1.5-7b-hf': BaseAiImageToText;\n  '@cf/openai/whisper': Base_Ai_Cf_Openai_Whisper;\n  '@cf/unum/uform-gen2-qwen-500m': Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M;\n  '@cf/openai/whisper-tiny-en': Base_Ai_Cf_Openai_Whisper_Tiny_En;\n  '@cf/openai/whisper-large-v3-turbo': Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo;\n  '@cf/baai/bge-m3': Base_Ai_Cf_Baai_Bge_M3;\n  '@cf/black-forest-labs/flux-1-schnell': Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell;\n  '@cf/meta/llama-3.2-11b-vision-instruct': Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct;\n  '@cf/meta/llama-guard-3-8b': Base_Ai_Cf_Meta_Llama_Guard_3_8B;\n  '@cf/baai/bge-reranker-base': Base_Ai_Cf_Baai_Bge_Reranker_Base;\n  '@cf/meta/llama-4-scout-17b-16e-instruct': Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct;\n}\ntype AiOptions = {\n  gateway?: GatewayOptions;\n  returnRawResponse?: boolean;\n  prefix?: string;\n  extraHeaders?: object;\n};\ntype ConversionResponse = {\n  name: string;\n  mimeType: string;\n  format: 'markdown';\n  tokens: number;\n  data: string;\n};\ntype AiModelsSearchParams = {\n  author?: string;\n  hide_experimental?: boolean;\n  page?: number;\n  per_page?: number;\n  search?: string;\n  source?: number;\n  task?: string;\n};\ntype AiModelsSearchObject = {\n  id: string;\n  source: number;\n  name: string;\n  description: string;\n  task: {\n    id: string;\n    name: string;\n    description: string;\n  };\n  tags: string[];\n  properties: {\n    property_id: string;\n    value: string;\n  }[];\n};\ninterface InferenceUpstreamError extends Error {}\ninterface AiInternalError extends Error {}\ntype AiModelListType = Record<string, any>;\ndeclare abstract class Ai<AiModelList extends AiModelListType = AiModels> {\n  aiGatewayLogId: string | null;\n  gateway(gatewayId: string): AiGateway;\n  autorag(autoragId: string): AutoRAG;\n  run<Name extends keyof AiModelList, Options extends AiOptions>(\n    model: Name,\n    inputs: AiModelList[Name]['inputs'],\n    options?: Options,\n  ): Promise<\n    Options extends {\n      returnRawResponse: true;\n    }\n      ? Response\n      : AiModelList[Name]['postProcessedOutputs']\n  >;\n  models(params?: AiModelsSearchParams): Promise<AiModelsSearchObject[]>;\n  toMarkdown(\n    files: {\n      name: string;\n      blob: Blob;\n    }[],\n    options?: {\n      gateway?: GatewayOptions;\n      extraHeaders?: object;\n    },\n  ): Promise<ConversionResponse[]>;\n  toMarkdown(\n    files: {\n      name: string;\n      blob: Blob;\n    },\n    options?: {\n      gateway?: GatewayOptions;\n      extraHeaders?: object;\n    },\n  ): Promise<ConversionResponse>;\n}\ntype GatewayRetries = {\n  maxAttempts?: 1 | 2 | 3 | 4 | 5;\n  retryDelayMs?: number;\n  backoff?: 'constant' | 'linear' | 'exponential';\n};\ntype GatewayOptions = {\n  id: string;\n  cacheKey?: string;\n  cacheTtl?: number;\n  skipCache?: boolean;\n  metadata?: Record<string, number | string | boolean | null | bigint>;\n  collectLog?: boolean;\n  eventId?: string;\n  requestTimeoutMs?: number;\n  retries?: GatewayRetries;\n};\ntype AiGatewayPatchLog = {\n  score?: number | null;\n  feedback?: -1 | 1 | null;\n  metadata?: Record<string, number | string | boolean | null | bigint> | null;\n};\ntype AiGatewayLog = {\n  id: string;\n  provider: string;\n  model: string;\n  model_type?: string;\n  path: string;\n  duration: number;\n  request_type?: string;\n  request_content_type?: string;\n  status_code: number;\n  response_content_type?: string;\n  success: boolean;\n  cached: boolean;\n  tokens_in?: number;\n  tokens_out?: number;\n  metadata?: Record<string, number | string | boolean | null | bigint>;\n  step?: number;\n  cost?: number;\n  custom_cost?: boolean;\n  request_size: number;\n  request_head?: string;\n  request_head_complete: boolean;\n  response_size: number;\n  response_head?: string;\n  response_head_complete: boolean;\n  created_at: Date;\n};\ntype AIGatewayProviders =\n  | 'workers-ai'\n  | 'anthropic'\n  | 'aws-bedrock'\n  | 'azure-openai'\n  | 'google-vertex-ai'\n  | 'huggingface'\n  | 'openai'\n  | 'perplexity-ai'\n  | 'replicate'\n  | 'groq'\n  | 'cohere'\n  | 'google-ai-studio'\n  | 'mistral'\n  | 'grok'\n  | 'openrouter'\n  | 'deepseek'\n  | 'cerebras'\n  | 'cartesia'\n  | 'elevenlabs'\n  | 'adobe-firefly';\ntype AIGatewayHeaders = {\n  'cf-aig-metadata': Record<string, number | string | boolean | null | bigint> | string;\n  'cf-aig-custom-cost':\n    | {\n        per_token_in?: number;\n        per_token_out?: number;\n      }\n    | {\n        total_cost?: number;\n      }\n    | string;\n  'cf-aig-cache-ttl': number | string;\n  'cf-aig-skip-cache': boolean | string;\n  'cf-aig-cache-key': string;\n  'cf-aig-event-id': string;\n  'cf-aig-request-timeout': number | string;\n  'cf-aig-max-attempts': number | string;\n  'cf-aig-retry-delay': number | string;\n  'cf-aig-backoff': string;\n  'cf-aig-collect-log': boolean | string;\n  Authorization: string;\n  'Content-Type': string;\n  [key: string]: string | number | boolean | object;\n};\ntype AIGatewayUniversalRequest = {\n  provider: AIGatewayProviders | string; // eslint-disable-line\n  endpoint: string;\n  headers: Partial<AIGatewayHeaders>;\n  query: unknown;\n};\ninterface AiGatewayInternalError extends Error {}\ninterface AiGatewayLogNotFound extends Error {}\ndeclare abstract class AiGateway {\n  patchLog(logId: string, data: AiGatewayPatchLog): Promise<void>;\n  getLog(logId: string): Promise<AiGatewayLog>;\n  run(\n    data: AIGatewayUniversalRequest | AIGatewayUniversalRequest[],\n    options?: {\n      gateway?: GatewayOptions;\n      extraHeaders?: object;\n    },\n  ): Promise<Response>;\n  getUrl(provider?: AIGatewayProviders | string): Promise<string>; // eslint-disable-line\n}\ninterface AutoRAGInternalError extends Error {}\ninterface AutoRAGNotFoundError extends Error {}\ninterface AutoRAGUnauthorizedError extends Error {}\ntype ComparisonFilter = {\n  key: string;\n  type: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte';\n  value: string | number | boolean;\n};\ntype CompoundFilter = {\n  type: 'and' | 'or';\n  filters: ComparisonFilter[];\n};\ntype AutoRagSearchRequest = {\n  query: string;\n  filters?: CompoundFilter | ComparisonFilter;\n  max_num_results?: number;\n  ranking_options?: {\n    ranker?: string;\n    score_threshold?: number;\n  };\n  rewrite_query?: boolean;\n};\ntype AutoRagAiSearchRequest = AutoRagSearchRequest & {\n  stream?: boolean;\n};\ntype AutoRagAiSearchRequestStreaming = Omit<AutoRagAiSearchRequest, 'stream'> & {\n  stream: true;\n};\ntype AutoRagSearchResponse = {\n  object: 'vector_store.search_results.page';\n  search_query: string;\n  data: {\n    file_id: string;\n    filename: string;\n    score: number;\n    attributes: Record<string, string | number | boolean | null>;\n    content: {\n      type: 'text';\n      text: string;\n    }[];\n  }[];\n  has_more: boolean;\n  next_page: string | null;\n};\ntype AutoRagAiSearchResponse = AutoRagSearchResponse & {\n  response: string;\n};\ndeclare abstract class AutoRAG {\n  search(params: AutoRagSearchRequest): Promise<AutoRagSearchResponse>;\n  aiSearch(params: AutoRagAiSearchRequestStreaming): Promise<Response>;\n  aiSearch(params: AutoRagAiSearchRequest): Promise<AutoRagAiSearchResponse>;\n  aiSearch(params: AutoRagAiSearchRequest): Promise<AutoRagAiSearchResponse | Response>;\n}\ninterface BasicImageTransformations {\n  /**\n   * Maximum width in image pixels. The value must be an integer.\n   */\n  width?: number;\n  /**\n   * Maximum height in image pixels. The value must be an integer.\n   */\n  height?: number;\n  /**\n   * Resizing mode as a string. It affects interpretation of width and height\n   * options:\n   *  - scale-down: Similar to contain, but the image is never enlarged. If\n   *    the image is larger than given width or height, it will be resized.\n   *    Otherwise its original size will be kept.\n   *  - contain: Resizes to maximum size that fits within the given width and\n   *    height. If only a single dimension is given (e.g. only width), the\n   *    image will be shrunk or enlarged to exactly match that dimension.\n   *    Aspect ratio is always preserved.\n   *  - cover: Resizes (shrinks or enlarges) to fill the entire area of width\n   *    and height. If the image has an aspect ratio different from the ratio\n   *    of width and height, it will be cropped to fit.\n   *  - crop: The image will be shrunk and cropped to fit within the area\n   *    specified by width and height. The image will not be enlarged. For images\n   *    smaller than the given dimensions it's the same as scale-down. For\n   *    images larger than the given dimensions, it's the same as cover.\n   *    See also trim.\n   *  - pad: Resizes to the maximum size that fits within the given width and\n   *    height, and then fills the remaining area with a background color\n   *    (white by default). Use of this mode is not recommended, as the same\n   *    effect can be more efficiently achieved with the contain mode and the\n   *    CSS object-fit: contain property.\n   *  - squeeze: Stretches and deforms to the width and height given, even if it\n   *    breaks aspect ratio\n   */\n  fit?: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad' | 'squeeze';\n  /**\n   * When cropping with fit: \"cover\", this defines the side or point that should\n   * be left uncropped. The value is either a string\n   * \"left\", \"right\", \"top\", \"bottom\", \"auto\", or \"center\" (the default),\n   * or an object {x, y} containing focal point coordinates in the original\n   * image expressed as fractions ranging from 0.0 (top or left) to 1.0\n   * (bottom or right), 0.5 being the center. {fit: \"cover\", gravity: \"top\"} will\n   * crop bottom or left and right sides as necessary, but won’t crop anything\n   * from the top. {fit: \"cover\", gravity: {x:0.5, y:0.2}} will crop each side to\n   * preserve as much as possible around a point at 20% of the height of the\n   * source image.\n   */\n  gravity?:\n    | 'left'\n    | 'right'\n    | 'top'\n    | 'bottom'\n    | 'center'\n    | 'auto'\n    | 'entropy'\n    | BasicImageTransformationsGravityCoordinates;\n  /**\n   * Background color to add underneath the image. Applies only to images with\n   * transparency (such as PNG). Accepts any CSS color (#RRGGBB, rgba(…),\n   * hsl(…), etc.)\n   */\n  background?: string;\n  /**\n   * Number of degrees (90, 180, 270) to rotate the image by. width and height\n   * options refer to axes after rotation.\n   */\n  rotate?: 0 | 90 | 180 | 270 | 360;\n}\ninterface BasicImageTransformationsGravityCoordinates {\n  x?: number;\n  y?: number;\n  mode?: 'remainder' | 'box-center';\n}\n/**\n * In addition to the properties you can set in the RequestInit dict\n * that you pass as an argument to the Request constructor, you can\n * set certain properties of a `cf` object to control how Cloudflare\n * features are applied to that new Request.\n *\n * Note: Currently, these properties cannot be tested in the\n * playground.\n */\ninterface RequestInitCfProperties extends Record<string, unknown> {\n  cacheEverything?: boolean;\n  /**\n   * A request's cache key is what determines if two requests are\n   * \"the same\" for caching purposes. If a request has the same cache key\n   * as some previous request, then we can serve the same cached response for\n   * both. (e.g. 'some-key')\n   *\n   * Only available for Enterprise customers.\n   */\n  cacheKey?: string;\n  /**\n   * This allows you to append additional Cache-Tag response headers\n   * to the origin response without modifications to the origin server.\n   * This will allow for greater control over the Purge by Cache Tag feature\n   * utilizing changes only in the Workers process.\n   *\n   * Only available for Enterprise customers.\n   */\n  cacheTags?: string[];\n  /**\n   * Force response to be cached for a given number of seconds. (e.g. 300)\n   */\n  cacheTtl?: number;\n  /**\n   * Force response to be cached for a given number of seconds based on the Origin status code.\n   * (e.g. { '200-299': 86400, '404': 1, '500-599': 0 })\n   */\n  cacheTtlByStatus?: Record<string, number>;\n  scrapeShield?: boolean;\n  apps?: boolean;\n  image?: RequestInitCfPropertiesImage;\n  minify?: RequestInitCfPropertiesImageMinify;\n  mirage?: boolean;\n  polish?: 'lossy' | 'lossless' | 'off';\n  r2?: RequestInitCfPropertiesR2;\n  /**\n   * Redirects the request to an alternate origin server. You can use this,\n   * for example, to implement load balancing across several origins.\n   * (e.g.us-east.example.com)\n   *\n   * Note - For security reasons, the hostname set in resolveOverride must\n   * be proxied on the same Cloudflare zone of the incoming request.\n   * Otherwise, the setting is ignored. CNAME hosts are allowed, so to\n   * resolve to a host under a different domain or a DNS only domain first\n   * declare a CNAME record within your own zone’s DNS mapping to the\n   * external hostname, set proxy on Cloudflare, then set resolveOverride\n   * to point to that CNAME record.\n   */\n  resolveOverride?: string;\n}\ninterface RequestInitCfPropertiesImageDraw extends BasicImageTransformations {\n  /**\n   * Absolute URL of the image file to use for the drawing. It can be any of\n   * the supported file formats. For drawing of watermarks or non-rectangular\n   * overlays we recommend using PNG or WebP images.\n   */\n  url: string;\n  /**\n   * Floating-point number between 0 (transparent) and 1 (opaque).\n   * For example, opacity: 0.5 makes overlay semitransparent.\n   */\n  opacity?: number;\n  /**\n   * - If set to true, the overlay image will be tiled to cover the entire\n   *   area. This is useful for stock-photo-like watermarks.\n   * - If set to \"x\", the overlay image will be tiled horizontally only\n   *   (form a line).\n   * - If set to \"y\", the overlay image will be tiled vertically only\n   *   (form a line).\n   */\n  repeat?: true | 'x' | 'y';\n  /**\n   * Position of the overlay image relative to a given edge. Each property is\n   * an offset in pixels. 0 aligns exactly to the edge. For example, left: 10\n   * positions left side of the overlay 10 pixels from the left edge of the\n   * image it's drawn over. bottom: 0 aligns bottom of the overlay with bottom\n   * of the background image.\n   *\n   * Setting both left & right, or both top & bottom is an error.\n   *\n   * If no position is specified, the image will be centered.\n   */\n  top?: number;\n  left?: number;\n  bottom?: number;\n  right?: number;\n}\ninterface RequestInitCfPropertiesImage extends BasicImageTransformations {\n  /**\n   * Device Pixel Ratio. Default 1. Multiplier for width/height that makes it\n   * easier to specify higher-DPI sizes in <img srcset>.\n   */\n  dpr?: number;\n  /**\n   * Allows you to trim your image. Takes dpr into account and is performed before\n   * resizing or rotation.\n   *\n   * It can be used as:\n   * - left, top, right, bottom - it will specify the number of pixels to cut\n   *   off each side\n   * - width, height - the width/height you'd like to end up with - can be used\n   *   in combination with the properties above\n   * - border - this will automatically trim the surroundings of an image based on\n   *   it's color. It consists of three properties:\n   *    - color: rgb or hex representation of the color you wish to trim (todo: verify the rgba bit)\n   *    - tolerance: difference from color to treat as color\n   *    - keep: the number of pixels of border to keep\n   */\n  trim?:\n    | 'border'\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n        width?: number;\n        height?: number;\n        border?:\n          | boolean\n          | {\n              color?: string;\n              tolerance?: number;\n              keep?: number;\n            };\n      };\n  /**\n   * Quality setting from 1-100 (useful values are in 60-90 range). Lower values\n   * make images look worse, but load faster. The default is 85. It applies only\n   * to JPEG and WebP images. It doesn’t have any effect on PNG.\n   */\n  quality?: number | 'low' | 'medium-low' | 'medium-high' | 'high';\n  /**\n   * Output format to generate. It can be:\n   *  - avif: generate images in AVIF format.\n   *  - webp: generate images in Google WebP format. Set quality to 100 to get\n   *    the WebP-lossless format.\n   *  - json: instead of generating an image, outputs information about the\n   *    image, in JSON format. The JSON object will contain image size\n   *    (before and after resizing), source image’s MIME type, file size, etc.\n   * - jpeg: generate images in JPEG format.\n   * - png: generate images in PNG format.\n   */\n  format?: 'avif' | 'webp' | 'json' | 'jpeg' | 'png' | 'baseline-jpeg' | 'png-force' | 'svg';\n  /**\n   * Whether to preserve animation frames from input files. Default is true.\n   * Setting it to false reduces animations to still images. This setting is\n   * recommended when enlarging images or processing arbitrary user content,\n   * because large GIF animations can weigh tens or even hundreds of megabytes.\n   * It is also useful to set anim:false when using format:\"json\" to get the\n   * response quicker without the number of frames.\n   */\n  anim?: boolean;\n  /**\n   * What EXIF data should be preserved in the output image. Note that EXIF\n   * rotation and embedded color profiles are always applied (\"baked in\" into\n   * the image), and aren't affected by this option. Note that if the Polish\n   * feature is enabled, all metadata may have been removed already and this\n   * option may have no effect.\n   *  - keep: Preserve most of EXIF metadata, including GPS location if there's\n   *    any.\n   *  - copyright: Only keep the copyright tag, and discard everything else.\n   *    This is the default behavior for JPEG files.\n   *  - none: Discard all invisible EXIF metadata. Currently WebP and PNG\n   *    output formats always discard metadata.\n   */\n  metadata?: 'keep' | 'copyright' | 'none';\n  /**\n   * Strength of sharpening filter to apply to the image. Floating-point\n   * number between 0 (no sharpening, default) and 10 (maximum). 1.0 is a\n   * recommended value for downscaled images.\n   */\n  sharpen?: number;\n  /**\n   * Radius of a blur filter (approximate gaussian). Maximum supported radius\n   * is 250.\n   */\n  blur?: number;\n  /**\n   * Overlays are drawn in the order they appear in the array (last array\n   * entry is the topmost layer).\n   */\n  draw?: RequestInitCfPropertiesImageDraw[];\n  /**\n   * Fetching image from authenticated origin. Setting this property will\n   * pass authentication headers (Authorization, Cookie, etc.) through to\n   * the origin.\n   */\n  'origin-auth'?: 'share-publicly';\n  /**\n   * Adds a border around the image. The border is added after resizing. Border\n   * width takes dpr into account, and can be specified either using a single\n   * width property, or individually for each side.\n   */\n  border?:\n    | {\n        color: string;\n        width: number;\n      }\n    | {\n        color: string;\n        top: number;\n        right: number;\n        bottom: number;\n        left: number;\n      };\n  /**\n   * Increase brightness by a factor. A value of 1.0 equals no change, a value\n   * of 0.5 equals half brightness, and a value of 2.0 equals twice as bright.\n   * 0 is ignored.\n   */\n  brightness?: number;\n  /**\n   * Increase contrast by a factor. A value of 1.0 equals no change, a value of\n   * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is\n   * ignored.\n   */\n  contrast?: number;\n  /**\n   * Increase exposure by a factor. A value of 1.0 equals no change, a value of\n   * 0.5 darkens the image, and a value of 2.0 lightens the image. 0 is ignored.\n   */\n  gamma?: number;\n  /**\n   * Increase contrast by a factor. A value of 1.0 equals no change, a value of\n   * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is\n   * ignored.\n   */\n  saturation?: number;\n  /**\n   * Flips the images horizontally, vertically, or both. Flipping is applied before\n   * rotation, so if you apply flip=h,rotate=90 then the image will be flipped\n   * horizontally, then rotated by 90 degrees.\n   */\n  flip?: 'h' | 'v' | 'hv';\n  /**\n   * Slightly reduces latency on a cache miss by selecting a\n   * quickest-to-compress file format, at a cost of increased file size and\n   * lower image quality. It will usually override the format option and choose\n   * JPEG over WebP or AVIF. We do not recommend using this option, except in\n   * unusual circumstances like resizing uncacheable dynamically-generated\n   * images.\n   */\n  compression?: 'fast';\n}\ninterface RequestInitCfPropertiesImageMinify {\n  javascript?: boolean;\n  css?: boolean;\n  html?: boolean;\n}\ninterface RequestInitCfPropertiesR2 {\n  /**\n   * Colo id of bucket that an object is stored in\n   */\n  bucketColoId?: number;\n}\n/**\n * Request metadata provided by Cloudflare's edge.\n */\ntype IncomingRequestCfProperties<HostMetadata = unknown> = IncomingRequestCfPropertiesBase &\n  IncomingRequestCfPropertiesBotManagementEnterprise &\n  IncomingRequestCfPropertiesCloudflareForSaaSEnterprise<HostMetadata> &\n  IncomingRequestCfPropertiesGeographicInformation &\n  IncomingRequestCfPropertiesCloudflareAccessOrApiShield;\ninterface IncomingRequestCfPropertiesBase extends Record<string, unknown> {\n  /**\n   * [ASN](https://www.iana.org/assignments/as-numbers/as-numbers.xhtml) of the incoming request.\n   *\n   * @example 395747\n   */\n  asn: number;\n  /**\n   * The organization which owns the ASN of the incoming request.\n   *\n   * @example \"Google Cloud\"\n   */\n  asOrganization: string;\n  /**\n   * The original value of the `Accept-Encoding` header if Cloudflare modified it.\n   *\n   * @example \"gzip, deflate, br\"\n   */\n  clientAcceptEncoding?: string;\n  /**\n   * The number of milliseconds it took for the request to reach your worker.\n   *\n   * @example 22\n   */\n  clientTcpRtt?: number;\n  /**\n   * The three-letter [IATA](https://en.wikipedia.org/wiki/IATA_airport_code)\n   * airport code of the data center that the request hit.\n   *\n   * @example \"DFW\"\n   */\n  colo: string;\n  /**\n   * Represents the upstream's response to a\n   * [TCP `keepalive` message](https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html)\n   * from cloudflare.\n   *\n   * For workers with no upstream, this will always be `1`.\n   *\n   * @example 3\n   */\n  edgeRequestKeepAliveStatus: IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus;\n  /**\n   * The HTTP Protocol the request used.\n   *\n   * @example \"HTTP/2\"\n   */\n  httpProtocol: string;\n  /**\n   * The browser-requested prioritization information in the request object.\n   *\n   * If no information was set, defaults to the empty string `\"\"`\n   *\n   * @example \"weight=192;exclusive=0;group=3;group-weight=127\"\n   * @default \"\"\n   */\n  requestPriority: string;\n  /**\n   * The TLS version of the connection to Cloudflare.\n   * In requests served over plaintext (without TLS), this property is the empty string `\"\"`.\n   *\n   * @example \"TLSv1.3\"\n   */\n  tlsVersion: string;\n  /**\n   * The cipher for the connection to Cloudflare.\n   * In requests served over plaintext (without TLS), this property is the empty string `\"\"`.\n   *\n   * @example \"AEAD-AES128-GCM-SHA256\"\n   */\n  tlsCipher: string;\n  /**\n   * Metadata containing the [`HELLO`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2) and [`FINISHED`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9) messages from this request's TLS handshake.\n   *\n   * If the incoming request was served over plaintext (without TLS) this field is undefined.\n   */\n  tlsExportedAuthenticator?: IncomingRequestCfPropertiesExportedAuthenticatorMetadata;\n}\ninterface IncomingRequestCfPropertiesBotManagementBase {\n  /**\n   * Cloudflare’s [level of certainty](https://developers.cloudflare.com/bots/concepts/bot-score/) that a request comes from a bot,\n   * represented as an integer percentage between `1` (almost certainly a bot) and `99` (almost certainly human).\n   *\n   * @example 54\n   */\n  score: number;\n  /**\n   * A boolean value that is true if the request comes from a good bot, like Google or Bing.\n   * Most customers choose to allow this traffic. For more details, see [Traffic from known bots](https://developers.cloudflare.com/firewall/known-issues-and-faq/#how-does-firewall-rules-handle-traffic-from-known-bots).\n   */\n  verifiedBot: boolean;\n  /**\n   * A boolean value that is true if the request originates from a\n   * Cloudflare-verified proxy service.\n   */\n  corporateProxy: boolean;\n  /**\n   * A boolean value that's true if the request matches [file extensions](https://developers.cloudflare.com/bots/reference/static-resources/) for many types of static resources.\n   */\n  staticResource: boolean;\n  /**\n   * List of IDs that correlate to the Bot Management heuristic detections made on a request (you can have multiple heuristic detections on the same request).\n   */\n  detectionIds: number[];\n}\ninterface IncomingRequestCfPropertiesBotManagement {\n  /**\n   * Results of Cloudflare's Bot Management analysis\n   */\n  botManagement: IncomingRequestCfPropertiesBotManagementBase;\n  /**\n   * Duplicate of `botManagement.score`.\n   *\n   * @deprecated\n   */\n  clientTrustScore: number;\n}\ninterface IncomingRequestCfPropertiesBotManagementEnterprise extends IncomingRequestCfPropertiesBotManagement {\n  /**\n   * Results of Cloudflare's Bot Management analysis\n   */\n  botManagement: IncomingRequestCfPropertiesBotManagementBase & {\n    /**\n     * A [JA3 Fingerprint](https://developers.cloudflare.com/bots/concepts/ja3-fingerprint/) to help profile specific SSL/TLS clients\n     * across different destination IPs, Ports, and X509 certificates.\n     */\n    ja3Hash: string;\n  };\n}\ninterface IncomingRequestCfPropertiesCloudflareForSaaSEnterprise<HostMetadata> {\n  /**\n   * Custom metadata set per-host in [Cloudflare for SaaS](https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/).\n   *\n   * This field is only present if you have Cloudflare for SaaS enabled on your account\n   * and you have followed the [required steps to enable it]((https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/domain-support/custom-metadata/)).\n   */\n  hostMetadata: HostMetadata;\n}\ninterface IncomingRequestCfPropertiesCloudflareAccessOrApiShield {\n  /**\n   * Information about the client certificate presented to Cloudflare.\n   *\n   * This is populated when the incoming request is served over TLS using\n   * either Cloudflare Access or API Shield (mTLS)\n   * and the presented SSL certificate has a valid\n   * [Certificate Serial Number](https://ldapwiki.com/wiki/Certificate%20Serial%20Number)\n   * (i.e., not `null` or `\"\"`).\n   *\n   * Otherwise, a set of placeholder values are used.\n   *\n   * The property `certPresented` will be set to `\"1\"` when\n   * the object is populated (i.e. the above conditions were met).\n   */\n  tlsClientAuth:\n    | IncomingRequestCfPropertiesTLSClientAuth\n    | IncomingRequestCfPropertiesTLSClientAuthPlaceholder;\n}\n/**\n * Metadata about the request's TLS handshake\n */\ninterface IncomingRequestCfPropertiesExportedAuthenticatorMetadata {\n  /**\n   * The client's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal\n   *\n   * @example \"44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d\"\n   */\n  clientHandshake: string;\n  /**\n   * The server's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal\n   *\n   * @example \"44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d\"\n   */\n  serverHandshake: string;\n  /**\n   * The client's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal\n   *\n   * @example \"084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b\"\n   */\n  clientFinished: string;\n  /**\n   * The server's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal\n   *\n   * @example \"084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b\"\n   */\n  serverFinished: string;\n}\n/**\n * Geographic data about the request's origin.\n */\ninterface IncomingRequestCfPropertiesGeographicInformation {\n  /**\n   * The [ISO 3166-1 Alpha 2](https://www.iso.org/iso-3166-country-codes.html) country code the request originated from.\n   *\n   * If your worker is [configured to accept TOR connections](https://support.cloudflare.com/hc/en-us/articles/203306930-Understanding-Cloudflare-Tor-support-and-Onion-Routing), this may also be `\"T1\"`, indicating a request that originated over TOR.\n   *\n   * If Cloudflare is unable to determine where the request originated this property is omitted.\n   *\n   * The country code `\"T1\"` is used for requests originating on TOR.\n   *\n   * @example \"GB\"\n   */\n  country?: Iso3166Alpha2Code | 'T1';\n  /**\n   * If present, this property indicates that the request originated in the EU\n   *\n   * @example \"1\"\n   */\n  isEUCountry?: '1';\n  /**\n   * A two-letter code indicating the continent the request originated from.\n   *\n   * @example \"AN\"\n   */\n  continent?: ContinentCode;\n  /**\n   * The city the request originated from\n   *\n   * @example \"Austin\"\n   */\n  city?: string;\n  /**\n   * Postal code of the incoming request\n   *\n   * @example \"78701\"\n   */\n  postalCode?: string;\n  /**\n   * Latitude of the incoming request\n   *\n   * @example \"30.27130\"\n   */\n  latitude?: string;\n  /**\n   * Longitude of the incoming request\n   *\n   * @example \"-97.74260\"\n   */\n  longitude?: string;\n  /**\n   * Timezone of the incoming request\n   *\n   * @example \"America/Chicago\"\n   */\n  timezone?: string;\n  /**\n   * If known, the ISO 3166-2 name for the first level region associated with\n   * the IP address of the incoming request\n   *\n   * @example \"Texas\"\n   */\n  region?: string;\n  /**\n   * If known, the ISO 3166-2 code for the first-level region associated with\n   * the IP address of the incoming request\n   *\n   * @example \"TX\"\n   */\n  regionCode?: string;\n  /**\n   * Metro code (DMA) of the incoming request\n   *\n   * @example \"635\"\n   */\n  metroCode?: string;\n}\n/** Data about the incoming request's TLS certificate */\ninterface IncomingRequestCfPropertiesTLSClientAuth {\n  /** Always `\"1\"`, indicating that the certificate was presented */\n  certPresented: '1';\n  /**\n   * Result of certificate verification.\n   *\n   * @example \"FAILED:self signed certificate\"\n   */\n  certVerified: Exclude<CertVerificationStatus, 'NONE'>;\n  /** The presented certificate's revokation status.\n   *\n   * - A value of `\"1\"` indicates the certificate has been revoked\n   * - A value of `\"0\"` indicates the certificate has not been revoked\n   */\n  certRevoked: '1' | '0';\n  /**\n   * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html)\n   *\n   * @example \"CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certIssuerDN: string;\n  /**\n   * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html)\n   *\n   * @example \"CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certSubjectDN: string;\n  /**\n   * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted)\n   *\n   * @example \"CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certIssuerDNRFC2253: string;\n  /**\n   * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted)\n   *\n   * @example \"CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certSubjectDNRFC2253: string;\n  /** The certificate issuer's distinguished name (legacy policies) */\n  certIssuerDNLegacy: string;\n  /** The certificate subject's distinguished name (legacy policies) */\n  certSubjectDNLegacy: string;\n  /**\n   * The certificate's serial number\n   *\n   * @example \"00936EACBE07F201DF\"\n   */\n  certSerial: string;\n  /**\n   * The certificate issuer's serial number\n   *\n   * @example \"2489002934BDFEA34\"\n   */\n  certIssuerSerial: string;\n  /**\n   * The certificate's Subject Key Identifier\n   *\n   * @example \"BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4\"\n   */\n  certSKI: string;\n  /**\n   * The certificate issuer's Subject Key Identifier\n   *\n   * @example \"BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4\"\n   */\n  certIssuerSKI: string;\n  /**\n   * The certificate's SHA-1 fingerprint\n   *\n   * @example \"6b9109f323999e52259cda7373ff0b4d26bd232e\"\n   */\n  certFingerprintSHA1: string;\n  /**\n   * The certificate's SHA-256 fingerprint\n   *\n   * @example \"acf77cf37b4156a2708e34c4eb755f9b5dbbe5ebb55adfec8f11493438d19e6ad3f157f81fa3b98278453d5652b0c1fd1d71e5695ae4d709803a4d3f39de9dea\"\n   */\n  certFingerprintSHA256: string;\n  /**\n   * The effective starting date of the certificate\n   *\n   * @example \"Dec 22 19:39:00 2018 GMT\"\n   */\n  certNotBefore: string;\n  /**\n   * The effective expiration date of the certificate\n   *\n   * @example \"Dec 22 19:39:00 2018 GMT\"\n   */\n  certNotAfter: string;\n}\n/** Placeholder values for TLS Client Authorization */\ninterface IncomingRequestCfPropertiesTLSClientAuthPlaceholder {\n  certPresented: '0';\n  certVerified: 'NONE';\n  certRevoked: '0';\n  certIssuerDN: '';\n  certSubjectDN: '';\n  certIssuerDNRFC2253: '';\n  certSubjectDNRFC2253: '';\n  certIssuerDNLegacy: '';\n  certSubjectDNLegacy: '';\n  certSerial: '';\n  certIssuerSerial: '';\n  certSKI: '';\n  certIssuerSKI: '';\n  certFingerprintSHA1: '';\n  certFingerprintSHA256: '';\n  certNotBefore: '';\n  certNotAfter: '';\n}\n/** Possible outcomes of TLS verification */\ndeclare type CertVerificationStatus =\n  /** Authentication succeeded */\n  | 'SUCCESS'\n  /** No certificate was presented */\n  | 'NONE'\n  /** Failed because the certificate was self-signed */\n  | 'FAILED:self signed certificate'\n  /** Failed because the certificate failed a trust chain check */\n  | 'FAILED:unable to verify the first certificate'\n  /** Failed because the certificate not yet valid */\n  | 'FAILED:certificate is not yet valid'\n  /** Failed because the certificate is expired */\n  | 'FAILED:certificate has expired'\n  /** Failed for another unspecified reason */\n  | 'FAILED';\n/**\n * An upstream endpoint's response to a TCP `keepalive` message from Cloudflare.\n */\ndeclare type IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus =\n  | 0 /** Unknown */\n  | 1 /** no keepalives (not found) */\n  | 2 /** no connection re-use, opening keepalive connection failed */\n  | 3 /** no connection re-use, keepalive accepted and saved */\n  | 4 /** connection re-use, refused by the origin server (`TCP FIN`) */\n  | 5; /** connection re-use, accepted by the origin server */\n/** ISO 3166-1 Alpha-2 codes */\ndeclare type Iso3166Alpha2Code =\n  | 'AD'\n  | 'AE'\n  | 'AF'\n  | 'AG'\n  | 'AI'\n  | 'AL'\n  | 'AM'\n  | 'AO'\n  | 'AQ'\n  | 'AR'\n  | 'AS'\n  | 'AT'\n  | 'AU'\n  | 'AW'\n  | 'AX'\n  | 'AZ'\n  | 'BA'\n  | 'BB'\n  | 'BD'\n  | 'BE'\n  | 'BF'\n  | 'BG'\n  | 'BH'\n  | 'BI'\n  | 'BJ'\n  | 'BL'\n  | 'BM'\n  | 'BN'\n  | 'BO'\n  | 'BQ'\n  | 'BR'\n  | 'BS'\n  | 'BT'\n  | 'BV'\n  | 'BW'\n  | 'BY'\n  | 'BZ'\n  | 'CA'\n  | 'CC'\n  | 'CD'\n  | 'CF'\n  | 'CG'\n  | 'CH'\n  | 'CI'\n  | 'CK'\n  | 'CL'\n  | 'CM'\n  | 'CN'\n  | 'CO'\n  | 'CR'\n  | 'CU'\n  | 'CV'\n  | 'CW'\n  | 'CX'\n  | 'CY'\n  | 'CZ'\n  | 'DE'\n  | 'DJ'\n  | 'DK'\n  | 'DM'\n  | 'DO'\n  | 'DZ'\n  | 'EC'\n  | 'EE'\n  | 'EG'\n  | 'EH'\n  | 'ER'\n  | 'ES'\n  | 'ET'\n  | 'FI'\n  | 'FJ'\n  | 'FK'\n  | 'FM'\n  | 'FO'\n  | 'FR'\n  | 'GA'\n  | 'GB'\n  | 'GD'\n  | 'GE'\n  | 'GF'\n  | 'GG'\n  | 'GH'\n  | 'GI'\n  | 'GL'\n  | 'GM'\n  | 'GN'\n  | 'GP'\n  | 'GQ'\n  | 'GR'\n  | 'GS'\n  | 'GT'\n  | 'GU'\n  | 'GW'\n  | 'GY'\n  | 'HK'\n  | 'HM'\n  | 'HN'\n  | 'HR'\n  | 'HT'\n  | 'HU'\n  | 'ID'\n  | 'IE'\n  | 'IL'\n  | 'IM'\n  | 'IN'\n  | 'IO'\n  | 'IQ'\n  | 'IR'\n  | 'IS'\n  | 'IT'\n  | 'JE'\n  | 'JM'\n  | 'JO'\n  | 'JP'\n  | 'KE'\n  | 'KG'\n  | 'KH'\n  | 'KI'\n  | 'KM'\n  | 'KN'\n  | 'KP'\n  | 'KR'\n  | 'KW'\n  | 'KY'\n  | 'KZ'\n  | 'LA'\n  | 'LB'\n  | 'LC'\n  | 'LI'\n  | 'LK'\n  | 'LR'\n  | 'LS'\n  | 'LT'\n  | 'LU'\n  | 'LV'\n  | 'LY'\n  | 'MA'\n  | 'MC'\n  | 'MD'\n  | 'ME'\n  | 'MF'\n  | 'MG'\n  | 'MH'\n  | 'MK'\n  | 'ML'\n  | 'MM'\n  | 'MN'\n  | 'MO'\n  | 'MP'\n  | 'MQ'\n  | 'MR'\n  | 'MS'\n  | 'MT'\n  | 'MU'\n  | 'MV'\n  | 'MW'\n  | 'MX'\n  | 'MY'\n  | 'MZ'\n  | 'NA'\n  | 'NC'\n  | 'NE'\n  | 'NF'\n  | 'NG'\n  | 'NI'\n  | 'NL'\n  | 'NO'\n  | 'NP'\n  | 'NR'\n  | 'NU'\n  | 'NZ'\n  | 'OM'\n  | 'PA'\n  | 'PE'\n  | 'PF'\n  | 'PG'\n  | 'PH'\n  | 'PK'\n  | 'PL'\n  | 'PM'\n  | 'PN'\n  | 'PR'\n  | 'PS'\n  | 'PT'\n  | 'PW'\n  | 'PY'\n  | 'QA'\n  | 'RE'\n  | 'RO'\n  | 'RS'\n  | 'RU'\n  | 'RW'\n  | 'SA'\n  | 'SB'\n  | 'SC'\n  | 'SD'\n  | 'SE'\n  | 'SG'\n  | 'SH'\n  | 'SI'\n  | 'SJ'\n  | 'SK'\n  | 'SL'\n  | 'SM'\n  | 'SN'\n  | 'SO'\n  | 'SR'\n  | 'SS'\n  | 'ST'\n  | 'SV'\n  | 'SX'\n  | 'SY'\n  | 'SZ'\n  | 'TC'\n  | 'TD'\n  | 'TF'\n  | 'TG'\n  | 'TH'\n  | 'TJ'\n  | 'TK'\n  | 'TL'\n  | 'TM'\n  | 'TN'\n  | 'TO'\n  | 'TR'\n  | 'TT'\n  | 'TV'\n  | 'TW'\n  | 'TZ'\n  | 'UA'\n  | 'UG'\n  | 'UM'\n  | 'US'\n  | 'UY'\n  | 'UZ'\n  | 'VA'\n  | 'VC'\n  | 'VE'\n  | 'VG'\n  | 'VI'\n  | 'VN'\n  | 'VU'\n  | 'WF'\n  | 'WS'\n  | 'YE'\n  | 'YT'\n  | 'ZA'\n  | 'ZM'\n  | 'ZW';\n/** The 2-letter continent codes Cloudflare uses */\ndeclare type ContinentCode = 'AF' | 'AN' | 'AS' | 'EU' | 'NA' | 'OC' | 'SA';\ntype CfProperties<HostMetadata = unknown> =\n  | IncomingRequestCfProperties<HostMetadata>\n  | RequestInitCfProperties;\ninterface D1Meta {\n  duration: number;\n  size_after: number;\n  rows_read: number;\n  rows_written: number;\n  last_row_id: number;\n  changed_db: boolean;\n  changes: number;\n  /**\n   * The region of the database instance that executed the query.\n   */\n  served_by_region?: string;\n  /**\n   * True if-and-only-if the database instance that executed the query was the primary.\n   */\n  served_by_primary?: boolean;\n  timings?: {\n    /**\n     * The duration of the SQL query execution by the database instance. It doesn't include any network time.\n     */\n    sql_duration_ms: number;\n  };\n}\ninterface D1Response {\n  success: true;\n  meta: D1Meta & Record<string, unknown>;\n  error?: never;\n}\ntype D1Result<T = unknown> = D1Response & {\n  results: T[];\n};\ninterface D1ExecResult {\n  count: number;\n  duration: number;\n}\ntype D1SessionConstraint =\n  // Indicates that the first query should go to the primary, and the rest queries\n  // using the same D1DatabaseSession will go to any replica that is consistent with\n  // the bookmark maintained by the session (returned by the first query).\n  | 'first-primary'\n  // Indicates that the first query can go anywhere (primary or replica), and the rest queries\n  // using the same D1DatabaseSession will go to any replica that is consistent with\n  // the bookmark maintained by the session (returned by the first query).\n  | 'first-unconstrained';\ntype D1SessionBookmark = string;\ndeclare abstract class D1Database {\n  prepare(query: string): D1PreparedStatement;\n  batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;\n  exec(query: string): Promise<D1ExecResult>;\n  /**\n   * Creates a new D1 Session anchored at the given constraint or the bookmark.\n   * All queries executed using the created session will have sequential consistency,\n   * meaning that all writes done through the session will be visible in subsequent reads.\n   *\n   * @param constraintOrBookmark Either the session constraint or the explicit bookmark to anchor the created session.\n   */\n  withSession(constraintOrBookmark?: D1SessionBookmark | D1SessionConstraint): D1DatabaseSession;\n  /**\n   * @deprecated dump() will be removed soon, only applies to deprecated alpha v1 databases.\n   */\n  dump(): Promise<ArrayBuffer>;\n}\ndeclare abstract class D1DatabaseSession {\n  prepare(query: string): D1PreparedStatement;\n  batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;\n  /**\n   * @returns The latest session bookmark across all executed queries on the session.\n   *          If no query has been executed yet, `null` is returned.\n   */\n  getBookmark(): D1SessionBookmark | null;\n}\ndeclare abstract class D1PreparedStatement {\n  bind(...values: unknown[]): D1PreparedStatement;\n  first<T = unknown>(colName: string): Promise<T | null>;\n  first<T = Record<string, unknown>>(): Promise<T | null>;\n  run<T = Record<string, unknown>>(): Promise<D1Result<T>>;\n  all<T = Record<string, unknown>>(): Promise<D1Result<T>>;\n  raw<T = unknown[]>(options: { columnNames: true }): Promise<[string[], ...T[]]>;\n  raw<T = unknown[]>(options?: { columnNames?: false }): Promise<T[]>;\n}\n// `Disposable` was added to TypeScript's standard lib types in version 5.2.\n// To support older TypeScript versions, define an empty `Disposable` interface.\n// Users won't be able to use `using`/`Symbol.dispose` without upgrading to 5.2,\n// but this will ensure type checking on older versions still passes.\n// TypeScript's interface merging will ensure our empty interface is effectively\n// ignored when `Disposable` is included in the standard lib.\ninterface Disposable {}\n/**\n * An email message that can be sent from a Worker.\n */\ninterface EmailMessage {\n  /**\n   * Envelope From attribute of the email message.\n   */\n  readonly from: string;\n  /**\n   * Envelope To attribute of the email message.\n   */\n  readonly to: string;\n}\n/**\n * An email message that is sent to a consumer Worker and can be rejected/forwarded.\n */\ninterface ForwardableEmailMessage extends EmailMessage {\n  /**\n   * Stream of the email message content.\n   */\n  readonly raw: ReadableStream<Uint8Array>;\n  /**\n   * An [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers).\n   */\n  readonly headers: Headers;\n  /**\n   * Size of the email message content.\n   */\n  readonly rawSize: number;\n  /**\n   * Reject this email message by returning a permanent SMTP error back to the connecting client including the given reason.\n   * @param reason The reject reason.\n   * @returns void\n   */\n  setReject(reason: string): void;\n  /**\n   * Forward this email message to a verified destination address of the account.\n   * @param rcptTo Verified destination address.\n   * @param headers A [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers).\n   * @returns A promise that resolves when the email message is forwarded.\n   */\n  forward(rcptTo: string, headers?: Headers): Promise<void>;\n  /**\n   * Reply to the sender of this email message with a new EmailMessage object.\n   * @param message The reply message.\n   * @returns A promise that resolves when the email message is replied.\n   */\n  reply(message: EmailMessage): Promise<void>;\n}\n/**\n * A binding that allows a Worker to send email messages.\n */\ninterface SendEmail {\n  send(message: EmailMessage): Promise<void>;\n}\ndeclare abstract class EmailEvent extends ExtendableEvent {\n  readonly message: ForwardableEmailMessage;\n}\ndeclare type EmailExportedHandler<Env = unknown> = (\n  message: ForwardableEmailMessage,\n  env: Env,\n  ctx: ExecutionContext,\n) => void | Promise<void>;\ndeclare module 'cloudflare:email' {\n  let _EmailMessage: {\n    prototype: EmailMessage;\n    new (from: string, to: string, raw: ReadableStream | string): EmailMessage;\n  };\n  export { _EmailMessage as EmailMessage };\n}\ninterface Hyperdrive {\n  /**\n   * Connect directly to Hyperdrive as if it's your database, returning a TCP socket.\n   *\n   * Calling this method returns an idential socket to if you call\n   * `connect(\"host:port\")` using the `host` and `port` fields from this object.\n   * Pick whichever approach works better with your preferred DB client library.\n   *\n   * Note that this socket is not yet authenticated -- it's expected that your\n   * code (or preferably, the client library of your choice) will authenticate\n   * using the information in this class's readonly fields.\n   */\n  connect(): Socket;\n  /**\n   * A valid DB connection string that can be passed straight into the typical\n   * client library/driver/ORM. This will typically be the easiest way to use\n   * Hyperdrive.\n   */\n  readonly connectionString: string;\n  /*\n   * A randomly generated hostname that is only valid within the context of the\n   * currently running Worker which, when passed into `connect()` function from\n   * the \"cloudflare:sockets\" module, will connect to the Hyperdrive instance\n   * for your database.\n   */\n  readonly host: string;\n  /*\n   * The port that must be paired the the host field when connecting.\n   */\n  readonly port: number;\n  /*\n   * The username to use when authenticating to your database via Hyperdrive.\n   * Unlike the host and password, this will be the same every time\n   */\n  readonly user: string;\n  /*\n   * The randomly generated password to use when authenticating to your\n   * database via Hyperdrive. Like the host field, this password is only valid\n   * within the context of the currently running Worker instance from which\n   * it's read.\n   */\n  readonly password: string;\n  /*\n   * The name of the database to connect to.\n   */\n  readonly database: string;\n}\n// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\ntype ImageInfoResponse =\n  | {\n      format: 'image/svg+xml';\n    }\n  | {\n      format: string;\n      fileSize: number;\n      width: number;\n      height: number;\n    };\ntype ImageTransform = {\n  width?: number;\n  height?: number;\n  background?: string;\n  blur?: number;\n  border?:\n    | {\n        color?: string;\n        width?: number;\n      }\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n      };\n  brightness?: number;\n  contrast?: number;\n  fit?: 'scale-down' | 'contain' | 'pad' | 'squeeze' | 'cover' | 'crop';\n  flip?: 'h' | 'v' | 'hv';\n  gamma?: number;\n  gravity?:\n    | 'left'\n    | 'right'\n    | 'top'\n    | 'bottom'\n    | 'center'\n    | 'auto'\n    | 'entropy'\n    | {\n        x?: number;\n        y?: number;\n        mode: 'remainder' | 'box-center';\n      };\n  rotate?: 0 | 90 | 180 | 270;\n  saturation?: number;\n  sharpen?: number;\n  trim?:\n    | 'border'\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n        width?: number;\n        height?: number;\n        border?:\n          | boolean\n          | {\n              color?: string;\n              tolerance?: number;\n              keep?: number;\n            };\n      };\n};\ntype ImageDrawOptions = {\n  opacity?: number;\n  repeat?: boolean | string;\n  top?: number;\n  left?: number;\n  bottom?: number;\n  right?: number;\n};\ntype ImageOutputOptions = {\n  format: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp' | 'image/avif' | 'rgb' | 'rgba';\n  quality?: number;\n  background?: string;\n};\ninterface ImagesBinding {\n  /**\n   * Get image metadata (type, width and height)\n   * @throws {@link ImagesError} with code 9412 if input is not an image\n   * @param stream The image bytes\n   */\n  info(stream: ReadableStream<Uint8Array>): Promise<ImageInfoResponse>;\n  /**\n   * Begin applying a series of transformations to an image\n   * @param stream The image bytes\n   * @returns A transform handle\n   */\n  input(stream: ReadableStream<Uint8Array>): ImageTransformer;\n}\ninterface ImageTransformer {\n  /**\n   * Apply transform next, returning a transform handle.\n   * You can then apply more transformations, draw, or retrieve the output.\n   * @param transform\n   */\n  transform(transform: ImageTransform): ImageTransformer;\n  /**\n   * Draw an image on this transformer, returning a transform handle.\n   * You can then apply more transformations, draw, or retrieve the output.\n   * @param image The image (or transformer that will give the image) to draw\n   * @param options The options configuring how to draw the image\n   */\n  draw(\n    image: ReadableStream<Uint8Array> | ImageTransformer,\n    options?: ImageDrawOptions,\n  ): ImageTransformer;\n  /**\n   * Retrieve the image that results from applying the transforms to the\n   * provided input\n   * @param options Options that apply to the output e.g. output format\n   */\n  output(options: ImageOutputOptions): Promise<ImageTransformationResult>;\n}\ninterface ImageTransformationResult {\n  /**\n   * The image as a response, ready to store in cache or return to users\n   */\n  response(): Response;\n  /**\n   * The content type of the returned image\n   */\n  contentType(): string;\n  /**\n   * The bytes of the response\n   */\n  image(): ReadableStream<Uint8Array>;\n}\ninterface ImagesError extends Error {\n  readonly code: number;\n  readonly message: string;\n  readonly stack?: string;\n}\ntype Params<P extends string = any> = Record<P, string | string[]>;\ntype EventContext<Env, P extends string, Data> = {\n  request: Request<unknown, IncomingRequestCfProperties<unknown>>;\n  functionPath: string;\n  waitUntil: (promise: Promise<any>) => void;\n  passThroughOnException: () => void;\n  next: (input?: Request | string, init?: RequestInit) => Promise<Response>;\n  env: Env & {\n    ASSETS: {\n      fetch: typeof fetch;\n    };\n  };\n  params: Params<P>;\n  data: Data;\n};\ntype PagesFunction<\n  Env = unknown,\n  Params extends string = any,\n  Data extends Record<string, unknown> = Record<string, unknown>,\n> = (context: EventContext<Env, Params, Data>) => Response | Promise<Response>;\ntype EventPluginContext<Env, P extends string, Data, PluginArgs> = {\n  request: Request<unknown, IncomingRequestCfProperties<unknown>>;\n  functionPath: string;\n  waitUntil: (promise: Promise<any>) => void;\n  passThroughOnException: () => void;\n  next: (input?: Request | string, init?: RequestInit) => Promise<Response>;\n  env: Env & {\n    ASSETS: {\n      fetch: typeof fetch;\n    };\n  };\n  params: Params<P>;\n  data: Data;\n  pluginArgs: PluginArgs;\n};\ntype PagesPluginFunction<\n  Env = unknown,\n  Params extends string = any,\n  Data extends Record<string, unknown> = Record<string, unknown>,\n  PluginArgs = unknown,\n> = (context: EventPluginContext<Env, Params, Data, PluginArgs>) => Response | Promise<Response>;\ndeclare module 'assets:*' {\n  export const onRequest: PagesFunction;\n}\n// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\ndeclare module 'cloudflare:pipelines' {\n  export abstract class PipelineTransformationEntrypoint<\n    Env = unknown,\n    I extends PipelineRecord = PipelineRecord,\n    O extends PipelineRecord = PipelineRecord,\n  > {\n    protected env: Env;\n    protected ctx: ExecutionContext;\n    constructor(ctx: ExecutionContext, env: Env);\n    /**\n     * run recieves an array of PipelineRecord which can be\n     * transformed and returned to the pipeline\n     * @param records Incoming records from the pipeline to be transformed\n     * @param metadata Information about the specific pipeline calling the transformation entrypoint\n     * @returns A promise containing the transformed PipelineRecord array\n     */\n    public run(records: I[], metadata: PipelineBatchMetadata): Promise<O[]>;\n  }\n  export type PipelineRecord = Record<string, unknown>;\n  export type PipelineBatchMetadata = {\n    pipelineId: string;\n    pipelineName: string;\n  };\n  export interface Pipeline<T extends PipelineRecord = PipelineRecord> {\n    /**\n     * The Pipeline interface represents the type of a binding to a Pipeline\n     *\n     * @param records The records to send to the pipeline\n     */\n    send(records: T[]): Promise<void>;\n  }\n}\n// PubSubMessage represents an incoming PubSub message.\n// The message includes metadata about the broker, the client, and the payload\n// itself.\n// https://developers.cloudflare.com/pub-sub/\ninterface PubSubMessage {\n  // Message ID\n  readonly mid: number;\n  // MQTT broker FQDN in the form mqtts://BROKER.NAMESPACE.cloudflarepubsub.com:PORT\n  readonly broker: string;\n  // The MQTT topic the message was sent on.\n  readonly topic: string;\n  // The client ID of the client that published this message.\n  readonly clientId: string;\n  // The unique identifier (JWT ID) used by the client to authenticate, if token\n  // auth was used.\n  readonly jti?: string;\n  // A Unix timestamp (seconds from Jan 1, 1970), set when the Pub/Sub Broker\n  // received the message from the client.\n  readonly receivedAt: number;\n  // An (optional) string with the MIME type of the payload, if set by the\n  // client.\n  readonly contentType: string;\n  // Set to 1 when the payload is a UTF-8 string\n  // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901063\n  readonly payloadFormatIndicator: number;\n  // Pub/Sub (MQTT) payloads can be UTF-8 strings, or byte arrays.\n  // You can use payloadFormatIndicator to inspect this before decoding.\n  payload: string | Uint8Array;\n}\n// JsonWebKey extended by kid parameter\ninterface JsonWebKeyWithKid extends JsonWebKey {\n  // Key Identifier of the JWK\n  readonly kid: string;\n}\ninterface RateLimitOptions {\n  key: string;\n}\ninterface RateLimitOutcome {\n  success: boolean;\n}\ninterface RateLimit {\n  /**\n   * Rate limit a request based on the provided options.\n   * @see https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/\n   * @returns A promise that resolves with the outcome of the rate limit.\n   */\n  limit(options: RateLimitOptions): Promise<RateLimitOutcome>;\n}\n// Namespace for RPC utility types. Unfortunately, we can't use a `module` here as these types need\n// to referenced by `Fetcher`. This is included in the \"importable\" version of the types which\n// strips all `module` blocks.\ndeclare namespace Rpc {\n  // Branded types for identifying `WorkerEntrypoint`/`DurableObject`/`Target`s.\n  // TypeScript uses *structural* typing meaning anything with the same shape as type `T` is a `T`.\n  // For the classes exported by `cloudflare:workers` we want *nominal* typing (i.e. we only want to\n  // accept `WorkerEntrypoint` from `cloudflare:workers`, not any other class with the same shape)\n  export const __RPC_STUB_BRAND: '__RPC_STUB_BRAND';\n  export const __RPC_TARGET_BRAND: '__RPC_TARGET_BRAND';\n  export const __WORKER_ENTRYPOINT_BRAND: '__WORKER_ENTRYPOINT_BRAND';\n  export const __DURABLE_OBJECT_BRAND: '__DURABLE_OBJECT_BRAND';\n  export const __WORKFLOW_ENTRYPOINT_BRAND: '__WORKFLOW_ENTRYPOINT_BRAND';\n  export interface RpcTargetBranded {\n    [__RPC_TARGET_BRAND]: never;\n  }\n  export interface WorkerEntrypointBranded {\n    [__WORKER_ENTRYPOINT_BRAND]: never;\n  }\n  export interface DurableObjectBranded {\n    [__DURABLE_OBJECT_BRAND]: never;\n  }\n  export interface WorkflowEntrypointBranded {\n    [__WORKFLOW_ENTRYPOINT_BRAND]: never;\n  }\n  export type EntrypointBranded =\n    | WorkerEntrypointBranded\n    | DurableObjectBranded\n    | WorkflowEntrypointBranded;\n  // Types that can be used through `Stub`s\n  export type Stubable = RpcTargetBranded | ((...args: any[]) => any);\n  // Types that can be passed over RPC\n  // The reason for using a generic type here is to build a serializable subset of structured\n  //   cloneable composite types. This allows types defined with the \"interface\" keyword to pass the\n  //   serializable check as well. Otherwise, only types defined with the \"type\" keyword would pass.\n  type Serializable<T> =\n    // Structured cloneables\n    | BaseType\n    // Structured cloneable composites\n    | Map<\n        T extends Map<infer U, unknown> ? Serializable<U> : never,\n        T extends Map<unknown, infer U> ? Serializable<U> : never\n      >\n    | Set<T extends Set<infer U> ? Serializable<U> : never>\n    | ReadonlyArray<T extends ReadonlyArray<infer U> ? Serializable<U> : never>\n    | {\n        [K in keyof T]: K extends number | string ? Serializable<T[K]> : never;\n      }\n    // Special types\n    | Stub<Stubable>\n    // Serialized as stubs, see `Stubify`\n    | Stubable;\n  // Base type for all RPC stubs, including common memory management methods.\n  // `T` is used as a marker type for unwrapping `Stub`s later.\n  interface StubBase<T extends Stubable> extends Disposable {\n    [__RPC_STUB_BRAND]: T;\n    dup(): this;\n  }\n  export type Stub<T extends Stubable> = Provider<T> & StubBase<T>;\n  // This represents all the types that can be sent as-is over an RPC boundary\n  type BaseType =\n    | void\n    | undefined\n    | null\n    | boolean\n    | number\n    | bigint\n    | string\n    | TypedArray\n    | ArrayBuffer\n    | DataView\n    | Date\n    | Error\n    | RegExp\n    | ReadableStream<Uint8Array>\n    | WritableStream<Uint8Array>\n    | Request\n    | Response\n    | Headers;\n  // Recursively rewrite all `Stubable` types with `Stub`s\n  // prettier-ignore\n  type Stubify<T> = T extends Stubable ? Stub<T> : T extends Map<infer K, infer V> ? Map<Stubify<K>, Stubify<V>> : T extends Set<infer V> ? Set<Stubify<V>> : T extends Array<infer V> ? Array<Stubify<V>> : T extends ReadonlyArray<infer V> ? ReadonlyArray<Stubify<V>> : T extends BaseType ? T : T extends {\n        [key: string | number]: any;\n    } ? {\n        [K in keyof T]: Stubify<T[K]>;\n    } : T;\n  // Recursively rewrite all `Stub<T>`s with the corresponding `T`s.\n  // Note we use `StubBase` instead of `Stub` here to avoid circular dependencies:\n  // `Stub` depends on `Provider`, which depends on `Unstubify`, which would depend on `Stub`.\n  // prettier-ignore\n  type Unstubify<T> = T extends StubBase<infer V> ? V : T extends Map<infer K, infer V> ? Map<Unstubify<K>, Unstubify<V>> : T extends Set<infer V> ? Set<Unstubify<V>> : T extends Array<infer V> ? Array<Unstubify<V>> : T extends ReadonlyArray<infer V> ? ReadonlyArray<Unstubify<V>> : T extends BaseType ? T : T extends {\n        [key: string | number]: unknown;\n    } ? {\n        [K in keyof T]: Unstubify<T[K]>;\n    } : T;\n  type UnstubifyAll<A extends any[]> = {\n    [I in keyof A]: Unstubify<A[I]>;\n  };\n  // Utility type for adding `Provider`/`Disposable`s to `object` types only.\n  // Note `unknown & T` is equivalent to `T`.\n  type MaybeProvider<T> = T extends object ? Provider<T> : unknown;\n  type MaybeDisposable<T> = T extends object ? Disposable : unknown;\n  // Type for method return or property on an RPC interface.\n  // - Stubable types are replaced by stubs.\n  // - Serializable types are passed by value, with stubable types replaced by stubs\n  //   and a top-level `Disposer`.\n  // Everything else can't be passed over PRC.\n  // Technically, we use custom thenables here, but they quack like `Promise`s.\n  // Intersecting with `(Maybe)Provider` allows pipelining.\n  // prettier-ignore\n  type Result<R> = R extends Stubable ? Promise<Stub<R>> & Provider<R> : R extends Serializable<R> ? Promise<Stubify<R> & MaybeDisposable<R>> & MaybeProvider<R> : never;\n  // Type for method or property on an RPC interface.\n  // For methods, unwrap `Stub`s in parameters, and rewrite returns to be `Result`s.\n  // Unwrapping `Stub`s allows calling with `Stubable` arguments.\n  // For properties, rewrite types to be `Result`s.\n  // In each case, unwrap `Promise`s.\n  type MethodOrProperty<V> = V extends (...args: infer P) => infer R\n    ? (...args: UnstubifyAll<P>) => Result<Awaited<R>>\n    : Result<Awaited<V>>;\n  // Type for the callable part of an `Provider` if `T` is callable.\n  // This is intersected with methods/properties.\n  type MaybeCallableProvider<T> = T extends (...args: any[]) => any ? MethodOrProperty<T> : unknown;\n  // Base type for all other types providing RPC-like interfaces.\n  // Rewrites all methods/properties to be `MethodOrProperty`s, while preserving callable types.\n  // `Reserved` names (e.g. stub method names like `dup()`) and symbols can't be accessed over RPC.\n  export type Provider<\n    T extends object,\n    Reserved extends string = never,\n  > = MaybeCallableProvider<T> & {\n    [K in Exclude<keyof T, Reserved | symbol | keyof StubBase<never>>]: MethodOrProperty<T[K]>;\n  };\n}\ndeclare namespace Cloudflare {\n  interface Env {}\n}\ndeclare module 'cloudflare:workers' {\n  export type RpcStub<T extends Rpc.Stubable> = Rpc.Stub<T>;\n  export const RpcStub: {\n    new <T extends Rpc.Stubable>(value: T): Rpc.Stub<T>;\n  };\n  export abstract class RpcTarget implements Rpc.RpcTargetBranded {\n    [Rpc.__RPC_TARGET_BRAND]: never;\n  }\n  // `protected` fields don't appear in `keyof`s, so can't be accessed over RPC\n  export abstract class WorkerEntrypoint<Env = unknown> implements Rpc.WorkerEntrypointBranded {\n    [Rpc.__WORKER_ENTRYPOINT_BRAND]: never;\n    protected ctx: ExecutionContext;\n    protected env: Env;\n    constructor(ctx: ExecutionContext, env: Env);\n    fetch?(request: Request): Response | Promise<Response>;\n    tail?(events: TraceItem[]): void | Promise<void>;\n    trace?(traces: TraceItem[]): void | Promise<void>;\n    scheduled?(controller: ScheduledController): void | Promise<void>;\n    queue?(batch: MessageBatch<unknown>): void | Promise<void>;\n    test?(controller: TestController): void | Promise<void>;\n  }\n  export abstract class DurableObject<Env = unknown> implements Rpc.DurableObjectBranded {\n    [Rpc.__DURABLE_OBJECT_BRAND]: never;\n    protected ctx: DurableObjectState;\n    protected env: Env;\n    constructor(ctx: DurableObjectState, env: Env);\n    fetch?(request: Request): Response | Promise<Response>;\n    alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise<void>;\n    webSocketMessage?(ws: WebSocket, message: string | ArrayBuffer): void | Promise<void>;\n    webSocketClose?(\n      ws: WebSocket,\n      code: number,\n      reason: string,\n      wasClean: boolean,\n    ): void | Promise<void>;\n    webSocketError?(ws: WebSocket, error: unknown): void | Promise<void>;\n  }\n  export type WorkflowDurationLabel =\n    | 'second'\n    | 'minute'\n    | 'hour'\n    | 'day'\n    | 'week'\n    | 'month'\n    | 'year';\n  export type WorkflowSleepDuration = `${number} ${WorkflowDurationLabel}${'s' | ''}` | number;\n  export type WorkflowDelayDuration = WorkflowSleepDuration;\n  export type WorkflowTimeoutDuration = WorkflowSleepDuration;\n  export type WorkflowBackoff = 'constant' | 'linear' | 'exponential';\n  export type WorkflowStepConfig = {\n    retries?: {\n      limit: number;\n      delay: WorkflowDelayDuration | number;\n      backoff?: WorkflowBackoff;\n    };\n    timeout?: WorkflowTimeoutDuration | number;\n  };\n  export type WorkflowEvent<T> = {\n    payload: Readonly<T>;\n    timestamp: Date;\n    instanceId: string;\n  };\n  export type WorkflowStepEvent<T> = {\n    payload: Readonly<T>;\n    timestamp: Date;\n    type: string;\n  };\n  export abstract class WorkflowStep {\n    do<T extends Rpc.Serializable<T>>(name: string, callback: () => Promise<T>): Promise<T>;\n    do<T extends Rpc.Serializable<T>>(\n      name: string,\n      config: WorkflowStepConfig,\n      callback: () => Promise<T>,\n    ): Promise<T>;\n    sleep: (name: string, duration: WorkflowSleepDuration) => Promise<void>;\n    sleepUntil: (name: string, timestamp: Date | number) => Promise<void>;\n    waitForEvent<T extends Rpc.Serializable<T>>(\n      name: string,\n      options: {\n        type: string;\n        timeout?: WorkflowTimeoutDuration | number;\n      },\n    ): Promise<WorkflowStepEvent<T>>;\n  }\n  export abstract class WorkflowEntrypoint<\n    Env = unknown,\n    T extends Rpc.Serializable<T> | unknown = unknown,\n  >\n    implements Rpc.WorkflowEntrypointBranded\n  {\n    [Rpc.__WORKFLOW_ENTRYPOINT_BRAND]: never;\n    protected ctx: ExecutionContext;\n    protected env: Env;\n    constructor(ctx: ExecutionContext, env: Env);\n    run(event: Readonly<WorkflowEvent<T>>, step: WorkflowStep): Promise<unknown>;\n  }\n  export const env: Cloudflare.Env;\n}\ninterface SecretsStoreSecret {\n  /**\n   * Get a secret from the Secrets Store, returning a string of the secret value\n   * if it exists, or throws an error if it does not exist\n   */\n  get(): Promise<string>;\n}\ndeclare module 'cloudflare:sockets' {\n  function _connect(address: string | SocketAddress, options?: SocketOptions): Socket;\n  export { _connect as connect };\n}\ndeclare namespace TailStream {\n  interface Header {\n    readonly name: string;\n    readonly value: string;\n  }\n  interface FetchEventInfo {\n    readonly type: 'fetch';\n    readonly method: string;\n    readonly url: string;\n    readonly cfJson: string;\n    readonly headers: Header[];\n  }\n  interface JsRpcEventInfo {\n    readonly type: 'jsrpc';\n    readonly methodName: string;\n  }\n  interface ScheduledEventInfo {\n    readonly type: 'scheduled';\n    readonly scheduledTime: Date;\n    readonly cron: string;\n  }\n  interface AlarmEventInfo {\n    readonly type: 'alarm';\n    readonly scheduledTime: Date;\n  }\n  interface QueueEventInfo {\n    readonly type: 'queue';\n    readonly queueName: string;\n    readonly batchSize: number;\n  }\n  interface EmailEventInfo {\n    readonly type: 'email';\n    readonly mailFrom: string;\n    readonly rcptTo: string;\n    readonly rawSize: number;\n  }\n  interface TraceEventInfo {\n    readonly type: 'trace';\n    readonly traces: (string | null)[];\n  }\n  interface HibernatableWebSocketEventInfoMessage {\n    readonly type: 'message';\n  }\n  interface HibernatableWebSocketEventInfoError {\n    readonly type: 'error';\n  }\n  interface HibernatableWebSocketEventInfoClose {\n    readonly type: 'close';\n    readonly code: number;\n    readonly wasClean: boolean;\n  }\n  interface HibernatableWebSocketEventInfo {\n    readonly type: 'hibernatableWebSocket';\n    readonly info:\n      | HibernatableWebSocketEventInfoClose\n      | HibernatableWebSocketEventInfoError\n      | HibernatableWebSocketEventInfoMessage;\n  }\n  interface Resume {\n    readonly type: 'resume';\n    readonly attachment?: any;\n  }\n  interface CustomEventInfo {\n    readonly type: 'custom';\n  }\n  interface FetchResponseInfo {\n    readonly type: 'fetch';\n    readonly statusCode: number;\n  }\n  type EventOutcome =\n    | 'ok'\n    | 'canceled'\n    | 'exception'\n    | 'unknown'\n    | 'killSwitch'\n    | 'daemonDown'\n    | 'exceededCpu'\n    | 'exceededMemory'\n    | 'loadShed'\n    | 'responseStreamDisconnected'\n    | 'scriptNotFound';\n  interface ScriptVersion {\n    readonly id: string;\n    readonly tag?: string;\n    readonly message?: string;\n  }\n  interface Trigger {\n    readonly traceId: string;\n    readonly invocationId: string;\n    readonly spanId: string;\n  }\n  interface Onset {\n    readonly type: 'onset';\n    readonly dispatchNamespace?: string;\n    readonly entrypoint?: string;\n    readonly scriptName?: string;\n    readonly scriptTags?: string[];\n    readonly scriptVersion?: ScriptVersion;\n    readonly trigger?: Trigger;\n    readonly info:\n      | FetchEventInfo\n      | JsRpcEventInfo\n      | ScheduledEventInfo\n      | AlarmEventInfo\n      | QueueEventInfo\n      | EmailEventInfo\n      | TraceEventInfo\n      | HibernatableWebSocketEventInfo\n      | Resume\n      | CustomEventInfo;\n  }\n  interface Outcome {\n    readonly type: 'outcome';\n    readonly outcome: EventOutcome;\n    readonly cpuTime: number;\n    readonly wallTime: number;\n  }\n  interface Hibernate {\n    readonly type: 'hibernate';\n  }\n  interface SpanOpen {\n    readonly type: 'spanOpen';\n    readonly name: string;\n    readonly info?: FetchEventInfo | JsRpcEventInfo | Attributes;\n  }\n  interface SpanClose {\n    readonly type: 'spanClose';\n    readonly outcome: EventOutcome;\n  }\n  interface DiagnosticChannelEvent {\n    readonly type: 'diagnosticChannel';\n    readonly channel: string;\n    readonly message: any;\n  }\n  interface Exception {\n    readonly type: 'exception';\n    readonly name: string;\n    readonly message: string;\n    readonly stack?: string;\n  }\n  interface Log {\n    readonly type: 'log';\n    readonly level: 'debug' | 'error' | 'info' | 'log' | 'warn';\n    readonly message: string;\n  }\n  interface Return {\n    readonly type: 'return';\n    readonly info?: FetchResponseInfo | Attributes;\n  }\n  interface Link {\n    readonly type: 'link';\n    readonly label?: string;\n    readonly traceId: string;\n    readonly invocationId: string;\n    readonly spanId: string;\n  }\n  interface Attribute {\n    readonly name: string;\n    readonly value: string | string[] | boolean | boolean[] | number | number[] | bigint | bigint[];\n  }\n  interface Attributes {\n    readonly type: 'attributes';\n    readonly info: Attribute[];\n  }\n  interface TailEvent {\n    readonly traceId: string;\n    readonly invocationId: string;\n    readonly spanId: string;\n    readonly timestamp: Date;\n    readonly sequence: number;\n    readonly event:\n      | Onset\n      | Outcome\n      | Hibernate\n      | SpanOpen\n      | SpanClose\n      | DiagnosticChannelEvent\n      | Exception\n      | Log\n      | Return\n      | Link\n      | Attributes;\n  }\n  type TailEventHandler = (event: TailEvent) => void | Promise<void>;\n  type TailEventHandlerName =\n    | 'outcome'\n    | 'hibernate'\n    | 'spanOpen'\n    | 'spanClose'\n    | 'diagnosticChannel'\n    | 'exception'\n    | 'log'\n    | 'return'\n    | 'link'\n    | 'attributes';\n  type TailEventHandlerObject = Record<TailEventHandlerName, TailEventHandler>;\n  type TailEventHandlerType = TailEventHandler | TailEventHandlerObject;\n}\n// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n/**\n * Data types supported for holding vector metadata.\n */\ntype VectorizeVectorMetadataValue = string | number | boolean | string[];\n/**\n * Additional information to associate with a vector.\n */\ntype VectorizeVectorMetadata =\n  | VectorizeVectorMetadataValue\n  | Record<string, VectorizeVectorMetadataValue>;\ntype VectorFloatArray = Float32Array | Float64Array;\ninterface VectorizeError {\n  code?: number;\n  error: string;\n}\n/**\n * Comparison logic/operation to use for metadata filtering.\n *\n * This list is expected to grow as support for more operations are released.\n */\ntype VectorizeVectorMetadataFilterOp = '$eq' | '$ne';\n/**\n * Filter criteria for vector metadata used to limit the retrieved query result set.\n */\ntype VectorizeVectorMetadataFilter = {\n  [field: string]:\n    | Exclude<VectorizeVectorMetadataValue, string[]>\n    | null\n    | {\n        [Op in VectorizeVectorMetadataFilterOp]?: Exclude<\n          VectorizeVectorMetadataValue,\n          string[]\n        > | null;\n      };\n};\n/**\n * Supported distance metrics for an index.\n * Distance metrics determine how other \"similar\" vectors are determined.\n */\ntype VectorizeDistanceMetric = 'euclidean' | 'cosine' | 'dot-product';\n/**\n * Metadata return levels for a Vectorize query.\n *\n * Default to \"none\".\n *\n * @property all      Full metadata for the vector return set, including all fields (including those un-indexed) without truncation. This is a more expensive retrieval, as it requires additional fetching & reading of un-indexed data.\n * @property indexed  Return all metadata fields configured for indexing in the vector return set. This level of retrieval is \"free\" in that no additional overhead is incurred returning this data. However, note that indexed metadata is subject to truncation (especially for larger strings).\n * @property none     No indexed metadata will be returned.\n */\ntype VectorizeMetadataRetrievalLevel = 'all' | 'indexed' | 'none';\ninterface VectorizeQueryOptions {\n  topK?: number;\n  namespace?: string;\n  returnValues?: boolean;\n  returnMetadata?: boolean | VectorizeMetadataRetrievalLevel;\n  filter?: VectorizeVectorMetadataFilter;\n}\n/**\n * Information about the configuration of an index.\n */\ntype VectorizeIndexConfig =\n  | {\n      dimensions: number;\n      metric: VectorizeDistanceMetric;\n    }\n  | {\n      preset: string; // keep this generic, as we'll be adding more presets in the future and this is only in a read capacity\n    };\n/**\n * Metadata about an existing index.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link VectorizeIndexInfo} for its post-beta equivalent.\n */\ninterface VectorizeIndexDetails {\n  /** The unique ID of the index */\n  readonly id: string;\n  /** The name of the index. */\n  name: string;\n  /** (optional) A human readable description for the index. */\n  description?: string;\n  /** The index configuration, including the dimension size and distance metric. */\n  config: VectorizeIndexConfig;\n  /** The number of records containing vectors within the index. */\n  vectorsCount: number;\n}\n/**\n * Metadata about an existing index.\n */\ninterface VectorizeIndexInfo {\n  /** The number of records containing vectors within the index. */\n  vectorCount: number;\n  /** Number of dimensions the index has been configured for. */\n  dimensions: number;\n  /** ISO 8601 datetime of the last processed mutation on in the index. All changes before this mutation will be reflected in the index state. */\n  processedUpToDatetime: number;\n  /** UUIDv4 of the last mutation processed by the index. All changes before this mutation will be reflected in the index state. */\n  processedUpToMutation: number;\n}\n/**\n * Represents a single vector value set along with its associated metadata.\n */\ninterface VectorizeVector {\n  /** The ID for the vector. This can be user-defined, and must be unique. It should uniquely identify the object, and is best set based on the ID of what the vector represents. */\n  id: string;\n  /** The vector values */\n  values: VectorFloatArray | number[];\n  /** The namespace this vector belongs to. */\n  namespace?: string;\n  /** Metadata associated with the vector. Includes the values of other fields and potentially additional details. */\n  metadata?: Record<string, VectorizeVectorMetadata>;\n}\n/**\n * Represents a matched vector for a query along with its score and (if specified) the matching vector information.\n */\ntype VectorizeMatch = Pick<Partial<VectorizeVector>, 'values'> &\n  Omit<VectorizeVector, 'values'> & {\n    /** The score or rank for similarity, when returned as a result */\n    score: number;\n  };\n/**\n * A set of matching {@link VectorizeMatch} for a particular query.\n */\ninterface VectorizeMatches {\n  matches: VectorizeMatch[];\n  count: number;\n}\n/**\n * Results of an operation that performed a mutation on a set of vectors.\n * Here, `ids` is a list of vectors that were successfully processed.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link VectorizeAsyncMutation} for its post-beta equivalent.\n */\ninterface VectorizeVectorMutation {\n  /* List of ids of vectors that were successfully processed. */\n  ids: string[];\n  /* Total count of the number of processed vectors. */\n  count: number;\n}\n/**\n * Result type indicating a mutation on the Vectorize Index.\n * Actual mutations are processed async where the `mutationId` is the unique identifier for the operation.\n */\ninterface VectorizeAsyncMutation {\n  /** The unique identifier for the async mutation operation containing the changeset. */\n  mutationId: string;\n}\n/**\n * A Vectorize Vector Search Index for querying vectors/embeddings.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link Vectorize} for its new implementation.\n */\ndeclare abstract class VectorizeIndex {\n  /**\n   * Get information about the currently bound index.\n   * @returns A promise that resolves with information about the current index.\n   */\n  public describe(): Promise<VectorizeIndexDetails>;\n  /**\n   * Use the provided vector to perform a similarity search across the index.\n   * @param vector Input vector that will be used to drive the similarity search.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  public query(\n    vector: VectorFloatArray | number[],\n    options?: VectorizeQueryOptions,\n  ): Promise<VectorizeMatches>;\n  /**\n   * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown.\n   * @param vectors List of vectors that will be inserted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed.\n   */\n  public insert(vectors: VectorizeVector[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values.\n   * @param vectors List of vectors that will be upserted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed.\n   */\n  public upsert(vectors: VectorizeVector[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Delete a list of vectors with a matching id.\n   * @param ids List of vector ids that should be deleted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed (and thus deleted).\n   */\n  public deleteByIds(ids: string[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Get a list of vectors with a matching id.\n   * @param ids List of vector ids that should be returned.\n   * @returns A promise that resolves with the raw unscored vectors matching the id set.\n   */\n  public getByIds(ids: string[]): Promise<VectorizeVector[]>;\n}\n/**\n * A Vectorize Vector Search Index for querying vectors/embeddings.\n *\n * Mutations in this version are async, returning a mutation id.\n */\ndeclare abstract class Vectorize {\n  /**\n   * Get information about the currently bound index.\n   * @returns A promise that resolves with information about the current index.\n   */\n  public describe(): Promise<VectorizeIndexInfo>;\n  /**\n   * Use the provided vector to perform a similarity search across the index.\n   * @param vector Input vector that will be used to drive the similarity search.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  public query(\n    vector: VectorFloatArray | number[],\n    options?: VectorizeQueryOptions,\n  ): Promise<VectorizeMatches>;\n  /**\n   * Use the provided vector-id to perform a similarity search across the index.\n   * @param vectorId Id for a vector in the index against which the index should be queried.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  public queryById(vectorId: string, options?: VectorizeQueryOptions): Promise<VectorizeMatches>;\n  /**\n   * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown.\n   * @param vectors List of vectors that will be inserted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the insert changeset.\n   */\n  public insert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values.\n   * @param vectors List of vectors that will be upserted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the upsert changeset.\n   */\n  public upsert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Delete a list of vectors with a matching id.\n   * @param ids List of vector ids that should be deleted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the delete changeset.\n   */\n  public deleteByIds(ids: string[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Get a list of vectors with a matching id.\n   * @param ids List of vector ids that should be returned.\n   * @returns A promise that resolves with the raw unscored vectors matching the id set.\n   */\n  public getByIds(ids: string[]): Promise<VectorizeVector[]>;\n}\n/**\n * The interface for \"version_metadata\" binding\n * providing metadata about the Worker Version using this binding.\n */\ntype WorkerVersionMetadata = {\n  /** The ID of the Worker Version using this binding */\n  id: string;\n  /** The tag of the Worker Version using this binding */\n  tag: string;\n  /** The timestamp of when the Worker Version was uploaded */\n  timestamp: string;\n};\ninterface DynamicDispatchLimits {\n  /**\n   * Limit CPU time in milliseconds.\n   */\n  cpuMs?: number;\n  /**\n   * Limit number of subrequests.\n   */\n  subRequests?: number;\n}\ninterface DynamicDispatchOptions {\n  /**\n   * Limit resources of invoked Worker script.\n   */\n  limits?: DynamicDispatchLimits;\n  /**\n   * Arguments for outbound Worker script, if configured.\n   */\n  outbound?: {\n    [key: string]: any;\n  };\n}\ninterface DispatchNamespace {\n  /**\n   * @param name Name of the Worker script.\n   * @param args Arguments to Worker script.\n   * @param options Options for Dynamic Dispatch invocation.\n   * @returns A Fetcher object that allows you to send requests to the Worker script.\n   * @throws If the Worker script does not exist in this dispatch namespace, an error will be thrown.\n   */\n  get(\n    name: string,\n    args?: {\n      [key: string]: any;\n    },\n    options?: DynamicDispatchOptions,\n  ): Fetcher;\n}\ndeclare module 'cloudflare:workflows' {\n  /**\n   * NonRetryableError allows for a user to throw a fatal error\n   * that makes a Workflow instance fail immediately without triggering a retry\n   */\n  export class NonRetryableError extends Error {\n    public constructor(message: string, name?: string);\n  }\n}\ndeclare abstract class Workflow<PARAMS = unknown> {\n  /**\n   * Get a handle to an existing instance of the Workflow.\n   * @param id Id for the instance of this Workflow\n   * @returns A promise that resolves with a handle for the Instance\n   */\n  public get(id: string): Promise<WorkflowInstance>;\n  /**\n   * Create a new instance and return a handle to it. If a provided id exists, an error will be thrown.\n   * @param options Options when creating an instance including id and params\n   * @returns A promise that resolves with a handle for the Instance\n   */\n  public create(options?: WorkflowInstanceCreateOptions<PARAMS>): Promise<WorkflowInstance>;\n  /**\n   * Create a batch of instances and return handle for all of them. If a provided id exists, an error will be thrown.\n   * `createBatch` is limited at 100 instances at a time or when the RPC limit for the batch (1MiB) is reached.\n   * @param batch List of Options when creating an instance including name and params\n   * @returns A promise that resolves with a list of handles for the created instances.\n   */\n  public createBatch(batch: WorkflowInstanceCreateOptions<PARAMS>[]): Promise<WorkflowInstance[]>;\n}\ninterface WorkflowInstanceCreateOptions<PARAMS = unknown> {\n  /**\n   * An id for your Workflow instance. Must be unique within the Workflow.\n   */\n  id?: string;\n  /**\n   * The event payload the Workflow instance is triggered with\n   */\n  params?: PARAMS;\n}\ntype InstanceStatus = {\n  status:\n    | 'queued' // means that instance is waiting to be started (see concurrency limits)\n    | 'running'\n    | 'paused'\n    | 'errored'\n    | 'terminated' // user terminated the instance while it was running\n    | 'complete'\n    | 'waiting' // instance is hibernating and waiting for sleep or event to finish\n    | 'waitingForPause' // instance is finishing the current work to pause\n    | 'unknown';\n  error?: string;\n  output?: object;\n};\ninterface WorkflowError {\n  code?: number;\n  message: string;\n}\ndeclare abstract class WorkflowInstance {\n  public id: string;\n  /**\n   * Pause the instance.\n   */\n  public pause(): Promise<void>;\n  /**\n   * Resume the instance. If it is already running, an error will be thrown.\n   */\n  public resume(): Promise<void>;\n  /**\n   * Terminate the instance. If it is errored, terminated or complete, an error will be thrown.\n   */\n  public terminate(): Promise<void>;\n  /**\n   * Restart the instance.\n   */\n  public restart(): Promise<void>;\n  /**\n   * Returns the current status of the instance.\n   */\n  public status(): Promise<InstanceStatus>;\n  /**\n   * Send an event to this instance.\n   */\n  public sendEvent({ type, payload }: { type: string; payload: unknown }): Promise<void>;\n}\n"
  },
  {
    "path": "examples/server/wrangler.jsonc",
    "content": "/**\n * For more details on how to configure Wrangler, refer to:\n * https://developers.cloudflare.com/workers/wrangler/configuration/\n */\n{\n  \"$schema\": \"node_modules/wrangler/config-schema.json\",\n  \"name\": \"remote-mcp-server-authless\",\n  \"main\": \"src/index.ts\",\n  \"compatibility_date\": \"2025-05-16\",\n  \"compatibility_flags\": [\"nodejs_compat\"],\n  \"migrations\": [\n    {\n      \"new_sqlite_classes\": [\"MyMCP\"],\n      \"tag\": \"v1\",\n    },\n  ],\n  \"durable_objects\": {\n    \"bindings\": [\n      {\n        \"class_name\": \"MyMCP\",\n        \"name\": \"MCP_OBJECT\",\n      },\n    ],\n  },\n  \"observability\": {\n    \"enabled\": true,\n  },\n}\n"
  },
  {
    "path": "examples/typescript-server-demo/README.md",
    "content": "# typescript-server-demo\n\nThis barebones server demonstrates how to use `@mcp-ui/server` to generate all three types of UI resources via three separate tools:\n\n- `showExternalUrl`: Renders an `<iframe>` pointing to an external URL.\n- `showRawHtml`: Renders a static block of HTML.\n- `showRemoteDom`: Executes a script that uses a custom component (`<ui-text>`) to render content, demonstrating how to leverage a client-side component library.\n\nFor a detailed explanation of how this server works, see the [TypeScript Server Walkthrough](https://mcpui.dev/guide/server/typescript/walkthrough.html).\n\n## Running the server\n\nTo run the server in development mode, first install the dependencies, then run the `dev` command:\n\n```bash\npnpm install\npnpm dev\n```\n\nThe server will be available at `http://localhost:3000`.\n\nYou can view the UI resources from this server by connecting to it with the [`ui-inspector`](https://github.com/idosal/ui-inspector) (target `http://localhost:3000/mcp` with Streamable HTTP Transport Type).\n\n"
  },
  {
    "path": "examples/typescript-server-demo/package.json",
    "content": "{\n  \"name\": \"typescript-server-demo\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"description\": \"Barebones TypeScript Model Context Protocol server demo.\",\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"start\": \"node dist/index.js\",\n    \"dev\": \"vite-node src/index.ts\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"@mcp-ui/server\": \"^5.2.0\",\n    \"@modelcontextprotocol/sdk\": \"^1.22.0\",\n    \"cors\": \"^2.8.5\",\n    \"express\": \"^4.17.1\"\n  },\n  \"devDependencies\": {\n    \"@types/cors\": \"^2.8.12\",\n    \"@types/express\": \"^4.17.13\",\n    \"@types/node\": \"^20.0.0\",\n    \"ts-node\": \"^10.4.0\",\n    \"typescript\": \"^5.2.0\",\n    \"vite\": \"^5.0.0\",\n    \"vite-node\": \"^1.0.0\"\n  }\n}\n"
  },
  {
    "path": "examples/typescript-server-demo/src/index.ts",
    "content": "import express from 'express';\nimport cors from 'cors';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';\nimport { createUIResource } from '@mcp-ui/server';\nimport { randomUUID } from 'crypto';\n\nconst app = express();\nconst port = 3000;\n\napp.use(\n  cors({\n    origin: '*',\n    exposedHeaders: ['Mcp-Session-Id'],\n    allowedHeaders: ['Content-Type', 'mcp-session-id'],\n  }),\n);\napp.use(express.json());\n\n// Map to store transports by session ID, as shown in the documentation.\nconst transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};\n\n// Handle POST requests for client-to-server communication.\napp.post('/mcp', async (req, res) => {\n  const sessionId = req.headers['mcp-session-id'] as string | undefined;\n  let transport: StreamableHTTPServerTransport;\n\n  if (sessionId && transports[sessionId]) {\n    // A session already exists; reuse the existing transport.\n    transport = transports[sessionId];\n  } else if (!sessionId && isInitializeRequest(req.body)) {\n    // This is a new initialization request. Create a new transport.\n    transport = new StreamableHTTPServerTransport({\n      sessionIdGenerator: () => randomUUID(),\n      onsessioninitialized: (sid) => {\n        transports[sid] = transport;\n        console.log(`MCP Session initialized: ${sid}`);\n      },\n    });\n\n    // Clean up the transport from our map when the session closes.\n    transport.onclose = () => {\n      if (transport.sessionId) {\n        console.log(`MCP Session closed: ${transport.sessionId}`);\n        delete transports[transport.sessionId];\n      }\n    };\n\n    // Create a new server instance for this specific session.\n    const server = new McpServer({\n      name: 'typescript-server-demo',\n      version: '1.0.0',\n    });\n\n    // Register our tools on the new server instance.\n    server.registerTool(\n      'showExternalUrl',\n      {\n        title: 'Show External URL',\n        description: 'Creates a UI resource displaying an external URL (example.com).',\n        inputSchema: {},\n      },\n      async () => {\n        // Create the UI resource to be returned to the client\n        // This is the only MCP-UI specific code in this example\n        const uiResource = await createUIResource({\n          uri: 'ui://greeting',\n          content: { type: 'externalUrl', iframeUrl: 'https://example.com' },\n          encoding: 'text',\n        });\n\n        return {\n          content: [uiResource],\n        };\n      },\n    );\n\n    server.registerTool(\n      'showRawHtml',\n      {\n        title: 'Show Raw HTML',\n        description: 'Creates a UI resource displaying raw HTML.',\n        inputSchema: {},\n      },\n      async () => {\n        const uiResource = await createUIResource({\n          uri: 'ui://raw-html-demo',\n          content: { type: 'rawHtml', htmlString: '<h1>Hello from Raw HTML</h1>' },\n          encoding: 'text',\n        });\n\n        return {\n          content: [uiResource],\n        };\n      },\n    );\n\n    // Connect the server instance to the transport for this session.\n    await server.connect(transport);\n  } else {\n    return res.status(400).json({\n      error: { message: 'Bad Request: No valid session ID provided' },\n    });\n  }\n\n  // Handle the client's request using the session's transport.\n  await transport.handleRequest(req, res, req.body);\n});\n\n// A separate, reusable handler for GET and DELETE requests.\nconst handleSessionRequest = async (req: express.Request, res: express.Response) => {\n  const sessionId = req.headers['mcp-session-id'] as string | undefined;\n  console.log('sessionId', sessionId);\n  if (!sessionId || !transports[sessionId]) {\n    return res.status(404).send('Session not found');\n  }\n\n  const transport = transports[sessionId];\n  await transport.handleRequest(req, res);\n};\n\n// GET handles the long-lived stream for server-to-client messages.\napp.get('/mcp', handleSessionRequest);\n\n// DELETE handles explicit session termination from the client.\napp.delete('/mcp', handleSessionRequest);\n\napp.listen(port, () => {\n  console.log(`TypeScript MCP server listening at http://localhost:${port}`);\n});\n"
  },
  {
    "path": "examples/typescript-server-demo/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\",\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "lefthook.yml",
    "content": "pre-commit:\n  parallel: true\n  stage_fixed: true\n  jobs:\n    - name: lint typescript\n      glob: '*.{ts,tsx}'\n      run: pnpm eslint --fix --max-warnings=0 --no-warn-ignored {staged_files} && pnpm prettier --write {staged_files}\n\n    - name: format js\n      glob: '*.{js,jsx,mjs,cjs}'\n      run: pnpm prettier --write {staged_files}\n\n    - name: format json/css\n      glob: '*.{json,css,scss}'\n      run: pnpm prettier --write {staged_files}\n\n    - name: format yaml\n      glob: '*.{yaml,yml}'\n      run: pnpm prettier --write {staged_files}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"mcp-ui\",\n  \"private\": true,\n  \"scripts\": {\n    \"preinstall\": \"npx only-allow pnpm\",\n    \"dev\": \"pnpm --filter=\\\"./sdks/typescript/*\\\" --parallel dev\",\n    \"build\": \"pnpm --filter=\\\"./sdks/typescript/*\\\" build\",\n    \"preview\": \"pnpm --filter=\\\"./sdks/typescript/*\\\" --parallel preview\",\n    \"test\": \"pnpm test:ts && pnpm test:ruby\",\n    \"test:ts\": \"vitest run\",\n    \"test:ruby\": \"cd sdks/ruby && bundle install && bundle exec rake spec\",\n    \"test:watch\": \"vitest watch\",\n    \"coverage\": \"vitest run --coverage\",\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint . --fix\",\n    \"format\": \"prettier --write \\\"**/*.{ts,tsx,js,jsx,json,md,yaml,yml,css,scss}\\\"\",\n    \"version:patch\": \"echo \\\"Versioning with Changesets, then run pnpm install\\\" && pnpm changeset version && pnpm install --lockfile-only\",\n    \"publish-packages\": \"pnpm build && pnpm changeset publish\",\n    \"docs:dev\": \"vitepress dev docs/src\",\n    \"docs:build\": \"vitepress build docs/src\",\n    \"docs:preview\": \"vitepress preview docs/src\"\n  },\n  \"devDependencies\": {\n    \"@eslint/js\": \"^9.25.0\",\n    \"@semantic-release/changelog\": \"^6.0.3\",\n    \"@semantic-release/commit-analyzer\": \"^13.0.1\",\n    \"@semantic-release/exec\": \"^7.1.0\",\n    \"@semantic-release/git\": \"^10.0.1\",\n    \"@semantic-release/github\": \"^12.0.2\",\n    \"@semantic-release/npm\": \"^13.1.2\",\n    \"@semantic-release/release-notes-generator\": \"^14.1.0\",\n    \"@swc/core\": \"^1.4.0\",\n    \"@testing-library/jest-dom\": \"^6.1.5\",\n    \"@testing-library/react\": \"^14.1.2\",\n    \"@types/jest\": \"^29.5.11\",\n    \"@types/node\": \"^22.15.18\",\n    \"@vitejs/plugin-react-swc\": \"^4.2.2\",\n    \"c8\": \"^10.1.3\",\n    \"eslint\": \"^9.39.1\",\n    \"eslint-config-prettier\": \"^10.1.8\",\n    \"eslint-plugin-jsx-a11y\": \"^6.8.0\",\n    \"eslint-plugin-react\": \"^7.37.5\",\n    \"eslint-plugin-react-hooks\": \"^7.0.1\",\n    \"globals\": \"^16.0.0\",\n    \"jsdom\": \"^25.0.1\",\n    \"prettier\": \"^3.7.4\",\n    \"prettier-plugin-tailwindcss\": \"^0.7.2\",\n    \"semantic-release\": \"^25.0.2\",\n    \"ts-jest\": \"^29.1.1\",\n    \"ts-node\": \"^10.9.2\",\n    \"typescript\": \"^5.9.3\",\n    \"typescript-eslint\": \"^8.30.1\",\n    \"vite\": \"^7.2.7\",\n    \"vite-plugin-dts\": \"^4.5.4\",\n    \"vitepress\": \"^1.6.4\",\n    \"vitest\": \"^4.0.15\"\n  },\n  \"packageManager\": \"pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977\",\n  \"license\": \"Apache-2.0\",\n  \"version\": \"5.2.0\"\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  # all packages in subdirs of sdks/typescript/\n  - 'sdks/typescript/*'\n  - 'examples/*'\n  - 'docs'\n"
  },
  {
    "path": "sdks/python/server/CHANGELOG.md",
    "content": "# 1.0.0 (2025-11-04)\n\n\n### Bug Fixes\n\n* add a bridge to pass messages in and out of the proxy ([#38](https://github.com/idosal/mcp-ui/issues/38)) ([30ccac0](https://github.com/idosal/mcp-ui/commit/30ccac0706ad8e02ebcd8960924ed1d58ddedf85))\n* bump client version ([75c9236](https://github.com/idosal/mcp-ui/commit/75c923689654b4443ad1093fafc0bad16902e4cc))\n* **client:** specify iframe ([fd0b70a](https://github.com/idosal/mcp-ui/commit/fd0b70a84948d3aa5d7a79269ff7c3bcd0946689))\n* **client:** styling ([6ff9b68](https://github.com/idosal/mcp-ui/commit/6ff9b685fd1be770fd103943e45275e9ec86905c))\n* dependencies ([887f61f](https://github.com/idosal/mcp-ui/commit/887f61f827b4585c17493d4fa2dfb251ea598587))\n* Enable bidirectional message relay in rawhtml proxy mode ([#138](https://github.com/idosal/mcp-ui/issues/138)) ([f0bdefb](https://github.com/idosal/mcp-ui/commit/f0bdefb818aca5a3bfbdfe28fc4fe057b1818cb5))\n* ensure Apps SDK adapter is bundled properly and initialized wth config ([#137](https://github.com/idosal/mcp-ui/issues/137)) ([4f7c25c](https://github.com/idosal/mcp-ui/commit/4f7c25ce7e6f25e36cfc188016b012d31d722204))\n* export RemoteDomResource ([2b86f2d](https://github.com/idosal/mcp-ui/commit/2b86f2dd4506de49c69908e23d84a2a323170446))\n* export ResourceRenderer and HtmlResource ([2b841a5](https://github.com/idosal/mcp-ui/commit/2b841a556c1111ed70ccb3d3987afd21fe7df897))\n* exports ([3a93a16](https://github.com/idosal/mcp-ui/commit/3a93a16e1b7438ba7b2ef49ca854479f755abcc6))\n* fix file extension reference in package.json ([927989c](https://github.com/idosal/mcp-ui/commit/927989c4f81742106b6f5e2f68343fb7ea7d016a))\n* iframe handle ([#15](https://github.com/idosal/mcp-ui/issues/15)) ([66bd4fd](https://github.com/idosal/mcp-ui/commit/66bd4fd3d04f82e3e4557f064e701b68e1d8af11))\n* lint ([4487820](https://github.com/idosal/mcp-ui/commit/44878203a71c3c9173d463b809be36769e996ba9))\n* lint ([d0a91f9](https://github.com/idosal/mcp-ui/commit/d0a91f9a07ec0042690240c3d8d0bad620f8c765))\n* minor typo ([a0bee9c](https://github.com/idosal/mcp-ui/commit/a0bee9c85e5ee02e021ba687940ced38220445fe))\n* move react dependencies to be peer dependencies ([#91](https://github.com/idosal/mcp-ui/issues/91)) ([f672f3e](https://github.com/idosal/mcp-ui/commit/f672f3efc1c2ba2fbae16f9dcdc2142c2b4bd920)), closes [#90](https://github.com/idosal/mcp-ui/issues/90)\n* package config ([8dc1e53](https://github.com/idosal/mcp-ui/commit/8dc1e5358c3c8e641206a5e6851427d360cc1955))\n* packaging ([9e6babd](https://github.com/idosal/mcp-ui/commit/9e6babd3a587213452ea7aec4cc9ae3a50fa1965))\n* pass ref explicitly using iframeProps ([#33](https://github.com/idosal/mcp-ui/issues/33)) ([d01b5d1](https://github.com/idosal/mcp-ui/commit/d01b5d1e4cdaedc436ba2fa8984d866d93d59087))\n* publish ([0943e7a](https://github.com/idosal/mcp-ui/commit/0943e7acaf17f32aae085c2313bfbec47bc59f1f))\n* ref passing to UIResourceRenderer ([#32](https://github.com/idosal/mcp-ui/issues/32)) ([d28c23f](https://github.com/idosal/mcp-ui/commit/d28c23f9b8ee320f4e361200ae02a23f0d2a1c0c))\n* remove shared dependency ([e66e8f4](https://github.com/idosal/mcp-ui/commit/e66e8f49b1ba46090db6e4682060488566f4fe41))\n* rename components and methods to fit new scope ([#22](https://github.com/idosal/mcp-ui/issues/22)) ([6bab1fe](https://github.com/idosal/mcp-ui/commit/6bab1fe3a168a18e7ba4762e23478abf4e0cc84c))\n* rename delivery -> encoding and flavor -> framework ([#36](https://github.com/idosal/mcp-ui/issues/36)) ([9a509ed](https://github.com/idosal/mcp-ui/commit/9a509ed80d051b0a8042b36958b401a0a7c1e138))\n* Ruby comment ([b22dc2e](https://github.com/idosal/mcp-ui/commit/b22dc2e0a0db20d98ada884649ad408ebaf72d22))\n* support react-router ([21ffb95](https://github.com/idosal/mcp-ui/commit/21ffb95fe6d77a348b95b38dbf3741ba6442894e))\n* text and blob support in RemoteDOM resources ([ec68eb9](https://github.com/idosal/mcp-ui/commit/ec68eb90df984da8b492cc25eafdafdeda79f299))\n* trigger release ([aaca831](https://github.com/idosal/mcp-ui/commit/aaca83125c3f7825ccdebf0f04f8553e953c5249))\n* typescript ci publish ([e7c0ebf](https://github.com/idosal/mcp-ui/commit/e7c0ebfa7f7b552f9763743fda659d1441f21692))\n* typescript types to be compatible with MCP SDK ([#10](https://github.com/idosal/mcp-ui/issues/10)) ([74365d7](https://github.com/idosal/mcp-ui/commit/74365d7ed6422beef6cd9ee0f5a97c847bd9827b))\n* update deps ([4091ef4](https://github.com/idosal/mcp-ui/commit/4091ef47da048fab3c4feb002f5287b2ff295744))\n* update isUIResource to use EmbeddedResource type ([#122](https://github.com/idosal/mcp-ui/issues/122)) ([5a65a0b](https://github.com/idosal/mcp-ui/commit/5a65a0b1ba63e6cfda26b8da41239a532f00d60a)), closes [#117](https://github.com/idosal/mcp-ui/issues/117)\n* use targetOrigin in the proxy message relay ([#40](https://github.com/idosal/mcp-ui/issues/40)) ([b3fb54e](https://github.com/idosal/mcp-ui/commit/b3fb54e28ca7b8eeda896b5bcf478b6343dbba47))\n* validate URL ([b7c994d](https://github.com/idosal/mcp-ui/commit/b7c994dfdd947b3dfbb903fc8cb896d61004c8d8))\n* wc dist overwrite ([#63](https://github.com/idosal/mcp-ui/issues/63)) ([9e46c56](https://github.com/idosal/mcp-ui/commit/9e46c56c7a8908410fad6d08a5d845139e93f80f))\n\n\n### Documentation\n\n* bump ([#4](https://github.com/idosal/mcp-ui/issues/4)) ([ad4d163](https://github.com/idosal/mcp-ui/commit/ad4d1632cc1f9c99072349a8f0cdaac343236132))\n\n\n### Features\n\n* add convenience function isUIResource to client SDK ([#86](https://github.com/idosal/mcp-ui/issues/86)) ([607c6ad](https://github.com/idosal/mcp-ui/commit/607c6add3567bb60c45accf3e1b25a38ed284a6f))\n* add embeddedResourceProps for annotations ([#99](https://github.com/idosal/mcp-ui/issues/99)) ([b96ec44](https://github.com/idosal/mcp-ui/commit/b96ec442ec319a1944393ada0bdcccb93b7ffc62))\n* add proxy option to externalUrl ([#37](https://github.com/idosal/mcp-ui/issues/37)) ([7b95cd0](https://github.com/idosal/mcp-ui/commit/7b95cd0b3873fc1cde28748ec463e81c6ff1c494))\n* add remote-dom content type ([#18](https://github.com/idosal/mcp-ui/issues/18)) ([5dacf37](https://github.com/idosal/mcp-ui/commit/5dacf37c22b5ee6ae795049a8d573fc073b8a1f5))\n* add Ruby server SDK ([#31](https://github.com/idosal/mcp-ui/issues/31)) ([5ffcde4](https://github.com/idosal/mcp-ui/commit/5ffcde4a373accdd063fa6c3b1b3d4df13c91b53))\n* add sandbox permissions instead of an override ([#83](https://github.com/idosal/mcp-ui/issues/83)) ([b1068e9](https://github.com/idosal/mcp-ui/commit/b1068e9e87caa2b4302bf145a33efdfd1af05c1d))\n* add ui-request-render-data message type ([#111](https://github.com/idosal/mcp-ui/issues/111)) ([26135ce](https://github.com/idosal/mcp-ui/commit/26135ce2c7f7d586b0b81a03623cd77dc1bc7f90))\n* add UIResourceRenderer Web Component ([#58](https://github.com/idosal/mcp-ui/issues/58)) ([ec8f299](https://github.com/idosal/mcp-ui/commit/ec8f2994ecf36774e6ad5191654ba22946d0ee49))\n* auto resize with the autoResizeIframe prop ([#56](https://github.com/idosal/mcp-ui/issues/56)) ([76c867a](https://github.com/idosal/mcp-ui/commit/76c867a569b72aed892290aa84e1194ab8eb79ce))\n* change onGenericMcpAction to optional onUiAction ([1913b59](https://github.com/idosal/mcp-ui/commit/1913b5977c30811f9e67659949e2d961f2eda983))\n* **client:** allow setting supportedContentTypes for HtmlResource ([#17](https://github.com/idosal/mcp-ui/issues/17)) ([e009ef1](https://github.com/idosal/mcp-ui/commit/e009ef10010134ba3d9893314cc4d8e1274f1f07))\n* consolidate ui:// and ui-app:// ([#8](https://github.com/idosal/mcp-ui/issues/8)) ([2e08035](https://github.com/idosal/mcp-ui/commit/2e08035676bb6a46ef3c94dba916bc895f1fa3cc))\n* pass iframe props down ([#14](https://github.com/idosal/mcp-ui/issues/14)) ([112539d](https://github.com/idosal/mcp-ui/commit/112539d28640a96e8375a6b416f2ba559370b312))\n* refactor UTFtoB64 (bump server version) ([#95](https://github.com/idosal/mcp-ui/issues/95)) ([2d5e16b](https://github.com/idosal/mcp-ui/commit/2d5e16bf39073ee890586f458412f0c3b474c2b8))\n* send render data to the iframe ([#51](https://github.com/idosal/mcp-ui/issues/51)) ([d38cfc7](https://github.com/idosal/mcp-ui/commit/d38cfc7925061c1ae1911bdee408033c8e9f283d))\n* separate html and remote-dom props ([#24](https://github.com/idosal/mcp-ui/issues/24)) ([a7f0529](https://github.com/idosal/mcp-ui/commit/a7f05299dc9cc40184f9ab25c5b648ee7077be64))\n* support adapters ([#127](https://github.com/idosal/mcp-ui/issues/127)) ([d4bd152](https://github.com/idosal/mcp-ui/commit/d4bd152db0a1bd27081502098f3cd9aa54ca359e))\n* support generic messages response ([#35](https://github.com/idosal/mcp-ui/issues/35)) ([10b407b](https://github.com/idosal/mcp-ui/commit/10b407b279b3ee9608ef077445f4d714f88343c5))\n* support metadata in Python SDK ([#134](https://github.com/idosal/mcp-ui/issues/134)) ([9bc3c64](https://github.com/idosal/mcp-ui/commit/9bc3c646c2638e16ac62edf9faca2dbee2b8cb7e))\n* support passing resource metadata ([#87](https://github.com/idosal/mcp-ui/issues/87)) ([f1c1c9b](https://github.com/idosal/mcp-ui/commit/f1c1c9b62dd74c63045b295eb388181843ac772a))\n* support proxy for rawHtml ([#132](https://github.com/idosal/mcp-ui/issues/132)) ([1bbeb09](https://github.com/idosal/mcp-ui/commit/1bbeb093bc9c389f4ccfd9e8df06dc7f1eaadde0))\n* support ui action result types ([#6](https://github.com/idosal/mcp-ui/issues/6)) ([899d152](https://github.com/idosal/mcp-ui/commit/899d1527286a281a23fbb8f3a207d435dfc3fe96))\n* switch to ResourceRenderer ([#21](https://github.com/idosal/mcp-ui/issues/21)) ([6fe3166](https://github.com/idosal/mcp-ui/commit/6fe316682675e27db914d60696754677e3783448))\n\n\n### BREAKING CHANGES\n\n* The existing naming is ambiguous. Renaming delivery to encoding and flavor to framework should clarify the intent.\n* exported names have changed\n* removed deprecated client API\n* (previous one didn't take due to semantic-release misalignment)\n\n# Changelog\n\nAll notable changes to the MCP UI Python Server SDK will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [0.1.0](https://github.com/idosal/mcp-ui/releases/tag/python-server-sdk/v0.1.0) (2025-10-25)\n\nInitial release of the MCP UI Python Server SDK.\n\n### Features\n\n* Initial implementation of MCP UI Server SDK for Python\n* Support for HTML and Remote DOM resource types\n* Type-safe API using Pydantic models\n* Comprehensive resource creation and management utilities\n"
  },
  {
    "path": "sdks/python/server/README.md",
    "content": "# MCP UI Server SDK for Python\n\nA Python SDK for creating MCP UI resources on the server side, enabling rich interactive experiences in MCP applications.\n\n## Installation\n\n```bash\npip install mcp-ui-server\n```\n\n## Quick Start\n\n```python\nfrom mcp_ui_server import create_ui_resource\n\n# Create an HTML resource\nresource = create_ui_resource({\n    \"uri\": \"ui://my-component\",\n    \"content\": {\n        \"type\": \"rawHtml\",\n        \"htmlString\": \"<h1>Hello MCP UI!</h1>\"\n    },\n    \"encoding\": \"text\"\n})\n\n# Use in MCP tool result\ntool_result = {\n    \"content\": [resource.to_dict()]\n}\n```\n\n## Features\n\n### Resource Types\n\nThe SDK supports three main content types:\n\n#### 1. Raw HTML\nDirect HTML content for embedding in the UI:\n\n```python\nhtml_resource = create_ui_resource({\n    \"uri\": \"ui://html-example\",\n    \"content\": {\n        \"type\": \"rawHtml\", \n        \"htmlString\": \"<div><h1>Dynamic Content</h1><p>Generated server-side</p></div>\"\n    },\n    \"encoding\": \"text\"  # or \"blob\" for base64 encoding\n})\n```\n\n#### 2. External URLs\nEmbed external websites via iframe:\n\n```python\nurl_resource = create_ui_resource({\n    \"uri\": \"ui://external-site\",\n    \"content\": {\n        \"type\": \"externalUrl\",\n        \"iframeUrl\": \"https://example.com\"\n    },\n    \"encoding\": \"text\"\n})\n```\n\n#### 3. Remote DOM Components\nInteractive components using React or Web Components:\n\n```python\n# React component\nreact_resource = create_ui_resource({\n    \"uri\": \"ui://react-component\",\n    \"content\": {\n        \"type\": \"remoteDom\",\n        \"script\": \"\"\"\n            function WeatherWidget({ location }) {\n                return (\n                    <div>\n                        <h3>Weather for {location}</h3>\n                        <p>Temperature: 72°F</p>\n                    </div>\n                );\n            }\n        \"\"\",\n        \"framework\": \"react\"\n    },\n    \"encoding\": \"text\"\n})\n\n# Web Components\nwc_resource = create_ui_resource({\n    \"uri\": \"ui://web-component\", \n    \"content\": {\n        \"type\": \"remoteDom\",\n        \"script\": \"\"\"\n            class StatusIndicator extends HTMLElement {\n                connectedCallback() {\n                    this.innerHTML = `\n                        <div style=\"color: green;\">\n                            ✅ System Online\n                        </div>\n                    `;\n                }\n            }\n            customElements.define('status-indicator', StatusIndicator);\n        \"\"\",\n        \"framework\": \"webcomponents\"\n    },\n    \"encoding\": \"blob\"\n})\n```\n\n### Encoding Options\n\nChoose between text and blob encoding:\n\n- **text**: Direct string content (recommended for development)\n- **blob**: Base64-encoded content (recommended for production)\n\n```python\n# Text encoding - content stored as plain text\ntext_resource = create_ui_resource({\n    \"uri\": \"ui://text-example\",\n    \"content\": {\"type\": \"rawHtml\", \"htmlString\": \"<p>Text content</p>\"},\n    \"encoding\": \"text\"\n})\n\n# Blob encoding - content base64 encoded\nblob_resource = create_ui_resource({\n    \"uri\": \"ui://blob-example\", \n    \"content\": {\"type\": \"rawHtml\", \"htmlString\": \"<p>Blob content</p>\"},\n    \"encoding\": \"blob\"\n})\n```\n\n### UI Metadata\n\nEnhance resources with metadata for client-side handling. The SDK automatically prefixes UI-specific metadata with `mcpui.dev/ui-` to distinguish it from custom metadata.\n\n#### Preferred Frame Size\n\nSpecify preferred dimensions for UI rendering:\n\n```python\nresource = create_ui_resource({\n    \"uri\": \"ui://chart\",\n    \"content\": {\n        \"type\": \"externalUrl\",\n        \"iframeUrl\": \"https://charts.example.com/widget\"\n    },\n    \"encoding\": \"text\",\n    \"uiMetadata\": {\n        \"preferred-frame-size\": [800, 600]  # width, height in pixels or css units\n    }\n})\n```\n\n#### Initial Render Data\n\nProvide data to components at initialization:\n\n```python\nresource = create_ui_resource({\n    \"uri\": \"ui://dashboard\",\n    \"content\": {\n        \"type\": \"remoteDom\",\n        \"script\": \"\"\"\n            function Dashboard({ theme, userId }) {\n                // Component receives initial data\n                return <div>Dashboard for user {userId}</div>;\n            }\n        \"\"\",\n        \"framework\": \"react\"\n    },\n    \"encoding\": \"text\",\n    \"uiMetadata\": {\n        \"initial-render-data\": {\n            \"theme\": \"dark\",\n            \"userId\": \"123\"\n        }\n    }\n})\n```\n\n#### Multiple Metadata Fields\n\nCombine multiple metadata fields:\n\n```python\nresource = create_ui_resource({\n    \"uri\": \"ui://data-viz\",\n    \"content\": {\n        \"type\": \"rawHtml\",\n        \"htmlString\": \"<canvas id='chart'></canvas>\"\n    },\n    \"encoding\": \"text\",\n    \"uiMetadata\": {\n        \"preferred-frame-size\": [\"800px\", \"600px\"],\n        \"initial-render-data\": {\n            \"chartType\": \"bar\",\n            \"dataSet\": \"quarterly-sales\"\n        }\n    }\n})\n```\n\n#### Custom Metadata\n\nAdd custom metadata alongside UI metadata:\n\n```python\nresource = create_ui_resource({\n    \"uri\": \"ui://custom-widget\",\n    \"content\": {\n        \"type\": \"rawHtml\",\n        \"htmlString\": \"<div>Widget</div>\"\n    },\n    \"encoding\": \"text\",\n    \"uiMetadata\": {\n        \"preferred-frame-size\": [640, 480]\n    },\n    \"metadata\": {\n        \"customKey\": \"customValue\",\n        \"version\": \"1.0.0\"\n    }\n})\n\n# Result includes both prefixed UI metadata and custom metadata:\n# {\n#   \"resource\": {\n#     \"meta\": {\n#       \"mcpui.dev/ui-preferred-frame-size\": [640, 480],\n#       \"customKey\": \"customValue\",\n#       \"version\": \"1.0.0\"\n#     }\n#   }\n# }\n```\n\n### UI Actions\n\nCreate action results for user interactions:\n\n```python\nfrom mcp_ui_server import (\n    ui_action_result_tool_call,\n    ui_action_result_prompt,\n    ui_action_result_link,\n    ui_action_result_intent,\n    ui_action_result_notification\n)\n\n# Tool execution\ntool_action = ui_action_result_tool_call(\"search_database\", {\n    \"query\": \"user input\",\n    \"limit\": 10\n})\n\n# User prompt\nprompt_action = ui_action_result_prompt(\"Enter search query:\")\n\n# External link\nlink_action = ui_action_result_link(\"https://docs.example.com\")\n\n# Intent trigger\nintent_action = ui_action_result_intent(\"show_details\", {\n    \"item_id\": \"123\"\n})\n\n# Notification\nnotify_action = ui_action_result_notification(\"Search completed!\")\n```\n\n## Advanced Usage\n\n### MCP Integration\n\nConvert UI resources for MCP tool results:\n\n```python\nfrom mcp_ui_server.utils import create_mcp_tool_result_content\n\n# Create resource\nresource = create_ui_resource({...})\n\n# Convert to MCP format\nmcp_content = create_mcp_tool_result_content(resource)\n\n# Use in tool result\nreturn {\n    \"content\": [mcp_content],\n    \"isError\": False\n}\n```\n\n### HTML Enhancement\n\nAutomatically enhance HTML with communication capabilities:\n\n```python\nfrom mcp_ui_server.utils import wrap_html_with_communication\n\n# Basic HTML\nhtml = \"<div>My content</div>\"\n\n# Enhanced with MCP UI communication\nenhanced_html = wrap_html_with_communication(html)\n\n# Use in resource\nresource = create_ui_resource({\n    \"uri\": \"ui://enhanced-html\",\n    \"content\": {\"type\": \"rawHtml\", \"htmlString\": enhanced_html},\n    \"encoding\": \"text\"\n})\n```\n\n### Error Handling\n\nThe SDK provides specific exception types:\n\n```python\nfrom mcp_ui_server.exceptions import (\n    MCPUIServerError,\n    InvalidURIError,\n    InvalidContentError,\n    EncodingError\n)\n\ntry:\n    resource = create_ui_resource({\n        \"uri\": \"invalid://test\",  # Must start with ui://\n        \"content\": {\"type\": \"rawHtml\", \"htmlString\": \"<p>Test</p>\"},\n        \"encoding\": \"text\"\n    })\nexcept InvalidURIError as e:\n    print(f\"URI validation failed: {e}\")\nexcept InvalidContentError as e:\n    print(f\"Content validation failed: {e}\")\nexcept MCPUIServerError as e:\n    print(f\"General SDK error: {e}\")\n```\n\n## API Reference\n\n### Core Functions\n\n#### `create_ui_resource(options: CreateUIResourceOptions) -> UIResource`\n\nCreates a UI resource from the given options.\n\n**Parameters:**\n- `options`: Configuration dictionary with `uri`, `content`, and `encoding`\n\n**Returns:**\n- `UIResource` instance ready for use in MCP tool results\n\n#### Action Result Functions\n\n- `ui_action_result_tool_call(tool_name: str, params: dict) -> UIActionResultToolCall`\n- `ui_action_result_prompt(prompt: str) -> UIActionResultPrompt`\n- `ui_action_result_link(url: str) -> UIActionResultLink`\n- `ui_action_result_intent(intent: str, params: dict) -> UIActionResultIntent`\n- `ui_action_result_notification(message: str) -> UIActionResultNotification`\n\n### Types\n\n#### `CreateUIResourceOptions`\n\n```python\n{\n    \"uri\": str,  # Must start with \"ui://\"\n    \"content\": Union[RawHtmlPayload, ExternalUrlPayload, RemoteDomPayload],\n    \"encoding\": Literal[\"text\", \"blob\"],\n    \"uiMetadata\": Optional[dict[str, Any]],  # UI-specific metadata (auto-prefixed)\n    \"metadata\": Optional[dict[str, Any]]     # Custom metadata\n}\n```\n\n#### Content Payloads\n\n```python\n# Raw HTML\n{\n    \"type\": \"rawHtml\",\n    \"htmlString\": str\n}\n\n# External URL\n{\n    \"type\": \"externalUrl\", \n    \"iframeUrl\": str\n}\n\n# Remote DOM\n{\n    \"type\": \"remoteDom\",\n    \"script\": str,\n    \"framework\": Literal[\"react\", \"webcomponents\"]\n}\n```\n\n## Examples\n\nSee the `examples/` directory for complete usage examples:\n\n- `basic_server_usage.py`: Basic resource creation and action results\n- `advanced_features.py`: Advanced patterns and integrations\n- `mcp_tool_integration.py`: Complete MCP tool implementation\n\n## Development\n\n### Setup\n\n```bash\n# Install development dependencies\npip install -e \".[dev]\"\n\n# Run tests\npytest\n\n# Run linting\nruff check .\nblack .\n\n# Type checking\nmypy src/\n```\n\n### Project Structure\n\n```\nsrc/mcp_ui_server/\n├── __init__.py          # Main exports\n├── types.py             # Type definitions\n├── core.py              # Core functionality\n├── utils.py             # Utility functions\n└── exceptions.py        # Custom exceptions\n```\n\n## License\n\nApache 2.0\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md) for contribution guidelines."
  },
  {
    "path": "sdks/python/server/pyproject.toml",
    "content": "[project]\nname = \"mcp-ui-server\"\nversion = \"1.0.0\"\ndescription = \"mcp-ui Server SDK for Python\"\nreadme = \"README.md\"\nrequires-python = \">=3.10\"\ndependencies = [\n    \"mcp>=1.0.0\",\n    \"pydantic>=2.0.0\",\n    \"typing-extensions>=4.0.0\"\n]\nauthors = [\n    {name = \"MCP UI Contributors\"}\n]\nlicense = {text = \"Apache-2.0\"}\nclassifiers = [\n    \"Development Status :: 4 - Beta\",\n    \"Intended Audience :: Developers\",\n    \"License :: OSI Approved :: Apache Software License\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n]\n\n[project.urls]\nHomepage = \"https://mcpui.dev\"\nRepository = \"https://github.com/idosal/mcp-ui\"\nDocumentation = \"https://mcpui.dev/guide/introduction\"\n\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[tool.hatch.build.targets.wheel]\npackages = [\"src/mcp_ui_server\"]\n\n[tool.hatch.build.targets.sdist]\nexclude = [\n    \"/.gitignore\",\n    \"*.publish.attestation\",\n]\n\n[project.optional-dependencies]\ndev = [\n    \"pytest>=7.0.0\",\n    \"ruff>=0.1.0\",\n    \"pyright>=1.1.0\",\n]\n\n[tool.ruff]\nline-length = 120\ntarget-version = \"py310\"\nexclude = [\n    \"README.md\"\n]\n\n[tool.ruff.lint]\nselect = [\"C4\", \"E\", \"F\", \"I\", \"PERF\", \"UP\"]\nignore = [\"PERF203\"]\n\n[tool.ruff.lint.per-file-ignores]\n\"__init__.py\" = [\"F401\"]\n\n[tool.pyright]\ninclude = [\"src\"]\nexclude = [\"**/__pycache__\"]\npythonVersion = \"3.10\"\npythonPlatform = \"All\"\ntypeCheckingMode = \"strict\"\n\n[dependency-groups]\ndev = [\n    \"pyright>=1.1.403\",\n    \"ruff>=0.12.9\",\n    \"pytest>=7.0.0\",\n    \"pytest-snapshot>=0.9.0\",\n    \"twine>=5.0.0\",\n]\n\n[tool.pytest.ini_options]\ntestpaths = [\"tests\"]\npython_files = \"test_*.py\"\npython_classes = \"Test*\"\npython_functions = \"test_*\"\naddopts = \"-v --tb=short\"\n"
  },
  {
    "path": "sdks/python/server/release.config.js",
    "content": "module.exports = {\n  branches: [\n    'main',\n    {\n      name: 'alpha',\n      prerelease: true,\n    },\n  ],\n  repositoryUrl: 'https://github.com/idosal/mcp-ui',\n  tagFormat: 'python-server-sdk/v${version}',\n  plugins: [\n    '@semantic-release/commit-analyzer',\n    '@semantic-release/release-notes-generator',\n    [\n      '@semantic-release/changelog',\n      {\n        changelogFile: 'CHANGELOG.md',\n      },\n    ],\n    [\n      '@semantic-release/exec',\n      {\n        prepareCmd:\n          'sed -i \\'s/^version = \".*\"/version = \"${nextRelease.version}\"/\\' pyproject.toml && rm -f pyproject.toml.bak && uv sync && uv build',\n        publishCmd: 'uv run twine upload --skip-existing dist/*',\n      },\n    ],\n    '@semantic-release/github',\n    [\n      '@semantic-release/git',\n      {\n        assets: ['CHANGELOG.md', 'pyproject.toml', 'uv.lock'],\n        message: 'chore(release): ${nextRelease.version} [skip ci]\\n\\n${nextRelease.notes}',\n      },\n    ],\n  ],\n};\n"
  },
  {
    "path": "sdks/python/server/src/mcp_ui_server/__init__.py",
    "content": "\"\"\"MCP UI Server SDK for Python.\n\nThis package provides server-side functionality for creating MCP UI resources.\n\"\"\"\n\nfrom .core import (\n    UIResource,\n    create_ui_resource,\n    ui_action_result_intent,\n    ui_action_result_link,\n    ui_action_result_notification,\n    ui_action_result_prompt,\n    ui_action_result_tool_call,\n)\nfrom .types import (\n    RESOURCE_MIME_TYPE,\n    URI,\n    CreateUIResourceOptions,\n    MimeType,\n    ResourceContentPayload,\n    UIActionResult,\n    UIActionResultIntent,\n    UIActionResultLink,\n    UIActionResultNotification,\n    UIActionResultPrompt,\n    UIActionResultToolCall,\n    UIActionType,\n    UIMetadataKey,\n)\n\n__version__ = \"5.2.0\"\n\n__all__ = [\n    \"URI\",\n    \"MimeType\",\n    \"RESOURCE_MIME_TYPE\",\n    \"ResourceContentPayload\",\n    \"CreateUIResourceOptions\",\n    \"UIActionType\",\n    \"UIActionResult\",\n    \"UIActionResultToolCall\",\n    \"UIActionResultPrompt\",\n    \"UIActionResultLink\",\n    \"UIActionResultIntent\",\n    \"UIActionResultNotification\",\n    \"UIMetadataKey\",\n    \"UIResource\",\n    \"create_ui_resource\",\n    \"ui_action_result_tool_call\",\n    \"ui_action_result_prompt\",\n    \"ui_action_result_link\",\n    \"ui_action_result_intent\",\n    \"ui_action_result_notification\",\n]"
  },
  {
    "path": "sdks/python/server/src/mcp_ui_server/core.py",
    "content": "\"\"\"Core functionality for MCP UI Server SDK.\"\"\"\n\nimport base64\nfrom typing import Any\n\nfrom mcp.types import BlobResourceContents, EmbeddedResource, TextResourceContents\nfrom pydantic import AnyUrl\n\nfrom .exceptions import InvalidContentError, InvalidURIError\nfrom .types import (\n    RESOURCE_MIME_TYPE,\n    UI_METADATA_PREFIX,\n    CreateUIResourceOptions,\n    ExternalUrlPayload,\n    RawHtmlPayload,\n    UIActionResultIntent,\n    UIActionResultLink,\n    UIActionResultNotification,\n    UIActionResultPrompt,\n    UIActionResultToolCall,\n)\n\n\nclass UIResource(EmbeddedResource):\n    \"\"\"Represents a UI resource that can be included in tool results.\"\"\"\n\n    def __init__(self, resource: TextResourceContents | BlobResourceContents, **kwargs: Any):\n        # Initialize with resource content\n        super().__init__(\n            type=\"resource\",\n            resource=resource,\n            **kwargs\n        )\n\n\ndef _get_additional_resource_props(options: CreateUIResourceOptions) -> dict[str, Any]:\n    \"\"\"Get additional resource properties including metadata.\n\n    Prefixes UI-specific metadata with the UI metadata prefix to be recognized by the client.\n\n    Args:\n        options: The UI resource options\n\n    Returns:\n        Dictionary of additional properties to merge into the resource\n    \"\"\"\n    additional_props: dict[str, Any] = {}\n\n    # Prefix ui specific metadata with the prefix to be recognized by the client\n    if options.uiMetadata or options.metadata:\n        ui_prefixed_metadata: dict[str, Any] = {}\n\n        if options.uiMetadata:\n            for key, value in options.uiMetadata.items():\n                ui_prefixed_metadata[f\"{UI_METADATA_PREFIX}{key}\"] = value\n\n        # Allow user defined metadata to override ui metadata\n        _meta: dict[str, Any] = {\n            **ui_prefixed_metadata,\n            **(options.metadata or {}),\n        }\n\n        if _meta:\n            additional_props[\"_meta\"] = _meta\n\n    return additional_props\n\n\ndef create_ui_resource(options_dict: dict[str, Any]) -> UIResource:\n    \"\"\"Create a UIResource.\n\n    This is the object that should be included in the 'content' array of a toolResult.\n\n    Args:\n        options_dict: Configuration dictionary for the interactive resource. Keys:\n            - uri (str): Resource identifier starting with 'ui://'\n            - content (dict): Content payload (type: rawHtml or externalUrl)\n            - encoding (str): 'text' or 'blob'\n            - uiMetadata (dict, optional): UI metadata. Use UIMetadataKey constants:\n                * UIMetadataKey.PREFERRED_FRAME_SIZE: list[str, str] - CSS dimensions like [\"800px\", \"600px\"]\n                * UIMetadataKey.INITIAL_RENDER_DATA: dict - Initial data for the UI\n            - metadata (dict, optional): Custom metadata (not prefixed)\n\n    Returns:\n        A UIResource instance ready to be included in tool results\n\n    Raises:\n        InvalidURIError: If the URI doesn't start with 'ui://'\n        InvalidContentError: If content validation fails\n        MCPUIServerError: For other errors\n\n    Example:\n        >>> from mcp_ui_server import create_ui_resource, UIMetadataKey\n        >>> resource = create_ui_resource({\n        ...     \"uri\": \"ui://my-widget\",\n        ...     \"content\": {\"type\": \"rawHtml\", \"htmlString\": \"<h1>Hello</h1>\"},\n        ...     \"encoding\": \"text\",\n        ...     \"uiMetadata\": {\n        ...         UIMetadataKey.PREFERRED_FRAME_SIZE: [\"800px\", \"600px\"]\n        ...     }\n        ... })\n    \"\"\"\n    options = CreateUIResourceOptions.model_validate(options_dict)\n    # Validate URI\n    if not options.uri.startswith(\"ui://\"):\n        raise InvalidURIError(f\"URI must start with 'ui://' but got: {options.uri}\")\n\n    content = options.content\n    content_type = content.type\n\n    # Determine content string and MIME type based on content type\n    if content_type == \"rawHtml\":\n        if not isinstance(content, RawHtmlPayload):\n            raise InvalidContentError(\"Content must be RawHtmlPayload when type is 'rawHtml'\")\n        htmlString = content.htmlString\n        if not htmlString:\n            raise InvalidContentError(\n                \"htmlString must be provided as a non-empty string when content.type is 'rawHtml'\"\n            )\n        actual_content_string = htmlString\n        mime_type = RESOURCE_MIME_TYPE\n\n    elif content_type == \"externalUrl\":\n        if not isinstance(content, ExternalUrlPayload):\n            raise InvalidContentError(\"Content must be ExternalUrlPayload when type is 'externalUrl'\")\n        iframe_url = content.iframeUrl\n        if not iframe_url:\n            raise InvalidContentError(\n                \"content.iframeUrl must be provided as a non-empty string when content.type is 'externalUrl'\"\n            )\n        actual_content_string = iframe_url\n        mime_type = RESOURCE_MIME_TYPE\n\n    else:\n        # This should be prevented by TypeScript/mypy, but handle gracefully\n        raise InvalidContentError(f\"Invalid content.type specified: {content_type}\")\n\n    # Create resource based on encoding type\n    encoding = options.encoding\n\n    # Get additional properties including metadata\n    additional_props = _get_additional_resource_props(options)\n\n    if encoding == \"text\":\n        resource: TextResourceContents | BlobResourceContents = TextResourceContents(\n            uri=AnyUrl(options.uri),\n            mimeType=mime_type,\n            text=actual_content_string,\n            **additional_props,\n        )\n    elif encoding == \"blob\":\n        resource = BlobResourceContents(\n            uri=AnyUrl(options.uri),\n            mimeType=mime_type,\n            blob=base64.b64encode(actual_content_string.encode('utf-8')).decode('ascii'),\n            **additional_props,\n        )\n    else:\n        raise InvalidContentError(f\"Invalid encoding type: {encoding}\")\n\n    return UIResource(resource=resource)\n\n\n\n# UI Action Result Helper Functions\n\ndef ui_action_result_tool_call(\n    tool_name: str, params: dict[str, Any]\n) -> UIActionResultToolCall:\n    \"\"\"Create a tool call UI action result.\n\n    Args:\n        tool_name: Name of the tool to call\n        params: Parameters for the tool call\n\n    Returns:\n        UIActionResultToolCall instance\n    \"\"\"\n    return UIActionResultToolCall(\n        type=\"tool\",\n        payload=UIActionResultToolCall.ToolCallPayload(\n            toolName=tool_name,\n            params=params,\n        ),\n    )\n\n\ndef ui_action_result_prompt(prompt: str) -> UIActionResultPrompt:\n    \"\"\"Create a prompt UI action result.\n\n    Args:\n        prompt: The prompt message\n\n    Returns:\n        UIActionResultPrompt instance\n    \"\"\"\n    return UIActionResultPrompt(\n        type=\"prompt\",\n        payload=UIActionResultPrompt.PromptPayload(\n            prompt=prompt,\n        ),\n    )\n\n\ndef ui_action_result_link(url: str) -> UIActionResultLink:\n    \"\"\"Create a link UI action result.\n\n    Args:\n        url: The URL to link to\n\n    Returns:\n        UIActionResultLink instance\n    \"\"\"\n    return UIActionResultLink(\n        type=\"link\",\n        payload=UIActionResultLink.LinkPayload(\n            url=url,\n        ),\n    )\n\n\ndef ui_action_result_intent(\n    intent: str, params: dict[str, Any]\n) -> UIActionResultIntent:\n    \"\"\"Create an intent UI action result.\n\n    Args:\n        intent: The intent identifier\n        params: Parameters for the intent\n\n    Returns:\n        UIActionResultIntent instance\n    \"\"\"\n    return UIActionResultIntent(\n        type=\"intent\",\n        payload=UIActionResultIntent.IntentPayload(\n            intent=intent,\n            params=params,\n        ),\n    )\n\n\ndef ui_action_result_notification(message: str) -> UIActionResultNotification:\n    \"\"\"Create a notification UI action result.\n\n    Args:\n        message: The notification message\n\n    Returns:\n        UIActionResultNotification instance\n    \"\"\"\n    return UIActionResultNotification(\n        type=\"notify\",\n        payload=UIActionResultNotification.NotificationPayload(\n            message=message,\n        ),\n    )\n"
  },
  {
    "path": "sdks/python/server/src/mcp_ui_server/exceptions.py",
    "content": "\"\"\"Custom exceptions for MCP UI Server SDK.\"\"\"\n\n\nclass MCPUIServerError(Exception):\n    \"\"\"Base exception for MCP UI Server SDK errors.\"\"\"\n    pass\n\n\nclass InvalidURIError(MCPUIServerError):\n    \"\"\"Error raised when URI validation fails.\"\"\"\n    pass\n\n\nclass InvalidContentError(MCPUIServerError):\n    \"\"\"Error raised when content validation fails.\"\"\"\n    pass\n\n\nclass EncodingError(MCPUIServerError):\n    \"\"\"Error raised when encoding operations fail.\"\"\"\n    pass"
  },
  {
    "path": "sdks/python/server/src/mcp_ui_server/types.py",
    "content": "\"\"\"Type definitions for MCP UI Server SDK.\"\"\"\n\nfrom typing import Any, Literal\n\nfrom pydantic import BaseModel\n\n# Primary identifier for the resource. Starts with ui://\nURI = str  # In TypeScript: `ui://${string}`, but Python doesn't have template literal types\n\n# MIME type for MCP Apps resources (used for both HTML and URL content)\nRESOURCE_MIME_TYPE = \"text/html;profile=mcp-app\"\n\nMimeType = Literal[\"text/html;profile=mcp-app\"]\n\nUIActionType = Literal[\"tool\", \"prompt\", \"link\", \"intent\", \"notify\"]\n\n\nclass RawHtmlPayload(BaseModel):\n    \"\"\"Raw HTML content payload.\"\"\"\n    type: Literal[\"rawHtml\"]\n    htmlString: str\n\n\nclass ExternalUrlPayload(BaseModel):\n    \"\"\"External URL content payload.\"\"\"\n    type: Literal[\"externalUrl\"]\n    iframeUrl: str\n\n\nResourceContentPayload = RawHtmlPayload | ExternalUrlPayload\n\n\n# UI Metadata constants\nUI_METADATA_PREFIX = \"mcpui.dev/ui-\"\n\n\nclass UIMetadataKey:\n    \"\"\"Keys for UI metadata with their expected value types.\n\n    These constants should be used as keys in the uiMetadata dictionary to avoid typos\n    and improve code maintainability.\n\n    Attributes:\n        PREFERRED_FRAME_SIZE: Key for specifying preferred iframe dimensions.\n            - Expected value type: list[str, str] or tuple[str, str]\n            - Format: [width, height] as CSS dimension strings\n            - Examples:\n                * [\"800px\", \"600px\"] - Fixed pixel dimensions\n                * [\"100%\", \"50vh\"] - Responsive with percentage and viewport height\n                * [\"50rem\", \"80%\"] - Relative and percentage units\n            - Important: Must be strings with CSS units (px, %, vh, vw, rem, em, etc.)\n            - Applied directly to iframe's CSS width and height properties\n\n        INITIAL_RENDER_DATA: Key for passing initial data to the UI component.\n            - Expected value type: dict[str, Any]\n            - Format: Any JSON-serializable dictionary\n            - Examples:\n                * {\"user\": {\"id\": \"123\", \"name\": \"John\"}}\n                * {\"config\": {\"theme\": \"dark\", \"language\": \"en\"}}\n            - Data is passed to the iframe on initial render\n\n    Example usage:\n        ```python\n        from mcp_ui_server import create_ui_resource, UIMetadataKey\n\n        ui_resource = create_ui_resource({\n            \"uri\": \"ui://my-component\",\n            \"content\": {\"type\": \"rawHtml\", \"htmlString\": \"<h1>Hello</h1>\"},\n            \"encoding\": \"text\",\n            \"uiMetadata\": {\n                UIMetadataKey.PREFERRED_FRAME_SIZE: [\"800px\", \"600px\"],\n                UIMetadataKey.INITIAL_RENDER_DATA: {\"user\": {\"id\": \"123\"}}\n            }\n        })\n        ```\n    \"\"\"\n    PREFERRED_FRAME_SIZE = \"preferred-frame-size\"\n    INITIAL_RENDER_DATA = \"initial-render-data\"\n\n\nclass CreateUIResourceOptions(BaseModel):\n    \"\"\"Options for creating a UI resource.\n\n    Attributes:\n        uri: The resource identifier. Must start with 'ui://'\n        content: The resource content payload (rawHtml or externalUrl)\n        encoding: Whether to encode as 'text' or 'blob' (base64)\n        uiMetadata: UI-specific metadata that will be prefixed with 'mcpui.dev/ui-'\n            Use UIMetadataKey constants for type-safe keys:\n            - UIMetadataKey.PREFERRED_FRAME_SIZE: list[str, str] - CSS dimensions\n            - UIMetadataKey.INITIAL_RENDER_DATA: dict[str, Any] - Initial data\n        metadata: Custom metadata (not prefixed). Merged with prefixed uiMetadata.\n            Example: {\"custom.author\": \"Server Name\", \"custom.version\": \"1.0.0\"}\n    \"\"\"\n    uri: URI\n    content: ResourceContentPayload\n    encoding: Literal[\"text\", \"blob\"]\n    uiMetadata: dict[str, Any] | None = None\n    metadata: dict[str, Any] | None = None\n\n\nclass GenericActionMessage(BaseModel):\n    \"\"\"Base message structure for UI actions.\"\"\"\n    messageId: str | None = None\n\n\nclass UIActionResultToolCall(GenericActionMessage):\n    \"\"\"Tool call action result.\"\"\"\n\n    class ToolCallPayload(BaseModel):\n        \"\"\"Payload for tool call actions.\"\"\"\n        toolName: str\n\n        params: dict[str, Any]\n\n    type: Literal[\"tool\"]\n    payload: ToolCallPayload\n\n\nclass UIActionResultPrompt(GenericActionMessage):\n    \"\"\"Prompt action result.\"\"\"\n\n    class PromptPayload(BaseModel):\n        \"\"\"Payload for prompt actions.\"\"\"\n        prompt: str\n\n    type: Literal[\"prompt\"]\n    payload: PromptPayload\n\n\nclass UIActionResultLink(GenericActionMessage):\n    \"\"\"Link action result.\"\"\"\n\n    class LinkPayload(BaseModel):\n        \"\"\"Payload for link actions.\"\"\"\n        url: str\n\n    type: Literal[\"link\"]\n    payload: LinkPayload\n\n\nclass UIActionResultIntent(GenericActionMessage):\n    \"\"\"Intent action result.\"\"\"\n\n    class IntentPayload(BaseModel):\n        \"\"\"Payload for intent actions.\"\"\"\n        intent: str\n        params: dict[str, Any]\n\n    type: Literal[\"intent\"]\n    payload: IntentPayload\n\n\nclass UIActionResultNotification(GenericActionMessage):\n    \"\"\"Notification action result.\"\"\"\n\n    class NotificationPayload(BaseModel):\n        \"\"\"Payload for notification actions.\"\"\"\n        message: str\n\n    type: Literal[\"notify\"]\n    payload: NotificationPayload\n\n\nUIActionResult = (\n    UIActionResultToolCall\n    | UIActionResultPrompt\n    | UIActionResultLink\n    | UIActionResultIntent\n    | UIActionResultNotification\n)\n"
  },
  {
    "path": "sdks/python/server/src/mcp_ui_server/utils.py",
    "content": "\"\"\"Utility functions for MCP UI Server SDK.\"\"\"\n\nimport json\nfrom typing import TYPE_CHECKING, Any, cast\n\nfrom .types import UIActionResult\n\nif TYPE_CHECKING:\n    from .core import UIResource\n\n\ndef validate_uri(uri: str) -> bool:\n    \"\"\"Validate that a URI starts with 'ui://'.\n    \n    Args:\n        uri: The URI to validate\n        \n    Returns:\n        True if valid, False otherwise\n    \"\"\"\n    return uri.startswith(\"ui://\")\n\n\ndef serialize_ui_resource(ui_resource: \"UIResource\") -> dict[str, Any]:\n    \"\"\"Serialize a UIResource to a dictionary.\n    \n    Args:\n        ui_resource: The UIResource instance\n        \n    Returns:\n        Dictionary representation suitable for JSON serialization\n    \"\"\"\n    return ui_resource.model_dump()\n\n\ndef serialize_ui_action_result(action_result: UIActionResult) -> str:\n    \"\"\"Serialize a UI action result to JSON.\n    \n    Args:\n        action_result: The action result to serialize\n        \n    Returns:\n        JSON string representation\n    \"\"\"\n    return action_result.model_dump_json()\n\n\ndef deserialize_ui_action_result(json_str: str) -> UIActionResult:\n    \"\"\"Deserialize a UI action result from JSON.\n    \n    Args:\n        json_str: JSON string to deserialize\n        \n    Returns:\n        UIActionResult instance\n        \n    Raises:\n        ValueError: If JSON is invalid or doesn't match expected format\n    \"\"\"\n    from .types import (\n        UIActionResultIntent,\n        UIActionResultLink,\n        UIActionResultNotification,\n        UIActionResultPrompt,\n        UIActionResultToolCall,\n    )\n    \n    try:\n        data = json.loads(json_str)\n        \n        # Basic validation\n        if not isinstance(data, dict) or \"type\" not in data or \"payload\" not in data:\n            raise ValueError(\"Invalid UI action result format\")\n        \n        action_type = cast(str, data[\"type\"])\n        \n        # Deserialize based on type\n        if action_type == \"tool\":\n            return UIActionResultToolCall.model_validate(data)\n        elif action_type == \"prompt\":\n            return UIActionResultPrompt.model_validate(data)\n        elif action_type == \"link\":\n            return UIActionResultLink.model_validate(data)\n        elif action_type == \"intent\":\n            return UIActionResultIntent.model_validate(data)\n        elif action_type == \"notify\":\n            return UIActionResultNotification.model_validate(data)\n        else:\n            raise ValueError(f\"Invalid action type: {action_type}\")\n        \n    except json.JSONDecodeError as e:\n        raise ValueError(f\"Invalid JSON: {e}\") from e\n\n\nclass ReservedUrlParams:\n    \"\"\"Reserved URL parameters for MCP UI.\"\"\"\n    WAIT_FOR_RENDER_DATA = \"waitForRenderData\"\n\n\nclass InternalMessageType:\n    \"\"\"Internal message types for MCP UI communication.\"\"\"\n    UI_MESSAGE_RECEIVED = \"ui-message-received\"\n    UI_MESSAGE_RESPONSE = \"ui-message-response\"\n    UI_SIZE_CHANGE = \"ui-size-change\"\n    UI_LIFECYCLE_IFRAME_READY = \"ui-lifecycle-iframe-ready\"\n    UI_LIFECYCLE_IFRAME_RENDER_DATA = \"ui-lifecycle-iframe-render-data\"\n\n\ndef create_iframe_communication_script() -> str:\n    \"\"\"Create JavaScript code for iframe communication.\n    \n    This generates the JavaScript needed for UI resources to communicate\n    with the parent frame.\n    \n    Returns:\n        JavaScript code as a string\n    \"\"\"\n    return \"\"\"\n// MCP UI communication script\n(function() {\n    window.mcpUI = {\n        postMessage: function(data) {\n            if (window.parent) {\n                window.parent.postMessage(data, '*');\n            }\n        },\n        \n        onMessage: function(callback) {\n            window.addEventListener('message', function(event) {\n                callback(event.data);\n            });\n        }\n    };\n})();\n\"\"\"\n\ndef wrap_html_with_communication(html_content: str, include_script: bool = True) -> str:\n    \"\"\"Wrap HTML content with MCP UI communication capabilities.\n    \n    Args:\n        html_content: The HTML content to wrap\n        include_script: Whether to include the communication script\n        \n    Returns:\n        Enhanced HTML with communication capabilities\n    \"\"\"\n    if not include_script:\n        return html_content\n    \n    script = create_iframe_communication_script()\n    \n    # If HTML already has a head tag, insert script there\n    if \"<head>\" in html_content:\n        return html_content.replace(\n            \"<head>\",\n            f\"<head><script>{script}</script>\"\n        )\n    \n    # If HTML has html tag but no head, add head\n    if \"<html>\" in html_content:\n        return html_content.replace(\n            \"<html>\",\n            f\"<html><head><script>{script}</script></head>\"\n        )\n    \n    # Otherwise, wrap the entire content\n    return f\"\"\"<!DOCTYPE html>\n<html>\n<head>\n    <script>{script}</script>\n</head>\n<body>\n{html_content}\n</body>\n</html>\"\"\""
  },
  {
    "path": "sdks/python/server/tests/__init__.py",
    "content": "\"\"\"Test suite for MCP UI Server SDK.\"\"\""
  },
  {
    "path": "sdks/python/server/tests/test_create_ui_resource.py",
    "content": "\"\"\"Tests for create_ui_resource functionality.\"\"\"\n\nimport base64\n\nimport pytest\nfrom pydantic import AnyUrl, ValidationError\n\nfrom mcp_ui_server import create_ui_resource\nfrom mcp_ui_server.exceptions import InvalidContentError, InvalidURIError\n\n\n@pytest.fixture\ndef raw_html_text_options():\n    \"\"\"Fixture for raw HTML text-based options.\"\"\"\n    return {\n        \"uri\": \"ui://test-html\",\n        \"content\": {\"type\": \"rawHtml\", \"htmlString\": \"<p>Test</p>\"},\n        \"encoding\": \"text\",\n    }\n\n\n@pytest.fixture\ndef raw_html_blob_options():\n    \"\"\"Fixture for raw HTML blob-based options.\"\"\"\n    return {\n        \"uri\": \"ui://test-html-blob\",\n        \"content\": {\"type\": \"rawHtml\", \"htmlString\": \"<h1>Blob</h1>\"},\n        \"encoding\": \"blob\",\n    }\n\n\n@pytest.fixture\ndef external_url_text_options():\n    \"\"\"Fixture for external URL text-based options.\"\"\"\n    return {\n        \"uri\": \"ui://test-url\",\n        \"content\": {\n            \"type\": \"externalUrl\",\n            \"iframeUrl\": \"https://example.com\",\n        },\n        \"encoding\": \"text\",\n    }\n\n\n@pytest.fixture\ndef external_url_blob_options():\n    \"\"\"Fixture for external URL blob-based options.\"\"\"\n    return {\n        \"uri\": \"ui://test-url-blob\",\n        \"content\": {\n            \"type\": \"externalUrl\",\n            \"iframeUrl\": \"https://example.com/blob\",\n        },\n        \"encoding\": \"blob\",\n    }\n\n\nclass TestCreateUIResource:\n    \"\"\"Test suite for create_ui_resource function.\"\"\"\n\n    def test_create_text_based_raw_html_resource(self, raw_html_text_options):\n        \"\"\"Test creating a text-based raw HTML resource.\"\"\"\n        resource = create_ui_resource(raw_html_text_options)\n        expected = {\n            \"meta\": None,\n            \"annotations\": None,\n            \"type\": \"resource\",\n            \"resource\": {\n                \"meta\": None,\n                \"uri\": AnyUrl(\"ui://test-html\"),\n                \"mimeType\": \"text/html;profile=mcp-app\",\n                \"text\": \"<p>Test</p>\",\n            }\n        }\n        assert resource.model_dump() == expected\n\n    def test_create_blob_based_raw_html_resource(self, raw_html_blob_options):\n        \"\"\"Test creating a blob-based raw HTML resource.\"\"\"\n        resource = create_ui_resource(raw_html_blob_options)\n        expected = {\n            \"meta\": None,\n            \"annotations\": None,\n            \"type\": \"resource\",\n            \"resource\": {\n                \"meta\": None,\n                \"uri\": AnyUrl(\"ui://test-html-blob\"),\n                \"mimeType\": \"text/html;profile=mcp-app\",\n                \"blob\": base64.b64encode(b\"<h1>Blob</h1>\").decode(\"ascii\"),\n            }\n        }\n        assert resource.model_dump() == expected\n\n    def test_create_text_based_external_url_resource(self, external_url_text_options):\n        \"\"\"Test creating a text-based external URL resource.\"\"\"\n        resource = create_ui_resource(external_url_text_options)\n        expected = {\n            \"meta\": None,\n            \"annotations\": None,\n            \"type\": \"resource\",\n            \"resource\": {\n                \"meta\": None,\n                \"uri\": AnyUrl(\"ui://test-url\"),\n                \"mimeType\": \"text/html;profile=mcp-app\",\n                \"text\": \"https://example.com\",\n            }\n        }\n        assert resource.model_dump() == expected\n\n    def test_create_blob_based_external_url_resource(self, external_url_blob_options):\n        \"\"\"Test creating a blob-based external URL resource.\"\"\"\n        resource = create_ui_resource(external_url_blob_options)\n        expected = {\n            \"meta\": None,\n            \"annotations\": None,\n            \"type\": \"resource\",\n            \"resource\": {\n                \"meta\": None,\n                \"uri\": AnyUrl(\"ui://test-url-blob\"),\n                \"mimeType\": \"text/html;profile=mcp-app\",\n                \"blob\": base64.b64encode(b\"https://example.com/blob\").decode(\"ascii\"),\n            }\n        }\n        assert resource.model_dump() == expected\n\n\nclass TestCreateUIResourceValidationErrors:\n    \"\"\"Test suite for create_ui_resource validation errors.\"\"\"\n\n    def test_invalid_uri_prefix_with_raw_html(self):\n        \"\"\"Test error for invalid URI prefix with rawHtml.\"\"\"\n        options = {\n            \"uri\": \"invalid://test-html\",\n            \"content\": {\"type\": \"rawHtml\", \"htmlString\": \"<p>Test</p>\"},\n            \"encoding\": \"text\",\n        }\n        with pytest.raises(InvalidURIError, match=\"URI must start with 'ui://'\"):\n            create_ui_resource(options)\n\n    def test_invalid_uri_prefix_with_external_url(self):\n        \"\"\"Test error for invalid URI prefix with externalUrl.\"\"\"\n        options = {\n            \"uri\": \"invalid://test-url\",\n            \"content\": {\n                \"type\": \"externalUrl\",\n                \"iframeUrl\": \"https://example.com\",\n            },\n            \"encoding\": \"text\",\n        }\n        with pytest.raises(InvalidURIError, match=\"URI must start with 'ui://'\"):\n            create_ui_resource(options)\n\n    def test_empty_html_string_error(self):\n        \"\"\"Test error when htmlString is empty for rawHtml.\"\"\"\n        options = {\n            \"uri\": \"ui://test\",\n            \"content\": {\"type\": \"rawHtml\", \"htmlString\": \"\"},\n            \"encoding\": \"text\",\n        }\n        with pytest.raises(InvalidContentError, match=\"htmlString must be provided as a non-empty string\"):\n            create_ui_resource(options)\n\n    def test_empty_iframe_url_error(self):\n        \"\"\"Test error when iframeUrl is empty for externalUrl.\"\"\"\n        options = {\n            \"uri\": \"ui://test\",\n            \"content\": {\"type\": \"externalUrl\", \"iframeUrl\": \"\"},\n            \"encoding\": \"text\",\n        }\n        with pytest.raises(InvalidContentError, match=\"content.iframeUrl must be provided as a non-empty string\"):\n            create_ui_resource(options)\n\n    def test_invalid_encoding_error(self):\n        \"\"\"Test error when encoding is invalid.\"\"\"\n        options = {\n            \"uri\": \"ui://test\",\n            \"content\": {\"type\": \"rawHtml\", \"htmlString\": \"<p>Test</p>\"},\n            \"encoding\": \"base64\",\n        }\n        with pytest.raises(ValidationError, match=\"Input should be 'text' or 'blob'\"):\n            create_ui_resource(options)\n"
  },
  {
    "path": "sdks/python/server/tests/test_metadata.py",
    "content": "\"\"\"Tests for UI metadata functionality.\"\"\"\n\nimport pytest\n\nfrom mcp_ui_server import create_ui_resource\nfrom mcp_ui_server.types import UI_METADATA_PREFIX\n\n\n@pytest.fixture\ndef basic_raw_html_options():\n    \"\"\"Fixture for basic raw HTML options.\"\"\"\n    return {\n        \"uri\": \"ui://test-html\",\n        \"content\": {\"type\": \"rawHtml\", \"htmlString\": \"<p>Test</p>\"},\n        \"encoding\": \"text\",\n    }\n\n\nclass TestUIMetadata:\n    \"\"\"Test suite for UI metadata functionality.\"\"\"\n\n    def test_create_resource_with_ui_metadata(self, basic_raw_html_options):\n        \"\"\"Test creating a resource with uiMetadata.\"\"\"\n        options = {\n            **basic_raw_html_options,\n            \"uiMetadata\": {\n                \"preferred-frame-size\": [800, 600],\n            }\n        }\n        resource = create_ui_resource(options)\n\n        result = resource.model_dump()\n\n        # Check that metadata is properly prefixed and included (meta field, serializes to _meta with by_alias=True)\n        assert result[\"resource\"][\"meta\"] is not None\n        assert f\"{UI_METADATA_PREFIX}preferred-frame-size\" in result[\"resource\"][\"meta\"]\n        assert result[\"resource\"][\"meta\"][f\"{UI_METADATA_PREFIX}preferred-frame-size\"] == [800, 600]\n\n        # Also verify by_alias=True serialization produces _meta\n        result_with_alias = resource.model_dump(by_alias=True)\n        assert \"_meta\" in result_with_alias[\"resource\"]\n        assert result_with_alias[\"resource\"][\"_meta\"][f\"{UI_METADATA_PREFIX}preferred-frame-size\"] == [800, 600]\n\n    def test_create_resource_with_multiple_ui_metadata_fields(self, basic_raw_html_options):\n        \"\"\"Test creating a resource with multiple uiMetadata fields.\"\"\"\n        options = {\n            **basic_raw_html_options,\n            \"uiMetadata\": {\n                \"preferred-frame-size\": [\"800px\", \"600px\"],\n                \"initial-render-data\": {\n                    \"theme\": \"dark\",\n                    \"chartType\": \"bar\",\n                }\n            }\n        }\n        resource = create_ui_resource(options)\n\n        result = resource.model_dump()\n\n        # Check that all metadata fields are properly prefixed and included\n        assert result[\"resource\"][\"meta\"] is not None\n        assert f\"{UI_METADATA_PREFIX}preferred-frame-size\" in result[\"resource\"][\"meta\"]\n        assert result[\"resource\"][\"meta\"][f\"{UI_METADATA_PREFIX}preferred-frame-size\"] == [\"800px\", \"600px\"]\n        assert f\"{UI_METADATA_PREFIX}initial-render-data\" in result[\"resource\"][\"meta\"]\n        assert result[\"resource\"][\"meta\"][f\"{UI_METADATA_PREFIX}initial-render-data\"] == {\n            \"theme\": \"dark\",\n            \"chartType\": \"bar\",\n        }\n\n    def test_create_resource_with_custom_metadata(self, basic_raw_html_options):\n        \"\"\"Test creating a resource with custom metadata (non-UI).\"\"\"\n        options = {\n            **basic_raw_html_options,\n            \"metadata\": {\n                \"customKey\": \"customValue\",\n                \"anotherKey\": 123,\n            }\n        }\n        resource = create_ui_resource(options)\n\n        result = resource.model_dump()\n\n        # Check that custom metadata is included without prefix\n        assert result[\"resource\"][\"meta\"] is not None\n        assert result[\"resource\"][\"meta\"][\"customKey\"] == \"customValue\"\n        assert result[\"resource\"][\"meta\"][\"anotherKey\"] == 123\n\n    def test_create_resource_with_both_ui_and_custom_metadata(self, basic_raw_html_options):\n        \"\"\"Test creating a resource with both uiMetadata and custom metadata.\"\"\"\n        options = {\n            **basic_raw_html_options,\n            \"uiMetadata\": {\n                \"preferred-frame-size\": [800, 600],\n            },\n            \"metadata\": {\n                \"customKey\": \"customValue\",\n            }\n        }\n        resource = create_ui_resource(options)\n\n        result = resource.model_dump()\n\n        # Check that both types of metadata are included\n        assert result[\"resource\"][\"meta\"] is not None\n        assert f\"{UI_METADATA_PREFIX}preferred-frame-size\" in result[\"resource\"][\"meta\"]\n        assert result[\"resource\"][\"meta\"][f\"{UI_METADATA_PREFIX}preferred-frame-size\"] == [800, 600]\n        assert result[\"resource\"][\"meta\"][\"customKey\"] == \"customValue\"\n\n    def test_metadata_override_behavior(self, basic_raw_html_options):\n        \"\"\"Test that custom metadata can override ui metadata if keys conflict.\"\"\"\n        options = {\n            **basic_raw_html_options,\n            \"uiMetadata\": {\n                \"preferred-frame-size\": [800, 600],\n            },\n            \"metadata\": {\n                f\"{UI_METADATA_PREFIX}preferred-frame-size\": [1024, 768],\n            }\n        }\n        resource = create_ui_resource(options)\n\n        result = resource.model_dump()\n\n        # Custom metadata should override UI metadata\n        assert result[\"resource\"][\"meta\"] is not None\n        assert result[\"resource\"][\"meta\"][f\"{UI_METADATA_PREFIX}preferred-frame-size\"] == [1024, 768]\n\n    def test_create_resource_without_metadata(self, basic_raw_html_options):\n        \"\"\"Test creating a resource without any metadata.\"\"\"\n        resource = create_ui_resource(basic_raw_html_options)\n\n        result = resource.model_dump()\n\n        # No metadata should be present\n        assert result[\"resource\"][\"meta\"] is None\n\n    def test_metadata_with_external_url_content(self):\n        \"\"\"Test metadata with external URL content type.\"\"\"\n        options = {\n            \"uri\": \"ui://test-url\",\n            \"content\": {\n                \"type\": \"externalUrl\",\n                \"iframeUrl\": \"https://example.com\",\n            },\n            \"encoding\": \"text\",\n            \"uiMetadata\": {\n                \"preferred-frame-size\": [\"100%\", \"500px\"],\n            }\n        }\n        resource = create_ui_resource(options)\n\n        result = resource.model_dump()\n\n        assert result[\"resource\"][\"meta\"] is not None\n        assert f\"{UI_METADATA_PREFIX}preferred-frame-size\" in result[\"resource\"][\"meta\"]\n        assert result[\"resource\"][\"meta\"][f\"{UI_METADATA_PREFIX}preferred-frame-size\"] == [\"100%\", \"500px\"]\n\n    def test_metadata_with_blob_encoding(self):\n        \"\"\"Test metadata with blob encoding.\"\"\"\n        options = {\n            \"uri\": \"ui://test-blob\",\n            \"content\": {\"type\": \"rawHtml\", \"htmlString\": \"<h1>Blob</h1>\"},\n            \"encoding\": \"blob\",\n            \"uiMetadata\": {\n                \"preferred-frame-size\": [640, 480],\n            }\n        }\n        resource = create_ui_resource(options)\n\n        result = resource.model_dump()\n\n        # Verify metadata is present with blob encoding\n        assert result[\"resource\"][\"meta\"] is not None\n        assert f\"{UI_METADATA_PREFIX}preferred-frame-size\" in result[\"resource\"][\"meta\"]\n        assert result[\"resource\"][\"meta\"][f\"{UI_METADATA_PREFIX}preferred-frame-size\"] == [640, 480]\n        # Verify blob is also present\n        assert \"blob\" in result[\"resource\"]\n\n    def test_empty_ui_metadata_dict(self, basic_raw_html_options):\n        \"\"\"Test creating a resource with empty uiMetadata dict.\"\"\"\n        options = {\n            **basic_raw_html_options,\n            \"uiMetadata\": {}\n        }\n        resource = create_ui_resource(options)\n\n        result = resource.model_dump()\n\n        # Empty metadata dict should not create meta field\n        assert result[\"resource\"][\"meta\"] is None\n\n    def test_empty_custom_metadata_dict(self, basic_raw_html_options):\n        \"\"\"Test creating a resource with empty custom metadata dict.\"\"\"\n        options = {\n            **basic_raw_html_options,\n            \"metadata\": {}\n        }\n        resource = create_ui_resource(options)\n\n        result = resource.model_dump()\n\n        # Empty metadata dict should not create meta field\n        assert result[\"resource\"][\"meta\"] is None\n"
  },
  {
    "path": "sdks/python/server/tests/test_ui_action_results.py",
    "content": "\"\"\"Tests for UI action result helper functions.\"\"\"\n\n\nfrom mcp_ui_server import (\n    ui_action_result_intent,\n    ui_action_result_link,\n    ui_action_result_notification,\n    ui_action_result_prompt,\n    ui_action_result_tool_call,\n)\n\n\nclass TestUIActionResultHelpers:\n    \"\"\"Test suite for UI action result helper functions.\"\"\"\n\n    def test_ui_action_result_tool_call(self):\n        \"\"\"Test creating a tool call action result.\"\"\"\n        result = ui_action_result_tool_call(\"testTool\", {\"param1\": \"value1\"})\n        expected = {\n            \"type\": \"tool\",\n            \"payload\": {\n                \"toolName\": \"testTool\",\n                \"params\": {\"param1\": \"value1\"},\n            },\n            \"messageId\": None,\n        }\n        assert result.model_dump() == expected\n\n    def test_ui_action_result_prompt(self):\n        \"\"\"Test creating a prompt action result.\"\"\"\n        result = ui_action_result_prompt(\"Enter your name\")\n        expected = {\n            \"type\": \"prompt\",\n            \"payload\": {\n                \"prompt\": \"Enter your name\",\n            },\n            \"messageId\": None,\n        }\n        assert result.model_dump() == expected\n\n    def test_ui_action_result_link(self):\n        \"\"\"Test creating a link action result.\"\"\"\n        result = ui_action_result_link(\"https://example.com\")\n        expected = {\n            \"type\": \"link\",\n            \"payload\": {\n                \"url\": \"https://example.com\",\n            },\n            \"messageId\": None,\n        }\n        assert result.model_dump() == expected\n\n    def test_ui_action_result_intent(self):\n        \"\"\"Test creating an intent action result.\"\"\"\n        result = ui_action_result_intent(\"doSomething\", {\"data\": \"abc\"})\n        expected = {\n            \"type\": \"intent\",\n            \"payload\": {\n                \"intent\": \"doSomething\",\n                \"params\": {\"data\": \"abc\"},\n            },\n            \"messageId\": None,\n        }\n        assert result.model_dump() == expected\n\n    def test_ui_action_result_notification(self):\n        \"\"\"Test creating a notification action result.\"\"\"\n        result = ui_action_result_notification(\"Success!\")\n        expected = {\n            \"type\": \"notify\",\n            \"payload\": {\n                \"message\": \"Success!\",\n            },\n            \"messageId\": None,\n        }\n        assert result.model_dump() == expected\n\n    def test_ui_action_result_tool_call_with_complex_params(self):\n        \"\"\"Test tool call action result with complex parameters.\"\"\"\n        complex_params = {\n            \"nested\": {\"key\": \"value\"},\n            \"list\": [1, 2, 3],\n            \"boolean\": True,\n            \"number\": 42,\n        }\n        result = ui_action_result_tool_call(\"complexTool\", complex_params)\n        expected = {\n            \"type\": \"tool\",\n            \"payload\": {\n                \"toolName\": \"complexTool\",\n                \"params\": complex_params,\n            },\n            \"messageId\": None,\n        }\n        assert result.model_dump() == expected\n\n    def test_ui_action_result_intent_with_empty_params(self):\n        \"\"\"Test intent action result with empty parameters.\"\"\"\n        result = ui_action_result_intent(\"simpleIntent\", {})\n        expected = {\n            \"type\": \"intent\",\n            \"payload\": {\n                \"intent\": \"simpleIntent\",\n                \"params\": {},\n            },\n            \"messageId\": None,\n        }\n        assert result.model_dump() == expected"
  },
  {
    "path": "sdks/ruby/.rspec",
    "content": "--require spec_helper\n--color\n--format documentation"
  },
  {
    "path": "sdks/ruby/.rubocop.yml",
    "content": "AllCops:\n  NewCops: enable\n  Exclude:\n    - 'bin/*'\n    - 'vendor/**/*'\n    - 'tmp/**/*'\n\nplugins:\n  - rubocop-rake\n  - rubocop-rspec\n\nStyle/StringLiterals:\n  EnforcedStyle: single_quotes\n\nStyle/Documentation:\n  Enabled: false\n\nGemspec/DevelopmentDependencies:\n  Enabled: false\n\nMetrics/BlockLength:\n  Exclude:\n    - 'spec/**/*.rb'\n    - '*.gemspec'\n\nRSpec/MultipleExpectations:\n  Enabled: false\n"
  },
  {
    "path": "sdks/ruby/CHANGELOG.md",
    "content": "## [1.0.1](https://github.com/idosal/mcp-ui/compare/ruby-server-sdk/v1.0.0...ruby-server-sdk/v1.0.1) (2025-07-19)\n\n\n### Bug Fixes\n\n* Ruby comment ([b22dc2e](https://github.com/idosal/mcp-ui/commit/b22dc2e0a0db20d98ada884649ad408ebaf72d22))\n"
  },
  {
    "path": "sdks/ruby/Gemfile",
    "content": "# frozen_string_literal: true\n\nsource 'https://rubygems.org'\n\ngemspec\n"
  },
  {
    "path": "sdks/ruby/README.md",
    "content": "# MCP UI Server SDK for Ruby\n\nThis is the Ruby server-side SDK for MCP UI. It provides helper methods to create UI resources that can be sent to the mcp-ui client.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'mcp_ui_server', git: 'https://github.com/idosal/mcp-ui'\n```\n\nAnd then execute:\n\n    $ bundle install\n\n## Usage\n\nThe main method is `McpUiServer.create_ui_resource`. It helps you construct a valid UIResource hash.\n\n### URI Requirements\n\nAll UI resources must use the `ui://` URI scheme. The SDK will validate it and raise a `McpUiServer::Error` if an invalid URI is provided.\n\n```ruby\n# Valid URI\nMcpUiServer.create_ui_resource(uri: 'ui://my-app/greeting', ...)\n\n# Invalid URI - will raise McpUiServer::Error\nMcpUiServer.create_ui_resource(uri: 'http://example.com', ...)\n```\n\n### Content Type Support\n\nThe Ruby SDK follows Ruby conventions by using snake_case symbols for content types:\n\n- `:raw_html` - Raw HTML content\n- `:external_url` - External URL content  \n- `:remote_dom` - Remote DOM content\n\nThe SDK maintains protocol consistency by internally mapping these to the camelCase format used in the MCP-UI specification.\n\n### Creating a `raw_html` resource\n\n```ruby\nrequire 'mcp_ui_server'\n\nresource = McpUiServer.create_ui_resource(\n  uri: 'ui://my-app/greeting',\n  content: {\n    type: :raw_html,\n    htmlString: '<h1>Hello, World!</h1>'\n  }\n)\n\n# The resulting resource hash can be serialized to JSON and sent in your API response.\n# {\n#   \"type\": \"resource\",\n#   \"resource\": {\n#     \"uri\": \"ui://my-app/greeting\",\n#     \"mimeType\": \"text/html;profile=mcp-app\",\n#     \"text\": \"<h1>Hello, World!</h1>\"\n#   }\n# }\n```\n\n### Creating an `external_url` resource\n\n```ruby\nrequire 'mcp_ui_server'\n\nresource = McpUiServer.create_ui_resource(\n  uri: 'ui://my-app/external-page',\n  content: {\n    type: :external_url,\n    iframeUrl: 'https://example.com'\n  }\n)\n\n# Results in:\n# {\n#   \"type\": \"resource\",\n#   \"resource\": {\n#     \"uri\": \"ui://my-app/external-page\",\n#     \"mimeType\": \"text/html;profile=mcp-app\",\n#     \"text\": \"https://example.com\"\n#   }\n# }\n```\n\n### Creating a `remote_dom` resource\n\n```ruby\nrequire 'mcp_ui_server'\n\nscript = \"...\" # Your javascript bundle for the remote-dom\n\nresource = McpUiServer.create_ui_resource(\n  uri: 'ui://my-app/complex-view',\n  content: {\n    type: :remote_dom,\n    script: script,\n    framework: :react # or :webcomponents\n  }\n)\n```\n\n### Using `blob` encoding\n\nFor binary content or to avoid encoding issues, you can use `blob` encoding, which will Base64 encode the content.\n\n```ruby\nresource = McpUiServer.create_ui_resource(\n  uri: 'ui://my-app/greeting',\n  content: {\n    type: :raw_html,\n    htmlString: '<h1>Hello, World!</h1>'\n  },\n  encoding: :blob\n)\n```\n\n## Error Handling\n\nThe SDK uses a custom `McpUiServer::Error` class for all SDK-specific errors. This makes it easy to rescue and handle SDK errors separately from other application errors.\n\n```ruby\nbegin\n  resource = McpUiServer.create_ui_resource(\n    uri: 'http://invalid-scheme',\n    content: { type: :raw_html, htmlString: '<h1>Hello</h1>' }\n  )\nrescue McpUiServer::Error => e\n  puts \"MCP UI SDK Error: #{e.message}\"\n  # Handle SDK-specific errors\nrescue => e\n  puts \"Other error: #{e.message}\"\n  # Handle other errors\nend\n```\n\nCommon error scenarios:\n- Invalid URI scheme (not starting with `ui://`)\n- Unknown content type (only `:raw_html`, `:external_url`, `:remote_dom` are supported)\n- Missing required content keys (e.g., `htmlString` for `raw_html`)\n- Unknown encoding type\n\n## Constants\n\nThe SDK provides constants for MIME types and content types:\n\n```ruby\n# MIME type constant (MCP Apps standard; used for both HTML and URL content)\nMcpUiServer::RESOURCE_MIME_TYPE     # 'text/html;profile=mcp-app'\nMcpUiServer::MIME_TYPE_HTML          # alias for RESOURCE_MIME_TYPE\nMcpUiServer::MIME_TYPE_URI_LIST      # alias for RESOURCE_MIME_TYPE\nMcpUiServer::MIME_TYPE_REMOTE_DOM    # 'application/vnd.mcp-ui.remote-dom; framework=%s'\n\n# Content type constants (Ruby snake_case)\nMcpUiServer::CONTENT_TYPE_RAW_HTML      # :raw_html\nMcpUiServer::CONTENT_TYPE_EXTERNAL_URL  # :external_url\nMcpUiServer::CONTENT_TYPE_REMOTE_DOM    # :remote_dom\n\n# Protocol mapping (for internal use)\nMcpUiServer::PROTOCOL_CONTENT_TYPES     # { raw_html: 'rawHtml', ... }\n\n# URI scheme constant\nMcpUiServer::UI_URI_SCHEME           # 'ui://'\n```\n\n## Development\n\nAfter checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bundle exec rubocop` to check for code style.\n\nTo install this gem onto your local machine, run `bundle exec rake install`.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/idosal/mcp-ui.\n\n## License\n\nThe gem is available as open source under the terms of the [Apache-2.0 License](https://www.apache.org/licenses/LICENSE-2.0). "
  },
  {
    "path": "sdks/ruby/Rakefile",
    "content": "# frozen_string_literal: true\n\nrequire 'bundler/gem_tasks'\nrequire 'rspec/core/rake_task'\n\nRSpec::Core::RakeTask.new(:spec)\n\ntask default: :spec\n"
  },
  {
    "path": "sdks/ruby/lib/mcp_ui_server/version.rb",
    "content": "# frozen_string_literal: true\n\nmodule McpUiServer\n  VERSION = '0.1.0'\nend\n"
  },
  {
    "path": "sdks/ruby/lib/mcp_ui_server.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative 'mcp_ui_server/version'\nrequire 'base64'\n\n# The McpUiServer module provides helper methods for creating UI resources\n# compatible with the Model Context Protocol UI (mcp-ui) client.\nmodule McpUiServer\n  class Error < StandardError; end\n\n  # MIME type for MCP Apps resources\n  RESOURCE_MIME_TYPE = 'text/html;profile=mcp-app'\n  # Convenience aliases\n  MIME_TYPE_HTML = RESOURCE_MIME_TYPE\n  MIME_TYPE_URI_LIST = RESOURCE_MIME_TYPE\n\n  # Content type constants\n  CONTENT_TYPE_RAW_HTML = :raw_html\n  CONTENT_TYPE_EXTERNAL_URL = :external_url\n\n  # Protocol mapping (snake_case to camelCase for protocol consistency)\n  PROTOCOL_CONTENT_TYPES = {\n    raw_html: 'rawHtml',\n    external_url: 'externalUrl'\n  }.freeze\n\n  # Required content keys for each content type\n  REQUIRED_CONTENT_KEYS = {\n    CONTENT_TYPE_RAW_HTML => :htmlString,\n    CONTENT_TYPE_EXTERNAL_URL => :iframeUrl\n  }.freeze\n\n  # URI scheme constant\n  UI_URI_SCHEME = 'ui://'\n\n  # Creates a UIResource hash structure for an MCP response.\n  # This structure can then be serialized to JSON by your web framework.\n  #\n  # @param uri [String] The unique identifier for the resource (e.g., 'ui://greeting/1').\n  # @param content [Hash] A hash describing the UI content.\n  #   - :type [Symbol] The type of content. One of :raw_html or :external_url.\n  #   - :htmlString [String] The raw HTML content (required if type is :raw_html).\n  #   - :iframeUrl [String] The URL for an external page (required if type is :external_url).\n  # @param encoding [Symbol] The encoding method. :text for plain string, :blob for base64 encoded.\n  #\n  # @return [Hash] A UIResource hash ready to be included in an MCP response.\n  #\n  # @raise [McpUiServer::Error] if URI scheme is invalid, content type is unknown,\n  #   encoding type is unknown, or required content keys are missing.\n  def self.create_ui_resource(uri:, content:, encoding: :text)\n    validate_uri_scheme(uri)\n\n    resource = { uri: uri }\n\n    content_value = process_content(content, resource)\n    process_encoding(encoding, resource, content_value)\n\n    {\n      type: 'resource',\n      resource: resource\n    }\n  end\n\n  # private\n\n  def self.validate_uri_scheme(uri)\n    raise Error, \"URI must start with '#{UI_URI_SCHEME}' but got: #{uri}\" unless uri.start_with?(UI_URI_SCHEME)\n  end\n  private_class_method :validate_uri_scheme\n\n  def self.validate_content_type(content_type)\n    return if PROTOCOL_CONTENT_TYPES.key?(content_type)\n\n    supported_types = PROTOCOL_CONTENT_TYPES.keys.join(', ')\n    raise Error, \"Unknown content type: #{content_type}. Supported types: #{supported_types}\"\n  end\n  private_class_method :validate_content_type\n\n  def self.process_content(content, resource)\n    content_type = content.fetch(:type)\n    validate_content_type(content_type)\n\n    case content_type\n    when CONTENT_TYPE_RAW_HTML\n      process_raw_html_content(content, resource)\n    when CONTENT_TYPE_EXTERNAL_URL\n      process_external_url_content(content, resource)\n    end\n  end\n  private_class_method :process_content\n\n  def self.process_raw_html_content(content, resource)\n    resource[:mimeType] = RESOURCE_MIME_TYPE\n    required_key = REQUIRED_CONTENT_KEYS[CONTENT_TYPE_RAW_HTML]\n    content.fetch(required_key) { raise Error, \"Missing required key :#{required_key} for raw_html content\" }\n  end\n  private_class_method :process_raw_html_content\n\n  def self.process_external_url_content(content, resource)\n    resource[:mimeType] = RESOURCE_MIME_TYPE\n    required_key = REQUIRED_CONTENT_KEYS[CONTENT_TYPE_EXTERNAL_URL]\n    content.fetch(required_key) { raise Error, \"Missing required key :#{required_key} for external_url content\" }\n  end\n  private_class_method :process_external_url_content\n\n  def self.process_encoding(encoding, resource, content_value)\n    case encoding\n    when :text\n      resource[:text] = content_value\n    when :blob\n      resource[:blob] = Base64.strict_encode64(content_value)\n    else\n      raise Error, \"Unknown encoding type: #{encoding}. Supported types: :text, :blob\"\n    end\n  end\n  private_class_method :process_encoding\nend\n"
  },
  {
    "path": "sdks/ruby/mcp_ui_server.gemspec",
    "content": "# frozen_string_literal: true\n\nrequire_relative 'lib/mcp_ui_server/version'\n\nGem::Specification.new do |spec|\n  spec.name        = 'mcp_ui_server'\n  spec.version     = McpUiServer::VERSION\n  spec.authors     = ['Ido Salomon']\n  spec.email       = ['idosalomon@gmail.com']\n  spec.summary     = 'Ruby Server SDK for MCP UI'\n  spec.description = 'A Ruby server SDK for MCP UI.'\n  spec.homepage    = 'https://github.com/idosal/mcp-ui'\n  spec.license     = 'Apache-2.0'\n  spec.required_ruby_version = '>= 2.6.0'\n\n  spec.metadata['rubygems_mfa_required'] = 'true'\n\n  spec.files = Dir['{lib}/**/*.rb', 'README.md']\n\n  spec.require_paths = ['lib']\n\n  # Add any runtime dependencies here\n  # spec.add_dependency \"http\"\n\n  # Add any development dependencies here\n  spec.add_development_dependency 'bundler'\n  spec.add_development_dependency 'rake'\n  spec.add_development_dependency 'rspec'\n  spec.add_development_dependency 'rubocop'\n  spec.add_development_dependency 'rubocop-rake'\n  spec.add_development_dependency 'rubocop-rspec'\nend\n"
  },
  {
    "path": "sdks/ruby/release.config.js",
    "content": "module.exports = {\n  branches: [\n    'main',\n    {\n      name: 'alpha',\n      prerelease: true,\n    },\n  ],\n  repositoryUrl: 'https://github.com/idosal/mcp-ui',\n  tagFormat: 'ruby-server-sdk/v${version}',\n  plugins: [\n    '@semantic-release/commit-analyzer',\n    '@semantic-release/release-notes-generator',\n    [\n      '@semantic-release/changelog',\n      {\n        changelogFile: 'CHANGELOG.md',\n      },\n    ],\n    [\n      '@semantic-release/exec',\n      {\n        prepareCmd:\n          'sed -i \\'s/VERSION = \".*\"/VERSION = \"${nextRelease.version}\"/\\' lib/mcp_ui_server/version.rb && bundle install && gem build mcp_ui_server.gemspec',\n        publishCmd: 'gem push *.gem',\n      },\n    ],\n    [\n      '@semantic-release/github',\n      {\n        assets: '*.gem',\n      },\n    ],\n    [\n      '@semantic-release/git',\n      {\n        assets: ['CHANGELOG.md', 'lib/mcp_ui_server/version.rb', 'Gemfile.lock'],\n        message: 'chore(release): ${nextRelease.version} [skip ci]\\n\\n${nextRelease.notes}',\n      },\n    ],\n  ],\n};\n"
  },
  {
    "path": "sdks/ruby/spec/mcp_ui_server_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'mcp_ui_server'\nrequire 'base64'\n\nRSpec.describe McpUiServer do\n  it 'has a version number' do\n    expect(McpUiServer::VERSION).not_to be_nil\n  end\n\n  describe '.create_ui_resource' do\n    let(:uri) { 'ui://test/1' }\n\n    context 'with raw_html content' do\n      let(:content) { { type: :raw_html, htmlString: '<h1>Hello</h1>' } }\n\n      it 'creates a resource with text/html;profile=mcp-app mimetype and text encoding' do\n        resource = described_class.create_ui_resource(uri: uri, content: content)\n        expect(resource[:type]).to eq('resource')\n        expect(resource[:resource][:uri]).to eq(uri)\n        expect(resource[:resource][:mimeType]).to eq('text/html;profile=mcp-app')\n        expect(resource[:resource][:text]).to eq('<h1>Hello</h1>')\n      end\n\n      it 'creates a resource with blob encoding' do\n        resource = described_class.create_ui_resource(uri: uri, content: content, encoding: :blob)\n        expect(resource[:resource][:blob]).to eq(Base64.strict_encode64('<h1>Hello</h1>'))\n      end\n    end\n\n    context 'with external_url content' do\n      let(:content) { { type: :external_url, iframeUrl: 'https://example.com' } }\n\n      it 'creates a resource with text/html;profile=mcp-app mimetype' do\n        resource = described_class.create_ui_resource(uri: uri, content: content)\n        expect(resource[:resource][:mimeType]).to eq('text/html;profile=mcp-app')\n        expect(resource[:resource][:text]).to eq('https://example.com')\n      end\n\n      it 'creates a resource with blob encoding' do\n        resource = described_class.create_ui_resource(uri: uri, content: content, encoding: :blob)\n        expect(resource[:resource][:blob]).to eq(Base64.strict_encode64('https://example.com'))\n      end\n    end\n\n    context 'with invalid input' do\n      it 'raises McpUiServer::Error for invalid URI scheme' do\n        invalid_uri = 'http://test/1'\n        content = { type: :raw_html, htmlString: '<h1>Hello</h1>' }\n        expect do\n          described_class.create_ui_resource(uri: invalid_uri, content: content)\n        end.to raise_error(McpUiServer::Error, \"URI must start with 'ui://' but got: #{invalid_uri}\")\n      end\n\n      it 'raises McpUiServer::Error for unknown content type' do\n        content = { type: :invalid, data: 'foo' }\n        expect do\n          described_class.create_ui_resource(uri: uri, content: content)\n        end.to raise_error(McpUiServer::Error, /Unknown content type: invalid/)\n      end\n\n      it 'raises McpUiServer::Error for camelCase string content type' do\n        content = { type: 'rawHtml', htmlString: '<h1>Hello</h1>' }\n        expect do\n          described_class.create_ui_resource(uri: uri, content: content)\n        end.to raise_error(McpUiServer::Error, /Unknown content type: rawHtml/)\n      end\n\n      it 'raises McpUiServer::Error for unknown encoding type' do\n        content = { type: :raw_html, htmlString: '<h1>Hello</h1>' }\n        expect do\n          described_class.create_ui_resource(uri: uri, content: content, encoding: :invalid)\n        end.to raise_error(McpUiServer::Error, /Unknown encoding type: invalid/)\n      end\n\n      it 'raises McpUiServer::Error if htmlString is missing' do\n        content = { type: :raw_html }\n        expect do\n          described_class.create_ui_resource(uri: uri, content: content)\n        end.to raise_error(McpUiServer::Error, /Missing required key :htmlString for raw_html content/)\n      end\n\n      it 'raises McpUiServer::Error if iframeUrl is missing' do\n        content = { type: :external_url }\n        expect do\n          described_class.create_ui_resource(uri: uri, content: content)\n        end.to raise_error(McpUiServer::Error, /Missing required key :iframeUrl for external_url content/)\n      end\n    end\n\n    context 'with constants' do\n      it 'defines expected MIME type constants' do\n        expect(McpUiServer::RESOURCE_MIME_TYPE).to eq('text/html;profile=mcp-app')\n        expect(McpUiServer::MIME_TYPE_HTML).to eq('text/html;profile=mcp-app')\n        expect(McpUiServer::MIME_TYPE_URI_LIST).to eq('text/html;profile=mcp-app')\n      end\n\n      it 'defines expected content type constants' do\n        expect(McpUiServer::CONTENT_TYPE_RAW_HTML).to eq(:raw_html)\n        expect(McpUiServer::CONTENT_TYPE_EXTERNAL_URL).to eq(:external_url)\n      end\n\n      it 'defines protocol content type mapping' do\n        expect(McpUiServer::PROTOCOL_CONTENT_TYPES).to include(\n          raw_html: 'rawHtml',\n          external_url: 'externalUrl'\n        )\n      end\n\n      it 'defines URI scheme constant' do\n        expect(McpUiServer::UI_URI_SCHEME).to eq('ui://')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "sdks/ruby/spec/spec_helper.rb",
    "content": "# frozen_string_literal: true\n\n# This file was generated by the `rspec --init` command. Conventionally, all\n# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.\n# The generated `.rspec` file contains `--require spec_helper` which will cause\n# this file to be loaded before any of your specs are run.\n#\n# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration\nRSpec.configure do |config|\n  # rspec-expectations config goes here. You can use an alternate\n  # assertion/expectation library such as wrong or the stdlib/minitest\n  # assertions if you prefer.\n  config.expect_with :rspec do |expectations|\n    # This option will default to `true` in RSpec 4. It makes the `description`\n    # and `failure_message` of custom matchers include text argument names\n    # that match what was passed in an expectation. For example:\n    #     be_bigger_than(2).and(be_smaller_than(4)).description\n    #     # => \"be bigger than 2 and be smaller than 4\"\n    # ...rather than:\n    #     # => \"be bigger than and be smaller than\"\n    expectations.include_chain_clauses_in_custom_matcher_descriptions = true\n  end\n\n  # rspec-mocks config goes here. You can use an alternate test double\n  # library (such as bogus or mocha) by changing the `mock_with` option here.\n  config.mock_with :rspec do |mocks|\n    # Prevents you from mocking or stubbing a method that does not exist on\n    # a real object. This is generally recommended, and will default to\n    # `true` in RSpec 4.\n    mocks.verify_partial_doubles = true\n  end\n\n  # This option will default to `:apply_to_host_groups` in RSpec 4 (and will\n  # have no way to turn it off -- the option exists only for backwards\n  # compatibility in RSpec 3). It causes shared context metadata to be\n  # inherited by the metadata hash of host groups and examples, rather than\n  # triggering implicit auto-inclusion in groups with matching metadata.\n  config.shared_context_metadata_behavior = :apply_to_host_groups\nend\n"
  },
  {
    "path": "sdks/typescript/client/CHANGELOG.md",
    "content": "# [7.0.0](https://github.com/MCP-UI-Org/mcp-ui/compare/client/v6.1.1...client/v7.0.0) (2026-03-12)\n\n\n### Bug Fixes\n\n* bump major for breaking change ([#187](https://github.com/MCP-UI-Org/mcp-ui/issues/187)) ([2223e76](https://github.com/MCP-UI-Org/mcp-ui/commit/2223e76b229559ae6f39d63dd581a08b52e35acd))\n\n\n### BREAKING CHANGES\n\n* failed to bump major in the last commit because of semantic release\n\n## [6.1.1](https://github.com/MCP-UI-Org/mcp-ui/compare/client/v6.1.0...client/v6.1.1) (2026-03-12)\n\n\n### Bug Fixes\n\n* bump version for legacy removal ([#186](https://github.com/MCP-UI-Org/mcp-ui/issues/186)) ([0a75453](https://github.com/MCP-UI-Org/mcp-ui/commit/0a754533c92b6e9e317bda03383fc2c2745b37c7))\n\n# [6.1.0](https://github.com/MCP-UI-Org/mcp-ui/compare/client/v6.0.0...client/v6.1.0) (2026-02-13)\n\n\n### Features\n\n* support experimental messages ([#176](https://github.com/MCP-UI-Org/mcp-ui/issues/176)) ([327e4d9](https://github.com/MCP-UI-Org/mcp-ui/commit/327e4d9e6771eceb42f8823ae864e6758f371970))\n\n# [6.0.0](https://github.com/MCP-UI-Org/mcp-ui/compare/client/v5.18.0...client/v6.0.0) (2026-01-26)\n\n\n### Bug Fixes\n\n* trigger MCP Apps client release ([#173](https://github.com/MCP-UI-Org/mcp-ui/issues/173)) ([b0c0659](https://github.com/MCP-UI-Org/mcp-ui/commit/b0c0659700b493a0083d0de12958f276e4c37715))\n\n\n### Features\n\n* mcp-ui -> mcp apps! ([#172](https://github.com/MCP-UI-Org/mcp-ui/issues/172)) ([93a6e22](https://github.com/MCP-UI-Org/mcp-ui/commit/93a6e225808894cac3206504cf15c481f88ed540))\n\n\n### BREAKING CHANGES\n\n* removed discarded content types, changed mimetype, updated docs, etc.\n\n# [5.18.0](https://github.com/MCP-UI-Org/mcp-ui/compare/client/v5.17.3...client/v5.18.0) (2026-01-25)\n\n\n### Features\n\n* React renderer for MCP Apps ([#147](https://github.com/MCP-UI-Org/mcp-ui/issues/147)) ([fbc918d](https://github.com/MCP-UI-Org/mcp-ui/commit/fbc918d713861396703ede124c5676cf3d1164dd))\n\n## [5.17.3](https://github.com/MCP-UI-Org/mcp-ui/compare/client/v5.17.2...client/v5.17.3) (2025-12-20)\n\n\n### Bug Fixes\n\n* add connectedMoveCallback to the WC implementation ([#170](https://github.com/MCP-UI-Org/mcp-ui/issues/170)) ([5ac4734](https://github.com/MCP-UI-Org/mcp-ui/commit/5ac4734078ff56e213e7a57029fd612638abb0b4))\n\n## [5.17.2](https://github.com/MCP-UI-Org/mcp-ui/compare/client/v5.17.1...client/v5.17.2) (2025-12-20)\n\n\n### Bug Fixes\n\n* **client:** use type-only imports for @modelcontextprotocol/sdk types ([#157](https://github.com/MCP-UI-Org/mcp-ui/issues/157)) ([d84cb3e](https://github.com/MCP-UI-Org/mcp-ui/commit/d84cb3e217dfa90f038a8fbe64b4ffd0e0a1bc3f))\n* revert externalUrl adapter for compatibility ([#159](https://github.com/MCP-UI-Org/mcp-ui/issues/159)) ([3c5289a](https://github.com/MCP-UI-Org/mcp-ui/commit/3c5289a5e59812f556bc2bb9e67c3ce2e3ce764e))\n* trigger TS release ([e28d1f6](https://github.com/MCP-UI-Org/mcp-ui/commit/e28d1f65cd5f26e46801480995e178ee50c31e51))\n* update adapter to latest MCP Apps spec ([#163](https://github.com/MCP-UI-Org/mcp-ui/issues/163)) ([e342034](https://github.com/MCP-UI-Org/mcp-ui/commit/e3420348098fe716f45b2182bc8c8d3358714035))\n* update MCP Apps adapter mimetype ([#162](https://github.com/MCP-UI-Org/mcp-ui/issues/162)) ([c91e533](https://github.com/MCP-UI-Org/mcp-ui/commit/c91e5331b68c65de2347b16766a5711e05985e68))\n* update ui/message to pass an array ([#167](https://github.com/MCP-UI-Org/mcp-ui/issues/167)) ([80cf222](https://github.com/MCP-UI-Org/mcp-ui/commit/80cf22290ff5e2f981be839f74705454483c21b5))\n\n## [5.17.1](https://github.com/MCP-UI-Org/mcp-ui/compare/client/v5.17.0...client/v5.17.1) (2025-12-01)\n\n\n### Bug Fixes\n\n* use document.write in proxy sandbox ([#156](https://github.com/MCP-UI-Org/mcp-ui/issues/156)) ([447f582](https://github.com/MCP-UI-Org/mcp-ui/commit/447f582105a0889ee47d912925d7f02034fc39a9))\n\n# [5.17.0](https://github.com/MCP-UI-Org/mcp-ui/compare/client/v5.16.0...client/v5.17.0) (2025-11-30)\n\n\n### Features\n\n* externalUrl adapter ([5a1e4de](https://github.com/MCP-UI-Org/mcp-ui/commit/5a1e4deeeda9ae0120d17b5abdf9096edf52054f))\n\n# [5.16.0](https://github.com/MCP-UI-Org/mcp-ui/compare/client/v5.15.0...client/v5.16.0) (2025-11-29)\n\n\n### Features\n\n* add MCP Apps adapter ([#154](https://github.com/MCP-UI-Org/mcp-ui/issues/154)) ([986fd63](https://github.com/MCP-UI-Org/mcp-ui/commit/986fd634448637aa6488a18ad80ee9cd6b8de318))\n\n# [5.15.0](https://github.com/MCP-UI-Org/mcp-ui/compare/client/v5.14.1...client/v5.15.0) (2025-11-29)\n\n\n### Bug Fixes\n\n* ensure Apps SDK adapter is bundled properly and initialized wth config ([#137](https://github.com/MCP-UI-Org/mcp-ui/issues/137)) ([4f7c25c](https://github.com/MCP-UI-Org/mcp-ui/commit/4f7c25ce7e6f25e36cfc188016b012d31d722204))\n\n\n### Features\n\n* support metadata in Python SDK ([#134](https://github.com/MCP-UI-Org/mcp-ui/issues/134)) ([9bc3c64](https://github.com/MCP-UI-Org/mcp-ui/commit/9bc3c646c2638e16ac62edf9faca2dbee2b8cb7e))\n\n## [5.14.1](https://github.com/idosal/mcp-ui/compare/client/v5.14.0...client/v5.14.1) (2025-10-24)\n\n\n### Bug Fixes\n\n* Enable bidirectional message relay in rawhtml proxy mode ([#138](https://github.com/idosal/mcp-ui/issues/138)) ([f0bdefb](https://github.com/idosal/mcp-ui/commit/f0bdefb818aca5a3bfbdfe28fc4fe057b1818cb5))\n\n# [5.14.0](https://github.com/idosal/mcp-ui/compare/client/v5.13.1...client/v5.14.0) (2025-10-22)\n\n\n### Features\n\n* support proxy for rawHtml ([#132](https://github.com/idosal/mcp-ui/issues/132)) ([1bbeb09](https://github.com/idosal/mcp-ui/commit/1bbeb093bc9c389f4ccfd9e8df06dc7f1eaadde0))\n\n## [5.13.1](https://github.com/idosal/mcp-ui/compare/client/v5.13.0...client/v5.13.1) (2025-10-21)\n\n\n### Bug Fixes\n\n* fix file extension reference in package.json ([927989c](https://github.com/idosal/mcp-ui/commit/927989c4f81742106b6f5e2f68343fb7ea7d016a))\n\n# [5.13.0](https://github.com/idosal/mcp-ui/compare/client/v5.12.1...client/v5.13.0) (2025-10-13)\n\n\n### Features\n\n* support adapters ([#127](https://github.com/idosal/mcp-ui/issues/127)) ([d4bd152](https://github.com/idosal/mcp-ui/commit/d4bd152db0a1bd27081502098f3cd9aa54ca359e))\n\n# [5.13.0-alpha.4](https://github.com/idosal/mcp-ui/compare/client/v5.13.0-alpha.3...client/v5.13.0-alpha.4) (2025-10-12)\n\n\n### Bug Fixes\n\n* exports vite ([4de2b0c](https://github.com/idosal/mcp-ui/commit/4de2b0cfae91813ad68fb1ce68b1cf7c2a161baf))\n\n# [5.13.0-alpha.3](https://github.com/idosal/mcp-ui/compare/client/v5.13.0-alpha.2...client/v5.13.0-alpha.3) (2025-10-11)\n\n\n### Bug Fixes\n\n* exports ([0018c17](https://github.com/idosal/mcp-ui/commit/0018c17dd8b184ee549327f1742d9da71edfd576))\n* version ([767a245](https://github.com/idosal/mcp-ui/commit/767a245d2374f05e27ece090dc5af8613a9a6b96))\n\n# [5.13.0-alpha.2](https://github.com/idosal/mcp-ui/compare/client/v5.13.0-alpha.1...client/v5.13.0-alpha.2) (2025-10-10)\n\n\n### Bug Fixes\n\n* set the mime type as text/html+skybridge for apps SDK ([bc47423](https://github.com/idosal/mcp-ui/commit/bc474232a249e5cc40f348e3a26f93c806fcc602))\n\n# [5.13.0-alpha.1](https://github.com/idosal/mcp-ui/compare/client/v5.12.1...client/v5.13.0-alpha.1) (2025-10-10)\n\n\n### Bug Fixes\n\n* adapter version ([259c842](https://github.com/idosal/mcp-ui/commit/259c84247a00933575e1fff08674cce52be59973))\n* release ([420efc0](https://github.com/idosal/mcp-ui/commit/420efc0a82bf8de2731514648268cad1209320e2))\n* server alpha versioning ([7f35d3b](https://github.com/idosal/mcp-ui/commit/7f35d3be2cfa6a535d3fbd5f86fbec1b20432dca))\n* server versioning ([2324371](https://github.com/idosal/mcp-ui/commit/2324371ed636381bb44a1feae1b59a87c84c6666))\n\n\n### Features\n\n* add adapters infra (appssdk) ([#125](https://github.com/idosal/mcp-ui/issues/125)) ([2e016cd](https://github.com/idosal/mcp-ui/commit/2e016cdc05d08c2f7c2e4a40efbec2b0704e7ef6))\n\n# [1.0.0-alpha.2](https://github.com/idosal/mcp-ui/compare/client/v1.0.0-alpha.1...client/v1.0.0-alpha.2) (2025-10-10)\n\n\n### Bug Fixes\n\n* server versioning ([2324371](https://github.com/idosal/mcp-ui/commit/2324371ed636381bb44a1feae1b59a87c84c6666))\n\n# 1.0.0-alpha.1 (2025-10-10)\n\n\n### Bug Fixes\n\n* adapter version ([259c842](https://github.com/idosal/mcp-ui/commit/259c84247a00933575e1fff08674cce52be59973))\n* add a bridge to pass messages in and out of the proxy ([#38](https://github.com/idosal/mcp-ui/issues/38)) ([30ccac0](https://github.com/idosal/mcp-ui/commit/30ccac0706ad8e02ebcd8960924ed1d58ddedf85))\n* bump client version ([75c9236](https://github.com/idosal/mcp-ui/commit/75c923689654b4443ad1093fafc0bad16902e4cc))\n* **client:** specify iframe ([fd0b70a](https://github.com/idosal/mcp-ui/commit/fd0b70a84948d3aa5d7a79269ff7c3bcd0946689))\n* **client:** styling ([6ff9b68](https://github.com/idosal/mcp-ui/commit/6ff9b685fd1be770fd103943e45275e9ec86905c))\n* dependencies ([887f61f](https://github.com/idosal/mcp-ui/commit/887f61f827b4585c17493d4fa2dfb251ea598587))\n* export RemoteDomResource ([2b86f2d](https://github.com/idosal/mcp-ui/commit/2b86f2dd4506de49c69908e23d84a2a323170446))\n* export ResourceRenderer and HtmlResource ([2b841a5](https://github.com/idosal/mcp-ui/commit/2b841a556c1111ed70ccb3d3987afd21fe7df897))\n* exports ([3a93a16](https://github.com/idosal/mcp-ui/commit/3a93a16e1b7438ba7b2ef49ca854479f755abcc6))\n* iframe handle ([#15](https://github.com/idosal/mcp-ui/issues/15)) ([66bd4fd](https://github.com/idosal/mcp-ui/commit/66bd4fd3d04f82e3e4557f064e701b68e1d8af11))\n* lint ([4487820](https://github.com/idosal/mcp-ui/commit/44878203a71c3c9173d463b809be36769e996ba9))\n* lint ([d0a91f9](https://github.com/idosal/mcp-ui/commit/d0a91f9a07ec0042690240c3d8d0bad620f8c765))\n* minor typo ([a0bee9c](https://github.com/idosal/mcp-ui/commit/a0bee9c85e5ee02e021ba687940ced38220445fe))\n* move react dependencies to be peer dependencies ([#91](https://github.com/idosal/mcp-ui/issues/91)) ([f672f3e](https://github.com/idosal/mcp-ui/commit/f672f3efc1c2ba2fbae16f9dcdc2142c2b4bd920)), closes [#90](https://github.com/idosal/mcp-ui/issues/90)\n* package config ([8dc1e53](https://github.com/idosal/mcp-ui/commit/8dc1e5358c3c8e641206a5e6851427d360cc1955))\n* packaging ([9e6babd](https://github.com/idosal/mcp-ui/commit/9e6babd3a587213452ea7aec4cc9ae3a50fa1965))\n* pass ref explicitly using iframeProps ([#33](https://github.com/idosal/mcp-ui/issues/33)) ([d01b5d1](https://github.com/idosal/mcp-ui/commit/d01b5d1e4cdaedc436ba2fa8984d866d93d59087))\n* publish ([0943e7a](https://github.com/idosal/mcp-ui/commit/0943e7acaf17f32aae085c2313bfbec47bc59f1f))\n* ref passing to UIResourceRenderer ([#32](https://github.com/idosal/mcp-ui/issues/32)) ([d28c23f](https://github.com/idosal/mcp-ui/commit/d28c23f9b8ee320f4e361200ae02a23f0d2a1c0c))\n* release ([420efc0](https://github.com/idosal/mcp-ui/commit/420efc0a82bf8de2731514648268cad1209320e2))\n* remove shared dependency ([e66e8f4](https://github.com/idosal/mcp-ui/commit/e66e8f49b1ba46090db6e4682060488566f4fe41))\n* rename components and methods to fit new scope ([#22](https://github.com/idosal/mcp-ui/issues/22)) ([6bab1fe](https://github.com/idosal/mcp-ui/commit/6bab1fe3a168a18e7ba4762e23478abf4e0cc84c))\n* rename delivery -> encoding and flavor -> framework ([#36](https://github.com/idosal/mcp-ui/issues/36)) ([9a509ed](https://github.com/idosal/mcp-ui/commit/9a509ed80d051b0a8042b36958b401a0a7c1e138))\n* Ruby comment ([b22dc2e](https://github.com/idosal/mcp-ui/commit/b22dc2e0a0db20d98ada884649ad408ebaf72d22))\n* support react-router ([21ffb95](https://github.com/idosal/mcp-ui/commit/21ffb95fe6d77a348b95b38dbf3741ba6442894e))\n* text and blob support in RemoteDOM resources ([ec68eb9](https://github.com/idosal/mcp-ui/commit/ec68eb90df984da8b492cc25eafdafdeda79f299))\n* trigger release ([aaca831](https://github.com/idosal/mcp-ui/commit/aaca83125c3f7825ccdebf0f04f8553e953c5249))\n* typescript ci publish ([e7c0ebf](https://github.com/idosal/mcp-ui/commit/e7c0ebfa7f7b552f9763743fda659d1441f21692))\n* typescript types to be compatible with MCP SDK ([#10](https://github.com/idosal/mcp-ui/issues/10)) ([74365d7](https://github.com/idosal/mcp-ui/commit/74365d7ed6422beef6cd9ee0f5a97c847bd9827b))\n* update deps ([4091ef4](https://github.com/idosal/mcp-ui/commit/4091ef47da048fab3c4feb002f5287b2ff295744))\n* update isUIResource to use EmbeddedResource type ([#122](https://github.com/idosal/mcp-ui/issues/122)) ([5a65a0b](https://github.com/idosal/mcp-ui/commit/5a65a0b1ba63e6cfda26b8da41239a532f00d60a)), closes [#117](https://github.com/idosal/mcp-ui/issues/117)\n* use targetOrigin in the proxy message relay ([#40](https://github.com/idosal/mcp-ui/issues/40)) ([b3fb54e](https://github.com/idosal/mcp-ui/commit/b3fb54e28ca7b8eeda896b5bcf478b6343dbba47))\n* validate URL ([b7c994d](https://github.com/idosal/mcp-ui/commit/b7c994dfdd947b3dfbb903fc8cb896d61004c8d8))\n* wc dist overwrite ([#63](https://github.com/idosal/mcp-ui/issues/63)) ([9e46c56](https://github.com/idosal/mcp-ui/commit/9e46c56c7a8908410fad6d08a5d845139e93f80f))\n\n\n### Documentation\n\n* bump ([#4](https://github.com/idosal/mcp-ui/issues/4)) ([ad4d163](https://github.com/idosal/mcp-ui/commit/ad4d1632cc1f9c99072349a8f0cdaac343236132))\n\n\n### Features\n\n* add adapters infra (appssdk) ([#125](https://github.com/idosal/mcp-ui/issues/125)) ([2e016cd](https://github.com/idosal/mcp-ui/commit/2e016cdc05d08c2f7c2e4a40efbec2b0704e7ef6))\n* add convenience function isUIResource to client SDK ([#86](https://github.com/idosal/mcp-ui/issues/86)) ([607c6ad](https://github.com/idosal/mcp-ui/commit/607c6add3567bb60c45accf3e1b25a38ed284a6f))\n* add embeddedResourceProps for annotations ([#99](https://github.com/idosal/mcp-ui/issues/99)) ([b96ec44](https://github.com/idosal/mcp-ui/commit/b96ec442ec319a1944393ada0bdcccb93b7ffc62))\n* add proxy option to externalUrl ([#37](https://github.com/idosal/mcp-ui/issues/37)) ([7b95cd0](https://github.com/idosal/mcp-ui/commit/7b95cd0b3873fc1cde28748ec463e81c6ff1c494))\n* add remote-dom content type ([#18](https://github.com/idosal/mcp-ui/issues/18)) ([5dacf37](https://github.com/idosal/mcp-ui/commit/5dacf37c22b5ee6ae795049a8d573fc073b8a1f5))\n* add Ruby server SDK ([#31](https://github.com/idosal/mcp-ui/issues/31)) ([5ffcde4](https://github.com/idosal/mcp-ui/commit/5ffcde4a373accdd063fa6c3b1b3d4df13c91b53))\n* add sandbox permissions instead of an override ([#83](https://github.com/idosal/mcp-ui/issues/83)) ([b1068e9](https://github.com/idosal/mcp-ui/commit/b1068e9e87caa2b4302bf145a33efdfd1af05c1d))\n* add ui-request-render-data message type ([#111](https://github.com/idosal/mcp-ui/issues/111)) ([26135ce](https://github.com/idosal/mcp-ui/commit/26135ce2c7f7d586b0b81a03623cd77dc1bc7f90))\n* add UIResourceRenderer Web Component ([#58](https://github.com/idosal/mcp-ui/issues/58)) ([ec8f299](https://github.com/idosal/mcp-ui/commit/ec8f2994ecf36774e6ad5191654ba22946d0ee49))\n* auto resize with the autoResizeIframe prop ([#56](https://github.com/idosal/mcp-ui/issues/56)) ([76c867a](https://github.com/idosal/mcp-ui/commit/76c867a569b72aed892290aa84e1194ab8eb79ce))\n* change onGenericMcpAction to optional onUiAction ([1913b59](https://github.com/idosal/mcp-ui/commit/1913b5977c30811f9e67659949e2d961f2eda983))\n* **client:** allow setting supportedContentTypes for HtmlResource ([#17](https://github.com/idosal/mcp-ui/issues/17)) ([e009ef1](https://github.com/idosal/mcp-ui/commit/e009ef10010134ba3d9893314cc4d8e1274f1f07))\n* consolidate ui:// and ui-app:// ([#8](https://github.com/idosal/mcp-ui/issues/8)) ([2e08035](https://github.com/idosal/mcp-ui/commit/2e08035676bb6a46ef3c94dba916bc895f1fa3cc))\n* pass iframe props down ([#14](https://github.com/idosal/mcp-ui/issues/14)) ([112539d](https://github.com/idosal/mcp-ui/commit/112539d28640a96e8375a6b416f2ba559370b312))\n* refactor UTFtoB64 (bump server version) ([#95](https://github.com/idosal/mcp-ui/issues/95)) ([2d5e16b](https://github.com/idosal/mcp-ui/commit/2d5e16bf39073ee890586f458412f0c3b474c2b8))\n* send render data to the iframe ([#51](https://github.com/idosal/mcp-ui/issues/51)) ([d38cfc7](https://github.com/idosal/mcp-ui/commit/d38cfc7925061c1ae1911bdee408033c8e9f283d))\n* separate html and remote-dom props ([#24](https://github.com/idosal/mcp-ui/issues/24)) ([a7f0529](https://github.com/idosal/mcp-ui/commit/a7f05299dc9cc40184f9ab25c5b648ee7077be64))\n* support generic messages response ([#35](https://github.com/idosal/mcp-ui/issues/35)) ([10b407b](https://github.com/idosal/mcp-ui/commit/10b407b279b3ee9608ef077445f4d714f88343c5))\n* support passing resource metadata ([#87](https://github.com/idosal/mcp-ui/issues/87)) ([f1c1c9b](https://github.com/idosal/mcp-ui/commit/f1c1c9b62dd74c63045b295eb388181843ac772a))\n* support ui action result types ([#6](https://github.com/idosal/mcp-ui/issues/6)) ([899d152](https://github.com/idosal/mcp-ui/commit/899d1527286a281a23fbb8f3a207d435dfc3fe96))\n* switch to ResourceRenderer ([#21](https://github.com/idosal/mcp-ui/issues/21)) ([6fe3166](https://github.com/idosal/mcp-ui/commit/6fe316682675e27db914d60696754677e3783448))\n\n\n### BREAKING CHANGES\n\n* The existing naming is ambiguous. Renaming delivery to encoding and flavor to framework should clarify the intent.\n* exported names have changed\n* removed deprecated client API\n* (previous one didn't take due to semantic-release misalignment)\n\n# [5.13.0-alpha.2](https://github.com/idosal/mcp-ui/compare/v5.13.0-alpha.1...v5.13.0-alpha.2) (2025-10-10)\n\n\n### Bug Fixes\n\n* adapter version ([259c842](https://github.com/idosal/mcp-ui/commit/259c84247a00933575e1fff08674cce52be59973))\n\n# [5.13.0-alpha.1](https://github.com/idosal/mcp-ui/compare/v5.12.1...v5.13.0-alpha.1) (2025-10-10)\n\n\n### Features\n\n* add adapters infra (appssdk) ([#125](https://github.com/idosal/mcp-ui/issues/125)) ([2e016cd](https://github.com/idosal/mcp-ui/commit/2e016cdc05d08c2f7c2e4a40efbec2b0704e7ef6))\n\n## [5.12.1](https://github.com/idosal/mcp-ui/compare/v5.12.0...v5.12.1) (2025-10-08)\n\n\n### Bug Fixes\n\n* update isUIResource to use EmbeddedResource type ([#122](https://github.com/idosal/mcp-ui/issues/122)) ([5a65a0b](https://github.com/idosal/mcp-ui/commit/5a65a0b1ba63e6cfda26b8da41239a532f00d60a)), closes [#117](https://github.com/idosal/mcp-ui/issues/117)\n\n# [5.12.0](https://github.com/idosal/mcp-ui/compare/v5.11.0...v5.12.0) (2025-09-23)\n\n\n### Features\n\n* add ui-request-render-data message type ([#111](https://github.com/idosal/mcp-ui/issues/111)) ([26135ce](https://github.com/idosal/mcp-ui/commit/26135ce2c7f7d586b0b81a03623cd77dc1bc7f90))\n\n# [5.9.0](https://github.com/idosal/mcp-ui/compare/v5.8.1...v5.9.0) (2025-08-20)\n\n\n### Features\n\n* support passing resource metadata ([#87](https://github.com/idosal/mcp-ui/issues/87)) ([f1c1c9b](https://github.com/idosal/mcp-ui/commit/f1c1c9b62dd74c63045b295eb388181843ac772a))\n\n## [5.8.1](https://github.com/idosal/mcp-ui/compare/v5.8.0...v5.8.1) (2025-08-20)\n\n\n### Bug Fixes\n\n* move react dependencies to be peer dependencies ([#91](https://github.com/idosal/mcp-ui/issues/91)) ([f672f3e](https://github.com/idosal/mcp-ui/commit/f672f3efc1c2ba2fbae16f9dcdc2142c2b4bd920)), closes [#90](https://github.com/idosal/mcp-ui/issues/90)\n\n# [5.8.0](https://github.com/idosal/mcp-ui/compare/v5.7.0...v5.8.0) (2025-08-19)\n\n\n### Bug Fixes\n\n* minor typo ([a0bee9c](https://github.com/idosal/mcp-ui/commit/a0bee9c85e5ee02e021ba687940ced38220445fe))\n\n\n### Features\n\n* add convenience function isUIResource to client SDK ([#86](https://github.com/idosal/mcp-ui/issues/86)) ([607c6ad](https://github.com/idosal/mcp-ui/commit/607c6add3567bb60c45accf3e1b25a38ed284a6f))\n\n# [5.7.0](https://github.com/idosal/mcp-ui/compare/v5.6.2...v5.7.0) (2025-08-15)\n\n\n### Features\n\n* add sandbox permissions instead of an override ([#83](https://github.com/idosal/mcp-ui/issues/83)) ([b1068e9](https://github.com/idosal/mcp-ui/commit/b1068e9e87caa2b4302bf145a33efdfd1af05c1d))\n\n## [5.6.2](https://github.com/idosal/mcp-ui/compare/v5.6.1...v5.6.2) (2025-08-05)\n\n\n### Bug Fixes\n\n* bump client version ([75c9236](https://github.com/idosal/mcp-ui/commit/75c923689654b4443ad1093fafc0bad16902e4cc))\n\n## [5.6.1](https://github.com/idosal/mcp-ui/compare/v5.6.0...v5.6.1) (2025-08-05)\n\n\n### Bug Fixes\n\n* wc dist overwrite ([#63](https://github.com/idosal/mcp-ui/issues/63)) ([9e46c56](https://github.com/idosal/mcp-ui/commit/9e46c56c7a8908410fad6d08a5d845139e93f80f))\n\n# [5.6.0](https://github.com/idosal/mcp-ui/compare/v5.5.0...v5.6.0) (2025-08-03)\n\n\n### Features\n\n* add UIResourceRenderer Web Component ([#58](https://github.com/idosal/mcp-ui/issues/58)) ([ec8f299](https://github.com/idosal/mcp-ui/commit/ec8f2994ecf36774e6ad5191654ba22946d0ee49))\n\n# [5.5.0](https://github.com/idosal/mcp-ui/compare/v5.4.0...v5.5.0) (2025-08-02)\n\n\n### Features\n\n* auto resize with the autoResizeIframe prop ([#56](https://github.com/idosal/mcp-ui/issues/56)) ([76c867a](https://github.com/idosal/mcp-ui/commit/76c867a569b72aed892290aa84e1194ab8eb79ce))\n\n# [5.4.0](https://github.com/idosal/mcp-ui/compare/v5.3.1...v5.4.0) (2025-08-01)\n\n\n### Features\n\n* send render data to the iframe ([#51](https://github.com/idosal/mcp-ui/issues/51)) ([d38cfc7](https://github.com/idosal/mcp-ui/commit/d38cfc7925061c1ae1911bdee408033c8e9f283d))\n\n## [5.3.1](https://github.com/idosal/mcp-ui/compare/v5.3.0...v5.3.1) (2025-07-19)\n\n\n### Bug Fixes\n\n* Ruby comment ([b22dc2e](https://github.com/idosal/mcp-ui/commit/b22dc2e0a0db20d98ada884649ad408ebaf72d22))\n* typescript ci publish ([e7c0ebf](https://github.com/idosal/mcp-ui/commit/e7c0ebfa7f7b552f9763743fda659d1441f21692))\n\n# [5.3.0](https://github.com/idosal/mcp-ui/compare/v5.2.0...v5.3.0) (2025-07-19)\n\n\n### Features\n\n* add Ruby server SDK ([#31](https://github.com/idosal/mcp-ui/issues/31)) ([5ffcde4](https://github.com/idosal/mcp-ui/commit/5ffcde4a373accdd063fa6c3b1b3d4df13c91b53))\n"
  },
  {
    "path": "sdks/typescript/client/README.md",
    "content": "## 📦 Model Context Protocol UI SDK\n\n<p align=\"center\">\n  <img width=\"250\" alt=\"image\" src=\"https://github.com/user-attachments/assets/65b9698f-990f-4846-9b2d-88de91d53d4d\" />\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@mcp-ui/server\"><img src=\"https://img.shields.io/npm/v/@mcp-ui/server?label=server&color=green\" alt=\"Server Version\"></a>\n  <a href=\"https://www.npmjs.com/package/@mcp-ui/client\"><img src=\"https://img.shields.io/npm/v/@mcp-ui/client?label=client&color=blue\" alt=\"Client Version\"></a>\n  <a href=\"https://rubygems.org/gems/mcp_ui_server\"><img src=\"https://img.shields.io/gem/v/mcp_ui_server\" alt=\"Ruby Server SDK Version\"></a>\n  <a href=\"https://pypi.org/project/mcp-ui-server/\"><img src=\"https://img.shields.io/pypi/v/mcp-ui-server?label=python&color=yellow\" alt=\"Python Server SDK Version\"></a>\n  <a href=\"https://discord.gg/CEAG4KW7ZH\"><img src=\"https://img.shields.io/discord/1401195140436983879?logo=discord&label=discord\" alt=\"Discord\"></a>\n  <a href=\"https://gitmcp.io/idosal/mcp-ui\"><img src=\"https://img.shields.io/endpoint?url=https://gitmcp.io/badge/idosal/mcp-ui\" alt=\"MCP Documentation\"></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"#-whats-mcp-ui\">What's mcp-ui?</a> •\n  <a href=\"#-core-concepts\">Core Concepts</a> •\n  <a href=\"#-installation\">Installation</a> •\n  <a href=\"#-getting-started\">Getting Started</a> •\n  <a href=\"#-walkthrough\">Walkthrough</a> •\n  <a href=\"#-examples\">Examples</a> •\n  <a href=\"#-supported-hosts\">Supported Hosts</a> •\n  <a href=\"#-security\">Security</a> •\n  <a href=\"#-roadmap\">Roadmap</a> •\n  <a href=\"#-contributing\">Contributing</a> •\n  <a href=\"#-license\">License</a>\n</p>\n\n----\n\n**`mcp-ui`** brings interactive web components to the [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP). Deliver rich, dynamic UI resources directly from your MCP server to be rendered by the client. Take AI interaction to the next level!\n\n> *This project is an experimental community playground for MCP UI ideas. Expect rapid iteration and enhancements!*\n\n<p align=\"center\">\n  <video src=\"https://github.com/user-attachments/assets/7180c822-2dd9-4f38-9d3e-b67679509483\"></video>\n</p>\n\n## 💡 What's `mcp-ui`?\n\n`mcp-ui` is a playground for the open spec of UI over MCP. It offers a collection of community SDKs comprising:\n\n* **`@mcp-ui/server` (TypeScript)**: Utilities to generate UI resources (`UIResource`) on your MCP server.\n* **`@mcp-ui/client` (TypeScript)**: UI components (e.g., `<AppRenderer />`) to render the UI resources and handle their events.\n* **`mcp_ui_server` (Ruby)**: Utilities to generate UI resources on your MCP server in a Ruby environment.\n* **`mcp-ui-server` (Python)**: Utilities to generate UI resources on your MCP server in a Python environment.\n\nTogether, they let you define reusable UI snippets on the server side, seamlessly and securely render them in the client, and react to their actions in the MCP host environment.\n\n## ✨ Core Concepts\n\nIn essence, by using `mcp-ui` SDKs, servers and hosts can agree on contracts that enable them to create and render interactive UI snippets (as a path to a standardized UI approach in MCP).\n\n### UI Resource\nThe primary payload returned from the server to the client is the `UIResource`:\n\n```ts\ninterface UIResource {\n  type: 'resource';\n  resource: {\n    uri: string;       // e.g., ui://component/id\n    mimeType: 'text/html;profile=mcp-app';\n    text?: string;      // HTML content\n    blob?: string;      // Base64-encoded HTML content\n  };\n}\n```\n\n* **`uri`**: Unique identifier for caching and routing\n  * `ui://…` — UI resources\n* **`mimeType`**: `text/html;profile=mcp-app` — the MCP Apps standard MIME type\n* **`text` vs. `blob`**: Choose `text` for simple strings; use `blob` for larger or encoded content.\n\n### AppRenderer\n\nFor MCP Apps hosts, use `AppRenderer` to render tool UIs:\n\n```tsx\nimport { AppRenderer } from '@mcp-ui/client';\n\nfunction ToolUI({ client, toolName, toolInput, toolResult }) {\n  return (\n    <AppRenderer\n      client={client}\n      toolName={toolName}\n      sandbox={{ url: sandboxUrl }}\n      toolInput={toolInput}\n      toolResult={toolResult}\n    />\n  );\n}\n```\n\n#### Advanced Usage\n\nYou can manually wrap HTML with adapters or access adapter scripts directly:\n\n```ts\nimport { wrapHtmlWithAdapters, getAppsSdkAdapterScript } from '@mcp-ui/server';\n\n// Manually wrap HTML with adapters\nconst wrappedHtml = wrapHtmlWithAdapters(\n  '<button>Click me</button>',\n  {\n    appsSdk: {\n      enabled: true,\n      config: { intentHandling: 'ignore' }\n    }\n  }\n);\n\n// Get a specific adapter script\nconst appsSdkScript = getAppsSdkAdapterScript({ timeout: 60000 });\n```\n\n## 🏗️ Installation\n\n### TypeScript\n\n```bash\n# using npm\nnpm install @mcp-ui/server @mcp-ui/client\n\n# or pnpm\npnpm add @mcp-ui/server @mcp-ui/client\n\n# or yarn\nyarn add @mcp-ui/server @mcp-ui/client\n```\n\n### Ruby\n\n```bash\ngem install mcp_ui_server\n```\n\n### Python\n\n```bash\n# using pip\npip install mcp-ui-server\n\n# or uv\nuv add mcp-ui-server\n```\n\n## 🚀 Getting Started\n\nYou can use [GitMCP](https://gitmcp.io/idosal/mcp-ui) to give your IDE access to `mcp-ui`'s latest documentation! \n\n### TypeScript\n\n1. **Server-side**: Build your UI resources\n\n   ```ts\n   import { createUIResource } from '@mcp-ui/server';\n   import {\n    createRemoteComponent,\n    createRemoteDocument,\n    createRemoteText,\n   } from '@remote-dom/core';\n\n   // Inline HTML\n   const htmlResource = createUIResource({\n     uri: 'ui://greeting/1',\n     content: { type: 'rawHtml', htmlString: '<p>Hello, MCP UI!</p>' },\n     encoding: 'text',\n   });\n\n   // External URL\n   const externalUrlResource = createUIResource({\n     uri: 'ui://greeting/1',\n     content: { type: 'externalUrl', iframeUrl: 'https://example.com' },\n     encoding: 'text',\n   });\n\n   // remote-dom\n   const remoteDomResource = createUIResource({\n     uri: 'ui://remote-component/action-button',\n     content: {\n       type: 'remoteDom',\n       script: `\n        const button = document.createElement('ui-button');\n        button.setAttribute('label', 'Click me for a tool call!');\n        button.addEventListener('press', () => {\n          window.parent.postMessage({ type: 'tool', payload: { toolName: 'uiInteraction', params: { action: 'button-click', from: 'remote-dom' } } }, '*');\n        });\n        root.appendChild(button);\n        `,\n       framework: 'react', // or 'webcomponents'\n     },\n     encoding: 'text',\n   });\n   ```\n\n2. **Client-side**: Render in your MCP host\n\n   ```tsx\n   import React from 'react';\n   import { UIResourceRenderer } from '@mcp-ui/client';\n\n   function App({ mcpResource }) {\n     if (\n       mcpResource.type === 'resource' &&\n       mcpResource.resource.uri?.startsWith('ui://')\n     ) {\n       return (\n         <UIResourceRenderer\n           resource={mcpResource.resource}\n           onUIAction={(result) => {\n             console.log('Action:', result);\n           }}\n         />\n       );\n     }\n     return <p>Unsupported resource</p>;\n   }\n   ```\n\n### Python\n\n**Server-side**: Build your UI resources\n\n   ```python\n   from mcp_ui_server import create_ui_resource\n\n   # Inline HTML\n   html_resource = create_ui_resource({\n     \"uri\": \"ui://greeting/1\",\n     \"content\": { \"type\": \"rawHtml\", \"htmlString\": \"<p>Hello, from Python!</p>\" },\n     \"encoding\": \"text\",\n   })\n\n   # External URL\n   external_url_resource = create_ui_resource({\n     \"uri\": \"ui://greeting/2\",\n     \"content\": { \"type\": \"externalUrl\", \"iframeUrl\": \"https://example.com\" },\n     \"encoding\": \"text\",\n   })\n   ```\n\n### Ruby\n\n**Server-side**: Build your UI resources\n\n   ```ruby\n   require 'mcp_ui_server'\n\n   # Inline HTML\n   html_resource = McpUiServer.create_ui_resource(\n     uri: 'ui://greeting/1',\n     content: { type: :raw_html, htmlString: '<p>Hello, from Ruby!</p>' },\n     encoding: :text\n   )\n\n   # External URL\n   external_url_resource = McpUiServer.create_ui_resource(\n     uri: 'ui://greeting/2',\n     content: { type: :external_url, iframeUrl: 'https://example.com' },\n     encoding: :text\n   )\n\n   # remote-dom\n   remote_dom_resource = McpUiServer.create_ui_resource(\n     uri: 'ui://remote-component/action-button',\n     content: {\n       type: :remote_dom,\n       script: \"\n        const button = document.createElement('ui-button');\n        button.setAttribute('label', 'Click me from Ruby!');\n        button.addEventListener('press', () => {\n          window.parent.postMessage({ type: 'tool', payload: { toolName: 'uiInteraction', params: { action: 'button-click', from: 'ruby-remote-dom' } } }, '*');\n        });\n        root.appendChild(button);\n        \",\n       framework: :react,\n     },\n     encoding: :text\n   )\n   ```\n\n## 🚶 Walkthrough\n\nFor a detailed, simple, step-by-step guide on how to integrate `mcp-ui` into your own server, check out the full server walkthroughs on the [mcp-ui documentation site](https://mcpui.dev):\n\n- **[TypeScript Server Walkthrough](https://mcpui.dev/guide/server/typescript/walkthrough)**\n- **[Ruby Server Walkthrough](https://mcpui.dev/guide/server/ruby/walkthrough)**\n- **[Python Server Walkthrough](https://mcpui.dev/guide/server/python/walkthrough)**\n\nThese guides will show you how to add a `mcp-ui` endpoint to an existing server, create tools that return UI resources, and test your setup with the `ui-inspector`!\n\n## 🌍 Examples\n\n**Client Examples**\n* [Goose](https://github.com/block/goose) - open source AI agent that supports `mcp-ui`.\n* [LibreChat](https://github.com/danny-avila/LibreChat) - enhanced ChatGPT clone that supports `mcp-ui`.\n* [ui-inspector](https://github.com/idosal/ui-inspector) - inspect local `mcp-ui`-enabled servers.\n* [MCP-UI Chat](https://github.com/idosal/scira-mcp-ui-chat) - interactive chat built with the `mcp-ui` client. Check out the [hosted version](https://scira-mcp-chat-git-main-idosals-projects.vercel.app/)!\n* MCP-UI RemoteDOM Playground (`examples/remote-dom-demo`) - local demo app to test RemoteDOM resources\n* MCP-UI Web Component Demo (`examples/wc-demo`) - local demo app to test the Web Component integration in hosts\n\n**Server Examples**\n* **TypeScript**: A [full-featured server](examples/server) that is deployed to a hosted environment for easy testing.\n  * **[`typescript-server-demo`](./examples/typescript-server-demo)**: A simple Typescript server that demonstrates how to generate UI resources.\n  * **server**: A [full-featured Typescript server](examples/server) that is deployed to a hosted Cloudflare environment for easy testing.\n    * **HTTP Streaming**: `https://remote-mcp-server-authless.idosalomon.workers.dev/mcp`\n    * **SSE**: `https://remote-mcp-server-authless.idosalomon.workers.dev/sse`\n* **Ruby**: A barebones [demo server](/examples/ruby-server-demo) that shows how to use `mcp_ui_server` and `mcp` gems together.\n* **Python**: A simple [demo server](/examples/python-server-demo) that shows how to use the `mcp-ui-server` Python package.\n* [XMCP](https://github.com/basementstudio/xmcp/tree/main/examples/mcp-ui) - Typescript MCP framework with `mcp-ui` starter example.\n\nDrop those URLs into any MCP-compatible host to see `mcp-ui` in action. For a supported local inspector, see the [ui-inspector](https://github.com/idosal/ui-inspector).\n\n## 💻 Supported Hosts\n\n`mcp-ui` is supported by a growing number of MCP-compatible clients. Feature support varies by host:\n\n| Host      | Rendering | UI Actions | Notes\n| :-------- | :-------: | :--------: | :--------: |\n| [Nanobot](https://www.nanobot.ai/)    |     ✅    |     ✅     |\n| [ChatGPT](https://chatgpt.com/)    |     ✅    |     ⚠️     | [Guide](https://mcpui.dev/guide/apps-sdk)\n| [Postman](https://www.postman.com/)   |     ✅    |     ⚠️      |\n| [Goose](https://block.github.io/goose/)     |     ✅    |     ⚠️      |\n| [LibreChat](https://www.librechat.ai/)    |     ✅    |     ⚠️     |\n| [Smithery](https://smithery.ai/playground)  |     ✅    |     ❌     |\n| [MCPJam](https://www.mcpjam.com/)    |     ✅    |     ❌     |\n| [fast-agent](https://fast-agent.ai/mcp/mcp-ui/) | ✅ | ❌ |\n| [VSCode](https://github.com/microsoft/vscode/issues/260218) (TBA)    |    ?    |    ?     |\n\n**Legend:**\n- ✅: Supported\n- ⚠️: Partial Support\n- ❌: Not Supported (yet)\n\n## 🔒 Security\nHost and user security is one of `mcp-ui`'s primary concerns. In all content types, the remote code is executed in a sandboxed iframe.\n\n## 🛣️ Roadmap\n\n- [X] Add online playground\n- [X] Expand UI Action API (beyond tool calls)\n- [X] Support Web Components\n- [X] Support Remote-DOM\n- [ ] Add component libraries (in progress)\n- [ ] Add SDKs for additional programming languages (in progress; Ruby, Python available)\n- [ ] Support additional frontend frameworks\n- [ ] Add declarative UI content type\n- [ ] Support generative UI?\n      \n## Core Team\n`mcp-ui` is a project by [Ido Salomon](https://x.com/idosal1), in collaboration with [Liad Yosef](https://x.com/liadyosef).\n\n## 🤝 Contributing\n\nContributions, ideas, and bug reports are welcome! See the [contribution guidelines](https://github.com/idosal/mcp-ui/blob/main/.github/CONTRIBUTING.md) to get started.\n\n## 📄 License\n\nApache License 2.0 © [The MCP-UI Authors](LICENSE)\n\n## Disclaimer\n\nThis project is provided \"as is\", without warranty of any kind. The `mcp-ui` authors and contributors shall not be held liable for any damages, losses, or issues arising from the use of this software. Use at your own risk.\n"
  },
  {
    "path": "sdks/typescript/client/package.json",
    "content": "{\n  \"name\": \"@mcp-ui/client\",\n  \"version\": \"7.0.0\",\n  \"description\": \"mcp-ui Client SDK\",\n  \"private\": false,\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./dist/index.mjs\",\n      \"require\": \"./dist/index.js\"\n    }\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"dependencies\": {\n    \"@modelcontextprotocol/ext-apps\": \"^1.2.0\",\n    \"@modelcontextprotocol/sdk\": \"^1.27.1\",\n    \"zod\": \"^3.23.8\"\n  },\n  \"devDependencies\": {\n    \"@testing-library/jest-dom\": \"^6.0.0\",\n    \"@testing-library/react\": \"^14.0.0\",\n    \"@types/jsdom\": \"^27.0.0\",\n    \"@types/react\": \"^18.2.0\",\n    \"@vitejs/plugin-react\": \"^4.0.0\",\n    \"jsdom\": \"^22.0.0\",\n    \"react\": \"^18.3.1\",\n    \"react-dom\": \"^18.3.1\",\n    \"rimraf\": \"^6.0.1\",\n    \"typescript\": \"^5.0.0\",\n    \"vite\": \"^5.0.0\",\n    \"vite-plugin-dts\": \"^3.6.0\",\n    \"vite-tsconfig-paths\": \"^5.1.4\",\n    \"vitest\": \"^1.0.0\"\n  },\n  \"peerDependencies\": {\n    \"react\": \"^18 || ^19\",\n    \"react-dom\": \"^18 || ^19\"\n  },\n  \"scripts\": {\n    \"prepublishOnly\": \"pnpm run build\",\n    \"build\": \"pnpm run clean && tsc && vite build\",\n    \"clean\": \"rimraf dist\",\n    \"dev\": \"vite\",\n    \"test\": \"vitest run\",\n    \"test:watch\": \"vitest watch\",\n    \"lint\": \"eslint .\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"release\": {\n    \"branches\": [\n      \"main\",\n      {\n        \"name\": \"alpha\",\n        \"prerelease\": true\n      }\n    ],\n    \"tagFormat\": \"client/v${version}\",\n    \"plugins\": [\n      \"@semantic-release/commit-analyzer\",\n      \"@semantic-release/release-notes-generator\",\n      [\n        \"@semantic-release/changelog\",\n        {\n          \"changelogFile\": \"CHANGELOG.md\"\n        }\n      ],\n      \"@semantic-release/npm\",\n      \"@semantic-release/github\",\n      [\n        \"@semantic-release/git\",\n        {\n          \"assets\": [\n            \"CHANGELOG.md\",\n            \"package.json\"\n          ],\n          \"message\": \"chore(release): ${nextRelease.version} [skip ci]\\\\n\\\\n${nextRelease.notes}\"\n        }\n      ]\n    ]\n  },\n  \"license\": \"Apache-2.0\"\n}\n"
  },
  {
    "path": "sdks/typescript/client/scripts/proxy/index.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>MCP-UI Proxy</title>\n    <style>\n      html,\n      body {\n        margin: 0;\n        height: 100vh;\n        width: 100vw;\n      }\n      body {\n        display: flex;\n        flex-direction: column;\n      }\n      * {\n        box-sizing: border-box;\n      }\n      iframe {\n        background-color: transparent;\n        border: 0px none transparent;\n        padding: 0px;\n        overflow: hidden;\n        flex-grow: 1;\n      }\n    </style>\n  </head>\n  <body>\n    <script>\n      const params = new URLSearchParams(location.search);\n      const contentType = params.get('contentType');\n      const target = params.get('url');\n\n      // Validate that the URL is a valid HTTP or HTTPS URL\n      function isValidHttpUrl(string) {\n        try {\n          const url = new URL(string);\n          return url.protocol === 'http:' || url.protocol === 'https:';\n        } catch (error) {\n          return false;\n        }\n      }\n\n      if (contentType === 'rawhtml') {\n        // Double-iframe raw HTML mode (HTML sent via postMessage)\n        const inner = document.createElement('iframe');\n\n        inner.id = 'root';\n        let pendingHtml = null;\n\n        // Helper function to write HTML using document.write\n        const renderHtmlInIframe = (markup) => {\n          const doc = inner.contentDocument || inner.contentWindow?.document;\n          if (!doc) return false;\n          try {\n            doc.open();\n            doc.write(markup);\n            doc.close();\n            return true;\n          } catch (error) {\n            console.error('Failed to write HTML to iframe:', error);\n            return false;\n          }\n        };\n\n        // Retry writing pending HTML when iframe finishes loading\n        inner.addEventListener('load', () => {\n          if (pendingHtml !== null && renderHtmlInIframe(pendingHtml)) {\n            pendingHtml = null;\n          }\n        });\n\n        inner.style = 'width:100%; height:100%; border:none;';\n        // Set src to about:blank so browser initializes contentDocument\n        inner.src = 'about:blank';\n        document.body.appendChild(inner);\n\n        // Wait for HTML content from parent\n        window.addEventListener('message', (event) => {\n          if (event.source === window.parent && event.data && event.data.type === 'ui-html-content') {\n            const payload = event.data.payload || {};\n            const html = payload.html;\n            if (typeof html === 'string') {\n              // Try to write immediately; if contentDocument isn't ready, queue for retry\n              if (!renderHtmlInIframe(html)) {\n                pendingHtml = html;\n              }\n            }\n          } else if (event.source === window.parent) {\n            // Forward other messages from parent to inner iframe\n            if (inner && inner.contentWindow) {\n              inner.contentWindow.postMessage(event.data, '*');\n            }\n          } else if (event.source === inner.contentWindow) {\n            // Relay messages from inner to parent\n            window.parent.postMessage(event.data, '*');\n          }\n        });\n\n        // Notify parent that proxy is ready to receive HTML (distinct event)\n        window.parent.postMessage({ type: 'ui-proxy-iframe-ready' }, '*');\n      } else if (target) {\n        if (!isValidHttpUrl(target)) {\n          document.body.textContent = 'Error: invalid URL. Only HTTP and HTTPS URLs are allowed.';\n        } else {\n          const inner = document.createElement('iframe');\n          inner.src = target;\n          inner.style = 'width:100%; height:100%; border:none;';\n          // Default external URL sandbox; can be adjusted later by protocol if needed\n          inner.setAttribute('sandbox', 'allow-same-origin allow-scripts');\n          document.body.appendChild(inner);\n          const urlOrigin = new URL(target).origin;\n\n          window.addEventListener('message', (event) => {\n            if (event.source === window.parent) {\n              // listen for messages from the parent and send them to the iframe\n              inner.contentWindow.postMessage(event.data, urlOrigin);\n            } else if (event.source === inner.contentWindow) {\n              // listen for messages from the iframe and send them to the parent\n              window.parent.postMessage(event.data, '*');\n            }\n          });\n        }\n      } else {\n        document.body.textContent = 'Error: missing url or html parameter';\n      }\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "sdks/typescript/client/src/__tests__/capabilities.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport {\n  type ClientCapabilitiesWithExtensions,\n  UI_EXTENSION_NAME,\n  UI_EXTENSION_CONFIG,\n  UI_EXTENSION_CAPABILITIES,\n} from '../capabilities';\nimport { RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/app-bridge';\n\ndescribe('UI Extension Capabilities', () => {\n  it('should have correct extension name', () => {\n    expect(UI_EXTENSION_NAME).toBe('io.modelcontextprotocol/ui');\n  });\n\n  it('should include RESOURCE_MIME_TYPE in mimeTypes', () => {\n    expect(UI_EXTENSION_CONFIG.mimeTypes).toContain(RESOURCE_MIME_TYPE);\n    expect(UI_EXTENSION_CONFIG.mimeTypes).toEqual(['text/html;profile=mcp-app']);\n  });\n\n  it('should structure capabilities with extension name as key', () => {\n    expect(UI_EXTENSION_CAPABILITIES[UI_EXTENSION_NAME]).toEqual(UI_EXTENSION_CONFIG);\n  });\n\n  it('should work with ClientCapabilitiesWithExtensions type', () => {\n    const capabilities: ClientCapabilitiesWithExtensions = {\n      roots: { listChanged: true },\n      extensions: UI_EXTENSION_CAPABILITIES,\n    };\n\n    expect(capabilities.roots).toEqual({ listChanged: true });\n    expect(capabilities.extensions?.[UI_EXTENSION_NAME]).toEqual(UI_EXTENSION_CONFIG);\n  });\n\n  it('should allow combining with other MCP capabilities', () => {\n    const capabilities: ClientCapabilitiesWithExtensions = {\n      roots: { listChanged: true },\n      sampling: {},\n      extensions: {\n        ...UI_EXTENSION_CAPABILITIES,\n        'custom/extension': { customKey: 'customValue' },\n      },\n    };\n\n    expect(capabilities.roots).toEqual({ listChanged: true });\n    expect(capabilities.sampling).toEqual({});\n    expect(capabilities.extensions?.[UI_EXTENSION_NAME]).toEqual(UI_EXTENSION_CONFIG);\n    expect(capabilities.extensions?.['custom/extension']).toEqual({ customKey: 'customValue' });\n  });\n\n  it('should allow extensions field to be optional', () => {\n    const capabilities: ClientCapabilitiesWithExtensions = {\n      roots: { listChanged: true },\n    };\n\n    expect(capabilities.extensions).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "sdks/typescript/client/src/capabilities.ts",
    "content": "import type { ClientCapabilities } from '@modelcontextprotocol/sdk/types.js';\nimport { RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/app-bridge';\n\n/**\n * Extended ClientCapabilities type that includes the `extensions` field.\n *\n * This type is a forward-compatible extension of the MCP SDK's ClientCapabilities,\n * adding support for the `extensions` field pattern proposed in SEP-1724.\n *\n * @see https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1724\n */\nexport interface ClientCapabilitiesWithExtensions extends ClientCapabilities {\n  extensions?: {\n    [extensionName: string]: unknown;\n  };\n}\n\n/**\n * Extension identifier for MCP UI support.\n *\n * Follows the pattern from SEP-1724: `{vendor-prefix}/{extension-name}`\n *\n * @see https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1724\n */\nexport const UI_EXTENSION_NAME = 'io.modelcontextprotocol/ui' as const;\n\n/**\n * UI extension capability configuration.\n *\n * Declares support for rendering UI resources with specific MIME types.\n */\nexport const UI_EXTENSION_CONFIG = {\n  mimeTypes: [RESOURCE_MIME_TYPE],\n} as const;\n\n/**\n * UI extension capabilities object to use in the `extensions` field.\n *\n * Use this when creating an MCP Client to declare support for rendering\n * UI resources.\n *\n * @example\n * ```typescript\n * import { Client } from '@modelcontextprotocol/sdk/client/index.js';\n * import {\n *   type ClientCapabilitiesWithExtensions,\n *   UI_EXTENSION_CAPABILITIES,\n * } from '@mcp-ui/client';\n *\n * const capabilities: ClientCapabilitiesWithExtensions = {\n *   // Standard MCP capabilities\n *   roots: { listChanged: true },\n *   // UI extension support (SEP-1724 pattern)\n *   extensions: UI_EXTENSION_CAPABILITIES,\n * };\n *\n * const client = new Client(\n *   { name: 'my-app', version: '1.0.0' },\n *   { capabilities }\n * );\n * ```\n *\n * @see https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1724\n */\nexport const UI_EXTENSION_CAPABILITIES = {\n  [UI_EXTENSION_NAME]: UI_EXTENSION_CONFIG,\n} as const;\n"
  },
  {
    "path": "sdks/typescript/client/src/components/AppFrame.tsx",
    "content": "import { useEffect, useRef, useState } from 'react';\n\nimport type { CallToolResult, Implementation } from '@modelcontextprotocol/sdk/types.js';\n\nimport {\n  type AppBridge,\n  PostMessageTransport,\n  type McpUiSizeChangedNotification,\n  type McpUiResourceCsp,\n  type McpUiAppCapabilities,\n} from '@modelcontextprotocol/ext-apps/app-bridge';\n\nimport { setupSandboxProxyIframe } from '../utils/app-host-utils';\n\n/**\n * Build sandbox URL with CSP query parameter for HTTP header-based CSP enforcement.\n *\n * When the proxy server supports it, CSP passed via query parameter allows the server\n * to set CSP via HTTP headers (tamper-proof) rather than relying on meta tags or\n * postMessage-based CSP injection (which can be bypassed by malicious content).\n *\n * @see https://github.com/modelcontextprotocol/ext-apps/pull/234\n */\nfunction buildSandboxUrl(baseUrl: URL, csp?: McpUiResourceCsp): URL {\n  const url = new URL(baseUrl.href);\n  if (csp && Object.keys(csp).length > 0) {\n    url.searchParams.set('csp', JSON.stringify(csp));\n  }\n  return url;\n}\n\n/**\n * Information about the guest app, available after initialization.\n */\nexport interface AppInfo {\n  /** Guest app's name and version */\n  appVersion?: Implementation;\n  /** Guest app's declared capabilities */\n  appCapabilities?: McpUiAppCapabilities;\n}\n\n/**\n * Sandbox configuration for the iframe.\n */\nexport interface SandboxConfig {\n  /** URL to the sandbox proxy HTML */\n  url: URL;\n  /** Override iframe sandbox attribute (default: \"allow-scripts allow-same-origin allow-forms\") */\n  permissions?: string;\n  /**\n   * CSP metadata for the sandbox.\n   *\n   * This CSP is passed to the sandbox proxy in two ways:\n   * 1. Via URL query parameter (`?csp=<json>`) - allows servers that support it to set\n   *    CSP via HTTP headers (tamper-proof, recommended)\n   * 2. Via postMessage after sandbox loads - fallback for proxies that don't parse query params\n   *\n   * For maximum security, use a proxy server that reads the `csp` query parameter and sets\n   * Content-Security-Policy HTTP headers accordingly.\n   *\n   * @see https://github.com/modelcontextprotocol/ext-apps/pull/234\n   */\n  csp?: McpUiResourceCsp;\n}\n\n/**\n * Props for the AppFrame component.\n */\nexport interface AppFrameProps {\n  /** Pre-fetched HTML content to render in the sandbox */\n  html: string;\n\n  /** Sandbox configuration */\n  sandbox: SandboxConfig;\n\n  /** Pre-configured AppBridge for MCP communication (required) */\n  appBridge: AppBridge;\n\n  /** Callback when guest reports size change */\n  onSizeChanged?: (params: McpUiSizeChangedNotification['params']) => void;\n\n  /** Callback when app initialization completes, with app info */\n  onInitialized?: (appInfo: AppInfo) => void;\n\n  /** Tool input arguments to send when app initializes */\n  toolInput?: Record<string, unknown>;\n\n  /** Tool result to send when app initializes */\n  toolResult?: CallToolResult;\n\n  /** Callback when an error occurs */\n  onError?: (error: Error) => void;\n}\n\n/**\n * Low-level component that renders pre-fetched HTML in a sandboxed iframe.\n *\n * This component requires a pre-configured AppBridge for MCP communication.\n * For automatic AppBridge creation and resource fetching, use the higher-level\n * AppRenderer component instead.\n *\n * @example With pre-configured AppBridge\n * ```tsx\n * const appBridge = new AppBridge(client, hostInfo, capabilities);\n * // ... configure appBridge handlers ...\n *\n * <AppFrame\n *   html={htmlContent}\n *   sandbox={{ url: sandboxUrl }}\n *   appBridge={appBridge}\n *   toolInput={args}\n *   toolResult={result}\n *   onSizeChanged={({ width, height }) => console.log('Size:', width, height)}\n * />\n * ```\n */\nexport const AppFrame = (props: AppFrameProps) => {\n  const { html, sandbox, appBridge, onSizeChanged, onInitialized, toolInput, toolResult, onError } =\n    props;\n\n  const [iframeReady, setIframeReady] = useState(false);\n  const [bridgeConnected, setBridgeConnected] = useState(false);\n  const [error, setError] = useState<Error | null>(null);\n  const containerRef = useRef<HTMLDivElement | null>(null);\n  const iframeRef = useRef<HTMLIFrameElement | null>(null);\n  // Track the current sandbox URL to detect when it changes\n  const currentSandboxUrlRef = useRef<string | null>(null);\n  // Track the current appBridge to detect when it changes (for isolation)\n  const currentAppBridgeRef = useRef<AppBridge | null>(null);\n\n  // Refs for callbacks to avoid effect re-runs\n  const onSizeChangedRef = useRef(onSizeChanged);\n  const onInitializedRef = useRef(onInitialized);\n  const onErrorRef = useRef(onError);\n\n  useEffect(() => {\n    onSizeChangedRef.current = onSizeChanged;\n    onInitializedRef.current = onInitialized;\n    onErrorRef.current = onError;\n  });\n\n  // Effect 1: Set up sandbox iframe and connect AppBridge\n  useEffect(() => {\n    // Build sandbox URL with CSP query parameter for HTTP header-based CSP enforcement.\n    // Servers that support this will parse the CSP from the query param and set it via\n    // HTTP headers (tamper-proof). The CSP is also sent via postMessage as fallback.\n    const sandboxUrl = buildSandboxUrl(sandbox.url, sandbox.csp);\n    const sandboxUrlString = sandboxUrl.href;\n\n    // If we already have an iframe set up for this sandbox URL AND the same appBridge, skip setup\n    // This preserves the iframe state across React re-renders (including StrictMode)\n    // but ensures isolation when switching to a different app/resource (different appBridge)\n    if (\n      currentSandboxUrlRef.current === sandboxUrlString &&\n      currentAppBridgeRef.current === appBridge &&\n      iframeRef.current\n    ) {\n      return;\n    }\n\n    // Reset state when setting up a new iframe/bridge to ensure isolation\n    // between different apps/resources\n    setIframeReady(false);\n    setBridgeConnected(false);\n    setError(null);\n\n    let mounted = true;\n\n    const setup = async () => {\n      try {\n        // If switching to a different sandbox URL or appBridge, clean up the old iframe first\n        if (iframeRef.current && containerRef.current?.contains(iframeRef.current)) {\n          containerRef.current.removeChild(iframeRef.current);\n          iframeRef.current = null;\n          currentSandboxUrlRef.current = null;\n          currentAppBridgeRef.current = null;\n        }\n\n        const { iframe, onReady } = await setupSandboxProxyIframe(sandboxUrl);\n\n        if (!mounted) return;\n\n        iframeRef.current = iframe;\n        currentSandboxUrlRef.current = sandboxUrlString;\n        currentAppBridgeRef.current = appBridge;\n        if (containerRef.current) {\n          containerRef.current.appendChild(iframe);\n        }\n\n        await onReady;\n\n        if (!mounted) return;\n\n        // Register size change handler\n        appBridge.onsizechange = async (params) => {\n          onSizeChangedRef.current?.(params);\n          // Also update iframe size\n          if (iframeRef.current) {\n            if (params.width !== undefined) {\n              iframeRef.current.style.width = `${params.width}px`;\n            }\n            if (params.height !== undefined) {\n              iframeRef.current.style.height = `${params.height}px`;\n            }\n          }\n        };\n\n        // Hook into initialization\n        appBridge.oninitialized = () => {\n          if (!mounted) return;\n          console.log('[AppFrame] App initialized');\n          setIframeReady(true);\n          onInitializedRef.current?.({\n            appVersion: appBridge.getAppVersion(),\n            appCapabilities: appBridge.getAppCapabilities(),\n          });\n        };\n\n        // Connect the bridge\n        await appBridge.connect(\n          new PostMessageTransport(iframe.contentWindow!, iframe.contentWindow!),\n        );\n\n        if (!mounted) return;\n\n        setBridgeConnected(true);\n      } catch (err) {\n        console.error('[AppFrame] Error:', err);\n        if (!mounted) return;\n        const error = err instanceof Error ? err : new Error(String(err));\n        setError(error);\n        onErrorRef.current?.(error);\n      }\n    };\n\n    setup();\n\n    return () => {\n      mounted = false;\n    };\n  }, [sandbox.url, sandbox.csp, appBridge]);\n\n  // Effect 2: Send HTML to sandbox when bridge is connected\n  useEffect(() => {\n    // Ensure we only send HTML to the correct appBridge that's currently connected\n    // This prevents race conditions when switching between apps\n    if (!bridgeConnected || !html || currentAppBridgeRef.current !== appBridge) return;\n\n    const sendHtml = async () => {\n      try {\n        console.log('[AppFrame] Sending HTML to sandbox');\n        await appBridge.sendSandboxResourceReady({\n          html,\n          csp: sandbox.csp,\n        });\n      } catch (err) {\n        const error = err instanceof Error ? err : new Error(String(err));\n        setError(error);\n        onErrorRef.current?.(error);\n      }\n    };\n\n    sendHtml();\n  }, [bridgeConnected, html, appBridge, sandbox.csp]);\n\n  // Effect 3: Send tool input when ready\n  useEffect(() => {\n    // Ensure we only send to the correct appBridge that's currently connected\n    if (bridgeConnected && iframeReady && toolInput && currentAppBridgeRef.current === appBridge) {\n      console.log('[AppFrame] Sending tool input:', toolInput);\n      appBridge.sendToolInput({ arguments: toolInput });\n    }\n  }, [appBridge, bridgeConnected, iframeReady, toolInput]);\n\n  // Effect 4: Send tool result when ready\n  useEffect(() => {\n    // Ensure we only send to the correct appBridge that's currently connected\n    if (bridgeConnected && iframeReady && toolResult && currentAppBridgeRef.current === appBridge) {\n      console.log('[AppFrame] Sending tool result:', toolResult);\n      appBridge.sendToolResult(toolResult);\n    }\n  }, [appBridge, bridgeConnected, iframeReady, toolResult]);\n\n  return (\n    <div\n      ref={containerRef}\n      style={{\n        width: '100%',\n        height: '100%',\n        display: 'flex',\n        flexDirection: 'column',\n      }}\n    >\n      {error && <div style={{ color: 'red', padding: '1rem' }}>Error: {error.message}</div>}\n    </div>\n  );\n};\n"
  },
  {
    "path": "sdks/typescript/client/src/components/AppRenderer.tsx",
    "content": "import { useEffect, useRef, useState, useImperativeHandle, forwardRef } from 'react';\n\nimport { type Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport {\n  type CallToolRequest,\n  type CallToolResult,\n  type JSONRPCRequest,\n  type ListPromptsRequest,\n  type ListPromptsResult,\n  type ListResourcesRequest,\n  type ListResourcesResult,\n  type ListResourceTemplatesRequest,\n  type ListResourceTemplatesResult,\n  type LoggingMessageNotification,\n  type ReadResourceRequest,\n  type ReadResourceResult,\n  McpError,\n  ErrorCode,\n} from '@modelcontextprotocol/sdk/types.js';\n\nimport {\n  AppBridge,\n  RESOURCE_MIME_TYPE,\n  type McpUiMessageRequest,\n  type McpUiMessageResult,\n  type McpUiOpenLinkRequest,\n  type McpUiOpenLinkResult,\n  type McpUiSizeChangedNotification,\n  type McpUiToolInputPartialNotification,\n  type McpUiHostContext,\n} from '@modelcontextprotocol/ext-apps/app-bridge';\n\nimport { AppFrame, type SandboxConfig } from './AppFrame';\nimport { getToolUiResourceUri, readToolUiResourceHtml } from '../utils/app-host-utils';\n\n/**\n * Extra metadata passed to request handlers (from AppBridge).\n */\nexport type RequestHandlerExtra = Parameters<Parameters<AppBridge['setRequestHandler']>[1]>[1];\n\n/**\n * Handle to access AppRenderer methods for sending notifications to the Guest UI.\n * Obtained via ref on AppRenderer.\n */\nexport interface AppRendererHandle {\n  /** Notify the Guest UI that the server's tool list has changed */\n  sendToolListChanged: () => void;\n  /** Notify the Guest UI that the server's resource list has changed */\n  sendResourceListChanged: () => void;\n  /** Notify the Guest UI that the server's prompt list has changed */\n  sendPromptListChanged: () => void;\n  /** Notify the Guest UI that the resource is being torn down / cleaned up */\n  teardownResource: () => void;\n}\n\n/**\n * Props for the AppRenderer component.\n */\nexport interface AppRendererProps {\n  /** MCP client connected to the server providing the tool. Omit to disable automatic MCP forwarding and use custom handlers instead. */\n  client?: Client;\n\n  /** Name of the MCP tool to render UI for */\n  toolName: string;\n\n  /** Sandbox configuration */\n  sandbox: SandboxConfig;\n\n  /** Optional pre-fetched resource URI. If not provided, will be fetched via getToolUiResourceUri() */\n  toolResourceUri?: string;\n\n  /** Optional pre-fetched HTML. If provided, skips all resource fetching */\n  html?: string;\n\n  /** Optional input arguments to pass to the tool UI once it's ready */\n  toolInput?: Record<string, unknown>;\n\n  /** Optional result from tool execution to pass to the tool UI once it's ready */\n  toolResult?: CallToolResult;\n\n  /** Partial/streaming tool input to send to the guest UI */\n  toolInputPartial?: McpUiToolInputPartialNotification['params'];\n\n  /** Set to true to notify the guest UI that the tool execution was cancelled */\n  toolCancelled?: boolean;\n\n  /** Host context (theme, viewport, locale, etc.) to pass to the guest UI */\n  hostContext?: McpUiHostContext;\n\n  /** Handler for open-link requests from the guest UI */\n  onOpenLink?: (\n    params: McpUiOpenLinkRequest['params'],\n    extra: RequestHandlerExtra,\n  ) => Promise<McpUiOpenLinkResult>;\n\n  /** Handler for message requests from the guest UI */\n  onMessage?: (\n    params: McpUiMessageRequest['params'],\n    extra: RequestHandlerExtra,\n  ) => Promise<McpUiMessageResult>;\n\n  /** Handler for logging messages from the guest UI */\n  onLoggingMessage?: (params: LoggingMessageNotification['params']) => void;\n\n  /** Handler for size change notifications from the guest UI */\n  onSizeChanged?: (params: McpUiSizeChangedNotification['params']) => void;\n\n  /** Callback invoked when an error occurs during setup or message handling */\n  onError?: (error: Error) => void;\n\n  // --- MCP Request Handlers (override automatic forwarding) ---\n\n  /**\n   * Handler for tools/call requests from the guest UI.\n   * If provided, overrides the automatic forwarding to the MCP client.\n   */\n  onCallTool?: (\n    params: CallToolRequest['params'],\n    extra: RequestHandlerExtra,\n  ) => Promise<CallToolResult>;\n\n  /**\n   * Handler for resources/list requests from the guest UI.\n   * If provided, overrides the automatic forwarding to the MCP client.\n   */\n  onListResources?: (\n    params: ListResourcesRequest['params'],\n    extra: RequestHandlerExtra,\n  ) => Promise<ListResourcesResult>;\n\n  /**\n   * Handler for resources/templates/list requests from the guest UI.\n   * If provided, overrides the automatic forwarding to the MCP client.\n   */\n  onListResourceTemplates?: (\n    params: ListResourceTemplatesRequest['params'],\n    extra: RequestHandlerExtra,\n  ) => Promise<ListResourceTemplatesResult>;\n\n  /**\n   * Handler for resources/read requests from the guest UI.\n   * If provided, overrides the automatic forwarding to the MCP client.\n   */\n  onReadResource?: (\n    params: ReadResourceRequest['params'],\n    extra: RequestHandlerExtra,\n  ) => Promise<ReadResourceResult>;\n\n  /**\n   * Handler for prompts/list requests from the guest UI.\n   * If provided, overrides the automatic forwarding to the MCP client.\n   */\n  onListPrompts?: (\n    params: ListPromptsRequest['params'],\n    extra: RequestHandlerExtra,\n  ) => Promise<ListPromptsResult>;\n\n  /**\n   * Handler for JSON-RPC requests from the guest UI that don't match any\n   * built-in handler (e.g., experimental methods like \"x/clipboard/write\",\n   * or standard MCP methods not yet in the Apps spec like \"sampling/createMessage\").\n   *\n   * This is wired to AppBridge's `fallbackRequestHandler` from the MCP SDK Protocol class.\n   * It receives the full JSON-RPC request and should return a result object or throw\n   * a McpError for unsupported methods.\n   *\n   * @example\n   * ```tsx\n   * <AppRenderer\n   *   onFallbackRequest={async (request, extra) => {\n   *     switch (request.method) {\n   *       case 'x/clipboard/write':\n   *         await navigator.clipboard.writeText(request.params?.text);\n   *         return { success: true };\n   *       case 'sampling/createMessage':\n   *         return mcpClient.createMessage(request.params);\n   *       default:\n   *         throw new McpError(ErrorCode.MethodNotFound, `Unknown method: ${request.method}`);\n   *     }\n   *   }}\n   * />\n   * ```\n   */\n  onFallbackRequest?: (\n    request: JSONRPCRequest,\n    extra: RequestHandlerExtra,\n  ) => Promise<Record<string, unknown>>;\n}\n\n/**\n * React component that renders an MCP tool's custom UI in a sandboxed iframe.\n *\n * This component manages the complete lifecycle of an MCP Apps tool:\n * 1. Creates AppBridge for MCP communication\n * 2. Fetches the tool's UI resource (HTML) if not provided\n * 3. Delegates rendering to AppFrame\n * 4. Handles UI actions (intents, link opening, prompts, notifications)\n *\n * For lower-level control or when you already have the HTML content,\n * use the AppFrame component directly.\n *\n * @example Basic usage\n * ```tsx\n * <AppRenderer\n *   sandbox={{ url: new URL('http://localhost:8765/sandbox_proxy.html') }}\n *   client={mcpClient}\n *   toolName=\"create-chart\"\n *   toolInput={{ data: [1, 2, 3], type: 'bar' }}\n *   onOpenLink={async ({ url }) => window.open(url)}\n *   onError={(error) => console.error('UI Error:', error)}\n * />\n * ```\n *\n * @example With pre-fetched HTML (skips resource fetching)\n * ```tsx\n * <AppRenderer\n *   sandbox={{ url: sandboxUrl }}\n *   client={mcpClient}\n *   toolName=\"my-tool\"\n *   html={preloadedHtml}\n *   toolInput={args}\n * />\n * ```\n *\n * @example Using ref to access AppBridge methods\n * ```tsx\n * const appRef = useRef<AppRendererHandle>(null);\n *\n * // Notify guest UI when tools change\n * useEffect(() => {\n *   appRef.current?.sendToolListChanged();\n * }, [toolsVersion]);\n *\n * <AppRenderer ref={appRef} ... />\n * ```\n *\n * @example With custom MCP request handlers (no client)\n * ```tsx\n * <AppRenderer\n *   // client omitted - use toolResourceUri + onReadResource to fetch HTML\n *   sandbox={{ url: sandboxUrl }}\n *   toolName=\"my-tool\"\n *   toolResourceUri=\"ui://my-server/my-tool\"\n *   onReadResource={async ({ uri }) => {\n *     // Proxy to your MCP client (e.g., in a different context)\n *     return myMcpProxy.readResource({ uri });\n *   }}\n *   onCallTool={async (params) => {\n *     // Custom tool call handling with caching/filtering\n *     return myCustomToolCall(params);\n *   }}\n *   onListResources={async () => {\n *     // Aggregate resources from multiple servers\n *     return { resources: [...server1Resources, ...server2Resources] };\n *   }}\n * />\n * ```\n */\nexport const AppRenderer = forwardRef<AppRendererHandle, AppRendererProps>((props, ref) => {\n  const {\n    client,\n    toolName,\n    sandbox,\n    toolResourceUri,\n    html: htmlProp,\n    toolInput,\n    toolResult,\n    toolInputPartial,\n    toolCancelled,\n    hostContext,\n    onMessage,\n    onOpenLink,\n    onLoggingMessage,\n    onSizeChanged,\n    onError,\n    onCallTool,\n    onListResources,\n    onListResourceTemplates,\n    onReadResource,\n    onListPrompts,\n    onFallbackRequest,\n  } = props;\n\n  // State\n  const [appBridge, setAppBridge] = useState<AppBridge | null>(null);\n  const [html, setHtml] = useState<string | null>(htmlProp ?? null);\n  const [error, setError] = useState<Error | null>(null);\n\n  // Refs for callbacks\n  const onMessageRef = useRef(onMessage);\n  const onOpenLinkRef = useRef(onOpenLink);\n  const onLoggingMessageRef = useRef(onLoggingMessage);\n  const onSizeChangedRef = useRef(onSizeChanged);\n  const onErrorRef = useRef(onError);\n  const onCallToolRef = useRef(onCallTool);\n  const onListResourcesRef = useRef(onListResources);\n  const onListResourceTemplatesRef = useRef(onListResourceTemplates);\n  const onReadResourceRef = useRef(onReadResource);\n  const onListPromptsRef = useRef(onListPrompts);\n  const onFallbackRequestRef = useRef(onFallbackRequest);\n\n  useEffect(() => {\n    onMessageRef.current = onMessage;\n    onOpenLinkRef.current = onOpenLink;\n    onLoggingMessageRef.current = onLoggingMessage;\n    onSizeChangedRef.current = onSizeChanged;\n    onErrorRef.current = onError;\n    onCallToolRef.current = onCallTool;\n    onListResourcesRef.current = onListResources;\n    onListResourceTemplatesRef.current = onListResourceTemplates;\n    onReadResourceRef.current = onReadResource;\n    onListPromptsRef.current = onListPrompts;\n    onFallbackRequestRef.current = onFallbackRequest;\n  });\n\n  // Expose send methods via ref for Host → Guest notifications\n  useImperativeHandle(\n    ref,\n    () => ({\n      sendToolListChanged: () => appBridge?.sendToolListChanged(),\n      sendResourceListChanged: () => appBridge?.sendResourceListChanged(),\n      sendPromptListChanged: () => appBridge?.sendPromptListChanged(),\n      teardownResource: () => appBridge?.teardownResource({}),\n    }),\n    [appBridge],\n  );\n\n  // Effect 1: Create and configure AppBridge\n  useEffect(() => {\n    let mounted = true;\n\n    const createBridge = () => {\n      try {\n        const serverCapabilities = client?.getServerCapabilities();\n        const bridge = new AppBridge(\n          client ?? null,\n          {\n            name: 'MCP-UI Host',\n            version: '1.0.0',\n          },\n          {\n            openLinks: {},\n            serverTools: serverCapabilities?.tools,\n            serverResources: serverCapabilities?.resources,\n          },\n        );\n\n        // Register message handler\n        bridge.onmessage = async (params, extra) => {\n          if (onMessageRef.current) {\n            return onMessageRef.current(params, extra);\n          } else {\n            throw new McpError(ErrorCode.MethodNotFound, 'Method not found');\n          }\n        };\n\n        // Register open-link handler\n        bridge.onopenlink = async (params, extra) => {\n          if (onOpenLinkRef.current) {\n            return onOpenLinkRef.current(params, extra);\n          } else {\n            throw new McpError(ErrorCode.MethodNotFound, 'Method not found');\n          }\n        };\n\n        // Register logging handler\n        bridge.onloggingmessage = (params) => {\n          if (onLoggingMessageRef.current) {\n            onLoggingMessageRef.current(params);\n          }\n        };\n\n        // Register custom MCP request handlers (these override automatic forwarding)\n        if (onCallToolRef.current) {\n          bridge.oncalltool = (params, extra) => onCallToolRef.current!(params, extra);\n        }\n        if (onListResourcesRef.current) {\n          bridge.onlistresources = (params, extra) => onListResourcesRef.current!(params, extra);\n        }\n        if (onListResourceTemplatesRef.current) {\n          bridge.onlistresourcetemplates = (params, extra) =>\n            onListResourceTemplatesRef.current!(params, extra);\n        }\n        if (onReadResourceRef.current) {\n          bridge.onreadresource = (params, extra) => onReadResourceRef.current!(params, extra);\n        }\n        if (onListPromptsRef.current) {\n          bridge.onlistprompts = (params, extra) => onListPromptsRef.current!(params, extra);\n        }\n\n        // Register fallback handler for unregistered JSON-RPC methods\n        // (e.g., experimental events like \"x/clipboard/write\" or MCP methods\n        // not yet in the Apps spec like \"sampling/createMessage\")\n        bridge.fallbackRequestHandler = async (request, extra) => {\n          if (onFallbackRequestRef.current) {\n            return onFallbackRequestRef.current(request, extra);\n          }\n          throw new McpError(\n            ErrorCode.MethodNotFound,\n            `No handler for method: ${request.method}`,\n          );\n        };\n\n        if (!mounted) return;\n        setAppBridge(bridge);\n      } catch (err) {\n        console.error('[AppRenderer] Error creating bridge:', err);\n        if (!mounted) return;\n        const error = err instanceof Error ? err : new Error(String(err));\n        setError(error);\n        onErrorRef.current?.(error);\n      }\n    };\n\n    createBridge();\n\n    return () => {\n      mounted = false;\n    };\n  }, [client]);\n\n  // Effect 2: Fetch HTML if not provided\n  useEffect(() => {\n    if (htmlProp) {\n      setHtml(htmlProp);\n      return;\n    }\n\n    // Determine if we can fetch HTML\n    const canFetchWithClient = !!client;\n    const canFetchWithCallback = !!toolResourceUri && !!onReadResourceRef.current;\n\n    if (!canFetchWithClient && !canFetchWithCallback) {\n      setError(\n        new Error(\n          \"Either 'html' prop, 'client', or ('toolResourceUri' + 'onReadResource') must be provided to fetch UI resource\",\n        ),\n      );\n      return;\n    }\n\n    let mounted = true;\n\n    const fetchHtml = async () => {\n      try {\n        // Get resource URI\n        let uri: string;\n        if (toolResourceUri) {\n          uri = toolResourceUri;\n          console.log(`[AppRenderer] Using provided resource URI: ${uri}`);\n        } else if (client) {\n          console.log(`[AppRenderer] Fetching resource URI for tool: ${toolName}`);\n          const info = await getToolUiResourceUri(client, toolName);\n          if (!info) {\n            throw new Error(\n              `Tool ${toolName} has no UI resource (no ui/resourceUri in tool._meta)`,\n            );\n          }\n          uri = info.uri;\n          console.log(`[AppRenderer] Got resource URI: ${uri}`);\n        } else {\n          throw new Error('Cannot determine resource URI without client or toolResourceUri');\n        }\n\n        if (!mounted) return;\n\n        // Read HTML content - use client if available, otherwise use onReadResource callback\n        console.log(`[AppRenderer] Reading resource HTML from: ${uri}`);\n        let htmlContent: string;\n\n        if (client) {\n          htmlContent = await readToolUiResourceHtml(client, { uri });\n        } else if (onReadResourceRef.current) {\n          // Use the onReadResource callback to fetch the HTML\n          const result = await onReadResourceRef.current({ uri }, {} as RequestHandlerExtra);\n          if (!result.contents || result.contents.length !== 1) {\n            throw new Error('Unsupported UI resource content length: ' + result.contents?.length);\n          }\n          const content = result.contents[0];\n          const isHtml = (t?: string) => t === RESOURCE_MIME_TYPE;\n\n          if ('text' in content && typeof content.text === 'string' && isHtml(content.mimeType)) {\n            htmlContent = content.text;\n          } else if (\n            'blob' in content &&\n            typeof content.blob === 'string' &&\n            isHtml(content.mimeType)\n          ) {\n            htmlContent = atob(content.blob);\n          } else {\n            throw new Error('Unsupported UI resource content format: ' + JSON.stringify(content));\n          }\n        } else {\n          throw new Error('No way to read resource HTML');\n        }\n\n        if (!mounted) return;\n\n        setHtml(htmlContent);\n      } catch (err) {\n        if (!mounted) return;\n        const error = err instanceof Error ? err : new Error(String(err));\n        setError(error);\n        onErrorRef.current?.(error);\n      }\n    };\n\n    fetchHtml();\n\n    return () => {\n      mounted = false;\n    };\n  }, [client, toolName, toolResourceUri, htmlProp]);\n\n  // Effect 3: Sync host context when it changes\n  useEffect(() => {\n    if (appBridge && hostContext) {\n      appBridge.setHostContext(hostContext);\n    }\n  }, [appBridge, hostContext]);\n\n  // Effect 4: Send partial tool input when it changes\n  useEffect(() => {\n    if (appBridge && toolInputPartial) {\n      appBridge.sendToolInputPartial(toolInputPartial);\n    }\n  }, [appBridge, toolInputPartial]);\n\n  // Effect 5: Send tool cancelled notification when flag is set\n  useEffect(() => {\n    if (appBridge && toolCancelled) {\n      appBridge.sendToolCancelled({});\n    }\n  }, [appBridge, toolCancelled]);\n\n  // Handle size change callback\n  const handleSizeChanged = onSizeChangedRef.current;\n\n  // Render error state\n  if (error) {\n    return <div style={{ color: 'red', padding: '1rem' }}>Error: {error.message}</div>;\n  }\n\n  // Render loading state\n  if (!appBridge || !html) {\n    return null;\n  }\n\n  // Render AppFrame with the fetched HTML and configured bridge\n  return (\n    <AppFrame\n      html={html}\n      sandbox={sandbox}\n      appBridge={appBridge}\n      toolInput={toolInput}\n      toolResult={toolResult}\n      onSizeChanged={handleSizeChanged}\n      onError={onError}\n    />\n  );\n});\n\nAppRenderer.displayName = 'AppRenderer';\n"
  },
  {
    "path": "sdks/typescript/client/src/components/__tests__/AppFrame.test.tsx",
    "content": "import { render, screen, waitFor, act } from '@testing-library/react';\nimport { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';\nimport '@testing-library/jest-dom';\n\nimport { AppFrame, type AppFrameProps } from '../AppFrame';\nimport * as appHostUtils from '../../utils/app-host-utils';\nimport type { AppBridge } from '@modelcontextprotocol/ext-apps/app-bridge';\n\n// Mock the ext-apps module\nvi.mock('@modelcontextprotocol/ext-apps/app-bridge', () => {\n  // Create a mock constructor for PostMessageTransport\n  const MockPostMessageTransport = vi.fn().mockImplementation(function (this: unknown) {\n    return this;\n  });\n\n  return {\n    AppBridge: vi.fn(),\n    PostMessageTransport: MockPostMessageTransport,\n  };\n});\n\n// Track registered handlers\nlet registeredOninitialized: (() => void) | null = null;\nlet registeredOnsizechange: ((params: { width?: number; height?: number }) => void) | null = null;\n\n// Mock AppBridge factory\nconst createMockAppBridge = () => {\n  const bridge = {\n    connect: vi.fn().mockResolvedValue(undefined),\n    sendSandboxResourceReady: vi.fn().mockResolvedValue(undefined),\n    sendToolInput: vi.fn(),\n    sendToolResult: vi.fn(),\n    getAppVersion: vi.fn().mockReturnValue({ name: 'TestApp', version: '1.0.0' }),\n    getAppCapabilities: vi.fn().mockReturnValue({ tools: {} }),\n    _oninitialized: null as (() => void) | null,\n    _onsizechange: null as ((params: { width?: number; height?: number }) => void) | null,\n  };\n\n  Object.defineProperty(bridge, 'oninitialized', {\n    set: (fn) => {\n      bridge._oninitialized = fn;\n      registeredOninitialized = fn;\n    },\n    get: () => bridge._oninitialized,\n  });\n  Object.defineProperty(bridge, 'onsizechange', {\n    set: (fn) => {\n      bridge._onsizechange = fn;\n      registeredOnsizechange = fn;\n    },\n    get: () => bridge._onsizechange,\n  });\n\n  return bridge;\n};\n\n// Mock the app-host-utils module\nvi.mock('../../utils/app-host-utils', () => ({\n  setupSandboxProxyIframe: vi.fn(),\n}));\n\ndescribe('<AppFrame />', () => {\n  let mockIframe: Partial<HTMLIFrameElement>;\n  let mockContentWindow: { postMessage: ReturnType<typeof vi.fn> };\n  let onReadyResolve: () => void;\n  let mockAppBridge: ReturnType<typeof createMockAppBridge>;\n\n  beforeEach(() => {\n    vi.clearAllMocks();\n    registeredOninitialized = null;\n    registeredOnsizechange = null;\n    mockAppBridge = createMockAppBridge();\n\n    // Create mock contentWindow\n    mockContentWindow = {\n      postMessage: vi.fn(),\n    };\n\n    // Create a real iframe element and mock contentWindow via defineProperty\n    const realIframe = document.createElement('iframe');\n    Object.defineProperty(realIframe, 'contentWindow', {\n      get: () => mockContentWindow as unknown as Window,\n      configurable: true,\n    });\n    mockIframe = realIframe;\n\n    // Setup mock for setupSandboxProxyIframe\n    const onReadyPromise = new Promise<void>((resolve) => {\n      onReadyResolve = resolve;\n    });\n\n    vi.mocked(appHostUtils.setupSandboxProxyIframe).mockResolvedValue({\n      iframe: mockIframe as HTMLIFrameElement,\n      onReady: onReadyPromise,\n    });\n  });\n\n  afterEach(() => {\n    vi.clearAllMocks();\n  });\n\n  const defaultProps: Omit<AppFrameProps, 'appBridge'> = {\n    html: '<html><body>Test</body></html>',\n    sandbox: { url: new URL('http://localhost:8081/sandbox.html') },\n  };\n\n  const getPropsWithBridge = (overrides: Partial<AppFrameProps> = {}): AppFrameProps => ({\n    ...defaultProps,\n    appBridge: mockAppBridge as unknown as AppBridge,\n    ...overrides,\n  });\n\n  it('should render without crashing', () => {\n    render(<AppFrame {...getPropsWithBridge()} />);\n    expect(document.querySelector('div')).toBeInTheDocument();\n  });\n\n  it('should call setupSandboxProxyIframe with sandbox URL', async () => {\n    render(<AppFrame {...getPropsWithBridge()} />);\n\n    await waitFor(() => {\n      expect(appHostUtils.setupSandboxProxyIframe).toHaveBeenCalledWith(defaultProps.sandbox.url);\n    });\n  });\n\n  it('should connect AppBridge when provided', async () => {\n    render(<AppFrame {...getPropsWithBridge()} />);\n\n    await act(() => {\n      onReadyResolve();\n    });\n\n    await waitFor(() => {\n      expect(mockAppBridge.connect).toHaveBeenCalled();\n    });\n  });\n\n  it('should send HTML via AppBridge.sendSandboxResourceReady', async () => {\n    render(<AppFrame {...getPropsWithBridge()} />);\n\n    await act(() => {\n      onReadyResolve();\n    });\n\n    // Trigger initialization\n    await act(() => {\n      registeredOninitialized?.();\n    });\n\n    await waitFor(() => {\n      expect(mockAppBridge.sendSandboxResourceReady).toHaveBeenCalledWith({\n        html: defaultProps.html,\n        csp: undefined,\n      });\n    });\n  });\n\n  it('should call onInitialized with app info when app initializes', async () => {\n    const onInitialized = vi.fn();\n\n    render(<AppFrame {...getPropsWithBridge({ onInitialized })} />);\n\n    await act(() => {\n      onReadyResolve();\n    });\n\n    await act(() => {\n      registeredOninitialized?.();\n    });\n\n    await waitFor(() => {\n      expect(onInitialized).toHaveBeenCalledWith({\n        appVersion: { name: 'TestApp', version: '1.0.0' },\n        appCapabilities: { tools: {} },\n      });\n    });\n  });\n\n  it('should send tool input after initialization', async () => {\n    const toolInput = { foo: 'bar' };\n\n    render(<AppFrame {...getPropsWithBridge({ toolInput })} />);\n\n    await act(() => {\n      onReadyResolve();\n    });\n\n    await act(() => {\n      registeredOninitialized?.();\n    });\n\n    await waitFor(() => {\n      expect(mockAppBridge.sendToolInput).toHaveBeenCalledWith({\n        arguments: toolInput,\n      });\n    });\n  });\n\n  it('should send tool result after initialization', async () => {\n    const toolResult = { content: [{ type: 'text' as const, text: 'result' }] };\n\n    render(<AppFrame {...getPropsWithBridge({ toolResult })} />);\n\n    await act(() => {\n      onReadyResolve();\n    });\n\n    await act(() => {\n      registeredOninitialized?.();\n    });\n\n    await waitFor(() => {\n      expect(mockAppBridge.sendToolResult).toHaveBeenCalledWith(toolResult);\n    });\n  });\n\n  it('should call onSizeChanged when size changes', async () => {\n    const onSizeChanged = vi.fn();\n\n    render(<AppFrame {...getPropsWithBridge({ onSizeChanged })} />);\n\n    await act(() => {\n      onReadyResolve();\n    });\n\n    await act(() => {\n      registeredOnsizechange?.({ width: 800, height: 600 });\n    });\n\n    expect(onSizeChanged).toHaveBeenCalledWith({ width: 800, height: 600 });\n  });\n\n  it('should forward CSP to sandbox', async () => {\n    const csp = {\n      connectDomains: ['api.example.com'],\n      resourceDomains: ['cdn.example.com'],\n    };\n\n    render(<AppFrame {...getPropsWithBridge({ sandbox: { ...defaultProps.sandbox, csp } })} />);\n\n    await act(() => {\n      onReadyResolve();\n    });\n\n    await act(() => {\n      registeredOninitialized?.();\n    });\n\n    await waitFor(() => {\n      expect(mockAppBridge.sendSandboxResourceReady).toHaveBeenCalledWith({\n        html: defaultProps.html,\n        csp,\n      });\n    });\n  });\n\n  it('should call onError when setup fails', async () => {\n    const onError = vi.fn();\n    const error = new Error('Setup failed');\n\n    vi.mocked(appHostUtils.setupSandboxProxyIframe).mockRejectedValue(error);\n\n    render(<AppFrame {...getPropsWithBridge({ onError })} />);\n\n    await waitFor(() => {\n      expect(onError).toHaveBeenCalledWith(error);\n    });\n  });\n\n  it('should display error message when error occurs', async () => {\n    const error = new Error('Test error');\n    vi.mocked(appHostUtils.setupSandboxProxyIframe).mockRejectedValue(error);\n\n    render(<AppFrame {...getPropsWithBridge()} />);\n\n    await waitFor(() => {\n      expect(screen.getByText(/Error: Test error/)).toBeInTheDocument();\n    });\n  });\n\n  describe('lifecycle', () => {\n    it('should preserve iframe across re-renders', async () => {\n      const { rerender } = render(<AppFrame {...getPropsWithBridge()} />);\n\n      await act(() => {\n        onReadyResolve();\n      });\n\n      await act(() => {\n        registeredOninitialized?.();\n      });\n\n      // setupSandboxProxyIframe should be called once on initial mount\n      expect(appHostUtils.setupSandboxProxyIframe).toHaveBeenCalledTimes(1);\n\n      // Re-render with same props (simulating React StrictMode remount or parent re-render)\n      rerender(<AppFrame {...getPropsWithBridge()} />);\n\n      // Should NOT call setupSandboxProxyIframe again - iframe is preserved\n      expect(appHostUtils.setupSandboxProxyIframe).toHaveBeenCalledTimes(1);\n    });\n\n    it('should recreate iframe when sandbox URL changes', async () => {\n      const { rerender } = render(<AppFrame {...getPropsWithBridge()} />);\n\n      await act(() => {\n        onReadyResolve();\n      });\n\n      await act(() => {\n        registeredOninitialized?.();\n      });\n\n      expect(appHostUtils.setupSandboxProxyIframe).toHaveBeenCalledTimes(1);\n\n      // Create new mock for second iframe\n      const secondOnReadyPromise = new Promise<void>((resolve) => {\n        onReadyResolve = resolve;\n      });\n      vi.mocked(appHostUtils.setupSandboxProxyIframe).mockResolvedValue({\n        iframe: mockIframe as HTMLIFrameElement,\n        onReady: secondOnReadyPromise,\n      });\n\n      // Re-render with DIFFERENT sandbox URL\n      const newSandboxUrl = new URL('http://localhost:9999/different-sandbox.html');\n      rerender(<AppFrame {...getPropsWithBridge({ sandbox: { url: newSandboxUrl } })} />);\n\n      // Should call setupSandboxProxyIframe again with new URL\n      await waitFor(() => {\n        expect(appHostUtils.setupSandboxProxyIframe).toHaveBeenCalledTimes(2);\n        expect(appHostUtils.setupSandboxProxyIframe).toHaveBeenLastCalledWith(newSandboxUrl);\n      });\n    });\n\n    it('should update HTML content without recreating iframe', async () => {\n      const { rerender } = render(<AppFrame {...getPropsWithBridge()} />);\n\n      await act(() => {\n        onReadyResolve();\n      });\n\n      await act(() => {\n        registeredOninitialized?.();\n      });\n\n      // Initial HTML sent\n      await waitFor(() => {\n        expect(mockAppBridge.sendSandboxResourceReady).toHaveBeenCalledTimes(1);\n        expect(mockAppBridge.sendSandboxResourceReady).toHaveBeenCalledWith({\n          html: defaultProps.html,\n          csp: undefined,\n        });\n      });\n\n      // Re-render with new HTML\n      const newHtml = '<html><body>Updated Content</body></html>';\n      rerender(<AppFrame {...getPropsWithBridge({ html: newHtml })} />);\n\n      // Should send new HTML without recreating iframe\n      await waitFor(() => {\n        expect(mockAppBridge.sendSandboxResourceReady).toHaveBeenCalledTimes(2);\n        expect(mockAppBridge.sendSandboxResourceReady).toHaveBeenLastCalledWith({\n          html: newHtml,\n          csp: undefined,\n        });\n      });\n\n      // Iframe should NOT be recreated\n      expect(appHostUtils.setupSandboxProxyIframe).toHaveBeenCalledTimes(1);\n    });\n\n    it('should update toolInput without recreating iframe', async () => {\n      const { rerender } = render(\n        <AppFrame {...getPropsWithBridge({ toolInput: { initial: true } })} />,\n      );\n\n      await act(() => {\n        onReadyResolve();\n      });\n\n      await act(() => {\n        registeredOninitialized?.();\n      });\n\n      await waitFor(() => {\n        expect(mockAppBridge.sendToolInput).toHaveBeenCalledWith({ arguments: { initial: true } });\n      });\n\n      // Re-render with new toolInput\n      rerender(<AppFrame {...getPropsWithBridge({ toolInput: { updated: true } })} />);\n\n      await waitFor(() => {\n        expect(mockAppBridge.sendToolInput).toHaveBeenCalledWith({ arguments: { updated: true } });\n      });\n\n      // Iframe should NOT be recreated\n      expect(appHostUtils.setupSandboxProxyIframe).toHaveBeenCalledTimes(1);\n    });\n  });\n});\n"
  },
  {
    "path": "sdks/typescript/client/src/components/__tests__/AppRenderer.test.tsx",
    "content": "import React from 'react';\nimport { render, screen, waitFor, act } from '@testing-library/react';\nimport { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';\nimport '@testing-library/jest-dom';\nimport type { Client } from '@modelcontextprotocol/sdk/client/index.js';\n\nimport { AppRenderer, type AppRendererProps, type AppRendererHandle } from '../AppRenderer';\nimport type { AppFrameProps } from '../AppFrame';\nimport type { AppBridge } from '@modelcontextprotocol/ext-apps/app-bridge';\nimport * as appHostUtils from '../../utils/app-host-utils';\n\n// Mock AppFrame to capture props\nconst mockAppFrame = vi.fn();\nvi.mock('../AppFrame', () => ({\n  AppFrame: (props: AppFrameProps) => {\n    mockAppFrame(props);\n    return (\n      <div\n        data-testid=\"app-frame\"\n        data-html={props.html}\n        data-sandbox-url={props.sandbox?.url?.href}\n      >\n        {props.toolInput && <span data-testid=\"tool-input\">{JSON.stringify(props.toolInput)}</span>}\n        {props.toolResult && (\n          <span data-testid=\"tool-result\">{JSON.stringify(props.toolResult)}</span>\n        )}\n      </div>\n    );\n  },\n}));\n\n// Mock app-host-utils\nvi.mock('../../utils/app-host-utils', () => ({\n  getToolUiResourceUri: vi.fn(),\n  readToolUiResourceHtml: vi.fn(),\n}));\n\n// Store mock bridge instance for test access\nlet mockBridgeInstance: Partial<AppBridge> | null = null;\n\n// Mock AppBridge constructor\nvi.mock('@modelcontextprotocol/ext-apps/app-bridge', () => {\n  return {\n    AppBridge: vi.fn().mockImplementation(function () {\n      mockBridgeInstance = {\n        onmessage: undefined,\n        onopenlink: undefined,\n        onloggingmessage: undefined,\n        oncalltool: undefined,\n        onlistresources: undefined,\n        onlistresourcetemplates: undefined,\n        onreadresource: undefined,\n        onlistprompts: undefined,\n        fallbackRequestHandler: undefined,\n        setHostContext: vi.fn(),\n        sendToolInputPartial: vi.fn(),\n        sendToolCancelled: vi.fn(),\n        sendToolListChanged: vi.fn(),\n        sendResourceListChanged: vi.fn(),\n        sendPromptListChanged: vi.fn(),\n        teardownResource: vi.fn(),\n      };\n      return mockBridgeInstance;\n    }),\n    RESOURCE_MIME_TYPE: 'text/html',\n  };\n});\n\n// Mock MCP Client\nconst mockClient = {\n  getServerCapabilities: vi.fn().mockReturnValue({\n    tools: {},\n    resources: {},\n  }),\n};\n\nfunction createMockExtra() {\n  return {\n    signal: new AbortController().signal,\n    requestId: 1,\n    sendNotification: vi.fn(),\n    sendRequest: vi.fn(),\n  };\n}\n\ndescribe('<AppRenderer />', () => {\n  const defaultProps: AppRendererProps = {\n    client: mockClient as unknown as Client,\n    toolName: 'test-tool',\n    sandbox: { url: new URL('http://localhost:8081/sandbox.html') },\n  };\n\n  beforeEach(() => {\n    vi.clearAllMocks();\n    mockBridgeInstance = null;\n    mockAppFrame.mockClear();\n\n    // Default mock implementations\n    vi.mocked(appHostUtils.getToolUiResourceUri).mockResolvedValue({\n      uri: 'ui://test-tool',\n    });\n    vi.mocked(appHostUtils.readToolUiResourceHtml).mockResolvedValue(\n      '<html><body>Test Tool UI</body></html>',\n    );\n  });\n\n  afterEach(() => {\n    vi.clearAllMocks();\n  });\n\n  describe('basic rendering', () => {\n    it('should render AppFrame after fetching HTML', async () => {\n      render(<AppRenderer {...defaultProps} />);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n      });\n    });\n\n    it('should fetch resource URI for the tool', async () => {\n      render(<AppRenderer {...defaultProps} />);\n\n      await waitFor(() => {\n        expect(appHostUtils.getToolUiResourceUri).toHaveBeenCalledWith(mockClient, 'test-tool');\n      });\n    });\n\n    it('should read HTML from resource URI', async () => {\n      render(<AppRenderer {...defaultProps} />);\n\n      await waitFor(() => {\n        expect(appHostUtils.readToolUiResourceHtml).toHaveBeenCalledWith(mockClient, {\n          uri: 'ui://test-tool',\n        });\n      });\n    });\n\n    it('should pass fetched HTML to AppFrame', async () => {\n      render(<AppRenderer {...defaultProps} />);\n\n      await waitFor(() => {\n        const appFrame = screen.getByTestId('app-frame');\n        expect(appFrame).toHaveAttribute('data-html', '<html><body>Test Tool UI</body></html>');\n      });\n    });\n\n    it('should use provided toolResourceUri instead of fetching', async () => {\n      const props: AppRendererProps = {\n        ...defaultProps,\n        toolResourceUri: 'ui://custom-uri',\n      };\n\n      render(<AppRenderer {...props} />);\n\n      await waitFor(() => {\n        expect(appHostUtils.getToolUiResourceUri).not.toHaveBeenCalled();\n        expect(appHostUtils.readToolUiResourceHtml).toHaveBeenCalledWith(mockClient, {\n          uri: 'ui://custom-uri',\n        });\n      });\n    });\n\n    it('should use provided HTML directly without fetching', async () => {\n      const props: AppRendererProps = {\n        ...defaultProps,\n        html: '<html><body>Pre-fetched HTML</body></html>',\n      };\n\n      render(<AppRenderer {...props} />);\n\n      await waitFor(() => {\n        expect(appHostUtils.getToolUiResourceUri).not.toHaveBeenCalled();\n        expect(appHostUtils.readToolUiResourceHtml).not.toHaveBeenCalled();\n        expect(screen.getByTestId('app-frame')).toHaveAttribute(\n          'data-html',\n          '<html><body>Pre-fetched HTML</body></html>',\n        );\n      });\n    });\n\n    it('should pass sandbox config to AppFrame', async () => {\n      render(<AppRenderer {...defaultProps} />);\n\n      await waitFor(() => {\n        const appFrame = screen.getByTestId('app-frame');\n        expect(appFrame).toHaveAttribute('data-sandbox-url', 'http://localhost:8081/sandbox.html');\n      });\n    });\n\n    it('should pass toolInput to AppFrame', async () => {\n      const toolInput = { query: 'test query' };\n      const props: AppRendererProps = {\n        ...defaultProps,\n        toolInput,\n      };\n\n      render(<AppRenderer {...props} />);\n\n      await waitFor(() => {\n        const toolInputEl = screen.getByTestId('tool-input');\n        expect(toolInputEl).toHaveTextContent(JSON.stringify(toolInput));\n      });\n    });\n\n    it('should pass toolResult to AppFrame', async () => {\n      const toolResult = { content: [{ type: 'text' as const, text: 'result' }] };\n      const props: AppRendererProps = {\n        ...defaultProps,\n        toolResult,\n      };\n\n      render(<AppRenderer {...props} />);\n\n      await waitFor(() => {\n        const toolResultEl = screen.getByTestId('tool-result');\n        expect(toolResultEl).toHaveTextContent(JSON.stringify(toolResult));\n      });\n    });\n\n    it('should display error when tool has no UI resource', async () => {\n      vi.mocked(appHostUtils.getToolUiResourceUri).mockResolvedValue(null);\n\n      render(<AppRenderer {...defaultProps} />);\n\n      await waitFor(() => {\n        expect(screen.getByText(/Error:/)).toBeInTheDocument();\n        expect(screen.getByText(/has no UI resource/)).toBeInTheDocument();\n      });\n    });\n\n    it('should call onError when resource fetch fails', async () => {\n      const onError = vi.fn();\n      const error = new Error('Fetch failed');\n      vi.mocked(appHostUtils.readToolUiResourceHtml).mockRejectedValue(error);\n\n      render(<AppRenderer {...defaultProps} onError={onError} />);\n\n      await waitFor(() => {\n        expect(onError).toHaveBeenCalledWith(error);\n      });\n    });\n\n    it('should return null while loading', () => {\n      // Make the promise never resolve\n      vi.mocked(appHostUtils.getToolUiResourceUri).mockReturnValue(new Promise(() => {}));\n\n      const { container } = render(<AppRenderer {...defaultProps} />);\n\n      // Should render nothing while loading\n      expect(container.firstChild).toBeNull();\n    });\n  });\n\n  describe('hostContext prop', () => {\n    it('should call setHostContext when hostContext is provided', async () => {\n      const hostContext = { theme: 'dark' as const };\n\n      render(<AppRenderer {...defaultProps} hostContext={hostContext} />);\n\n      await waitFor(() => {\n        expect(mockBridgeInstance?.setHostContext).toHaveBeenCalledWith(hostContext);\n      });\n    });\n\n    it('should update hostContext when prop changes', async () => {\n      const { rerender } = render(\n        <AppRenderer {...defaultProps} hostContext={{ theme: 'light' as const }} />,\n      );\n\n      await waitFor(() => {\n        expect(mockBridgeInstance?.setHostContext).toHaveBeenCalledWith({ theme: 'light' });\n      });\n\n      rerender(<AppRenderer {...defaultProps} hostContext={{ theme: 'dark' as const }} />);\n\n      await waitFor(() => {\n        expect(mockBridgeInstance?.setHostContext).toHaveBeenCalledWith({ theme: 'dark' });\n      });\n    });\n  });\n\n  describe('toolInputPartial prop', () => {\n    it('should call sendToolInputPartial when toolInputPartial is provided', async () => {\n      const toolInputPartial = { arguments: { delta: 'partial data' } };\n\n      render(<AppRenderer {...defaultProps} toolInputPartial={toolInputPartial} />);\n\n      await waitFor(() => {\n        expect(mockBridgeInstance?.sendToolInputPartial).toHaveBeenCalledWith(toolInputPartial);\n      });\n    });\n  });\n\n  describe('toolCancelled prop', () => {\n    it('should call sendToolCancelled when toolCancelled is true', async () => {\n      render(<AppRenderer {...defaultProps} toolCancelled={true} />);\n\n      await waitFor(() => {\n        expect(mockBridgeInstance?.sendToolCancelled).toHaveBeenCalledWith({});\n      });\n    });\n\n    it('should not call sendToolCancelled when toolCancelled is false', async () => {\n      render(<AppRenderer {...defaultProps} toolCancelled={false} />);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n      });\n\n      expect(mockBridgeInstance?.sendToolCancelled).not.toHaveBeenCalled();\n    });\n  });\n\n  describe('ref methods', () => {\n    it('should expose sendToolListChanged via ref', async () => {\n      const ref = React.createRef<AppRendererHandle>();\n\n      render(<AppRenderer ref={ref} {...defaultProps} />);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n      });\n\n      await waitFor(() => {\n        expect(ref.current).not.toBeNull();\n      });\n\n      act(() => {\n        ref.current?.sendToolListChanged();\n      });\n\n      expect(mockBridgeInstance?.sendToolListChanged).toHaveBeenCalled();\n    });\n\n    it('should expose sendResourceListChanged via ref', async () => {\n      const ref = React.createRef<AppRendererHandle>();\n\n      render(<AppRenderer ref={ref} {...defaultProps} />);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n      });\n\n      await waitFor(() => {\n        expect(ref.current).not.toBeNull();\n      });\n\n      act(() => {\n        ref.current?.sendResourceListChanged();\n      });\n\n      expect(mockBridgeInstance?.sendResourceListChanged).toHaveBeenCalled();\n    });\n\n    it('should expose sendPromptListChanged via ref', async () => {\n      const ref = React.createRef<AppRendererHandle>();\n\n      render(<AppRenderer ref={ref} {...defaultProps} />);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n      });\n\n      await waitFor(() => {\n        expect(ref.current).not.toBeNull();\n      });\n\n      act(() => {\n        ref.current?.sendPromptListChanged();\n      });\n\n      expect(mockBridgeInstance?.sendPromptListChanged).toHaveBeenCalled();\n    });\n\n    it('should expose teardownResource via ref', async () => {\n      const ref = React.createRef<AppRendererHandle>();\n\n      render(<AppRenderer ref={ref} {...defaultProps} />);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n      });\n\n      await waitFor(() => {\n        expect(ref.current).not.toBeNull();\n      });\n\n      act(() => {\n        ref.current?.teardownResource();\n      });\n\n      expect(mockBridgeInstance?.teardownResource).toHaveBeenCalledWith({});\n    });\n  });\n\n  describe('MCP request handler props', () => {\n    it('should register onCallTool handler on AppBridge', async () => {\n      const onCallTool = vi.fn().mockResolvedValue({ content: [] });\n\n      render(<AppRenderer {...defaultProps} onCallTool={onCallTool} />);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n      });\n\n      // The handler should be registered\n      expect(mockBridgeInstance?.oncalltool).toBeDefined();\n    });\n\n    it('should register onListResources handler on AppBridge', async () => {\n      const onListResources = vi.fn().mockResolvedValue({ resources: [] });\n\n      render(<AppRenderer {...defaultProps} onListResources={onListResources} />);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n      });\n\n      expect(mockBridgeInstance?.onlistresources).toBeDefined();\n    });\n\n    it('should register onListResourceTemplates handler on AppBridge', async () => {\n      const onListResourceTemplates = vi.fn().mockResolvedValue({ resourceTemplates: [] });\n\n      render(<AppRenderer {...defaultProps} onListResourceTemplates={onListResourceTemplates} />);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n      });\n\n      expect(mockBridgeInstance?.onlistresourcetemplates).toBeDefined();\n    });\n\n    it('should register onReadResource handler on AppBridge', async () => {\n      const onReadResource = vi.fn().mockResolvedValue({ contents: [] });\n\n      render(<AppRenderer {...defaultProps} onReadResource={onReadResource} />);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n      });\n\n      expect(mockBridgeInstance?.onreadresource).toBeDefined();\n    });\n\n    it('should register onListPrompts handler on AppBridge', async () => {\n      const onListPrompts = vi.fn().mockResolvedValue({ prompts: [] });\n\n      render(<AppRenderer {...defaultProps} onListPrompts={onListPrompts} />);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n      });\n\n      expect(mockBridgeInstance?.onlistprompts).toBeDefined();\n    });\n  });\n\n  describe('callback props', () => {\n    it('should pass onSizeChanged to AppFrame', async () => {\n      const onSizeChanged = vi.fn();\n\n      render(<AppRenderer {...defaultProps} onSizeChanged={onSizeChanged} />);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n      });\n\n      expect(mockAppFrame).toHaveBeenCalledWith(\n        expect.objectContaining({\n          onSizeChanged: expect.any(Function),\n        }),\n      );\n    });\n\n    it('should pass onError to AppFrame', async () => {\n      const onError = vi.fn();\n\n      render(<AppRenderer {...defaultProps} onError={onError} />);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n      });\n\n      expect(mockAppFrame).toHaveBeenCalledWith(\n        expect.objectContaining({\n          onError,\n        }),\n      );\n    });\n  });\n\n  describe('onFallbackRequest prop', () => {\n    it('should register fallbackRequestHandler on AppBridge', async () => {\n      const onFallbackRequest = vi.fn().mockResolvedValue({ success: true });\n\n      render(<AppRenderer {...defaultProps} onFallbackRequest={onFallbackRequest} />);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n      });\n\n      // fallbackRequestHandler should always be set (even without the prop, it throws MethodNotFound)\n      expect(mockBridgeInstance?.fallbackRequestHandler).toBeDefined();\n    });\n\n    it('should invoke onFallbackRequest when fallbackRequestHandler is called', async () => {\n      const onFallbackRequest = vi.fn().mockResolvedValue({ clipboard: 'written' });\n\n      render(<AppRenderer {...defaultProps} onFallbackRequest={onFallbackRequest} />);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n      });\n\n      // Simulate AppBridge calling the fallback handler with a custom method\n      const mockRequest = {\n        jsonrpc: '2.0' as const,\n        id: 1,\n        method: 'x/clipboard/write',\n        params: { text: 'hello' },\n      };\n      const mockExtra = createMockExtra();\n\n      const result = await mockBridgeInstance?.fallbackRequestHandler?.(mockRequest, mockExtra as never);\n\n      expect(onFallbackRequest).toHaveBeenCalledWith(mockRequest, mockExtra);\n      expect(result).toEqual({ clipboard: 'written' });\n    });\n\n    it('should throw MethodNotFound when onFallbackRequest is not provided', async () => {\n      render(<AppRenderer {...defaultProps} />);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n      });\n\n      const mockRequest = {\n        jsonrpc: '2.0' as const,\n        id: 1,\n        method: 'x/unknown/method',\n        params: {},\n      };\n      const mockExtra = createMockExtra();\n\n      await expect(\n        mockBridgeInstance?.fallbackRequestHandler?.(mockRequest, mockExtra as never),\n      ).rejects.toThrow('No handler for method: x/unknown/method');\n    });\n\n    it('should use the latest onFallbackRequest callback (ref stability)', async () => {\n      const firstHandler = vi.fn().mockResolvedValue({ version: 1 });\n      const secondHandler = vi.fn().mockResolvedValue({ version: 2 });\n\n      const { rerender } = render(\n        <AppRenderer {...defaultProps} onFallbackRequest={firstHandler} />,\n      );\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n      });\n\n      // Update the handler\n      rerender(<AppRenderer {...defaultProps} onFallbackRequest={secondHandler} />);\n\n      const mockRequest = {\n        jsonrpc: '2.0' as const,\n        id: 1,\n        method: 'x/test/method',\n        params: {},\n      };\n      const mockExtra = createMockExtra();\n\n      const result = await mockBridgeInstance?.fallbackRequestHandler?.(mockRequest, mockExtra as never);\n\n      // Should use the second (latest) handler\n      expect(firstHandler).not.toHaveBeenCalled();\n      expect(secondHandler).toHaveBeenCalledWith(mockRequest, mockExtra);\n      expect(result).toEqual({ version: 2 });\n    });\n\n    it('should propagate errors from onFallbackRequest', async () => {\n      const onFallbackRequest = vi.fn().mockRejectedValue(new Error('Permission denied'));\n\n      render(<AppRenderer {...defaultProps} onFallbackRequest={onFallbackRequest} />);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n      });\n\n      const mockRequest = {\n        jsonrpc: '2.0' as const,\n        id: 1,\n        method: 'x/restricted/action',\n        params: {},\n      };\n      const mockExtra = createMockExtra();\n\n      await expect(\n        mockBridgeInstance?.fallbackRequestHandler?.(mockRequest, mockExtra as never),\n      ).rejects.toThrow('Permission denied');\n    });\n  });\n\n  describe('no client', () => {\n    it('should work without client when html is provided', async () => {\n      const props: AppRendererProps = {\n        // client omitted - using html prop instead\n        toolName: 'test-tool',\n        sandbox: { url: new URL('http://localhost:8081/sandbox.html') },\n        html: '<html><body>Static HTML</body></html>',\n      };\n\n      render(<AppRenderer {...props} />);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n        expect(screen.getByTestId('app-frame')).toHaveAttribute(\n          'data-html',\n          '<html><body>Static HTML</body></html>',\n        );\n      });\n    });\n\n    it('should show error without client and no html', async () => {\n      const props: AppRendererProps = {\n        // client omitted, no html provided\n        toolName: 'test-tool',\n        sandbox: { url: new URL('http://localhost:8081/sandbox.html') },\n      };\n\n      render(<AppRenderer {...props} />);\n\n      await waitFor(() => {\n        expect(screen.getByText(/Error:/)).toBeInTheDocument();\n      });\n    });\n\n    it('should work with onReadResource and toolResourceUri instead of client', async () => {\n      const mockReadResource = vi.fn().mockResolvedValue({\n        contents: [\n          {\n            uri: 'ui://test/tool',\n            mimeType: 'text/html',\n            text: '<html><body>Custom fetched HTML</body></html>',\n          },\n        ],\n      });\n\n      const props: AppRendererProps = {\n        // client omitted - using onReadResource + toolResourceUri instead\n        toolName: 'test-tool',\n        sandbox: { url: new URL('http://localhost:8081/sandbox.html') },\n        toolResourceUri: 'ui://test/tool',\n        onReadResource: mockReadResource,\n      };\n\n      render(<AppRenderer {...props} />);\n\n      await waitFor(() => {\n        expect(mockReadResource).toHaveBeenCalledWith({ uri: 'ui://test/tool' }, expect.anything());\n        expect(screen.getByTestId('app-frame')).toBeInTheDocument();\n        expect(screen.getByTestId('app-frame')).toHaveAttribute(\n          'data-html',\n          '<html><body>Custom fetched HTML</body></html>',\n        );\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "sdks/typescript/client/src/components/__tests__/ProxyScript.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { JSDOM } from 'jsdom';\nimport '@testing-library/jest-dom';\n\nfunction nextTick(): Promise<void> {\n  return new Promise((resolve) => setTimeout(resolve, 0));\n}\n\ndescribe('Proxy script', () => {\n  it('should use document.write to inject HTML via postMessage', async () => {\n    const proxyPath = path.resolve(__dirname, '../../../scripts/proxy/index.html');\n    const proxyHtml = readFileSync(proxyPath, 'utf8');\n\n    // Create jsdom with proxy URL using contentType=rawhtml\n    const dom = new JSDOM(proxyHtml, {\n      url: 'http://proxy.local/?contentType=rawhtml',\n      runScripts: 'dangerously',\n      resources: 'usable',\n      pretendToBeVisual: true,\n    });\n\n    const { window } = dom;\n\n    // Capture the ready signal emitted by the proxy\n    let proxyReady = false;\n    window.addEventListener('message', (event: MessageEvent) => {\n      const data = event.data as { type?: string };\n      if (data?.type === 'ui-proxy-iframe-ready') {\n        proxyReady = true;\n      }\n    });\n\n    // Allow the inline script to run and emit readiness\n    await nextTick();\n    expect(proxyReady).toBe(true);\n\n    // Find the iframe created by the script\n    const outerDoc = window.document;\n    const innerIframe = outerDoc.querySelector('iframe');\n    expect(innerIframe).toBeTruthy();\n\n    // Verify the iframe has id=\"root\" and src=\"about:blank\" as per the new implementation\n    expect(innerIframe?.getAttribute('id')).toBe('root');\n    expect(innerIframe?.getAttribute('src')).toBe('about:blank');\n\n    // Send the html payload\n    const html = '<!doctype html><html><body><form><input></form></body></html>';\n    // Simulate parent -> proxy message ensuring source === window.parent\n    const MsgEvent: typeof MessageEvent = window.MessageEvent;\n    window.dispatchEvent(\n      new MsgEvent('message', {\n        data: { type: 'ui-html-content', payload: { html } },\n        source: window.parent,\n      }),\n    );\n\n    // Let the proxy handle the message\n    await nextTick();\n    await nextTick();\n\n    // Note: JSDOM has limitations with document.write on dynamically created iframes\n    // The new implementation uses document.write() instead of srcdoc, which avoids\n    // CSP base-uri issues. In a real browser, the contentDocument would contain the HTML.\n    // Here we just verify the iframe structure is correct.\n    expect(innerIframe).toBeTruthy();\n\n    // The new implementation doesn't use sandbox attributes from payload since\n    // allow-same-origin is required for document.write to work. The sandbox\n    // attribute is not set on the inner iframe anymore.\n    expect(innerIframe?.hasAttribute('srcdoc')).toBe(false);\n  });\n});\n"
  },
  {
    "path": "sdks/typescript/client/src/index.ts",
    "content": "export { getUIResourceMetadata, getResourceMetadata } from './utils/metadataUtils';\nexport { isUIResource } from './utils/isUIResource';\n\n// Client capabilities for UI extension support\nexport {\n  type ClientCapabilitiesWithExtensions,\n  UI_EXTENSION_NAME,\n  UI_EXTENSION_CONFIG,\n  UI_EXTENSION_CAPABILITIES,\n} from './capabilities';\n\n// MCP Apps renderers\nexport {\n  AppRenderer,\n  type AppRendererProps,\n  type AppRendererHandle,\n  type RequestHandlerExtra,\n} from './components/AppRenderer';\nexport {\n  AppFrame,\n  type AppFrameProps,\n  type SandboxConfig,\n  type AppInfo,\n} from './components/AppFrame';\n\n// Re-export AppBridge, transport, and common types for advanced use cases\nexport {\n  AppBridge,\n  PostMessageTransport,\n  type McpUiHostContext,\n} from '@modelcontextprotocol/ext-apps/app-bridge';\n\n// Re-export JSONRPCRequest for typing onFallbackRequest handlers\nexport type { JSONRPCRequest } from '@modelcontextprotocol/sdk/types.js';\n\nexport type { UIResourceMetadata } from './types';\n"
  },
  {
    "path": "sdks/typescript/client/src/test-setup.ts",
    "content": "import '@testing-library/jest-dom';\n\n// Polyfill for requestAnimationFrame\nif (typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'undefined') {\n  window.requestAnimationFrame = (callback) => {\n    return setTimeout(callback, 0);\n  };\n}\n\nif (typeof window !== 'undefined' && typeof window.cancelAnimationFrame === 'undefined') {\n  window.cancelAnimationFrame = (id) => {\n    clearTimeout(id);\n  };\n}\n"
  },
  {
    "path": "sdks/typescript/client/src/types.ts",
    "content": "export const UIMetadataKey = {\n  PREFERRED_FRAME_SIZE: 'preferred-frame-size',\n  INITIAL_RENDER_DATA: 'initial-render-data',\n} as const;\n\nexport const UI_METADATA_PREFIX = 'mcpui.dev/ui-';\n\nexport type UIResourceMetadata = {\n  [UIMetadataKey.PREFERRED_FRAME_SIZE]?: [string, string];\n  [UIMetadataKey.INITIAL_RENDER_DATA]?: Record<string, unknown>;\n};\n"
  },
  {
    "path": "sdks/typescript/client/src/utils/__tests__/isUIResource.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { isUIResource } from '../isUIResource';\n\ndescribe('isUIResource', () => {\n  const uiResources = [\n    { type: 'resource', resource: { uri: 'ui://test' } },\n    {\n      type: 'resource',\n      resource: { uri: 'ui://test', mimeType: 'text/html', text: 'Hello, world!' },\n      arbitraryProp: 'arbitrary',\n    },\n    {\n      type: 'resource',\n      resource: {\n        uri: 'ui://test',\n        mimeType: 'text/html;profile=mcp-app',\n        text: 'https://example.com',\n      },\n    },\n    {\n      type: 'resource',\n      resource: {\n        uri: 'ui://test',\n        mimeType: 'text/html;profile=mcp-app',\n        text: 'Hello, world!',\n      },\n    },\n  ];\n\n  const nonUiResources = [\n    { type: 'resource', resource: { uri: 'https://example.com' } },\n    { type: 'text', text: 'Hello, world!' },\n    { type: 'text', resource: { uri: 'ui://test' } },\n  ];\n\n  it(`should return true if the content is a UI resource: ${JSON.stringify(uiResources)}`, () => {\n    uiResources.forEach((content) => {\n      expect(isUIResource(content)).toBe(true);\n    });\n  });\n\n  nonUiResources.forEach((content) => {\n    it(`should return false if the content is not a UI resource: ${JSON.stringify(content)}`, () => {\n      expect(isUIResource(content)).toBe(false);\n    });\n  });\n\n  it('should return false if the content is not a UI resource', () => {\n    const content = { type: 'resource', resource: { uri: 'https://example.com' } };\n    expect(isUIResource(content)).toBe(false);\n  });\n});\n"
  },
  {
    "path": "sdks/typescript/client/src/utils/__tests__/metadataUtils.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { getResourceMetadata, getUIResourceMetadata } from '../metadataUtils';\nimport { UI_METADATA_PREFIX } from '../../types';\n\ndescribe('metadataUtils', () => {\n  it('should get resource metadata', () => {\n    const resource = { _meta: { foo: 'bar' } };\n    expect(getResourceMetadata(resource)).toEqual({ foo: 'bar' });\n  });\n\n  it('should get ui resource metadata', () => {\n    const resource = { _meta: { [`${UI_METADATA_PREFIX}foo`]: 'bar' } };\n    expect(getUIResourceMetadata(resource)).toEqual({ foo: 'bar' });\n  });\n\n  it('should get ui resource metadata with prefix and user defined metadata', () => {\n    const resource = { _meta: { [`${UI_METADATA_PREFIX}foo`]: 'bar', foo: 'baz' } };\n    expect(getUIResourceMetadata(resource)).toEqual({ foo: 'bar' });\n  });\n});\n"
  },
  {
    "path": "sdks/typescript/client/src/utils/app-host-utils.ts",
    "content": "import {\n  SANDBOX_PROXY_READY_METHOD,\n  getToolUiResourceUri as _getToolUiResourceUri,\n  RESOURCE_MIME_TYPE,\n} from '@modelcontextprotocol/ext-apps/app-bridge';\nimport { type Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport { type Tool } from '@modelcontextprotocol/sdk/types.js';\nconst DEFAULT_SANDBOX_TIMEOUT_MS = 10000;\n\nexport async function setupSandboxProxyIframe(sandboxProxyUrl: URL): Promise<{\n  iframe: HTMLIFrameElement;\n  onReady: Promise<void>;\n}> {\n  const iframe = document.createElement('iframe');\n  iframe.style.width = '100%';\n  iframe.style.height = '600px';\n  iframe.style.border = 'none';\n  iframe.style.backgroundColor = 'transparent';\n  iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms');\n\n  const onReady = new Promise<void>((resolve, reject) => {\n    let settled = false;\n\n    const cleanup = () => {\n      window.removeEventListener('message', messageListener);\n      iframe.removeEventListener('error', errorListener);\n    };\n\n    const timeoutId = setTimeout(() => {\n      if (!settled) {\n        settled = true;\n        cleanup();\n        reject(new Error('Timed out waiting for sandbox proxy iframe to be ready'));\n      }\n    }, DEFAULT_SANDBOX_TIMEOUT_MS);\n\n    const messageListener = (event: MessageEvent) => {\n      if (event.source === iframe.contentWindow) {\n        if (event.data && event.data.method === SANDBOX_PROXY_READY_METHOD) {\n          if (!settled) {\n            settled = true;\n            clearTimeout(timeoutId);\n            cleanup();\n            resolve();\n          }\n        }\n      }\n    };\n\n    const errorListener = () => {\n      if (!settled) {\n        settled = true;\n        clearTimeout(timeoutId);\n        cleanup();\n        reject(new Error('Failed to load sandbox proxy iframe'));\n      }\n    };\n\n    window.addEventListener('message', messageListener);\n    iframe.addEventListener('error', errorListener);\n  });\n\n  iframe.src = sandboxProxyUrl.href;\n\n  return { iframe, onReady };\n}\n\nexport type ToolUiResourceInfo = {\n  uri: string;\n};\n\nexport async function getToolUiResourceUri(\n  client: Client,\n  toolName: string,\n): Promise<ToolUiResourceInfo | null> {\n  let tool: Tool | undefined;\n  let cursor: string | undefined = undefined;\n  do {\n    const toolsResult = await client.listTools({ cursor });\n    tool = toolsResult.tools.find((t) => t.name === toolName);\n    cursor = toolsResult.nextCursor;\n  } while (!tool && cursor);\n  if (!tool) {\n    throw new Error(`tool ${toolName} not found`);\n  }\n  if (!tool._meta) {\n    return null;\n  }\n\n  const uri = _getToolUiResourceUri(tool);\n  if (!uri) {\n    return null;\n  }\n  if (!uri.startsWith('ui://')) {\n    throw new Error(`tool ${toolName} has unsupported output template URI: ${uri}`);\n  }\n  return { uri };\n}\n\nexport async function readToolUiResourceHtml(\n  client: Client,\n  opts: {\n    uri: string;\n  },\n): Promise<string> {\n  const resource = await client.readResource({ uri: opts.uri });\n\n  if (!resource) {\n    throw new Error('UI resource not found: ' + opts.uri);\n  }\n  if (resource.contents.length !== 1) {\n    throw new Error('Unsupported UI resource content length: ' + resource.contents.length);\n  }\n  const content = resource.contents[0];\n  let html: string;\n  const isHtml = (t?: string) => t === RESOURCE_MIME_TYPE;\n\n  if ('text' in content && typeof content.text === 'string' && isHtml(content.mimeType)) {\n    html = content.text;\n  } else if ('blob' in content && typeof content.blob === 'string' && isHtml(content.mimeType)) {\n    html = atob(content.blob);\n  } else {\n    throw new Error('Unsupported UI resource content format: ' + JSON.stringify(content));\n  }\n\n  return html;\n}\n"
  },
  {
    "path": "sdks/typescript/client/src/utils/isUIResource.ts",
    "content": "import type { EmbeddedResource } from '@modelcontextprotocol/sdk/types.js';\n\nexport function isUIResource<\n  T extends { type: string; resource?: Partial<EmbeddedResource['resource']> },\n>(\n  content: T,\n): content is T & { type: 'resource'; resource: Partial<EmbeddedResource['resource']> } {\n  return (content.type === 'resource' && content.resource?.uri?.startsWith('ui://')) ?? false;\n}\n"
  },
  {
    "path": "sdks/typescript/client/src/utils/metadataUtils.ts",
    "content": "import type { Resource } from '@modelcontextprotocol/sdk/types.js';\nimport { UI_METADATA_PREFIX, type UIResourceMetadata } from '../types';\n\nexport function getResourceMetadata(resource: Partial<Resource>): Record<string, unknown> {\n  return resource._meta ?? {};\n}\n\nexport function getUIResourceMetadata(\n  resource: Partial<Resource>,\n): UIResourceMetadata & Record<string, unknown> {\n  const metadata = getResourceMetadata(resource);\n  const uiMetadata: UIResourceMetadata & Record<string, unknown> = {};\n  Object.entries(metadata).forEach(([key, value]) => {\n    if (key.startsWith(UI_METADATA_PREFIX)) {\n      uiMetadata[key.slice(UI_METADATA_PREFIX.length)] = value;\n    }\n  });\n  return uiMetadata;\n}\n"
  },
  {
    "path": "sdks/typescript/client/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"jsx\": \"react-jsx\",\n    \"forceConsistentCasingInFileNames\": true\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\", \"dist\", \"src/__tests__\"]\n}\n"
  },
  {
    "path": "sdks/typescript/client/tsconfig.test.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"jsx\": \"react-jsx\"\n  },\n  \"include\": [\"src/**/*.ts\", \"src/**/*.tsx\"]\n}\n"
  },
  {
    "path": "sdks/typescript/client/vite.config.ts",
    "content": "import { defineConfig } from 'vite';\nimport dts from 'vite-plugin-dts';\nimport path from 'path';\nimport react from '@vitejs/plugin-react-swc';\n\nexport default defineConfig({\n  plugins: [\n    react(),\n    dts({\n      insertTypesEntry: false,\n      exclude: ['**/__tests__/**', '**/*.test.ts', '**/*.spec.ts'],\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    }) as any,\n  ],\n  build: {\n    emptyOutDir: false,\n    lib: {\n      entry: path.resolve(__dirname, 'src/index.ts'),\n      name: 'McpUiClient',\n      formats: ['es', 'umd'],\n      fileName: (format) =>\n        `index.${format === 'es' ? 'mjs' : format === 'umd' ? 'js' : format + '.js'}`,\n    },\n    rollupOptions: {\n      external: [\n        'react',\n        'react/jsx-runtime',\n        '@mcp-ui/shared',\n        /@modelcontextprotocol\\/sdk(\\/.*)?/,\n      ],\n      output: {\n        globals: {\n          react: 'React',\n          'react/jsx-runtime': 'jsxRuntime',\n          '@mcp-ui/shared': 'McpUiShared',\n          '@modelcontextprotocol/sdk': 'ModelContextProtocolSDK',\n        },\n      },\n    },\n    sourcemap: false,\n  },\n  // Vitest specific config can go here if not using a separate vitest.config.ts for the package\n  // test: { ... }\n});\n"
  },
  {
    "path": "sdks/typescript/client/vitest.config.ts",
    "content": "import { defineConfig } from 'vitest/config';\nimport react from '@vitejs/plugin-react-swc';\nimport tsconfigPaths from 'vite-tsconfig-paths';\n\nexport default defineConfig({\n  plugins: [react(), tsconfigPaths({ projects: ['./tsconfig.test.json'] })],\n  test: {\n    globals: true,\n    environment: 'jsdom',\n    setupFiles: ['./src/test-setup.ts'],\n  },\n});\n"
  },
  {
    "path": "sdks/typescript/server/CHANGELOG.md",
    "content": "# [6.1.0](https://github.com/MCP-UI-Org/mcp-ui/compare/server/v6.0.1...server/v6.1.0) (2026-02-13)\n\n\n### Features\n\n* support experimental messages ([#176](https://github.com/MCP-UI-Org/mcp-ui/issues/176)) ([327e4d9](https://github.com/MCP-UI-Org/mcp-ui/commit/327e4d9e6771eceb42f8823ae864e6758f371970))\n\n## [6.0.1](https://github.com/MCP-UI-Org/mcp-ui/compare/server/v6.0.0...server/v6.0.1) (2026-02-06)\n\n\n### Bug Fixes\n\n* trigger MCP Apps client release ([#173](https://github.com/MCP-UI-Org/mcp-ui/issues/173)) ([b0c0659](https://github.com/MCP-UI-Org/mcp-ui/commit/b0c0659700b493a0083d0de12958f276e4c37715))\n\n# [6.0.0](https://github.com/MCP-UI-Org/mcp-ui/compare/server/v5.17.0...server/v6.0.0) (2026-01-26)\n\n\n### Features\n\n* mcp-ui -> mcp apps! ([#172](https://github.com/MCP-UI-Org/mcp-ui/issues/172)) ([93a6e22](https://github.com/MCP-UI-Org/mcp-ui/commit/93a6e225808894cac3206504cf15c481f88ed540))\n\n\n### BREAKING CHANGES\n\n* removed discarded content types, changed mimetype, updated docs, etc.\n\n# [5.17.0](https://github.com/MCP-UI-Org/mcp-ui/compare/server/v5.16.3...server/v5.17.0) (2026-01-25)\n\n\n### Bug Fixes\n\n* add connectedMoveCallback to the WC implementation ([#170](https://github.com/MCP-UI-Org/mcp-ui/issues/170)) ([5ac4734](https://github.com/MCP-UI-Org/mcp-ui/commit/5ac4734078ff56e213e7a57029fd612638abb0b4))\n\n\n### Features\n\n* React renderer for MCP Apps ([#147](https://github.com/MCP-UI-Org/mcp-ui/issues/147)) ([fbc918d](https://github.com/MCP-UI-Org/mcp-ui/commit/fbc918d713861396703ede124c5676cf3d1164dd))\n\n## [5.16.3](https://github.com/MCP-UI-Org/mcp-ui/compare/server/v5.16.2...server/v5.16.3) (2025-12-18)\n\n\n### Bug Fixes\n\n* **client:** use type-only imports for @modelcontextprotocol/sdk types ([#157](https://github.com/MCP-UI-Org/mcp-ui/issues/157)) ([d84cb3e](https://github.com/MCP-UI-Org/mcp-ui/commit/d84cb3e217dfa90f038a8fbe64b4ffd0e0a1bc3f))\n* trigger TS release ([e28d1f6](https://github.com/MCP-UI-Org/mcp-ui/commit/e28d1f65cd5f26e46801480995e178ee50c31e51))\n* update adapter to latest MCP Apps spec ([#163](https://github.com/MCP-UI-Org/mcp-ui/issues/163)) ([e342034](https://github.com/MCP-UI-Org/mcp-ui/commit/e3420348098fe716f45b2182bc8c8d3358714035))\n* update MCP Apps adapter mimetype ([#162](https://github.com/MCP-UI-Org/mcp-ui/issues/162)) ([c91e533](https://github.com/MCP-UI-Org/mcp-ui/commit/c91e5331b68c65de2347b16766a5711e05985e68))\n* update ui/message to pass an array ([#167](https://github.com/MCP-UI-Org/mcp-ui/issues/167)) ([80cf222](https://github.com/MCP-UI-Org/mcp-ui/commit/80cf22290ff5e2f981be839f74705454483c21b5))\n\n## [5.16.2](https://github.com/MCP-UI-Org/mcp-ui/compare/server/v5.16.1...server/v5.16.2) (2025-12-08)\n\n\n### Bug Fixes\n\n* revert externalUrl adapter for compatibility ([#159](https://github.com/MCP-UI-Org/mcp-ui/issues/159)) ([3c5289a](https://github.com/MCP-UI-Org/mcp-ui/commit/3c5289a5e59812f556bc2bb9e67c3ce2e3ce764e))\n\n## [5.16.1](https://github.com/MCP-UI-Org/mcp-ui/compare/server/v5.16.0...server/v5.16.1) (2025-12-01)\n\n\n### Bug Fixes\n\n* use document.write in proxy sandbox ([#156](https://github.com/MCP-UI-Org/mcp-ui/issues/156)) ([447f582](https://github.com/MCP-UI-Org/mcp-ui/commit/447f582105a0889ee47d912925d7f02034fc39a9))\n\n# [5.16.0](https://github.com/MCP-UI-Org/mcp-ui/compare/server/v5.15.0...server/v5.16.0) (2025-11-30)\n\n\n### Features\n\n* externalUrl adapter ([5a1e4de](https://github.com/MCP-UI-Org/mcp-ui/commit/5a1e4deeeda9ae0120d17b5abdf9096edf52054f))\n\n# [5.15.0](https://github.com/MCP-UI-Org/mcp-ui/compare/server/v5.14.0...server/v5.15.0) (2025-11-29)\n\n\n### Features\n\n* add MCP Apps adapter ([#154](https://github.com/MCP-UI-Org/mcp-ui/issues/154)) ([986fd63](https://github.com/MCP-UI-Org/mcp-ui/commit/986fd634448637aa6488a18ad80ee9cd6b8de318))\n\n# [5.14.0](https://github.com/MCP-UI-Org/mcp-ui/compare/server/v5.13.1...server/v5.14.0) (2025-11-29)\n\n\n### Features\n\n* support metadata in Python SDK ([#134](https://github.com/MCP-UI-Org/mcp-ui/issues/134)) ([9bc3c64](https://github.com/MCP-UI-Org/mcp-ui/commit/9bc3c646c2638e16ac62edf9faca2dbee2b8cb7e))\n\n## [5.13.1](https://github.com/idosal/mcp-ui/compare/server/v5.13.0...server/v5.13.1) (2025-10-25)\n\n\n### Bug Fixes\n\n* Enable bidirectional message relay in rawhtml proxy mode ([#138](https://github.com/idosal/mcp-ui/issues/138)) ([f0bdefb](https://github.com/idosal/mcp-ui/commit/f0bdefb818aca5a3bfbdfe28fc4fe057b1818cb5))\n* ensure Apps SDK adapter is bundled properly and initialized wth config ([#137](https://github.com/idosal/mcp-ui/issues/137)) ([4f7c25c](https://github.com/idosal/mcp-ui/commit/4f7c25ce7e6f25e36cfc188016b012d31d722204))\n\n# [5.13.0](https://github.com/idosal/mcp-ui/compare/server/v5.12.0...server/v5.13.0) (2025-10-22)\n\n\n### Bug Fixes\n\n* fix file extension reference in package.json ([927989c](https://github.com/idosal/mcp-ui/commit/927989c4f81742106b6f5e2f68343fb7ea7d016a))\n\n\n### Features\n\n* support proxy for rawHtml ([#132](https://github.com/idosal/mcp-ui/issues/132)) ([1bbeb09](https://github.com/idosal/mcp-ui/commit/1bbeb093bc9c389f4ccfd9e8df06dc7f1eaadde0))\n\n# [5.12.0](https://github.com/idosal/mcp-ui/compare/server/v5.11.0...server/v5.12.0) (2025-10-13)\n\n\n### Bug Fixes\n\n* update isUIResource to use EmbeddedResource type ([#122](https://github.com/idosal/mcp-ui/issues/122)) ([5a65a0b](https://github.com/idosal/mcp-ui/commit/5a65a0b1ba63e6cfda26b8da41239a532f00d60a)), closes [#117](https://github.com/idosal/mcp-ui/issues/117)\n\n\n### Features\n\n* add ui-request-render-data message type ([#111](https://github.com/idosal/mcp-ui/issues/111)) ([26135ce](https://github.com/idosal/mcp-ui/commit/26135ce2c7f7d586b0b81a03623cd77dc1bc7f90))\n* support adapters ([#127](https://github.com/idosal/mcp-ui/issues/127)) ([d4bd152](https://github.com/idosal/mcp-ui/commit/d4bd152db0a1bd27081502098f3cd9aa54ca359e))\n\n# [5.12.0-alpha.6](https://github.com/idosal/mcp-ui/compare/server/v5.12.0-alpha.5...server/v5.12.0-alpha.6) (2025-10-13)\n\n\n### Bug Fixes\n\n* separate adapter wrappers for flexibility ([#128](https://github.com/idosal/mcp-ui/issues/128)) ([a636844](https://github.com/idosal/mcp-ui/commit/a6368448c44b15da87c883cb3a65ebfe6119e85d))\n\n# [5.12.0-alpha.5](https://github.com/idosal/mcp-ui/compare/server/v5.12.0-alpha.4...server/v5.12.0-alpha.5) (2025-10-12)\n\n\n### Bug Fixes\n\n* exports vite ([4de2b0c](https://github.com/idosal/mcp-ui/commit/4de2b0cfae91813ad68fb1ce68b1cf7c2a161baf))\n\n# [5.12.0-alpha.4](https://github.com/idosal/mcp-ui/compare/server/v5.12.0-alpha.3...server/v5.12.0-alpha.4) (2025-10-11)\n\n\n### Bug Fixes\n\n* exports ([0018c17](https://github.com/idosal/mcp-ui/commit/0018c17dd8b184ee549327f1742d9da71edfd576))\n\n# [5.12.0-alpha.3](https://github.com/idosal/mcp-ui/compare/server/v5.12.0-alpha.2...server/v5.12.0-alpha.3) (2025-10-11)\n\n\n### Bug Fixes\n\n* version ([767a245](https://github.com/idosal/mcp-ui/commit/767a245d2374f05e27ece090dc5af8613a9a6b96))\n\n# [5.12.0-alpha.2](https://github.com/idosal/mcp-ui/compare/server/v5.12.0-alpha.1...server/v5.12.0-alpha.2) (2025-10-10)\n\n\n### Bug Fixes\n\n* set the mime type as text/html+skybridge for apps SDK ([bc47423](https://github.com/idosal/mcp-ui/commit/bc474232a249e5cc40f348e3a26f93c806fcc602))\n\n# [5.12.0-alpha.1](https://github.com/idosal/mcp-ui/compare/server/v5.11.0...server/v5.12.0-alpha.1) (2025-10-10)\n\n\n### Bug Fixes\n\n* adapter version ([259c842](https://github.com/idosal/mcp-ui/commit/259c84247a00933575e1fff08674cce52be59973))\n* release ([420efc0](https://github.com/idosal/mcp-ui/commit/420efc0a82bf8de2731514648268cad1209320e2))\n* server alpha versioning ([7f35d3b](https://github.com/idosal/mcp-ui/commit/7f35d3be2cfa6a535d3fbd5f86fbec1b20432dca))\n* server versioning ([2324371](https://github.com/idosal/mcp-ui/commit/2324371ed636381bb44a1feae1b59a87c84c6666))\n* update isUIResource to use EmbeddedResource type ([#122](https://github.com/idosal/mcp-ui/issues/122)) ([5a65a0b](https://github.com/idosal/mcp-ui/commit/5a65a0b1ba63e6cfda26b8da41239a532f00d60a)), closes [#117](https://github.com/idosal/mcp-ui/issues/117)\n\n\n### Features\n\n* add adapters infra (appssdk) ([#125](https://github.com/idosal/mcp-ui/issues/125)) ([2e016cd](https://github.com/idosal/mcp-ui/commit/2e016cdc05d08c2f7c2e4a40efbec2b0704e7ef6))\n* add ui-request-render-data message type ([#111](https://github.com/idosal/mcp-ui/issues/111)) ([26135ce](https://github.com/idosal/mcp-ui/commit/26135ce2c7f7d586b0b81a03623cd77dc1bc7f90))\n\n# 1.0.0-alpha.1 (2025-10-10)\n\n\n### Bug Fixes\n\n* adapter version ([259c842](https://github.com/idosal/mcp-ui/commit/259c84247a00933575e1fff08674cce52be59973))\n* add a bridge to pass messages in and out of the proxy ([#38](https://github.com/idosal/mcp-ui/issues/38)) ([30ccac0](https://github.com/idosal/mcp-ui/commit/30ccac0706ad8e02ebcd8960924ed1d58ddedf85))\n* bump client version ([75c9236](https://github.com/idosal/mcp-ui/commit/75c923689654b4443ad1093fafc0bad16902e4cc))\n* **client:** specify iframe ([fd0b70a](https://github.com/idosal/mcp-ui/commit/fd0b70a84948d3aa5d7a79269ff7c3bcd0946689))\n* **client:** styling ([6ff9b68](https://github.com/idosal/mcp-ui/commit/6ff9b685fd1be770fd103943e45275e9ec86905c))\n* dependencies ([887f61f](https://github.com/idosal/mcp-ui/commit/887f61f827b4585c17493d4fa2dfb251ea598587))\n* export RemoteDomResource ([2b86f2d](https://github.com/idosal/mcp-ui/commit/2b86f2dd4506de49c69908e23d84a2a323170446))\n* export ResourceRenderer and HtmlResource ([2b841a5](https://github.com/idosal/mcp-ui/commit/2b841a556c1111ed70ccb3d3987afd21fe7df897))\n* exports ([3a93a16](https://github.com/idosal/mcp-ui/commit/3a93a16e1b7438ba7b2ef49ca854479f755abcc6))\n* iframe handle ([#15](https://github.com/idosal/mcp-ui/issues/15)) ([66bd4fd](https://github.com/idosal/mcp-ui/commit/66bd4fd3d04f82e3e4557f064e701b68e1d8af11))\n* lint ([4487820](https://github.com/idosal/mcp-ui/commit/44878203a71c3c9173d463b809be36769e996ba9))\n* lint ([d0a91f9](https://github.com/idosal/mcp-ui/commit/d0a91f9a07ec0042690240c3d8d0bad620f8c765))\n* minor typo ([a0bee9c](https://github.com/idosal/mcp-ui/commit/a0bee9c85e5ee02e021ba687940ced38220445fe))\n* move react dependencies to be peer dependencies ([#91](https://github.com/idosal/mcp-ui/issues/91)) ([f672f3e](https://github.com/idosal/mcp-ui/commit/f672f3efc1c2ba2fbae16f9dcdc2142c2b4bd920)), closes [#90](https://github.com/idosal/mcp-ui/issues/90)\n* package config ([8dc1e53](https://github.com/idosal/mcp-ui/commit/8dc1e5358c3c8e641206a5e6851427d360cc1955))\n* packaging ([9e6babd](https://github.com/idosal/mcp-ui/commit/9e6babd3a587213452ea7aec4cc9ae3a50fa1965))\n* pass ref explicitly using iframeProps ([#33](https://github.com/idosal/mcp-ui/issues/33)) ([d01b5d1](https://github.com/idosal/mcp-ui/commit/d01b5d1e4cdaedc436ba2fa8984d866d93d59087))\n* publish ([0943e7a](https://github.com/idosal/mcp-ui/commit/0943e7acaf17f32aae085c2313bfbec47bc59f1f))\n* ref passing to UIResourceRenderer ([#32](https://github.com/idosal/mcp-ui/issues/32)) ([d28c23f](https://github.com/idosal/mcp-ui/commit/d28c23f9b8ee320f4e361200ae02a23f0d2a1c0c))\n* release ([420efc0](https://github.com/idosal/mcp-ui/commit/420efc0a82bf8de2731514648268cad1209320e2))\n* remove shared dependency ([e66e8f4](https://github.com/idosal/mcp-ui/commit/e66e8f49b1ba46090db6e4682060488566f4fe41))\n* rename components and methods to fit new scope ([#22](https://github.com/idosal/mcp-ui/issues/22)) ([6bab1fe](https://github.com/idosal/mcp-ui/commit/6bab1fe3a168a18e7ba4762e23478abf4e0cc84c))\n* rename delivery -> encoding and flavor -> framework ([#36](https://github.com/idosal/mcp-ui/issues/36)) ([9a509ed](https://github.com/idosal/mcp-ui/commit/9a509ed80d051b0a8042b36958b401a0a7c1e138))\n* Ruby comment ([b22dc2e](https://github.com/idosal/mcp-ui/commit/b22dc2e0a0db20d98ada884649ad408ebaf72d22))\n* server versioning ([2324371](https://github.com/idosal/mcp-ui/commit/2324371ed636381bb44a1feae1b59a87c84c6666))\n* support react-router ([21ffb95](https://github.com/idosal/mcp-ui/commit/21ffb95fe6d77a348b95b38dbf3741ba6442894e))\n* text and blob support in RemoteDOM resources ([ec68eb9](https://github.com/idosal/mcp-ui/commit/ec68eb90df984da8b492cc25eafdafdeda79f299))\n* trigger release ([aaca831](https://github.com/idosal/mcp-ui/commit/aaca83125c3f7825ccdebf0f04f8553e953c5249))\n* typescript ci publish ([e7c0ebf](https://github.com/idosal/mcp-ui/commit/e7c0ebfa7f7b552f9763743fda659d1441f21692))\n* typescript types to be compatible with MCP SDK ([#10](https://github.com/idosal/mcp-ui/issues/10)) ([74365d7](https://github.com/idosal/mcp-ui/commit/74365d7ed6422beef6cd9ee0f5a97c847bd9827b))\n* update deps ([4091ef4](https://github.com/idosal/mcp-ui/commit/4091ef47da048fab3c4feb002f5287b2ff295744))\n* update isUIResource to use EmbeddedResource type ([#122](https://github.com/idosal/mcp-ui/issues/122)) ([5a65a0b](https://github.com/idosal/mcp-ui/commit/5a65a0b1ba63e6cfda26b8da41239a532f00d60a)), closes [#117](https://github.com/idosal/mcp-ui/issues/117)\n* use targetOrigin in the proxy message relay ([#40](https://github.com/idosal/mcp-ui/issues/40)) ([b3fb54e](https://github.com/idosal/mcp-ui/commit/b3fb54e28ca7b8eeda896b5bcf478b6343dbba47))\n* validate URL ([b7c994d](https://github.com/idosal/mcp-ui/commit/b7c994dfdd947b3dfbb903fc8cb896d61004c8d8))\n* wc dist overwrite ([#63](https://github.com/idosal/mcp-ui/issues/63)) ([9e46c56](https://github.com/idosal/mcp-ui/commit/9e46c56c7a8908410fad6d08a5d845139e93f80f))\n\n\n### Documentation\n\n* bump ([#4](https://github.com/idosal/mcp-ui/issues/4)) ([ad4d163](https://github.com/idosal/mcp-ui/commit/ad4d1632cc1f9c99072349a8f0cdaac343236132))\n\n\n### Features\n\n* add adapters infra (appssdk) ([#125](https://github.com/idosal/mcp-ui/issues/125)) ([2e016cd](https://github.com/idosal/mcp-ui/commit/2e016cdc05d08c2f7c2e4a40efbec2b0704e7ef6))\n* add convenience function isUIResource to client SDK ([#86](https://github.com/idosal/mcp-ui/issues/86)) ([607c6ad](https://github.com/idosal/mcp-ui/commit/607c6add3567bb60c45accf3e1b25a38ed284a6f))\n* add embeddedResourceProps for annotations ([#99](https://github.com/idosal/mcp-ui/issues/99)) ([b96ec44](https://github.com/idosal/mcp-ui/commit/b96ec442ec319a1944393ada0bdcccb93b7ffc62))\n* add proxy option to externalUrl ([#37](https://github.com/idosal/mcp-ui/issues/37)) ([7b95cd0](https://github.com/idosal/mcp-ui/commit/7b95cd0b3873fc1cde28748ec463e81c6ff1c494))\n* add remote-dom content type ([#18](https://github.com/idosal/mcp-ui/issues/18)) ([5dacf37](https://github.com/idosal/mcp-ui/commit/5dacf37c22b5ee6ae795049a8d573fc073b8a1f5))\n* add Ruby server SDK ([#31](https://github.com/idosal/mcp-ui/issues/31)) ([5ffcde4](https://github.com/idosal/mcp-ui/commit/5ffcde4a373accdd063fa6c3b1b3d4df13c91b53))\n* add sandbox permissions instead of an override ([#83](https://github.com/idosal/mcp-ui/issues/83)) ([b1068e9](https://github.com/idosal/mcp-ui/commit/b1068e9e87caa2b4302bf145a33efdfd1af05c1d))\n* add ui-request-render-data message type ([#111](https://github.com/idosal/mcp-ui/issues/111)) ([26135ce](https://github.com/idosal/mcp-ui/commit/26135ce2c7f7d586b0b81a03623cd77dc1bc7f90))\n* add UIResourceRenderer Web Component ([#58](https://github.com/idosal/mcp-ui/issues/58)) ([ec8f299](https://github.com/idosal/mcp-ui/commit/ec8f2994ecf36774e6ad5191654ba22946d0ee49))\n* auto resize with the autoResizeIframe prop ([#56](https://github.com/idosal/mcp-ui/issues/56)) ([76c867a](https://github.com/idosal/mcp-ui/commit/76c867a569b72aed892290aa84e1194ab8eb79ce))\n* change onGenericMcpAction to optional onUiAction ([1913b59](https://github.com/idosal/mcp-ui/commit/1913b5977c30811f9e67659949e2d961f2eda983))\n* **client:** allow setting supportedContentTypes for HtmlResource ([#17](https://github.com/idosal/mcp-ui/issues/17)) ([e009ef1](https://github.com/idosal/mcp-ui/commit/e009ef10010134ba3d9893314cc4d8e1274f1f07))\n* consolidate ui:// and ui-app:// ([#8](https://github.com/idosal/mcp-ui/issues/8)) ([2e08035](https://github.com/idosal/mcp-ui/commit/2e08035676bb6a46ef3c94dba916bc895f1fa3cc))\n* pass iframe props down ([#14](https://github.com/idosal/mcp-ui/issues/14)) ([112539d](https://github.com/idosal/mcp-ui/commit/112539d28640a96e8375a6b416f2ba559370b312))\n* refactor UTFtoB64 (bump server version) ([#95](https://github.com/idosal/mcp-ui/issues/95)) ([2d5e16b](https://github.com/idosal/mcp-ui/commit/2d5e16bf39073ee890586f458412f0c3b474c2b8))\n* send render data to the iframe ([#51](https://github.com/idosal/mcp-ui/issues/51)) ([d38cfc7](https://github.com/idosal/mcp-ui/commit/d38cfc7925061c1ae1911bdee408033c8e9f283d))\n* separate html and remote-dom props ([#24](https://github.com/idosal/mcp-ui/issues/24)) ([a7f0529](https://github.com/idosal/mcp-ui/commit/a7f05299dc9cc40184f9ab25c5b648ee7077be64))\n* support generic messages response ([#35](https://github.com/idosal/mcp-ui/issues/35)) ([10b407b](https://github.com/idosal/mcp-ui/commit/10b407b279b3ee9608ef077445f4d714f88343c5))\n* support passing resource metadata ([#87](https://github.com/idosal/mcp-ui/issues/87)) ([f1c1c9b](https://github.com/idosal/mcp-ui/commit/f1c1c9b62dd74c63045b295eb388181843ac772a))\n* support ui action result types ([#6](https://github.com/idosal/mcp-ui/issues/6)) ([899d152](https://github.com/idosal/mcp-ui/commit/899d1527286a281a23fbb8f3a207d435dfc3fe96))\n* switch to ResourceRenderer ([#21](https://github.com/idosal/mcp-ui/issues/21)) ([6fe3166](https://github.com/idosal/mcp-ui/commit/6fe316682675e27db914d60696754677e3783448))\n\n\n### BREAKING CHANGES\n\n* The existing naming is ambiguous. Renaming delivery to encoding and flavor to framework should clarify the intent.\n* exported names have changed\n* removed deprecated client API\n* (previous one didn't take due to semantic-release misalignment)\n\n# [5.11.0](https://github.com/idosal/mcp-ui/compare/v5.10.0...v5.11.0) (2025-09-16)\n\n\n### Features\n\n* add embeddedResourceProps for annotations ([#99](https://github.com/idosal/mcp-ui/issues/99)) ([b96ec44](https://github.com/idosal/mcp-ui/commit/b96ec442ec319a1944393ada0bdcccb93b7ffc62))\n\n# [5.10.0](https://github.com/idosal/mcp-ui/compare/v5.9.0...v5.10.0) (2025-08-22)\n\n\n### Features\n\n* refactor UTFtoB64 (bump server version) ([#95](https://github.com/idosal/mcp-ui/issues/95)) ([2d5e16b](https://github.com/idosal/mcp-ui/commit/2d5e16bf39073ee890586f458412f0c3b474c2b8))\n"
  },
  {
    "path": "sdks/typescript/server/README.md",
    "content": "## 📦 Model Context Protocol UI SDK\n\n<p align=\"center\">\n  <img width=\"250\" alt=\"image\" src=\"https://github.com/user-attachments/assets/65b9698f-990f-4846-9b2d-88de91d53d4d\" />\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@mcp-ui/server\"><img src=\"https://img.shields.io/npm/v/@mcp-ui/server?label=server&color=green\" alt=\"Server Version\"></a>\n  <a href=\"https://www.npmjs.com/package/@mcp-ui/client\"><img src=\"https://img.shields.io/npm/v/@mcp-ui/client?label=client&color=blue\" alt=\"Client Version\"></a>\n  <a href=\"https://rubygems.org/gems/mcp_ui_server\"><img src=\"https://img.shields.io/gem/v/mcp_ui_server\" alt=\"Ruby Server SDK Version\"></a>\n  <a href=\"https://pypi.org/project/mcp-ui-server/\"><img src=\"https://img.shields.io/pypi/v/mcp-ui-server?label=python&color=yellow\" alt=\"Python Server SDK Version\"></a>\n  <a href=\"https://discord.gg/CEAG4KW7ZH\"><img src=\"https://img.shields.io/discord/1401195140436983879?logo=discord&label=discord\" alt=\"Discord\"></a>\n  <a href=\"https://gitmcp.io/idosal/mcp-ui\"><img src=\"https://img.shields.io/endpoint?url=https://gitmcp.io/badge/idosal/mcp-ui\" alt=\"MCP Documentation\"></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"#-whats-mcp-ui\">What's mcp-ui?</a> •\n  <a href=\"#-core-concepts\">Core Concepts</a> •\n  <a href=\"#-installation\">Installation</a> •\n  <a href=\"#-getting-started\">Getting Started</a> •\n  <a href=\"#-walkthrough\">Walkthrough</a> •\n  <a href=\"#-examples\">Examples</a> •\n  <a href=\"#-supported-hosts\">Supported Hosts</a> •\n  <a href=\"#-security\">Security</a> •\n  <a href=\"#-roadmap\">Roadmap</a> •\n  <a href=\"#-contributing\">Contributing</a> •\n  <a href=\"#-license\">License</a>\n</p>\n\n----\n\n**`mcp-ui`** brings interactive web components to the [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP). Deliver rich, dynamic UI resources directly from your MCP server to be rendered by the client. Take AI interaction to the next level!\n\n> *This project is an experimental community playground for MCP UI ideas. Expect rapid iteration and enhancements!*\n\n<p align=\"center\">\n  <video src=\"https://github.com/user-attachments/assets/7180c822-2dd9-4f38-9d3e-b67679509483\"></video>\n</p>\n\n## 💡 What's `mcp-ui`?\n\n`mcp-ui` is a playground for the open spec of UI over MCP. It offers a collection of community SDKs comprising:\n\n* **`@mcp-ui/server` (TypeScript)**: Utilities to generate UI resources (`UIResource`) on your MCP server.\n* **`@mcp-ui/client` (TypeScript)**: UI components (e.g., `<AppRenderer />`) to render the UI resources and handle their events.\n* **`mcp_ui_server` (Ruby)**: Utilities to generate UI resources on your MCP server in a Ruby environment.\n* **`mcp-ui-server` (Python)**: Utilities to generate UI resources on your MCP server in a Python environment.\n\nTogether, they let you define reusable UI snippets on the server side, seamlessly and securely render them in the client, and react to their actions in the MCP host environment.\n\n## ✨ Core Concepts\n\nIn essence, by using `mcp-ui` SDKs, servers and hosts can agree on contracts that enable them to create and render interactive UI snippets (as a path to a standardized UI approach in MCP).\n\n### UI Resource\nThe primary payload returned from the server to the client is the `UIResource`:\n\n```ts\ninterface UIResource {\n  type: 'resource';\n  resource: {\n    uri: string;       // e.g., ui://component/id\n    mimeType: 'text/html;profile=mcp-app';\n    text?: string;      // HTML content\n    blob?: string;      // Base64-encoded HTML content\n  };\n}\n```\n\n* **`uri`**: Unique identifier for caching and routing\n  * `ui://…` — UI resources\n* **`mimeType`**: `text/html;profile=mcp-app` — the MCP Apps standard MIME type\n* **`text` vs. `blob`**: Choose `text` for simple strings; use `blob` for larger or encoded content.\n\n## 🏗️ Installation\n\n### TypeScript\n\n```bash\n# using npm\nnpm install @mcp-ui/server @mcp-ui/client\n\n# or pnpm\npnpm add @mcp-ui/server @mcp-ui/client\n\n# or yarn\nyarn add @mcp-ui/server @mcp-ui/client\n```\n\n### Ruby\n\n```bash\ngem install mcp_ui_server\n```\n\n### Python\n\n```bash\n# using pip\npip install mcp-ui-server\n\n# or uv\nuv add mcp-ui-server\n```\n\n## 🚀 Getting Started\n\nYou can use [GitMCP](https://gitmcp.io/idosal/mcp-ui) to give your IDE access to `mcp-ui`'s latest documentation! \n\n### TypeScript\n\n1. **Server-side**: Build your UI resources\n\n   ```ts\n   import { createUIResource } from '@mcp-ui/server';\n\n   // Inline HTML\n   const htmlResource = await createUIResource({\n     uri: 'ui://greeting/1',\n     content: { type: 'rawHtml', htmlString: '<p>Hello, MCP UI!</p>' },\n     encoding: 'text',\n   });\n\n   // External URL (fetches the page HTML and injects a <base> tag)\n   const externalUrlResource = await createUIResource({\n     uri: 'ui://greeting/1',\n     content: { type: 'externalUrl', iframeUrl: 'https://example.com' },\n     encoding: 'text',\n   });\n   ```\n\n2. **Client-side**: Render in your MCP host\n\n   ```tsx\n   import React from 'react';\n   import { AppRenderer } from '@mcp-ui/client';\n\n   function ToolUI({ client, toolName, toolInput, toolResult }) {\n     return (\n       <AppRenderer\n         client={client}\n         toolName={toolName}\n         sandbox={{ url: sandboxUrl }}\n         toolInput={toolInput}\n         toolResult={toolResult}\n       />\n     );\n   }\n   ```\n\n### Python\n\n**Server-side**: Build your UI resources\n\n   ```python\n   from mcp_ui_server import create_ui_resource\n\n   # Inline HTML\n   html_resource = create_ui_resource({\n     \"uri\": \"ui://greeting/1\",\n     \"content\": { \"type\": \"rawHtml\", \"htmlString\": \"<p>Hello, from Python!</p>\" },\n     \"encoding\": \"text\",\n   })\n\n   # External URL\n   external_url_resource = create_ui_resource({\n     \"uri\": \"ui://greeting/2\",\n     \"content\": { \"type\": \"externalUrl\", \"iframeUrl\": \"https://example.com\" },\n     \"encoding\": \"text\",\n   })\n   ```\n\n### Ruby\n\n**Server-side**: Build your UI resources\n\n   ```ruby\n   require 'mcp_ui_server'\n\n   # Inline HTML\n   html_resource = McpUiServer.create_ui_resource(\n     uri: 'ui://greeting/1',\n     content: { type: :raw_html, htmlString: '<p>Hello, from Ruby!</p>' },\n     encoding: :text\n   )\n\n   # External URL\n   external_url_resource = McpUiServer.create_ui_resource(\n     uri: 'ui://greeting/2',\n     content: { type: :external_url, iframeUrl: 'https://example.com' },\n     encoding: :text\n   )\n\n   ```\n\n## 🚶 Walkthrough\n\nFor a detailed, simple, step-by-step guide on how to integrate `mcp-ui` into your own server, check out the full server walkthroughs on the [mcp-ui documentation site](https://mcpui.dev):\n\n- **[TypeScript Server Walkthrough](https://mcpui.dev/guide/server/typescript/walkthrough)**\n- **[Ruby Server Walkthrough](https://mcpui.dev/guide/server/ruby/walkthrough)**\n- **[Python Server Walkthrough](https://mcpui.dev/guide/server/python/walkthrough)**\n\nThese guides will show you how to add a `mcp-ui` endpoint to an existing server, create tools that return UI resources, and test your setup with the `ui-inspector`!\n\n## 🌍 Examples\n\n**Client Examples**\n* [Goose](https://github.com/block/goose) - open source AI agent that supports `mcp-ui`.\n* [LibreChat](https://github.com/danny-avila/LibreChat) - enhanced ChatGPT clone that supports `mcp-ui`.\n* [ui-inspector](https://github.com/idosal/ui-inspector) - inspect local `mcp-ui`-enabled servers.\n* [MCP-UI Chat](https://github.com/idosal/scira-mcp-ui-chat) - interactive chat built with the `mcp-ui` client. Check out the [hosted version](https://scira-mcp-chat-git-main-idosals-projects.vercel.app/)!\n**Server Examples**\n* **TypeScript**: A [full-featured server](examples/server) that is deployed to a hosted environment for easy testing.\n  * **[`typescript-server-demo`](./examples/typescript-server-demo)**: A simple Typescript server that demonstrates how to generate UI resources.\n  * **server**: A [full-featured Typescript server](examples/server) that is deployed to a hosted Cloudflare environment for easy testing.\n    * **HTTP Streaming**: `https://remote-mcp-server-authless.idosalomon.workers.dev/mcp`\n    * **SSE**: `https://remote-mcp-server-authless.idosalomon.workers.dev/sse`\n* **Ruby**: A barebones [demo server](/examples/ruby-server-demo) that shows how to use `mcp_ui_server` and `mcp` gems together.\n* **Python**: A simple [demo server](/examples/python-server-demo) that shows how to use the `mcp-ui-server` Python package.\n* [XMCP](https://github.com/basementstudio/xmcp/tree/main/examples/mcp-ui) - Typescript MCP framework with `mcp-ui` starter example.\n\nDrop those URLs into any MCP-compatible host to see `mcp-ui` in action. For a supported local inspector, see the [ui-inspector](https://github.com/idosal/ui-inspector).\n\n## 💻 Supported Hosts\n\n`mcp-ui` is supported by a growing number of MCP-compatible clients. Feature support varies by host:\n\n| Host      | Rendering | UI Actions | Notes\n| :-------- | :-------: | :--------: | :--------: |\n| [Nanobot](https://www.nanobot.ai/)    |     ✅    |     ✅     |\n| [ChatGPT](https://chatgpt.com/)    |     ✅    |     ⚠️     | [Guide](https://mcpui.dev/guide/apps-sdk)\n| [Postman](https://www.postman.com/)   |     ✅    |     ⚠️      |\n| [Goose](https://block.github.io/goose/)     |     ✅    |     ⚠️      |\n| [LibreChat](https://www.librechat.ai/)    |     ✅    |     ⚠️     |\n| [Smithery](https://smithery.ai/playground)  |     ✅    |     ❌     |\n| [MCPJam](https://www.mcpjam.com/)    |     ✅    |     ❌     |\n| [fast-agent](https://fast-agent.ai/mcp/mcp-ui/) | ✅ | ❌ |\n| [VSCode](https://github.com/microsoft/vscode/issues/260218) (TBA)    |    ?    |    ?     |\n\n**Legend:**\n- ✅: Supported\n- ⚠️: Partial Support\n- ❌: Not Supported (yet)\n\n## 🔒 Security\nHost and user security is one of `mcp-ui`'s primary concerns. In all content types, the remote code is executed in a sandboxed iframe.\n\n## 🛣️ Roadmap\n\n- [X] Add online playground\n- [X] Expand UI Action API (beyond tool calls)\n- [ ] Add component libraries (in progress)\n- [ ] Add SDKs for additional programming languages (in progress; Ruby, Python available)\n- [ ] Support additional frontend frameworks\n- [ ] Add declarative UI content type\n- [ ] Support generative UI?\n      \n## Core Team\n`mcp-ui` is a project by [Ido Salomon](https://x.com/idosal1), in collaboration with [Liad Yosef](https://x.com/liadyosef).\n\n## 🤝 Contributing\n\nContributions, ideas, and bug reports are welcome! See the [contribution guidelines](https://github.com/idosal/mcp-ui/blob/main/.github/CONTRIBUTING.md) to get started.\n\n## 📄 License\n\nApache License 2.0 © [The MCP-UI Authors](LICENSE)\n\n## Disclaimer\n\nThis project is provided \"as is\", without warranty of any kind. The `mcp-ui` authors and contributors shall not be held liable for any damages, losses, or issues arising from the use of this software. Use at your own risk.\n"
  },
  {
    "path": "sdks/typescript/server/package.json",
    "content": "{\n  \"name\": \"@mcp-ui/server\",\n  \"version\": \"6.1.0\",\n  \"private\": false,\n  \"description\": \"mcp-ui Server SDK\",\n  \"type\": \"module\",\n  \"main\": \"./dist/index.cjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/src/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/src/index.d.ts\",\n      \"import\": \"./dist/index.mjs\",\n      \"require\": \"./dist/index.cjs\"\n    }\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"devDependencies\": {\n    \"@types/node\": \"^18.19.100\",\n    \"@vitest/coverage-v8\": \"^1.0.0\",\n    \"typescript\": \"^5.0.0\",\n    \"vite\": \"^5.0.0\",\n    \"vite-plugin-dts\": \"^3.6.0\",\n    \"vitest\": \"^1.0.0\"\n  },\n  \"scripts\": {\n    \"prepublishOnly\": \"pnpm run build\",\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"test\": \"vitest run\",\n    \"test:watch\": \"vitest watch\",\n    \"lint\": \"eslint .\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"release\": {\n    \"branches\": [\n      \"main\",\n      {\n        \"name\": \"alpha\",\n        \"prerelease\": true\n      }\n    ],\n    \"tagFormat\": \"server/v${version}\",\n    \"plugins\": [\n      \"@semantic-release/commit-analyzer\",\n      \"@semantic-release/release-notes-generator\",\n      [\n        \"@semantic-release/changelog\",\n        {\n          \"changelogFile\": \"CHANGELOG.md\"\n        }\n      ],\n      \"@semantic-release/npm\",\n      \"@semantic-release/github\",\n      [\n        \"@semantic-release/git\",\n        {\n          \"assets\": [\n            \"CHANGELOG.md\",\n            \"package.json\"\n          ],\n          \"message\": \"chore(release): ${nextRelease.version} [skip ci]\\\\n\\\\n${nextRelease.notes}\"\n        }\n      ]\n    ]\n  },\n  \"license\": \"Apache-2.0\",\n  \"dependencies\": {\n    \"@modelcontextprotocol/ext-apps\": \"^0.3.1\",\n    \"@modelcontextprotocol/sdk\": \"^1.25.1\"\n  }\n}\n"
  },
  {
    "path": "sdks/typescript/server/src/__tests__/index.test.ts",
    "content": "import {\n  createUIResource,\n  sendExperimentalRequest,\n} from '../index';\nimport { UI_METADATA_PREFIX } from '../types.js';\nimport { mockFetchWithBody } from './test-utils';\n\ndescribe('@mcp-ui/server', () => {\n  describe('createUIResource', () => {\n    it('should create a text-based direct HTML resource', async () => {\n      const options = {\n        uri: 'ui://test-html' as const,\n        content: { type: 'rawHtml' as const, htmlString: '<p>Test</p>' },\n        encoding: 'text' as const,\n      };\n      const resource = await createUIResource(options);\n      expect(resource.type).toBe('resource');\n      expect(resource.resource.uri).toBe('ui://test-html');\n      expect(resource.resource.mimeType).toBe('text/html;profile=mcp-app');\n      expect(resource.resource.text).toBe('<p>Test</p>');\n      expect(resource.resource.blob).toBeUndefined();\n    });\n\n    it('should create a blob-based direct HTML resource', async () => {\n      const options = {\n        uri: 'ui://test-html-blob' as const,\n        content: { type: 'rawHtml' as const, htmlString: '<h1>Blob</h1>' },\n        encoding: 'blob' as const,\n      };\n      const resource = await createUIResource(options);\n      expect(resource.resource.blob).toBe(Buffer.from('<h1>Blob</h1>').toString('base64'));\n      expect(resource.resource.text).toBeUndefined();\n    });\n\n    describe('externalUrl (fetches and injects <base>)', () => {\n      const MOCK_HTML = '<html><head><title>Test</title></head><body>Hello</body></html>';\n\n      beforeEach(() => {\n        mockFetchWithBody(MOCK_HTML);\n      });\n\n      afterEach(() => {\n        vi.unstubAllGlobals();\n      });\n\n      it('should fetch external URL, inject <base> tag, and set CSP resourceDomains', async () => {\n        const resource = await createUIResource({\n          uri: 'ui://test-url' as const,\n          content: { type: 'externalUrl' as const, iframeUrl: 'https://example.com/page' },\n          encoding: 'text' as const,\n        });\n\n        expect(fetch).toHaveBeenCalledWith('https://example.com/page', expect.objectContaining({\n          signal: expect.any(AbortSignal),\n        }));\n        expect(resource.resource.uri).toBe('ui://test-url');\n        expect(resource.resource.mimeType).toBe('text/html;profile=mcp-app');\n        expect(resource.resource.text).toBe(\n          '<html><head><base href=\"https://example.com/page\"><title>Test</title></head><body>Hello</body></html>',\n        );\n        expect(resource.resource.blob).toBeUndefined();\n        // CSP resourceDomains should contain the external origin\n        expect(resource.resource._meta).toEqual({\n          csp: { baseUriDomains: ['https://example.com'] },\n        });\n      });\n\n      it('should fetch and encode as blob', async () => {\n        const resource = await createUIResource({\n          uri: 'ui://test-url-blob' as const,\n          content: { type: 'externalUrl' as const, iframeUrl: 'https://example.com/blob' },\n          encoding: 'blob' as const,\n        });\n\n        expect(fetch).toHaveBeenCalledWith('https://example.com/blob', expect.objectContaining({\n          signal: expect.any(AbortSignal),\n        }));\n        const expectedHtml =\n          '<html><head><base href=\"https://example.com/blob\"><title>Test</title></head><body>Hello</body></html>';\n        expect(resource.resource.blob).toBe(Buffer.from(expectedHtml).toString('base64'));\n        expect(resource.resource.text).toBeUndefined();\n      });\n\n      it('should include metadata on fetched external URL resource', async () => {\n        const resource = await createUIResource({\n          uri: 'ui://test-url' as const,\n          content: { type: 'externalUrl' as const, iframeUrl: 'https://example.com' },\n          encoding: 'text' as const,\n          uiMetadata: { 'preferred-frame-size': ['100px', '100px'] as [string, string] },\n          resourceProps: { _meta: { 'arbitrary-prop': 'arbitrary' } },\n        });\n\n        expect(resource.resource._meta).toEqual({\n          [`${UI_METADATA_PREFIX}preferred-frame-size`]: ['100px', '100px'],\n          'arbitrary-prop': 'arbitrary',\n          csp: { baseUriDomains: ['https://example.com'] },\n        });\n      });\n\n      it('should include metadata respecting order of overriding metadata', async () => {\n        const resource = await createUIResource({\n          uri: 'ui://test-url' as const,\n          content: { type: 'externalUrl' as const, iframeUrl: 'https://example.com' },\n          encoding: 'text' as const,\n          metadata: { 'arbitrary-prop': 'arbitrary', foo: 'bar' },\n          resourceProps: { _meta: { 'arbitrary-prop': 'arbitrary2' } },\n        });\n\n        expect(resource.resource._meta).toEqual({\n          foo: 'bar',\n          'arbitrary-prop': 'arbitrary2',\n          csp: { baseUriDomains: ['https://example.com'] },\n        });\n      });\n\n      it('should include embedded resource props', async () => {\n        const resource = await createUIResource({\n          uri: 'ui://test-url' as const,\n          content: { type: 'externalUrl' as const, iframeUrl: 'https://example.com' },\n          encoding: 'text' as const,\n          uiMetadata: { 'preferred-frame-size': ['100px', '100px'] as [string, string] },\n          resourceProps: { _meta: { 'arbitrary-metadata': 'resource-level-metadata' } },\n          embeddedResourceProps: {\n            annotations: { audience: ['user'] },\n            _meta: { 'arbitrary-metadata': 'embedded-resource-metadata' },\n          },\n        });\n\n        expect(resource.annotations).toEqual({ audience: ['user'] });\n        expect(resource._meta).toEqual({ 'arbitrary-metadata': 'embedded-resource-metadata' });\n        expect(resource.resource._meta).toEqual({\n          'arbitrary-metadata': 'resource-level-metadata',\n          [`${UI_METADATA_PREFIX}preferred-frame-size`]: ['100px', '100px'],\n          csp: { baseUriDomains: ['https://example.com'] },\n        });\n      });\n\n      it('should merge with existing CSP baseUriDomains without duplicating', async () => {\n        const resource = await createUIResource({\n          uri: 'ui://test-url' as const,\n          content: { type: 'externalUrl' as const, iframeUrl: 'https://example.com/page' },\n          encoding: 'text' as const,\n          resourceProps: {\n            _meta: { csp: { baseUriDomains: ['https://cdn.other.com'] } },\n          },\n        });\n\n        expect(resource.resource._meta).toEqual({\n          csp: { baseUriDomains: ['https://cdn.other.com', 'https://example.com'] },\n        });\n      });\n\n      it('should not duplicate origin if already present in CSP baseUriDomains', async () => {\n        const resource = await createUIResource({\n          uri: 'ui://test-url' as const,\n          content: { type: 'externalUrl' as const, iframeUrl: 'https://example.com/page' },\n          encoding: 'text' as const,\n          resourceProps: {\n            _meta: { csp: { baseUriDomains: ['https://example.com'] } },\n          },\n        });\n\n        expect(resource.resource._meta).toEqual({\n          csp: { baseUriDomains: ['https://example.com'] },\n        });\n      });\n\n      it('should throw when fetch fails', async () => {\n        vi.stubGlobal(\n          'fetch',\n          vi.fn().mockResolvedValue({\n            ok: false,\n            status: 404,\n            statusText: 'Not Found',\n            headers: new Headers(),\n          }),\n        );\n\n        await expect(\n          createUIResource({\n            uri: 'ui://test-url' as const,\n            content: { type: 'externalUrl' as const, iframeUrl: 'https://example.com/missing' },\n            encoding: 'text' as const,\n          }),\n        ).rejects.toThrow('Failed to fetch external URL');\n      });\n    });\n\n    it('should create a blob-based direct HTML resource with correct mimetype', async () => {\n      const options = {\n        uri: 'ui://test-html-blob' as const,\n        content: { type: 'rawHtml' as const, htmlString: '<h1>Blob</h1>' },\n        encoding: 'blob' as const,\n      };\n      const resource = await createUIResource(options);\n      expect(resource.resource.mimeType).toBe('text/html;profile=mcp-app');\n      expect(resource.resource.blob).toBe(Buffer.from('<h1>Blob</h1>').toString('base64'));\n      expect(resource.resource.text).toBeUndefined();\n    });\n\n    it('should throw error for invalid URI prefix with rawHtml', async () => {\n      const options = {\n        uri: 'invalid://test-html' as const,\n        content: { type: 'rawHtml' as const, htmlString: '<p>Test</p>' },\n        encoding: 'text' as const,\n      };\n      // @ts-expect-error We are intentionally passing an invalid URI to test the error.\n      await expect(createUIResource(options)).rejects.toThrow(\n        \"MCP-UI SDK: URI must start with 'ui://'.\",\n      );\n    });\n\n    it('should throw error for invalid URI prefix with externalUrl', async () => {\n      const options = {\n        uri: 'invalid://test-url' as const,\n        content: {\n          type: 'externalUrl' as const,\n          iframeUrl: 'https://example.com',\n        },\n        encoding: 'text' as const,\n      };\n      // @ts-expect-error We are intentionally passing an invalid URI to test the error.\n      await expect(createUIResource(options)).rejects.toThrow(\n        \"MCP-UI SDK: URI must start with 'ui://'.\",\n      );\n    });\n\n    it('should throw an error if htmlString is not a string for rawHtml', async () => {\n      const options = {\n        uri: 'ui://test' as const,\n        content: { type: 'rawHtml' as const, htmlString: null },\n      };\n      // @ts-expect-error intentionally passing invalid type\n      await expect(createUIResource(options)).rejects.toThrow(\n        \"MCP-UI SDK: content.htmlString must be provided as a string when content.type is 'rawHtml'.\",\n      );\n    });\n\n    it('should throw an error if iframeUrl is not a string for externalUrl', async () => {\n      const options = {\n        uri: 'ui://test' as const,\n        content: { type: 'externalUrl' as const, iframeUrl: 123 },\n      };\n      // @ts-expect-error intentionally passing invalid type\n      await expect(createUIResource(options)).rejects.toThrow(\n        \"MCP-UI SDK: content.iframeUrl must be provided as a string when content.type is 'externalUrl'.\",\n      );\n    });\n\n    it('should use MCP Apps mime type', async () => {\n      const options = {\n        uri: 'ui://test-html-no-config' as const,\n        content: { type: 'rawHtml' as const, htmlString: '<p>Test no config</p>' },\n        encoding: 'text' as const,\n      };\n      const resource = await createUIResource(options);\n      expect(resource.resource.mimeType).toBe('text/html;profile=mcp-app');\n      expect(resource.resource.text).toBe('<p>Test no config</p>');\n    });\n  });\n});\n\ndescribe('sendExperimentalRequest', () => {\n  let originalParent: typeof window.parent;\n  const mockParent = {\n    postMessage: vi.fn(),\n  };\n\n  beforeEach(() => {\n    vi.useFakeTimers();\n    originalParent = window.parent;\n    // Simulate being inside an iframe by making parent !== window\n    Object.defineProperty(window, 'parent', {\n      value: mockParent,\n      writable: true,\n      configurable: true,\n    });\n    mockParent.postMessage.mockClear();\n  });\n\n  afterEach(() => {\n    vi.useRealTimers();\n    Object.defineProperty(window, 'parent', {\n      value: originalParent,\n      writable: true,\n      configurable: true,\n    });\n  });\n\n  /** Simulate the host responding to a JSON-RPC request via postMessage */\n  function simulateResponse(data: Record<string, unknown>, source: unknown = mockParent) {\n    const event = new MessageEvent('message', { data, source: source as Window });\n    window.dispatchEvent(event);\n  }\n\n  it('should reject when not inside an iframe', async () => {\n    // Restore parent === window (top-level context)\n    Object.defineProperty(window, 'parent', {\n      value: window,\n      writable: true,\n      configurable: true,\n    });\n\n    await expect(sendExperimentalRequest('x/test')).rejects.toThrow(\n      'sendExperimentalRequest must be called from within an iframe',\n    );\n  });\n\n  it('should post a JSON-RPC request to the parent window', () => {\n    sendExperimentalRequest('x/clipboard/write', { text: 'hello' });\n\n    expect(mockParent.postMessage).toHaveBeenCalledWith(\n      expect.objectContaining({\n        jsonrpc: '2.0',\n        method: 'x/clipboard/write',\n        params: { text: 'hello' },\n      }),\n      '*',\n    );\n  });\n\n  it('should omit params when not provided', () => {\n    sendExperimentalRequest('x/ping');\n\n    const posted = mockParent.postMessage.mock.calls[0][0];\n    expect(posted).not.toHaveProperty('params');\n  });\n\n  it('should resolve with the result on a successful response', async () => {\n    const promise = sendExperimentalRequest('x/test', { key: 'val' });\n    const sentId = mockParent.postMessage.mock.calls[0][0].id;\n\n    simulateResponse({ jsonrpc: '2.0', id: sentId, result: { success: true } });\n\n    await expect(promise).resolves.toEqual({ success: true });\n  });\n\n  it('should reject with the error on an error response', async () => {\n    const promise = sendExperimentalRequest('x/test');\n    const sentId = mockParent.postMessage.mock.calls[0][0].id;\n\n    const error = { code: -32601, message: 'Method not found' };\n    simulateResponse({ jsonrpc: '2.0', id: sentId, error });\n\n    await expect(promise).rejects.toEqual(error);\n  });\n\n  it('should ignore messages from non-parent sources', async () => {\n    const promise = sendExperimentalRequest('x/test', undefined, { timeoutMs: 100 });\n    const sentId = mockParent.postMessage.mock.calls[0][0].id;\n\n    // Message from a different source — should be ignored\n    simulateResponse({ jsonrpc: '2.0', id: sentId, result: { spoofed: true } }, {} as Window);\n\n    // The promise should still be pending; advance timers to trigger timeout\n    vi.advanceTimersByTime(100);\n\n    await expect(promise).rejects.toThrow('timed out');\n  });\n\n  it('should ignore messages with non-matching ids', async () => {\n    const promise = sendExperimentalRequest('x/test', undefined, { timeoutMs: 100 });\n    const sentId = mockParent.postMessage.mock.calls[0][0].id;\n\n    // Response with a different id\n    simulateResponse({ jsonrpc: '2.0', id: sentId + 999, result: { wrong: true } });\n\n    vi.advanceTimersByTime(100);\n\n    await expect(promise).rejects.toThrow('timed out');\n  });\n\n  it('should reject after default timeout', async () => {\n    const promise = sendExperimentalRequest('x/slow');\n\n    vi.advanceTimersByTime(30_000);\n\n    await expect(promise).rejects.toThrow('timed out after 30000ms');\n  });\n\n  it('should reject after custom timeout', async () => {\n    const promise = sendExperimentalRequest('x/slow', undefined, { timeoutMs: 500 });\n\n    vi.advanceTimersByTime(500);\n\n    await expect(promise).rejects.toThrow('timed out after 500ms');\n  });\n\n  it('should not timeout when timeoutMs is 0', async () => {\n    const promise = sendExperimentalRequest('x/test', undefined, { timeoutMs: 0 });\n    const sentId = mockParent.postMessage.mock.calls[0][0].id;\n\n    // Advance far into the future — should not reject\n    vi.advanceTimersByTime(999_999);\n\n    // Now respond — should still resolve\n    simulateResponse({ jsonrpc: '2.0', id: sentId, result: { late: true } });\n\n    await expect(promise).resolves.toEqual({ late: true });\n  });\n\n  it('should reject immediately when signal is already aborted', async () => {\n    const controller = new AbortController();\n    controller.abort();\n\n    await expect(\n      sendExperimentalRequest('x/test', undefined, { signal: controller.signal }),\n    ).rejects.toThrow('was aborted');\n  });\n\n  it('should reject when signal is aborted mid-request', async () => {\n    const controller = new AbortController();\n    const promise = sendExperimentalRequest('x/test', undefined, {\n      signal: controller.signal,\n      timeoutMs: 0,\n    });\n\n    controller.abort();\n\n    await expect(promise).rejects.toThrow('was aborted');\n  });\n\n  it('should clean up the message listener after resolving', async () => {\n    const removeSpy = vi.spyOn(window, 'removeEventListener');\n\n    const promise = sendExperimentalRequest('x/test');\n    const sentId = mockParent.postMessage.mock.calls[0][0].id;\n\n    simulateResponse({ jsonrpc: '2.0', id: sentId, result: {} });\n    await promise;\n\n    expect(removeSpy).toHaveBeenCalledWith('message', expect.any(Function));\n    removeSpy.mockRestore();\n  });\n\n  it('should clean up the message listener after timeout', async () => {\n    const removeSpy = vi.spyOn(window, 'removeEventListener');\n\n    const promise = sendExperimentalRequest('x/test', undefined, { timeoutMs: 100 });\n\n    vi.advanceTimersByTime(100);\n\n    await expect(promise).rejects.toThrow('timed out');\n    expect(removeSpy).toHaveBeenCalledWith('message', expect.any(Function));\n    removeSpy.mockRestore();\n  });\n});\n"
  },
  {
    "path": "sdks/typescript/server/src/__tests__/test-utils.ts",
    "content": "import { vi } from 'vitest';\n\n/**\n * Stubs the global `fetch` to return a successful response with a streaming body\n * containing the given text. Useful for testing code that reads via `response.body.getReader()`.\n */\nexport function mockFetchWithBody(body: string, headers?: Record<string, string>) {\n  const encoder = new TextEncoder();\n  const encoded = encoder.encode(body);\n  let readCalled = false;\n  vi.stubGlobal(\n    'fetch',\n    vi.fn().mockResolvedValue({\n      ok: true,\n      headers: new Headers(headers ?? {}),\n      body: {\n        getReader: () => ({\n          read: () => {\n            if (!readCalled) {\n              readCalled = true;\n              return Promise.resolve({ done: false, value: encoded });\n            }\n            return Promise.resolve({ done: true, value: undefined });\n          },\n          cancel: vi.fn(),\n        }),\n      },\n    }),\n  );\n}\n"
  },
  {
    "path": "sdks/typescript/server/src/__tests__/utils.test.ts",
    "content": "import { describe, it, expect, vi } from 'vitest';\nimport {\n  extractOrigin,\n  fetchExternalUrl,\n  getAdditionalResourceProps,\n  injectBaseTag,\n  utf8ToBase64,\n  validateExternalUrl,\n} from '../utils.js';\nimport { UI_METADATA_PREFIX, RESOURCE_MIME_TYPE } from '../types.js';\nimport { mockFetchWithBody } from './test-utils';\nimport { RESOURCE_MIME_TYPE as EXT_APPS_RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps';\n\ndescribe('getAdditionalResourceProps', () => {\n  it('should return the additional resource props', () => {\n    const uiMetadata = {\n      'preferred-frame-size': ['100px', '100px'] as [string, string],\n      'initial-render-data': { test: 'test' },\n    };\n    const additionalResourceProps = getAdditionalResourceProps({ uiMetadata });\n    expect(additionalResourceProps).toEqual({\n      _meta: {\n        [`${UI_METADATA_PREFIX}preferred-frame-size`]: ['100px', '100px'],\n        [`${UI_METADATA_PREFIX}initial-render-data`]: { test: 'test' },\n      },\n    });\n  });\n\n  it('should return the additional resource props with user defined _meta', () => {\n    const uiMetadata = {\n      'preferred-frame-size': ['100px', '100px'] as [string, string],\n      'initial-render-data': { test: 'test' },\n    };\n    const additionalResourceProps = getAdditionalResourceProps({\n      uiMetadata,\n      resourceProps: {\n        annotations: { audience: 'user' },\n        _meta: { foo: 'bar', [`${UI_METADATA_PREFIX}preferred-frame-size`]: ['200px', '200px'] },\n      },\n    });\n    expect(additionalResourceProps).toEqual({\n      _meta: {\n        [`${UI_METADATA_PREFIX}initial-render-data`]: { test: 'test' },\n        foo: 'bar',\n        [`${UI_METADATA_PREFIX}preferred-frame-size`]: ['200px', '200px'],\n      },\n      annotations: { audience: 'user' },\n    });\n  });\n\n  it('should return an empty object if no uiMetadata or metadata is provided', () => {\n    const additionalResourceProps = getAdditionalResourceProps({});\n    expect(additionalResourceProps).toEqual({});\n  });\n\n  it('should respect order of overriding metadata', () => {\n    const additionalResourceProps = getAdditionalResourceProps({\n      uiMetadata: { 'preferred-frame-size': ['100px', '100px'] as [string, string] },\n      metadata: { [`${UI_METADATA_PREFIX}preferred-frame-size`]: ['200px', '200px'], foo: 'bar' },\n      resourceProps: { annotations: { audience: 'user' }, _meta: { foo: 'baz' } },\n    });\n    expect(additionalResourceProps).toEqual({\n      _meta: {\n        [`${UI_METADATA_PREFIX}preferred-frame-size`]: ['200px', '200px'],\n        foo: 'baz',\n      },\n      annotations: { audience: 'user' },\n    });\n  });\n});\n\ndescribe('utf8ToBase64', () => {\n  it('should correctly encode a simple ASCII string', () => {\n    const str = 'hello world';\n    const expected = 'aGVsbG8gd29ybGQ=';\n    expect(utf8ToBase64(str)).toBe(expected);\n  });\n\n  it('should correctly encode a string with UTF-8 characters', () => {\n    const str = '你好,世界';\n    const expected = '5L2g5aW9LOS4lueVjA==';\n    expect(utf8ToBase64(str)).toBe(expected);\n  });\n\n  it('should correctly encode an empty string', () => {\n    const str = '';\n    const expected = '';\n    expect(utf8ToBase64(str)).toBe(expected);\n  });\n\n  it('should correctly encode a string with various special characters', () => {\n    const str = '`~!@#$%^&*()_+-=[]{}\\\\|;\\':\",./<>?';\n    const expected = 'YH4hQCMkJV4mKigpXystPVtde31cfDsnOiIsLi88Pj8=';\n    expect(utf8ToBase64(str)).toBe(expected);\n  });\n\n  it('should use TextEncoder and btoa when Buffer is not available', () => {\n    const str = 'hello world';\n    const expected = 'aGVsbG8gd29ybGQ=';\n\n    const bufferBackup = global.Buffer;\n    // @ts-expect-error - simulating Buffer not being available\n    delete global.Buffer;\n\n    expect(utf8ToBase64(str)).toBe(expected);\n\n    global.Buffer = bufferBackup;\n  });\n\n  it('should use fallback btoa when Buffer and TextEncoder are not available', () => {\n    const str = 'hello world';\n    const expected = 'aGVsbG8gd29ybGQ=';\n\n    const bufferBackup = global.Buffer;\n    const textEncoderBackup = global.TextEncoder;\n\n    // @ts-expect-error - simulating Buffer not being available\n    delete global.Buffer;\n    // @ts-expect-error - simulating TextEncoder not being available\n    delete global.TextEncoder;\n\n    const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});\n    expect(utf8ToBase64(str)).toBe(expected);\n    expect(consoleWarnSpy).toHaveBeenCalledWith(\n      'MCP-UI SDK: Buffer API and TextEncoder/btoa not available. Base64 encoding might not be UTF-8 safe.',\n    );\n\n    consoleWarnSpy.mockRestore();\n    global.Buffer = bufferBackup;\n    global.TextEncoder = textEncoderBackup;\n  });\n\n  it('should throw an error if all encoding methods fail', () => {\n    const str = 'hello world';\n\n    const bufferBackup = global.Buffer;\n    const textEncoderBackup = global.TextEncoder;\n    const btoaBackup = global.btoa;\n\n    // @ts-expect-error - simulating Buffer not being available\n    delete global.Buffer;\n    // @ts-expect-error - simulating TextEncoder not being available\n    delete global.TextEncoder;\n    // @ts-expect-error - simulating btoa not being available\n    delete global.btoa;\n\n    expect(() => utf8ToBase64(str)).toThrow(\n      'MCP-UI SDK: Suitable UTF-8 to Base64 encoding method not found, and fallback btoa failed.',\n    );\n\n    global.Buffer = bufferBackup;\n    global.TextEncoder = textEncoderBackup;\n    global.btoa = btoaBackup;\n  });\n});\n\ndescribe('injectBaseTag', () => {\n  it('should inject after <head>', () => {\n    const html = '<html><head><title>Test</title></head><body></body></html>';\n    expect(injectBaseTag(html, 'https://example.com/page')).toBe(\n      '<html><head><base href=\"https://example.com/page\"><title>Test</title></head><body></body></html>',\n    );\n  });\n\n  it('should inject after <head> with attributes', () => {\n    const html = '<html><head lang=\"en\"><title>Test</title></head></html>';\n    expect(injectBaseTag(html, 'https://example.com')).toBe(\n      '<html><head lang=\"en\"><base href=\"https://example.com\"><title>Test</title></head></html>',\n    );\n  });\n\n  it('should prepend if no <head> tag', () => {\n    const html = '<p>Hello</p>';\n    expect(injectBaseTag(html, 'https://example.com')).toBe(\n      '<base href=\"https://example.com\"><p>Hello</p>',\n    );\n  });\n\n  it('should not add if <base> already exists', () => {\n    const html = '<html><head><base href=\"https://other.com\"><title>T</title></head></html>';\n    expect(injectBaseTag(html, 'https://example.com')).toBe(html);\n  });\n\n  it('should escape ampersands and quotes in URL', () => {\n    const html = '<head></head>';\n    expect(injectBaseTag(html, 'https://example.com/a?b=1&c=\"2\"')).toBe(\n      '<head><base href=\"https://example.com/a?b=1&amp;c=&quot;2&quot;\"></head>',\n    );\n  });\n});\n\ndescribe('extractOrigin', () => {\n  it('should extract origin from a valid URL', () => {\n    expect(extractOrigin('https://example.com/page?q=1')).toBe('https://example.com');\n  });\n\n  it('should include port if non-default', () => {\n    expect(extractOrigin('https://example.com:8080/path')).toBe('https://example.com:8080');\n  });\n\n  it('should return undefined for invalid URLs', () => {\n    expect(extractOrigin('not a url')).toBeUndefined();\n  });\n});\n\ndescribe('validateExternalUrl', () => {\n  it('should accept valid http URLs', () => {\n    expect(() => validateExternalUrl('http://example.com')).not.toThrow();\n  });\n\n  it('should accept valid https URLs', () => {\n    expect(() => validateExternalUrl('https://example.com/page?q=1')).not.toThrow();\n  });\n\n  it('should reject non-http protocols', () => {\n    expect(() => validateExternalUrl('ftp://example.com')).toThrow('must use http or https');\n    expect(() => validateExternalUrl('file:///etc/passwd')).toThrow('must use http or https');\n  });\n\n  it('should reject invalid URLs', () => {\n    expect(() => validateExternalUrl('not a url')).toThrow('Invalid external URL');\n  });\n\n  it('should reject localhost', () => {\n    expect(() => validateExternalUrl('http://localhost:3000')).toThrow('localhost or loopback');\n    expect(() => validateExternalUrl('http://127.0.0.1:8080')).toThrow('localhost or loopback');\n    expect(() => validateExternalUrl('http://[::1]/page')).toThrow('localhost or loopback');\n    expect(() => validateExternalUrl('http://0.0.0.0')).toThrow('localhost or loopback');\n  });\n\n  it('should reject private IPv4 ranges', () => {\n    expect(() => validateExternalUrl('http://10.0.0.1')).toThrow('private network');\n    expect(() => validateExternalUrl('http://172.16.0.1')).toThrow('private network');\n    expect(() => validateExternalUrl('http://172.31.255.255')).toThrow('private network');\n    expect(() => validateExternalUrl('http://192.168.1.1')).toThrow('private network');\n    expect(() => validateExternalUrl('http://169.254.169.254')).toThrow('private network');\n  });\n\n  it('should allow public IPs and non-private ranges', () => {\n    expect(() => validateExternalUrl('http://8.8.8.8')).not.toThrow();\n    expect(() => validateExternalUrl('http://172.32.0.1')).not.toThrow();\n    expect(() => validateExternalUrl('https://203.0.113.1')).not.toThrow();\n  });\n});\n\ndescribe('fetchExternalUrl', () => {\n  afterEach(() => {\n    vi.unstubAllGlobals();\n  });\n\n  it('should fetch and inject <base> tag', async () => {\n    mockFetchWithBody('<html><head></head><body>Hi</body></html>');\n\n    const result = await fetchExternalUrl('https://example.com/page');\n    expect(fetch).toHaveBeenCalledWith('https://example.com/page', expect.objectContaining({\n      signal: expect.any(AbortSignal),\n      redirect: 'follow',\n    }));\n    expect(result).toBe(\n      '<html><head><base href=\"https://example.com/page\"></head><body>Hi</body></html>',\n    );\n  });\n\n  it('should throw on non-OK response', async () => {\n    vi.stubGlobal(\n      'fetch',\n      vi.fn().mockResolvedValue({\n        ok: false,\n        status: 500,\n        statusText: 'Internal Server Error',\n        headers: new Headers(),\n      }),\n    );\n\n    await expect(fetchExternalUrl('https://example.com/error')).rejects.toThrow(\n      'Failed to fetch external URL \"https://example.com/error\": 500 Internal Server Error',\n    );\n  });\n\n  it('should reject URLs with non-http protocols', async () => {\n    await expect(fetchExternalUrl('ftp://example.com')).rejects.toThrow('must use http or https');\n  });\n\n  it('should reject localhost URLs', async () => {\n    await expect(fetchExternalUrl('http://localhost:3000')).rejects.toThrow('localhost or loopback');\n  });\n\n  it('should reject private IP URLs', async () => {\n    await expect(fetchExternalUrl('http://10.0.0.1/admin')).rejects.toThrow('private network');\n  });\n\n  it('should reject responses exceeding content-length limit', async () => {\n    vi.stubGlobal(\n      'fetch',\n      vi.fn().mockResolvedValue({\n        ok: true,\n        headers: new Headers({ 'content-length': '999999999' }),\n        body: { getReader: () => ({ read: () => Promise.resolve({ done: true, value: undefined }), cancel: vi.fn() }) },\n      }),\n    );\n\n    await expect(fetchExternalUrl('https://example.com/huge')).rejects.toThrow(\n      'response too large',\n    );\n  });\n\n  it('should reject responses exceeding streaming size limit', async () => {\n    // Create a mock that returns chunks totalling over 10MB\n    const bigChunk = new Uint8Array(6 * 1024 * 1024); // 6MB\n    let callCount = 0;\n    vi.stubGlobal(\n      'fetch',\n      vi.fn().mockResolvedValue({\n        ok: true,\n        headers: new Headers(),\n        body: {\n          getReader: () => ({\n            read: () => {\n              callCount++;\n              if (callCount <= 2) {\n                return Promise.resolve({ done: false, value: bigChunk });\n              }\n              return Promise.resolve({ done: true, value: undefined });\n            },\n            cancel: vi.fn(),\n          }),\n        },\n      }),\n    );\n\n    await expect(fetchExternalUrl('https://example.com/stream-huge')).rejects.toThrow(\n      'response too large',\n    );\n  });\n});\n\ndescribe('RESOURCE_MIME_TYPE constant from @modelcontextprotocol/ext-apps', () => {\n  it('should match the expected MCP Apps MIME type', () => {\n    // This test ensures that if @modelcontextprotocol/ext-apps changes RESOURCE_MIME_TYPE,\n    // we'll be alerted to the breaking change\n    expect(EXT_APPS_RESOURCE_MIME_TYPE).toBe('text/html;profile=mcp-app');\n  });\n\n  it('should be re-exported correctly from types.ts', () => {\n    // Verify that our re-export matches the original\n    expect(RESOURCE_MIME_TYPE).toBe(EXT_APPS_RESOURCE_MIME_TYPE);\n  });\n});\n"
  },
  {
    "path": "sdks/typescript/server/src/index.ts",
    "content": "import {\n  type Base64BlobContent,\n  type CreateUIResourceOptions,\n  type HTMLTextContent,\n  type MimeType,\n  RESOURCE_MIME_TYPE,\n} from './types.js';\nimport {\n  extractOrigin,\n  fetchExternalUrl,\n  getAdditionalResourceProps,\n  utf8ToBase64,\n} from './utils.js';\n\nexport type UIResource = {\n  type: 'resource';\n  resource: HTMLTextContent | Base64BlobContent;\n  annotations?: Record<string, unknown>;\n  _meta?: Record<string, unknown>;\n};\n\n/**\n * Creates a UIResource.\n * This is the object that should be included in the 'content' array of a toolResult.\n *\n * For `externalUrl` content, fetches the URL's HTML and injects a `<base>` tag\n * so that relative paths resolve against the original URL.\n *\n * @param options Configuration for the interactive resource.\n * @returns a UIResource (async for externalUrl content which requires fetching)\n */\nexport async function createUIResource(options: CreateUIResourceOptions): Promise<UIResource> {\n  let actualContentString: string;\n  const mimeType: MimeType = RESOURCE_MIME_TYPE;\n\n  if (!options.uri.startsWith('ui://')) {\n    throw new Error(\"MCP-UI SDK: URI must start with 'ui://'.\");\n  }\n\n  let externalOrigin: string | undefined;\n\n  if (options.content.type === 'rawHtml') {\n    actualContentString = options.content.htmlString;\n    if (typeof actualContentString !== 'string') {\n      throw new Error(\n        \"MCP-UI SDK: content.htmlString must be provided as a string when content.type is 'rawHtml'.\",\n      );\n    }\n  } else if (options.content.type === 'externalUrl') {\n    const iframeUrl = options.content.iframeUrl;\n    if (typeof iframeUrl !== 'string') {\n      throw new Error(\n        \"MCP-UI SDK: content.iframeUrl must be provided as a string when content.type is 'externalUrl'.\",\n      );\n    }\n    actualContentString = await fetchExternalUrl(iframeUrl);\n    externalOrigin = extractOrigin(iframeUrl);\n  } else {\n    // This case should ideally be prevented by TypeScript's discriminated union checks\n    const exhaustiveCheckContent: never = options.content;\n    throw new Error(`MCP-UI SDK: Invalid content.type specified: ${exhaustiveCheckContent}`);\n  }\n\n  const additionalProps = getAdditionalResourceProps(options);\n\n  // For externalUrl, auto-populate _meta.csp.baseUriDomains with the origin\n  // so the sandbox iframe's CSP base-uri directive allows the injected <base> tag.\n  if (externalOrigin) {\n    const meta = (additionalProps._meta ?? {}) as Record<string, unknown>;\n    const existingCsp = (meta.csp ?? {}) as Record<string, unknown>;\n    const existingDomains = (existingCsp.baseUriDomains ?? []) as string[];\n    if (!existingDomains.includes(externalOrigin)) {\n      meta.csp = { ...existingCsp, baseUriDomains: [...existingDomains, externalOrigin] };\n    }\n    additionalProps._meta = meta;\n  }\n\n  let resource: UIResource['resource'];\n\n  switch (options.encoding) {\n    case 'text':\n      resource = {\n        uri: options.uri,\n        mimeType,\n        text: actualContentString,\n        ...additionalProps,\n      };\n      break;\n    case 'blob':\n      resource = {\n        uri: options.uri,\n        mimeType,\n        blob: utf8ToBase64(actualContentString),\n        ...additionalProps,\n      };\n      break;\n    default: {\n      const exhaustiveCheck: never = options.encoding;\n      throw new Error(`MCP-UI SDK: Invalid encoding type: ${exhaustiveCheck}`);\n    }\n  }\n\n  return {\n    type: 'resource',\n    resource: resource,\n    ...(options.embeddedResourceProps ?? {}),\n  };\n}\n\nexport type {\n  CreateUIResourceOptions,\n  ResourceContentPayload,\n} from './types.js';\n\n// Re-export constants from @modelcontextprotocol/ext-apps via types.js\n// This allows users to import everything they need from @mcp-ui/server\nexport { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE } from './types.js';\n\n// --- Experimental JSON-RPC helpers ---\n// These enable guest UIs to send custom JSON-RPC requests to the host's\n// onFallbackRequest handler on AppRenderer, using the existing PostMessageTransport.\n\nlet _experimentalRequestId = 0;\n\nconst DEFAULT_EXPERIMENTAL_REQUEST_TIMEOUT_MS = 30_000;\n\n/**\n * Send an experimental JSON-RPC request to the host from inside a guest UI iframe.\n *\n * The host must have an `onFallbackRequest` handler registered on AppRenderer.\n * The request flows through PostMessageTransport and AppBridge's fallbackRequestHandler.\n *\n * @param method - JSON-RPC method name. Convention: use \"x/<namespace>/<action>\" for\n *   experimental methods (e.g., \"x/clipboard/write\"). Standard MCP methods not yet\n *   in the Apps spec (e.g., \"sampling/createMessage\") can use their canonical names.\n * @param params - Request parameters\n * @param options - Optional configuration\n * @param options.signal - AbortSignal to cancel the request\n * @param options.timeoutMs - Timeout in milliseconds (default: 30000). Set to 0 to disable.\n * @returns Promise that resolves with the host's JSON-RPC response result, or rejects\n *   with the JSON-RPC error\n *\n * @example\n * ```ts\n * const result = await sendExperimentalRequest('x/clipboard/write', { text: 'hello' });\n * ```\n */\nexport function sendExperimentalRequest(\n  method: string,\n  params?: Record<string, unknown>,\n  options?: { signal?: AbortSignal; timeoutMs?: number },\n): Promise<unknown> {\n  if (window.parent === window) {\n    return Promise.reject(\n      new Error('sendExperimentalRequest must be called from within an iframe'),\n    );\n  }\n\n  const id = ++_experimentalRequestId;\n  const timeoutMs = options?.timeoutMs ?? DEFAULT_EXPERIMENTAL_REQUEST_TIMEOUT_MS;\n\n  return new Promise((resolve, reject) => {\n    let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n    const cleanup = () => {\n      window.removeEventListener('message', handler);\n      if (timeoutId !== undefined) {\n        clearTimeout(timeoutId);\n      }\n      options?.signal?.removeEventListener('abort', onAbort);\n    };\n\n    const handler = (event: MessageEvent) => {\n      // Only accept responses from the parent window\n      if (event.source !== window.parent) return;\n\n      const data = event.data;\n      if (data?.jsonrpc === '2.0' && data?.id === id) {\n        cleanup();\n        if (data.error) {\n          reject(data.error);\n        } else {\n          resolve(data.result);\n        }\n      }\n    };\n\n    const onAbort = () => {\n      cleanup();\n      reject(new Error(`Experimental request \"${method}\" was aborted`));\n    };\n\n    if (options?.signal?.aborted) {\n      reject(new Error(`Experimental request \"${method}\" was aborted`));\n      return;\n    }\n\n    options?.signal?.addEventListener('abort', onAbort);\n    window.addEventListener('message', handler);\n\n    if (timeoutMs > 0) {\n      timeoutId = setTimeout(() => {\n        cleanup();\n        reject(new Error(`Experimental request \"${method}\" timed out after ${timeoutMs}ms`));\n      }, timeoutMs);\n    }\n\n    window.parent.postMessage(\n      {\n        jsonrpc: '2.0',\n        id,\n        method,\n        ...(params !== undefined && { params }),\n      },\n      '*',\n    );\n  });\n}\n"
  },
  {
    "path": "sdks/typescript/server/src/types.ts",
    "content": "import type { EmbeddedResource, Resource } from '@modelcontextprotocol/sdk/types.js';\n\n// Re-export constants from the official ext-apps SDK for convenience\n// This ensures we stay in sync with the MCP Apps specification\nexport { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps';\n\n// Primary identifier for the resource. Starts with ui://`\nexport type URI = `ui://${string}`;\n\n// text/html;profile=mcp-app is the MCP Apps standard MIME type\nexport type MimeType = 'text/html;profile=mcp-app';\n\nexport type HTMLTextContent = {\n  uri: URI;\n  mimeType: MimeType;\n  text: string; // HTML content or iframe URL\n  blob?: never;\n  _meta?: Record<string, unknown>;\n};\n\nexport type Base64BlobContent = {\n  uri: URI;\n  mimeType: MimeType;\n  blob: string; // Base64 encoded HTML content or iframe URL\n  text?: never;\n  _meta?: Record<string, unknown>;\n};\n\nexport type ResourceContentPayload =\n  | { type: 'rawHtml'; htmlString: string }\n  | { type: 'externalUrl'; iframeUrl: string };\n\nexport interface CreateUIResourceOptions {\n  uri: URI;\n  content: ResourceContentPayload;\n  encoding: 'text' | 'blob';\n  // specific mcp-ui metadata\n  uiMetadata?: UIResourceMetadata;\n  // additional metadata to be passed on _meta\n  metadata?: Record<string, unknown>;\n  // additional resource props to be passed on the resource itself\n  resourceProps?: UIResourceProps;\n  // additional resource props to be passed on the top-level embedded resource (i.e. annotations)\n  embeddedResourceProps?: EmbeddedUIResourceProps;\n}\n\nexport type UIResourceProps = Omit<Partial<Resource>, 'uri' | 'mimeType'>;\nexport type EmbeddedUIResourceProps = Omit<Partial<EmbeddedResource>, 'resource' | 'type'>;\n\nexport const UIMetadataKey = {\n  PREFERRED_FRAME_SIZE: 'preferred-frame-size',\n  INITIAL_RENDER_DATA: 'initial-render-data',\n} as const;\n\nexport const UI_METADATA_PREFIX = 'mcpui.dev/ui-';\n\nexport type UIResourceMetadata = {\n  [UIMetadataKey.PREFERRED_FRAME_SIZE]?: [string, string];\n  [UIMetadataKey.INITIAL_RENDER_DATA]?: Record<string, unknown>;\n};\n"
  },
  {
    "path": "sdks/typescript/server/src/utils.ts",
    "content": "import type { CreateUIResourceOptions, UIResourceProps } from './types.js';\nimport { UI_METADATA_PREFIX } from './types.js';\n\n/** Maximum response body size in bytes (10 MB). */\nconst MAX_RESPONSE_BYTES = 10 * 1024 * 1024;\n\n/** Default fetch timeout in milliseconds (30 seconds). */\nconst FETCH_TIMEOUT_MS = 30_000;\n\n/** Hostnames that are always blocked to prevent SSRF. */\nconst BLOCKED_HOSTNAMES = new Set([\n  'localhost',\n  '127.0.0.1',\n  '0.0.0.0',\n  '[::1]',\n  '[::]',\n]);\n\n/**\n * Returns true if the hostname belongs to a private/reserved IPv4 range.\n * Checks 10.x.x.x, 172.16-31.x.x, 192.168.x.x, and 169.254.x.x (link-local).\n */\nfunction isPrivateIPv4(hostname: string): boolean {\n  const parts = hostname.split('.');\n  if (parts.length !== 4) return false;\n  const nums = parts.map(Number);\n  if (nums.some((n) => isNaN(n) || n < 0 || n > 255)) return false;\n\n  const [a, b] = nums;\n  // 10.0.0.0/8\n  if (a === 10) return true;\n  // 172.16.0.0/12\n  if (a === 172 && b >= 16 && b <= 31) return true;\n  // 192.168.0.0/16\n  if (a === 192 && b === 168) return true;\n  // 169.254.0.0/16 (link-local)\n  if (a === 169 && b === 254) return true;\n\n  return false;\n}\n\n/**\n * Validates that a URL is safe for server-side fetching.\n * Restricts to http/https and blocks private/reserved network addresses.\n *\n * @throws Error if the URL is invalid or targets a restricted address.\n */\nexport function validateExternalUrl(url: string): URL {\n  let parsed: URL;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`MCP-UI SDK: Invalid external URL: \"${url}\"`);\n  }\n\n  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n    throw new Error(\n      `MCP-UI SDK: External URL must use http or https protocol, got \"${parsed.protocol}\" in \"${url}\"`,\n    );\n  }\n\n  const hostname = parsed.hostname.toLowerCase();\n\n  if (BLOCKED_HOSTNAMES.has(hostname)) {\n    throw new Error(\n      `MCP-UI SDK: External URL must not target localhost or loopback addresses: \"${url}\"`,\n    );\n  }\n\n  if (isPrivateIPv4(hostname)) {\n    throw new Error(\n      `MCP-UI SDK: External URL must not target private network addresses: \"${url}\"`,\n    );\n  }\n\n  return parsed;\n}\n\n/**\n * Fetches the HTML content from an external URL and injects a `<base>` tag\n * so that relative paths (CSS, JS, images, etc.) resolve against the original URL.\n *\n * Includes SSRF protections (protocol/host validation), a timeout, and a\n * response size limit.\n *\n * @param url The external URL to fetch.\n * @returns The fetched HTML with a `<base>` tag injected.\n */\nexport async function fetchExternalUrl(url: string): Promise<string> {\n  const parsed = validateExternalUrl(url);\n\n  const controller = new AbortController();\n  const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n\n  try {\n    const response = await fetch(parsed.href, {\n      signal: controller.signal,\n      redirect: 'follow',\n    });\n\n    if (!response.ok) {\n      throw new Error(\n        `MCP-UI SDK: Failed to fetch external URL \"${url}\": ${response.status} ${response.statusText}`,\n      );\n    }\n\n    const contentLength = response.headers.get('content-length');\n    if (contentLength && parseInt(contentLength, 10) > MAX_RESPONSE_BYTES) {\n      throw new Error(\n        `MCP-UI SDK: External URL response too large (${contentLength} bytes, max ${MAX_RESPONSE_BYTES}): \"${url}\"`,\n      );\n    }\n\n    // Read body in chunks to enforce size limit even without content-length\n    const reader = response.body?.getReader();\n    if (!reader) {\n      throw new Error(`MCP-UI SDK: Unable to read response body from \"${url}\"`);\n    }\n\n    const decoder = new TextDecoder();\n    let html = '';\n    let totalBytes = 0;\n\n    for (;;) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      totalBytes += value.byteLength;\n      if (totalBytes > MAX_RESPONSE_BYTES) {\n        reader.cancel();\n        throw new Error(\n          `MCP-UI SDK: External URL response too large (exceeded ${MAX_RESPONSE_BYTES} bytes): \"${url}\"`,\n        );\n      }\n      html += decoder.decode(value, { stream: true });\n    }\n    html += decoder.decode(); // flush remaining\n\n    return injectBaseTag(html, url);\n  } finally {\n    clearTimeout(timeoutId);\n  }\n}\n\n/**\n * Injects a `<base href=\"...\">` tag into HTML so relative paths resolve against\n * the given URL. If the HTML already contains a `<base` tag, it is left as-is.\n */\nexport function injectBaseTag(html: string, url: string): string {\n  // Don't add <base> if one already exists\n  if (/<base\\s/i.test(html)) {\n    return html;\n  }\n\n  const baseTag = `<base href=\"${escapeHtmlAttr(url)}\">`;\n\n  // Inject after <head> or <head ...> if present\n  const headMatch = html.match(/<head(\\s[^>]*)?>/i);\n  if (headMatch) {\n    const insertPos = headMatch.index! + headMatch[0].length;\n    return html.slice(0, insertPos) + baseTag + html.slice(insertPos);\n  }\n\n  // No <head> tag — prepend\n  return baseTag + html;\n}\n\nfunction escapeHtmlAttr(str: string): string {\n  return str.replace(/&/g, '&amp;').replace(/\"/g, '&quot;');\n}\n\n/**\n * Extracts the origin (scheme + host) from a URL string.\n * Returns undefined if the URL is invalid.\n */\nexport function extractOrigin(url: string): string | undefined {\n  try {\n    return new URL(url).origin;\n  } catch {\n    return undefined;\n  }\n}\n\nexport function getAdditionalResourceProps(\n  resourceOptions: Partial<CreateUIResourceOptions>,\n): UIResourceProps {\n  const additionalResourceProps = { ...(resourceOptions.resourceProps ?? {}) } as UIResourceProps;\n\n  // prefix ui specific metadata with the prefix to be recognized by the client\n  if (resourceOptions.uiMetadata || resourceOptions.metadata) {\n    const uiPrefixedMetadata = Object.fromEntries(\n      Object.entries(resourceOptions.uiMetadata ?? {}).map(([key, value]) => [\n        `${UI_METADATA_PREFIX}${key}`,\n        value,\n      ]),\n    );\n    // allow user defined _meta to override ui metadata\n    additionalResourceProps._meta = {\n      ...uiPrefixedMetadata,\n      ...(resourceOptions.metadata ?? {}),\n      ...(additionalResourceProps._meta ?? {}),\n    };\n  }\n\n  return additionalResourceProps;\n}\n\n/**\n * Robustly encodes a UTF-8 string to Base64.\n * Uses Node.js Buffer if available, otherwise TextEncoder and btoa.\n * @param str The string to encode.\n * @returns Base64 encoded string.\n */\nexport function utf8ToBase64(str: string): string {\n  if (typeof Buffer !== 'undefined') {\n    return Buffer.from(str, 'utf-8').toString('base64');\n  } else if (typeof TextEncoder !== 'undefined' && typeof btoa !== 'undefined') {\n    const encoder = new TextEncoder();\n    const uint8Array = encoder.encode(str);\n    // Efficiently convert Uint8Array to binary string, handling large arrays in chunks\n    let binaryString = '';\n    // 8192 is a common chunk size used in JavaScript for performance reasons.\n    // It tends to align well with internal buffer sizes and memory page sizes,\n    // and it's small enough to avoid stack overflow errors with String.fromCharCode.\n    const CHUNK_SIZE = 8192;\n    for (let i = 0; i < uint8Array.length; i += CHUNK_SIZE) {\n      binaryString += String.fromCharCode(...uint8Array.slice(i, i + CHUNK_SIZE));\n    }\n    return btoa(binaryString);\n  } else {\n    console.warn(\n      'MCP-UI SDK: Buffer API and TextEncoder/btoa not available. Base64 encoding might not be UTF-8 safe.',\n    );\n    try {\n      return btoa(str);\n    } catch (_e) {\n      throw new Error(\n        'MCP-UI SDK: Suitable UTF-8 to Base64 encoding method not found, and fallback btoa failed.',\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "sdks/typescript/server/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"forceConsistentCasingInFileNames\": true\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\", \"dist\", \"src/__tests__\"]\n}\n"
  },
  {
    "path": "sdks/typescript/server/vite.config.ts",
    "content": "import { defineConfig } from 'vite';\nimport dts from 'vite-plugin-dts';\nimport path from 'path';\nimport react from '@vitejs/plugin-react-swc';\n\nexport default defineConfig({\n  plugins: [\n    react(),\n    dts({\n      insertTypesEntry: false,\n      tsconfigPath: path.resolve(__dirname, 'tsconfig.json'),\n      exclude: ['**/__tests__/**', '**/*.test.ts', '**/*.spec.ts'],\n    }),\n  ],\n  build: {\n    lib: {\n      entry: path.resolve(__dirname, 'src/index.ts'),\n      name: 'McpUiServer',\n      formats: ['es', 'cjs'], // cjs for Node compatibility\n      fileName: (format) => `index.${format === 'es' ? 'mjs' : 'cjs'}`,\n    },\n    target: 'node18',\n    sourcemap: true,\n  },\n});\n"
  },
  {
    "path": "sdks/typescript/server/vitest.config.ts",
    "content": "import { defineConfig } from 'vitest/config';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nexport default defineConfig({\n  test: {\n    globals: true,\n    environment: 'node',\n    globalSetup: join(__dirname, '../../../vitest.global-setup.ts'),\n  },\n});\n"
  },
  {
    "path": "sdks/typescript/shared/package.json",
    "content": "{\n  \"name\": \"@mcp-ui/shared\",\n  \"version\": \"0.1.0\",\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\",\n    \"test\": \"vitest run\",\n    \"test:watch\": \"vitest watch\",\n    \"lint\": \"eslint .\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"^5.0.0\",\n    \"vite\": \"^5.0.0\",\n    \"vite-plugin-dts\": \"^3.6.0\",\n    \"vitest\": \"^1.0.0\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  }\n}\n"
  },
  {
    "path": "sdks/typescript/shared/src/index.ts",
    "content": ""
  },
  {
    "path": "sdks/typescript/shared/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \"src\",\n    \"paths\": {\n      \"@/*\": [\"./src/\"]\n    }\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\", \"dist\", \"src/__tests__\"]\n}\n"
  },
  {
    "path": "sdks/typescript/shared/vite.config.ts",
    "content": "import { defineConfig } from 'vite';\nimport dts from 'vite-plugin-dts';\nimport react from '@vitejs/plugin-react-swc';\nimport path from 'path';\n\nexport default defineConfig({\n  plugins: [\n    react(),\n    dts({\n      insertTypesEntry: true,\n      tsconfigPath: path.resolve(__dirname, 'tsconfig.json'),\n    }),\n  ],\n  build: {\n    lib: {\n      entry: path.resolve(__dirname, 'src/index.ts'),\n      name: 'McpUiShared',\n      formats: ['es', 'umd'], // UMD for broader compatibility if needed, es for modern\n      fileName: (format) =>\n        `index.${format === 'es' ? 'mjs' : format === 'umd' ? 'js' : format + '.js'}`,\n    },\n    sourcemap: true,\n    // Minify options if needed, default is esbuild which is fast\n    // minify: 'terser',\n    // terserOptions: { ... }\n  },\n});\n"
  },
  {
    "path": "tsconfig.base.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"target\": \"ES2022\",\n    \"sourceMap\": true,\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"resolveJsonModule\": true,\n    \"noEmit\": true,\n    \"isolatedModules\": true,\n    \"types\": [\"vitest/globals\"]\n  }\n}\n"
  },
  {
    "path": "vitest.config.ts",
    "content": "import { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n  test: {\n    globals: true, // Use global APIs (describe, it, expect)\n    environment: 'jsdom', // Default environment, can be overridden per package/file\n    setupFiles: './vitest.setup.ts',\n    // include: ['sdks/typescript/*/src/**/__tests__/**/*.test.ts'],\n    exclude: ['**/node_modules/**', '**/dist/**', '**/.pnpm-store/**'],\n    coverage: {\n      provider: 'v8',\n      reporter: ['text', 'json', 'html'],\n      reportsDirectory: './coverage',\n      include: ['sdks/typescript/*/src/**/*.{ts,tsx}'],\n      exclude: [\n        'sdks/typescript/*/src/index.{ts,tsx}',\n        'sdks/typescript/*/**/*.d.ts',\n        'sdks/typescript/*/**/__tests__/**',\n        'sdks/typescript/*/**/dist/**',\n        'docs/src/**',\n      ],\n    },\n  },\n});\n"
  },
  {
    "path": "vitest.setup.ts",
    "content": "import '@testing-library/jest-dom';\n"
  }
]