#
tokens: 44325/50000 1/821 files (page 48/52)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 48 of 52. Use http://codebase.md/eyaltoledano/claude-task-master?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .changeset
│   ├── config.json
│   └── README.md
├── .claude
│   ├── agents
│   │   ├── task-checker.md
│   │   ├── task-executor.md
│   │   └── task-orchestrator.md
│   ├── commands
│   │   ├── dedupe.md
│   │   └── tm
│   │       ├── add-dependency
│   │       │   └── add-dependency.md
│   │       ├── add-subtask
│   │       │   ├── add-subtask.md
│   │       │   └── convert-task-to-subtask.md
│   │       ├── add-task
│   │       │   └── add-task.md
│   │       ├── analyze-complexity
│   │       │   └── analyze-complexity.md
│   │       ├── complexity-report
│   │       │   └── complexity-report.md
│   │       ├── expand
│   │       │   ├── expand-all-tasks.md
│   │       │   └── expand-task.md
│   │       ├── fix-dependencies
│   │       │   └── fix-dependencies.md
│   │       ├── generate
│   │       │   └── generate-tasks.md
│   │       ├── help.md
│   │       ├── init
│   │       │   ├── init-project-quick.md
│   │       │   └── init-project.md
│   │       ├── learn.md
│   │       ├── list
│   │       │   ├── list-tasks-by-status.md
│   │       │   ├── list-tasks-with-subtasks.md
│   │       │   └── list-tasks.md
│   │       ├── models
│   │       │   ├── setup-models.md
│   │       │   └── view-models.md
│   │       ├── next
│   │       │   └── next-task.md
│   │       ├── parse-prd
│   │       │   ├── parse-prd-with-research.md
│   │       │   └── parse-prd.md
│   │       ├── remove-dependency
│   │       │   └── remove-dependency.md
│   │       ├── remove-subtask
│   │       │   └── remove-subtask.md
│   │       ├── remove-subtasks
│   │       │   ├── remove-all-subtasks.md
│   │       │   └── remove-subtasks.md
│   │       ├── remove-task
│   │       │   └── remove-task.md
│   │       ├── set-status
│   │       │   ├── to-cancelled.md
│   │       │   ├── to-deferred.md
│   │       │   ├── to-done.md
│   │       │   ├── to-in-progress.md
│   │       │   ├── to-pending.md
│   │       │   └── to-review.md
│   │       ├── setup
│   │       │   ├── install-taskmaster.md
│   │       │   └── quick-install-taskmaster.md
│   │       ├── show
│   │       │   └── show-task.md
│   │       ├── status
│   │       │   └── project-status.md
│   │       ├── sync-readme
│   │       │   └── sync-readme.md
│   │       ├── tm-main.md
│   │       ├── update
│   │       │   ├── update-single-task.md
│   │       │   ├── update-task.md
│   │       │   └── update-tasks-from-id.md
│   │       ├── utils
│   │       │   └── analyze-project.md
│   │       ├── validate-dependencies
│   │       │   └── validate-dependencies.md
│   │       └── workflows
│   │           ├── auto-implement-tasks.md
│   │           ├── command-pipeline.md
│   │           └── smart-workflow.md
│   └── TM_COMMANDS_GUIDE.md
├── .coderabbit.yaml
├── .cursor
│   ├── mcp.json
│   └── rules
│       ├── ai_providers.mdc
│       ├── ai_services.mdc
│       ├── architecture.mdc
│       ├── changeset.mdc
│       ├── commands.mdc
│       ├── context_gathering.mdc
│       ├── cursor_rules.mdc
│       ├── dependencies.mdc
│       ├── dev_workflow.mdc
│       ├── git_workflow.mdc
│       ├── glossary.mdc
│       ├── mcp.mdc
│       ├── new_features.mdc
│       ├── self_improve.mdc
│       ├── tags.mdc
│       ├── taskmaster.mdc
│       ├── tasks.mdc
│       ├── telemetry.mdc
│       ├── test_workflow.mdc
│       ├── tests.mdc
│       ├── ui.mdc
│       └── utilities.mdc
├── .cursorignore
├── .env.example
├── .github
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.md
│   │   ├── enhancements---feature-requests.md
│   │   └── feedback.md
│   ├── PULL_REQUEST_TEMPLATE
│   │   ├── bugfix.md
│   │   ├── config.yml
│   │   ├── feature.md
│   │   └── integration.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── scripts
│   │   ├── auto-close-duplicates.mjs
│   │   ├── backfill-duplicate-comments.mjs
│   │   ├── check-pre-release-mode.mjs
│   │   ├── parse-metrics.mjs
│   │   ├── release.mjs
│   │   ├── tag-extension.mjs
│   │   └── utils.mjs
│   └── workflows
│       ├── auto-close-duplicates.yml
│       ├── backfill-duplicate-comments.yml
│       ├── ci.yml
│       ├── claude-dedupe-issues.yml
│       ├── claude-docs-trigger.yml
│       ├── claude-docs-updater.yml
│       ├── claude-issue-triage.yml
│       ├── claude.yml
│       ├── extension-ci.yml
│       ├── extension-release.yml
│       ├── log-issue-events.yml
│       ├── pre-release.yml
│       ├── release-check.yml
│       ├── release.yml
│       ├── update-models-md.yml
│       └── weekly-metrics-discord.yml
├── .gitignore
├── .kiro
│   ├── hooks
│   │   ├── tm-code-change-task-tracker.kiro.hook
│   │   ├── tm-complexity-analyzer.kiro.hook
│   │   ├── tm-daily-standup-assistant.kiro.hook
│   │   ├── tm-git-commit-task-linker.kiro.hook
│   │   ├── tm-pr-readiness-checker.kiro.hook
│   │   ├── tm-task-dependency-auto-progression.kiro.hook
│   │   └── tm-test-success-task-completer.kiro.hook
│   ├── settings
│   │   └── mcp.json
│   └── steering
│       ├── dev_workflow.md
│       ├── kiro_rules.md
│       ├── self_improve.md
│       ├── taskmaster_hooks_workflow.md
│       └── taskmaster.md
├── .manypkg.json
├── .mcp.json
├── .npmignore
├── .nvmrc
├── .taskmaster
│   ├── CLAUDE.md
│   ├── config.json
│   ├── docs
│   │   ├── MIGRATION-ROADMAP.md
│   │   ├── prd-tm-start.txt
│   │   ├── prd.txt
│   │   ├── README.md
│   │   ├── research
│   │   │   ├── 2025-06-14_how-can-i-improve-the-scope-up-and-scope-down-comm.md
│   │   │   ├── 2025-06-14_should-i-be-using-any-specific-libraries-for-this.md
│   │   │   ├── 2025-06-14_test-save-functionality.md
│   │   │   ├── 2025-06-14_test-the-fix-for-duplicate-saves-final-test.md
│   │   │   └── 2025-08-01_do-we-need-to-add-new-commands-or-can-we-just-weap.md
│   │   ├── task-template-importing-prd.txt
│   │   ├── test-prd.txt
│   │   └── tm-core-phase-1.txt
│   ├── reports
│   │   ├── task-complexity-report_cc-kiro-hooks.json
│   │   ├── task-complexity-report_test-prd-tag.json
│   │   ├── task-complexity-report_tm-core-phase-1.json
│   │   ├── task-complexity-report.json
│   │   └── tm-core-complexity.json
│   ├── state.json
│   ├── tasks
│   │   ├── task_001_tm-start.txt
│   │   ├── task_002_tm-start.txt
│   │   ├── task_003_tm-start.txt
│   │   ├── task_004_tm-start.txt
│   │   ├── task_007_tm-start.txt
│   │   └── tasks.json
│   └── templates
│       └── example_prd.txt
├── .vscode
│   ├── extensions.json
│   └── settings.json
├── apps
│   ├── cli
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── commands
│   │   │   │   ├── auth.command.ts
│   │   │   │   ├── context.command.ts
│   │   │   │   ├── list.command.ts
│   │   │   │   ├── set-status.command.ts
│   │   │   │   ├── show.command.ts
│   │   │   │   └── start.command.ts
│   │   │   ├── index.ts
│   │   │   ├── ui
│   │   │   │   ├── components
│   │   │   │   │   ├── dashboard.component.ts
│   │   │   │   │   ├── header.component.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── next-task.component.ts
│   │   │   │   │   ├── suggested-steps.component.ts
│   │   │   │   │   └── task-detail.component.ts
│   │   │   │   └── index.ts
│   │   │   └── utils
│   │   │       ├── auto-update.ts
│   │   │       └── ui.ts
│   │   └── tsconfig.json
│   ├── docs
│   │   ├── archive
│   │   │   ├── ai-client-utils-example.mdx
│   │   │   ├── ai-development-workflow.mdx
│   │   │   ├── command-reference.mdx
│   │   │   ├── configuration.mdx
│   │   │   ├── cursor-setup.mdx
│   │   │   ├── examples.mdx
│   │   │   └── Installation.mdx
│   │   ├── best-practices
│   │   │   ├── advanced-tasks.mdx
│   │   │   ├── configuration-advanced.mdx
│   │   │   └── index.mdx
│   │   ├── capabilities
│   │   │   ├── cli-root-commands.mdx
│   │   │   ├── index.mdx
│   │   │   ├── mcp.mdx
│   │   │   └── task-structure.mdx
│   │   ├── CHANGELOG.md
│   │   ├── docs.json
│   │   ├── favicon.svg
│   │   ├── getting-started
│   │   │   ├── contribute.mdx
│   │   │   ├── faq.mdx
│   │   │   └── quick-start
│   │   │       ├── configuration-quick.mdx
│   │   │       ├── execute-quick.mdx
│   │   │       ├── installation.mdx
│   │   │       ├── moving-forward.mdx
│   │   │       ├── prd-quick.mdx
│   │   │       ├── quick-start.mdx
│   │   │       ├── requirements.mdx
│   │   │       ├── rules-quick.mdx
│   │   │       └── tasks-quick.mdx
│   │   ├── introduction.mdx
│   │   ├── licensing.md
│   │   ├── logo
│   │   │   ├── dark.svg
│   │   │   ├── light.svg
│   │   │   └── task-master-logo.png
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── style.css
│   │   ├── vercel.json
│   │   └── whats-new.mdx
│   └── extension
│       ├── .vscodeignore
│       ├── assets
│       │   ├── banner.png
│       │   ├── icon-dark.svg
│       │   ├── icon-light.svg
│       │   ├── icon.png
│       │   ├── screenshots
│       │   │   ├── kanban-board.png
│       │   │   └── task-details.png
│       │   └── sidebar-icon.svg
│       ├── CHANGELOG.md
│       ├── components.json
│       ├── docs
│       │   ├── extension-CI-setup.md
│       │   └── extension-development-guide.md
│       ├── esbuild.js
│       ├── LICENSE
│       ├── package.json
│       ├── package.mjs
│       ├── package.publish.json
│       ├── README.md
│       ├── src
│       │   ├── components
│       │   │   ├── ConfigView.tsx
│       │   │   ├── constants.ts
│       │   │   ├── TaskDetails
│       │   │   │   ├── AIActionsSection.tsx
│       │   │   │   ├── DetailsSection.tsx
│       │   │   │   ├── PriorityBadge.tsx
│       │   │   │   ├── SubtasksSection.tsx
│       │   │   │   ├── TaskMetadataSidebar.tsx
│       │   │   │   └── useTaskDetails.ts
│       │   │   ├── TaskDetailsView.tsx
│       │   │   ├── TaskMasterLogo.tsx
│       │   │   └── ui
│       │   │       ├── badge.tsx
│       │   │       ├── breadcrumb.tsx
│       │   │       ├── button.tsx
│       │   │       ├── card.tsx
│       │   │       ├── collapsible.tsx
│       │   │       ├── CollapsibleSection.tsx
│       │   │       ├── dropdown-menu.tsx
│       │   │       ├── label.tsx
│       │   │       ├── scroll-area.tsx
│       │   │       ├── separator.tsx
│       │   │       ├── shadcn-io
│       │   │       │   └── kanban
│       │   │       │       └── index.tsx
│       │   │       └── textarea.tsx
│       │   ├── extension.ts
│       │   ├── index.ts
│       │   ├── lib
│       │   │   └── utils.ts
│       │   ├── services
│       │   │   ├── config-service.ts
│       │   │   ├── error-handler.ts
│       │   │   ├── notification-preferences.ts
│       │   │   ├── polling-service.ts
│       │   │   ├── polling-strategies.ts
│       │   │   ├── sidebar-webview-manager.ts
│       │   │   ├── task-repository.ts
│       │   │   ├── terminal-manager.ts
│       │   │   └── webview-manager.ts
│       │   ├── test
│       │   │   └── extension.test.ts
│       │   ├── utils
│       │   │   ├── configManager.ts
│       │   │   ├── connectionManager.ts
│       │   │   ├── errorHandler.ts
│       │   │   ├── event-emitter.ts
│       │   │   ├── logger.ts
│       │   │   ├── mcpClient.ts
│       │   │   ├── notificationPreferences.ts
│       │   │   └── task-master-api
│       │   │       ├── cache
│       │   │       │   └── cache-manager.ts
│       │   │       ├── index.ts
│       │   │       ├── mcp-client.ts
│       │   │       ├── transformers
│       │   │       │   └── task-transformer.ts
│       │   │       └── types
│       │   │           └── index.ts
│       │   └── webview
│       │       ├── App.tsx
│       │       ├── components
│       │       │   ├── AppContent.tsx
│       │       │   ├── EmptyState.tsx
│       │       │   ├── ErrorBoundary.tsx
│       │       │   ├── PollingStatus.tsx
│       │       │   ├── PriorityBadge.tsx
│       │       │   ├── SidebarView.tsx
│       │       │   ├── TagDropdown.tsx
│       │       │   ├── TaskCard.tsx
│       │       │   ├── TaskEditModal.tsx
│       │       │   ├── TaskMasterKanban.tsx
│       │       │   ├── ToastContainer.tsx
│       │       │   └── ToastNotification.tsx
│       │       ├── constants
│       │       │   └── index.ts
│       │       ├── contexts
│       │       │   └── VSCodeContext.tsx
│       │       ├── hooks
│       │       │   ├── useTaskQueries.ts
│       │       │   ├── useVSCodeMessages.ts
│       │       │   └── useWebviewHeight.ts
│       │       ├── index.css
│       │       ├── index.tsx
│       │       ├── providers
│       │       │   └── QueryProvider.tsx
│       │       ├── reducers
│       │       │   └── appReducer.ts
│       │       ├── sidebar.tsx
│       │       ├── types
│       │       │   └── index.ts
│       │       └── utils
│       │           ├── logger.ts
│       │           └── toast.ts
│       └── tsconfig.json
├── assets
│   ├── .windsurfrules
│   ├── AGENTS.md
│   ├── claude
│   │   ├── agents
│   │   │   ├── task-checker.md
│   │   │   ├── task-executor.md
│   │   │   └── task-orchestrator.md
│   │   ├── commands
│   │   │   └── tm
│   │   │       ├── add-dependency
│   │   │       │   └── add-dependency.md
│   │   │       ├── add-subtask
│   │   │       │   ├── add-subtask.md
│   │   │       │   └── convert-task-to-subtask.md
│   │   │       ├── add-task
│   │   │       │   └── add-task.md
│   │   │       ├── analyze-complexity
│   │   │       │   └── analyze-complexity.md
│   │   │       ├── clear-subtasks
│   │   │       │   ├── clear-all-subtasks.md
│   │   │       │   └── clear-subtasks.md
│   │   │       ├── complexity-report
│   │   │       │   └── complexity-report.md
│   │   │       ├── expand
│   │   │       │   ├── expand-all-tasks.md
│   │   │       │   └── expand-task.md
│   │   │       ├── fix-dependencies
│   │   │       │   └── fix-dependencies.md
│   │   │       ├── generate
│   │   │       │   └── generate-tasks.md
│   │   │       ├── help.md
│   │   │       ├── init
│   │   │       │   ├── init-project-quick.md
│   │   │       │   └── init-project.md
│   │   │       ├── learn.md
│   │   │       ├── list
│   │   │       │   ├── list-tasks-by-status.md
│   │   │       │   ├── list-tasks-with-subtasks.md
│   │   │       │   └── list-tasks.md
│   │   │       ├── models
│   │   │       │   ├── setup-models.md
│   │   │       │   └── view-models.md
│   │   │       ├── next
│   │   │       │   └── next-task.md
│   │   │       ├── parse-prd
│   │   │       │   ├── parse-prd-with-research.md
│   │   │       │   └── parse-prd.md
│   │   │       ├── remove-dependency
│   │   │       │   └── remove-dependency.md
│   │   │       ├── remove-subtask
│   │   │       │   └── remove-subtask.md
│   │   │       ├── remove-subtasks
│   │   │       │   ├── remove-all-subtasks.md
│   │   │       │   └── remove-subtasks.md
│   │   │       ├── remove-task
│   │   │       │   └── remove-task.md
│   │   │       ├── set-status
│   │   │       │   ├── to-cancelled.md
│   │   │       │   ├── to-deferred.md
│   │   │       │   ├── to-done.md
│   │   │       │   ├── to-in-progress.md
│   │   │       │   ├── to-pending.md
│   │   │       │   └── to-review.md
│   │   │       ├── setup
│   │   │       │   ├── install-taskmaster.md
│   │   │       │   └── quick-install-taskmaster.md
│   │   │       ├── show
│   │   │       │   └── show-task.md
│   │   │       ├── status
│   │   │       │   └── project-status.md
│   │   │       ├── sync-readme
│   │   │       │   └── sync-readme.md
│   │   │       ├── tm-main.md
│   │   │       ├── update
│   │   │       │   ├── update-single-task.md
│   │   │       │   ├── update-task.md
│   │   │       │   └── update-tasks-from-id.md
│   │   │       ├── utils
│   │   │       │   └── analyze-project.md
│   │   │       ├── validate-dependencies
│   │   │       │   └── validate-dependencies.md
│   │   │       └── workflows
│   │   │           ├── auto-implement-tasks.md
│   │   │           ├── command-pipeline.md
│   │   │           └── smart-workflow.md
│   │   └── TM_COMMANDS_GUIDE.md
│   ├── config.json
│   ├── env.example
│   ├── example_prd.txt
│   ├── gitignore
│   ├── kiro-hooks
│   │   ├── tm-code-change-task-tracker.kiro.hook
│   │   ├── tm-complexity-analyzer.kiro.hook
│   │   ├── tm-daily-standup-assistant.kiro.hook
│   │   ├── tm-git-commit-task-linker.kiro.hook
│   │   ├── tm-pr-readiness-checker.kiro.hook
│   │   ├── tm-task-dependency-auto-progression.kiro.hook
│   │   └── tm-test-success-task-completer.kiro.hook
│   ├── roocode
│   │   ├── .roo
│   │   │   ├── rules-architect
│   │   │   │   └── architect-rules
│   │   │   ├── rules-ask
│   │   │   │   └── ask-rules
│   │   │   ├── rules-code
│   │   │   │   └── code-rules
│   │   │   ├── rules-debug
│   │   │   │   └── debug-rules
│   │   │   ├── rules-orchestrator
│   │   │   │   └── orchestrator-rules
│   │   │   └── rules-test
│   │   │       └── test-rules
│   │   └── .roomodes
│   ├── rules
│   │   ├── cursor_rules.mdc
│   │   ├── dev_workflow.mdc
│   │   ├── self_improve.mdc
│   │   ├── taskmaster_hooks_workflow.mdc
│   │   └── taskmaster.mdc
│   └── scripts_README.md
├── bin
│   └── task-master.js
├── biome.json
├── CHANGELOG.md
├── CLAUDE.md
├── context
│   ├── chats
│   │   ├── add-task-dependencies-1.md
│   │   └── max-min-tokens.txt.md
│   ├── fastmcp-core.txt
│   ├── fastmcp-docs.txt
│   ├── MCP_INTEGRATION.md
│   ├── mcp-js-sdk-docs.txt
│   ├── mcp-protocol-repo.txt
│   ├── mcp-protocol-schema-03262025.json
│   └── mcp-protocol-spec.txt
├── CONTRIBUTING.md
├── docs
│   ├── CLI-COMMANDER-PATTERN.md
│   ├── command-reference.md
│   ├── configuration.md
│   ├── contributor-docs
│   │   └── testing-roo-integration.md
│   ├── cross-tag-task-movement.md
│   ├── examples
│   │   └── claude-code-usage.md
│   ├── examples.md
│   ├── licensing.md
│   ├── mcp-provider-guide.md
│   ├── mcp-provider.md
│   ├── migration-guide.md
│   ├── models.md
│   ├── providers
│   │   └── gemini-cli.md
│   ├── README.md
│   ├── scripts
│   │   └── models-json-to-markdown.js
│   ├── task-structure.md
│   └── tutorial.md
├── images
│   └── logo.png
├── index.js
├── jest.config.js
├── jest.resolver.cjs
├── LICENSE
├── llms-install.md
├── mcp-server
│   ├── server.js
│   └── src
│       ├── core
│       │   ├── __tests__
│       │   │   └── context-manager.test.js
│       │   ├── context-manager.js
│       │   ├── direct-functions
│       │   │   ├── add-dependency.js
│       │   │   ├── add-subtask.js
│       │   │   ├── add-tag.js
│       │   │   ├── add-task.js
│       │   │   ├── analyze-task-complexity.js
│       │   │   ├── cache-stats.js
│       │   │   ├── clear-subtasks.js
│       │   │   ├── complexity-report.js
│       │   │   ├── copy-tag.js
│       │   │   ├── create-tag-from-branch.js
│       │   │   ├── delete-tag.js
│       │   │   ├── expand-all-tasks.js
│       │   │   ├── expand-task.js
│       │   │   ├── fix-dependencies.js
│       │   │   ├── generate-task-files.js
│       │   │   ├── initialize-project.js
│       │   │   ├── list-tags.js
│       │   │   ├── list-tasks.js
│       │   │   ├── models.js
│       │   │   ├── move-task-cross-tag.js
│       │   │   ├── move-task.js
│       │   │   ├── next-task.js
│       │   │   ├── parse-prd.js
│       │   │   ├── remove-dependency.js
│       │   │   ├── remove-subtask.js
│       │   │   ├── remove-task.js
│       │   │   ├── rename-tag.js
│       │   │   ├── research.js
│       │   │   ├── response-language.js
│       │   │   ├── rules.js
│       │   │   ├── scope-down.js
│       │   │   ├── scope-up.js
│       │   │   ├── set-task-status.js
│       │   │   ├── show-task.js
│       │   │   ├── update-subtask-by-id.js
│       │   │   ├── update-task-by-id.js
│       │   │   ├── update-tasks.js
│       │   │   ├── use-tag.js
│       │   │   └── validate-dependencies.js
│       │   ├── task-master-core.js
│       │   └── utils
│       │       ├── env-utils.js
│       │       └── path-utils.js
│       ├── custom-sdk
│       │   ├── errors.js
│       │   ├── index.js
│       │   ├── json-extractor.js
│       │   ├── language-model.js
│       │   ├── message-converter.js
│       │   └── schema-converter.js
│       ├── index.js
│       ├── logger.js
│       ├── providers
│       │   └── mcp-provider.js
│       └── tools
│           ├── add-dependency.js
│           ├── add-subtask.js
│           ├── add-tag.js
│           ├── add-task.js
│           ├── analyze.js
│           ├── clear-subtasks.js
│           ├── complexity-report.js
│           ├── copy-tag.js
│           ├── delete-tag.js
│           ├── expand-all.js
│           ├── expand-task.js
│           ├── fix-dependencies.js
│           ├── generate.js
│           ├── get-operation-status.js
│           ├── get-task.js
│           ├── get-tasks.js
│           ├── index.js
│           ├── initialize-project.js
│           ├── list-tags.js
│           ├── models.js
│           ├── move-task.js
│           ├── next-task.js
│           ├── parse-prd.js
│           ├── remove-dependency.js
│           ├── remove-subtask.js
│           ├── remove-task.js
│           ├── rename-tag.js
│           ├── research.js
│           ├── response-language.js
│           ├── rules.js
│           ├── scope-down.js
│           ├── scope-up.js
│           ├── set-task-status.js
│           ├── update-subtask.js
│           ├── update-task.js
│           ├── update.js
│           ├── use-tag.js
│           ├── utils.js
│           └── validate-dependencies.js
├── mcp-test.js
├── output.json
├── package-lock.json
├── package.json
├── packages
│   ├── build-config
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   └── tsdown.base.ts
│   │   └── tsconfig.json
│   └── tm-core
│       ├── .gitignore
│       ├── CHANGELOG.md
│       ├── docs
│       │   └── listTasks-architecture.md
│       ├── package.json
│       ├── POC-STATUS.md
│       ├── README.md
│       ├── src
│       │   ├── auth
│       │   │   ├── auth-manager.test.ts
│       │   │   ├── auth-manager.ts
│       │   │   ├── config.ts
│       │   │   ├── credential-store.test.ts
│       │   │   ├── credential-store.ts
│       │   │   ├── index.ts
│       │   │   ├── oauth-service.ts
│       │   │   ├── supabase-session-storage.ts
│       │   │   └── types.ts
│       │   ├── clients
│       │   │   ├── index.ts
│       │   │   └── supabase-client.ts
│       │   ├── config
│       │   │   ├── config-manager.spec.ts
│       │   │   ├── config-manager.ts
│       │   │   ├── index.ts
│       │   │   └── services
│       │   │       ├── config-loader.service.spec.ts
│       │   │       ├── config-loader.service.ts
│       │   │       ├── config-merger.service.spec.ts
│       │   │       ├── config-merger.service.ts
│       │   │       ├── config-persistence.service.spec.ts
│       │   │       ├── config-persistence.service.ts
│       │   │       ├── environment-config-provider.service.spec.ts
│       │   │       ├── environment-config-provider.service.ts
│       │   │       ├── index.ts
│       │   │       ├── runtime-state-manager.service.spec.ts
│       │   │       └── runtime-state-manager.service.ts
│       │   ├── constants
│       │   │   └── index.ts
│       │   ├── entities
│       │   │   └── task.entity.ts
│       │   ├── errors
│       │   │   ├── index.ts
│       │   │   └── task-master-error.ts
│       │   ├── executors
│       │   │   ├── base-executor.ts
│       │   │   ├── claude-executor.ts
│       │   │   ├── executor-factory.ts
│       │   │   ├── executor-service.ts
│       │   │   ├── index.ts
│       │   │   └── types.ts
│       │   ├── index.ts
│       │   ├── interfaces
│       │   │   ├── ai-provider.interface.ts
│       │   │   ├── configuration.interface.ts
│       │   │   ├── index.ts
│       │   │   └── storage.interface.ts
│       │   ├── logger
│       │   │   ├── factory.ts
│       │   │   ├── index.ts
│       │   │   └── logger.ts
│       │   ├── mappers
│       │   │   └── TaskMapper.ts
│       │   ├── parser
│       │   │   └── index.ts
│       │   ├── providers
│       │   │   ├── ai
│       │   │   │   ├── base-provider.ts
│       │   │   │   └── index.ts
│       │   │   └── index.ts
│       │   ├── repositories
│       │   │   ├── supabase-task-repository.ts
│       │   │   └── task-repository.interface.ts
│       │   ├── services
│       │   │   ├── index.ts
│       │   │   ├── organization.service.ts
│       │   │   ├── task-execution-service.ts
│       │   │   └── task-service.ts
│       │   ├── storage
│       │   │   ├── api-storage.ts
│       │   │   ├── file-storage
│       │   │   │   ├── file-operations.ts
│       │   │   │   ├── file-storage.ts
│       │   │   │   ├── format-handler.ts
│       │   │   │   ├── index.ts
│       │   │   │   └── path-resolver.ts
│       │   │   ├── index.ts
│       │   │   └── storage-factory.ts
│       │   ├── subpath-exports.test.ts
│       │   ├── task-master-core.ts
│       │   ├── types
│       │   │   ├── database.types.ts
│       │   │   ├── index.ts
│       │   │   └── legacy.ts
│       │   └── utils
│       │       ├── id-generator.ts
│       │       └── index.ts
│       ├── tests
│       │   ├── integration
│       │   │   └── list-tasks.test.ts
│       │   ├── mocks
│       │   │   └── mock-provider.ts
│       │   ├── setup.ts
│       │   └── unit
│       │       ├── base-provider.test.ts
│       │       ├── executor.test.ts
│       │       └── smoke.test.ts
│       ├── tsconfig.json
│       └── vitest.config.ts
├── README-task-master.md
├── README.md
├── scripts
│   ├── dev.js
│   ├── init.js
│   ├── modules
│   │   ├── ai-services-unified.js
│   │   ├── commands.js
│   │   ├── config-manager.js
│   │   ├── dependency-manager.js
│   │   ├── index.js
│   │   ├── prompt-manager.js
│   │   ├── supported-models.json
│   │   ├── sync-readme.js
│   │   ├── task-manager
│   │   │   ├── add-subtask.js
│   │   │   ├── add-task.js
│   │   │   ├── analyze-task-complexity.js
│   │   │   ├── clear-subtasks.js
│   │   │   ├── expand-all-tasks.js
│   │   │   ├── expand-task.js
│   │   │   ├── find-next-task.js
│   │   │   ├── generate-task-files.js
│   │   │   ├── is-task-dependent.js
│   │   │   ├── list-tasks.js
│   │   │   ├── migrate.js
│   │   │   ├── models.js
│   │   │   ├── move-task.js
│   │   │   ├── parse-prd
│   │   │   │   ├── index.js
│   │   │   │   ├── parse-prd-config.js
│   │   │   │   ├── parse-prd-helpers.js
│   │   │   │   ├── parse-prd-non-streaming.js
│   │   │   │   ├── parse-prd-streaming.js
│   │   │   │   └── parse-prd.js
│   │   │   ├── remove-subtask.js
│   │   │   ├── remove-task.js
│   │   │   ├── research.js
│   │   │   ├── response-language.js
│   │   │   ├── scope-adjustment.js
│   │   │   ├── set-task-status.js
│   │   │   ├── tag-management.js
│   │   │   ├── task-exists.js
│   │   │   ├── update-single-task-status.js
│   │   │   ├── update-subtask-by-id.js
│   │   │   ├── update-task-by-id.js
│   │   │   └── update-tasks.js
│   │   ├── task-manager.js
│   │   ├── ui.js
│   │   ├── update-config-tokens.js
│   │   ├── utils
│   │   │   ├── contextGatherer.js
│   │   │   ├── fuzzyTaskSearch.js
│   │   │   └── git-utils.js
│   │   └── utils.js
│   ├── task-complexity-report.json
│   ├── test-claude-errors.js
│   └── test-claude.js
├── src
│   ├── ai-providers
│   │   ├── anthropic.js
│   │   ├── azure.js
│   │   ├── base-provider.js
│   │   ├── bedrock.js
│   │   ├── claude-code.js
│   │   ├── custom-sdk
│   │   │   ├── claude-code
│   │   │   │   ├── errors.js
│   │   │   │   ├── index.js
│   │   │   │   ├── json-extractor.js
│   │   │   │   ├── language-model.js
│   │   │   │   ├── message-converter.js
│   │   │   │   └── types.js
│   │   │   └── grok-cli
│   │   │       ├── errors.js
│   │   │       ├── index.js
│   │   │       ├── json-extractor.js
│   │   │       ├── language-model.js
│   │   │       ├── message-converter.js
│   │   │       └── types.js
│   │   ├── gemini-cli.js
│   │   ├── google-vertex.js
│   │   ├── google.js
│   │   ├── grok-cli.js
│   │   ├── groq.js
│   │   ├── index.js
│   │   ├── ollama.js
│   │   ├── openai.js
│   │   ├── openrouter.js
│   │   ├── perplexity.js
│   │   └── xai.js
│   ├── constants
│   │   ├── commands.js
│   │   ├── paths.js
│   │   ├── profiles.js
│   │   ├── providers.js
│   │   ├── rules-actions.js
│   │   ├── task-priority.js
│   │   └── task-status.js
│   ├── profiles
│   │   ├── amp.js
│   │   ├── base-profile.js
│   │   ├── claude.js
│   │   ├── cline.js
│   │   ├── codex.js
│   │   ├── cursor.js
│   │   ├── gemini.js
│   │   ├── index.js
│   │   ├── kilo.js
│   │   ├── kiro.js
│   │   ├── opencode.js
│   │   ├── roo.js
│   │   ├── trae.js
│   │   ├── vscode.js
│   │   ├── windsurf.js
│   │   └── zed.js
│   ├── progress
│   │   ├── base-progress-tracker.js
│   │   ├── cli-progress-factory.js
│   │   ├── parse-prd-tracker.js
│   │   ├── progress-tracker-builder.js
│   │   └── tracker-ui.js
│   ├── prompts
│   │   ├── add-task.json
│   │   ├── analyze-complexity.json
│   │   ├── expand-task.json
│   │   ├── parse-prd.json
│   │   ├── README.md
│   │   ├── research.json
│   │   ├── schemas
│   │   │   ├── parameter.schema.json
│   │   │   ├── prompt-template.schema.json
│   │   │   ├── README.md
│   │   │   └── variant.schema.json
│   │   ├── update-subtask.json
│   │   ├── update-task.json
│   │   └── update-tasks.json
│   ├── provider-registry
│   │   └── index.js
│   ├── task-master.js
│   ├── ui
│   │   ├── confirm.js
│   │   ├── indicators.js
│   │   └── parse-prd.js
│   └── utils
│       ├── asset-resolver.js
│       ├── create-mcp-config.js
│       ├── format.js
│       ├── getVersion.js
│       ├── logger-utils.js
│       ├── manage-gitignore.js
│       ├── path-utils.js
│       ├── profiles.js
│       ├── rule-transformer.js
│       ├── stream-parser.js
│       └── timeout-manager.js
├── test-clean-tags.js
├── test-config-manager.js
├── test-prd.txt
├── test-tag-functions.js
├── test-version-check-full.js
├── test-version-check.js
├── tests
│   ├── e2e
│   │   ├── e2e_helpers.sh
│   │   ├── parse_llm_output.cjs
│   │   ├── run_e2e.sh
│   │   ├── run_fallback_verification.sh
│   │   └── test_llm_analysis.sh
│   ├── fixture
│   │   └── test-tasks.json
│   ├── fixtures
│   │   ├── .taskmasterconfig
│   │   ├── sample-claude-response.js
│   │   ├── sample-prd.txt
│   │   └── sample-tasks.js
│   ├── integration
│   │   ├── claude-code-optional.test.js
│   │   ├── cli
│   │   │   ├── commands.test.js
│   │   │   ├── complex-cross-tag-scenarios.test.js
│   │   │   └── move-cross-tag.test.js
│   │   ├── manage-gitignore.test.js
│   │   ├── mcp-server
│   │   │   └── direct-functions.test.js
│   │   ├── move-task-cross-tag.integration.test.js
│   │   ├── move-task-simple.integration.test.js
│   │   └── profiles
│   │       ├── amp-init-functionality.test.js
│   │       ├── claude-init-functionality.test.js
│   │       ├── cline-init-functionality.test.js
│   │       ├── codex-init-functionality.test.js
│   │       ├── cursor-init-functionality.test.js
│   │       ├── gemini-init-functionality.test.js
│   │       ├── opencode-init-functionality.test.js
│   │       ├── roo-files-inclusion.test.js
│   │       ├── roo-init-functionality.test.js
│   │       ├── rules-files-inclusion.test.js
│   │       ├── trae-init-functionality.test.js
│   │       ├── vscode-init-functionality.test.js
│   │       └── windsurf-init-functionality.test.js
│   ├── manual
│   │   ├── progress
│   │   │   ├── parse-prd-analysis.js
│   │   │   ├── test-parse-prd.js
│   │   │   └── TESTING_GUIDE.md
│   │   └── prompts
│   │       ├── prompt-test.js
│   │       └── README.md
│   ├── README.md
│   ├── setup.js
│   └── unit
│       ├── ai-providers
│       │   ├── claude-code.test.js
│       │   ├── custom-sdk
│       │   │   └── claude-code
│       │   │       └── language-model.test.js
│       │   ├── gemini-cli.test.js
│       │   ├── mcp-components.test.js
│       │   └── openai.test.js
│       ├── ai-services-unified.test.js
│       ├── commands.test.js
│       ├── config-manager.test.js
│       ├── config-manager.test.mjs
│       ├── dependency-manager.test.js
│       ├── init.test.js
│       ├── initialize-project.test.js
│       ├── kebab-case-validation.test.js
│       ├── manage-gitignore.test.js
│       ├── mcp
│       │   └── tools
│       │       ├── __mocks__
│       │       │   └── move-task.js
│       │       ├── add-task.test.js
│       │       ├── analyze-complexity.test.js
│       │       ├── expand-all.test.js
│       │       ├── get-tasks.test.js
│       │       ├── initialize-project.test.js
│       │       ├── move-task-cross-tag-options.test.js
│       │       ├── move-task-cross-tag.test.js
│       │       └── remove-task.test.js
│       ├── mcp-providers
│       │   ├── mcp-components.test.js
│       │   └── mcp-provider.test.js
│       ├── parse-prd.test.js
│       ├── profiles
│       │   ├── amp-integration.test.js
│       │   ├── claude-integration.test.js
│       │   ├── cline-integration.test.js
│       │   ├── codex-integration.test.js
│       │   ├── cursor-integration.test.js
│       │   ├── gemini-integration.test.js
│       │   ├── kilo-integration.test.js
│       │   ├── kiro-integration.test.js
│       │   ├── mcp-config-validation.test.js
│       │   ├── opencode-integration.test.js
│       │   ├── profile-safety-check.test.js
│       │   ├── roo-integration.test.js
│       │   ├── rule-transformer-cline.test.js
│       │   ├── rule-transformer-cursor.test.js
│       │   ├── rule-transformer-gemini.test.js
│       │   ├── rule-transformer-kilo.test.js
│       │   ├── rule-transformer-kiro.test.js
│       │   ├── rule-transformer-opencode.test.js
│       │   ├── rule-transformer-roo.test.js
│       │   ├── rule-transformer-trae.test.js
│       │   ├── rule-transformer-vscode.test.js
│       │   ├── rule-transformer-windsurf.test.js
│       │   ├── rule-transformer-zed.test.js
│       │   ├── rule-transformer.test.js
│       │   ├── selective-profile-removal.test.js
│       │   ├── subdirectory-support.test.js
│       │   ├── trae-integration.test.js
│       │   ├── vscode-integration.test.js
│       │   ├── windsurf-integration.test.js
│       │   └── zed-integration.test.js
│       ├── progress
│       │   └── base-progress-tracker.test.js
│       ├── prompt-manager.test.js
│       ├── prompts
│       │   └── expand-task-prompt.test.js
│       ├── providers
│       │   └── provider-registry.test.js
│       ├── scripts
│       │   └── modules
│       │       ├── commands
│       │       │   ├── move-cross-tag.test.js
│       │       │   └── README.md
│       │       ├── dependency-manager
│       │       │   ├── circular-dependencies.test.js
│       │       │   ├── cross-tag-dependencies.test.js
│       │       │   └── fix-dependencies-command.test.js
│       │       ├── task-manager
│       │       │   ├── add-subtask.test.js
│       │       │   ├── add-task.test.js
│       │       │   ├── analyze-task-complexity.test.js
│       │       │   ├── clear-subtasks.test.js
│       │       │   ├── complexity-report-tag-isolation.test.js
│       │       │   ├── expand-all-tasks.test.js
│       │       │   ├── expand-task.test.js
│       │       │   ├── find-next-task.test.js
│       │       │   ├── generate-task-files.test.js
│       │       │   ├── list-tasks.test.js
│       │       │   ├── move-task-cross-tag.test.js
│       │       │   ├── move-task.test.js
│       │       │   ├── parse-prd.test.js
│       │       │   ├── remove-subtask.test.js
│       │       │   ├── remove-task.test.js
│       │       │   ├── research.test.js
│       │       │   ├── scope-adjustment.test.js
│       │       │   ├── set-task-status.test.js
│       │       │   ├── setup.js
│       │       │   ├── update-single-task-status.test.js
│       │       │   ├── update-subtask-by-id.test.js
│       │       │   ├── update-task-by-id.test.js
│       │       │   └── update-tasks.test.js
│       │       ├── ui
│       │       │   └── cross-tag-error-display.test.js
│       │       └── utils-tag-aware-paths.test.js
│       ├── task-finder.test.js
│       ├── task-manager
│       │   ├── clear-subtasks.test.js
│       │   ├── move-task.test.js
│       │   ├── tag-boundary.test.js
│       │   └── tag-management.test.js
│       ├── task-master.test.js
│       ├── ui
│       │   └── indicators.test.js
│       ├── ui.test.js
│       ├── utils-strip-ansi.test.js
│       └── utils.test.js
├── tsconfig.json
├── tsdown.config.ts
└── turbo.json
```

# Files

--------------------------------------------------------------------------------
/context/fastmcp-docs.txt:
--------------------------------------------------------------------------------

```
   1 | Directory Structure:
   2 | 
   3 | └── ./
   4 |     ├── src
   5 |     │   ├── bin
   6 |     │   │   └── fastmcp.ts
   7 |     │   ├── examples
   8 |     │   │   └── addition.ts
   9 |     │   ├── FastMCP.test.ts
  10 |     │   └── FastMCP.ts
  11 |     ├── eslint.config.js
  12 |     ├── package.json
  13 |     ├── README.md
  14 |     └── vitest.config.js
  15 | 
  16 | 
  17 | 
  18 | ---
  19 | File: /src/bin/fastmcp.ts
  20 | ---
  21 | 
  22 | #!/usr/bin/env node
  23 | 
  24 | import yargs from "yargs";
  25 | import { hideBin } from "yargs/helpers";
  26 | import { execa } from "execa";
  27 | 
  28 | await yargs(hideBin(process.argv))
  29 |   .scriptName("fastmcp")
  30 |   .command(
  31 |     "dev <file>",
  32 |     "Start a development server",
  33 |     (yargs) => {
  34 |       return yargs.positional("file", {
  35 |         type: "string",
  36 |         describe: "The path to the server file",
  37 |         demandOption: true,
  38 |       });
  39 |     },
  40 |     async (argv) => {
  41 |       try {
  42 |         await execa({
  43 |           stdin: "inherit",
  44 |           stdout: "inherit",
  45 |           stderr: "inherit",
  46 |         })`npx @wong2/mcp-cli npx tsx ${argv.file}`;
  47 |       } catch {
  48 |         process.exit(1);
  49 |       }
  50 |     },
  51 |   )
  52 |   .command(
  53 |     "inspect <file>",
  54 |     "Inspect a server file",
  55 |     (yargs) => {
  56 |       return yargs.positional("file", {
  57 |         type: "string",
  58 |         describe: "The path to the server file",
  59 |         demandOption: true,
  60 |       });
  61 |     },
  62 |     async (argv) => {
  63 |       try {
  64 |         await execa({
  65 |           stdout: "inherit",
  66 |           stderr: "inherit",
  67 |         })`npx @modelcontextprotocol/inspector npx tsx ${argv.file}`;
  68 |       } catch {
  69 |         process.exit(1);
  70 |       }
  71 |     },
  72 |   )
  73 |   .help()
  74 |   .parseAsync();
  75 | 
  76 | 
  77 | 
  78 | ---
  79 | File: /src/examples/addition.ts
  80 | ---
  81 | 
  82 | /**
  83 |  * This is a complete example of an MCP server.
  84 |  */
  85 | import { FastMCP } from "../FastMCP.js";
  86 | import { z } from "zod";
  87 | 
  88 | const server = new FastMCP({
  89 |   name: "Addition",
  90 |   version: "1.0.0",
  91 | });
  92 | 
  93 | server.addTool({
  94 |   name: "add",
  95 |   description: "Add two numbers",
  96 |   parameters: z.object({
  97 |     a: z.number(),
  98 |     b: z.number(),
  99 |   }),
 100 |   execute: async (args) => {
 101 |     return String(args.a + args.b);
 102 |   },
 103 | });
 104 | 
 105 | server.addResource({
 106 |   uri: "file:///logs/app.log",
 107 |   name: "Application Logs",
 108 |   mimeType: "text/plain",
 109 |   async load() {
 110 |     return {
 111 |       text: "Example log content",
 112 |     };
 113 |   },
 114 | });
 115 | 
 116 | server.addPrompt({
 117 |   name: "git-commit",
 118 |   description: "Generate a Git commit message",
 119 |   arguments: [
 120 |     {
 121 |       name: "changes",
 122 |       description: "Git diff or description of changes",
 123 |       required: true,
 124 |     },
 125 |   ],
 126 |   load: async (args) => {
 127 |     return `Generate a concise but descriptive commit message for these changes:\n\n${args.changes}`;
 128 |   },
 129 | });
 130 | 
 131 | server.start({
 132 |   transportType: "stdio",
 133 | });
 134 | 
 135 | 
 136 | 
 137 | ---
 138 | File: /src/FastMCP.test.ts
 139 | ---
 140 | 
 141 | import { FastMCP, FastMCPSession, UserError, imageContent } from "./FastMCP.js";
 142 | import { z } from "zod";
 143 | import { test, expect, vi } from "vitest";
 144 | import { Client } from "@modelcontextprotocol/sdk/client/index.js";
 145 | import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
 146 | import { getRandomPort } from "get-port-please";
 147 | import { setTimeout as delay } from "timers/promises";
 148 | import {
 149 |   CreateMessageRequestSchema,
 150 |   ErrorCode,
 151 |   ListRootsRequestSchema,
 152 |   LoggingMessageNotificationSchema,
 153 |   McpError,
 154 |   PingRequestSchema,
 155 |   Root,
 156 | } from "@modelcontextprotocol/sdk/types.js";
 157 | import { createEventSource, EventSourceClient } from 'eventsource-client';
 158 | 
 159 | const runWithTestServer = async ({
 160 |   run,
 161 |   client: createClient,
 162 |   server: createServer,
 163 | }: {
 164 |   server?: () => Promise<FastMCP>;
 165 |   client?: () => Promise<Client>;
 166 |   run: ({
 167 |     client,
 168 |     server,
 169 |   }: {
 170 |     client: Client;
 171 |     server: FastMCP;
 172 |     session: FastMCPSession;
 173 |   }) => Promise<void>;
 174 | }) => {
 175 |   const port = await getRandomPort();
 176 | 
 177 |   const server = createServer
 178 |     ? await createServer()
 179 |     : new FastMCP({
 180 |         name: "Test",
 181 |         version: "1.0.0",
 182 |       });
 183 | 
 184 |   await server.start({
 185 |     transportType: "sse",
 186 |     sse: {
 187 |       endpoint: "/sse",
 188 |       port,
 189 |     },
 190 |   });
 191 | 
 192 |   try {
 193 |     const client = createClient
 194 |       ? await createClient()
 195 |       : new Client(
 196 |           {
 197 |             name: "example-client",
 198 |             version: "1.0.0",
 199 |           },
 200 |           {
 201 |             capabilities: {},
 202 |           },
 203 |         );
 204 | 
 205 |     const transport = new SSEClientTransport(
 206 |       new URL(`http://localhost:${port}/sse`),
 207 |     );
 208 | 
 209 |     const session = await new Promise<FastMCPSession>((resolve) => {
 210 |       server.on("connect", (event) => {
 211 |         
 212 |         resolve(event.session);
 213 |       });
 214 | 
 215 |       client.connect(transport);
 216 |     });
 217 | 
 218 |     await run({ client, server, session });
 219 |   } finally {
 220 |     await server.stop();
 221 |   }
 222 | 
 223 |   return port;
 224 | };
 225 | 
 226 | test("adds tools", async () => {
 227 |   await runWithTestServer({
 228 |     server: async () => {
 229 |       const server = new FastMCP({
 230 |         name: "Test",
 231 |         version: "1.0.0",
 232 |       });
 233 | 
 234 |       server.addTool({
 235 |         name: "add",
 236 |         description: "Add two numbers",
 237 |         parameters: z.object({
 238 |           a: z.number(),
 239 |           b: z.number(),
 240 |         }),
 241 |         execute: async (args) => {
 242 |           return String(args.a + args.b);
 243 |         },
 244 |       });
 245 | 
 246 |       return server;
 247 |     },
 248 |     run: async ({ client }) => {
 249 |       expect(await client.listTools()).toEqual({
 250 |         tools: [
 251 |           {
 252 |             name: "add",
 253 |             description: "Add two numbers",
 254 |             inputSchema: {
 255 |               additionalProperties: false,
 256 |               $schema: "http://json-schema.org/draft-07/schema#",
 257 |               type: "object",
 258 |               properties: {
 259 |                 a: { type: "number" },
 260 |                 b: { type: "number" },
 261 |               },
 262 |               required: ["a", "b"],
 263 |             },
 264 |           },
 265 |         ],
 266 |       });
 267 |     },
 268 |   });
 269 | });
 270 | 
 271 | test("calls a tool", async () => {
 272 |   await runWithTestServer({
 273 |     server: async () => {
 274 |       const server = new FastMCP({
 275 |         name: "Test",
 276 |         version: "1.0.0",
 277 |       });
 278 | 
 279 |       server.addTool({
 280 |         name: "add",
 281 |         description: "Add two numbers",
 282 |         parameters: z.object({
 283 |           a: z.number(),
 284 |           b: z.number(),
 285 |         }),
 286 |         execute: async (args) => {
 287 |           return String(args.a + args.b);
 288 |         },
 289 |       });
 290 | 
 291 |       return server;
 292 |     },
 293 |     run: async ({ client }) => {
 294 |       expect(
 295 |         await client.callTool({
 296 |           name: "add",
 297 |           arguments: {
 298 |             a: 1,
 299 |             b: 2,
 300 |           },
 301 |         }),
 302 |       ).toEqual({
 303 |         content: [{ type: "text", text: "3" }],
 304 |       });
 305 |     },
 306 |   });
 307 | });
 308 | 
 309 | test("returns a list", async () => {
 310 |   await runWithTestServer({
 311 |     server: async () => {
 312 |       const server = new FastMCP({
 313 |         name: "Test",
 314 |         version: "1.0.0",
 315 |       });
 316 | 
 317 |       server.addTool({
 318 |         name: "add",
 319 |         description: "Add two numbers",
 320 |         parameters: z.object({
 321 |           a: z.number(),
 322 |           b: z.number(),
 323 |         }),
 324 |         execute: async () => {
 325 |           return {
 326 |             content: [
 327 |               { type: "text", text: "a" },
 328 |               { type: "text", text: "b" },
 329 |             ],
 330 |           };
 331 |         },
 332 |       });
 333 | 
 334 |       return server;
 335 |     },
 336 |     run: async ({ client }) => {
 337 |       expect(
 338 |         await client.callTool({
 339 |           name: "add",
 340 |           arguments: {
 341 |             a: 1,
 342 |             b: 2,
 343 |           },
 344 |         }),
 345 |       ).toEqual({
 346 |         content: [
 347 |           { type: "text", text: "a" },
 348 |           { type: "text", text: "b" },
 349 |         ],
 350 |       });
 351 |     },
 352 |   });
 353 | });
 354 | 
 355 | test("returns an image", async () => {
 356 |   await runWithTestServer({
 357 |     server: async () => {
 358 |       const server = new FastMCP({
 359 |         name: "Test",
 360 |         version: "1.0.0",
 361 |       });
 362 | 
 363 |       server.addTool({
 364 |         name: "add",
 365 |         description: "Add two numbers",
 366 |         parameters: z.object({
 367 |           a: z.number(),
 368 |           b: z.number(),
 369 |         }),
 370 |         execute: async () => {
 371 |           return imageContent({
 372 |             buffer: Buffer.from(
 373 |               "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
 374 |               "base64",
 375 |             ),
 376 |           });
 377 |         },
 378 |       });
 379 | 
 380 |       return server;
 381 |     },
 382 |     run: async ({ client }) => {
 383 |       expect(
 384 |         await client.callTool({
 385 |           name: "add",
 386 |           arguments: {
 387 |             a: 1,
 388 |             b: 2,
 389 |           },
 390 |         }),
 391 |       ).toEqual({
 392 |         content: [
 393 |           {
 394 |             type: "image",
 395 |             data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
 396 |             mimeType: "image/png",
 397 |           },
 398 |         ],
 399 |       });
 400 |     },
 401 |   });
 402 | });
 403 | 
 404 | test("handles UserError errors", async () => {
 405 |   await runWithTestServer({
 406 |     server: async () => {
 407 |       const server = new FastMCP({
 408 |         name: "Test",
 409 |         version: "1.0.0",
 410 |       });
 411 | 
 412 |       server.addTool({
 413 |         name: "add",
 414 |         description: "Add two numbers",
 415 |         parameters: z.object({
 416 |           a: z.number(),
 417 |           b: z.number(),
 418 |         }),
 419 |         execute: async () => {
 420 |           throw new UserError("Something went wrong");
 421 |         },
 422 |       });
 423 | 
 424 |       return server;
 425 |     },
 426 |     run: async ({ client }) => {
 427 |       expect(
 428 |         await client.callTool({
 429 |           name: "add",
 430 |           arguments: {
 431 |             a: 1,
 432 |             b: 2,
 433 |           },
 434 |         }),
 435 |       ).toEqual({
 436 |         content: [{ type: "text", text: "Something went wrong" }],
 437 |         isError: true,
 438 |       });
 439 |     },
 440 |   });
 441 | });
 442 | 
 443 | test("calling an unknown tool throws McpError with MethodNotFound code", async () => {
 444 |   await runWithTestServer({
 445 |     server: async () => {
 446 |       const server = new FastMCP({
 447 |         name: "Test",
 448 |         version: "1.0.0",
 449 |       });
 450 | 
 451 |       return server;
 452 |     },
 453 |     run: async ({ client }) => {
 454 |       try {
 455 |         await client.callTool({
 456 |           name: "add",
 457 |           arguments: {
 458 |             a: 1,
 459 |             b: 2,
 460 |           },
 461 |         });
 462 |       } catch (error) {
 463 |         expect(error).toBeInstanceOf(McpError);
 464 | 
 465 |         // @ts-expect-error - we know that error is an McpError
 466 |         expect(error.code).toBe(ErrorCode.MethodNotFound);
 467 |       }
 468 |     },
 469 |   });
 470 | });
 471 | 
 472 | test("tracks tool progress", async () => {
 473 |   await runWithTestServer({
 474 |     server: async () => {
 475 |       const server = new FastMCP({
 476 |         name: "Test",
 477 |         version: "1.0.0",
 478 |       });
 479 | 
 480 |       server.addTool({
 481 |         name: "add",
 482 |         description: "Add two numbers",
 483 |         parameters: z.object({
 484 |           a: z.number(),
 485 |           b: z.number(),
 486 |         }),
 487 |         execute: async (args, { reportProgress }) => {
 488 |           reportProgress({
 489 |             progress: 0,
 490 |             total: 10,
 491 |           });
 492 | 
 493 |           await delay(100);
 494 | 
 495 |           return String(args.a + args.b);
 496 |         },
 497 |       });
 498 | 
 499 |       return server;
 500 |     },
 501 |     run: async ({ client }) => {
 502 |       const onProgress = vi.fn();
 503 | 
 504 |       await client.callTool(
 505 |         {
 506 |           name: "add",
 507 |           arguments: {
 508 |             a: 1,
 509 |             b: 2,
 510 |           },
 511 |         },
 512 |         undefined,
 513 |         {
 514 |           onprogress: onProgress,
 515 |         },
 516 |       );
 517 | 
 518 |       expect(onProgress).toHaveBeenCalledTimes(1);
 519 |       expect(onProgress).toHaveBeenCalledWith({
 520 |         progress: 0,
 521 |         total: 10,
 522 |       });
 523 |     },
 524 |   });
 525 | });
 526 | 
 527 | test("sets logging levels", async () => {
 528 |   await runWithTestServer({
 529 |     run: async ({ client, session }) => {
 530 |       await client.setLoggingLevel("debug");
 531 | 
 532 |       expect(session.loggingLevel).toBe("debug");
 533 | 
 534 |       await client.setLoggingLevel("info");
 535 | 
 536 |       expect(session.loggingLevel).toBe("info");
 537 |     },
 538 |   });
 539 | });
 540 | 
 541 | test("sends logging messages to the client", async () => {
 542 |   await runWithTestServer({
 543 |     server: async () => {
 544 |       const server = new FastMCP({
 545 |         name: "Test",
 546 |         version: "1.0.0",
 547 |       });
 548 | 
 549 |       server.addTool({
 550 |         name: "add",
 551 |         description: "Add two numbers",
 552 |         parameters: z.object({
 553 |           a: z.number(),
 554 |           b: z.number(),
 555 |         }),
 556 |         execute: async (args, { log }) => {
 557 |           log.debug("debug message", {
 558 |             foo: "bar",
 559 |           });
 560 |           log.error("error message");
 561 |           log.info("info message");
 562 |           log.warn("warn message");
 563 | 
 564 |           return String(args.a + args.b);
 565 |         },
 566 |       });
 567 | 
 568 |       return server;
 569 |     },
 570 |     run: async ({ client }) => {
 571 |       const onLog = vi.fn();
 572 | 
 573 |       client.setNotificationHandler(
 574 |         LoggingMessageNotificationSchema,
 575 |         (message) => {
 576 |           if (message.method === "notifications/message") {
 577 |             onLog({
 578 |               level: message.params.level,
 579 |               ...(message.params.data ?? {}),
 580 |             });
 581 |           }
 582 |         },
 583 |       );
 584 | 
 585 |       await client.callTool({
 586 |         name: "add",
 587 |         arguments: {
 588 |           a: 1,
 589 |           b: 2,
 590 |         },
 591 |       });
 592 | 
 593 |       expect(onLog).toHaveBeenCalledTimes(4);
 594 |       expect(onLog).toHaveBeenNthCalledWith(1, {
 595 |         level: "debug",
 596 |         message: "debug message",
 597 |         context: {
 598 |           foo: "bar",
 599 |         },
 600 |       });
 601 |       expect(onLog).toHaveBeenNthCalledWith(2, {
 602 |         level: "error",
 603 |         message: "error message",
 604 |       });
 605 |       expect(onLog).toHaveBeenNthCalledWith(3, {
 606 |         level: "info",
 607 |         message: "info message",
 608 |       });
 609 |       expect(onLog).toHaveBeenNthCalledWith(4, {
 610 |         level: "warning",
 611 |         message: "warn message",
 612 |       });
 613 |     },
 614 |   });
 615 | });
 616 | 
 617 | test("adds resources", async () => {
 618 |   await runWithTestServer({
 619 |     server: async () => {
 620 |       const server = new FastMCP({
 621 |         name: "Test",
 622 |         version: "1.0.0",
 623 |       });
 624 | 
 625 |       server.addResource({
 626 |         uri: "file:///logs/app.log",
 627 |         name: "Application Logs",
 628 |         mimeType: "text/plain",
 629 |         async load() {
 630 |           return {
 631 |             text: "Example log content",
 632 |           };
 633 |         },
 634 |       });
 635 | 
 636 |       return server;
 637 |     },
 638 |     run: async ({ client }) => {
 639 |       expect(await client.listResources()).toEqual({
 640 |         resources: [
 641 |           {
 642 |             uri: "file:///logs/app.log",
 643 |             name: "Application Logs",
 644 |             mimeType: "text/plain",
 645 |           },
 646 |         ],
 647 |       });
 648 |     },
 649 |   });
 650 | });
 651 | 
 652 | test("clients reads a resource", async () => {
 653 |   await runWithTestServer({
 654 |     server: async () => {
 655 |       const server = new FastMCP({
 656 |         name: "Test",
 657 |         version: "1.0.0",
 658 |       });
 659 | 
 660 |       server.addResource({
 661 |         uri: "file:///logs/app.log",
 662 |         name: "Application Logs",
 663 |         mimeType: "text/plain",
 664 |         async load() {
 665 |           return {
 666 |             text: "Example log content",
 667 |           };
 668 |         },
 669 |       });
 670 | 
 671 |       return server;
 672 |     },
 673 |     run: async ({ client }) => {
 674 |       expect(
 675 |         await client.readResource({
 676 |           uri: "file:///logs/app.log",
 677 |         }),
 678 |       ).toEqual({
 679 |         contents: [
 680 |           {
 681 |             uri: "file:///logs/app.log",
 682 |             name: "Application Logs",
 683 |             text: "Example log content",
 684 |             mimeType: "text/plain",
 685 |           },
 686 |         ],
 687 |       });
 688 |     },
 689 |   });
 690 | });
 691 | 
 692 | test("clients reads a resource that returns multiple resources", async () => {
 693 |   await runWithTestServer({
 694 |     server: async () => {
 695 |       const server = new FastMCP({
 696 |         name: "Test",
 697 |         version: "1.0.0",
 698 |       });
 699 | 
 700 |       server.addResource({
 701 |         uri: "file:///logs/app.log",
 702 |         name: "Application Logs",
 703 |         mimeType: "text/plain",
 704 |         async load() {
 705 |           return [
 706 |             {
 707 |               text: "a",
 708 |             },
 709 |             {
 710 |               text: "b",
 711 |             },
 712 |           ];
 713 |         },
 714 |       });
 715 | 
 716 |       return server;
 717 |     },
 718 |     run: async ({ client }) => {
 719 |       expect(
 720 |         await client.readResource({
 721 |           uri: "file:///logs/app.log",
 722 |         }),
 723 |       ).toEqual({
 724 |         contents: [
 725 |           {
 726 |             uri: "file:///logs/app.log",
 727 |             name: "Application Logs",
 728 |             text: "a",
 729 |             mimeType: "text/plain",
 730 |           },
 731 |           {
 732 |             uri: "file:///logs/app.log",
 733 |             name: "Application Logs",
 734 |             text: "b",
 735 |             mimeType: "text/plain",
 736 |           },
 737 |         ],
 738 |       });
 739 |     },
 740 |   });
 741 | });
 742 | 
 743 | test("adds prompts", async () => {
 744 |   await runWithTestServer({
 745 |     server: async () => {
 746 |       const server = new FastMCP({
 747 |         name: "Test",
 748 |         version: "1.0.0",
 749 |       });
 750 | 
 751 |       server.addPrompt({
 752 |         name: "git-commit",
 753 |         description: "Generate a Git commit message",
 754 |         arguments: [
 755 |           {
 756 |             name: "changes",
 757 |             description: "Git diff or description of changes",
 758 |             required: true,
 759 |           },
 760 |         ],
 761 |         load: async (args) => {
 762 |           return `Generate a concise but descriptive commit message for these changes:\n\n${args.changes}`;
 763 |         },
 764 |       });
 765 | 
 766 |       return server;
 767 |     },
 768 |     run: async ({ client }) => {
 769 |       expect(
 770 |         await client.getPrompt({
 771 |           name: "git-commit",
 772 |           arguments: {
 773 |             changes: "foo",
 774 |           },
 775 |         }),
 776 |       ).toEqual({
 777 |         description: "Generate a Git commit message",
 778 |         messages: [
 779 |           {
 780 |             role: "user",
 781 |             content: {
 782 |               type: "text",
 783 |               text: "Generate a concise but descriptive commit message for these changes:\n\nfoo",
 784 |             },
 785 |           },
 786 |         ],
 787 |       });
 788 | 
 789 |       expect(await client.listPrompts()).toEqual({
 790 |         prompts: [
 791 |           {
 792 |             name: "git-commit",
 793 |             description: "Generate a Git commit message",
 794 |             arguments: [
 795 |               {
 796 |                 name: "changes",
 797 |                 description: "Git diff or description of changes",
 798 |                 required: true,
 799 |               },
 800 |             ],
 801 |           },
 802 |         ],
 803 |       });
 804 |     },
 805 |   });
 806 | });
 807 | 
 808 | test("uses events to notify server of client connect/disconnect", async () => {
 809 |   const port = await getRandomPort();
 810 | 
 811 |   const server = new FastMCP({
 812 |     name: "Test",
 813 |     version: "1.0.0",
 814 |   });
 815 | 
 816 |   const onConnect = vi.fn();
 817 |   const onDisconnect = vi.fn();
 818 | 
 819 |   server.on("connect", onConnect);
 820 |   server.on("disconnect", onDisconnect);
 821 | 
 822 |   await server.start({
 823 |     transportType: "sse",
 824 |     sse: {
 825 |       endpoint: "/sse",
 826 |       port,
 827 |     },
 828 |   });
 829 | 
 830 |   const client = new Client(
 831 |     {
 832 |       name: "example-client",
 833 |       version: "1.0.0",
 834 |     },
 835 |     {
 836 |       capabilities: {},
 837 |     },
 838 |   );
 839 | 
 840 |   const transport = new SSEClientTransport(
 841 |     new URL(`http://localhost:${port}/sse`),
 842 |   );
 843 | 
 844 |   await client.connect(transport);
 845 | 
 846 |   await delay(100);
 847 | 
 848 |   expect(onConnect).toHaveBeenCalledTimes(1);
 849 |   expect(onDisconnect).toHaveBeenCalledTimes(0);
 850 | 
 851 |   expect(server.sessions).toEqual([expect.any(FastMCPSession)]);
 852 | 
 853 |   await client.close();
 854 | 
 855 |   await delay(100);
 856 | 
 857 |   expect(onConnect).toHaveBeenCalledTimes(1);
 858 |   expect(onDisconnect).toHaveBeenCalledTimes(1);
 859 | 
 860 |   await server.stop();
 861 | });
 862 | 
 863 | test("handles multiple clients", async () => {
 864 |   const port = await getRandomPort();
 865 | 
 866 |   const server = new FastMCP({
 867 |     name: "Test",
 868 |     version: "1.0.0",
 869 |   });
 870 | 
 871 |   await server.start({
 872 |     transportType: "sse",
 873 |     sse: {
 874 |       endpoint: "/sse",
 875 |       port,
 876 |     },
 877 |   });
 878 | 
 879 |   const client1 = new Client(
 880 |     {
 881 |       name: "example-client",
 882 |       version: "1.0.0",
 883 |     },
 884 |     {
 885 |       capabilities: {},
 886 |     },
 887 |   );
 888 | 
 889 |   const transport1 = new SSEClientTransport(
 890 |     new URL(`http://localhost:${port}/sse`),
 891 |   );
 892 | 
 893 |   await client1.connect(transport1);
 894 | 
 895 |   const client2 = new Client(
 896 |     {
 897 |       name: "example-client",
 898 |       version: "1.0.0",
 899 |     },
 900 |     {
 901 |       capabilities: {},
 902 |     },
 903 |   );
 904 | 
 905 |   const transport2 = new SSEClientTransport(
 906 |     new URL(`http://localhost:${port}/sse`),
 907 |   );
 908 | 
 909 |   await client2.connect(transport2);
 910 | 
 911 |   await delay(100);
 912 | 
 913 |   expect(server.sessions).toEqual([
 914 |     expect.any(FastMCPSession),
 915 |     expect.any(FastMCPSession),
 916 |   ]);
 917 | 
 918 |   await server.stop();
 919 | });
 920 | 
 921 | test("session knows about client capabilities", async () => {
 922 |   await runWithTestServer({
 923 |     client: async () => {
 924 |       const client = new Client(
 925 |         {
 926 |           name: "example-client",
 927 |           version: "1.0.0",
 928 |         },
 929 |         {
 930 |           capabilities: {
 931 |             roots: {
 932 |               listChanged: true,
 933 |             },
 934 |           },
 935 |         },
 936 |       );
 937 | 
 938 |       client.setRequestHandler(ListRootsRequestSchema, () => {
 939 |         return {
 940 |           roots: [
 941 |             {
 942 |               uri: "file:///home/user/projects/frontend",
 943 |               name: "Frontend Repository",
 944 |             },
 945 |           ],
 946 |         };
 947 |       });
 948 | 
 949 |       return client;
 950 |     },
 951 |     run: async ({ session }) => {
 952 |       expect(session.clientCapabilities).toEqual({
 953 |         roots: {
 954 |           listChanged: true,
 955 |         },
 956 |       });
 957 |     },
 958 |   });
 959 | });
 960 | 
 961 | test("session knows about roots", async () => {
 962 |   await runWithTestServer({
 963 |     client: async () => {
 964 |       const client = new Client(
 965 |         {
 966 |           name: "example-client",
 967 |           version: "1.0.0",
 968 |         },
 969 |         {
 970 |           capabilities: {
 971 |             roots: {
 972 |               listChanged: true,
 973 |             },
 974 |           },
 975 |         },
 976 |       );
 977 | 
 978 |       client.setRequestHandler(ListRootsRequestSchema, () => {
 979 |         return {
 980 |           roots: [
 981 |             {
 982 |               uri: "file:///home/user/projects/frontend",
 983 |               name: "Frontend Repository",
 984 |             },
 985 |           ],
 986 |         };
 987 |       });
 988 | 
 989 |       return client;
 990 |     },
 991 |     run: async ({ session }) => {
 992 |       expect(session.roots).toEqual([
 993 |         {
 994 |           uri: "file:///home/user/projects/frontend",
 995 |           name: "Frontend Repository",
 996 |         },
 997 |       ]);
 998 |     },
 999 |   });
1000 | });
1001 | 
1002 | test("session listens to roots changes", async () => {
1003 |   let clientRoots: Root[] = [
1004 |     {
1005 |       uri: "file:///home/user/projects/frontend",
1006 |       name: "Frontend Repository",
1007 |     },
1008 |   ];
1009 | 
1010 |   await runWithTestServer({
1011 |     client: async () => {
1012 |       const client = new Client(
1013 |         {
1014 |           name: "example-client",
1015 |           version: "1.0.0",
1016 |         },
1017 |         {
1018 |           capabilities: {
1019 |             roots: {
1020 |               listChanged: true,
1021 |             },
1022 |           },
1023 |         },
1024 |       );
1025 | 
1026 |       client.setRequestHandler(ListRootsRequestSchema, () => {
1027 |         return {
1028 |           roots: clientRoots,
1029 |         };
1030 |       });
1031 | 
1032 |       return client;
1033 |     },
1034 |     run: async ({ session, client }) => {
1035 |       expect(session.roots).toEqual([
1036 |         {
1037 |           uri: "file:///home/user/projects/frontend",
1038 |           name: "Frontend Repository",
1039 |         },
1040 |       ]);
1041 | 
1042 |       clientRoots.push({
1043 |         uri: "file:///home/user/projects/backend",
1044 |         name: "Backend Repository",
1045 |       });
1046 | 
1047 |       await client.sendRootsListChanged();
1048 | 
1049 |       const onRootsChanged = vi.fn();
1050 | 
1051 |       session.on("rootsChanged", onRootsChanged);
1052 | 
1053 |       await delay(100);
1054 | 
1055 |       expect(session.roots).toEqual([
1056 |         {
1057 |           uri: "file:///home/user/projects/frontend",
1058 |           name: "Frontend Repository",
1059 |         },
1060 |         {
1061 |           uri: "file:///home/user/projects/backend",
1062 |           name: "Backend Repository",
1063 |         },
1064 |       ]);
1065 | 
1066 |       expect(onRootsChanged).toHaveBeenCalledTimes(1);
1067 |       expect(onRootsChanged).toHaveBeenCalledWith({
1068 |         roots: [
1069 |           {
1070 |             uri: "file:///home/user/projects/frontend",
1071 |             name: "Frontend Repository",
1072 |           },
1073 |           {
1074 |             uri: "file:///home/user/projects/backend",
1075 |             name: "Backend Repository",
1076 |           },
1077 |         ],
1078 |       });
1079 |     },
1080 |   });
1081 | });
1082 | 
1083 | test("session sends pings to the client", async () => {
1084 |   await runWithTestServer({
1085 |     run: async ({ client }) => {
1086 |       const onPing = vi.fn().mockReturnValue({});
1087 | 
1088 |       client.setRequestHandler(PingRequestSchema, onPing);
1089 | 
1090 |       await delay(2000);
1091 | 
1092 |       expect(onPing).toHaveBeenCalledTimes(1);
1093 |     },
1094 |   });
1095 | });
1096 | 
1097 | test("completes prompt arguments", async () => {
1098 |   await runWithTestServer({
1099 |     server: async () => {
1100 |       const server = new FastMCP({
1101 |         name: "Test",
1102 |         version: "1.0.0",
1103 |       });
1104 | 
1105 |       server.addPrompt({
1106 |         name: "countryPoem",
1107 |         description: "Writes a poem about a country",
1108 |         load: async ({ name }) => {
1109 |           return `Hello, ${name}!`;
1110 |         },
1111 |         arguments: [
1112 |           {
1113 |             name: "name",
1114 |             description: "Name of the country",
1115 |             required: true,
1116 |             complete: async (value) => {
1117 |               if (value === "Germ") {
1118 |                 return {
1119 |                   values: ["Germany"],
1120 |                 };
1121 |               }
1122 | 
1123 |               return {
1124 |                 values: [],
1125 |               };
1126 |             },
1127 |           },
1128 |         ],
1129 |       });
1130 | 
1131 |       return server;
1132 |     },
1133 |     run: async ({ client }) => {
1134 |       const response = await client.complete({
1135 |         ref: {
1136 |           type: "ref/prompt",
1137 |           name: "countryPoem",
1138 |         },
1139 |         argument: {
1140 |           name: "name",
1141 |           value: "Germ",
1142 |         },
1143 |       });
1144 | 
1145 |       expect(response).toEqual({
1146 |         completion: {
1147 |           values: ["Germany"],
1148 |         },
1149 |       });
1150 |     },
1151 |   });
1152 | });
1153 | 
1154 | test("adds automatic prompt argument completion when enum is provided", async () => {
1155 |   await runWithTestServer({
1156 |     server: async () => {
1157 |       const server = new FastMCP({
1158 |         name: "Test",
1159 |         version: "1.0.0",
1160 |       });
1161 | 
1162 |       server.addPrompt({
1163 |         name: "countryPoem",
1164 |         description: "Writes a poem about a country",
1165 |         load: async ({ name }) => {
1166 |           return `Hello, ${name}!`;
1167 |         },
1168 |         arguments: [
1169 |           {
1170 |             name: "name",
1171 |             description: "Name of the country",
1172 |             required: true,
1173 |             enum: ["Germany", "France", "Italy"],
1174 |           },
1175 |         ],
1176 |       });
1177 | 
1178 |       return server;
1179 |     },
1180 |     run: async ({ client }) => {
1181 |       const response = await client.complete({
1182 |         ref: {
1183 |           type: "ref/prompt",
1184 |           name: "countryPoem",
1185 |         },
1186 |         argument: {
1187 |           name: "name",
1188 |           value: "Germ",
1189 |         },
1190 |       });
1191 | 
1192 |       expect(response).toEqual({
1193 |         completion: {
1194 |           values: ["Germany"],
1195 |           total: 1,
1196 |         },
1197 |       });
1198 |     },
1199 |   });
1200 | });
1201 | 
1202 | test("completes template resource arguments", async () => {
1203 |   await runWithTestServer({
1204 |     server: async () => {
1205 |       const server = new FastMCP({
1206 |         name: "Test",
1207 |         version: "1.0.0",
1208 |       });
1209 | 
1210 |       server.addResourceTemplate({
1211 |         uriTemplate: "issue:///{issueId}",
1212 |         name: "Issue",
1213 |         mimeType: "text/plain",
1214 |         arguments: [
1215 |           {
1216 |             name: "issueId",
1217 |             description: "ID of the issue",
1218 |             complete: async (value) => {
1219 |               if (value === "123") {
1220 |                 return {
1221 |                   values: ["123456"],
1222 |                 };
1223 |               }
1224 | 
1225 |               return {
1226 |                 values: [],
1227 |               };
1228 |             },
1229 |           },
1230 |         ],
1231 |         load: async ({ issueId }) => {
1232 |           return {
1233 |             text: `Issue ${issueId}`,
1234 |           };
1235 |         },
1236 |       });
1237 | 
1238 |       return server;
1239 |     },
1240 |     run: async ({ client }) => {
1241 |       const response = await client.complete({
1242 |         ref: {
1243 |           type: "ref/resource",
1244 |           uri: "issue:///{issueId}",
1245 |         },
1246 |         argument: {
1247 |           name: "issueId",
1248 |           value: "123",
1249 |         },
1250 |       });
1251 | 
1252 |       expect(response).toEqual({
1253 |         completion: {
1254 |           values: ["123456"],
1255 |         },
1256 |       });
1257 |     },
1258 |   });
1259 | });
1260 | 
1261 | test("lists resource templates", async () => {
1262 |   await runWithTestServer({
1263 |     server: async () => {
1264 |       const server = new FastMCP({
1265 |         name: "Test",
1266 |         version: "1.0.0",
1267 |       });
1268 | 
1269 |       server.addResourceTemplate({
1270 |         uriTemplate: "file:///logs/{name}.log",
1271 |         name: "Application Logs",
1272 |         mimeType: "text/plain",
1273 |         arguments: [
1274 |           {
1275 |             name: "name",
1276 |             description: "Name of the log",
1277 |             required: true,
1278 |           },
1279 |         ],
1280 |         load: async ({ name }) => {
1281 |           return {
1282 |             text: `Example log content for ${name}`,
1283 |           };
1284 |         },
1285 |       });
1286 | 
1287 |       return server;
1288 |     },
1289 |     run: async ({ client }) => {
1290 |       expect(await client.listResourceTemplates()).toEqual({
1291 |         resourceTemplates: [
1292 |           {
1293 |             name: "Application Logs",
1294 |             uriTemplate: "file:///logs/{name}.log",
1295 |           },
1296 |         ],
1297 |       });
1298 |     },
1299 |   });
1300 | });
1301 | 
1302 | test("clients reads a resource accessed via a resource template", async () => {
1303 |   const loadSpy = vi.fn((_args) => {
1304 |     return {
1305 |       text: "Example log content",
1306 |     };
1307 |   });
1308 | 
1309 |   await runWithTestServer({
1310 |     server: async () => {
1311 |       const server = new FastMCP({
1312 |         name: "Test",
1313 |         version: "1.0.0",
1314 |       });
1315 | 
1316 |       server.addResourceTemplate({
1317 |         uriTemplate: "file:///logs/{name}.log",
1318 |         name: "Application Logs",
1319 |         mimeType: "text/plain",
1320 |         arguments: [
1321 |           {
1322 |             name: "name",
1323 |             description: "Name of the log",
1324 |           },
1325 |         ],
1326 |         async load(args) {
1327 |           return loadSpy(args);
1328 |         },
1329 |       });
1330 | 
1331 |       return server;
1332 |     },
1333 |     run: async ({ client }) => {
1334 |       expect(
1335 |         await client.readResource({
1336 |           uri: "file:///logs/app.log",
1337 |         }),
1338 |       ).toEqual({
1339 |         contents: [
1340 |           {
1341 |             uri: "file:///logs/app.log",
1342 |             name: "Application Logs",
1343 |             text: "Example log content",
1344 |             mimeType: "text/plain",
1345 |           },
1346 |         ],
1347 |       });
1348 | 
1349 |       expect(loadSpy).toHaveBeenCalledWith({
1350 |         name: "app",
1351 |       });
1352 |     },
1353 |   });
1354 | });
1355 | 
1356 | test("makes a sampling request", async () => {
1357 |   const onMessageRequest = vi.fn(() => {
1358 |     return {
1359 |       model: "gpt-3.5-turbo",
1360 |       role: "assistant",
1361 |       content: {
1362 |         type: "text",
1363 |         text: "The files are in the current directory.",
1364 |       },
1365 |     };
1366 |   });
1367 | 
1368 |   await runWithTestServer({
1369 |     client: async () => {
1370 |       const client = new Client(
1371 |         {
1372 |           name: "example-client",
1373 |           version: "1.0.0",
1374 |         },
1375 |         {
1376 |           capabilities: {
1377 |             sampling: {},
1378 |           },
1379 |         },
1380 |       );
1381 |       return client;
1382 |     },
1383 |     run: async ({ client, session }) => {
1384 |       client.setRequestHandler(CreateMessageRequestSchema, onMessageRequest);
1385 | 
1386 |       const response = await session.requestSampling({
1387 |         messages: [
1388 |           {
1389 |             role: "user",
1390 |             content: {
1391 |               type: "text",
1392 |               text: "What files are in the current directory?",
1393 |             },
1394 |           },
1395 |         ],
1396 |         systemPrompt: "You are a helpful file system assistant.",
1397 |         includeContext: "thisServer",
1398 |         maxTokens: 100,
1399 |       });
1400 | 
1401 |       expect(response).toEqual({
1402 |         model: "gpt-3.5-turbo",
1403 |         role: "assistant",
1404 |         content: {
1405 |           type: "text",
1406 |           text: "The files are in the current directory.",
1407 |         },
1408 |       });
1409 | 
1410 |       expect(onMessageRequest).toHaveBeenCalledTimes(1);
1411 |     },
1412 |   });
1413 | });
1414 | 
1415 | test("throws ErrorCode.InvalidParams if tool parameters do not match zod schema", async () => {
1416 |   await runWithTestServer({
1417 |     server: async () => {
1418 |       const server = new FastMCP({
1419 |         name: "Test",
1420 |         version: "1.0.0",
1421 |       });
1422 | 
1423 |       server.addTool({
1424 |         name: "add",
1425 |         description: "Add two numbers",
1426 |         parameters: z.object({
1427 |           a: z.number(),
1428 |           b: z.number(),
1429 |         }),
1430 |         execute: async (args) => {
1431 |           return String(args.a + args.b);
1432 |         },
1433 |       });
1434 | 
1435 |       return server;
1436 |     },
1437 |     run: async ({ client }) => {
1438 |       try {
1439 |         await client.callTool({
1440 |           name: "add",
1441 |           arguments: {
1442 |             a: 1,
1443 |             b: "invalid",
1444 |           },
1445 |         });
1446 |       } catch (error) {
1447 |         expect(error).toBeInstanceOf(McpError);
1448 | 
1449 |         // @ts-expect-error - we know that error is an McpError
1450 |         expect(error.code).toBe(ErrorCode.InvalidParams);
1451 | 
1452 |         // @ts-expect-error - we know that error is an McpError
1453 |         expect(error.message).toBe("MCP error -32602: MCP error -32602: Invalid add parameters");
1454 |       }
1455 |     },
1456 |   });
1457 | });
1458 | 
1459 | test("server remains usable after InvalidParams error", async () => {
1460 |   await runWithTestServer({
1461 |     server: async () => {
1462 |       const server = new FastMCP({
1463 |         name: "Test",
1464 |         version: "1.0.0",
1465 |       });
1466 | 
1467 |       server.addTool({
1468 |         name: "add",
1469 |         description: "Add two numbers",
1470 |         parameters: z.object({
1471 |           a: z.number(),
1472 |           b: z.number(),
1473 |         }),
1474 |         execute: async (args) => {
1475 |           return String(args.a + args.b);
1476 |         },
1477 |       });
1478 | 
1479 |       return server;
1480 |     },
1481 |     run: async ({ client }) => {
1482 |       try {
1483 |         await client.callTool({
1484 |           name: "add",
1485 |           arguments: {
1486 |             a: 1,
1487 |             b: "invalid",
1488 |           },
1489 |         });
1490 |       } catch (error) {
1491 |         expect(error).toBeInstanceOf(McpError);
1492 | 
1493 |         // @ts-expect-error - we know that error is an McpError
1494 |         expect(error.code).toBe(ErrorCode.InvalidParams);
1495 | 
1496 |         // @ts-expect-error - we know that error is an McpError
1497 |         expect(error.message).toBe("MCP error -32602: MCP error -32602: Invalid add parameters");
1498 |       }
1499 | 
1500 |       expect(
1501 |         await client.callTool({
1502 |           name: "add",
1503 |           arguments: {
1504 |             a: 1,
1505 |             b: 2,
1506 |           },
1507 |         }),
1508 |       ).toEqual({
1509 |         content: [{ type: "text", text: "3" }],
1510 |       });
1511 |     },
1512 |   });
1513 | });
1514 | 
1515 | test("allows new clients to connect after a client disconnects", async () => {
1516 |   const port = await getRandomPort();
1517 | 
1518 |   const server = new FastMCP({
1519 |     name: "Test",
1520 |     version: "1.0.0",
1521 |   });
1522 | 
1523 |   server.addTool({
1524 |     name: "add",
1525 |     description: "Add two numbers",
1526 |     parameters: z.object({
1527 |       a: z.number(),
1528 |       b: z.number(),
1529 |     }),
1530 |     execute: async (args) => {
1531 |       return String(args.a + args.b);
1532 |     },
1533 |   });
1534 | 
1535 |   await server.start({
1536 |     transportType: "sse",
1537 |     sse: {
1538 |       endpoint: "/sse",
1539 |       port,
1540 |     },
1541 |   });
1542 | 
1543 |   const client1 = new Client(
1544 |     {
1545 |       name: "example-client",
1546 |       version: "1.0.0",
1547 |     },
1548 |     {
1549 |       capabilities: {},
1550 |     },
1551 |   );
1552 | 
1553 |   const transport1 = new SSEClientTransport(
1554 |     new URL(`http://localhost:${port}/sse`),
1555 |   );
1556 | 
1557 |   await client1.connect(transport1);
1558 | 
1559 |   expect(
1560 |     await client1.callTool({
1561 |       name: "add",
1562 |       arguments: {
1563 |         a: 1,
1564 |         b: 2,
1565 |       },
1566 |     }),
1567 |   ).toEqual({
1568 |     content: [{ type: "text", text: "3" }],
1569 |   });
1570 | 
1571 |   await client1.close();
1572 | 
1573 |   const client2 = new Client(
1574 |     {
1575 |       name: "example-client",
1576 |       version: "1.0.0",
1577 |     },
1578 |     {
1579 |       capabilities: {},
1580 |     },
1581 |   );
1582 | 
1583 |   const transport2 = new SSEClientTransport(
1584 |     new URL(`http://localhost:${port}/sse`),
1585 |   );
1586 | 
1587 |   await client2.connect(transport2);
1588 | 
1589 |   expect(
1590 |     await client2.callTool({
1591 |       name: "add",
1592 |       arguments: {
1593 |         a: 1,
1594 |         b: 2,
1595 |       },
1596 |     }),
1597 |   ).toEqual({
1598 |     content: [{ type: "text", text: "3" }],
1599 |   });
1600 | 
1601 |   await client2.close();
1602 | 
1603 |   await server.stop();
1604 | });
1605 | 
1606 | test("able to close server immediately after starting it", async () => {
1607 |   const port = await getRandomPort();
1608 | 
1609 |   const server = new FastMCP({
1610 |     name: "Test",
1611 |     version: "1.0.0",
1612 |   });
1613 | 
1614 |   await server.start({
1615 |     transportType: "sse",
1616 |     sse: {
1617 |       endpoint: "/sse",
1618 |       port,
1619 |     },
1620 |   });
1621 | 
1622 |   // We were previously not waiting for the server to start.
1623 |   // Therefore, this would have caused error 'Server is not running.'.
1624 |   await server.stop();
1625 | });
1626 | 
1627 | test("closing event source does not produce error", async () => {
1628 |   const port = await getRandomPort();
1629 | 
1630 |   const server = new FastMCP({
1631 |     name: "Test",
1632 |     version: "1.0.0",
1633 |   });
1634 | 
1635 |   server.addTool({
1636 |     name: "add",
1637 |     description: "Add two numbers",
1638 |     parameters: z.object({
1639 |       a: z.number(),
1640 |       b: z.number(),
1641 |     }),
1642 |     execute: async (args) => {
1643 |       return String(args.a + args.b);
1644 |     },
1645 |   });
1646 | 
1647 |   await server.start({
1648 |     transportType: "sse",
1649 |     sse: {
1650 |       endpoint: "/sse",
1651 |       port,
1652 |     },
1653 |   });
1654 | 
1655 |   const eventSource = await new Promise<EventSourceClient>((onMessage) => {
1656 |     const eventSource = createEventSource({
1657 |       onConnect: () => {
1658 |         console.info('connected');
1659 |       },
1660 |       onDisconnect: () => {
1661 |         console.info('disconnected');
1662 |       },
1663 |       onMessage: () => {
1664 |         onMessage(eventSource);
1665 |       },
1666 |       url: `http://127.0.0.1:${port}/sse`,
1667 |     });
1668 |   });
1669 | 
1670 |   expect(eventSource.readyState).toBe('open');
1671 | 
1672 |   eventSource.close();
1673 | 
1674 |   // We were getting unhandled error 'Not connected'
1675 |   // https://github.com/punkpeye/mcp-proxy/commit/62cf27d5e3dfcbc353e8d03c7714a62c37177b52
1676 |   await delay(1000);
1677 | 
1678 |   await server.stop();
1679 | });
1680 | 
1681 | test("provides auth to tools", async () => {
1682 |   const port = await getRandomPort();
1683 | 
1684 |   const authenticate = vi.fn(async () => {
1685 |     return {
1686 |       id: 1,
1687 |     };
1688 |   });
1689 | 
1690 |   const server = new FastMCP<{id: number}>({
1691 |     name: "Test",
1692 |     version: "1.0.0",
1693 |     authenticate,
1694 |   });
1695 | 
1696 |   const execute = vi.fn(async (args) => {
1697 |     return String(args.a + args.b);
1698 |   });
1699 | 
1700 |   server.addTool({
1701 |     name: "add",
1702 |     description: "Add two numbers",
1703 |     parameters: z.object({
1704 |       a: z.number(),
1705 |       b: z.number(),
1706 |     }),
1707 |     execute,
1708 |   });
1709 | 
1710 |   await server.start({
1711 |     transportType: "sse",
1712 |     sse: {
1713 |       endpoint: "/sse",
1714 |       port,
1715 |     },
1716 |   });
1717 | 
1718 |   const client = new Client(
1719 |     {
1720 |       name: "example-client",
1721 |       version: "1.0.0",
1722 |     },
1723 |     {
1724 |       capabilities: {},
1725 |     },
1726 |   );
1727 | 
1728 |   const transport = new SSEClientTransport(
1729 |     new URL(`http://localhost:${port}/sse`),
1730 |     {
1731 |       eventSourceInit: {
1732 |         fetch: async (url, init) => {
1733 |           return fetch(url, {
1734 |             ...init,
1735 |             headers: {
1736 |               ...init?.headers,
1737 |               "x-api-key": "123",
1738 |             },
1739 |           });
1740 |         },
1741 |       },
1742 |     },
1743 |   );
1744 | 
1745 |   await client.connect(transport);
1746 | 
1747 |   expect(authenticate, "authenticate should have been called").toHaveBeenCalledTimes(1);
1748 | 
1749 |   expect(
1750 |     await client.callTool({
1751 |       name: "add",
1752 |       arguments: {
1753 |         a: 1,
1754 |         b: 2,
1755 |       },
1756 |     }),
1757 |   ).toEqual({
1758 |     content: [{ type: "text", text: "3" }],
1759 |   });
1760 | 
1761 |   expect(execute, "execute should have been called").toHaveBeenCalledTimes(1);
1762 | 
1763 |   expect(execute).toHaveBeenCalledWith({
1764 |     a: 1,
1765 |     b: 2,
1766 |   }, {
1767 |     log: {
1768 |       debug: expect.any(Function),
1769 |       error: expect.any(Function),
1770 |       info: expect.any(Function),
1771 |       warn: expect.any(Function),
1772 |     },
1773 |     reportProgress: expect.any(Function),
1774 |     session: { id: 1 },
1775 |   });
1776 | });
1777 | 
1778 | test("blocks unauthorized requests", async () => {
1779 |   const port = await getRandomPort();
1780 | 
1781 |   const server = new FastMCP<{id: number}>({
1782 |     name: "Test",
1783 |     version: "1.0.0",
1784 |     authenticate: async () => {
1785 |       throw new Response(null, {
1786 |         status: 401,
1787 |         statusText: "Unauthorized",
1788 |       });
1789 |     },
1790 |   });
1791 | 
1792 |   await server.start({
1793 |     transportType: "sse",
1794 |     sse: {
1795 |       endpoint: "/sse",
1796 |       port,
1797 |     },
1798 |   });
1799 | 
1800 |   const client = new Client(
1801 |     {
1802 |       name: "example-client",
1803 |       version: "1.0.0",
1804 |     },
1805 |     {
1806 |       capabilities: {},
1807 |     },
1808 |   );
1809 | 
1810 |   const transport = new SSEClientTransport(
1811 |     new URL(`http://localhost:${port}/sse`),
1812 |   );
1813 | 
1814 |   expect(async () => {
1815 |     await client.connect(transport);
1816 |   }).rejects.toThrow("SSE error: Non-200 status code (401)");
1817 | });
1818 | 
1819 | 
1820 | ---
1821 | File: /src/FastMCP.ts
1822 | ---
1823 | 
1824 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1825 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1826 | import {
1827 |   CallToolRequestSchema,
1828 |   ClientCapabilities,
1829 |   CompleteRequestSchema,
1830 |   CreateMessageRequestSchema,
1831 |   ErrorCode,
1832 |   GetPromptRequestSchema,
1833 |   ListPromptsRequestSchema,
1834 |   ListResourcesRequestSchema,
1835 |   ListResourceTemplatesRequestSchema,
1836 |   ListToolsRequestSchema,
1837 |   McpError,
1838 |   ReadResourceRequestSchema,
1839 |   Root,
1840 |   RootsListChangedNotificationSchema,
1841 |   ServerCapabilities,
1842 |   SetLevelRequestSchema,
1843 | } from "@modelcontextprotocol/sdk/types.js";
1844 | import { zodToJsonSchema } from "zod-to-json-schema";
1845 | import { z } from "zod";
1846 | import { setTimeout as delay } from "timers/promises";
1847 | import { readFile } from "fs/promises";
1848 | import { fileTypeFromBuffer } from "file-type";
1849 | import { StrictEventEmitter } from "strict-event-emitter-types";
1850 | import { EventEmitter } from "events";
1851 | import Fuse from "fuse.js";
1852 | import { startSSEServer } from "mcp-proxy";
1853 | import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
1854 | import parseURITemplate from "uri-templates";
1855 | import http from "http";
1856 | import {
1857 |   fetch
1858 | } from "undici";
1859 | 
1860 | export type SSEServer = {
1861 |   close: () => Promise<void>;
1862 | };
1863 | 
1864 | type FastMCPEvents<T extends FastMCPSessionAuth> = {
1865 |   connect: (event: { session: FastMCPSession<T> }) => void;
1866 |   disconnect: (event: { session: FastMCPSession<T> }) => void;
1867 | };
1868 | 
1869 | type FastMCPSessionEvents = {
1870 |   rootsChanged: (event: { roots: Root[] }) => void;
1871 |   error: (event: { error: Error }) => void;
1872 | };
1873 | 
1874 | /**
1875 |  * Generates an image content object from a URL, file path, or buffer.
1876 |  */
1877 | export const imageContent = async (
1878 |   input: { url: string } | { path: string } | { buffer: Buffer },
1879 | ): Promise<ImageContent> => {
1880 |   let rawData: Buffer;
1881 | 
1882 |   if ("url" in input) {
1883 |     const response = await fetch(input.url);
1884 | 
1885 |     if (!response.ok) {
1886 |       throw new Error(`Failed to fetch image from URL: ${response.statusText}`);
1887 |     }
1888 | 
1889 |     rawData = Buffer.from(await response.arrayBuffer());
1890 |   } else if ("path" in input) {
1891 |     rawData = await readFile(input.path);
1892 |   } else if ("buffer" in input) {
1893 |     rawData = input.buffer;
1894 |   } else {
1895 |     throw new Error(
1896 |       "Invalid input: Provide a valid 'url', 'path', or 'buffer'",
1897 |     );
1898 |   }
1899 | 
1900 |   const mimeType = await fileTypeFromBuffer(rawData);
1901 | 
1902 |   const base64Data = rawData.toString("base64");
1903 | 
1904 |   return {
1905 |     type: "image",
1906 |     data: base64Data,
1907 |     mimeType: mimeType?.mime ?? "image/png",
1908 |   } as const;
1909 | };
1910 | 
1911 | abstract class FastMCPError extends Error {
1912 |   public constructor(message?: string) {
1913 |     super(message);
1914 |     this.name = new.target.name;
1915 |   }
1916 | }
1917 | 
1918 | type Extra = unknown;
1919 | 
1920 | type Extras = Record<string, Extra>;
1921 | 
1922 | export class UnexpectedStateError extends FastMCPError {
1923 |   public extras?: Extras;
1924 | 
1925 |   public constructor(message: string, extras?: Extras) {
1926 |     super(message);
1927 |     this.name = new.target.name;
1928 |     this.extras = extras;
1929 |   }
1930 | }
1931 | 
1932 | /**
1933 |  * An error that is meant to be surfaced to the user.
1934 |  */
1935 | export class UserError extends UnexpectedStateError {}
1936 | 
1937 | type ToolParameters = z.ZodTypeAny;
1938 | 
1939 | type Literal = boolean | null | number | string | undefined;
1940 | 
1941 | type SerializableValue =
1942 |   | Literal
1943 |   | SerializableValue[]
1944 |   | { [key: string]: SerializableValue };
1945 | 
1946 | type Progress = {
1947 |   /**
1948 |    * The progress thus far. This should increase every time progress is made, even if the total is unknown.
1949 |    */
1950 |   progress: number;
1951 |   /**
1952 |    * Total number of items to process (or total progress required), if known.
1953 |    */
1954 |   total?: number;
1955 | };
1956 | 
1957 | type Context<T extends FastMCPSessionAuth> = {
1958 |   session: T | undefined;
1959 |   reportProgress: (progress: Progress) => Promise<void>;
1960 |   log: {
1961 |     debug: (message: string, data?: SerializableValue) => void;
1962 |     error: (message: string, data?: SerializableValue) => void;
1963 |     info: (message: string, data?: SerializableValue) => void;
1964 |     warn: (message: string, data?: SerializableValue) => void;
1965 |   };
1966 | };
1967 | 
1968 | type TextContent = {
1969 |   type: "text";
1970 |   text: string;
1971 | };
1972 | 
1973 | const TextContentZodSchema = z
1974 |   .object({
1975 |     type: z.literal("text"),
1976 |     /**
1977 |      * The text content of the message.
1978 |      */
1979 |     text: z.string(),
1980 |   })
1981 |   .strict() satisfies z.ZodType<TextContent>;
1982 | 
1983 | type ImageContent = {
1984 |   type: "image";
1985 |   data: string;
1986 |   mimeType: string;
1987 | };
1988 | 
1989 | const ImageContentZodSchema = z
1990 |   .object({
1991 |     type: z.literal("image"),
1992 |     /**
1993 |      * The base64-encoded image data.
1994 |      */
1995 |     data: z.string().base64(),
1996 |     /**
1997 |      * The MIME type of the image. Different providers may support different image types.
1998 |      */
1999 |     mimeType: z.string(),
2000 |   })
2001 |   .strict() satisfies z.ZodType<ImageContent>;
2002 | 
2003 | type Content = TextContent | ImageContent;
2004 | 
2005 | const ContentZodSchema = z.discriminatedUnion("type", [
2006 |   TextContentZodSchema,
2007 |   ImageContentZodSchema,
2008 | ]) satisfies z.ZodType<Content>;
2009 | 
2010 | type ContentResult = {
2011 |   content: Content[];
2012 |   isError?: boolean;
2013 | };
2014 | 
2015 | const ContentResultZodSchema = z
2016 |   .object({
2017 |     content: ContentZodSchema.array(),
2018 |     isError: z.boolean().optional(),
2019 |   })
2020 |   .strict() satisfies z.ZodType<ContentResult>;
2021 | 
2022 | type Completion = {
2023 |   values: string[];
2024 |   total?: number;
2025 |   hasMore?: boolean;
2026 | };
2027 | 
2028 | /**
2029 |  * https://github.com/modelcontextprotocol/typescript-sdk/blob/3164da64d085ec4e022ae881329eee7b72f208d4/src/types.ts#L983-L1003
2030 |  */
2031 | const CompletionZodSchema = z.object({
2032 |   /**
2033 |    * An array of completion values. Must not exceed 100 items.
2034 |    */
2035 |   values: z.array(z.string()).max(100),
2036 |   /**
2037 |    * The total number of completion options available. This can exceed the number of values actually sent in the response.
2038 |    */
2039 |   total: z.optional(z.number().int()),
2040 |   /**
2041 |    * Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown.
2042 |    */
2043 |   hasMore: z.optional(z.boolean()),
2044 | }) satisfies z.ZodType<Completion>;
2045 | 
2046 | type Tool<T extends FastMCPSessionAuth, Params extends ToolParameters = ToolParameters> = {
2047 |   name: string;
2048 |   description?: string;
2049 |   parameters?: Params;
2050 |   execute: (
2051 |     args: z.infer<Params>,
2052 |     context: Context<T>,
2053 |   ) => Promise<string | ContentResult | TextContent | ImageContent>;
2054 | };
2055 | 
2056 | type ResourceResult =
2057 |   | {
2058 |       text: string;
2059 |     }
2060 |   | {
2061 |       blob: string;
2062 |     };
2063 | 
2064 | type InputResourceTemplateArgument = Readonly<{
2065 |   name: string;
2066 |   description?: string;
2067 |   complete?: ArgumentValueCompleter;
2068 | }>;
2069 | 
2070 | type ResourceTemplateArgument = Readonly<{
2071 |   name: string;
2072 |   description?: string;
2073 |   complete?: ArgumentValueCompleter;
2074 | }>;
2075 | 
2076 | type ResourceTemplate<
2077 |   Arguments extends ResourceTemplateArgument[] = ResourceTemplateArgument[],
2078 | > = {
2079 |   uriTemplate: string;
2080 |   name: string;
2081 |   description?: string;
2082 |   mimeType?: string;
2083 |   arguments: Arguments;
2084 |   complete?: (name: string, value: string) => Promise<Completion>;
2085 |   load: (
2086 |     args: ResourceTemplateArgumentsToObject<Arguments>,
2087 |   ) => Promise<ResourceResult>;
2088 | };
2089 | 
2090 | type ResourceTemplateArgumentsToObject<T extends { name: string }[]> = {
2091 |   [K in T[number]["name"]]: string;
2092 | };
2093 | 
2094 | type InputResourceTemplate<
2095 |   Arguments extends ResourceTemplateArgument[] = ResourceTemplateArgument[],
2096 | > = {
2097 |   uriTemplate: string;
2098 |   name: string;
2099 |   description?: string;
2100 |   mimeType?: string;
2101 |   arguments: Arguments;
2102 |   load: (
2103 |     args: ResourceTemplateArgumentsToObject<Arguments>,
2104 |   ) => Promise<ResourceResult>;
2105 | };
2106 | 
2107 | type Resource = {
2108 |   uri: string;
2109 |   name: string;
2110 |   description?: string;
2111 |   mimeType?: string;
2112 |   load: () => Promise<ResourceResult | ResourceResult[]>;
2113 |   complete?: (name: string, value: string) => Promise<Completion>;
2114 | };
2115 | 
2116 | type ArgumentValueCompleter = (value: string) => Promise<Completion>;
2117 | 
2118 | type InputPromptArgument = Readonly<{
2119 |   name: string;
2120 |   description?: string;
2121 |   required?: boolean;
2122 |   complete?: ArgumentValueCompleter;
2123 |   enum?: string[];
2124 | }>;
2125 | 
2126 | type PromptArgumentsToObject<T extends { name: string; required?: boolean }[]> =
2127 |   {
2128 |     [K in T[number]["name"]]: Extract<
2129 |       T[number],
2130 |       { name: K }
2131 |     >["required"] extends true
2132 |       ? string
2133 |       : string | undefined;
2134 |   };
2135 | 
2136 | type InputPrompt<
2137 |   Arguments extends InputPromptArgument[] = InputPromptArgument[],
2138 |   Args = PromptArgumentsToObject<Arguments>,
2139 | > = {
2140 |   name: string;
2141 |   description?: string;
2142 |   arguments?: InputPromptArgument[];
2143 |   load: (args: Args) => Promise<string>;
2144 | };
2145 | 
2146 | type PromptArgument = Readonly<{
2147 |   name: string;
2148 |   description?: string;
2149 |   required?: boolean;
2150 |   complete?: ArgumentValueCompleter;
2151 |   enum?: string[];
2152 | }>;
2153 | 
2154 | type Prompt<
2155 |   Arguments extends PromptArgument[] = PromptArgument[],
2156 |   Args = PromptArgumentsToObject<Arguments>,
2157 | > = {
2158 |   arguments?: PromptArgument[];
2159 |   complete?: (name: string, value: string) => Promise<Completion>;
2160 |   description?: string;
2161 |   load: (args: Args) => Promise<string>;
2162 |   name: string;
2163 | };
2164 | 
2165 | type ServerOptions<T extends FastMCPSessionAuth> = {
2166 |   name: string;
2167 |   version: `${number}.${number}.${number}`;
2168 |   authenticate?: Authenticate<T>;
2169 | };
2170 | 
2171 | type LoggingLevel =
2172 |   | "debug"
2173 |   | "info"
2174 |   | "notice"
2175 |   | "warning"
2176 |   | "error"
2177 |   | "critical"
2178 |   | "alert"
2179 |   | "emergency";
2180 | 
2181 | const FastMCPSessionEventEmitterBase: {
2182 |   new (): StrictEventEmitter<EventEmitter, FastMCPSessionEvents>;
2183 | } = EventEmitter;
2184 | 
2185 | class FastMCPSessionEventEmitter extends FastMCPSessionEventEmitterBase {}
2186 | 
2187 | type SamplingResponse = {
2188 |   model: string;
2189 |   stopReason?: "endTurn" | "stopSequence" | "maxTokens" | string;
2190 |   role: "user" | "assistant";
2191 |   content: TextContent | ImageContent;
2192 | };
2193 | 
2194 | type FastMCPSessionAuth = Record<string, unknown> | undefined;
2195 | 
2196 | export class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth> extends FastMCPSessionEventEmitter {
2197 |   #capabilities: ServerCapabilities = {};
2198 |   #clientCapabilities?: ClientCapabilities;
2199 |   #loggingLevel: LoggingLevel = "info";
2200 |   #prompts: Prompt[] = [];
2201 |   #resources: Resource[] = [];
2202 |   #resourceTemplates: ResourceTemplate[] = [];
2203 |   #roots: Root[] = [];
2204 |   #server: Server;
2205 |   #auth: T | undefined;
2206 | 
2207 |   constructor({
2208 |     auth,
2209 |     name,
2210 |     version,
2211 |     tools,
2212 |     resources,
2213 |     resourcesTemplates,
2214 |     prompts,
2215 |   }: {
2216 |     auth?: T;
2217 |     name: string;
2218 |     version: string;
2219 |     tools: Tool<T>[];
2220 |     resources: Resource[];
2221 |     resourcesTemplates: InputResourceTemplate[];
2222 |     prompts: Prompt[];
2223 |   }) {
2224 |     super();
2225 | 
2226 |     this.#auth = auth;
2227 | 
2228 |     if (tools.length) {
2229 |       this.#capabilities.tools = {};
2230 |     }
2231 | 
2232 |     if (resources.length || resourcesTemplates.length) {
2233 |       this.#capabilities.resources = {};
2234 |     }
2235 | 
2236 |     if (prompts.length) {
2237 |       for (const prompt of prompts) {
2238 |         this.addPrompt(prompt);
2239 |       }
2240 | 
2241 |       this.#capabilities.prompts = {};
2242 |     }
2243 | 
2244 |     this.#capabilities.logging = {};
2245 | 
2246 |     this.#server = new Server(
2247 |       { name: name, version: version },
2248 |       { capabilities: this.#capabilities },
2249 |     );
2250 | 
2251 |     this.setupErrorHandling();
2252 |     this.setupLoggingHandlers();
2253 |     this.setupRootsHandlers();
2254 |     this.setupCompleteHandlers();
2255 | 
2256 |     if (tools.length) {
2257 |       this.setupToolHandlers(tools);
2258 |     }
2259 | 
2260 |     if (resources.length || resourcesTemplates.length) {
2261 |       for (const resource of resources) {
2262 |         this.addResource(resource);
2263 |       }
2264 | 
2265 |       this.setupResourceHandlers(resources);
2266 | 
2267 |       if (resourcesTemplates.length) {
2268 |         for (const resourceTemplate of resourcesTemplates) {
2269 |           this.addResourceTemplate(resourceTemplate);
2270 |         }
2271 | 
2272 |         this.setupResourceTemplateHandlers(resourcesTemplates);
2273 |       }
2274 |     }
2275 | 
2276 |     if (prompts.length) {
2277 |       this.setupPromptHandlers(prompts);
2278 |     }
2279 |   }
2280 | 
2281 |   private addResource(inputResource: Resource) {
2282 |     this.#resources.push(inputResource);
2283 |   }
2284 | 
2285 |   private addResourceTemplate(inputResourceTemplate: InputResourceTemplate) {
2286 |     const completers: Record<string, ArgumentValueCompleter> = {};
2287 | 
2288 |     for (const argument of inputResourceTemplate.arguments ?? []) {
2289 |       if (argument.complete) {
2290 |         completers[argument.name] = argument.complete;
2291 |       }
2292 |     }
2293 | 
2294 |     const resourceTemplate = {
2295 |       ...inputResourceTemplate,
2296 |       complete: async (name: string, value: string) => {
2297 |         if (completers[name]) {
2298 |           return await completers[name](value);
2299 |         }
2300 | 
2301 |         return {
2302 |           values: [],
2303 |         };
2304 |       },
2305 |     };
2306 | 
2307 |     this.#resourceTemplates.push(resourceTemplate);
2308 |   }
2309 | 
2310 |   private addPrompt(inputPrompt: InputPrompt) {
2311 |     const completers: Record<string, ArgumentValueCompleter> = {};
2312 |     const enums: Record<string, string[]> = {};
2313 | 
2314 |     for (const argument of inputPrompt.arguments ?? []) {
2315 |       if (argument.complete) {
2316 |         completers[argument.name] = argument.complete;
2317 |       }
2318 | 
2319 |       if (argument.enum) {
2320 |         enums[argument.name] = argument.enum;
2321 |       }
2322 |     }
2323 | 
2324 |     const prompt = {
2325 |       ...inputPrompt,
2326 |       complete: async (name: string, value: string) => {
2327 |         if (completers[name]) {
2328 |           return await completers[name](value);
2329 |         }
2330 | 
2331 |         if (enums[name]) {
2332 |           const fuse = new Fuse(enums[name], {
2333 |             keys: ["value"],
2334 |           });
2335 | 
2336 |           const result = fuse.search(value);
2337 | 
2338 |           return {
2339 |             values: result.map((item) => item.item),
2340 |             total: result.length,
2341 |           };
2342 |         }
2343 | 
2344 |         return {
2345 |           values: [],
2346 |         };
2347 |       },
2348 |     };
2349 | 
2350 |     this.#prompts.push(prompt);
2351 |   }
2352 | 
2353 |   public get clientCapabilities(): ClientCapabilities | null {
2354 |     return this.#clientCapabilities ?? null;
2355 |   }
2356 | 
2357 |   public get server(): Server {
2358 |     return this.#server;
2359 |   }
2360 | 
2361 |   #pingInterval: ReturnType<typeof setInterval> | null = null;
2362 | 
2363 |   public async requestSampling(
2364 |     message: z.infer<typeof CreateMessageRequestSchema>["params"],
2365 |   ): Promise<SamplingResponse> {
2366 |     return this.#server.createMessage(message);
2367 |   }
2368 | 
2369 |   public async connect(transport: Transport) {
2370 |     if (this.#server.transport) {
2371 |       throw new UnexpectedStateError("Server is already connected");
2372 |     }
2373 | 
2374 |     await this.#server.connect(transport);
2375 | 
2376 |     let attempt = 0;
2377 | 
2378 |     while (attempt++ < 10) {
2379 |       const capabilities = await this.#server.getClientCapabilities();
2380 | 
2381 |       if (capabilities) {
2382 |         this.#clientCapabilities = capabilities;
2383 | 
2384 |         break;
2385 |       }
2386 | 
2387 |       await delay(100);
2388 |     }
2389 | 
2390 |     if (!this.#clientCapabilities) {
2391 |       console.warn('[warning] FastMCP could not infer client capabilities')
2392 |     }
2393 | 
2394 |     if (this.#clientCapabilities?.roots?.listChanged) {
2395 |       try {
2396 |         const roots = await this.#server.listRoots();
2397 |         this.#roots = roots.roots;
2398 |       } catch(e) {
2399 |         console.error(`[error] FastMCP received error listing roots.\n\n${e instanceof Error ? e.stack : JSON.stringify(e)}`)
2400 |       }
2401 |     }
2402 | 
2403 |     this.#pingInterval = setInterval(async () => {
2404 |       try {
2405 |         await this.#server.ping();
2406 |       } catch (error) {
2407 |         this.emit("error", {
2408 |           error: error as Error,
2409 |         });
2410 |       }
2411 |     }, 1000);
2412 |   }
2413 | 
2414 |   public get roots(): Root[] {
2415 |     return this.#roots;
2416 |   }
2417 | 
2418 |   public async close() {
2419 |     if (this.#pingInterval) {
2420 |       clearInterval(this.#pingInterval);
2421 |     }
2422 | 
2423 |     try {
2424 |       await this.#server.close();
2425 |     } catch (error) {
2426 |       console.error("[MCP Error]", "could not close server", error);
2427 |     }
2428 |   }
2429 | 
2430 |   private setupErrorHandling() {
2431 |     this.#server.onerror = (error) => {
2432 |       console.error("[MCP Error]", error);
2433 |     };
2434 |   }
2435 | 
2436 |   public get loggingLevel(): LoggingLevel {
2437 |     return this.#loggingLevel;
2438 |   }
2439 | 
2440 |   private setupCompleteHandlers() {
2441 |     this.#server.setRequestHandler(CompleteRequestSchema, async (request) => {
2442 |       if (request.params.ref.type === "ref/prompt") {
2443 |         const prompt = this.#prompts.find(
2444 |           (prompt) => prompt.name === request.params.ref.name,
2445 |         );
2446 | 
2447 |         if (!prompt) {
2448 |           throw new UnexpectedStateError("Unknown prompt", {
2449 |             request,
2450 |           });
2451 |         }
2452 | 
2453 |         if (!prompt.complete) {
2454 |           throw new UnexpectedStateError("Prompt does not support completion", {
2455 |             request,
2456 |           });
2457 |         }
2458 | 
2459 |         const completion = CompletionZodSchema.parse(
2460 |           await prompt.complete(
2461 |             request.params.argument.name,
2462 |             request.params.argument.value,
2463 |           ),
2464 |         );
2465 | 
2466 |         return {
2467 |           completion,
2468 |         };
2469 |       }
2470 | 
2471 |       if (request.params.ref.type === "ref/resource") {
2472 |         const resource = this.#resourceTemplates.find(
2473 |           (resource) => resource.uriTemplate === request.params.ref.uri,
2474 |         );
2475 | 
2476 |         if (!resource) {
2477 |           throw new UnexpectedStateError("Unknown resource", {
2478 |             request,
2479 |           });
2480 |         }
2481 | 
2482 |         if (!("uriTemplate" in resource)) {
2483 |           throw new UnexpectedStateError("Unexpected resource");
2484 |         }
2485 | 
2486 |         if (!resource.complete) {
2487 |           throw new UnexpectedStateError(
2488 |             "Resource does not support completion",
2489 |             {
2490 |               request,
2491 |             },
2492 |           );
2493 |         }
2494 | 
2495 |         const completion = CompletionZodSchema.parse(
2496 |           await resource.complete(
2497 |             request.params.argument.name,
2498 |             request.params.argument.value,
2499 |           ),
2500 |         );
2501 | 
2502 |         return {
2503 |           completion,
2504 |         };
2505 |       }
2506 | 
2507 |       throw new UnexpectedStateError("Unexpected completion request", {
2508 |         request,
2509 |       });
2510 |     });
2511 |   }
2512 | 
2513 |   private setupRootsHandlers() {
2514 |     this.#server.setNotificationHandler(
2515 |       RootsListChangedNotificationSchema,
2516 |       () => {
2517 |         this.#server.listRoots().then((roots) => {
2518 |           this.#roots = roots.roots;
2519 | 
2520 |           this.emit("rootsChanged", {
2521 |             roots: roots.roots,
2522 |           });
2523 |         });
2524 |       },
2525 |     );
2526 |   }
2527 | 
2528 |   private setupLoggingHandlers() {
2529 |     this.#server.setRequestHandler(SetLevelRequestSchema, (request) => {
2530 |       this.#loggingLevel = request.params.level;
2531 | 
2532 |       return {};
2533 |     });
2534 |   }
2535 | 
2536 |   private setupToolHandlers(tools: Tool<T>[]) {
2537 |     this.#server.setRequestHandler(ListToolsRequestSchema, async () => {
2538 |       return {
2539 |         tools: tools.map((tool) => {
2540 |           return {
2541 |             name: tool.name,
2542 |             description: tool.description,
2543 |             inputSchema: tool.parameters
2544 |               ? zodToJsonSchema(tool.parameters)
2545 |               : undefined,
2546 |           };
2547 |         }),
2548 |       };
2549 |     });
2550 | 
2551 |     this.#server.setRequestHandler(CallToolRequestSchema, async (request) => {
2552 |       const tool = tools.find((tool) => tool.name === request.params.name);
2553 | 
2554 |       if (!tool) {
2555 |         throw new McpError(
2556 |           ErrorCode.MethodNotFound,
2557 |           `Unknown tool: ${request.params.name}`,
2558 |         );
2559 |       }
2560 | 
2561 |       let args: any = undefined;
2562 | 
2563 |       if (tool.parameters) {
2564 |         const parsed = tool.parameters.safeParse(request.params.arguments);
2565 | 
2566 |         if (!parsed.success) {
2567 |           throw new McpError(
2568 |             ErrorCode.InvalidParams,
2569 |             `Invalid ${request.params.name} parameters`,
2570 |           );
2571 |         }
2572 | 
2573 |         args = parsed.data;
2574 |       }
2575 | 
2576 |       const progressToken = request.params?._meta?.progressToken;
2577 | 
2578 |       let result: ContentResult;
2579 | 
2580 |       try {
2581 |         const reportProgress = async (progress: Progress) => {
2582 |           await this.#server.notification({
2583 |             method: "notifications/progress",
2584 |             params: {
2585 |               ...progress,
2586 |               progressToken,
2587 |             },
2588 |           });
2589 |         };
2590 | 
2591 |         const log = {
2592 |           debug: (message: string, context?: SerializableValue) => {
2593 |             this.#server.sendLoggingMessage({
2594 |               level: "debug",
2595 |               data: {
2596 |                 message,
2597 |                 context,
2598 |               },
2599 |             });
2600 |           },
2601 |           error: (message: string, context?: SerializableValue) => {
2602 |             this.#server.sendLoggingMessage({
2603 |               level: "error",
2604 |               data: {
2605 |                 message,
2606 |                 context,
2607 |               },
2608 |             });
2609 |           },
2610 |           info: (message: string, context?: SerializableValue) => {
2611 |             this.#server.sendLoggingMessage({
2612 |               level: "info",
2613 |               data: {
2614 |                 message,
2615 |                 context,
2616 |               },
2617 |             });
2618 |           },
2619 |           warn: (message: string, context?: SerializableValue) => {
2620 |             this.#server.sendLoggingMessage({
2621 |               level: "warning",
2622 |               data: {
2623 |                 message,
2624 |                 context,
2625 |               },
2626 |             });
2627 |           },
2628 |         };
2629 | 
2630 |         const maybeStringResult = await tool.execute(args, {
2631 |           reportProgress,
2632 |           log,
2633 |           session: this.#auth,
2634 |         });
2635 | 
2636 |         if (typeof maybeStringResult === "string") {
2637 |           result = ContentResultZodSchema.parse({
2638 |             content: [{ type: "text", text: maybeStringResult }],
2639 |           });
2640 |         } else if ("type" in maybeStringResult) {
2641 |           result = ContentResultZodSchema.parse({
2642 |             content: [maybeStringResult],
2643 |           });
2644 |         } else {
2645 |           result = ContentResultZodSchema.parse(maybeStringResult);
2646 |         }
2647 |       } catch (error) {
2648 |         if (error instanceof UserError) {
2649 |           return {
2650 |             content: [{ type: "text", text: error.message }],
2651 |             isError: true,
2652 |           };
2653 |         }
2654 | 
2655 |         return {
2656 |           content: [{ type: "text", text: `Error: ${error}` }],
2657 |           isError: true,
2658 |         };
2659 |       }
2660 | 
2661 |       return result;
2662 |     });
2663 |   }
2664 | 
2665 |   private setupResourceHandlers(resources: Resource[]) {
2666 |     this.#server.setRequestHandler(ListResourcesRequestSchema, async () => {
2667 |       return {
2668 |         resources: resources.map((resource) => {
2669 |           return {
2670 |             uri: resource.uri,
2671 |             name: resource.name,
2672 |             mimeType: resource.mimeType,
2673 |           };
2674 |         }),
2675 |       };
2676 |     });
2677 | 
2678 |     this.#server.setRequestHandler(
2679 |       ReadResourceRequestSchema,
2680 |       async (request) => {
2681 |         if ("uri" in request.params) {
2682 |           const resource = resources.find(
2683 |             (resource) =>
2684 |               "uri" in resource && resource.uri === request.params.uri,
2685 |           );
2686 | 
2687 |           if (!resource) {
2688 |             for (const resourceTemplate of this.#resourceTemplates) {
2689 |               const uriTemplate = parseURITemplate(
2690 |                 resourceTemplate.uriTemplate,
2691 |               );
2692 | 
2693 |               const match = uriTemplate.fromUri(request.params.uri);
2694 | 
2695 |               if (!match) {
2696 |                 continue;
2697 |               }
2698 | 
2699 |               const uri = uriTemplate.fill(match);
2700 | 
2701 |               const result = await resourceTemplate.load(match);
2702 | 
2703 |               return {
2704 |                 contents: [
2705 |                   {
2706 |                     uri: uri,
2707 |                     mimeType: resourceTemplate.mimeType,
2708 |                     name: resourceTemplate.name,
2709 |                     ...result,
2710 |                   },
2711 |                 ],
2712 |               };
2713 |             }
2714 | 
2715 |             throw new McpError(
2716 |               ErrorCode.MethodNotFound,
2717 |               `Unknown resource: ${request.params.uri}`,
2718 |             );
2719 |           }
2720 | 
2721 |           if (!("uri" in resource)) {
2722 |             throw new UnexpectedStateError("Resource does not support reading");
2723 |           }
2724 | 
2725 |           let maybeArrayResult: Awaited<ReturnType<Resource["load"]>>;
2726 | 
2727 |           try {
2728 |             maybeArrayResult = await resource.load();
2729 |           } catch (error) {
2730 |             throw new McpError(
2731 |               ErrorCode.InternalError,
2732 |               `Error reading resource: ${error}`,
2733 |               {
2734 |                 uri: resource.uri,
2735 |               },
2736 |             );
2737 |           }
2738 | 
2739 |           if (Array.isArray(maybeArrayResult)) {
2740 |             return {
2741 |               contents: maybeArrayResult.map((result) => ({
2742 |                 uri: resource.uri,
2743 |                 mimeType: resource.mimeType,
2744 |                 name: resource.name,
2745 |                 ...result,
2746 |               })),
2747 |             };
2748 |           } else {
2749 |             return {
2750 |               contents: [
2751 |                 {
2752 |                   uri: resource.uri,
2753 |                   mimeType: resource.mimeType,
2754 |                   name: resource.name,
2755 |                   ...maybeArrayResult,
2756 |                 },
2757 |               ],
2758 |             };
2759 |           }
2760 |         }
2761 | 
2762 |         throw new UnexpectedStateError("Unknown resource request", {
2763 |           request,
2764 |         });
2765 |       },
2766 |     );
2767 |   }
2768 | 
2769 |   private setupResourceTemplateHandlers(resourceTemplates: ResourceTemplate[]) {
2770 |     this.#server.setRequestHandler(
2771 |       ListResourceTemplatesRequestSchema,
2772 |       async () => {
2773 |         return {
2774 |           resourceTemplates: resourceTemplates.map((resourceTemplate) => {
2775 |             return {
2776 |               name: resourceTemplate.name,
2777 |               uriTemplate: resourceTemplate.uriTemplate,
2778 |             };
2779 |           }),
2780 |         };
2781 |       },
2782 |     );
2783 |   }
2784 | 
2785 |   private setupPromptHandlers(prompts: Prompt[]) {
2786 |     this.#server.setRequestHandler(ListPromptsRequestSchema, async () => {
2787 |       return {
2788 |         prompts: prompts.map((prompt) => {
2789 |           return {
2790 |             name: prompt.name,
2791 |             description: prompt.description,
2792 |             arguments: prompt.arguments,
2793 |             complete: prompt.complete,
2794 |           };
2795 |         }),
2796 |       };
2797 |     });
2798 | 
2799 |     this.#server.setRequestHandler(GetPromptRequestSchema, async (request) => {
2800 |       const prompt = prompts.find(
2801 |         (prompt) => prompt.name === request.params.name,
2802 |       );
2803 | 
2804 |       if (!prompt) {
2805 |         throw new McpError(
2806 |           ErrorCode.MethodNotFound,
2807 |           `Unknown prompt: ${request.params.name}`,
2808 |         );
2809 |       }
2810 | 
2811 |       const args = request.params.arguments;
2812 | 
2813 |       for (const arg of prompt.arguments ?? []) {
2814 |         if (arg.required && !(args && arg.name in args)) {
2815 |           throw new McpError(
2816 |             ErrorCode.InvalidRequest,
2817 |             `Missing required argument: ${arg.name}`,
2818 |           );
2819 |         }
2820 |       }
2821 | 
2822 |       let result: Awaited<ReturnType<Prompt["load"]>>;
2823 | 
2824 |       try {
2825 |         result = await prompt.load(args as Record<string, string | undefined>);
2826 |       } catch (error) {
2827 |         throw new McpError(
2828 |           ErrorCode.InternalError,
2829 |           `Error loading prompt: ${error}`,
2830 |         );
2831 |       }
2832 | 
2833 |       return {
2834 |         description: prompt.description,
2835 |         messages: [
2836 |           {
2837 |             role: "user",
2838 |             content: { type: "text", text: result },
2839 |           },
2840 |         ],
2841 |       };
2842 |     });
2843 |   }
2844 | }
2845 | 
2846 | const FastMCPEventEmitterBase: {
2847 |   new (): StrictEventEmitter<EventEmitter, FastMCPEvents<FastMCPSessionAuth>>;
2848 | } = EventEmitter;
2849 | 
2850 | class FastMCPEventEmitter extends FastMCPEventEmitterBase {}
2851 | 
2852 | type Authenticate<T> = (request: http.IncomingMessage) => Promise<T>;
2853 | 
2854 | export class FastMCP<T extends Record<string, unknown> | undefined = undefined> extends FastMCPEventEmitter {
2855 |   #options: ServerOptions<T>;
2856 |   #prompts: InputPrompt[] = [];
2857 |   #resources: Resource[] = [];
2858 |   #resourcesTemplates: InputResourceTemplate[] = [];
2859 |   #sessions: FastMCPSession<T>[] = [];
2860 |   #sseServer: SSEServer | null = null;
2861 |   #tools: Tool<T>[] = [];
2862 |   #authenticate: Authenticate<T> | undefined;
2863 | 
2864 |   constructor(public options: ServerOptions<T>) {
2865 |     super();
2866 | 
2867 |     this.#options = options;
2868 |     this.#authenticate = options.authenticate;
2869 |   }
2870 | 
2871 |   public get sessions(): FastMCPSession<T>[] {
2872 |     return this.#sessions;
2873 |   }
2874 | 
2875 |   /**
2876 |    * Adds a tool to the server.
2877 |    */
2878 |   public addTool<Params extends ToolParameters>(tool: Tool<T, Params>) {
2879 |     this.#tools.push(tool as unknown as Tool<T>);
2880 |   }
2881 | 
2882 |   /**
2883 |    * Adds a resource to the server.
2884 |    */
2885 |   public addResource(resource: Resource) {
2886 |     this.#resources.push(resource);
2887 |   }
2888 | 
2889 |   /**
2890 |    * Adds a resource template to the server.
2891 |    */
2892 |   public addResourceTemplate<
2893 |     const Args extends InputResourceTemplateArgument[],
2894 |   >(resource: InputResourceTemplate<Args>) {
2895 |     this.#resourcesTemplates.push(resource);
2896 |   }
2897 | 
2898 |   /**
2899 |    * Adds a prompt to the server.
2900 |    */
2901 |   public addPrompt<const Args extends InputPromptArgument[]>(
2902 |     prompt: InputPrompt<Args>,
2903 |   ) {
2904 |     this.#prompts.push(prompt);
2905 |   }
2906 | 
2907 |   /**
2908 |    * Starts the server.
2909 |    */
2910 |   public async start(
2911 |     options:
2912 |       | { transportType: "stdio" }
2913 |       | {
2914 |           transportType: "sse";
2915 |           sse: { endpoint: `/${string}`; port: number };
2916 |         } = {
2917 |       transportType: "stdio",
2918 |     },
2919 |   ) {
2920 |     if (options.transportType === "stdio") {
2921 |       const transport = new StdioServerTransport();
2922 | 
2923 |       const session = new FastMCPSession<T>({
2924 |         name: this.#options.name,
2925 |         version: this.#options.version,
2926 |         tools: this.#tools,
2927 |         resources: this.#resources,
2928 |         resourcesTemplates: this.#resourcesTemplates,
2929 |         prompts: this.#prompts,
2930 |       });
2931 | 
2932 |       await session.connect(transport);
2933 | 
2934 |       this.#sessions.push(session);
2935 | 
2936 |       this.emit("connect", {
2937 |         session,
2938 |       });
2939 | 
2940 |     } else if (options.transportType === "sse") {
2941 |       this.#sseServer = await startSSEServer<FastMCPSession<T>>({
2942 |         endpoint: options.sse.endpoint as `/${string}`,
2943 |         port: options.sse.port,
2944 |         createServer: async (request) => {
2945 |           let auth: T | undefined;
2946 | 
2947 |           if (this.#authenticate) {
2948 |             auth = await this.#authenticate(request);
2949 |           }
2950 | 
2951 |           return new FastMCPSession<T>({
2952 |             auth,
2953 |             name: this.#options.name,
2954 |             version: this.#options.version,
2955 |             tools: this.#tools,
2956 |             resources: this.#resources,
2957 |             resourcesTemplates: this.#resourcesTemplates,
2958 |             prompts: this.#prompts,
2959 |           });
2960 |         },
2961 |         onClose: (session) => {
2962 |           this.emit("disconnect", {
2963 |             session,
2964 |           });
2965 |         },
2966 |         onConnect: async (session) => {
2967 |           this.#sessions.push(session);
2968 | 
2969 |           this.emit("connect", {
2970 |             session,
2971 |           });
2972 |         },
2973 |       });
2974 | 
2975 |       console.info(
2976 |         `server is running on SSE at http://localhost:${options.sse.port}${options.sse.endpoint}`,
2977 |       );
2978 |     } else {
2979 |       throw new Error("Invalid transport type");
2980 |     }
2981 |   }
2982 | 
2983 |   /**
2984 |    * Stops the server.
2985 |    */
2986 |   public async stop() {
2987 |     if (this.#sseServer) {
2988 |       this.#sseServer.close();
2989 |     }
2990 |   }
2991 | }
2992 | 
2993 | export type { Context };
2994 | export type { Tool, ToolParameters };
2995 | export type { Content, TextContent, ImageContent, ContentResult };
2996 | export type { Progress, SerializableValue };
2997 | export type { Resource, ResourceResult };
2998 | export type { ResourceTemplate, ResourceTemplateArgument };
2999 | export type { Prompt, PromptArgument };
3000 | export type { InputPrompt, InputPromptArgument };
3001 | export type { ServerOptions, LoggingLevel };
3002 | export type { FastMCPEvents, FastMCPSessionEvents };
3003 | 
3004 | 
3005 | 
3006 | ---
3007 | File: /eslint.config.js
3008 | ---
3009 | 
3010 | import perfectionist from "eslint-plugin-perfectionist";
3011 | 
3012 | export default [perfectionist.configs["recommended-alphabetical"]];
3013 | 
3014 | 
3015 | 
3016 | ---
3017 | File: /package.json
3018 | ---
3019 | 
3020 | {
3021 |   "name": "fastmcp",
3022 |   "version": "1.0.0",
3023 |   "main": "dist/FastMCP.js",
3024 |   "scripts": {
3025 |     "build": "tsup",
3026 |     "test": "vitest run && tsc && jsr publish --dry-run",
3027 |     "format": "prettier --write . && eslint --fix ."
3028 |   },
3029 |   "bin": {
3030 |     "fastmcp": "dist/bin/fastmcp.js"
3031 |   },
3032 |   "keywords": [
3033 |     "MCP",
3034 |     "SSE"
3035 |   ],
3036 |   "type": "module",
3037 |   "author": "Frank Fiegel <[email protected]>",
3038 |   "license": "MIT",
3039 |   "description": "A TypeScript framework for building MCP servers.",
3040 |   "module": "dist/FastMCP.js",
3041 |   "types": "dist/FastMCP.d.ts",
3042 |   "dependencies": {
3043 |     "@modelcontextprotocol/sdk": "^1.6.0",
3044 |     "execa": "^9.5.2",
3045 |     "file-type": "^20.3.0",
3046 |     "fuse.js": "^7.1.0",
3047 |     "mcp-proxy": "^2.10.4",
3048 |     "strict-event-emitter-types": "^2.0.0",
3049 |     "undici": "^7.4.0",
3050 |     "uri-templates": "^0.2.0",
3051 |     "yargs": "^17.7.2",
3052 |     "zod": "^3.24.2",
3053 |     "zod-to-json-schema": "^3.24.3"
3054 |   },
3055 |   "repository": {
3056 |     "url": "https://github.com/punkpeye/fastmcp"
3057 |   },
3058 |   "homepage": "https://glama.ai/mcp",
3059 |   "release": {
3060 |     "branches": [
3061 |       "main"
3062 |     ],
3063 |     "plugins": [
3064 |       "@semantic-release/commit-analyzer",
3065 |       "@semantic-release/release-notes-generator",
3066 |       "@semantic-release/npm",
3067 |       "@semantic-release/github",
3068 |       "@sebbo2002/semantic-release-jsr"
3069 |     ]
3070 |   },
3071 |   "devDependencies": {
3072 |     "@sebbo2002/semantic-release-jsr": "^2.0.4",
3073 |     "@tsconfig/node22": "^22.0.0",
3074 |     "@types/node": "^22.13.5",
3075 |     "@types/uri-templates": "^0.1.34",
3076 |     "@types/yargs": "^17.0.33",
3077 |     "eslint": "^9.21.0",
3078 |     "eslint-plugin-perfectionist": "^4.9.0",
3079 |     "eventsource-client": "^1.1.3",
3080 |     "get-port-please": "^3.1.2",
3081 |     "jsr": "^0.13.3",
3082 |     "prettier": "^3.5.2",
3083 |     "semantic-release": "^24.2.3",
3084 |     "tsup": "^8.4.0",
3085 |     "typescript": "^5.7.3",
3086 |     "vitest": "^3.0.7"
3087 |   },
3088 |   "tsup": {
3089 |     "entry": [
3090 |       "src/FastMCP.ts",
3091 |       "src/bin/fastmcp.ts"
3092 |     ],
3093 |     "format": [
3094 |       "esm"
3095 |     ],
3096 |     "dts": true,
3097 |     "splitting": true,
3098 |     "sourcemap": true,
3099 |     "clean": true
3100 |   }
3101 | }
3102 | 
3103 | 
3104 | 
3105 | ---
3106 | File: /README.md
3107 | ---
3108 | 
3109 | # FastMCP
3110 | 
3111 | A TypeScript framework for building [MCP](https://glama.ai/mcp) servers capable of handling client sessions.
3112 | 
3113 | > [!NOTE]
3114 | >
3115 | > For a Python implementation, see [FastMCP](https://github.com/jlowin/fastmcp).
3116 | 
3117 | ## Features
3118 | 
3119 | - Simple Tool, Resource, Prompt definition
3120 | - [Authentication](#authentication)
3121 | - [Sessions](#sessions)
3122 | - [Image content](#returning-an-image)
3123 | - [Logging](#logging)
3124 | - [Error handling](#errors)
3125 | - [SSE](#sse)
3126 | - CORS (enabled by default)
3127 | - [Progress notifications](#progress)
3128 | - [Typed server events](#typed-server-events)
3129 | - [Prompt argument auto-completion](#prompt-argument-auto-completion)
3130 | - [Sampling](#requestsampling)
3131 | - Automated SSE pings
3132 | - Roots
3133 | - CLI for [testing](#test-with-mcp-cli) and [debugging](#inspect-with-mcp-inspector)
3134 | 
3135 | ## Installation
3136 | 
3137 | ```bash
3138 | npm install fastmcp
3139 | ```
3140 | 
3141 | ## Quickstart
3142 | 
3143 | ```ts
3144 | import { FastMCP } from "fastmcp";
3145 | import { z } from "zod";
3146 | 
3147 | const server = new FastMCP({
3148 |   name: "My Server",
3149 |   version: "1.0.0",
3150 | });
3151 | 
3152 | server.addTool({
3153 |   name: "add",
3154 |   description: "Add two numbers",
3155 |   parameters: z.object({
3156 |     a: z.number(),
3157 |     b: z.number(),
3158 |   }),
3159 |   execute: async (args) => {
3160 |     return String(args.a + args.b);
3161 |   },
3162 | });
3163 | 
3164 | server.start({
3165 |   transportType: "stdio",
3166 | });
3167 | ```
3168 | 
3169 | _That's it!_ You have a working MCP server.
3170 | 
3171 | You can test the server in terminal with:
3172 | 
3173 | ```bash
3174 | git clone https://github.com/punkpeye/fastmcp.git
3175 | cd fastmcp
3176 | 
3177 | npm install
3178 | 
3179 | # Test the addition server example using CLI:
3180 | npx fastmcp dev src/examples/addition.ts
3181 | # Test the addition server example using MCP Inspector:
3182 | npx fastmcp inspect src/examples/addition.ts
3183 | ```
3184 | 
3185 | ### SSE
3186 | 
3187 | You can also run the server with SSE support:
3188 | 
3189 | ```ts
3190 | server.start({
3191 |   transportType: "sse",
3192 |   sse: {
3193 |     endpoint: "/sse",
3194 |     port: 8080,
3195 |   },
3196 | });
3197 | ```
3198 | 
3199 | This will start the server and listen for SSE connections on `http://localhost:8080/sse`.
3200 | 
3201 | You can then use `SSEClientTransport` to connect to the server:
3202 | 
3203 | ```ts
3204 | import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
3205 | 
3206 | const client = new Client(
3207 |   {
3208 |     name: "example-client",
3209 |     version: "1.0.0",
3210 |   },
3211 |   {
3212 |     capabilities: {},
3213 |   },
3214 | );
3215 | 
3216 | const transport = new SSEClientTransport(new URL(`http://localhost:8080/sse`));
3217 | 
3218 | await client.connect(transport);
3219 | ```
3220 | 
3221 | ## Core Concepts
3222 | 
3223 | ### Tools
3224 | 
3225 | [Tools](https://modelcontextprotocol.io/docs/concepts/tools) in MCP allow servers to expose executable functions that can be invoked by clients and used by LLMs to perform actions.
3226 | 
3227 | ```js
3228 | server.addTool({
3229 |   name: "fetch",
3230 |   description: "Fetch the content of a url",
3231 |   parameters: z.object({
3232 |     url: z.string(),
3233 |   }),
3234 |   execute: async (args) => {
3235 |     return await fetchWebpageContent(args.url);
3236 |   },
3237 | });
3238 | ```
3239 | 
3240 | #### Returning a string
3241 | 
3242 | `execute` can return a string:
3243 | 
3244 | ```js
3245 | server.addTool({
3246 |   name: "download",
3247 |   description: "Download a file",
3248 |   parameters: z.object({
3249 |     url: z.string(),
3250 |   }),
3251 |   execute: async (args) => {
3252 |     return "Hello, world!";
3253 |   },
3254 | });
3255 | ```
3256 | 
3257 | The latter is equivalent to:
3258 | 
3259 | ```js
3260 | server.addTool({
3261 |   name: "download",
3262 |   description: "Download a file",
3263 |   parameters: z.object({
3264 |     url: z.string(),
3265 |   }),
3266 |   execute: async (args) => {
3267 |     return {
3268 |       content: [
3269 |         {
3270 |           type: "text",
3271 |           text: "Hello, world!",
3272 |         },
3273 |       ],
3274 |     };
3275 |   },
3276 | });
3277 | ```
3278 | 
3279 | #### Returning a list
3280 | 
3281 | If you want to return a list of messages, you can return an object with a `content` property:
3282 | 
3283 | ```js
3284 | server.addTool({
3285 |   name: "download",
3286 |   description: "Download a file",
3287 |   parameters: z.object({
3288 |     url: z.string(),
3289 |   }),
3290 |   execute: async (args) => {
3291 |     return {
3292 |       content: [
3293 |         { type: "text", text: "First message" },
3294 |         { type: "text", text: "Second message" },
3295 |       ],
3296 |     };
3297 |   },
3298 | });
3299 | ```
3300 | 
3301 | #### Returning an image
3302 | 
3303 | Use the `imageContent` to create a content object for an image:
3304 | 
3305 | ```js
3306 | import { imageContent } from "fastmcp";
3307 | 
3308 | server.addTool({
3309 |   name: "download",
3310 |   description: "Download a file",
3311 |   parameters: z.object({
3312 |     url: z.string(),
3313 |   }),
3314 |   execute: async (args) => {
3315 |     return imageContent({
3316 |       url: "https://example.com/image.png",
3317 |     });
3318 | 
3319 |     // or...
3320 |     // return imageContent({
3321 |     //   path: "/path/to/image.png",
3322 |     // });
3323 | 
3324 |     // or...
3325 |     // return imageContent({
3326 |     //   buffer: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "base64"),
3327 |     // });
3328 | 
3329 |     // or...
3330 |     // return {
3331 |     //   content: [
3332 |     //     await imageContent(...)
3333 |     //   ],
3334 |     // };
3335 |   },
3336 | });
3337 | ```
3338 | 
3339 | The `imageContent` function takes the following options:
3340 | 
3341 | - `url`: The URL of the image.
3342 | - `path`: The path to the image file.
3343 | - `buffer`: The image data as a buffer.
3344 | 
3345 | Only one of `url`, `path`, or `buffer` must be specified.
3346 | 
3347 | The above example is equivalent to:
3348 | 
3349 | ```js
3350 | server.addTool({
3351 |   name: "download",
3352 |   description: "Download a file",
3353 |   parameters: z.object({
3354 |     url: z.string(),
3355 |   }),
3356 |   execute: async (args) => {
3357 |     return {
3358 |       content: [
3359 |         {
3360 |           type: "image",
3361 |           data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
3362 |           mimeType: "image/png",
3363 |         },
3364 |       ],
3365 |     };
3366 |   },
3367 | });
3368 | ```
3369 | 
3370 | #### Logging
3371 | 
3372 | Tools can log messages to the client using the `log` object in the context object:
3373 | 
3374 | ```js
3375 | server.addTool({
3376 |   name: "download",
3377 |   description: "Download a file",
3378 |   parameters: z.object({
3379 |     url: z.string(),
3380 |   }),
3381 |   execute: async (args, { log }) => {
3382 |     log.info("Downloading file...", {
3383 |       url,
3384 |     });
3385 | 
3386 |     // ...
3387 | 
3388 |     log.info("Downloaded file");
3389 | 
3390 |     return "done";
3391 |   },
3392 | });
3393 | ```
3394 | 
3395 | The `log` object has the following methods:
3396 | 
3397 | - `debug(message: string, data?: SerializableValue)`
3398 | - `error(message: string, data?: SerializableValue)`
3399 | - `info(message: string, data?: SerializableValue)`
3400 | - `warn(message: string, data?: SerializableValue)`
3401 | 
3402 | #### Errors
3403 | 
3404 | The errors that are meant to be shown to the user should be thrown as `UserError` instances:
3405 | 
3406 | ```js
3407 | import { UserError } from "fastmcp";
3408 | 
3409 | server.addTool({
3410 |   name: "download",
3411 |   description: "Download a file",
3412 |   parameters: z.object({
3413 |     url: z.string(),
3414 |   }),
3415 |   execute: async (args) => {
3416 |     if (args.url.startsWith("https://example.com")) {
3417 |       throw new UserError("This URL is not allowed");
3418 |     }
3419 | 
3420 |     return "done";
3421 |   },
3422 | });
3423 | ```
3424 | 
3425 | #### Progress
3426 | 
3427 | Tools can report progress by calling `reportProgress` in the context object:
3428 | 
3429 | ```js
3430 | server.addTool({
3431 |   name: "download",
3432 |   description: "Download a file",
3433 |   parameters: z.object({
3434 |     url: z.string(),
3435 |   }),
3436 |   execute: async (args, { reportProgress }) => {
3437 |     reportProgress({
3438 |       progress: 0,
3439 |       total: 100,
3440 |     });
3441 | 
3442 |     // ...
3443 | 
3444 |     reportProgress({
3445 |       progress: 100,
3446 |       total: 100,
3447 |     });
3448 | 
3449 |     return "done";
3450 |   },
3451 | });
3452 | ```
3453 | 
3454 | ### Resources
3455 | 
3456 | [Resources](https://modelcontextprotocol.io/docs/concepts/resources) represent any kind of data that an MCP server wants to make available to clients. This can include:
3457 | 
3458 | - File contents
3459 | - Screenshots and images
3460 | - Log files
3461 | - And more
3462 | 
3463 | Each resource is identified by a unique URI and can contain either text or binary data.
3464 | 
3465 | ```ts
3466 | server.addResource({
3467 |   uri: "file:///logs/app.log",
3468 |   name: "Application Logs",
3469 |   mimeType: "text/plain",
3470 |   async load() {
3471 |     return {
3472 |       text: await readLogFile(),
3473 |     };
3474 |   },
3475 | });
3476 | ```
3477 | 
3478 | > [!NOTE]
3479 | >
3480 | > `load` can return multiple resources. This could be used, for example, to return a list of files inside a directory when the directory is read.
3481 | >
3482 | > ```ts
3483 | > async load() {
3484 | >   return [
3485 | >     {
3486 | >       text: "First file content",
3487 | >     },
3488 | >     {
3489 | >       text: "Second file content",
3490 | >     },
3491 | >   ];
3492 | > }
3493 | > ```
3494 | 
3495 | You can also return binary contents in `load`:
3496 | 
3497 | ```ts
3498 | async load() {
3499 |   return {
3500 |     blob: 'base64-encoded-data'
3501 |   };
3502 | }
3503 | ```
3504 | 
3505 | ### Resource templates
3506 | 
3507 | You can also define resource templates:
3508 | 
3509 | ```ts
3510 | server.addResourceTemplate({
3511 |   uriTemplate: "file:///logs/{name}.log",
3512 |   name: "Application Logs",
3513 |   mimeType: "text/plain",
3514 |   arguments: [
3515 |     {
3516 |       name: "name",
3517 |       description: "Name of the log",
3518 |       required: true,
3519 |     },
3520 |   ],
3521 |   async load({ name }) {
3522 |     return {
3523 |       text: `Example log content for ${name}`,
3524 |     };
3525 |   },
3526 | });
3527 | ```
3528 | 
3529 | #### Resource template argument auto-completion
3530 | 
3531 | Provide `complete` functions for resource template arguments to enable automatic completion:
3532 | 
3533 | ```ts
3534 | server.addResourceTemplate({
3535 |   uriTemplate: "file:///logs/{name}.log",
3536 |   name: "Application Logs",
3537 |   mimeType: "text/plain",
3538 |   arguments: [
3539 |     {
3540 |       name: "name",
3541 |       description: "Name of the log",
3542 |       required: true,
3543 |       complete: async (value) => {
3544 |         if (value === "Example") {
3545 |           return {
3546 |             values: ["Example Log"],
3547 |           };
3548 |         }
3549 | 
3550 |         return {
3551 |           values: [],
3552 |         };
3553 |       },
3554 |     },
3555 |   ],
3556 |   async load({ name }) {
3557 |     return {
3558 |       text: `Example log content for ${name}`,
3559 |     };
3560 |   },
3561 | });
3562 | ```
3563 | 
3564 | ### Prompts
3565 | 
3566 | [Prompts](https://modelcontextprotocol.io/docs/concepts/prompts) enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs. They provide a powerful way to standardize and share common LLM interactions.
3567 | 
3568 | ```ts
3569 | server.addPrompt({
3570 |   name: "git-commit",
3571 |   description: "Generate a Git commit message",
3572 |   arguments: [
3573 |     {
3574 |       name: "changes",
3575 |       description: "Git diff or description of changes",
3576 |       required: true,
3577 |     },
3578 |   ],
3579 |   load: async (args) => {
3580 |     return `Generate a concise but descriptive commit message for these changes:\n\n${args.changes}`;
3581 |   },
3582 | });
3583 | ```
3584 | 
3585 | #### Prompt argument auto-completion
3586 | 
3587 | Prompts can provide auto-completion for their arguments:
3588 | 
3589 | ```js
3590 | server.addPrompt({
3591 |   name: "countryPoem",
3592 |   description: "Writes a poem about a country",
3593 |   load: async ({ name }) => {
3594 |     return `Hello, ${name}!`;
3595 |   },
3596 |   arguments: [
3597 |     {
3598 |       name: "name",
3599 |       description: "Name of the country",
3600 |       required: true,
3601 |       complete: async (value) => {
3602 |         if (value === "Germ") {
3603 |           return {
3604 |             values: ["Germany"],
3605 |           };
3606 |         }
3607 | 
3608 |         return {
3609 |           values: [],
3610 |         };
3611 |       },
3612 |     },
3613 |   ],
3614 | });
3615 | ```
3616 | 
3617 | #### Prompt argument auto-completion using `enum`
3618 | 
3619 | If you provide an `enum` array for an argument, the server will automatically provide completions for the argument.
3620 | 
3621 | ```js
3622 | server.addPrompt({
3623 |   name: "countryPoem",
3624 |   description: "Writes a poem about a country",
3625 |   load: async ({ name }) => {
3626 |     return `Hello, ${name}!`;
3627 |   },
3628 |   arguments: [
3629 |     {
3630 |       name: "name",
3631 |       description: "Name of the country",
3632 |       required: true,
3633 |       enum: ["Germany", "France", "Italy"],
3634 |     },
3635 |   ],
3636 | });
3637 | ```
3638 | 
3639 | ### Authentication
3640 | 
3641 | FastMCP allows you to `authenticate` clients using a custom function:
3642 | 
3643 | ```ts
3644 | import { AuthError } from "fastmcp";
3645 | 
3646 | const server = new FastMCP({
3647 |   name: "My Server",
3648 |   version: "1.0.0",
3649 |   authenticate: ({request}) => {
3650 |     const apiKey = request.headers["x-api-key"];
3651 | 
3652 |     if (apiKey !== '123') {
3653 |       throw new Response(null, {
3654 |         status: 401,
3655 |         statusText: "Unauthorized",
3656 |       });
3657 |     }
3658 | 
3659 |     // Whatever you return here will be accessible in the `context.session` object.
3660 |     return {
3661 |       id: 1,
3662 |     }
3663 |   },
3664 | });
3665 | ```
3666 | 
3667 | Now you can access the authenticated session data in your tools:
3668 | 
3669 | ```ts
3670 | server.addTool({
3671 |   name: "sayHello",
3672 |   execute: async (args, { session }) => {
3673 |     return `Hello, ${session.id}!`;
3674 |   },
3675 | });
3676 | ```
3677 | 
3678 | ### Sessions
3679 | 
3680 | The `session` object is an instance of `FastMCPSession` and it describes active client sessions.
3681 | 
3682 | ```ts
3683 | server.sessions;
3684 | ```
3685 | 
3686 | We allocate a new server instance for each client connection to enable 1:1 communication between a client and the server.
3687 | 
3688 | ### Typed server events
3689 | 
3690 | You can listen to events emitted by the server using the `on` method:
3691 | 
3692 | ```ts
3693 | server.on("connect", (event) => {
3694 |   console.log("Client connected:", event.session);
3695 | });
3696 | 
3697 | server.on("disconnect", (event) => {
3698 |   console.log("Client disconnected:", event.session);
3699 | });
3700 | ```
3701 | 
3702 | ## `FastMCPSession`
3703 | 
3704 | `FastMCPSession` represents a client session and provides methods to interact with the client.
3705 | 
3706 | Refer to [Sessions](#sessions) for examples of how to obtain a `FastMCPSession` instance.
3707 | 
3708 | ### `requestSampling`
3709 | 
3710 | `requestSampling` creates a [sampling](https://modelcontextprotocol.io/docs/concepts/sampling) request and returns the response.
3711 | 
3712 | ```ts
3713 | await session.requestSampling({
3714 |   messages: [
3715 |     {
3716 |       role: "user",
3717 |       content: {
3718 |         type: "text",
3719 |         text: "What files are in the current directory?",
3720 |       },
3721 |     },
3722 |   ],
3723 |   systemPrompt: "You are a helpful file system assistant.",
3724 |   includeContext: "thisServer",
3725 |   maxTokens: 100,
3726 | });
3727 | ```
3728 | 
3729 | ### `clientCapabilities`
3730 | 
3731 | The `clientCapabilities` property contains the client capabilities.
3732 | 
3733 | ```ts
3734 | session.clientCapabilities;
3735 | ```
3736 | 
3737 | ### `loggingLevel`
3738 | 
3739 | The `loggingLevel` property describes the logging level as set by the client.
3740 | 
3741 | ```ts
3742 | session.loggingLevel;
3743 | ```
3744 | 
3745 | ### `roots`
3746 | 
3747 | The `roots` property contains the roots as set by the client.
3748 | 
3749 | ```ts
3750 | session.roots;
3751 | ```
3752 | 
3753 | ### `server`
3754 | 
3755 | The `server` property contains an instance of MCP server that is associated with the session.
3756 | 
3757 | ```ts
3758 | session.server;
3759 | ```
3760 | 
3761 | ### Typed session events
3762 | 
3763 | You can listen to events emitted by the session using the `on` method:
3764 | 
3765 | ```ts
3766 | session.on("rootsChanged", (event) => {
3767 |   console.log("Roots changed:", event.roots);
3768 | });
3769 | 
3770 | session.on("error", (event) => {
3771 |   console.error("Error:", event.error);
3772 | });
3773 | ```
3774 | 
3775 | ## Running Your Server
3776 | 
3777 | ### Test with `mcp-cli`
3778 | 
3779 | The fastest way to test and debug your server is with `fastmcp dev`:
3780 | 
3781 | ```bash
3782 | npx fastmcp dev server.js
3783 | npx fastmcp dev server.ts
3784 | ```
3785 | 
3786 | This will run your server with [`mcp-cli`](https://github.com/wong2/mcp-cli) for testing and debugging your MCP server in the terminal.
3787 | 
3788 | ### Inspect with `MCP Inspector`
3789 | 
3790 | Another way is to use the official [`MCP Inspector`](https://modelcontextprotocol.io/docs/tools/inspector) to inspect your server with a Web UI:
3791 | 
3792 | ```bash
3793 | npx fastmcp inspect server.ts
3794 | ```
3795 | 
3796 | ## FAQ
3797 | 
3798 | ### How to use with Claude Desktop?
3799 | 
3800 | Follow the guide https://modelcontextprotocol.io/quickstart/user and add the following configuration:
3801 | 
3802 | ```json
3803 | {
3804 |   "mcpServers": {
3805 |     "my-mcp-server": {
3806 |       "command": "npx",
3807 |       "args": [
3808 |         "tsx",
3809 |         "/PATH/TO/YOUR_PROJECT/src/index.ts"
3810 |       ],
3811 |       "env": {
3812 |         "YOUR_ENV_VAR": "value"
3813 |       }
3814 |     }
3815 |   }
3816 | }
3817 | ```
3818 | 
3819 | ## Showcase
3820 | 
3821 | > [!NOTE]
3822 | >
3823 | > If you've developed a server using FastMCP, please [submit a PR](https://github.com/punkpeye/fastmcp) to showcase it here!
3824 | 
3825 | - https://github.com/apinetwork/piapi-mcp-server
3826 | - https://github.com/Meeting-Baas/meeting-mcp - Meeting BaaS MCP server that enables AI assistants to create meeting bots, search transcripts, and manage recording data
3827 | 
3828 | ## Acknowledgements
3829 | 
3830 | - FastMCP is inspired by the [Python implementation](https://github.com/jlowin/fastmcp) by [Jonathan Lowin](https://github.com/jlowin).
3831 | - Parts of codebase were adopted from [LiteMCP](https://github.com/wong2/litemcp).
3832 | - Parts of codebase were adopted from [Model Context protocolでSSEをやってみる](https://dev.classmethod.jp/articles/mcp-sse/).
3833 | 
3834 | 
3835 | 
3836 | ---
3837 | File: /vitest.config.js
3838 | ---
3839 | 
3840 | import { defineConfig } from "vitest/config";
3841 | 
3842 | export default defineConfig({
3843 |   test: {
3844 |     poolOptions: {
3845 |       forks: { execArgv: ["--experimental-eventsource"] },
3846 |     },
3847 |   },
3848 | });
3849 | 
3850 | 
```
Page 48/52FirstPrevNextLast