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,779 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Session Manager - Manages multiple concurrent MCP sessions
|
|
3
|
+
* @copyright 2024-2026 nirholas
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { randomUUID } from 'crypto';
|
|
8
|
+
|
|
9
|
+
import { McpClient } from './client.js';
|
|
10
|
+
import type { EnhancedClientOptions } from './client.js';
|
|
11
|
+
import { createTransport } from './transports.js';
|
|
12
|
+
import type {
|
|
13
|
+
TransportConfig,
|
|
14
|
+
McpSession,
|
|
15
|
+
SessionManagerOptions,
|
|
16
|
+
McpClientOptions,
|
|
17
|
+
ConnectionState,
|
|
18
|
+
} from './types.js';
|
|
19
|
+
import {
|
|
20
|
+
McpSessionError,
|
|
21
|
+
McpErrorCode,
|
|
22
|
+
DEFAULT_SESSION_MANAGER_OPTIONS,
|
|
23
|
+
} from './types.js';
|
|
24
|
+
import type { Logger } from './logger.js';
|
|
25
|
+
import { createNoopLogger } from './logger.js';
|
|
26
|
+
import type { McpEventEmitter, McpEvent } from './events.js';
|
|
27
|
+
import { createEventEmitter } from './events.js';
|
|
28
|
+
import type { SessionManagerMetrics } from './metrics.js';
|
|
29
|
+
import { createSessionManagerMetrics } from './metrics.js';
|
|
30
|
+
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Enhanced Session Manager Types
|
|
33
|
+
// ============================================================================
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Session health status
|
|
37
|
+
*/
|
|
38
|
+
export interface SessionHealthStatus {
|
|
39
|
+
readonly sessionId: string;
|
|
40
|
+
readonly state: ConnectionState;
|
|
41
|
+
readonly healthy: boolean;
|
|
42
|
+
readonly lastUsedAt: Date;
|
|
43
|
+
readonly createdAt: Date;
|
|
44
|
+
readonly ageMs: number;
|
|
45
|
+
readonly idleMs: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Enhanced session manager options
|
|
50
|
+
*/
|
|
51
|
+
export interface EnhancedSessionManagerOptions extends SessionManagerOptions {
|
|
52
|
+
readonly logger?: Logger;
|
|
53
|
+
readonly events?: McpEventEmitter;
|
|
54
|
+
readonly enableMetrics?: boolean;
|
|
55
|
+
readonly healthCheckIntervalMs?: number;
|
|
56
|
+
readonly clientOptions?: Partial<EnhancedClientOptions>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// Session Manager Class
|
|
61
|
+
// ============================================================================
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Manages multiple concurrent MCP client sessions with automatic cleanup.
|
|
65
|
+
*
|
|
66
|
+
* Features:
|
|
67
|
+
* - Session creation with configurable transports
|
|
68
|
+
* - Automatic session expiration (default: 5 minutes)
|
|
69
|
+
* - Maximum session limit (default: 100)
|
|
70
|
+
* - Session retrieval and cleanup
|
|
71
|
+
* - Event emission for session lifecycle
|
|
72
|
+
* - Metrics tracking (optional)
|
|
73
|
+
* - Health monitoring
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* const manager = SessionManager.getInstance({
|
|
78
|
+
* logger: createConsoleLogger(),
|
|
79
|
+
* enableMetrics: true,
|
|
80
|
+
* });
|
|
81
|
+
*
|
|
82
|
+
* // Subscribe to session events
|
|
83
|
+
* manager.events.on('session:created', (event) => {
|
|
84
|
+
* console.log(`Session created: ${event.sessionId}`);
|
|
85
|
+
* });
|
|
86
|
+
*
|
|
87
|
+
* const session = await manager.createSession({
|
|
88
|
+
* type: 'stdio',
|
|
89
|
+
* command: 'npx',
|
|
90
|
+
* args: ['tsx', 'server.ts'],
|
|
91
|
+
* });
|
|
92
|
+
*
|
|
93
|
+
* // Use the session
|
|
94
|
+
* const client = manager.getClient(session.id);
|
|
95
|
+
* const tools = await client?.listTools();
|
|
96
|
+
*
|
|
97
|
+
* // Check health
|
|
98
|
+
* const health = manager.getSessionHealth(session.id);
|
|
99
|
+
* console.log('Session healthy:', health?.healthy);
|
|
100
|
+
*
|
|
101
|
+
* // Sessions are automatically cleaned up after timeout
|
|
102
|
+
* // Or manually cleanup:
|
|
103
|
+
* await manager.destroySession(session.id);
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export class SessionManager {
|
|
107
|
+
private static _instance: SessionManager | null = null;
|
|
108
|
+
|
|
109
|
+
private readonly _options: Required<SessionManagerOptions>;
|
|
110
|
+
private readonly _logger: Logger;
|
|
111
|
+
private readonly _events: McpEventEmitter;
|
|
112
|
+
private readonly _metrics: SessionManagerMetrics | null;
|
|
113
|
+
private readonly _sessions: Map<string, SessionContext> = new Map();
|
|
114
|
+
private readonly _clientOptions: Partial<EnhancedClientOptions>;
|
|
115
|
+
private _healthCheckInterval: ReturnType<typeof setInterval> | null = null;
|
|
116
|
+
private _isShuttingDown = false;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Creates a new SessionManager instance.
|
|
120
|
+
* Use getInstance() to get the singleton instance.
|
|
121
|
+
*
|
|
122
|
+
* @param options - Session manager configuration options
|
|
123
|
+
*/
|
|
124
|
+
private constructor(options: EnhancedSessionManagerOptions = {}) {
|
|
125
|
+
this._options = {
|
|
126
|
+
...DEFAULT_SESSION_MANAGER_OPTIONS,
|
|
127
|
+
...options,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
this._logger = options.logger ?? createNoopLogger();
|
|
131
|
+
this._events = options.events ?? createEventEmitter();
|
|
132
|
+
this._metrics = options.enableMetrics ? createSessionManagerMetrics() : null;
|
|
133
|
+
this._clientOptions = options.clientOptions ?? {};
|
|
134
|
+
|
|
135
|
+
// Start health check if configured
|
|
136
|
+
if (options.healthCheckIntervalMs && options.healthCheckIntervalMs > 0) {
|
|
137
|
+
this._startHealthCheck(options.healthCheckIntervalMs);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
this._logger.info('SessionManager initialized', {
|
|
141
|
+
data: {
|
|
142
|
+
maxSessions: this._options.maxSessions,
|
|
143
|
+
sessionTimeoutMs: this._options.sessionTimeoutMs,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ============================================================================
|
|
149
|
+
// Singleton Access
|
|
150
|
+
// ============================================================================
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Gets the singleton SessionManager instance.
|
|
154
|
+
*
|
|
155
|
+
* @param options - Optional configuration (only used on first call)
|
|
156
|
+
* @returns The singleton SessionManager instance
|
|
157
|
+
*/
|
|
158
|
+
static getInstance(options?: EnhancedSessionManagerOptions): SessionManager {
|
|
159
|
+
if (!SessionManager._instance) {
|
|
160
|
+
SessionManager._instance = new SessionManager(options);
|
|
161
|
+
}
|
|
162
|
+
return SessionManager._instance;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Resets the singleton instance (useful for testing).
|
|
167
|
+
* WARNING: This will destroy all active sessions.
|
|
168
|
+
*/
|
|
169
|
+
static async resetInstance(): Promise<void> {
|
|
170
|
+
if (SessionManager._instance) {
|
|
171
|
+
await SessionManager._instance.shutdown();
|
|
172
|
+
SessionManager._instance = null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ============================================================================
|
|
177
|
+
// Public Properties
|
|
178
|
+
// ============================================================================
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Number of active sessions.
|
|
182
|
+
*/
|
|
183
|
+
get sessionCount(): number {
|
|
184
|
+
return this._sessions.size;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Maximum allowed sessions.
|
|
189
|
+
*/
|
|
190
|
+
get maxSessions(): number {
|
|
191
|
+
return this._options.maxSessions;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Session timeout in milliseconds.
|
|
196
|
+
*/
|
|
197
|
+
get sessionTimeoutMs(): number {
|
|
198
|
+
return this._options.sessionTimeoutMs;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Whether the manager is shutting down.
|
|
203
|
+
*/
|
|
204
|
+
get isShuttingDown(): boolean {
|
|
205
|
+
return this._isShuttingDown;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Event emitter for subscribing to session events.
|
|
210
|
+
*/
|
|
211
|
+
get events(): McpEventEmitter {
|
|
212
|
+
return this._events;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Session metrics (if enabled).
|
|
217
|
+
*/
|
|
218
|
+
get metrics(): SessionManagerMetrics | null {
|
|
219
|
+
return this._metrics;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ============================================================================
|
|
223
|
+
// Session Management
|
|
224
|
+
// ============================================================================
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Creates a new MCP session with the specified transport configuration.
|
|
228
|
+
*
|
|
229
|
+
* @param transportConfig - Transport configuration for the session
|
|
230
|
+
* @param clientOptions - Optional client configuration
|
|
231
|
+
* @returns The created session information
|
|
232
|
+
* @throws {McpSessionError} If max sessions reached or creation fails
|
|
233
|
+
*/
|
|
234
|
+
async createSession(
|
|
235
|
+
transportConfig: TransportConfig,
|
|
236
|
+
clientOptions?: Partial<EnhancedClientOptions>
|
|
237
|
+
): Promise<McpSession> {
|
|
238
|
+
if (this._isShuttingDown) {
|
|
239
|
+
throw new McpSessionError(
|
|
240
|
+
McpErrorCode.InternalError,
|
|
241
|
+
'SessionManager is shutting down'
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const timer = this._metrics?.sessionCreationDuration.start();
|
|
246
|
+
|
|
247
|
+
// Check session limit
|
|
248
|
+
if (this._sessions.size >= this._options.maxSessions) {
|
|
249
|
+
// Try to clean up expired sessions first
|
|
250
|
+
this._cleanupExpiredSessions();
|
|
251
|
+
|
|
252
|
+
if (this._sessions.size >= this._options.maxSessions) {
|
|
253
|
+
// Still at limit - evict the oldest session
|
|
254
|
+
const oldestSession = this._findOldestSession();
|
|
255
|
+
if (oldestSession) {
|
|
256
|
+
this._logger.info('Evicting oldest session', { data: { sessionId: oldestSession.session.id } });
|
|
257
|
+
this._metrics?.sessionsEvicted.increment();
|
|
258
|
+
await this._destroySessionContext(oldestSession, 'evicted');
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Check again after cleanup
|
|
263
|
+
if (this._sessions.size >= this._options.maxSessions) {
|
|
264
|
+
timer?.cancel();
|
|
265
|
+
throw new McpSessionError(
|
|
266
|
+
McpErrorCode.MaxSessionsReached,
|
|
267
|
+
`Maximum sessions (${this._options.maxSessions}) reached`
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const sessionId = randomUUID();
|
|
273
|
+
const now = new Date();
|
|
274
|
+
|
|
275
|
+
this._logger.info('Creating session', { sessionId, data: { transportType: transportConfig.type } });
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
// Create client with merged options
|
|
279
|
+
const mergedClientOptions: EnhancedClientOptions = {
|
|
280
|
+
name: 'mcp-session',
|
|
281
|
+
version: '1.0.0',
|
|
282
|
+
...this._clientOptions,
|
|
283
|
+
...clientOptions,
|
|
284
|
+
// Share events and logger with the client
|
|
285
|
+
logger: clientOptions?.logger ?? this._logger,
|
|
286
|
+
events: clientOptions?.events ?? this._events,
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const client = new McpClient(mergedClientOptions);
|
|
290
|
+
|
|
291
|
+
// Connect the client
|
|
292
|
+
await client.connect(transportConfig);
|
|
293
|
+
|
|
294
|
+
// Create session object
|
|
295
|
+
const session: McpSession = {
|
|
296
|
+
id: sessionId,
|
|
297
|
+
transportConfig,
|
|
298
|
+
state: 'connected',
|
|
299
|
+
capabilities: client.capabilities,
|
|
300
|
+
serverInfo: client.serverInfo,
|
|
301
|
+
createdAt: now,
|
|
302
|
+
lastUsedAt: now,
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// Set up cleanup timer
|
|
306
|
+
const cleanupTimer = setTimeout(() => {
|
|
307
|
+
this._onSessionTimeout(sessionId);
|
|
308
|
+
}, this._options.sessionTimeoutMs);
|
|
309
|
+
|
|
310
|
+
// Store session context
|
|
311
|
+
const context: SessionContext = {
|
|
312
|
+
session,
|
|
313
|
+
client,
|
|
314
|
+
cleanupTimer,
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
this._sessions.set(sessionId, context);
|
|
318
|
+
|
|
319
|
+
// Update metrics
|
|
320
|
+
this._metrics?.sessionsCreated.increment();
|
|
321
|
+
this._metrics?.sessionsActive.set(this._sessions.size);
|
|
322
|
+
timer?.end();
|
|
323
|
+
|
|
324
|
+
// Emit event
|
|
325
|
+
this._events.emit({
|
|
326
|
+
type: 'session:created',
|
|
327
|
+
sessionId,
|
|
328
|
+
timestamp: now,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
this._logger.info('Session created', {
|
|
332
|
+
sessionId,
|
|
333
|
+
data: {
|
|
334
|
+
serverName: client.serverInfo?.name,
|
|
335
|
+
serverVersion: client.serverInfo?.version,
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
return { ...session };
|
|
340
|
+
} catch (error) {
|
|
341
|
+
timer?.cancel();
|
|
342
|
+
this._logger.error('Failed to create session', {
|
|
343
|
+
data: { errorMessage: error instanceof Error ? error.message : String(error) },
|
|
344
|
+
});
|
|
345
|
+
throw new McpSessionError(
|
|
346
|
+
McpErrorCode.ConnectionFailed,
|
|
347
|
+
`Failed to create session: ${error instanceof Error ? error.message : String(error)}`,
|
|
348
|
+
error
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Gets a session by ID.
|
|
355
|
+
*
|
|
356
|
+
* @param sessionId - The session ID to retrieve
|
|
357
|
+
* @returns The session if found, undefined otherwise
|
|
358
|
+
*/
|
|
359
|
+
getSession(sessionId: string): McpSession | undefined {
|
|
360
|
+
const context = this._sessions.get(sessionId);
|
|
361
|
+
if (!context) {
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Update last used time and reset timer
|
|
366
|
+
this._touchSession(context);
|
|
367
|
+
|
|
368
|
+
return { ...context.session };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Gets the MCP client for a session.
|
|
373
|
+
*
|
|
374
|
+
* @param sessionId - The session ID
|
|
375
|
+
* @returns The McpClient if session exists and is connected, undefined otherwise
|
|
376
|
+
*/
|
|
377
|
+
getClient(sessionId: string): McpClient | undefined {
|
|
378
|
+
const context = this._sessions.get(sessionId);
|
|
379
|
+
if (!context || !context.client.isConnected) {
|
|
380
|
+
return undefined;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Update last used time and reset timer
|
|
384
|
+
this._touchSession(context);
|
|
385
|
+
|
|
386
|
+
return context.client;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Gets all active session IDs.
|
|
391
|
+
*
|
|
392
|
+
* @returns Array of active session IDs
|
|
393
|
+
*/
|
|
394
|
+
getSessionIds(): readonly string[] {
|
|
395
|
+
return Array.from(this._sessions.keys());
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Gets all active sessions.
|
|
400
|
+
*
|
|
401
|
+
* @returns Array of active sessions
|
|
402
|
+
*/
|
|
403
|
+
getAllSessions(): readonly McpSession[] {
|
|
404
|
+
return Array.from(this._sessions.values()).map((ctx) => ({ ...ctx.session }));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Destroys a session and releases its resources.
|
|
409
|
+
*
|
|
410
|
+
* @param sessionId - The session ID to destroy
|
|
411
|
+
* @returns True if the session was destroyed, false if not found
|
|
412
|
+
*/
|
|
413
|
+
async destroySession(sessionId: string): Promise<boolean> {
|
|
414
|
+
const context = this._sessions.get(sessionId);
|
|
415
|
+
if (!context) {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
await this._destroySessionContext(context);
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Shuts down the session manager and destroys all sessions.
|
|
425
|
+
*/
|
|
426
|
+
async shutdown(): Promise<void> {
|
|
427
|
+
if (this._isShuttingDown) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
this._isShuttingDown = true;
|
|
432
|
+
this._stopHealthCheck();
|
|
433
|
+
this._logger.info('Shutting down SessionManager...', { data: { sessionCount: this._sessions.size } });
|
|
434
|
+
|
|
435
|
+
const destroyPromises = Array.from(this._sessions.values()).map((context) =>
|
|
436
|
+
this._destroySessionContext(context, 'shutdown').catch((error) => {
|
|
437
|
+
this._logger.error('Error destroying session during shutdown', {
|
|
438
|
+
sessionId: context.session.id,
|
|
439
|
+
data: { errorMessage: error instanceof Error ? error.message : String(error) },
|
|
440
|
+
});
|
|
441
|
+
})
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
await Promise.all(destroyPromises);
|
|
445
|
+
|
|
446
|
+
this._sessions.clear();
|
|
447
|
+
this._isShuttingDown = false;
|
|
448
|
+
this._logger.info('SessionManager shut down');
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ============================================================================
|
|
452
|
+
// Session Queries
|
|
453
|
+
// ============================================================================
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Checks if a session exists.
|
|
457
|
+
*
|
|
458
|
+
* @param sessionId - The session ID to check
|
|
459
|
+
* @returns True if the session exists
|
|
460
|
+
*/
|
|
461
|
+
hasSession(sessionId: string): boolean {
|
|
462
|
+
return this._sessions.has(sessionId);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Gets the state of a session.
|
|
467
|
+
*
|
|
468
|
+
* @param sessionId - The session ID
|
|
469
|
+
* @returns The session state, or undefined if not found
|
|
470
|
+
*/
|
|
471
|
+
getSessionState(sessionId: string): ConnectionState | undefined {
|
|
472
|
+
const context = this._sessions.get(sessionId);
|
|
473
|
+
return context?.client.state;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Refreshes a session's timeout, keeping it alive.
|
|
478
|
+
*
|
|
479
|
+
* @param sessionId - The session ID to refresh
|
|
480
|
+
* @returns True if the session was refreshed, false if not found
|
|
481
|
+
*/
|
|
482
|
+
refreshSession(sessionId: string): boolean {
|
|
483
|
+
const context = this._sessions.get(sessionId);
|
|
484
|
+
if (!context) {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
this._touchSession(context);
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Gets the health status of a session.
|
|
494
|
+
*
|
|
495
|
+
* @param sessionId - The session ID
|
|
496
|
+
* @returns Session health status, or undefined if not found
|
|
497
|
+
*/
|
|
498
|
+
getSessionHealth(sessionId: string): SessionHealthStatus | undefined {
|
|
499
|
+
const context = this._sessions.get(sessionId);
|
|
500
|
+
if (!context) {
|
|
501
|
+
return undefined;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const now = Date.now();
|
|
505
|
+
|
|
506
|
+
return {
|
|
507
|
+
sessionId,
|
|
508
|
+
state: context.client.state,
|
|
509
|
+
healthy: context.client.isConnected && context.client.state !== 'error',
|
|
510
|
+
lastUsedAt: context.session.lastUsedAt,
|
|
511
|
+
createdAt: context.session.createdAt,
|
|
512
|
+
ageMs: now - context.session.createdAt.getTime(),
|
|
513
|
+
idleMs: now - context.session.lastUsedAt.getTime(),
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Gets health status for all sessions.
|
|
519
|
+
*
|
|
520
|
+
* @returns Array of session health statuses
|
|
521
|
+
*/
|
|
522
|
+
getAllSessionHealth(): readonly SessionHealthStatus[] {
|
|
523
|
+
return Array.from(this._sessions.keys()).map(
|
|
524
|
+
(sessionId) => this.getSessionHealth(sessionId)!
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// ============================================================================
|
|
529
|
+
// Private Methods
|
|
530
|
+
// ============================================================================
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Updates a session's last used time and resets its cleanup timer.
|
|
534
|
+
*/
|
|
535
|
+
private _touchSession(context: SessionContext): void {
|
|
536
|
+
context.session.lastUsedAt = new Date();
|
|
537
|
+
|
|
538
|
+
// Reset cleanup timer
|
|
539
|
+
clearTimeout(context.cleanupTimer);
|
|
540
|
+
context.cleanupTimer = setTimeout(() => {
|
|
541
|
+
this._onSessionTimeout(context.session.id);
|
|
542
|
+
}, this._options.sessionTimeoutMs);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Handles session timeout expiration.
|
|
547
|
+
*/
|
|
548
|
+
private _onSessionTimeout(sessionId: string): void {
|
|
549
|
+
const context = this._sessions.get(sessionId);
|
|
550
|
+
if (context) {
|
|
551
|
+
this._logger.info('Session timed out', { sessionId });
|
|
552
|
+
this._metrics?.sessionsTimedOut.increment();
|
|
553
|
+
this._destroySessionContext(context, 'timeout').catch((error) => {
|
|
554
|
+
this._logger.error('Error destroying timed out session', {
|
|
555
|
+
sessionId,
|
|
556
|
+
data: { errorMessage: error instanceof Error ? error.message : String(error) },
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Destroys a session context and cleans up resources.
|
|
564
|
+
*/
|
|
565
|
+
private async _destroySessionContext(
|
|
566
|
+
context: SessionContext,
|
|
567
|
+
reason: 'timeout' | 'manual' | 'evicted' | 'error' | 'shutdown' = 'manual'
|
|
568
|
+
): Promise<void> {
|
|
569
|
+
const sessionId = context.session.id;
|
|
570
|
+
const timer = this._metrics?.sessionDuration.start();
|
|
571
|
+
|
|
572
|
+
this._logger.info('Destroying session', { sessionId, data: { reason } });
|
|
573
|
+
|
|
574
|
+
// Clear timeout
|
|
575
|
+
clearTimeout(context.cleanupTimer);
|
|
576
|
+
|
|
577
|
+
// Remove from map first to prevent re-entry
|
|
578
|
+
this._sessions.delete(sessionId);
|
|
579
|
+
|
|
580
|
+
// Disconnect client
|
|
581
|
+
try {
|
|
582
|
+
await context.client.disconnect();
|
|
583
|
+
} catch (error) {
|
|
584
|
+
this._logger.warn('Error disconnecting client', {
|
|
585
|
+
sessionId,
|
|
586
|
+
data: { errorMessage: error instanceof Error ? error.message : String(error) },
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Update metrics
|
|
591
|
+
this._metrics?.sessionsDestroyed.increment();
|
|
592
|
+
this._metrics?.sessionsActive.set(this._sessions.size);
|
|
593
|
+
timer?.end();
|
|
594
|
+
|
|
595
|
+
// Emit event
|
|
596
|
+
this._events.emit({
|
|
597
|
+
type: 'session:destroyed',
|
|
598
|
+
sessionId,
|
|
599
|
+
reason,
|
|
600
|
+
timestamp: new Date(),
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
this._logger.info('Session destroyed', { sessionId });
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Finds the oldest session based on last used time.
|
|
608
|
+
*/
|
|
609
|
+
private _findOldestSession(): SessionContext | undefined {
|
|
610
|
+
let oldest: SessionContext | undefined;
|
|
611
|
+
|
|
612
|
+
for (const context of this._sessions.values()) {
|
|
613
|
+
if (!oldest || context.session.lastUsedAt < oldest.session.lastUsedAt) {
|
|
614
|
+
oldest = context;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return oldest;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Cleans up expired sessions.
|
|
623
|
+
*/
|
|
624
|
+
private _cleanupExpiredSessions(): void {
|
|
625
|
+
const now = Date.now();
|
|
626
|
+
const expiredSessions: SessionContext[] = [];
|
|
627
|
+
|
|
628
|
+
for (const context of this._sessions.values()) {
|
|
629
|
+
const age = now - context.session.lastUsedAt.getTime();
|
|
630
|
+
if (age >= this._options.sessionTimeoutMs) {
|
|
631
|
+
expiredSessions.push(context);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
for (const context of expiredSessions) {
|
|
636
|
+
this._destroySessionContext(context, 'timeout').catch((error) => {
|
|
637
|
+
this._logger.error('Error cleaning up expired session', {
|
|
638
|
+
sessionId: context.session.id,
|
|
639
|
+
data: { errorMessage: error instanceof Error ? error.message : String(error) },
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (expiredSessions.length > 0) {
|
|
645
|
+
this._logger.info('Cleaned up expired sessions', { data: { count: expiredSessions.length } });
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Start health check interval.
|
|
651
|
+
*/
|
|
652
|
+
private _startHealthCheck(intervalMs: number): void {
|
|
653
|
+
this._stopHealthCheck();
|
|
654
|
+
|
|
655
|
+
this._healthCheckInterval = setInterval(() => {
|
|
656
|
+
if (this._isShuttingDown) return;
|
|
657
|
+
|
|
658
|
+
this._performHealthCheck();
|
|
659
|
+
}, intervalMs);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Stop health check interval.
|
|
664
|
+
*/
|
|
665
|
+
private _stopHealthCheck(): void {
|
|
666
|
+
if (this._healthCheckInterval) {
|
|
667
|
+
clearInterval(this._healthCheckInterval);
|
|
668
|
+
this._healthCheckInterval = null;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Perform health check on all sessions.
|
|
674
|
+
*/
|
|
675
|
+
private _performHealthCheck(): void {
|
|
676
|
+
const unhealthySessions: SessionContext[] = [];
|
|
677
|
+
|
|
678
|
+
for (const context of this._sessions.values()) {
|
|
679
|
+
if (!context.client.isConnected || context.client.state === 'error') {
|
|
680
|
+
unhealthySessions.push(context);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
for (const context of unhealthySessions) {
|
|
685
|
+
this._logger.warn('Unhealthy session detected', {
|
|
686
|
+
sessionId: context.session.id,
|
|
687
|
+
data: { state: context.client.state },
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
this._destroySessionContext(context, 'error').catch((error) => {
|
|
691
|
+
this._logger.error('Error destroying unhealthy session', {
|
|
692
|
+
sessionId: context.session.id,
|
|
693
|
+
data: { errorMessage: error instanceof Error ? error.message : String(error) },
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Logs a message if debug mode is enabled.
|
|
701
|
+
* @deprecated Use this._logger instead
|
|
702
|
+
*/
|
|
703
|
+
private _log(message: string): void {
|
|
704
|
+
if (this._options.debug) {
|
|
705
|
+
console.log(`[SessionManager] ${message}`);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// ============================================================================
|
|
711
|
+
// Internal Types
|
|
712
|
+
// ============================================================================
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Internal session context with client and cleanup timer
|
|
716
|
+
*/
|
|
717
|
+
interface SessionContext {
|
|
718
|
+
session: McpSession;
|
|
719
|
+
client: McpClient;
|
|
720
|
+
cleanupTimer: ReturnType<typeof setTimeout>;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// ============================================================================
|
|
724
|
+
// Convenience Functions
|
|
725
|
+
// ============================================================================
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Gets the singleton SessionManager instance.
|
|
729
|
+
*
|
|
730
|
+
* @param options - Optional configuration (only used on first call)
|
|
731
|
+
* @returns The singleton SessionManager instance
|
|
732
|
+
*/
|
|
733
|
+
export function getSessionManager(options?: EnhancedSessionManagerOptions): SessionManager {
|
|
734
|
+
return SessionManager.getInstance(options);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Creates a new session using the default SessionManager.
|
|
739
|
+
*
|
|
740
|
+
* @param transportConfig - Transport configuration
|
|
741
|
+
* @param clientOptions - Optional client options
|
|
742
|
+
* @returns The created session
|
|
743
|
+
*/
|
|
744
|
+
export async function createSession(
|
|
745
|
+
transportConfig: TransportConfig,
|
|
746
|
+
clientOptions?: Partial<EnhancedClientOptions>
|
|
747
|
+
): Promise<McpSession> {
|
|
748
|
+
return SessionManager.getInstance().createSession(transportConfig, clientOptions);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Gets a client from the default SessionManager.
|
|
753
|
+
*
|
|
754
|
+
* @param sessionId - Session ID
|
|
755
|
+
* @returns The client if found
|
|
756
|
+
*/
|
|
757
|
+
export function getClient(sessionId: string): McpClient | undefined {
|
|
758
|
+
return SessionManager.getInstance().getClient(sessionId);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Destroys a session in the default SessionManager.
|
|
763
|
+
*
|
|
764
|
+
* @param sessionId - Session ID
|
|
765
|
+
* @returns True if destroyed
|
|
766
|
+
*/
|
|
767
|
+
export async function destroySession(sessionId: string): Promise<boolean> {
|
|
768
|
+
return SessionManager.getInstance().destroySession(sessionId);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Gets session health from the default SessionManager.
|
|
773
|
+
*
|
|
774
|
+
* @param sessionId - Session ID
|
|
775
|
+
* @returns Session health status if found
|
|
776
|
+
*/
|
|
777
|
+
export function getSessionHealth(sessionId: string): SessionHealthStatus | undefined {
|
|
778
|
+
return SessionManager.getInstance().getSessionHealth(sessionId);
|
|
779
|
+
}
|