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,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview MCP server introspection - extract tools from existing MCP repos
|
|
3
|
+
* @copyright Copyright (c) 2024-2026 nirholas
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ExtractedTool, McpToolDefinition } from './types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Extract tool definitions from existing MCP server code
|
|
11
|
+
*/
|
|
12
|
+
export class McpIntrospector {
|
|
13
|
+
/**
|
|
14
|
+
* Extract tools from TypeScript/JavaScript MCP server code
|
|
15
|
+
*/
|
|
16
|
+
extractFromTypeScript(code: string, filePath: string): ExtractedTool[] {
|
|
17
|
+
const tools: ExtractedTool[] = [];
|
|
18
|
+
|
|
19
|
+
// Pattern 1: Tool definitions in array format
|
|
20
|
+
// tools: [{ name: "...", description: "...", inputSchema: {...} }]
|
|
21
|
+
const toolArrayMatch = code.match(/tools\s*:\s*\[([\s\S]*?)\]/);
|
|
22
|
+
if (toolArrayMatch) {
|
|
23
|
+
const toolsFromArray = this.parseToolArray(toolArrayMatch[1], filePath);
|
|
24
|
+
tools.push(...toolsFromArray);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Pattern 2: ListToolsRequestSchema handler
|
|
28
|
+
// server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [...] }))
|
|
29
|
+
const listToolsMatch = code.match(
|
|
30
|
+
/setRequestHandler\s*\(\s*ListToolsRequestSchema[\s\S]*?tools\s*:\s*\[([\s\S]*?)\]\s*\}/
|
|
31
|
+
);
|
|
32
|
+
if (listToolsMatch) {
|
|
33
|
+
const toolsFromHandler = this.parseToolArray(listToolsMatch[1], filePath);
|
|
34
|
+
tools.push(...toolsFromHandler);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Pattern 3: Individual tool definitions
|
|
38
|
+
// { name: "tool_name", description: "...", inputSchema: {...} }
|
|
39
|
+
const individualToolMatches = code.matchAll(
|
|
40
|
+
/\{\s*name\s*:\s*['"`](\w+)['"`]\s*,\s*description\s*:\s*['"`]([^'"`]+)['"`]\s*,\s*inputSchema\s*:\s*(\{[\s\S]*?\})\s*\}/g
|
|
41
|
+
);
|
|
42
|
+
for (const match of individualToolMatches) {
|
|
43
|
+
const [, name, description, schemaStr] = match;
|
|
44
|
+
try {
|
|
45
|
+
// Try to parse the schema (may fail for complex cases)
|
|
46
|
+
const inputSchema = this.parseJsonLike(schemaStr);
|
|
47
|
+
tools.push({
|
|
48
|
+
name,
|
|
49
|
+
description,
|
|
50
|
+
inputSchema,
|
|
51
|
+
source: { type: 'mcp-introspect', file: filePath }
|
|
52
|
+
});
|
|
53
|
+
} catch {
|
|
54
|
+
// Fallback with basic schema
|
|
55
|
+
tools.push({
|
|
56
|
+
name,
|
|
57
|
+
description,
|
|
58
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
59
|
+
source: { type: 'mcp-introspect', file: filePath }
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Pattern 4: server.tool() calls (MCP SDK pattern)
|
|
65
|
+
// server.tool("name", "description", { schema }, handler)
|
|
66
|
+
const serverToolMatches = code.matchAll(
|
|
67
|
+
/server\.tool\s*\(\s*['"`](\w+)['"`]\s*,\s*['"`]([^'"`]+)['"`]\s*,?\s*(\{[\s\S]*?\})?\s*,/g
|
|
68
|
+
);
|
|
69
|
+
for (const match of serverToolMatches) {
|
|
70
|
+
const [, name, description, schemaStr] = match;
|
|
71
|
+
let inputSchema = { type: 'object', properties: {}, required: [] as string[] };
|
|
72
|
+
|
|
73
|
+
if (schemaStr) {
|
|
74
|
+
try {
|
|
75
|
+
inputSchema = this.parseJsonLike(schemaStr);
|
|
76
|
+
} catch { /* use default */ }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
tools.push({
|
|
80
|
+
name,
|
|
81
|
+
description,
|
|
82
|
+
inputSchema,
|
|
83
|
+
source: { type: 'mcp-introspect', file: filePath }
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return this.deduplicateTools(tools);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Extract tools from Python MCP server code
|
|
92
|
+
*/
|
|
93
|
+
extractFromPython(code: string, filePath: string): ExtractedTool[] {
|
|
94
|
+
const tools: ExtractedTool[] = [];
|
|
95
|
+
|
|
96
|
+
// Pattern 1: @server.tool() decorator
|
|
97
|
+
// @server.tool()
|
|
98
|
+
// async def tool_name(arg1: str, arg2: int) -> str:
|
|
99
|
+
// """Description"""
|
|
100
|
+
const decoratorMatches = code.matchAll(
|
|
101
|
+
/@(?:server|mcp)\.(?:tool|call_tool)\s*\([^)]*\)\s*\n\s*(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)[^:]*:\s*\n\s*(?:"""([^"]+)"""|'''([^']+)''')?/g
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
for (const match of decoratorMatches) {
|
|
105
|
+
const [, name, argsStr, docstring1, docstring2] = match;
|
|
106
|
+
const description = (docstring1 || docstring2 || `Python tool: ${name}`).trim();
|
|
107
|
+
const inputSchema = this.pythonArgsToSchema(argsStr);
|
|
108
|
+
|
|
109
|
+
tools.push({
|
|
110
|
+
name,
|
|
111
|
+
description,
|
|
112
|
+
inputSchema,
|
|
113
|
+
source: { type: 'mcp-introspect', file: filePath }
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Pattern 2: Tool class definitions
|
|
118
|
+
// class MyTool(Tool):
|
|
119
|
+
// name = "my_tool"
|
|
120
|
+
// description = "..."
|
|
121
|
+
const classMatches = code.matchAll(
|
|
122
|
+
/class\s+(\w+)\s*\([^)]*Tool[^)]*\)[\s\S]*?name\s*=\s*['"`](\w+)['"`][\s\S]*?description\s*=\s*['"`]([^'"`]+)['"`]/g
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
for (const match of classMatches) {
|
|
126
|
+
const [, , name, description] = match;
|
|
127
|
+
tools.push({
|
|
128
|
+
name,
|
|
129
|
+
description,
|
|
130
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
131
|
+
source: { type: 'mcp-introspect', file: filePath }
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Pattern 3: tools list/dict
|
|
136
|
+
// tools = [{"name": "...", "description": "..."}]
|
|
137
|
+
const toolsListMatch = code.match(/tools\s*=\s*\[([\s\S]*?)\]/);
|
|
138
|
+
if (toolsListMatch) {
|
|
139
|
+
const dictMatches = toolsListMatch[1].matchAll(
|
|
140
|
+
/\{\s*["']name["']\s*:\s*["'](\w+)["']\s*,\s*["']description["']\s*:\s*["']([^"']+)["']/g
|
|
141
|
+
);
|
|
142
|
+
for (const m of dictMatches) {
|
|
143
|
+
tools.push({
|
|
144
|
+
name: m[1],
|
|
145
|
+
description: m[2],
|
|
146
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
147
|
+
source: { type: 'mcp-introspect', file: filePath }
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return this.deduplicateTools(tools);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Parse Python function arguments to JSON schema
|
|
157
|
+
*/
|
|
158
|
+
private pythonArgsToSchema(argsStr: string): { type: string; properties: Record<string, any>; required: string[] } {
|
|
159
|
+
const properties: Record<string, any> = {};
|
|
160
|
+
const required: string[] = [];
|
|
161
|
+
|
|
162
|
+
// Parse args like: arg1: str, arg2: int = 5, arg3: Optional[str] = None
|
|
163
|
+
const argMatches = argsStr.matchAll(
|
|
164
|
+
/(\w+)\s*:\s*([^,=]+)(?:\s*=\s*([^,]+))?/g
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
for (const match of argMatches) {
|
|
168
|
+
const [, name, typeHint, defaultValue] = match;
|
|
169
|
+
if (name === 'self' || name === 'ctx' || name === 'context') continue;
|
|
170
|
+
|
|
171
|
+
const jsonType = this.pythonTypeToJsonType(typeHint.trim());
|
|
172
|
+
properties[name] = {
|
|
173
|
+
type: jsonType,
|
|
174
|
+
description: `${name} parameter`
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// If no default value and not Optional, it's required
|
|
178
|
+
if (!defaultValue && !typeHint.includes('Optional') && !typeHint.includes('None')) {
|
|
179
|
+
required.push(name);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return { type: 'object', properties, required };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Map Python type hints to JSON Schema types
|
|
188
|
+
*/
|
|
189
|
+
private pythonTypeToJsonType(pyType: string): string {
|
|
190
|
+
const typeMap: Record<string, string> = {
|
|
191
|
+
'str': 'string',
|
|
192
|
+
'int': 'integer',
|
|
193
|
+
'float': 'number',
|
|
194
|
+
'bool': 'boolean',
|
|
195
|
+
'list': 'array',
|
|
196
|
+
'dict': 'object',
|
|
197
|
+
'List': 'array',
|
|
198
|
+
'Dict': 'object',
|
|
199
|
+
'Any': 'string'
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Handle Optional[X], List[X], etc
|
|
203
|
+
const baseMatch = pyType.match(/^(?:Optional|List|Dict|Union)?\[?(\w+)/);
|
|
204
|
+
if (baseMatch) {
|
|
205
|
+
return typeMap[baseMatch[1]] || 'string';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return typeMap[pyType] || 'string';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Parse a tool array string into tool definitions
|
|
213
|
+
*/
|
|
214
|
+
private parseToolArray(arrayContent: string, filePath: string): ExtractedTool[] {
|
|
215
|
+
const tools: ExtractedTool[] = [];
|
|
216
|
+
|
|
217
|
+
// Find each tool object
|
|
218
|
+
const toolMatches = arrayContent.matchAll(
|
|
219
|
+
/\{\s*(?:["'])?name(?:["'])?\s*:\s*["'](\w+)["']\s*,\s*(?:["'])?description(?:["'])?\s*:\s*["']([^"']+)["']/g
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
for (const match of toolMatches) {
|
|
223
|
+
tools.push({
|
|
224
|
+
name: match[1],
|
|
225
|
+
description: match[2],
|
|
226
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
227
|
+
source: { type: 'mcp-introspect', file: filePath }
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return tools;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Parse JSON-like object strings (handles some JS patterns)
|
|
236
|
+
*/
|
|
237
|
+
private parseJsonLike(str: string): any {
|
|
238
|
+
// Convert JS object syntax to valid JSON
|
|
239
|
+
let jsonStr = str
|
|
240
|
+
.replace(/(\w+)\s*:/g, '"$1":') // unquoted keys
|
|
241
|
+
.replace(/'/g, '"') // single to double quotes
|
|
242
|
+
.replace(/,\s*}/g, '}') // trailing commas
|
|
243
|
+
.replace(/,\s*]/g, ']');
|
|
244
|
+
|
|
245
|
+
return JSON.parse(jsonStr);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Remove duplicate tools by name
|
|
250
|
+
*/
|
|
251
|
+
private deduplicateTools(tools: ExtractedTool[]): ExtractedTool[] {
|
|
252
|
+
const seen = new Set<string>();
|
|
253
|
+
return tools.filter(tool => {
|
|
254
|
+
if (seen.has(tool.name)) return false;
|
|
255
|
+
seen.add(tool.name);
|
|
256
|
+
return true;
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Detect if a file is likely an MCP server
|
|
262
|
+
*/
|
|
263
|
+
isLikelyMcpServer(code: string): boolean {
|
|
264
|
+
const indicators = [
|
|
265
|
+
'@modelcontextprotocol',
|
|
266
|
+
'mcp.server',
|
|
267
|
+
'McpServer',
|
|
268
|
+
'MCP server',
|
|
269
|
+
'ListToolsRequestSchema',
|
|
270
|
+
'CallToolRequestSchema',
|
|
271
|
+
'StdioServerTransport',
|
|
272
|
+
'@server.tool',
|
|
273
|
+
'from mcp',
|
|
274
|
+
'import mcp'
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
return indicators.some(indicator => code.includes(indicator));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Monorepo detection and processing
|
|
3
|
+
* @copyright Copyright (c) 2024-2026 nirholas
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { MonorepoInfo, MonorepoType, MonorepoPackage } from './types';
|
|
8
|
+
import type { GithubClient } from './github-client';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Detects and processes monorepo structures
|
|
12
|
+
*/
|
|
13
|
+
export class MonorepoDetector {
|
|
14
|
+
private verbose: boolean;
|
|
15
|
+
|
|
16
|
+
constructor(verbose: boolean = false) {
|
|
17
|
+
this.verbose = verbose;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Detect if repository is a monorepo and identify its structure
|
|
22
|
+
*/
|
|
23
|
+
async detect(
|
|
24
|
+
github: GithubClient,
|
|
25
|
+
owner: string,
|
|
26
|
+
repo: string,
|
|
27
|
+
branch?: string
|
|
28
|
+
): Promise<MonorepoInfo | null> {
|
|
29
|
+
// Try each detection method in order of specificity
|
|
30
|
+
const detectors: Array<() => Promise<MonorepoInfo | null>> = [
|
|
31
|
+
() => this.detectLerna(github, owner, repo, branch),
|
|
32
|
+
() => this.detectNx(github, owner, repo, branch),
|
|
33
|
+
() => this.detectTurborepo(github, owner, repo, branch),
|
|
34
|
+
() => this.detectPnpmWorkspace(github, owner, repo, branch),
|
|
35
|
+
() => this.detectNpmYarnWorkspace(github, owner, repo, branch),
|
|
36
|
+
() => this.detectCustomLayout(github, owner, repo, branch)
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
for (const detector of detectors) {
|
|
40
|
+
const result = await detector();
|
|
41
|
+
if (result && result.packages.length > 0) {
|
|
42
|
+
if (this.verbose) {
|
|
43
|
+
console.log(`[Monorepo] Detected ${result.type} monorepo with ${result.packages.length} packages`);
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Detect Lerna monorepo (lerna.json)
|
|
54
|
+
*/
|
|
55
|
+
private async detectLerna(
|
|
56
|
+
github: GithubClient,
|
|
57
|
+
owner: string,
|
|
58
|
+
repo: string,
|
|
59
|
+
branch?: string
|
|
60
|
+
): Promise<MonorepoInfo | null> {
|
|
61
|
+
const lernaConfig = await github.getFileContent(owner, repo, 'lerna.json', branch);
|
|
62
|
+
if (!lernaConfig) return null;
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const config = JSON.parse(lernaConfig.content);
|
|
66
|
+
const packagePatterns = config.packages || ['packages/*'];
|
|
67
|
+
const packages = await this.resolvePackagePatterns(github, owner, repo, packagePatterns, branch);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
type: 'lerna',
|
|
71
|
+
packages,
|
|
72
|
+
rootPath: ''
|
|
73
|
+
};
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Detect Nx monorepo (nx.json)
|
|
81
|
+
*/
|
|
82
|
+
private async detectNx(
|
|
83
|
+
github: GithubClient,
|
|
84
|
+
owner: string,
|
|
85
|
+
repo: string,
|
|
86
|
+
branch?: string
|
|
87
|
+
): Promise<MonorepoInfo | null> {
|
|
88
|
+
const nxConfig = await github.getFileContent(owner, repo, 'nx.json', branch);
|
|
89
|
+
if (!nxConfig) return null;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
// Nx typically uses apps/ and libs/ directories
|
|
93
|
+
const patterns = ['apps/*', 'libs/*', 'packages/*'];
|
|
94
|
+
const packages = await this.resolvePackagePatterns(github, owner, repo, patterns, branch);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
type: 'nx',
|
|
98
|
+
packages,
|
|
99
|
+
rootPath: ''
|
|
100
|
+
};
|
|
101
|
+
} catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Detect Turborepo monorepo (turbo.json)
|
|
108
|
+
*/
|
|
109
|
+
private async detectTurborepo(
|
|
110
|
+
github: GithubClient,
|
|
111
|
+
owner: string,
|
|
112
|
+
repo: string,
|
|
113
|
+
branch?: string
|
|
114
|
+
): Promise<MonorepoInfo | null> {
|
|
115
|
+
const turboConfig = await github.getFileContent(owner, repo, 'turbo.json', branch);
|
|
116
|
+
if (!turboConfig) return null;
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
// Turborepo typically uses packages/ and apps/ directories
|
|
120
|
+
const patterns = ['packages/*', 'apps/*'];
|
|
121
|
+
const packages = await this.resolvePackagePatterns(github, owner, repo, patterns, branch);
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
type: 'turborepo',
|
|
125
|
+
packages,
|
|
126
|
+
rootPath: ''
|
|
127
|
+
};
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Detect pnpm workspace (pnpm-workspace.yaml)
|
|
135
|
+
*/
|
|
136
|
+
private async detectPnpmWorkspace(
|
|
137
|
+
github: GithubClient,
|
|
138
|
+
owner: string,
|
|
139
|
+
repo: string,
|
|
140
|
+
branch?: string
|
|
141
|
+
): Promise<MonorepoInfo | null> {
|
|
142
|
+
const pnpmConfig = await github.getFileContent(owner, repo, 'pnpm-workspace.yaml', branch);
|
|
143
|
+
if (!pnpmConfig) return null;
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
// Simple YAML parsing for packages array
|
|
147
|
+
const patterns = this.parseYamlPackages(pnpmConfig.content);
|
|
148
|
+
if (patterns.length === 0) return null;
|
|
149
|
+
|
|
150
|
+
const packages = await this.resolvePackagePatterns(github, owner, repo, patterns, branch);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
type: 'pnpm',
|
|
154
|
+
packages,
|
|
155
|
+
rootPath: ''
|
|
156
|
+
};
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Detect npm/yarn workspaces from package.json
|
|
164
|
+
*/
|
|
165
|
+
private async detectNpmYarnWorkspace(
|
|
166
|
+
github: GithubClient,
|
|
167
|
+
owner: string,
|
|
168
|
+
repo: string,
|
|
169
|
+
branch?: string
|
|
170
|
+
): Promise<MonorepoInfo | null> {
|
|
171
|
+
const packageJson = await github.getFileContent(owner, repo, 'package.json', branch);
|
|
172
|
+
if (!packageJson) return null;
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const pkg = JSON.parse(packageJson.content);
|
|
176
|
+
const workspaces = pkg.workspaces;
|
|
177
|
+
|
|
178
|
+
if (!workspaces) return null;
|
|
179
|
+
|
|
180
|
+
// Handle both array format and object format (yarn)
|
|
181
|
+
const patterns = Array.isArray(workspaces)
|
|
182
|
+
? workspaces
|
|
183
|
+
: (workspaces.packages || []);
|
|
184
|
+
|
|
185
|
+
if (patterns.length === 0) return null;
|
|
186
|
+
|
|
187
|
+
const packages = await this.resolvePackagePatterns(github, owner, repo, patterns, branch);
|
|
188
|
+
|
|
189
|
+
// Detect yarn.lock vs package-lock.json
|
|
190
|
+
const yarnLock = await github.getFileContent(owner, repo, 'yarn.lock', branch);
|
|
191
|
+
const type = yarnLock ? 'yarn' : 'npm';
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
type,
|
|
195
|
+
packages,
|
|
196
|
+
rootPath: ''
|
|
197
|
+
};
|
|
198
|
+
} catch {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Detect custom directory layout (packages/*, apps/*, libs/*)
|
|
205
|
+
*/
|
|
206
|
+
private async detectCustomLayout(
|
|
207
|
+
github: GithubClient,
|
|
208
|
+
owner: string,
|
|
209
|
+
repo: string,
|
|
210
|
+
branch?: string
|
|
211
|
+
): Promise<MonorepoInfo | null> {
|
|
212
|
+
const patterns = ['packages/*', 'apps/*', 'libs/*', 'modules/*'];
|
|
213
|
+
const packages = await this.resolvePackagePatterns(github, owner, repo, patterns, branch);
|
|
214
|
+
|
|
215
|
+
if (packages.length < 2) return null; // Need at least 2 packages to be a monorepo
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
type: 'custom',
|
|
219
|
+
packages,
|
|
220
|
+
rootPath: ''
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Parse packages from pnpm-workspace.yaml
|
|
226
|
+
*/
|
|
227
|
+
private parseYamlPackages(yamlContent: string): string[] {
|
|
228
|
+
const patterns: string[] = [];
|
|
229
|
+
const lines = yamlContent.split('\n');
|
|
230
|
+
let inPackages = false;
|
|
231
|
+
|
|
232
|
+
for (const line of lines) {
|
|
233
|
+
if (line.trim() === 'packages:') {
|
|
234
|
+
inPackages = true;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (inPackages) {
|
|
238
|
+
if (line.match(/^\s+-\s+["']?(.+?)["']?\s*$/)) {
|
|
239
|
+
const match = line.match(/^\s+-\s+["']?(.+?)["']?\s*$/);
|
|
240
|
+
if (match) {
|
|
241
|
+
patterns.push(match[1]);
|
|
242
|
+
}
|
|
243
|
+
} else if (!line.startsWith(' ') && !line.startsWith('-') && line.trim()) {
|
|
244
|
+
break; // New section started
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return patterns;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Resolve package patterns to actual packages
|
|
254
|
+
*/
|
|
255
|
+
private async resolvePackagePatterns(
|
|
256
|
+
github: GithubClient,
|
|
257
|
+
owner: string,
|
|
258
|
+
repo: string,
|
|
259
|
+
patterns: string[],
|
|
260
|
+
branch?: string
|
|
261
|
+
): Promise<MonorepoPackage[]> {
|
|
262
|
+
const packages: MonorepoPackage[] = [];
|
|
263
|
+
const seen = new Set<string>();
|
|
264
|
+
|
|
265
|
+
for (const pattern of patterns) {
|
|
266
|
+
// Handle simple patterns like "packages/*"
|
|
267
|
+
const basePath = pattern.replace(/\/\*.*$/, '');
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const dirs = await github.listDirectory(owner, repo, basePath, branch);
|
|
271
|
+
|
|
272
|
+
for (const dir of dirs) {
|
|
273
|
+
if (dir.type !== 'dir') continue;
|
|
274
|
+
if (seen.has(dir.path)) continue;
|
|
275
|
+
seen.add(dir.path);
|
|
276
|
+
|
|
277
|
+
// Try to get package name from package.json or Cargo.toml or similar
|
|
278
|
+
const packageInfo = await this.getPackageInfo(github, owner, repo, dir.path, branch);
|
|
279
|
+
|
|
280
|
+
if (packageInfo) {
|
|
281
|
+
packages.push({
|
|
282
|
+
name: packageInfo.name,
|
|
283
|
+
path: dir.path,
|
|
284
|
+
language: packageInfo.language
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
} catch {
|
|
289
|
+
// Directory doesn't exist or can't be listed
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return packages;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Get package info from manifest file
|
|
299
|
+
*/
|
|
300
|
+
private async getPackageInfo(
|
|
301
|
+
github: GithubClient,
|
|
302
|
+
owner: string,
|
|
303
|
+
repo: string,
|
|
304
|
+
packagePath: string,
|
|
305
|
+
branch?: string
|
|
306
|
+
): Promise<{ name: string; language?: string } | null> {
|
|
307
|
+
// Try package.json (Node.js)
|
|
308
|
+
const packageJson = await github.getFileContent(owner, repo, `${packagePath}/package.json`, branch);
|
|
309
|
+
if (packageJson) {
|
|
310
|
+
try {
|
|
311
|
+
const pkg = JSON.parse(packageJson.content);
|
|
312
|
+
const name = pkg.name || packagePath.split('/').pop()!;
|
|
313
|
+
return { name, language: 'typescript' };
|
|
314
|
+
} catch {}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Try Cargo.toml (Rust)
|
|
318
|
+
const cargoToml = await github.getFileContent(owner, repo, `${packagePath}/Cargo.toml`, branch);
|
|
319
|
+
if (cargoToml) {
|
|
320
|
+
const nameMatch = cargoToml.content.match(/name\s*=\s*"([^"]+)"/);
|
|
321
|
+
return {
|
|
322
|
+
name: nameMatch?.[1] || packagePath.split('/').pop()!,
|
|
323
|
+
language: 'rust'
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Try pyproject.toml (Python)
|
|
328
|
+
const pyprojectToml = await github.getFileContent(owner, repo, `${packagePath}/pyproject.toml`, branch);
|
|
329
|
+
if (pyprojectToml) {
|
|
330
|
+
const nameMatch = pyprojectToml.content.match(/name\s*=\s*"([^"]+)"/);
|
|
331
|
+
return {
|
|
332
|
+
name: nameMatch?.[1] || packagePath.split('/').pop()!,
|
|
333
|
+
language: 'python'
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Try setup.py (Python legacy)
|
|
338
|
+
const setupPy = await github.getFileContent(owner, repo, `${packagePath}/setup.py`, branch);
|
|
339
|
+
if (setupPy) {
|
|
340
|
+
const nameMatch = setupPy.content.match(/name\s*=\s*["']([^"']+)["']/);
|
|
341
|
+
return {
|
|
342
|
+
name: nameMatch?.[1] || packagePath.split('/').pop()!,
|
|
343
|
+
language: 'python'
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Try go.mod (Go)
|
|
348
|
+
const goMod = await github.getFileContent(owner, repo, `${packagePath}/go.mod`, branch);
|
|
349
|
+
if (goMod) {
|
|
350
|
+
const moduleMatch = goMod.content.match(/module\s+(\S+)/);
|
|
351
|
+
const name = moduleMatch?.[1].split('/').pop() || packagePath.split('/').pop()!;
|
|
352
|
+
return { name, language: 'go' };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Try looking for main source file
|
|
356
|
+
const contents = await github.listDirectory(owner, repo, packagePath, branch);
|
|
357
|
+
for (const item of contents) {
|
|
358
|
+
if (item.path.endsWith('.gemspec')) {
|
|
359
|
+
return { name: packagePath.split('/').pop()!, language: 'ruby' };
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Fallback: use directory name
|
|
364
|
+
return { name: packagePath.split('/').pop()! };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Add namespace prefix to tool names for monorepo packages
|
|
369
|
+
*/
|
|
370
|
+
addNamespacePrefix(toolName: string, packageName: string): string {
|
|
371
|
+
// Clean package name for use as prefix
|
|
372
|
+
const prefix = packageName
|
|
373
|
+
.replace(/^@[^/]+\//, '') // Remove scope like @org/
|
|
374
|
+
.replace(/[^a-zA-Z0-9]/g, '_'); // Replace special chars
|
|
375
|
+
|
|
376
|
+
return `${prefix}/${toolName}`;
|
|
377
|
+
}
|
|
378
|
+
}
|