github-to-mcp-monorepo 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +8 -0
- package/.github/CODEOWNERS +6 -0
- package/.husky/pre-commit +1 -0
- package/.nvmrc +1 -0
- package/.prettierignore +5 -0
- package/.prettierrc +7 -0
- package/.vscode/settings.json +4 -0
- package/ARCHITECTURE.md +1429 -0
- package/CHANGELOG.md +167 -0
- package/CONTRIBUTING.md +327 -0
- package/LICENSE +201 -0
- package/README.md +1028 -0
- package/SECURITY.md +248 -0
- package/VISUAL_GUIDE.md +437 -0
- package/apps/vscode/IMPLEMENTATION.md +480 -0
- package/apps/vscode/README.md +248 -0
- package/apps/vscode/package.json +381 -0
- package/apps/vscode/resources/icon.png +0 -0
- package/apps/vscode/resources/icon.svg +5 -0
- package/apps/vscode/src/commands/browseRegistry.ts +211 -0
- package/apps/vscode/src/commands/configureClaudeDesktop.ts +332 -0
- package/apps/vscode/src/commands/convert.ts +82 -0
- package/apps/vscode/src/commands/convertCurrentRepo.ts +109 -0
- package/apps/vscode/src/commands/convertFromUrl.ts +138 -0
- package/apps/vscode/src/commands/index.ts +121 -0
- package/apps/vscode/src/commands/validate.ts +197 -0
- package/apps/vscode/src/extension.ts +464 -0
- package/apps/vscode/src/global.d.ts +36 -0
- package/apps/vscode/src/test/extension.test.ts +73 -0
- package/apps/vscode/src/utils/file-generator.ts +529 -0
- package/apps/vscode/src/utils/github-api.ts +335 -0
- package/apps/vscode/src/utils/index.ts +29 -0
- package/apps/vscode/src/utils/mcp-config.ts +334 -0
- package/apps/vscode/src/utils/storage.ts +87 -0
- package/apps/vscode/src/views/McpServersTreeView.ts +160 -0
- package/apps/vscode/src/views/OutputChannelView.ts +195 -0
- package/apps/vscode/src/views/StatusBarItem.ts +251 -0
- package/apps/vscode/src/views/ToolsExplorerView.ts +314 -0
- package/apps/vscode/src/views/historyProvider.ts +75 -0
- package/apps/vscode/src/views/index.ts +12 -0
- package/apps/vscode/src/views/resultsPanel.ts +330 -0
- package/apps/vscode/src/webviews/ConversionPanel.ts +350 -0
- package/apps/vscode/src/webviews/ToolDetailsPanel.ts +448 -0
- package/apps/vscode/src/webviews/index.ts +9 -0
- package/apps/vscode/src/webviews/webview-ui/styles.ts +492 -0
- package/apps/vscode/tsconfig.json +20 -0
- package/apps/web/PLAYGROUND_GUIDE.md +499 -0
- package/apps/web/README.md +505 -0
- package/apps/web/app/api/convert/route.ts +100 -0
- package/apps/web/app/api/convert/stream/route.ts +198 -0
- package/apps/web/app/api/deploy/route.ts +157 -0
- package/apps/web/app/api/edge/route.ts +308 -0
- package/apps/web/app/api/export-docker/route.ts +284 -0
- package/apps/web/app/api/generate-openapi/route.ts +119 -0
- package/apps/web/app/api/mcp/[serverId]/route.ts +263 -0
- package/apps/web/app/api/playground/connect/route.ts +143 -0
- package/apps/web/app/api/playground/disconnect/route.ts +78 -0
- package/apps/web/app/api/playground/execute/route.ts +135 -0
- package/apps/web/app/api/playground/sessions/route.ts +103 -0
- package/apps/web/app/api/playground/tools/route.ts +117 -0
- package/apps/web/app/api/playground/v2/connect/route.ts +96 -0
- package/apps/web/app/api/playground/v2/disconnect/route.ts +88 -0
- package/apps/web/app/api/playground/v2/health/route.ts +80 -0
- package/apps/web/app/api/playground/v2/prompts/route.ts +160 -0
- package/apps/web/app/api/playground/v2/resources/route.ts +159 -0
- package/apps/web/app/api/playground/v2/sessions/route.ts +184 -0
- package/apps/web/app/api/playground/v2/tools/route.ts +167 -0
- package/apps/web/app/api/stream/route.ts +232 -0
- package/apps/web/app/batch/BatchConvertClient.tsx +190 -0
- package/apps/web/app/batch/page.tsx +37 -0
- package/apps/web/app/convert/page.tsx +269 -0
- package/apps/web/app/dashboard/page.tsx +380 -0
- package/apps/web/app/globals.css +622 -0
- package/apps/web/app/layout.tsx +120 -0
- package/apps/web/app/manifest.ts +31 -0
- package/apps/web/app/opengraph-image.tsx +112 -0
- package/apps/web/app/page.old.tsx +924 -0
- package/apps/web/app/page.tsx +77 -0
- package/apps/web/app/playground/page.tsx +306 -0
- package/apps/web/app/playground/v2/error.tsx +163 -0
- package/apps/web/app/playground/v2/layout.tsx +58 -0
- package/apps/web/app/playground/v2/loading.tsx +152 -0
- package/apps/web/app/playground/v2/page.tsx +644 -0
- package/apps/web/app/playground/v2/providers.tsx +214 -0
- package/apps/web/app/playground/v2/use-shortcuts.ts +209 -0
- package/apps/web/app/playground/v2/use-url-state.ts +296 -0
- package/apps/web/app/providers.tsx +22 -0
- package/apps/web/app/sitemap.ts +32 -0
- package/apps/web/app/twitter-image.tsx +112 -0
- package/apps/web/components/BranchSelector.tsx +401 -0
- package/apps/web/components/ClaudeConfigExport.tsx +226 -0
- package/apps/web/components/Features.tsx +84 -0
- package/apps/web/components/Footer.tsx +119 -0
- package/apps/web/components/GenerationProgress.tsx +248 -0
- package/apps/web/components/GithubUrlInput.tsx +483 -0
- package/apps/web/components/Header.tsx +175 -0
- package/apps/web/components/Hero.tsx +117 -0
- package/apps/web/components/HowItWorks.tsx +119 -0
- package/apps/web/components/InstallBanner.tsx +158 -0
- package/apps/web/components/Logo.tsx +116 -0
- package/apps/web/components/ParticleBackground.tsx +105 -0
- package/apps/web/components/Playground.tsx +472 -0
- package/apps/web/components/PlaygroundToolTester.tsx +410 -0
- package/apps/web/components/ProductCards.tsx +179 -0
- package/apps/web/components/SplitView.tsx +194 -0
- package/apps/web/components/ToolFilter.tsx +260 -0
- package/apps/web/components/ToolList.tsx +325 -0
- package/apps/web/components/batch/BatchConvert.tsx +785 -0
- package/apps/web/components/batch/index.ts +7 -0
- package/apps/web/components/convert/ConfigTabs.tsx +230 -0
- package/apps/web/components/convert/ConversionResult.tsx +482 -0
- package/apps/web/components/convert/InlinePlayground.tsx +259 -0
- package/apps/web/components/convert/LoadingSteps.tsx +311 -0
- package/apps/web/components/convert/OneClickInstall.tsx +224 -0
- package/apps/web/components/convert/ToolCard.tsx +189 -0
- package/apps/web/components/convert/TryInPlayground.tsx +242 -0
- package/apps/web/components/convert/index.ts +12 -0
- package/apps/web/components/deploy/DeployButton.tsx +369 -0
- package/apps/web/components/deploy/index.ts +7 -0
- package/apps/web/components/docker/DockerExport.tsx +690 -0
- package/apps/web/components/docker/index.ts +7 -0
- package/apps/web/components/install/OneClickInstall.tsx +676 -0
- package/apps/web/components/install/index.ts +7 -0
- package/apps/web/components/playground/CapabilityTabs.tsx +150 -0
- package/apps/web/components/playground/ConnectionStatusV2.tsx +322 -0
- package/apps/web/components/playground/EmptyStates.tsx +305 -0
- package/apps/web/components/playground/ExecutionLog.tsx +260 -0
- package/apps/web/components/playground/ExecutionLogV2.tsx +378 -0
- package/apps/web/components/playground/JsonViewer.tsx +388 -0
- package/apps/web/components/playground/PlaygroundLayout.tsx +244 -0
- package/apps/web/components/playground/PromptsPanel.tsx +385 -0
- package/apps/web/components/playground/ResourcesPanel.tsx +378 -0
- package/apps/web/components/playground/SchemaForm.tsx +477 -0
- package/apps/web/components/playground/ServerStatus.tsx +151 -0
- package/apps/web/components/playground/ShareButton.tsx +239 -0
- package/apps/web/components/playground/ToolsPanel.tsx +309 -0
- package/apps/web/components/playground/TransportConfigurator.tsx +563 -0
- package/apps/web/components/playground/index.ts +74 -0
- package/apps/web/components/playground/types.ts +202 -0
- package/apps/web/components/streaming/StreamingProgress.tsx +441 -0
- package/apps/web/components/streaming/index.ts +7 -0
- package/apps/web/components/ui/badge.tsx +42 -0
- package/apps/web/components/ui/button.tsx +88 -0
- package/apps/web/components/ui/card.tsx +75 -0
- package/apps/web/components/ui/code-block.tsx +122 -0
- package/apps/web/components/ui/index.ts +12 -0
- package/apps/web/components/ui/input.tsx +55 -0
- package/apps/web/components/ui/tabs.tsx +61 -0
- package/apps/web/hooks/index.ts +85 -0
- package/apps/web/hooks/types.ts +1173 -0
- package/apps/web/hooks/use-conversion.ts +133 -0
- package/apps/web/hooks/use-execution-history.ts +376 -0
- package/apps/web/hooks/use-generation-progress.ts +147 -0
- package/apps/web/hooks/use-local-storage.ts +88 -0
- package/apps/web/hooks/use-mcp-client.ts +623 -0
- package/apps/web/hooks/use-mcp-connection.ts +500 -0
- package/apps/web/hooks/use-mcp-execution.ts +282 -0
- package/apps/web/hooks/use-mcp-prompts.ts +441 -0
- package/apps/web/hooks/use-mcp-resources.ts +430 -0
- package/apps/web/hooks/use-mcp-tools.ts +540 -0
- package/apps/web/hooks/use-playground-store.ts +299 -0
- package/apps/web/hooks/use-playground.ts +184 -0
- package/apps/web/hooks/use-streaming-conversion.ts +227 -0
- package/apps/web/hooks/useBatchConversion.ts +271 -0
- package/apps/web/hooks/useDockerConfig.ts +161 -0
- package/apps/web/hooks/usePlatformDetection.ts +80 -0
- package/apps/web/hooks/useStreaming.ts +199 -0
- package/apps/web/lib/api/errors.ts +386 -0
- package/apps/web/lib/api/index.ts +137 -0
- package/apps/web/lib/api/logger.ts +187 -0
- package/apps/web/lib/api/middleware.ts +364 -0
- package/apps/web/lib/api/openapi.ts +977 -0
- package/apps/web/lib/api/session-manager.ts +594 -0
- package/apps/web/lib/api/types.ts +433 -0
- package/apps/web/lib/api/validation.ts +523 -0
- package/apps/web/lib/constants.ts +114 -0
- package/apps/web/lib/mcp/client.ts +1137 -0
- package/apps/web/lib/mcp/events.ts +651 -0
- package/apps/web/lib/mcp/index.ts +347 -0
- package/apps/web/lib/mcp/logger.ts +428 -0
- package/apps/web/lib/mcp/metrics.ts +703 -0
- package/apps/web/lib/mcp/retry.ts +616 -0
- package/apps/web/lib/mcp/session-manager.ts +779 -0
- package/apps/web/lib/mcp/transports.ts +988 -0
- package/apps/web/lib/mcp/types.ts +594 -0
- package/apps/web/lib/mcp-client-enhanced.ts +871 -0
- package/apps/web/lib/mcp-client.ts +778 -0
- package/apps/web/lib/mcp-errors.ts +489 -0
- package/apps/web/lib/mcp-sandbox.ts +593 -0
- package/apps/web/lib/mcp-testing.ts +428 -0
- package/apps/web/lib/mcp-types.ts +448 -0
- package/apps/web/lib/playground-store.tsx +1147 -0
- package/apps/web/lib/utils.ts +439 -0
- package/apps/web/next-env.d.ts +5 -0
- package/apps/web/next.config.js +23 -0
- package/apps/web/package.json +55 -0
- package/apps/web/postcss.config.js +6 -0
- package/apps/web/public/.well-known/ai-plugin.json +17 -0
- package/apps/web/public/logo.svg +6 -0
- package/apps/web/public/robots.txt +22 -0
- package/apps/web/public/schema.json +27 -0
- package/apps/web/tailwind.config.js +26 -0
- package/apps/web/tailwind.config.ts +123 -0
- package/apps/web/tsconfig.json +20 -0
- package/apps/web/types/deploy.ts +139 -0
- package/apps/web/types/index.ts +247 -0
- package/apps/web/vercel.json +39 -0
- package/eslint.config.mjs +23 -0
- package/llms.txt +102 -0
- package/mkdocs/docs/api/core.md +318 -0
- package/mkdocs/docs/api/index.md +128 -0
- package/mkdocs/docs/api/mcp-server.md +301 -0
- package/mkdocs/docs/api/openapi-parser.md +254 -0
- package/mkdocs/docs/assets/logo.svg +7 -0
- package/mkdocs/docs/changelog.md +118 -0
- package/mkdocs/docs/cli/generate.md +148 -0
- package/mkdocs/docs/cli/index.md +52 -0
- package/mkdocs/docs/cli/inspect.md +164 -0
- package/mkdocs/docs/cli/serve.md +136 -0
- package/mkdocs/docs/concepts/classification.md +254 -0
- package/mkdocs/docs/concepts/how-it-works.md +299 -0
- package/mkdocs/docs/concepts/index.md +77 -0
- package/mkdocs/docs/concepts/mcp-protocol.md +362 -0
- package/mkdocs/docs/concepts/tool-types.md +382 -0
- package/mkdocs/docs/contributing/architecture.md +262 -0
- package/mkdocs/docs/contributing/development.md +245 -0
- package/mkdocs/docs/contributing/index.md +73 -0
- package/mkdocs/docs/contributing/testing.md +320 -0
- package/mkdocs/docs/getting-started/configuration.md +235 -0
- package/mkdocs/docs/getting-started/index.md +54 -0
- package/mkdocs/docs/getting-started/installation.md +145 -0
- package/mkdocs/docs/getting-started/quickstart.md +160 -0
- package/mkdocs/docs/guides/batch.md +375 -0
- package/mkdocs/docs/guides/claude-desktop.md +227 -0
- package/mkdocs/docs/guides/cursor.md +188 -0
- package/mkdocs/docs/guides/custom-tools.md +367 -0
- package/mkdocs/docs/guides/index.md +78 -0
- package/mkdocs/docs/guides/private-repos.md +221 -0
- package/mkdocs/docs/guides/vscode.md +247 -0
- package/mkdocs/docs/index.md +175 -0
- package/mkdocs/docs/reference/config.md +223 -0
- package/mkdocs/docs/reference/env.md +192 -0
- package/mkdocs/docs/reference/index.md +102 -0
- package/mkdocs/docs/reference/tools.md +309 -0
- package/mkdocs/docs/stylesheets/extra.css +231 -0
- package/mkdocs/mkdocs.yml +204 -0
- package/mkdocs/overrides/.gitkeep +1 -0
- package/mkdocs/overrides/main.html +7 -0
- package/mkdocs/python-deps.txt +7 -0
- package/mkdocs/vercel.json +11 -0
- package/package.json +63 -0
- package/packages/core/package.json +61 -0
- package/packages/core/src/__tests__/bitbucket-client.test.ts +366 -0
- package/packages/core/src/__tests__/cli.test.ts +235 -0
- package/packages/core/src/__tests__/code-extractor.test.ts +378 -0
- package/packages/core/src/__tests__/docker-generator.test.ts +255 -0
- package/packages/core/src/__tests__/github-client.test.ts +390 -0
- package/packages/core/src/__tests__/gitlab-client.test.ts +319 -0
- package/packages/core/src/__tests__/go-extractor.test.ts +351 -0
- package/packages/core/src/__tests__/graphql-extractor.test.ts +330 -0
- package/packages/core/src/__tests__/java-extractor.test.ts +497 -0
- package/packages/core/src/__tests__/plugins.test.ts +467 -0
- package/packages/core/src/__tests__/readme-extractor.test.ts +258 -0
- package/packages/core/src/__tests__/redis-cache.test.ts +307 -0
- package/packages/core/src/__tests__/rust-extractor.test.ts +252 -0
- package/packages/core/src/__tests__/streaming.test.ts +251 -0
- package/packages/core/src/additional-extractors.ts +333 -0
- package/packages/core/src/cache/cache-interface.ts +179 -0
- package/packages/core/src/cache/index.ts +210 -0
- package/packages/core/src/cache/redis-cache.ts +291 -0
- package/packages/core/src/cache/upstash-cache.ts +379 -0
- package/packages/core/src/cache.ts +251 -0
- package/packages/core/src/cli.ts +822 -0
- package/packages/core/src/code-extractor.ts +696 -0
- package/packages/core/src/docker-generator.ts +470 -0
- package/packages/core/src/edge-compatible.ts +491 -0
- package/packages/core/src/extractors/go-extractor.ts +791 -0
- package/packages/core/src/extractors/index.ts +9 -0
- package/packages/core/src/extractors/java-extractor.ts +937 -0
- package/packages/core/src/extractors/rust-extractor.ts +744 -0
- package/packages/core/src/github-client.ts +319 -0
- package/packages/core/src/go-generator.ts +356 -0
- package/packages/core/src/graphql-extractor.ts +358 -0
- package/packages/core/src/index.ts +797 -0
- package/packages/core/src/langchain-exporter.ts +617 -0
- package/packages/core/src/language-parsers.ts +1114 -0
- package/packages/core/src/mcp-introspector.ts +279 -0
- package/packages/core/src/monorepo-detector.ts +378 -0
- package/packages/core/src/plugins/index.ts +370 -0
- package/packages/core/src/plugins/registry.ts +404 -0
- package/packages/core/src/plugins/types.ts +215 -0
- package/packages/core/src/providers/base-provider.ts +246 -0
- package/packages/core/src/providers/bitbucket-client.ts +464 -0
- package/packages/core/src/providers/gitlab-client.ts +388 -0
- package/packages/core/src/providers/index.ts +176 -0
- package/packages/core/src/python-generator.ts +260 -0
- package/packages/core/src/queue/index.ts +100 -0
- package/packages/core/src/queue/memory-queue.ts +445 -0
- package/packages/core/src/queue/redis-queue.ts +578 -0
- package/packages/core/src/queue/types.ts +251 -0
- package/packages/core/src/readme-extractor.ts +409 -0
- package/packages/core/src/schema-generator.ts +638 -0
- package/packages/core/src/streaming.ts +999 -0
- package/packages/core/src/types.ts +289 -0
- package/packages/core/tsconfig.json +9 -0
- package/packages/core/tsup.config.ts +25 -0
- package/packages/mcp-server/README.md +297 -0
- package/packages/mcp-server/package.json +55 -0
- package/packages/mcp-server/src/__tests__/mcp-server.test.ts +177 -0
- package/packages/mcp-server/src/__tests__/tools.test.ts +217 -0
- package/packages/mcp-server/src/index.ts +1206 -0
- package/packages/mcp-server/src/prompts/index.ts +601 -0
- package/packages/mcp-server/src/tools/export-docker.ts +362 -0
- package/packages/mcp-server/src/tools/generate-openapi.ts +162 -0
- package/packages/mcp-server/src/tools/monitor-mcp-server.ts +448 -0
- package/packages/mcp-server/src/tools/stream-convert.ts +398 -0
- package/packages/mcp-server/src/tools/test-mcp-tool.ts +531 -0
- package/packages/mcp-server/tsconfig.json +12 -0
- package/packages/mcp-server/tsup.config.ts +14 -0
- package/packages/openapi-parser/package-lock.json +3028 -0
- package/packages/openapi-parser/package.json +41 -0
- package/packages/openapi-parser/src/analyzer.ts +700 -0
- package/packages/openapi-parser/src/asyncapi-parser.ts +475 -0
- package/packages/openapi-parser/src/cli.ts +302 -0
- package/packages/openapi-parser/src/generator.ts +570 -0
- package/packages/openapi-parser/src/generators/express-analyzer.ts +649 -0
- package/packages/openapi-parser/src/generators/fastapi-analyzer.ts +960 -0
- package/packages/openapi-parser/src/generators/index.ts +200 -0
- package/packages/openapi-parser/src/generators/nextjs-analyzer.ts +768 -0
- package/packages/openapi-parser/src/generators/openapi-builder.ts +527 -0
- package/packages/openapi-parser/src/generators/types.ts +298 -0
- package/packages/openapi-parser/src/graphql-parser.ts +462 -0
- package/packages/openapi-parser/src/grpc-parser.ts +649 -0
- package/packages/openapi-parser/src/har-parser.ts +723 -0
- package/packages/openapi-parser/src/index.ts +635 -0
- package/packages/openapi-parser/src/insomnia-parser.ts +614 -0
- package/packages/openapi-parser/src/parser.ts +231 -0
- package/packages/openapi-parser/src/postman-parser.ts +611 -0
- package/packages/openapi-parser/src/ref-resolver.ts +313 -0
- package/packages/openapi-parser/src/transformer.ts +459 -0
- package/packages/openapi-parser/tests/generators/express.test.ts +209 -0
- package/packages/openapi-parser/tests/generators/fastapi.test.ts +236 -0
- package/packages/openapi-parser/tests/generators/nextjs.test.ts +273 -0
- package/packages/openapi-parser/tests/parsers.test.ts +847 -0
- package/packages/openapi-parser/tsconfig.json +9 -0
- package/packages/openapi-parser/tsup.config.ts +11 -0
- package/packages/registry/package.json +59 -0
- package/packages/registry/src/cli.ts +456 -0
- package/packages/registry/src/index.ts +44 -0
- package/packages/registry/src/popular/github.json +47 -0
- package/packages/registry/src/popular/index.ts +55 -0
- package/packages/registry/src/popular/linear.json +42 -0
- package/packages/registry/src/popular/notion.json +42 -0
- package/packages/registry/src/popular/openai.json +40 -0
- package/packages/registry/src/popular/resend.json +38 -0
- package/packages/registry/src/popular/slack.json +42 -0
- package/packages/registry/src/popular/stripe.json +163 -0
- package/packages/registry/src/popular/supabase.json +42 -0
- package/packages/registry/src/popular/twilio.json +40 -0
- package/packages/registry/src/popular/vercel.json +40 -0
- package/packages/registry/src/registry.ts +492 -0
- package/packages/registry/src/storage.ts +334 -0
- package/packages/registry/src/types.ts +275 -0
- package/packages/registry/src/updater.ts +208 -0
- package/packages/registry/tsconfig.json +10 -0
- package/packages/registry/tsup.config.ts +11 -0
- package/pnpm-workspace.yaml +3 -0
- package/scripts/build-docs.sh +16 -0
- package/server.json +9 -0
- package/templates/Dockerfile.python.template +60 -0
- package/templates/Dockerfile.typescript.template +60 -0
- package/templates/docker-compose.template.yml +68 -0
- package/tests/fixtures/express-app/index.js +34 -0
- package/tests/fixtures/express-app/routes/posts.js +43 -0
- package/tests/fixtures/express-app/routes/users.js +58 -0
- package/tests/fixtures/fastapi-app/main.py +125 -0
- package/tests/fixtures/fastapi-app/routes/admin.py +42 -0
- package/tests/fixtures/graphql/simple-schema.graphql +65 -0
- package/tests/fixtures/mocks/github-api-responses.json +63 -0
- package/tests/fixtures/nextjs-app/app/api/posts/route.ts +55 -0
- package/tests/fixtures/nextjs-app/app/api/users/[id]/route.ts +63 -0
- package/tests/fixtures/nextjs-app/app/api/users/route.ts +44 -0
- package/tests/fixtures/nextjs-app/pages/api/health.ts +28 -0
- package/tests/fixtures/openapi/petstore.yaml +179 -0
- package/tests/integration/langchain-export.test.ts +405 -0
- package/tests/integration/openapi-conversion.test.ts +221 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +32 -0
|
@@ -0,0 +1,1137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Client Wrapper - High-level client for MCP protocol operations
|
|
3
|
+
* @copyright 2024-2026 nirholas
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
8
|
+
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
9
|
+
|
|
10
|
+
import { createTransport } from './transports.js';
|
|
11
|
+
import type {
|
|
12
|
+
TransportConfig,
|
|
13
|
+
McpCapabilities,
|
|
14
|
+
McpTool,
|
|
15
|
+
McpToolCallResult,
|
|
16
|
+
McpResource,
|
|
17
|
+
McpResourceContents,
|
|
18
|
+
McpPrompt,
|
|
19
|
+
McpPromptResult,
|
|
20
|
+
McpClientOptions,
|
|
21
|
+
McpExecutionResult,
|
|
22
|
+
ConnectionState,
|
|
23
|
+
} from './types.js';
|
|
24
|
+
import {
|
|
25
|
+
McpError,
|
|
26
|
+
McpErrorCode,
|
|
27
|
+
McpConnectionError,
|
|
28
|
+
McpTimeoutError,
|
|
29
|
+
DEFAULT_CLIENT_OPTIONS,
|
|
30
|
+
} from './types.js';
|
|
31
|
+
import type { Logger } from './logger.js';
|
|
32
|
+
import { createNoopLogger } from './logger.js';
|
|
33
|
+
import type { McpEventEmitter, McpEvent } from './events.js';
|
|
34
|
+
import { createEventEmitter } from './events.js';
|
|
35
|
+
import type { RetryConfig } from './retry.js';
|
|
36
|
+
import { retry, DEFAULT_RETRY_CONFIG } from './retry.js';
|
|
37
|
+
import type { McpClientMetrics } from './metrics.js';
|
|
38
|
+
import { createClientMetrics } from './metrics.js';
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Enhanced Client Types
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Middleware function type for request interception
|
|
46
|
+
*/
|
|
47
|
+
export type MiddlewareFunction = (
|
|
48
|
+
request: MiddlewareRequest,
|
|
49
|
+
next: () => Promise<MiddlewareResponse>
|
|
50
|
+
) => Promise<MiddlewareResponse>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Middleware request context
|
|
54
|
+
*/
|
|
55
|
+
export interface MiddlewareRequest {
|
|
56
|
+
readonly method: string;
|
|
57
|
+
readonly params: Record<string, unknown>;
|
|
58
|
+
readonly requestId: string;
|
|
59
|
+
readonly timestamp: Date;
|
|
60
|
+
readonly signal?: AbortSignal;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Middleware response
|
|
65
|
+
*/
|
|
66
|
+
export interface MiddlewareResponse {
|
|
67
|
+
readonly success: boolean;
|
|
68
|
+
readonly data?: unknown;
|
|
69
|
+
readonly error?: Error;
|
|
70
|
+
readonly durationMs: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Batch operation item
|
|
75
|
+
*/
|
|
76
|
+
export interface BatchToolCall {
|
|
77
|
+
readonly name: string;
|
|
78
|
+
readonly params?: Record<string, unknown>;
|
|
79
|
+
readonly signal?: AbortSignal;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Batch result
|
|
84
|
+
*/
|
|
85
|
+
export interface BatchResult<T> {
|
|
86
|
+
readonly results: Array<McpExecutionResult<T>>;
|
|
87
|
+
readonly totalDurationMs: number;
|
|
88
|
+
readonly successCount: number;
|
|
89
|
+
readonly failureCount: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Enhanced client options
|
|
94
|
+
*/
|
|
95
|
+
export interface EnhancedClientOptions extends McpClientOptions {
|
|
96
|
+
readonly logger?: Logger;
|
|
97
|
+
readonly events?: McpEventEmitter;
|
|
98
|
+
readonly retryConfig?: Partial<RetryConfig>;
|
|
99
|
+
readonly enableMetrics?: boolean;
|
|
100
|
+
readonly middleware?: MiddlewareFunction[];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// McpClient Class
|
|
105
|
+
// ============================================================================
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Generate a unique request ID
|
|
109
|
+
*/
|
|
110
|
+
function generateRequestId(): string {
|
|
111
|
+
return `req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* High-level MCP client wrapper that provides a clean API for interacting
|
|
116
|
+
* with MCP servers using the official SDK.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```typescript
|
|
120
|
+
* const client = new McpClient({
|
|
121
|
+
* name: 'my-app',
|
|
122
|
+
* version: '1.0.0',
|
|
123
|
+
* logger: createConsoleLogger(),
|
|
124
|
+
* enableMetrics: true,
|
|
125
|
+
* });
|
|
126
|
+
*
|
|
127
|
+
* // Subscribe to events
|
|
128
|
+
* client.events.on('tool:complete', (event) => {
|
|
129
|
+
* console.log(`Tool ${event.toolName} completed in ${event.durationMs}ms`);
|
|
130
|
+
* });
|
|
131
|
+
*
|
|
132
|
+
* await client.connect({
|
|
133
|
+
* type: 'stdio',
|
|
134
|
+
* command: 'npx',
|
|
135
|
+
* args: ['tsx', 'server.ts'],
|
|
136
|
+
* });
|
|
137
|
+
*
|
|
138
|
+
* // Use with abort signal for cancellation
|
|
139
|
+
* const controller = new AbortController();
|
|
140
|
+
* const result = await client.callTool('my-tool', { param: 'value' }, { signal: controller.signal });
|
|
141
|
+
*
|
|
142
|
+
* // Batch operations
|
|
143
|
+
* const batch = await client.callToolsBatch([
|
|
144
|
+
* { name: 'tool1', params: { a: 1 } },
|
|
145
|
+
* { name: 'tool2', params: { b: 2 } },
|
|
146
|
+
* ]);
|
|
147
|
+
*
|
|
148
|
+
* await client.disconnect();
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
export class McpClient {
|
|
152
|
+
private readonly _options: Required<McpClientOptions>;
|
|
153
|
+
private readonly _logger: Logger;
|
|
154
|
+
private readonly _events: McpEventEmitter;
|
|
155
|
+
private readonly _metrics: McpClientMetrics | null;
|
|
156
|
+
private readonly _retryConfig: RetryConfig;
|
|
157
|
+
private readonly _middleware: MiddlewareFunction[];
|
|
158
|
+
|
|
159
|
+
private _client: Client | null = null;
|
|
160
|
+
private _transport: Transport | null = null;
|
|
161
|
+
private _state: ConnectionState = 'disconnected';
|
|
162
|
+
private _capabilities: McpCapabilities | undefined;
|
|
163
|
+
private _serverInfo: { name: string; version: string } | undefined;
|
|
164
|
+
private _activeRequests: Map<string, AbortController> = new Map();
|
|
165
|
+
private _cachedTools: readonly McpTool[] | null = null;
|
|
166
|
+
private _cachedResources: readonly McpResource[] | null = null;
|
|
167
|
+
private _cachedPrompts: readonly McpPrompt[] | null = null;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Creates a new McpClient instance.
|
|
171
|
+
*
|
|
172
|
+
* @param options - Client configuration options
|
|
173
|
+
*/
|
|
174
|
+
constructor(options: EnhancedClientOptions) {
|
|
175
|
+
this._options = {
|
|
176
|
+
...DEFAULT_CLIENT_OPTIONS,
|
|
177
|
+
...options,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
this._logger = options.logger ?? createNoopLogger();
|
|
181
|
+
this._events = options.events ?? createEventEmitter();
|
|
182
|
+
this._metrics = options.enableMetrics ? createClientMetrics() : null;
|
|
183
|
+
this._retryConfig = { ...DEFAULT_RETRY_CONFIG, ...options.retryConfig };
|
|
184
|
+
this._middleware = options.middleware ?? [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ============================================================================
|
|
188
|
+
// Public Properties
|
|
189
|
+
// ============================================================================
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Current connection state of the client.
|
|
193
|
+
*/
|
|
194
|
+
get state(): ConnectionState {
|
|
195
|
+
return this._state;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Whether the client is currently connected.
|
|
200
|
+
*/
|
|
201
|
+
get isConnected(): boolean {
|
|
202
|
+
return this._state === 'connected';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Server capabilities discovered during initialization.
|
|
207
|
+
* Only available after successful connection.
|
|
208
|
+
*/
|
|
209
|
+
get capabilities(): McpCapabilities | undefined {
|
|
210
|
+
return this._capabilities;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Server information from initialization.
|
|
215
|
+
* Only available after successful connection.
|
|
216
|
+
*/
|
|
217
|
+
get serverInfo(): Readonly<{ name: string; version: string }> | undefined {
|
|
218
|
+
return this._serverInfo;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Event emitter for subscribing to client events.
|
|
223
|
+
*/
|
|
224
|
+
get events(): McpEventEmitter {
|
|
225
|
+
return this._events;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Client metrics (if enabled).
|
|
230
|
+
*/
|
|
231
|
+
get metrics(): McpClientMetrics | null {
|
|
232
|
+
return this._metrics;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ============================================================================
|
|
236
|
+
// Connection Management
|
|
237
|
+
// ============================================================================
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Connects to an MCP server using the specified transport configuration.
|
|
241
|
+
*
|
|
242
|
+
* @param config - Transport configuration
|
|
243
|
+
* @param signal - Optional abort signal for cancellation
|
|
244
|
+
* @throws {McpConnectionError} If connection fails
|
|
245
|
+
* @throws {McpTimeoutError} If connection times out
|
|
246
|
+
*/
|
|
247
|
+
async connect(config: TransportConfig, signal?: AbortSignal): Promise<void> {
|
|
248
|
+
if (this._state === 'connected') {
|
|
249
|
+
throw new McpConnectionError(
|
|
250
|
+
McpErrorCode.AlreadyConnected,
|
|
251
|
+
'Client is already connected. Call disconnect() first.'
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (this._state === 'connecting') {
|
|
256
|
+
throw new McpConnectionError(
|
|
257
|
+
McpErrorCode.AlreadyConnected,
|
|
258
|
+
'Connection already in progress.'
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const previousState = this._state;
|
|
263
|
+
this._state = 'connecting';
|
|
264
|
+
|
|
265
|
+
this._emitStateChange(previousState, 'connecting');
|
|
266
|
+
this._metrics?.connectionAttempts.increment();
|
|
267
|
+
|
|
268
|
+
const timer = this._metrics?.connectionDuration.start();
|
|
269
|
+
this._logger.info('Connecting to MCP server...');
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
// Check for cancellation
|
|
273
|
+
if (signal?.aborted) {
|
|
274
|
+
throw new DOMException('Connection aborted', 'AbortError');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Create transport
|
|
278
|
+
this._transport = createTransport(config);
|
|
279
|
+
|
|
280
|
+
// Create client
|
|
281
|
+
this._client = new Client(
|
|
282
|
+
{
|
|
283
|
+
name: this._options.name,
|
|
284
|
+
version: this._options.version,
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
capabilities: {
|
|
288
|
+
// Request sampling capability for completions
|
|
289
|
+
sampling: {},
|
|
290
|
+
},
|
|
291
|
+
}
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
// Set up transport event handlers
|
|
295
|
+
this._setupTransportHandlers();
|
|
296
|
+
|
|
297
|
+
// Connect with timeout
|
|
298
|
+
await this._connectWithTimeout(signal);
|
|
299
|
+
|
|
300
|
+
// Extract capabilities and server info
|
|
301
|
+
this._extractServerInfo();
|
|
302
|
+
|
|
303
|
+
this._state = 'connected';
|
|
304
|
+
this._metrics?.connectionSuccesses.increment();
|
|
305
|
+
this._metrics?.activeConnections.increment();
|
|
306
|
+
timer?.end();
|
|
307
|
+
|
|
308
|
+
this._emitStateChange('connecting', 'connected');
|
|
309
|
+
this._events.emit({
|
|
310
|
+
type: 'connection:established',
|
|
311
|
+
serverName: this._serverInfo?.name ?? 'unknown',
|
|
312
|
+
serverVersion: this._serverInfo?.version ?? 'unknown',
|
|
313
|
+
timestamp: new Date(),
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
this._logger.info('Connected successfully', {
|
|
317
|
+
data: {
|
|
318
|
+
serverName: this._serverInfo?.name,
|
|
319
|
+
serverVersion: this._serverInfo?.version,
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
} catch (error) {
|
|
323
|
+
this._state = 'error';
|
|
324
|
+
this._metrics?.connectionFailures.increment();
|
|
325
|
+
timer?.cancel();
|
|
326
|
+
|
|
327
|
+
this._emitStateChange('connecting', 'error');
|
|
328
|
+
await this._cleanup();
|
|
329
|
+
|
|
330
|
+
if (error instanceof McpError) {
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
throw new McpConnectionError(
|
|
335
|
+
McpErrorCode.ConnectionFailed,
|
|
336
|
+
`Failed to connect: ${error instanceof Error ? error.message : String(error)}`,
|
|
337
|
+
error
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Disconnects from the MCP server and cleans up resources.
|
|
344
|
+
*/
|
|
345
|
+
async disconnect(): Promise<void> {
|
|
346
|
+
if (this._state === 'disconnected') {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const previousState = this._state;
|
|
351
|
+
this._logger.info('Disconnecting from MCP server...');
|
|
352
|
+
|
|
353
|
+
// Cancel all active requests
|
|
354
|
+
this._cancelAllRequests();
|
|
355
|
+
|
|
356
|
+
// Clear caches
|
|
357
|
+
this._cachedTools = null;
|
|
358
|
+
this._cachedResources = null;
|
|
359
|
+
this._cachedPrompts = null;
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
await this._cleanup();
|
|
363
|
+
} finally {
|
|
364
|
+
this._state = 'disconnected';
|
|
365
|
+
this._capabilities = undefined;
|
|
366
|
+
this._serverInfo = undefined;
|
|
367
|
+
this._metrics?.activeConnections.decrement();
|
|
368
|
+
|
|
369
|
+
this._emitStateChange(previousState, 'disconnected');
|
|
370
|
+
this._events.emit({
|
|
371
|
+
type: 'connection:closed',
|
|
372
|
+
reason: 'manual',
|
|
373
|
+
wasClean: true,
|
|
374
|
+
timestamp: new Date(),
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
this._logger.info('Disconnected');
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Cancel all active requests.
|
|
383
|
+
*/
|
|
384
|
+
cancelAllRequests(): void {
|
|
385
|
+
this._cancelAllRequests();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ============================================================================
|
|
389
|
+
// Tool Operations
|
|
390
|
+
// ============================================================================
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Lists all tools available on the MCP server.
|
|
394
|
+
*
|
|
395
|
+
* @param options - Request options
|
|
396
|
+
* @returns Array of tool definitions
|
|
397
|
+
* @throws {McpConnectionError} If not connected
|
|
398
|
+
* @throws {McpTimeoutError} If request times out
|
|
399
|
+
*/
|
|
400
|
+
async listTools(options?: { signal?: AbortSignal; useCache?: boolean }): Promise<readonly McpTool[]> {
|
|
401
|
+
this._ensureConnected();
|
|
402
|
+
|
|
403
|
+
// Return cached if available and requested
|
|
404
|
+
if (options?.useCache && this._cachedTools) {
|
|
405
|
+
return this._cachedTools;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const requestId = generateRequestId();
|
|
409
|
+
const startTime = Date.now();
|
|
410
|
+
|
|
411
|
+
this._logger.debug('Listing tools...', { requestId });
|
|
412
|
+
this._emitRequestStart(requestId, 'listTools');
|
|
413
|
+
|
|
414
|
+
try {
|
|
415
|
+
const result = await this._executeWithMiddleware(
|
|
416
|
+
{
|
|
417
|
+
method: 'listTools',
|
|
418
|
+
params: {},
|
|
419
|
+
requestId,
|
|
420
|
+
timestamp: new Date(),
|
|
421
|
+
signal: options?.signal,
|
|
422
|
+
},
|
|
423
|
+
async () => {
|
|
424
|
+
const response = await this._executeWithTimeout(
|
|
425
|
+
async () => this._client!.listTools(),
|
|
426
|
+
'listTools',
|
|
427
|
+
options?.signal
|
|
428
|
+
);
|
|
429
|
+
return { success: true, data: response, durationMs: Date.now() - startTime };
|
|
430
|
+
}
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
if (!result.success || !result.data) {
|
|
434
|
+
throw result.error ?? new McpError(McpErrorCode.InternalError, 'Failed to list tools');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const responseData = result.data as { tools?: Array<{ name: string; description?: string; inputSchema: unknown }> };
|
|
438
|
+
const tools: McpTool[] = (responseData.tools ?? []).map((tool) => ({
|
|
439
|
+
name: tool.name,
|
|
440
|
+
description: tool.description,
|
|
441
|
+
inputSchema: tool.inputSchema as McpTool['inputSchema'],
|
|
442
|
+
}));
|
|
443
|
+
|
|
444
|
+
// Cache results
|
|
445
|
+
this._cachedTools = tools;
|
|
446
|
+
|
|
447
|
+
const durationMs = Date.now() - startTime;
|
|
448
|
+
this._emitRequestComplete(requestId, 'listTools', durationMs);
|
|
449
|
+
this._events.emit({
|
|
450
|
+
type: 'tools:changed',
|
|
451
|
+
tools,
|
|
452
|
+
timestamp: new Date(),
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
this._logger.debug(`Found ${tools.length} tools`, { data: { count: tools.length, durationMs } });
|
|
456
|
+
return tools;
|
|
457
|
+
} catch (error) {
|
|
458
|
+
const durationMs = Date.now() - startTime;
|
|
459
|
+
this._emitRequestError(requestId, 'listTools', error, durationMs);
|
|
460
|
+
throw this._wrapError(error, 'Failed to list tools');
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Calls a tool on the MCP server.
|
|
466
|
+
*
|
|
467
|
+
* @param name - Name of the tool to call
|
|
468
|
+
* @param params - Parameters to pass to the tool
|
|
469
|
+
* @param options - Request options
|
|
470
|
+
* @returns Tool execution result
|
|
471
|
+
*/
|
|
472
|
+
async callTool(
|
|
473
|
+
name: string,
|
|
474
|
+
params: Record<string, unknown> = {},
|
|
475
|
+
options?: { signal?: AbortSignal; retryEnabled?: boolean }
|
|
476
|
+
): Promise<McpExecutionResult<McpToolCallResult>> {
|
|
477
|
+
this._ensureConnected();
|
|
478
|
+
|
|
479
|
+
const requestId = generateRequestId();
|
|
480
|
+
const startTime = Date.now();
|
|
481
|
+
|
|
482
|
+
this._logger.debug(`Calling tool: ${name}`, { requestId, data: { toolName: name } });
|
|
483
|
+
this._metrics?.toolCalls.increment();
|
|
484
|
+
|
|
485
|
+
this._events.emit({
|
|
486
|
+
type: 'tool:start',
|
|
487
|
+
toolName: name,
|
|
488
|
+
requestId,
|
|
489
|
+
params,
|
|
490
|
+
timestamp: new Date(),
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
const timer = this._metrics?.toolCallDuration.start();
|
|
494
|
+
|
|
495
|
+
try {
|
|
496
|
+
const executeToolCall = async (): Promise<McpExecutionResult<McpToolCallResult>> => {
|
|
497
|
+
const result = await this._executeWithMiddleware(
|
|
498
|
+
{
|
|
499
|
+
method: 'callTool',
|
|
500
|
+
params: { name, arguments: params },
|
|
501
|
+
requestId,
|
|
502
|
+
timestamp: new Date(),
|
|
503
|
+
signal: options?.signal,
|
|
504
|
+
},
|
|
505
|
+
async () => {
|
|
506
|
+
const response = await this._executeWithTimeout(
|
|
507
|
+
async () => this._client!.callTool({ name, arguments: params }),
|
|
508
|
+
'callTool',
|
|
509
|
+
options?.signal
|
|
510
|
+
);
|
|
511
|
+
return { success: true, data: response, durationMs: Date.now() - startTime };
|
|
512
|
+
}
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
if (!result.success || !result.data) {
|
|
516
|
+
throw result.error ?? new McpError(McpErrorCode.InternalError, `Tool '${name}' failed`);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const responseData = result.data as { content: McpToolCallResult['content']; isError?: boolean };
|
|
520
|
+
return {
|
|
521
|
+
success: true,
|
|
522
|
+
data: {
|
|
523
|
+
content: responseData.content,
|
|
524
|
+
isError: responseData.isError === true,
|
|
525
|
+
},
|
|
526
|
+
executionTimeMs: Date.now() - startTime,
|
|
527
|
+
};
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
let finalResult: McpExecutionResult<McpToolCallResult>;
|
|
531
|
+
|
|
532
|
+
if (options?.retryEnabled !== false) {
|
|
533
|
+
const retryResult = await retry(executeToolCall, this._retryConfig);
|
|
534
|
+
if (!retryResult.success) {
|
|
535
|
+
throw retryResult.error;
|
|
536
|
+
}
|
|
537
|
+
finalResult = retryResult.value!;
|
|
538
|
+
} else {
|
|
539
|
+
finalResult = await executeToolCall();
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const durationMs = Date.now() - startTime;
|
|
543
|
+
timer?.end();
|
|
544
|
+
|
|
545
|
+
if (finalResult.success) {
|
|
546
|
+
const resultData = finalResult.data;
|
|
547
|
+
this._events.emit({
|
|
548
|
+
type: 'tool:complete',
|
|
549
|
+
toolName: name,
|
|
550
|
+
requestId,
|
|
551
|
+
result: resultData,
|
|
552
|
+
durationMs,
|
|
553
|
+
timestamp: new Date(),
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
this._logger.debug(`Tool '${name}' completed`, { data: { durationMs } });
|
|
558
|
+
return finalResult;
|
|
559
|
+
} catch (error) {
|
|
560
|
+
const durationMs = Date.now() - startTime;
|
|
561
|
+
timer?.cancel();
|
|
562
|
+
this._metrics?.toolCallErrors.increment();
|
|
563
|
+
|
|
564
|
+
const wrappedError = this._wrapError(error, `Tool '${name}' failed`);
|
|
565
|
+
|
|
566
|
+
this._events.emit({
|
|
567
|
+
type: 'tool:error',
|
|
568
|
+
toolName: name,
|
|
569
|
+
requestId,
|
|
570
|
+
error: wrappedError,
|
|
571
|
+
durationMs,
|
|
572
|
+
timestamp: new Date(),
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
return {
|
|
576
|
+
success: false,
|
|
577
|
+
error: wrappedError,
|
|
578
|
+
executionTimeMs: durationMs,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Call multiple tools in batch.
|
|
585
|
+
*
|
|
586
|
+
* @param calls - Array of tool calls to execute
|
|
587
|
+
* @param options - Batch options
|
|
588
|
+
* @returns Batch result with all results
|
|
589
|
+
*/
|
|
590
|
+
async callToolsBatch(
|
|
591
|
+
calls: BatchToolCall[],
|
|
592
|
+
options?: { concurrency?: number; stopOnError?: boolean }
|
|
593
|
+
): Promise<BatchResult<McpToolCallResult>> {
|
|
594
|
+
const concurrency = options?.concurrency ?? 5;
|
|
595
|
+
const stopOnError = options?.stopOnError ?? false;
|
|
596
|
+
const startTime = Date.now();
|
|
597
|
+
|
|
598
|
+
const results: Array<McpExecutionResult<McpToolCallResult>> = [];
|
|
599
|
+
let successCount = 0;
|
|
600
|
+
let failureCount = 0;
|
|
601
|
+
|
|
602
|
+
// Process in batches based on concurrency
|
|
603
|
+
for (let i = 0; i < calls.length; i += concurrency) {
|
|
604
|
+
const batch = calls.slice(i, i + concurrency);
|
|
605
|
+
|
|
606
|
+
const batchPromises = batch.map(async (call) => {
|
|
607
|
+
try {
|
|
608
|
+
const result = await this.callTool(call.name, call.params ?? {}, { signal: call.signal });
|
|
609
|
+
if (result.success) {
|
|
610
|
+
successCount++;
|
|
611
|
+
} else {
|
|
612
|
+
failureCount++;
|
|
613
|
+
}
|
|
614
|
+
return result;
|
|
615
|
+
} catch (error) {
|
|
616
|
+
failureCount++;
|
|
617
|
+
return {
|
|
618
|
+
success: false,
|
|
619
|
+
error: error instanceof McpError ? error : new McpError(McpErrorCode.InternalError, String(error)),
|
|
620
|
+
executionTimeMs: 0,
|
|
621
|
+
} as McpExecutionResult<McpToolCallResult>;
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
const batchResults = await Promise.all(batchPromises);
|
|
626
|
+
results.push(...batchResults);
|
|
627
|
+
|
|
628
|
+
// Stop on error if requested
|
|
629
|
+
if (stopOnError && failureCount > 0) {
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return {
|
|
635
|
+
results,
|
|
636
|
+
totalDurationMs: Date.now() - startTime,
|
|
637
|
+
successCount,
|
|
638
|
+
failureCount,
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// ============================================================================
|
|
643
|
+
// Resource Operations
|
|
644
|
+
// ============================================================================
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Lists all resources available on the MCP server.
|
|
648
|
+
*
|
|
649
|
+
* @returns Array of resource definitions
|
|
650
|
+
* @throws {McpConnectionError} If not connected
|
|
651
|
+
* @throws {McpTimeoutError} If request times out
|
|
652
|
+
*/
|
|
653
|
+
async listResources(): Promise<readonly McpResource[]> {
|
|
654
|
+
this._ensureConnected();
|
|
655
|
+
|
|
656
|
+
const startTime = Date.now();
|
|
657
|
+
this._log('Listing resources...');
|
|
658
|
+
|
|
659
|
+
try {
|
|
660
|
+
const result = await this._executeWithTimeout(
|
|
661
|
+
async () => this._client!.listResources(),
|
|
662
|
+
'listResources'
|
|
663
|
+
);
|
|
664
|
+
|
|
665
|
+
const resources: McpResource[] = (result.resources ?? []).map((resource) => ({
|
|
666
|
+
uri: resource.uri,
|
|
667
|
+
name: resource.name,
|
|
668
|
+
description: resource.description,
|
|
669
|
+
mimeType: resource.mimeType,
|
|
670
|
+
}));
|
|
671
|
+
|
|
672
|
+
this._log(`Found ${resources.length} resources in ${Date.now() - startTime}ms`);
|
|
673
|
+
return resources;
|
|
674
|
+
} catch (error) {
|
|
675
|
+
throw this._wrapError(error, 'Failed to list resources');
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Reads the contents of a resource from the MCP server.
|
|
681
|
+
*
|
|
682
|
+
* @param uri - URI of the resource to read
|
|
683
|
+
* @returns Resource contents
|
|
684
|
+
* @throws {McpConnectionError} If not connected
|
|
685
|
+
* @throws {McpError} If resource reading fails
|
|
686
|
+
* @throws {McpTimeoutError} If request times out
|
|
687
|
+
*/
|
|
688
|
+
async readResource(uri: string): Promise<McpExecutionResult<McpResourceContents[]>> {
|
|
689
|
+
this._ensureConnected();
|
|
690
|
+
|
|
691
|
+
const startTime = Date.now();
|
|
692
|
+
this._log(`Reading resource: ${uri}`);
|
|
693
|
+
|
|
694
|
+
try {
|
|
695
|
+
const result = await this._executeWithTimeout(
|
|
696
|
+
async () => this._client!.readResource({ uri }),
|
|
697
|
+
'readResource'
|
|
698
|
+
);
|
|
699
|
+
|
|
700
|
+
const executionTimeMs = Date.now() - startTime;
|
|
701
|
+
this._log(`Resource '${uri}' read in ${executionTimeMs}ms`);
|
|
702
|
+
|
|
703
|
+
const contents: McpResourceContents[] = (result.contents ?? []).map((content) => ({
|
|
704
|
+
uri: content.uri,
|
|
705
|
+
mimeType: content.mimeType,
|
|
706
|
+
text: 'text' in content ? content.text : undefined,
|
|
707
|
+
blob: 'blob' in content ? content.blob : undefined,
|
|
708
|
+
}));
|
|
709
|
+
|
|
710
|
+
return {
|
|
711
|
+
success: true,
|
|
712
|
+
data: contents,
|
|
713
|
+
executionTimeMs,
|
|
714
|
+
};
|
|
715
|
+
} catch (error) {
|
|
716
|
+
const executionTimeMs = Date.now() - startTime;
|
|
717
|
+
|
|
718
|
+
return {
|
|
719
|
+
success: false,
|
|
720
|
+
error: this._wrapError(error, `Failed to read resource '${uri}'`),
|
|
721
|
+
executionTimeMs,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// ============================================================================
|
|
727
|
+
// Prompt Operations
|
|
728
|
+
// ============================================================================
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Lists all prompts available on the MCP server.
|
|
732
|
+
*
|
|
733
|
+
* @returns Array of prompt definitions
|
|
734
|
+
* @throws {McpConnectionError} If not connected
|
|
735
|
+
* @throws {McpTimeoutError} If request times out
|
|
736
|
+
*/
|
|
737
|
+
async listPrompts(): Promise<readonly McpPrompt[]> {
|
|
738
|
+
this._ensureConnected();
|
|
739
|
+
|
|
740
|
+
const startTime = Date.now();
|
|
741
|
+
this._log('Listing prompts...');
|
|
742
|
+
|
|
743
|
+
try {
|
|
744
|
+
const result = await this._executeWithTimeout(
|
|
745
|
+
async () => this._client!.listPrompts(),
|
|
746
|
+
'listPrompts'
|
|
747
|
+
);
|
|
748
|
+
|
|
749
|
+
const prompts: McpPrompt[] = (result.prompts ?? []).map((prompt) => ({
|
|
750
|
+
name: prompt.name,
|
|
751
|
+
description: prompt.description,
|
|
752
|
+
arguments: prompt.arguments?.map((arg) => ({
|
|
753
|
+
name: arg.name,
|
|
754
|
+
description: arg.description,
|
|
755
|
+
required: arg.required,
|
|
756
|
+
})),
|
|
757
|
+
}));
|
|
758
|
+
|
|
759
|
+
this._log(`Found ${prompts.length} prompts in ${Date.now() - startTime}ms`);
|
|
760
|
+
return prompts;
|
|
761
|
+
} catch (error) {
|
|
762
|
+
throw this._wrapError(error, 'Failed to list prompts');
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Gets a prompt from the MCP server with the given arguments.
|
|
768
|
+
*
|
|
769
|
+
* @param name - Name of the prompt to get
|
|
770
|
+
* @param args - Arguments to pass to the prompt
|
|
771
|
+
* @returns Prompt result with messages
|
|
772
|
+
* @throws {McpConnectionError} If not connected
|
|
773
|
+
* @throws {McpError} If prompt retrieval fails
|
|
774
|
+
* @throws {McpTimeoutError} If request times out
|
|
775
|
+
*/
|
|
776
|
+
async getPrompt(
|
|
777
|
+
name: string,
|
|
778
|
+
args: Record<string, string> = {}
|
|
779
|
+
): Promise<McpExecutionResult<McpPromptResult>> {
|
|
780
|
+
this._ensureConnected();
|
|
781
|
+
|
|
782
|
+
const startTime = Date.now();
|
|
783
|
+
this._log(`Getting prompt: ${name}`);
|
|
784
|
+
|
|
785
|
+
try {
|
|
786
|
+
const result = await this._executeWithTimeout(
|
|
787
|
+
async () =>
|
|
788
|
+
this._client!.getPrompt({
|
|
789
|
+
name,
|
|
790
|
+
arguments: args,
|
|
791
|
+
}),
|
|
792
|
+
'getPrompt'
|
|
793
|
+
);
|
|
794
|
+
|
|
795
|
+
const executionTimeMs = Date.now() - startTime;
|
|
796
|
+
this._log(`Prompt '${name}' retrieved in ${executionTimeMs}ms`);
|
|
797
|
+
|
|
798
|
+
return {
|
|
799
|
+
success: true,
|
|
800
|
+
data: {
|
|
801
|
+
description: result.description,
|
|
802
|
+
messages: result.messages as McpPromptResult['messages'],
|
|
803
|
+
},
|
|
804
|
+
executionTimeMs,
|
|
805
|
+
};
|
|
806
|
+
} catch (error) {
|
|
807
|
+
const executionTimeMs = Date.now() - startTime;
|
|
808
|
+
|
|
809
|
+
return {
|
|
810
|
+
success: false,
|
|
811
|
+
error: this._wrapError(error, `Failed to get prompt '${name}'`),
|
|
812
|
+
executionTimeMs,
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// ============================================================================
|
|
818
|
+
// Private Methods
|
|
819
|
+
// ============================================================================
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Sets up event handlers for the transport.
|
|
823
|
+
*/
|
|
824
|
+
private _setupTransportHandlers(): void {
|
|
825
|
+
if (!this._transport) return;
|
|
826
|
+
|
|
827
|
+
this._transport.onclose = () => {
|
|
828
|
+
this._log('Transport closed');
|
|
829
|
+
if (this._state === 'connected') {
|
|
830
|
+
this._state = 'disconnected';
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
this._transport.onerror = (error: Error) => {
|
|
835
|
+
this._log(`Transport error: ${error.message}`);
|
|
836
|
+
this._state = 'error';
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Connects to the server with a timeout.
|
|
842
|
+
*/
|
|
843
|
+
private async _connectWithTimeout(signal?: AbortSignal): Promise<void> {
|
|
844
|
+
if (!this._client || !this._transport) {
|
|
845
|
+
throw new McpConnectionError(
|
|
846
|
+
McpErrorCode.ConnectionFailed,
|
|
847
|
+
'Client or transport not initialized'
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
const timeoutMs = this._options.connectionTimeoutMs;
|
|
852
|
+
|
|
853
|
+
const connectPromise = this._client.connect(this._transport);
|
|
854
|
+
|
|
855
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
856
|
+
setTimeout(() => {
|
|
857
|
+
reject(
|
|
858
|
+
new McpTimeoutError('connect', timeoutMs)
|
|
859
|
+
);
|
|
860
|
+
}, timeoutMs);
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
// Handle abort signal
|
|
864
|
+
if (signal) {
|
|
865
|
+
const abortPromise = new Promise<never>((_, reject) => {
|
|
866
|
+
signal.addEventListener('abort', () => {
|
|
867
|
+
reject(new DOMException('Connection aborted', 'AbortError'));
|
|
868
|
+
}, { once: true });
|
|
869
|
+
});
|
|
870
|
+
await Promise.race([connectPromise, timeoutPromise, abortPromise]);
|
|
871
|
+
} else {
|
|
872
|
+
await Promise.race([connectPromise, timeoutPromise]);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Executes an operation with a timeout.
|
|
878
|
+
*/
|
|
879
|
+
private async _executeWithTimeout<T>(
|
|
880
|
+
operation: () => Promise<T>,
|
|
881
|
+
operationName: string,
|
|
882
|
+
signal?: AbortSignal
|
|
883
|
+
): Promise<T> {
|
|
884
|
+
const requestId = generateRequestId();
|
|
885
|
+
const requestController = new AbortController();
|
|
886
|
+
this._activeRequests.set(requestId, requestController);
|
|
887
|
+
|
|
888
|
+
const timeoutMs = this._options.requestTimeoutMs;
|
|
889
|
+
this._metrics?.requestsTotal.increment();
|
|
890
|
+
this._metrics?.requestsInFlight.increment();
|
|
891
|
+
|
|
892
|
+
try {
|
|
893
|
+
// Check for external abort signal
|
|
894
|
+
if (signal?.aborted) {
|
|
895
|
+
throw new DOMException('Operation aborted', 'AbortError');
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
const operationPromise = operation();
|
|
899
|
+
|
|
900
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
901
|
+
setTimeout(() => {
|
|
902
|
+
this._metrics?.requestTimeouts.increment();
|
|
903
|
+
reject(new McpTimeoutError(operationName, timeoutMs));
|
|
904
|
+
}, timeoutMs);
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
// Handle abort signal
|
|
908
|
+
const promises: Promise<T | never>[] = [operationPromise, timeoutPromise];
|
|
909
|
+
|
|
910
|
+
if (signal) {
|
|
911
|
+
const abortPromise = new Promise<never>((_, reject) => {
|
|
912
|
+
signal.addEventListener('abort', () => {
|
|
913
|
+
this._metrics?.requestsCancelled.increment();
|
|
914
|
+
reject(new DOMException('Operation aborted', 'AbortError'));
|
|
915
|
+
}, { once: true });
|
|
916
|
+
});
|
|
917
|
+
promises.push(abortPromise);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Also handle internal abort
|
|
921
|
+
const internalAbortPromise = new Promise<never>((_, reject) => {
|
|
922
|
+
requestController.signal.addEventListener('abort', () => {
|
|
923
|
+
this._metrics?.requestsCancelled.increment();
|
|
924
|
+
reject(new DOMException('Operation aborted', 'AbortError'));
|
|
925
|
+
}, { once: true });
|
|
926
|
+
});
|
|
927
|
+
promises.push(internalAbortPromise);
|
|
928
|
+
|
|
929
|
+
const result = await Promise.race(promises);
|
|
930
|
+
this._metrics?.requestsSuccess.increment();
|
|
931
|
+
return result;
|
|
932
|
+
} catch (error) {
|
|
933
|
+
this._metrics?.requestsFailure.increment();
|
|
934
|
+
throw error;
|
|
935
|
+
} finally {
|
|
936
|
+
this._activeRequests.delete(requestId);
|
|
937
|
+
this._metrics?.requestsInFlight.decrement();
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Execute operation with middleware chain.
|
|
943
|
+
*/
|
|
944
|
+
private async _executeWithMiddleware(
|
|
945
|
+
request: MiddlewareRequest,
|
|
946
|
+
operation: () => Promise<MiddlewareResponse>
|
|
947
|
+
): Promise<MiddlewareResponse> {
|
|
948
|
+
if (this._middleware.length === 0) {
|
|
949
|
+
return operation();
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// Build middleware chain
|
|
953
|
+
let index = 0;
|
|
954
|
+
const next = async (): Promise<MiddlewareResponse> => {
|
|
955
|
+
if (index < this._middleware.length) {
|
|
956
|
+
const middleware = this._middleware[index++];
|
|
957
|
+
return middleware(request, next);
|
|
958
|
+
}
|
|
959
|
+
return operation();
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
return next();
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Emit connection state change event.
|
|
967
|
+
*/
|
|
968
|
+
private _emitStateChange(
|
|
969
|
+
previousState: ConnectionState,
|
|
970
|
+
currentState: ConnectionState
|
|
971
|
+
): void {
|
|
972
|
+
this._events.emit({
|
|
973
|
+
type: 'connection:stateChange',
|
|
974
|
+
previousState,
|
|
975
|
+
currentState,
|
|
976
|
+
timestamp: new Date(),
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* Emit request start event.
|
|
982
|
+
*/
|
|
983
|
+
private _emitRequestStart(requestId: string, method: string): void {
|
|
984
|
+
this._events.emit({
|
|
985
|
+
type: 'request:start',
|
|
986
|
+
requestId,
|
|
987
|
+
method,
|
|
988
|
+
timestamp: new Date(),
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* Emit request complete event.
|
|
994
|
+
*/
|
|
995
|
+
private _emitRequestComplete(requestId: string, method: string, durationMs: number): void {
|
|
996
|
+
this._events.emit({
|
|
997
|
+
type: 'request:complete',
|
|
998
|
+
requestId,
|
|
999
|
+
method,
|
|
1000
|
+
durationMs,
|
|
1001
|
+
timestamp: new Date(),
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* Emit request error event.
|
|
1007
|
+
*/
|
|
1008
|
+
private _emitRequestError(
|
|
1009
|
+
requestId: string,
|
|
1010
|
+
method: string,
|
|
1011
|
+
error: unknown,
|
|
1012
|
+
durationMs: number
|
|
1013
|
+
): void {
|
|
1014
|
+
this._events.emit({
|
|
1015
|
+
type: 'request:error',
|
|
1016
|
+
requestId,
|
|
1017
|
+
method,
|
|
1018
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
1019
|
+
durationMs,
|
|
1020
|
+
timestamp: new Date(),
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* Cancel all active requests.
|
|
1026
|
+
*/
|
|
1027
|
+
private _cancelAllRequests(): void {
|
|
1028
|
+
for (const [requestId, controller] of this._activeRequests) {
|
|
1029
|
+
controller.abort();
|
|
1030
|
+
this._logger.debug('Cancelled request', { data: { requestId } });
|
|
1031
|
+
}
|
|
1032
|
+
this._activeRequests.clear();
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/**
|
|
1036
|
+
* Extracts server info and capabilities from the connected client.
|
|
1037
|
+
*/
|
|
1038
|
+
private _extractServerInfo(): void {
|
|
1039
|
+
if (!this._client) return;
|
|
1040
|
+
|
|
1041
|
+
// Get server info from client's internal state
|
|
1042
|
+
// The SDK stores this after initialization
|
|
1043
|
+
const serverCapabilities = this._client.getServerCapabilities?.();
|
|
1044
|
+
const serverVersion = this._client.getServerVersion?.();
|
|
1045
|
+
|
|
1046
|
+
if (serverCapabilities) {
|
|
1047
|
+
this._capabilities = {
|
|
1048
|
+
tools: serverCapabilities.tools ? { listChanged: serverCapabilities.tools.listChanged } : undefined,
|
|
1049
|
+
resources: serverCapabilities.resources
|
|
1050
|
+
? {
|
|
1051
|
+
subscribe: serverCapabilities.resources.subscribe,
|
|
1052
|
+
listChanged: serverCapabilities.resources.listChanged,
|
|
1053
|
+
}
|
|
1054
|
+
: undefined,
|
|
1055
|
+
prompts: serverCapabilities.prompts ? { listChanged: serverCapabilities.prompts.listChanged } : undefined,
|
|
1056
|
+
logging: serverCapabilities.logging as Record<string, unknown> | undefined,
|
|
1057
|
+
experimental: serverCapabilities.experimental as Record<string, unknown> | undefined,
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
if (serverVersion) {
|
|
1062
|
+
this._serverInfo = {
|
|
1063
|
+
name: serverVersion.name,
|
|
1064
|
+
version: serverVersion.version,
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* Ensures the client is connected.
|
|
1071
|
+
*/
|
|
1072
|
+
private _ensureConnected(): void {
|
|
1073
|
+
if (this._state !== 'connected' || !this._client) {
|
|
1074
|
+
throw new McpConnectionError(
|
|
1075
|
+
McpErrorCode.NotConnected,
|
|
1076
|
+
'Client is not connected. Call connect() first.'
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* Cleans up client and transport resources.
|
|
1083
|
+
*/
|
|
1084
|
+
private async _cleanup(): Promise<void> {
|
|
1085
|
+
try {
|
|
1086
|
+
if (this._client) {
|
|
1087
|
+
await this._client.close?.();
|
|
1088
|
+
}
|
|
1089
|
+
} catch (error) {
|
|
1090
|
+
this._log(`Error closing client: ${error instanceof Error ? error.message : String(error)}`);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
try {
|
|
1094
|
+
if (this._transport) {
|
|
1095
|
+
await this._transport.close?.();
|
|
1096
|
+
}
|
|
1097
|
+
} catch (error) {
|
|
1098
|
+
this._log(`Error closing transport: ${error instanceof Error ? error.message : String(error)}`);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
this._client = null;
|
|
1102
|
+
this._transport = null;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Wraps an error in an McpError if needed.
|
|
1107
|
+
*/
|
|
1108
|
+
private _wrapError(error: unknown, context: string): McpError {
|
|
1109
|
+
if (error instanceof McpError) {
|
|
1110
|
+
return error;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
if (error instanceof Error) {
|
|
1114
|
+
// Check for common error types
|
|
1115
|
+
if (error.message.includes('timeout') || error.message.includes('timed out')) {
|
|
1116
|
+
return new McpTimeoutError(context, this._options.requestTimeoutMs, error);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
if (error.message.includes('not found')) {
|
|
1120
|
+
return new McpError(McpErrorCode.MethodNotFound, `${context}: ${error.message}`, error);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
return new McpError(McpErrorCode.InternalError, `${context}: ${error.message}`, error);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
return new McpError(McpErrorCode.Unknown, `${context}: ${String(error)}`, error);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/**
|
|
1130
|
+
* Logs a message if debug mode is enabled.
|
|
1131
|
+
*/
|
|
1132
|
+
private _log(message: string): void {
|
|
1133
|
+
if (this._options.debug) {
|
|
1134
|
+
console.log(`[McpClient] ${message}`);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
}
|