purecontext-mcp 1.0.1
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/LICENSE +21 -0
- package/README.md +207 -0
- package/dist/adapters/actix-web.d.ts +28 -0
- package/dist/adapters/actix-web.d.ts.map +1 -0
- package/dist/adapters/actix-web.js +362 -0
- package/dist/adapters/actix-web.js.map +1 -0
- package/dist/adapters/adapter-registry.d.ts +34 -0
- package/dist/adapters/adapter-registry.d.ts.map +1 -0
- package/dist/adapters/adapter-registry.js +92 -0
- package/dist/adapters/adapter-registry.js.map +1 -0
- package/dist/adapters/angular.d.ts +13 -0
- package/dist/adapters/angular.d.ts.map +1 -0
- package/dist/adapters/angular.js +348 -0
- package/dist/adapters/angular.js.map +1 -0
- package/dist/adapters/axum.d.ts +16 -0
- package/dist/adapters/axum.d.ts.map +1 -0
- package/dist/adapters/axum.js +299 -0
- package/dist/adapters/axum.js.map +1 -0
- package/dist/adapters/django-orm.d.ts +28 -0
- package/dist/adapters/django-orm.d.ts.map +1 -0
- package/dist/adapters/django-orm.js +464 -0
- package/dist/adapters/django-orm.js.map +1 -0
- package/dist/adapters/django.d.ts +16 -0
- package/dist/adapters/django.d.ts.map +1 -0
- package/dist/adapters/django.js +257 -0
- package/dist/adapters/django.js.map +1 -0
- package/dist/adapters/echo.d.ts +15 -0
- package/dist/adapters/echo.d.ts.map +1 -0
- package/dist/adapters/echo.js +142 -0
- package/dist/adapters/echo.js.map +1 -0
- package/dist/adapters/express.d.ts +15 -0
- package/dist/adapters/express.d.ts.map +1 -0
- package/dist/adapters/express.js +126 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/fastapi.d.ts +16 -0
- package/dist/adapters/fastapi.d.ts.map +1 -0
- package/dist/adapters/fastapi.js +113 -0
- package/dist/adapters/fastapi.js.map +1 -0
- package/dist/adapters/fastify.d.ts +14 -0
- package/dist/adapters/fastify.d.ts.map +1 -0
- package/dist/adapters/fastify.js +124 -0
- package/dist/adapters/fastify.js.map +1 -0
- package/dist/adapters/fiber.d.ts +15 -0
- package/dist/adapters/fiber.d.ts.map +1 -0
- package/dist/adapters/fiber.js +147 -0
- package/dist/adapters/fiber.js.map +1 -0
- package/dist/adapters/flask.d.ts +18 -0
- package/dist/adapters/flask.d.ts.map +1 -0
- package/dist/adapters/flask.js +175 -0
- package/dist/adapters/flask.js.map +1 -0
- package/dist/adapters/flutter.d.ts +19 -0
- package/dist/adapters/flutter.d.ts.map +1 -0
- package/dist/adapters/flutter.js +251 -0
- package/dist/adapters/flutter.js.map +1 -0
- package/dist/adapters/gin.d.ts +16 -0
- package/dist/adapters/gin.d.ts.map +1 -0
- package/dist/adapters/gin.js +159 -0
- package/dist/adapters/gin.js.map +1 -0
- package/dist/adapters/hibernate.d.ts +24 -0
- package/dist/adapters/hibernate.d.ts.map +1 -0
- package/dist/adapters/hibernate.js +448 -0
- package/dist/adapters/hibernate.js.map +1 -0
- package/dist/adapters/ktor.d.ts +18 -0
- package/dist/adapters/ktor.d.ts.map +1 -0
- package/dist/adapters/ktor.js +219 -0
- package/dist/adapters/ktor.js.map +1 -0
- package/dist/adapters/laravel.d.ts +19 -0
- package/dist/adapters/laravel.d.ts.map +1 -0
- package/dist/adapters/laravel.js +370 -0
- package/dist/adapters/laravel.js.map +1 -0
- package/dist/adapters/micronaut.d.ts +21 -0
- package/dist/adapters/micronaut.d.ts.map +1 -0
- package/dist/adapters/micronaut.js +435 -0
- package/dist/adapters/micronaut.js.map +1 -0
- package/dist/adapters/nestjs.d.ts +12 -0
- package/dist/adapters/nestjs.d.ts.map +1 -0
- package/dist/adapters/nestjs.js +401 -0
- package/dist/adapters/nestjs.js.map +1 -0
- package/dist/adapters/nextjs.d.ts +49 -0
- package/dist/adapters/nextjs.d.ts.map +1 -0
- package/dist/adapters/nextjs.js +412 -0
- package/dist/adapters/nextjs.js.map +1 -0
- package/dist/adapters/nuxt.d.ts +44 -0
- package/dist/adapters/nuxt.d.ts.map +1 -0
- package/dist/adapters/nuxt.js +243 -0
- package/dist/adapters/nuxt.js.map +1 -0
- package/dist/adapters/quarkus.d.ts +21 -0
- package/dist/adapters/quarkus.d.ts.map +1 -0
- package/dist/adapters/quarkus.js +395 -0
- package/dist/adapters/quarkus.js.map +1 -0
- package/dist/adapters/rails.d.ts +18 -0
- package/dist/adapters/rails.d.ts.map +1 -0
- package/dist/adapters/rails.js +363 -0
- package/dist/adapters/rails.js.map +1 -0
- package/dist/adapters/react.d.ts +18 -0
- package/dist/adapters/react.d.ts.map +1 -0
- package/dist/adapters/react.js +95 -0
- package/dist/adapters/react.js.map +1 -0
- package/dist/adapters/rocket.d.ts +19 -0
- package/dist/adapters/rocket.d.ts.map +1 -0
- package/dist/adapters/rocket.js +271 -0
- package/dist/adapters/rocket.js.map +1 -0
- package/dist/adapters/sinatra.d.ts +16 -0
- package/dist/adapters/sinatra.d.ts.map +1 -0
- package/dist/adapters/sinatra.js +114 -0
- package/dist/adapters/sinatra.js.map +1 -0
- package/dist/adapters/spring-boot.d.ts +24 -0
- package/dist/adapters/spring-boot.d.ts.map +1 -0
- package/dist/adapters/spring-boot.js +399 -0
- package/dist/adapters/spring-boot.js.map +1 -0
- package/dist/adapters/spring-kotlin.d.ts +19 -0
- package/dist/adapters/spring-kotlin.d.ts.map +1 -0
- package/dist/adapters/spring-kotlin.js +297 -0
- package/dist/adapters/spring-kotlin.js.map +1 -0
- package/dist/adapters/sqlalchemy.d.ts +28 -0
- package/dist/adapters/sqlalchemy.d.ts.map +1 -0
- package/dist/adapters/sqlalchemy.js +435 -0
- package/dist/adapters/sqlalchemy.js.map +1 -0
- package/dist/adapters/symfony.d.ts +16 -0
- package/dist/adapters/symfony.d.ts.map +1 -0
- package/dist/adapters/symfony.js +242 -0
- package/dist/adapters/symfony.js.map +1 -0
- package/dist/adapters/vapor.d.ts +21 -0
- package/dist/adapters/vapor.d.ts.map +1 -0
- package/dist/adapters/vapor.js +269 -0
- package/dist/adapters/vapor.js.map +1 -0
- package/dist/adapters/vue-preprocessor.d.ts +26 -0
- package/dist/adapters/vue-preprocessor.d.ts.map +1 -0
- package/dist/adapters/vue-preprocessor.js +82 -0
- package/dist/adapters/vue-preprocessor.js.map +1 -0
- package/dist/adapters/vue.d.ts +13 -0
- package/dist/adapters/vue.d.ts.map +1 -0
- package/dist/adapters/vue.js +134 -0
- package/dist/adapters/vue.js.map +1 -0
- package/dist/config/cli.d.ts +26 -0
- package/dist/config/cli.d.ts.map +1 -0
- package/dist/config/cli.js +291 -0
- package/dist/config/cli.js.map +1 -0
- package/dist/config/config-loader.d.ts +26 -0
- package/dist/config/config-loader.d.ts.map +1 -0
- package/dist/config/config-loader.js +168 -0
- package/dist/config/config-loader.js.map +1 -0
- package/dist/config/config-schema.d.ts +228 -0
- package/dist/config/config-schema.d.ts.map +1 -0
- package/dist/config/config-schema.js +400 -0
- package/dist/config/config-schema.js.map +1 -0
- package/dist/config/keys-cli.d.ts +37 -0
- package/dist/config/keys-cli.d.ts.map +1 -0
- package/dist/config/keys-cli.js +179 -0
- package/dist/config/keys-cli.js.map +1 -0
- package/dist/config/workspaces-cli.d.ts +2 -0
- package/dist/config/workspaces-cli.d.ts.map +1 -0
- package/dist/config/workspaces-cli.js +84 -0
- package/dist/config/workspaces-cli.js.map +1 -0
- package/dist/core/db/api-keys.d.ts +95 -0
- package/dist/core/db/api-keys.d.ts.map +1 -0
- package/dist/core/db/api-keys.js +293 -0
- package/dist/core/db/api-keys.js.map +1 -0
- package/dist/core/db/dep-store.d.ts +18 -0
- package/dist/core/db/dep-store.d.ts.map +1 -0
- package/dist/core/db/dep-store.js +155 -0
- package/dist/core/db/dep-store.js.map +1 -0
- package/dist/core/db/embedding-store.d.ts +40 -0
- package/dist/core/db/embedding-store.d.ts.map +1 -0
- package/dist/core/db/embedding-store.js +120 -0
- package/dist/core/db/embedding-store.js.map +1 -0
- package/dist/core/db/file-store.d.ts +27 -0
- package/dist/core/db/file-store.d.ts.map +1 -0
- package/dist/core/db/file-store.js +101 -0
- package/dist/core/db/file-store.js.map +1 -0
- package/dist/core/db/request-log.d.ts +40 -0
- package/dist/core/db/request-log.d.ts.map +1 -0
- package/dist/core/db/request-log.js +90 -0
- package/dist/core/db/request-log.js.map +1 -0
- package/dist/core/db/savings-store.d.ts +9 -0
- package/dist/core/db/savings-store.d.ts.map +1 -0
- package/dist/core/db/savings-store.js +64 -0
- package/dist/core/db/savings-store.js.map +1 -0
- package/dist/core/db/schema.d.ts +14 -0
- package/dist/core/db/schema.d.ts.map +1 -0
- package/dist/core/db/schema.js +235 -0
- package/dist/core/db/schema.js.map +1 -0
- package/dist/core/db/symbol-store.d.ts +64 -0
- package/dist/core/db/symbol-store.d.ts.map +1 -0
- package/dist/core/db/symbol-store.js +243 -0
- package/dist/core/db/symbol-store.js.map +1 -0
- package/dist/core/db/tenants.d.ts +91 -0
- package/dist/core/db/tenants.d.ts.map +1 -0
- package/dist/core/db/tenants.js +219 -0
- package/dist/core/db/tenants.js.map +1 -0
- package/dist/core/errors.d.ts +72 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +140 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/file-discovery.d.ts +17 -0
- package/dist/core/file-discovery.d.ts.map +1 -0
- package/dist/core/file-discovery.js +136 -0
- package/dist/core/file-discovery.js.map +1 -0
- package/dist/core/file-processor.d.ts +21 -0
- package/dist/core/file-processor.d.ts.map +1 -0
- package/dist/core/file-processor.js +110 -0
- package/dist/core/file-processor.js.map +1 -0
- package/dist/core/hash-cache.d.ts +18 -0
- package/dist/core/hash-cache.d.ts.map +1 -0
- package/dist/core/hash-cache.js +35 -0
- package/dist/core/hash-cache.js.map +1 -0
- package/dist/core/index-manager.d.ts +18 -0
- package/dist/core/index-manager.d.ts.map +1 -0
- package/dist/core/index-manager.js +461 -0
- package/dist/core/index-manager.js.map +1 -0
- package/dist/core/indexing-worker.d.ts +60 -0
- package/dist/core/indexing-worker.d.ts.map +1 -0
- package/dist/core/indexing-worker.js +128 -0
- package/dist/core/indexing-worker.js.map +1 -0
- package/dist/core/logger.d.ts +10 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +40 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/parse-dispatcher.d.ts +18 -0
- package/dist/core/parse-dispatcher.d.ts.map +1 -0
- package/dist/core/parse-dispatcher.js +74 -0
- package/dist/core/parse-dispatcher.js.map +1 -0
- package/dist/core/search/query-preprocessor.d.ts +22 -0
- package/dist/core/search/query-preprocessor.d.ts.map +1 -0
- package/dist/core/search/query-preprocessor.js +77 -0
- package/dist/core/search/query-preprocessor.js.map +1 -0
- package/dist/core/search/relevance-ranker.d.ts +34 -0
- package/dist/core/search/relevance-ranker.d.ts.map +1 -0
- package/dist/core/search/relevance-ranker.js +132 -0
- package/dist/core/search/relevance-ranker.js.map +1 -0
- package/dist/core/security.d.ts +40 -0
- package/dist/core/security.d.ts.map +1 -0
- package/dist/core/security.js +133 -0
- package/dist/core/security.js.map +1 -0
- package/dist/core/telemetry.d.ts +19 -0
- package/dist/core/telemetry.d.ts.map +1 -0
- package/dist/core/telemetry.js +101 -0
- package/dist/core/telemetry.js.map +1 -0
- package/dist/core/tenant-context.d.ts +22 -0
- package/dist/core/tenant-context.d.ts.map +1 -0
- package/dist/core/tenant-context.js +16 -0
- package/dist/core/tenant-context.js.map +1 -0
- package/dist/core/token-tracker.d.ts +39 -0
- package/dist/core/token-tracker.d.ts.map +1 -0
- package/dist/core/token-tracker.js +153 -0
- package/dist/core/token-tracker.js.map +1 -0
- package/dist/core/types.d.ts +145 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/watcher/file-watcher.d.ts +18 -0
- package/dist/core/watcher/file-watcher.d.ts.map +1 -0
- package/dist/core/watcher/file-watcher.js +123 -0
- package/dist/core/watcher/file-watcher.js.map +1 -0
- package/dist/core/worker-pool.d.ts +24 -0
- package/dist/core/worker-pool.d.ts.map +1 -0
- package/dist/core/worker-pool.js +92 -0
- package/dist/core/worker-pool.js.map +1 -0
- package/dist/graph/graph-builder.d.ts +16 -0
- package/dist/graph/graph-builder.d.ts.map +1 -0
- package/dist/graph/graph-builder.js +51 -0
- package/dist/graph/graph-builder.js.map +1 -0
- package/dist/graph/graph-traversal.d.ts +57 -0
- package/dist/graph/graph-traversal.d.ts.map +1 -0
- package/dist/graph/graph-traversal.js +144 -0
- package/dist/graph/graph-traversal.js.map +1 -0
- package/dist/graph/path-resolver.d.ts +13 -0
- package/dist/graph/path-resolver.d.ts.map +1 -0
- package/dist/graph/path-resolver.js +161 -0
- package/dist/graph/path-resolver.js.map +1 -0
- package/dist/handlers/c.d.ts +3 -0
- package/dist/handlers/c.d.ts.map +1 -0
- package/dist/handlers/c.js +473 -0
- package/dist/handlers/c.js.map +1 -0
- package/dist/handlers/cpp.d.ts +3 -0
- package/dist/handlers/cpp.d.ts.map +1 -0
- package/dist/handlers/cpp.js +682 -0
- package/dist/handlers/cpp.js.map +1 -0
- package/dist/handlers/csharp.d.ts +3 -0
- package/dist/handlers/csharp.d.ts.map +1 -0
- package/dist/handlers/csharp.js +369 -0
- package/dist/handlers/csharp.js.map +1 -0
- package/dist/handlers/dart.d.ts +3 -0
- package/dist/handlers/dart.d.ts.map +1 -0
- package/dist/handlers/dart.js +596 -0
- package/dist/handlers/dart.js.map +1 -0
- package/dist/handlers/elixir.d.ts +8 -0
- package/dist/handlers/elixir.d.ts.map +1 -0
- package/dist/handlers/elixir.js +412 -0
- package/dist/handlers/elixir.js.map +1 -0
- package/dist/handlers/go.d.ts +3 -0
- package/dist/handlers/go.d.ts.map +1 -0
- package/dist/handlers/go.js +262 -0
- package/dist/handlers/go.js.map +1 -0
- package/dist/handlers/handler-registry.d.ts +20 -0
- package/dist/handlers/handler-registry.d.ts.map +1 -0
- package/dist/handlers/handler-registry.js +69 -0
- package/dist/handlers/handler-registry.js.map +1 -0
- package/dist/handlers/haskell.d.ts +17 -0
- package/dist/handlers/haskell.d.ts.map +1 -0
- package/dist/handlers/haskell.js +356 -0
- package/dist/handlers/haskell.js.map +1 -0
- package/dist/handlers/java.d.ts +3 -0
- package/dist/handlers/java.d.ts.map +1 -0
- package/dist/handlers/java.js +350 -0
- package/dist/handlers/java.js.map +1 -0
- package/dist/handlers/javascript.d.ts +3 -0
- package/dist/handlers/javascript.d.ts.map +1 -0
- package/dist/handlers/javascript.js +232 -0
- package/dist/handlers/javascript.js.map +1 -0
- package/dist/handlers/kotlin.d.ts +3 -0
- package/dist/handlers/kotlin.d.ts.map +1 -0
- package/dist/handlers/kotlin.js +317 -0
- package/dist/handlers/kotlin.js.map +1 -0
- package/dist/handlers/lua.d.ts +3 -0
- package/dist/handlers/lua.d.ts.map +1 -0
- package/dist/handlers/lua.js +328 -0
- package/dist/handlers/lua.js.map +1 -0
- package/dist/handlers/php.d.ts +3 -0
- package/dist/handlers/php.d.ts.map +1 -0
- package/dist/handlers/php.js +343 -0
- package/dist/handlers/php.js.map +1 -0
- package/dist/handlers/python.d.ts +3 -0
- package/dist/handlers/python.d.ts.map +1 -0
- package/dist/handlers/python.js +343 -0
- package/dist/handlers/python.js.map +1 -0
- package/dist/handlers/r.d.ts +9 -0
- package/dist/handlers/r.d.ts.map +1 -0
- package/dist/handlers/r.js +452 -0
- package/dist/handlers/r.js.map +1 -0
- package/dist/handlers/ruby.d.ts +8 -0
- package/dist/handlers/ruby.d.ts.map +1 -0
- package/dist/handlers/ruby.js +271 -0
- package/dist/handlers/ruby.js.map +1 -0
- package/dist/handlers/rust.d.ts +3 -0
- package/dist/handlers/rust.d.ts.map +1 -0
- package/dist/handlers/rust.js +420 -0
- package/dist/handlers/rust.js.map +1 -0
- package/dist/handlers/scala.d.ts +9 -0
- package/dist/handlers/scala.d.ts.map +1 -0
- package/dist/handlers/scala.js +480 -0
- package/dist/handlers/scala.js.map +1 -0
- package/dist/handlers/swift.d.ts +3 -0
- package/dist/handlers/swift.d.ts.map +1 -0
- package/dist/handlers/swift.js +553 -0
- package/dist/handlers/swift.js.map +1 -0
- package/dist/handlers/typescript.d.ts +4 -0
- package/dist/handlers/typescript.d.ts.map +1 -0
- package/dist/handlers/typescript.js +325 -0
- package/dist/handlers/typescript.js.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +266 -0
- package/dist/index.js.map +1 -0
- package/dist/semantic/embedding-provider.d.ts +66 -0
- package/dist/semantic/embedding-provider.d.ts.map +1 -0
- package/dist/semantic/embedding-provider.js +176 -0
- package/dist/semantic/embedding-provider.js.map +1 -0
- package/dist/semantic/hnsw-index.d.ts +96 -0
- package/dist/semantic/hnsw-index.d.ts.map +1 -0
- package/dist/semantic/hnsw-index.js +364 -0
- package/dist/semantic/hnsw-index.js.map +1 -0
- package/dist/semantic/hybrid-search.d.ts +46 -0
- package/dist/semantic/hybrid-search.d.ts.map +1 -0
- package/dist/semantic/hybrid-search.js +164 -0
- package/dist/semantic/hybrid-search.js.map +1 -0
- package/dist/semantic/query-expansion.d.ts +25 -0
- package/dist/semantic/query-expansion.d.ts.map +1 -0
- package/dist/semantic/query-expansion.js +150 -0
- package/dist/semantic/query-expansion.js.map +1 -0
- package/dist/semantic/semantic-indexer.d.ts +64 -0
- package/dist/semantic/semantic-indexer.d.ts.map +1 -0
- package/dist/semantic/semantic-indexer.js +162 -0
- package/dist/semantic/semantic-indexer.js.map +1 -0
- package/dist/semantic/text-preparation.d.ts +23 -0
- package/dist/semantic/text-preparation.d.ts.map +1 -0
- package/dist/semantic/text-preparation.js +56 -0
- package/dist/semantic/text-preparation.js.map +1 -0
- package/dist/semantic/vector-store.d.ts +81 -0
- package/dist/semantic/vector-store.d.ts.map +1 -0
- package/dist/semantic/vector-store.js +157 -0
- package/dist/semantic/vector-store.js.map +1 -0
- package/dist/server/admin-api.d.ts +88 -0
- package/dist/server/admin-api.d.ts.map +1 -0
- package/dist/server/admin-api.js +661 -0
- package/dist/server/admin-api.js.map +1 -0
- package/dist/server/auth/api-key.d.ts +105 -0
- package/dist/server/auth/api-key.d.ts.map +1 -0
- package/dist/server/auth/api-key.js +276 -0
- package/dist/server/auth/api-key.js.map +1 -0
- package/dist/server/http-server.d.ts +100 -0
- package/dist/server/http-server.d.ts.map +1 -0
- package/dist/server/http-server.js +823 -0
- package/dist/server/http-server.js.map +1 -0
- package/dist/server/mcp-server.d.ts +23 -0
- package/dist/server/mcp-server.d.ts.map +1 -0
- package/dist/server/mcp-server.js +214 -0
- package/dist/server/mcp-server.js.map +1 -0
- package/dist/server/rate-limit-store.d.ts +25 -0
- package/dist/server/rate-limit-store.d.ts.map +1 -0
- package/dist/server/rate-limit-store.js +46 -0
- package/dist/server/rate-limit-store.js.map +1 -0
- package/dist/server/rate-limiter.d.ts +59 -0
- package/dist/server/rate-limiter.d.ts.map +1 -0
- package/dist/server/rate-limiter.js +85 -0
- package/dist/server/rate-limiter.js.map +1 -0
- package/dist/server/tools/_meta.d.ts +24 -0
- package/dist/server/tools/_meta.d.ts.map +1 -0
- package/dist/server/tools/_meta.js +35 -0
- package/dist/server/tools/_meta.js.map +1 -0
- package/dist/server/tools/find-dead-code.d.ts +11 -0
- package/dist/server/tools/find-dead-code.d.ts.map +1 -0
- package/dist/server/tools/find-dead-code.js +45 -0
- package/dist/server/tools/find-dead-code.js.map +1 -0
- package/dist/server/tools/find-importers.d.ts +13 -0
- package/dist/server/tools/find-importers.d.ts.map +1 -0
- package/dist/server/tools/find-importers.js +46 -0
- package/dist/server/tools/find-importers.js.map +1 -0
- package/dist/server/tools/get-blast-radius.d.ts +15 -0
- package/dist/server/tools/get-blast-radius.d.ts.map +1 -0
- package/dist/server/tools/get-blast-radius.js +51 -0
- package/dist/server/tools/get-blast-radius.js.map +1 -0
- package/dist/server/tools/get-context-bundle.d.ts +15 -0
- package/dist/server/tools/get-context-bundle.d.ts.map +1 -0
- package/dist/server/tools/get-context-bundle.js +53 -0
- package/dist/server/tools/get-context-bundle.js.map +1 -0
- package/dist/server/tools/get-file-outline.d.ts +13 -0
- package/dist/server/tools/get-file-outline.d.ts.map +1 -0
- package/dist/server/tools/get-file-outline.js +42 -0
- package/dist/server/tools/get-file-outline.js.map +1 -0
- package/dist/server/tools/get-file-tree.d.ts +11 -0
- package/dist/server/tools/get-file-tree.d.ts.map +1 -0
- package/dist/server/tools/get-file-tree.js +63 -0
- package/dist/server/tools/get-file-tree.js.map +1 -0
- package/dist/server/tools/get-graph.d.ts +46 -0
- package/dist/server/tools/get-graph.d.ts.map +1 -0
- package/dist/server/tools/get-graph.js +168 -0
- package/dist/server/tools/get-graph.js.map +1 -0
- package/dist/server/tools/get-layer-violations.d.ts +37 -0
- package/dist/server/tools/get-layer-violations.d.ts.map +1 -0
- package/dist/server/tools/get-layer-violations.js +138 -0
- package/dist/server/tools/get-layer-violations.js.map +1 -0
- package/dist/server/tools/get-repo-outline.d.ts +13 -0
- package/dist/server/tools/get-repo-outline.d.ts.map +1 -0
- package/dist/server/tools/get-repo-outline.js +72 -0
- package/dist/server/tools/get-repo-outline.js.map +1 -0
- package/dist/server/tools/get-savings-stats.d.ts +11 -0
- package/dist/server/tools/get-savings-stats.d.ts.map +1 -0
- package/dist/server/tools/get-savings-stats.js +56 -0
- package/dist/server/tools/get-savings-stats.js.map +1 -0
- package/dist/server/tools/get-symbol-source.d.ts +13 -0
- package/dist/server/tools/get-symbol-source.d.ts.map +1 -0
- package/dist/server/tools/get-symbol-source.js +93 -0
- package/dist/server/tools/get-symbol-source.js.map +1 -0
- package/dist/server/tools/index-folder.d.ts +15 -0
- package/dist/server/tools/index-folder.d.ts.map +1 -0
- package/dist/server/tools/index-folder.js +45 -0
- package/dist/server/tools/index-folder.js.map +1 -0
- package/dist/server/tools/index-repo.d.ts +26 -0
- package/dist/server/tools/index-repo.d.ts.map +1 -0
- package/dist/server/tools/index-repo.js +164 -0
- package/dist/server/tools/index-repo.js.map +1 -0
- package/dist/server/tools/list-repos.d.ts +11 -0
- package/dist/server/tools/list-repos.d.ts.map +1 -0
- package/dist/server/tools/list-repos.js +60 -0
- package/dist/server/tools/list-repos.js.map +1 -0
- package/dist/server/tools/resolve-repo.d.ts +11 -0
- package/dist/server/tools/resolve-repo.d.ts.map +1 -0
- package/dist/server/tools/resolve-repo.js +44 -0
- package/dist/server/tools/resolve-repo.js.map +1 -0
- package/dist/server/tools/search-semantic.d.ts +56 -0
- package/dist/server/tools/search-semantic.d.ts.map +1 -0
- package/dist/server/tools/search-semantic.js +187 -0
- package/dist/server/tools/search-semantic.js.map +1 -0
- package/dist/server/tools/search-symbols.d.ts +43 -0
- package/dist/server/tools/search-symbols.d.ts.map +1 -0
- package/dist/server/tools/search-symbols.js +197 -0
- package/dist/server/tools/search-symbols.js.map +1 -0
- package/dist/server/tools/search-text.d.ts +21 -0
- package/dist/server/tools/search-text.d.ts.map +1 -0
- package/dist/server/tools/search-text.js +131 -0
- package/dist/server/tools/search-text.js.map +1 -0
- package/dist/server/transport.d.ts +59 -0
- package/dist/server/transport.d.ts.map +1 -0
- package/dist/server/transport.js +92 -0
- package/dist/server/transport.js.map +1 -0
- package/dist/summarizer/ai-summarizer.d.ts +71 -0
- package/dist/summarizer/ai-summarizer.d.ts.map +1 -0
- package/dist/summarizer/ai-summarizer.js +228 -0
- package/dist/summarizer/ai-summarizer.js.map +1 -0
- package/dist/summarizer/docstring-extractor.d.ts +9 -0
- package/dist/summarizer/docstring-extractor.d.ts.map +1 -0
- package/dist/summarizer/docstring-extractor.js +89 -0
- package/dist/summarizer/docstring-extractor.js.map +1 -0
- package/dist/summarizer/embeddings.d.ts +29 -0
- package/dist/summarizer/embeddings.d.ts.map +1 -0
- package/dist/summarizer/embeddings.js +133 -0
- package/dist/summarizer/embeddings.js.map +1 -0
- package/dist/summarizer/summarizer.d.ts +22 -0
- package/dist/summarizer/summarizer.d.ts.map +1 -0
- package/dist/summarizer/summarizer.js +86 -0
- package/dist/summarizer/summarizer.js.map +1 -0
- package/dist/ui/assets/BlastRadius-ccQ3ktbv.js +1 -0
- package/dist/ui/assets/DependencyGraph-BZV40eAE.css +1 -0
- package/dist/ui/assets/DependencyGraph-Ca84q89b.js +27 -0
- package/dist/ui/assets/NotFound-C1wVKP-6.js +1 -0
- package/dist/ui/assets/RepoDetail-B_8qid2Y.js +1 -0
- package/dist/ui/assets/RepoList-BuPu-ODM.js +1 -0
- package/dist/ui/assets/Search-BzZ9Og8E.js +1 -0
- package/dist/ui/assets/SymbolView-FdpKlzql.js +14 -0
- package/dist/ui/assets/client-BANW_tIp.js +1 -0
- package/dist/ui/assets/index-CnwwQi_S.js +61 -0
- package/dist/ui/assets/index-SS2IXr8I.css +1 -0
- package/dist/ui/assets/repoStore-SbGR6YI6.js +1 -0
- package/dist/ui/assets/useSearch-DHyve22h.js +1 -0
- package/dist/ui/index.html +13 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/grammars/tree-sitter-c.wasm +0 -0
- package/grammars/tree-sitter-cpp.wasm +0 -0
- package/grammars/tree-sitter-csharp.wasm +0 -0
- package/grammars/tree-sitter-dart.wasm +0 -0
- package/grammars/tree-sitter-elixir.wasm +0 -0
- package/grammars/tree-sitter-go.wasm +0 -0
- package/grammars/tree-sitter-haskell.wasm +0 -0
- package/grammars/tree-sitter-java.wasm +0 -0
- package/grammars/tree-sitter-javascript.wasm +0 -0
- package/grammars/tree-sitter-kotlin.wasm +0 -0
- package/grammars/tree-sitter-lua.wasm +0 -0
- package/grammars/tree-sitter-php.wasm +0 -0
- package/grammars/tree-sitter-python.wasm +0 -0
- package/grammars/tree-sitter-r.wasm +0 -0
- package/grammars/tree-sitter-ruby.wasm +0 -0
- package/grammars/tree-sitter-rust.wasm +0 -0
- package/grammars/tree-sitter-scala.wasm +0 -0
- package/grammars/tree-sitter-swift.wasm +0 -0
- package/grammars/tree-sitter-tsx.wasm +0 -0
- package/grammars/tree-sitter-typescript.wasm +0 -0
- package/package.json +104 -0
- package/scripts/check-sqlite.js +41 -0
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
3
|
+
import { createReadStream, existsSync, statSync, readdirSync } from 'node:fs';
|
|
4
|
+
import { join, extname, resolve as resolvePath } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
7
|
+
import { logger } from '../core/logger.js';
|
|
8
|
+
import { QuotaExceededError } from '../core/errors.js';
|
|
9
|
+
import { VERSION } from '../version.js';
|
|
10
|
+
import { getIndexDir } from '../core/db/schema.js';
|
|
11
|
+
import { TokenBucketLimiter } from './rate-limiter.js';
|
|
12
|
+
import { extractApiKeyFromHeaders, validateFormat, getRequiredPermission, hasPermission, } from './auth/api-key.js';
|
|
13
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
14
|
+
const MAX_BODY_BYTES = 1_048_576; // 1 MB — guard against malformed / oversized requests
|
|
15
|
+
const SSE_KEEPALIVE_MS = 30_000; // 30 s — prevents proxy/load-balancer timeouts on idle SSE streams
|
|
16
|
+
// ─── CORS ──────────────────────────────────────────────────────────────────────
|
|
17
|
+
/**
|
|
18
|
+
* Match an origin against a list of patterns.
|
|
19
|
+
* Patterns may contain `*` as a wildcard within a path segment (e.g. `http://localhost:*`).
|
|
20
|
+
*/
|
|
21
|
+
export function originAllowed(origin, patterns) {
|
|
22
|
+
for (const pattern of patterns) {
|
|
23
|
+
// Escape regex special chars except *, then replace * with a segment wildcard
|
|
24
|
+
const re = new RegExp('^' +
|
|
25
|
+
pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '[^/]*') +
|
|
26
|
+
'$');
|
|
27
|
+
if (re.test(origin))
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
function setCorsHeaders(req, res, corsOrigins) {
|
|
33
|
+
const origin = req.headers['origin'] ?? '';
|
|
34
|
+
if (origin && originAllowed(origin, corsOrigins)) {
|
|
35
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
36
|
+
res.setHeader('Vary', 'Origin');
|
|
37
|
+
}
|
|
38
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
39
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, mcp-session-id, last-event-id');
|
|
40
|
+
}
|
|
41
|
+
// ─── Auth ─────────────────────────────────────────────────────────────────────
|
|
42
|
+
/**
|
|
43
|
+
* Check the Authorization header against the configured bearer token.
|
|
44
|
+
* Uses timing-safe comparison to prevent timing attacks.
|
|
45
|
+
* Returns true if auth is disabled OR the token matches.
|
|
46
|
+
*/
|
|
47
|
+
export function checkAuth(req, auth) {
|
|
48
|
+
if (!auth.enabled)
|
|
49
|
+
return true;
|
|
50
|
+
const header = req.headers['authorization'] ?? '';
|
|
51
|
+
const prefix = 'Bearer ';
|
|
52
|
+
if (!header.startsWith(prefix))
|
|
53
|
+
return false;
|
|
54
|
+
const provided = header.slice(prefix.length);
|
|
55
|
+
if (!provided || !auth.token)
|
|
56
|
+
return false;
|
|
57
|
+
// Pad to same length before comparison to avoid length-based oracle
|
|
58
|
+
try {
|
|
59
|
+
const a = Buffer.from(provided.padEnd(auth.token.length, '\0'));
|
|
60
|
+
const b = Buffer.from(auth.token.padEnd(provided.length, '\0'));
|
|
61
|
+
// timingSafeEqual requires same-length buffers
|
|
62
|
+
const maxLen = Math.max(a.length, b.length);
|
|
63
|
+
const ba = Buffer.alloc(maxLen);
|
|
64
|
+
const bb = Buffer.alloc(maxLen);
|
|
65
|
+
a.copy(ba);
|
|
66
|
+
b.copy(bb);
|
|
67
|
+
return timingSafeEqual(ba, bb) && provided.length === auth.token.length;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// ─── Body reader ──────────────────────────────────────────────────────────────
|
|
74
|
+
export function readBody(req, maxBytes) {
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
const chunks = [];
|
|
77
|
+
let total = 0;
|
|
78
|
+
req.on('data', (chunk) => {
|
|
79
|
+
total += chunk.length;
|
|
80
|
+
if (total > maxBytes) {
|
|
81
|
+
req.destroy(new Error('Request body too large'));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
chunks.push(chunk);
|
|
85
|
+
});
|
|
86
|
+
req.on('end', () => resolve(Buffer.concat(chunks)));
|
|
87
|
+
req.on('error', reject);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// ─── Rate limit helpers ────────────────────────────────────────────────────────
|
|
91
|
+
/**
|
|
92
|
+
* Extract the MCP tool name from a parsed JSON-RPC request body.
|
|
93
|
+
* Returns undefined for non-tool-call methods (e.g. initialize, list_tools).
|
|
94
|
+
*/
|
|
95
|
+
export function extractToolName(body) {
|
|
96
|
+
if (typeof body !== 'object' || body === null)
|
|
97
|
+
return undefined;
|
|
98
|
+
const b = body;
|
|
99
|
+
if (b['method'] !== 'tools/call')
|
|
100
|
+
return undefined;
|
|
101
|
+
const params = b['params'];
|
|
102
|
+
if (typeof params !== 'object' || params === null)
|
|
103
|
+
return undefined;
|
|
104
|
+
const name = params['name'];
|
|
105
|
+
return typeof name === 'string' ? name : undefined;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Return the token cost for a tool call.
|
|
109
|
+
* Falls back to 1 for tools not in the perToolLimits map.
|
|
110
|
+
*/
|
|
111
|
+
export function getToolCost(toolName, perToolLimits) {
|
|
112
|
+
if (toolName === undefined)
|
|
113
|
+
return 1;
|
|
114
|
+
return perToolLimits[toolName] ?? 1;
|
|
115
|
+
}
|
|
116
|
+
// ─── Health helpers ───────────────────────────────────────────────────────────
|
|
117
|
+
function getRepoCount() {
|
|
118
|
+
const dir = getIndexDir();
|
|
119
|
+
if (!existsSync(dir))
|
|
120
|
+
return 0;
|
|
121
|
+
try {
|
|
122
|
+
return readdirSync(dir).filter((f) => f.endsWith('.db')).length;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// ─── REST API ─────────────────────────────────────────────────────────────────
|
|
129
|
+
/**
|
|
130
|
+
* Parse URL search params from a URL string that may include a query string.
|
|
131
|
+
*/
|
|
132
|
+
export function parseQuery(url) {
|
|
133
|
+
const idx = url.indexOf('?');
|
|
134
|
+
return new URLSearchParams(idx >= 0 ? url.slice(idx + 1) : '');
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Match a URL pattern with named segments. Returns captured groups or null.
|
|
138
|
+
* Pattern example: '/api/repos/:id/tree'
|
|
139
|
+
*/
|
|
140
|
+
export function matchRoute(url, pattern) {
|
|
141
|
+
const urlPath = url.split('?')[0];
|
|
142
|
+
const patternParts = pattern.split('/');
|
|
143
|
+
const urlParts = urlPath.split('/');
|
|
144
|
+
if (patternParts.length !== urlParts.length)
|
|
145
|
+
return null;
|
|
146
|
+
const params = {};
|
|
147
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
148
|
+
const p = patternParts[i];
|
|
149
|
+
const u = urlParts[i];
|
|
150
|
+
if (p.startsWith(':')) {
|
|
151
|
+
params[p.slice(1)] = decodeURIComponent(u ?? '');
|
|
152
|
+
}
|
|
153
|
+
else if (p !== u) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return params;
|
|
158
|
+
}
|
|
159
|
+
function sendJson(res, status, body) {
|
|
160
|
+
const payload = JSON.stringify(body);
|
|
161
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
162
|
+
res.end(payload);
|
|
163
|
+
}
|
|
164
|
+
async function handleRestApi(_req, res, url, method) {
|
|
165
|
+
if (method !== 'GET') {
|
|
166
|
+
sendJson(res, 405, { error: 'Method not allowed' });
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const query = parseQuery(url);
|
|
170
|
+
// GET /api/repos
|
|
171
|
+
if (matchRoute(url, '/api/repos') !== null) {
|
|
172
|
+
const { handler } = await import('./tools/list-repos.js');
|
|
173
|
+
const result = handler();
|
|
174
|
+
const text = result.content[0];
|
|
175
|
+
if (text && text.type === 'text') {
|
|
176
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
177
|
+
res.end(text.text);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
sendJson(res, 500, { error: 'Unexpected tool response' });
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// GET /api/repos/:id/tree
|
|
185
|
+
const treeParams = matchRoute(url, '/api/repos/:id/tree');
|
|
186
|
+
if (treeParams !== null) {
|
|
187
|
+
const { handler } = await import('./tools/get-file-tree.js');
|
|
188
|
+
const result = handler({ repoId: treeParams['id'] });
|
|
189
|
+
const text = result.content[0];
|
|
190
|
+
if (text && text.type === 'text') {
|
|
191
|
+
if (result.isError) {
|
|
192
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
196
|
+
}
|
|
197
|
+
res.end(text.text);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
sendJson(res, 500, { error: 'Unexpected tool response' });
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
// GET /api/repos/:id/outline
|
|
205
|
+
const outlineParams = matchRoute(url, '/api/repos/:id/outline');
|
|
206
|
+
if (outlineParams !== null) {
|
|
207
|
+
const { handler } = await import('./tools/get-repo-outline.js');
|
|
208
|
+
const limitStr = query.get('limit');
|
|
209
|
+
const limit = limitStr ? parseInt(limitStr, 10) : undefined;
|
|
210
|
+
const result = handler({ repoId: outlineParams['id'], limit });
|
|
211
|
+
const text = result.content[0];
|
|
212
|
+
if (text && text.type === 'text') {
|
|
213
|
+
if (result.isError) {
|
|
214
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
218
|
+
}
|
|
219
|
+
res.end(text.text);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
sendJson(res, 500, { error: 'Unexpected tool response' });
|
|
223
|
+
}
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// GET /api/repos/:id/search
|
|
227
|
+
const searchParams = matchRoute(url, '/api/repos/:id/search');
|
|
228
|
+
if (searchParams !== null) {
|
|
229
|
+
const q = query.get('query');
|
|
230
|
+
if (!q) {
|
|
231
|
+
sendJson(res, 400, { error: 'Missing required query parameter: query' });
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const { handler } = await import('./tools/search-symbols.js');
|
|
235
|
+
const limitStr = query.get('limit');
|
|
236
|
+
const result = await handler({
|
|
237
|
+
repoId: searchParams['id'],
|
|
238
|
+
query: q,
|
|
239
|
+
kind: query.get('kind') ?? undefined,
|
|
240
|
+
filePath: query.get('filePath') ?? undefined,
|
|
241
|
+
limit: limitStr ? parseInt(limitStr, 10) : undefined,
|
|
242
|
+
mode: query.get('mode') ?? undefined,
|
|
243
|
+
});
|
|
244
|
+
const text = result.content[0];
|
|
245
|
+
if (text && text.type === 'text') {
|
|
246
|
+
if (result.isError) {
|
|
247
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
251
|
+
}
|
|
252
|
+
res.end(text.text);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
sendJson(res, 500, { error: 'Unexpected tool response' });
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
// GET /api/symbols/:id
|
|
260
|
+
const symbolParams = matchRoute(url, '/api/symbols/:id');
|
|
261
|
+
if (symbolParams !== null) {
|
|
262
|
+
const repoId = query.get('repoId');
|
|
263
|
+
if (!repoId) {
|
|
264
|
+
sendJson(res, 400, { error: 'Missing required query parameter: repoId' });
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const { handler } = await import('./tools/get-symbol-source.js');
|
|
268
|
+
const result = handler({ repoId, symbolId: symbolParams['id'] });
|
|
269
|
+
const text = result.content[0];
|
|
270
|
+
if (text && text.type === 'text') {
|
|
271
|
+
if (result.isError) {
|
|
272
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
276
|
+
}
|
|
277
|
+
res.end(text.text);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
sendJson(res, 500, { error: 'Unexpected tool response' });
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
// GET /api/repos/:id/file-outline
|
|
285
|
+
const fileOutlineParams = matchRoute(url, '/api/repos/:id/file-outline');
|
|
286
|
+
if (fileOutlineParams !== null) {
|
|
287
|
+
const filePath = query.get('filePath');
|
|
288
|
+
if (!filePath) {
|
|
289
|
+
sendJson(res, 400, { error: 'Missing required query parameter: filePath' });
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const { handler } = await import('./tools/get-file-outline.js');
|
|
293
|
+
const result = handler({ repoId: fileOutlineParams['id'], filePath });
|
|
294
|
+
const text = result.content[0];
|
|
295
|
+
if (text && text.type === 'text') {
|
|
296
|
+
if (result.isError) {
|
|
297
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
301
|
+
}
|
|
302
|
+
res.end(text.text);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
sendJson(res, 500, { error: 'Unexpected tool response' });
|
|
306
|
+
}
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
// GET /api/repos/:id/graph
|
|
310
|
+
const graphParams = matchRoute(url, '/api/repos/:id/graph');
|
|
311
|
+
if (graphParams !== null) {
|
|
312
|
+
const { handler } = await import('./tools/get-graph.js');
|
|
313
|
+
const depthStr = query.get('depth');
|
|
314
|
+
const limitStr2 = query.get('limit');
|
|
315
|
+
const result = handler({
|
|
316
|
+
repoId: graphParams['id'],
|
|
317
|
+
filePath: query.get('filePath') ?? undefined,
|
|
318
|
+
depth: depthStr ? parseInt(depthStr, 10) : undefined,
|
|
319
|
+
limit: limitStr2 ? parseInt(limitStr2, 10) : undefined,
|
|
320
|
+
});
|
|
321
|
+
const text = result.content[0];
|
|
322
|
+
if (text && text.type === 'text') {
|
|
323
|
+
if (result.isError) {
|
|
324
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
328
|
+
}
|
|
329
|
+
res.end(text.text);
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
sendJson(res, 500, { error: 'Unexpected tool response' });
|
|
333
|
+
}
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
// GET /api/repos/:id/blast-radius?symbolId=...&depth=...
|
|
337
|
+
const blastRadiusParams = matchRoute(url, '/api/repos/:id/blast-radius');
|
|
338
|
+
if (blastRadiusParams !== null) {
|
|
339
|
+
const symbolId = query.get('symbolId');
|
|
340
|
+
if (!symbolId) {
|
|
341
|
+
sendJson(res, 400, { error: 'Missing required query parameter: symbolId' });
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const depthStr = query.get('depth');
|
|
345
|
+
const depth = depthStr ? parseInt(depthStr, 10) : undefined;
|
|
346
|
+
const t0 = Date.now();
|
|
347
|
+
try {
|
|
348
|
+
const { openDatabase } = await import('../core/db/schema.js');
|
|
349
|
+
const { getBlastRadiusWithDepths } = await import('../graph/graph-traversal.js');
|
|
350
|
+
const db = openDatabase(blastRadiusParams['id']);
|
|
351
|
+
const result = getBlastRadiusWithDepths(symbolId, blastRadiusParams['id'], db, depth);
|
|
352
|
+
db.close();
|
|
353
|
+
if (!result) {
|
|
354
|
+
sendJson(res, 404, { error: `Symbol not found: ${symbolId}` });
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const payload = JSON.stringify({ ...result, _meta: { timingMs: Date.now() - t0 } });
|
|
358
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
359
|
+
res.end(payload);
|
|
360
|
+
}
|
|
361
|
+
catch (err) {
|
|
362
|
+
sendJson(res, 500, { error: String(err) });
|
|
363
|
+
}
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
// GET /api/repos/:id/importers
|
|
367
|
+
const importersParams = matchRoute(url, '/api/repos/:id/importers');
|
|
368
|
+
if (importersParams !== null) {
|
|
369
|
+
const filePath = query.get('filePath');
|
|
370
|
+
if (!filePath) {
|
|
371
|
+
sendJson(res, 400, { error: 'Missing required query parameter: filePath' });
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const { handler } = await import('./tools/find-importers.js');
|
|
375
|
+
const result = handler({ repoId: importersParams['id'], filePath });
|
|
376
|
+
const text = result.content[0];
|
|
377
|
+
if (text && text.type === 'text') {
|
|
378
|
+
if (result.isError) {
|
|
379
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
383
|
+
}
|
|
384
|
+
res.end(text.text);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
sendJson(res, 500, { error: 'Unexpected tool response' });
|
|
388
|
+
}
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
sendJson(res, 404, { error: 'API route not found' });
|
|
392
|
+
}
|
|
393
|
+
// ─── Static file serving ──────────────────────────────────────────────────────
|
|
394
|
+
const MIME_TYPES = {
|
|
395
|
+
'.html': 'text/html; charset=utf-8',
|
|
396
|
+
'.js': 'application/javascript',
|
|
397
|
+
'.mjs': 'application/javascript',
|
|
398
|
+
'.css': 'text/css',
|
|
399
|
+
'.json': 'application/json',
|
|
400
|
+
'.svg': 'image/svg+xml',
|
|
401
|
+
'.png': 'image/png',
|
|
402
|
+
'.ico': 'image/x-icon',
|
|
403
|
+
'.woff': 'font/woff',
|
|
404
|
+
'.woff2': 'font/woff2',
|
|
405
|
+
};
|
|
406
|
+
/** Resolve path to the built UI directory. */
|
|
407
|
+
function getUiDir() {
|
|
408
|
+
// In production: dist/server/http-server.js → dist/server/ → dist/ → project root
|
|
409
|
+
// In source/test: src/server/http-server.ts → src/server/ → src/ → project root
|
|
410
|
+
// Either way, the compiled UI lives at <project root>/dist/ui/
|
|
411
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
412
|
+
const projectRoot = resolvePath(__filename, '..', '..', '..');
|
|
413
|
+
return join(projectRoot, 'dist', 'ui');
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Serve a static file from dist/ui/. Returns true if handled.
|
|
417
|
+
* For SPA: unknown paths serve index.html.
|
|
418
|
+
* Returns false immediately when serveUi is false.
|
|
419
|
+
*/
|
|
420
|
+
function serveStatic(_req, res, url, serveUi) {
|
|
421
|
+
if (!serveUi)
|
|
422
|
+
return false;
|
|
423
|
+
const uiDir = getUiDir();
|
|
424
|
+
if (!existsSync(uiDir))
|
|
425
|
+
return false; // UI not built — skip
|
|
426
|
+
// Strip query string for file lookup
|
|
427
|
+
const pathname = url.split('?')[0];
|
|
428
|
+
const safePath = resolvePath(uiDir, '.' + pathname);
|
|
429
|
+
// Path traversal guard
|
|
430
|
+
if (!safePath.startsWith(uiDir)) {
|
|
431
|
+
sendJson(res, 403, { error: 'Forbidden' });
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
434
|
+
let filePath = safePath;
|
|
435
|
+
let stat;
|
|
436
|
+
try {
|
|
437
|
+
stat = statSync(filePath);
|
|
438
|
+
if (stat.isDirectory()) {
|
|
439
|
+
filePath = join(filePath, 'index.html');
|
|
440
|
+
stat = statSync(filePath);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
// File not found — SPA fallback to index.html
|
|
445
|
+
filePath = join(uiDir, 'index.html');
|
|
446
|
+
try {
|
|
447
|
+
stat = statSync(filePath);
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
return false; // No index.html either
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const ext = extname(filePath);
|
|
454
|
+
const contentType = MIME_TYPES[ext] ?? 'application/octet-stream';
|
|
455
|
+
res.writeHead(200, {
|
|
456
|
+
'Content-Type': contentType,
|
|
457
|
+
'Content-Length': stat.size,
|
|
458
|
+
});
|
|
459
|
+
createReadStream(filePath).pipe(res);
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
// ─── HTTP server ──────────────────────────────────────────────────────────────
|
|
463
|
+
export function startHttpServer(options) {
|
|
464
|
+
const startTime = Date.now();
|
|
465
|
+
// Create a single limiter instance shared across all requests
|
|
466
|
+
const rl = options.rateLimit;
|
|
467
|
+
const limiter = (rl?.enabled ?? false)
|
|
468
|
+
? new TokenBucketLimiter({
|
|
469
|
+
maxTokens: rl.maxTokens,
|
|
470
|
+
refillRate: rl.refillRate,
|
|
471
|
+
})
|
|
472
|
+
: null;
|
|
473
|
+
return new Promise((resolve, reject) => {
|
|
474
|
+
const server = createServer(async (req, res) => {
|
|
475
|
+
const url = req.url ?? '/';
|
|
476
|
+
const method = req.method ?? 'GET';
|
|
477
|
+
const clientIp = req.socket.remoteAddress ?? 'unknown';
|
|
478
|
+
logger.debug(`${method} ${url} from ${clientIp}`);
|
|
479
|
+
setCorsHeaders(req, res, options.corsOrigins);
|
|
480
|
+
// ── CORS preflight ──────────────────────────────────────────────────────
|
|
481
|
+
if (method === 'OPTIONS') {
|
|
482
|
+
res.writeHead(204);
|
|
483
|
+
res.end();
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
// ── Health check ────────────────────────────────────────────────────────
|
|
487
|
+
if (url === '/health' && method === 'GET') {
|
|
488
|
+
const body = JSON.stringify({
|
|
489
|
+
status: 'ok',
|
|
490
|
+
version: VERSION,
|
|
491
|
+
repoCount: getRepoCount(),
|
|
492
|
+
uptime: (Date.now() - startTime) / 1000,
|
|
493
|
+
});
|
|
494
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
495
|
+
res.end(body);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
// ── MCP SSE endpoint (stateful sessions, for agent connections) ─────────
|
|
499
|
+
if ((url === '/mcp/sse' || url.startsWith('/mcp/sse?')) && options.sseTransport) {
|
|
500
|
+
// ── Authentication ─────────────────────────────────────────────────
|
|
501
|
+
let authResult = null;
|
|
502
|
+
if (options.apiKeyAuth?.enabled) {
|
|
503
|
+
const apiKey = extractApiKeyFromHeaders(req.headers);
|
|
504
|
+
if (apiKey === null) {
|
|
505
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
506
|
+
res.end(JSON.stringify({ error: 'Unauthorized: API key required (Authorization: Bearer or X-API-Key)' }));
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
if (!validateFormat(apiKey)) {
|
|
510
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
511
|
+
res.end(JSON.stringify({ error: 'Unauthorized: invalid API key format' }));
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
authResult = await options.apiKeyAuth.validator.validate(apiKey);
|
|
515
|
+
if (!authResult.valid) {
|
|
516
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
517
|
+
res.end(JSON.stringify({ error: 'Unauthorized: API key not found or revoked' }));
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
else if (!checkAuth(req, options.auth)) {
|
|
522
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
523
|
+
res.end(JSON.stringify({ error: 'Unauthorized' }));
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
// ── POST-specific: body reading, permissions, rate limiting ────────
|
|
527
|
+
let sseParsedBody;
|
|
528
|
+
if (method === 'POST') {
|
|
529
|
+
const contentLength = parseInt(req.headers['content-length'] ?? '0', 10);
|
|
530
|
+
if (!isNaN(contentLength) && contentLength > MAX_BODY_BYTES) {
|
|
531
|
+
res.writeHead(413, { 'Content-Type': 'application/json' });
|
|
532
|
+
res.end(JSON.stringify({ error: 'Request body too large' }));
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
try {
|
|
536
|
+
const raw = await readBody(req, MAX_BODY_BYTES);
|
|
537
|
+
sseParsedBody = JSON.parse(raw.toString('utf8'));
|
|
538
|
+
}
|
|
539
|
+
catch (err) {
|
|
540
|
+
if (!res.headersSent) {
|
|
541
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
542
|
+
res.end(JSON.stringify({ error: `Bad request: ${err}` }));
|
|
543
|
+
}
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const toolName = extractToolName(sseParsedBody);
|
|
547
|
+
if (authResult !== null && toolName !== undefined) {
|
|
548
|
+
const required = getRequiredPermission(toolName);
|
|
549
|
+
if (!hasPermission(authResult, required)) {
|
|
550
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
551
|
+
res.end(JSON.stringify({
|
|
552
|
+
error: `Forbidden: '${required}' permission required for ${toolName}`,
|
|
553
|
+
}));
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (limiter !== null) {
|
|
558
|
+
const rateLimitKey = authResult?.tenantId ? `tenant:${authResult.tenantId}` : clientIp;
|
|
559
|
+
const cost = getToolCost(toolName, rl.perToolLimits);
|
|
560
|
+
const rlResult = limiter.tryConsume(rateLimitKey, cost);
|
|
561
|
+
if (!rlResult.allowed) {
|
|
562
|
+
const retryAfterSec = Math.ceil(rlResult.retryAfterMs / 1000);
|
|
563
|
+
res.writeHead(429, {
|
|
564
|
+
'Content-Type': 'application/json',
|
|
565
|
+
'Retry-After': String(retryAfterSec),
|
|
566
|
+
'X-RateLimit-Limit': String(rl.maxTokens),
|
|
567
|
+
'X-RateLimit-Remaining': String(rlResult.remainingTokens),
|
|
568
|
+
});
|
|
569
|
+
res.end(JSON.stringify({ error: 'Too Many Requests', retryAfterMs: rlResult.retryAfterMs }));
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// ── Keepalive pings for GET (SSE) connections ──────────────────────
|
|
575
|
+
// Writes a comment line every 30 s to prevent proxy/LB connection timeouts.
|
|
576
|
+
if (method === 'GET') {
|
|
577
|
+
const keepaliveTimer = setInterval(() => {
|
|
578
|
+
if (!res.writableEnded) {
|
|
579
|
+
res.write(': ping\n\n');
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
clearInterval(keepaliveTimer);
|
|
583
|
+
}
|
|
584
|
+
}, SSE_KEEPALIVE_MS);
|
|
585
|
+
res.on('close', () => clearInterval(keepaliveTimer));
|
|
586
|
+
}
|
|
587
|
+
try {
|
|
588
|
+
await options.sseTransport.handleRequest(req, res, sseParsedBody);
|
|
589
|
+
}
|
|
590
|
+
catch (err) {
|
|
591
|
+
logger.warn(`MCP SSE request error: ${err}`);
|
|
592
|
+
if (!res.headersSent) {
|
|
593
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
594
|
+
res.end(JSON.stringify({
|
|
595
|
+
jsonrpc: '2.0',
|
|
596
|
+
error: { code: -32603, message: 'Internal server error' },
|
|
597
|
+
id: null,
|
|
598
|
+
}));
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
// ── MCP endpoint (Streamable HTTP, stateless per-request) ───────────────
|
|
604
|
+
if (url === '/mcp') {
|
|
605
|
+
if (method !== 'POST') {
|
|
606
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
607
|
+
res.end(JSON.stringify({ error: 'Method not allowed. Use POST for MCP requests.' }));
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
// ── Authentication ───────────────────────────────────────────────────
|
|
611
|
+
let authResult = null;
|
|
612
|
+
if (options.apiKeyAuth?.enabled) {
|
|
613
|
+
// API key authentication (Task 97) — supersedes simple bearer token
|
|
614
|
+
const apiKey = extractApiKeyFromHeaders(req.headers);
|
|
615
|
+
if (apiKey === null) {
|
|
616
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
617
|
+
res.end(JSON.stringify({ error: 'Unauthorized: API key required (Authorization: Bearer or X-API-Key)' }));
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
// Fast format check before any DB access
|
|
621
|
+
if (!validateFormat(apiKey)) {
|
|
622
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
623
|
+
res.end(JSON.stringify({ error: 'Unauthorized: invalid API key format' }));
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
// Full DB validation
|
|
627
|
+
authResult = await options.apiKeyAuth.validator.validate(apiKey);
|
|
628
|
+
if (!authResult.valid) {
|
|
629
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
630
|
+
res.end(JSON.stringify({ error: 'Unauthorized: API key not found or revoked' }));
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
// Legacy bearer token authentication
|
|
636
|
+
if (!checkAuth(req, options.auth)) {
|
|
637
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
638
|
+
res.end(JSON.stringify({ error: 'Unauthorized' }));
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
// Reject oversized requests before reading body
|
|
643
|
+
const contentLength = parseInt(req.headers['content-length'] ?? '0', 10);
|
|
644
|
+
if (!isNaN(contentLength) && contentLength > MAX_BODY_BYTES) {
|
|
645
|
+
res.writeHead(413, { 'Content-Type': 'application/json' });
|
|
646
|
+
res.end(JSON.stringify({ error: 'Request body too large' }));
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
let body;
|
|
650
|
+
try {
|
|
651
|
+
const raw = await readBody(req, MAX_BODY_BYTES);
|
|
652
|
+
body = JSON.parse(raw.toString('utf8'));
|
|
653
|
+
}
|
|
654
|
+
catch (err) {
|
|
655
|
+
if (!res.headersSent) {
|
|
656
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
657
|
+
res.end(JSON.stringify({ error: `Bad request: ${err}` }));
|
|
658
|
+
}
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
// Extract tool name once — used by both permission check and rate limiting
|
|
662
|
+
const toolName = extractToolName(body);
|
|
663
|
+
// ── Permission check (API key auth only) ─────────────────────────────
|
|
664
|
+
if (authResult !== null && toolName !== undefined) {
|
|
665
|
+
const required = getRequiredPermission(toolName);
|
|
666
|
+
if (!hasPermission(authResult, required)) {
|
|
667
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
668
|
+
res.end(JSON.stringify({
|
|
669
|
+
error: `Forbidden: '${required}' permission required for ${toolName}`,
|
|
670
|
+
}));
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
// ── Rate limiting ────────────────────────────────────────────────────
|
|
675
|
+
if (limiter !== null) {
|
|
676
|
+
// Prefer tenant ID as rate-limit key when authenticated; fall back to IP
|
|
677
|
+
const rateLimitKey = authResult?.tenantId ? `tenant:${authResult.tenantId}` : clientIp;
|
|
678
|
+
const cost = getToolCost(toolName, rl.perToolLimits);
|
|
679
|
+
const result = limiter.tryConsume(rateLimitKey, cost);
|
|
680
|
+
if (!result.allowed) {
|
|
681
|
+
const retryAfterSec = Math.ceil(result.retryAfterMs / 1000);
|
|
682
|
+
res.writeHead(429, {
|
|
683
|
+
'Content-Type': 'application/json',
|
|
684
|
+
'Retry-After': String(retryAfterSec),
|
|
685
|
+
'X-RateLimit-Limit': String(rl.maxTokens),
|
|
686
|
+
'X-RateLimit-Remaining': String(result.remainingTokens),
|
|
687
|
+
});
|
|
688
|
+
res.end(JSON.stringify({
|
|
689
|
+
error: 'Too Many Requests',
|
|
690
|
+
retryAfterMs: result.retryAfterMs,
|
|
691
|
+
}));
|
|
692
|
+
logger.debug(`Rate limit exceeded for ${rateLimitKey} (tool: ${toolName ?? 'unknown'})`);
|
|
693
|
+
options.adminApi?.api.logRequest({
|
|
694
|
+
timestamp: new Date().toISOString(),
|
|
695
|
+
tenantId: authResult?.tenantId ?? null,
|
|
696
|
+
tool: toolName ?? '__unknown__',
|
|
697
|
+
durationMs: 0,
|
|
698
|
+
status: 'rate_limited',
|
|
699
|
+
errorMessage: null,
|
|
700
|
+
});
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
// Each request gets a fresh McpServer + transport (stateless mode)
|
|
705
|
+
const mcpServer = options.serverFactory();
|
|
706
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
707
|
+
const mcpStart = Date.now();
|
|
708
|
+
try {
|
|
709
|
+
await mcpServer.connect(transport);
|
|
710
|
+
await transport.handleRequest(req, res, body);
|
|
711
|
+
options.adminApi?.api.logRequest({
|
|
712
|
+
timestamp: new Date().toISOString(),
|
|
713
|
+
tenantId: authResult?.tenantId ?? null,
|
|
714
|
+
tool: toolName ?? '__unknown__',
|
|
715
|
+
durationMs: Date.now() - mcpStart,
|
|
716
|
+
status: 'success',
|
|
717
|
+
errorMessage: null,
|
|
718
|
+
});
|
|
719
|
+
res.on('close', () => {
|
|
720
|
+
transport.close().catch(() => { });
|
|
721
|
+
mcpServer.close().catch(() => { });
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
catch (err) {
|
|
725
|
+
logger.warn(`MCP request error: ${err}`);
|
|
726
|
+
options.adminApi?.api.logRequest({
|
|
727
|
+
timestamp: new Date().toISOString(),
|
|
728
|
+
tenantId: authResult?.tenantId ?? null,
|
|
729
|
+
tool: toolName ?? '__unknown__',
|
|
730
|
+
durationMs: Date.now() - mcpStart,
|
|
731
|
+
status: 'error',
|
|
732
|
+
errorMessage: String(err),
|
|
733
|
+
});
|
|
734
|
+
if (!res.headersSent) {
|
|
735
|
+
if (err instanceof QuotaExceededError) {
|
|
736
|
+
res.writeHead(507, { 'Content-Type': 'application/json' });
|
|
737
|
+
res.end(JSON.stringify({
|
|
738
|
+
error: 'Insufficient Storage',
|
|
739
|
+
tenantId: err.tenantId,
|
|
740
|
+
quotaBytes: err.quotaBytes,
|
|
741
|
+
usedBytes: err.usedBytes,
|
|
742
|
+
requestedBytes: err.requestedBytes,
|
|
743
|
+
}));
|
|
744
|
+
}
|
|
745
|
+
else {
|
|
746
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
747
|
+
res.end(JSON.stringify({
|
|
748
|
+
jsonrpc: '2.0',
|
|
749
|
+
error: { code: -32603, message: 'Internal server error' },
|
|
750
|
+
id: null,
|
|
751
|
+
}));
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
// ── Admin API ────────────────────────────────────────────────────────────
|
|
758
|
+
if (url.startsWith('/admin') && options.adminApi) {
|
|
759
|
+
// Admin routes require API key authentication
|
|
760
|
+
if (!options.apiKeyAuth?.enabled) {
|
|
761
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
762
|
+
res.end(JSON.stringify({ error: 'Forbidden: admin API requires API key authentication' }));
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
const adminApiKey = extractApiKeyFromHeaders(req.headers);
|
|
766
|
+
if (adminApiKey === null || !validateFormat(adminApiKey)) {
|
|
767
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
768
|
+
res.end(JSON.stringify({ error: 'Unauthorized: valid API key required' }));
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const adminAuth = await options.apiKeyAuth.validator.validate(adminApiKey);
|
|
772
|
+
if (!adminAuth.valid) {
|
|
773
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
774
|
+
res.end(JSON.stringify({ error: 'Unauthorized: API key not found or revoked' }));
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
if (!hasPermission(adminAuth, 'admin')) {
|
|
778
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
779
|
+
res.end(JSON.stringify({ error: 'Forbidden: admin permission required' }));
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
await options.adminApi.api.handle(req, res, adminAuth);
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
// ── REST API for web UI ──────────────────────────────────────────────────
|
|
786
|
+
if (url.startsWith('/api/')) {
|
|
787
|
+
// Apply the same API key auth as the MCP endpoint when apiKeyAuth is enabled
|
|
788
|
+
if (options.apiKeyAuth?.enabled) {
|
|
789
|
+
const apiKey = extractApiKeyFromHeaders(req.headers);
|
|
790
|
+
if (apiKey === null || !validateFormat(apiKey)) {
|
|
791
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
792
|
+
res.end(JSON.stringify({ error: 'Unauthorized: API key required (Authorization: Bearer pctx_...)' }));
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
const auth = await options.apiKeyAuth.validator.validate(apiKey);
|
|
796
|
+
if (!auth.valid) {
|
|
797
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
798
|
+
res.end(JSON.stringify({ error: 'Forbidden: invalid or revoked API key' }));
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
await handleRestApi(req, res, url, method);
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
// ── Static UI assets ─────────────────────────────────────────────────────
|
|
806
|
+
{
|
|
807
|
+
const served = serveStatic(req, res, url, options.serveUi ?? true);
|
|
808
|
+
if (served)
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
// ── 404 ─────────────────────────────────────────────────────────────────
|
|
812
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
813
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
814
|
+
});
|
|
815
|
+
server.listen(options.port, options.host, () => {
|
|
816
|
+
const addr = server.address();
|
|
817
|
+
logger.info(`PureContext MCP listening on http://${options.host}:${addr.port}`);
|
|
818
|
+
resolve(server);
|
|
819
|
+
});
|
|
820
|
+
server.on('error', reject);
|
|
821
|
+
}); // end Promise
|
|
822
|
+
}
|
|
823
|
+
//# sourceMappingURL=http-server.js.map
|