@cyanheads/mcp-ts-core 0.1.0-beta.12
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/CLAUDE.md +583 -0
- package/LICENSE +201 -0
- package/README.md +287 -0
- package/biome.json +103 -0
- package/dist/app.d.ts +82 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +222 -0
- package/dist/app.js.map +1 -0
- package/dist/cli/init.d.ts +8 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +161 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/config/index.d.ts +349 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +464 -0
- package/dist/config/index.js.map +1 -0
- package/dist/context.d.ts +119 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +144 -0
- package/dist/context.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server/prompts/prompt-registration.d.ts +33 -0
- package/dist/mcp-server/prompts/prompt-registration.d.ts.map +1 -0
- package/dist/mcp-server/prompts/prompt-registration.js +91 -0
- package/dist/mcp-server/prompts/prompt-registration.js.map +1 -0
- package/dist/mcp-server/prompts/utils/newPromptDefinition.d.ts +49 -0
- package/dist/mcp-server/prompts/utils/newPromptDefinition.d.ts.map +1 -0
- package/dist/mcp-server/prompts/utils/newPromptDefinition.js +39 -0
- package/dist/mcp-server/prompts/utils/newPromptDefinition.js.map +1 -0
- package/dist/mcp-server/prompts/utils/promptDefinition.d.ts +37 -0
- package/dist/mcp-server/prompts/utils/promptDefinition.d.ts.map +1 -0
- package/dist/mcp-server/prompts/utils/promptDefinition.js +2 -0
- package/dist/mcp-server/prompts/utils/promptDefinition.js.map +1 -0
- package/dist/mcp-server/resources/resource-registration.d.ts +27 -0
- package/dist/mcp-server/resources/resource-registration.d.ts.map +1 -0
- package/dist/mcp-server/resources/resource-registration.js +85 -0
- package/dist/mcp-server/resources/resource-registration.js.map +1 -0
- package/dist/mcp-server/resources/utils/newResourceDefinition.d.ts +84 -0
- package/dist/mcp-server/resources/utils/newResourceDefinition.d.ts.map +1 -0
- package/dist/mcp-server/resources/utils/newResourceDefinition.js +40 -0
- package/dist/mcp-server/resources/utils/newResourceDefinition.js.map +1 -0
- package/dist/mcp-server/resources/utils/newResourceHandlerFactory.d.ts +32 -0
- package/dist/mcp-server/resources/utils/newResourceHandlerFactory.d.ts.map +1 -0
- package/dist/mcp-server/resources/utils/newResourceHandlerFactory.js +103 -0
- package/dist/mcp-server/resources/utils/newResourceHandlerFactory.js.map +1 -0
- package/dist/mcp-server/resources/utils/resourceDefinition.d.ts +94 -0
- package/dist/mcp-server/resources/utils/resourceDefinition.d.ts.map +1 -0
- package/dist/mcp-server/resources/utils/resourceDefinition.js +2 -0
- package/dist/mcp-server/resources/utils/resourceDefinition.js.map +1 -0
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts +14 -0
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -0
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +111 -0
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -0
- package/dist/mcp-server/roots/roots-registration.d.ts +22 -0
- package/dist/mcp-server/roots/roots-registration.d.ts.map +1 -0
- package/dist/mcp-server/roots/roots-registration.js +25 -0
- package/dist/mcp-server/roots/roots-registration.js.map +1 -0
- package/dist/mcp-server/server.d.ts +34 -0
- package/dist/mcp-server/server.d.ts.map +1 -0
- package/dist/mcp-server/server.js +62 -0
- package/dist/mcp-server/server.js.map +1 -0
- package/dist/mcp-server/tasks/core/sessionAwareTaskStore.d.ts +42 -0
- package/dist/mcp-server/tasks/core/sessionAwareTaskStore.d.ts.map +1 -0
- package/dist/mcp-server/tasks/core/sessionAwareTaskStore.js +70 -0
- package/dist/mcp-server/tasks/core/sessionAwareTaskStore.js.map +1 -0
- package/dist/mcp-server/tasks/core/storageBackedTaskStore.d.ts +109 -0
- package/dist/mcp-server/tasks/core/storageBackedTaskStore.d.ts.map +1 -0
- package/dist/mcp-server/tasks/core/storageBackedTaskStore.js +209 -0
- package/dist/mcp-server/tasks/core/storageBackedTaskStore.js.map +1 -0
- package/dist/mcp-server/tasks/core/taskManager.d.ts +103 -0
- package/dist/mcp-server/tasks/core/taskManager.d.ts.map +1 -0
- package/dist/mcp-server/tasks/core/taskManager.js +144 -0
- package/dist/mcp-server/tasks/core/taskManager.js.map +1 -0
- package/dist/mcp-server/tasks/core/taskTypes.d.ts +11 -0
- package/dist/mcp-server/tasks/core/taskTypes.d.ts.map +1 -0
- package/dist/mcp-server/tasks/core/taskTypes.js +13 -0
- package/dist/mcp-server/tasks/core/taskTypes.js.map +1 -0
- package/dist/mcp-server/tasks/utils/taskToolDefinition.d.ts +108 -0
- package/dist/mcp-server/tasks/utils/taskToolDefinition.d.ts.map +1 -0
- package/dist/mcp-server/tasks/utils/taskToolDefinition.js +14 -0
- package/dist/mcp-server/tasks/utils/taskToolDefinition.js.map +1 -0
- package/dist/mcp-server/tools/tool-registration.d.ts +49 -0
- package/dist/mcp-server/tools/tool-registration.d.ts.map +1 -0
- package/dist/mcp-server/tools/tool-registration.js +269 -0
- package/dist/mcp-server/tools/tool-registration.js.map +1 -0
- package/dist/mcp-server/tools/utils/newToolDefinition.d.ts +73 -0
- package/dist/mcp-server/tools/utils/newToolDefinition.d.ts.map +1 -0
- package/dist/mcp-server/tools/utils/newToolDefinition.js +45 -0
- package/dist/mcp-server/tools/utils/newToolDefinition.js.map +1 -0
- package/dist/mcp-server/tools/utils/newToolHandlerFactory.d.ts +33 -0
- package/dist/mcp-server/tools/utils/newToolHandlerFactory.d.ts.map +1 -0
- package/dist/mcp-server/tools/utils/newToolHandlerFactory.js +107 -0
- package/dist/mcp-server/tools/utils/newToolHandlerFactory.js.map +1 -0
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts +118 -0
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts.map +1 -0
- package/dist/mcp-server/tools/utils/toolDefinition.js +2 -0
- package/dist/mcp-server/tools/utils/toolDefinition.js.map +1 -0
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +34 -0
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -0
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js +68 -0
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -0
- package/dist/mcp-server/transports/ITransport.d.ts +15 -0
- package/dist/mcp-server/transports/ITransport.d.ts.map +1 -0
- package/dist/mcp-server/transports/ITransport.js +2 -0
- package/dist/mcp-server/transports/ITransport.js.map +1 -0
- package/dist/mcp-server/transports/auth/authFactory.d.ts +11 -0
- package/dist/mcp-server/transports/auth/authFactory.d.ts.map +1 -0
- package/dist/mcp-server/transports/auth/authFactory.js +43 -0
- package/dist/mcp-server/transports/auth/authFactory.js.map +1 -0
- package/dist/mcp-server/transports/auth/authMiddleware.d.ts +24 -0
- package/dist/mcp-server/transports/auth/authMiddleware.d.ts.map +1 -0
- package/dist/mcp-server/transports/auth/authMiddleware.js +69 -0
- package/dist/mcp-server/transports/auth/authMiddleware.js.map +1 -0
- package/dist/mcp-server/transports/auth/lib/authContext.d.ts +34 -0
- package/dist/mcp-server/transports/auth/lib/authContext.d.ts.map +1 -0
- package/dist/mcp-server/transports/auth/lib/authContext.js +25 -0
- package/dist/mcp-server/transports/auth/lib/authContext.js.map +1 -0
- package/dist/mcp-server/transports/auth/lib/authTypes.d.ts +19 -0
- package/dist/mcp-server/transports/auth/lib/authTypes.d.ts.map +1 -0
- package/dist/mcp-server/transports/auth/lib/authTypes.js +2 -0
- package/dist/mcp-server/transports/auth/lib/authTypes.js.map +1 -0
- package/dist/mcp-server/transports/auth/lib/authUtils.d.ts +18 -0
- package/dist/mcp-server/transports/auth/lib/authUtils.d.ts.map +1 -0
- package/dist/mcp-server/transports/auth/lib/authUtils.js +64 -0
- package/dist/mcp-server/transports/auth/lib/authUtils.js.map +1 -0
- package/dist/mcp-server/transports/auth/lib/checkScopes.d.ts +25 -0
- package/dist/mcp-server/transports/auth/lib/checkScopes.d.ts.map +1 -0
- package/dist/mcp-server/transports/auth/lib/checkScopes.js +34 -0
- package/dist/mcp-server/transports/auth/lib/checkScopes.js.map +1 -0
- package/dist/mcp-server/transports/auth/lib/claimParser.d.ts +34 -0
- package/dist/mcp-server/transports/auth/lib/claimParser.d.ts.map +1 -0
- package/dist/mcp-server/transports/auth/lib/claimParser.js +58 -0
- package/dist/mcp-server/transports/auth/lib/claimParser.js.map +1 -0
- package/dist/mcp-server/transports/auth/lib/withAuth.d.ts +25 -0
- package/dist/mcp-server/transports/auth/lib/withAuth.d.ts.map +1 -0
- package/dist/mcp-server/transports/auth/lib/withAuth.js +30 -0
- package/dist/mcp-server/transports/auth/lib/withAuth.js.map +1 -0
- package/dist/mcp-server/transports/auth/strategies/authStrategy.d.ts +18 -0
- package/dist/mcp-server/transports/auth/strategies/authStrategy.d.ts.map +1 -0
- package/dist/mcp-server/transports/auth/strategies/authStrategy.js +2 -0
- package/dist/mcp-server/transports/auth/strategies/authStrategy.js.map +1 -0
- package/dist/mcp-server/transports/auth/strategies/jwtStrategy.d.ts +14 -0
- package/dist/mcp-server/transports/auth/strategies/jwtStrategy.d.ts.map +1 -0
- package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js +86 -0
- package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js.map +1 -0
- package/dist/mcp-server/transports/auth/strategies/oauthStrategy.d.ts +14 -0
- package/dist/mcp-server/transports/auth/strategies/oauthStrategy.d.ts.map +1 -0
- package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js +113 -0
- package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js.map +1 -0
- package/dist/mcp-server/transports/http/httpErrorHandler.d.ts +25 -0
- package/dist/mcp-server/transports/http/httpErrorHandler.d.ts.map +1 -0
- package/dist/mcp-server/transports/http/httpErrorHandler.js +112 -0
- package/dist/mcp-server/transports/http/httpErrorHandler.js.map +1 -0
- package/dist/mcp-server/transports/http/httpTransport.d.ts +47 -0
- package/dist/mcp-server/transports/http/httpTransport.d.ts.map +1 -0
- package/dist/mcp-server/transports/http/httpTransport.js +396 -0
- package/dist/mcp-server/transports/http/httpTransport.js.map +1 -0
- package/dist/mcp-server/transports/http/httpTypes.d.ts +17 -0
- package/dist/mcp-server/transports/http/httpTypes.d.ts.map +1 -0
- package/dist/mcp-server/transports/http/httpTypes.js +2 -0
- package/dist/mcp-server/transports/http/httpTypes.js.map +1 -0
- package/dist/mcp-server/transports/http/protectedResourceMetadata.d.ts +21 -0
- package/dist/mcp-server/transports/http/protectedResourceMetadata.d.ts.map +1 -0
- package/dist/mcp-server/transports/http/protectedResourceMetadata.js +44 -0
- package/dist/mcp-server/transports/http/protectedResourceMetadata.js.map +1 -0
- package/dist/mcp-server/transports/http/sessionIdUtils.d.ts +33 -0
- package/dist/mcp-server/transports/http/sessionIdUtils.d.ts.map +1 -0
- package/dist/mcp-server/transports/http/sessionIdUtils.js +54 -0
- package/dist/mcp-server/transports/http/sessionIdUtils.js.map +1 -0
- package/dist/mcp-server/transports/http/sessionStore.d.ts +87 -0
- package/dist/mcp-server/transports/http/sessionStore.d.ts.map +1 -0
- package/dist/mcp-server/transports/http/sessionStore.js +209 -0
- package/dist/mcp-server/transports/http/sessionStore.js.map +1 -0
- package/dist/mcp-server/transports/manager.d.ts +22 -0
- package/dist/mcp-server/transports/manager.d.ts.map +1 -0
- package/dist/mcp-server/transports/manager.js +62 -0
- package/dist/mcp-server/transports/manager.js.map +1 -0
- package/dist/mcp-server/transports/stdio/stdioTransport.d.ts +44 -0
- package/dist/mcp-server/transports/stdio/stdioTransport.d.ts.map +1 -0
- package/dist/mcp-server/transports/stdio/stdioTransport.js +63 -0
- package/dist/mcp-server/transports/stdio/stdioTransport.js.map +1 -0
- package/dist/services/graph/core/GraphService.d.ts +205 -0
- package/dist/services/graph/core/GraphService.d.ts.map +1 -0
- package/dist/services/graph/core/GraphService.js +231 -0
- package/dist/services/graph/core/GraphService.js.map +1 -0
- package/dist/services/graph/core/IGraphProvider.d.ts +295 -0
- package/dist/services/graph/core/IGraphProvider.d.ts.map +1 -0
- package/dist/services/graph/core/IGraphProvider.js +8 -0
- package/dist/services/graph/core/IGraphProvider.js.map +1 -0
- package/dist/services/graph/types.d.ts +107 -0
- package/dist/services/graph/types.d.ts.map +1 -0
- package/dist/services/graph/types.js +8 -0
- package/dist/services/graph/types.js.map +1 -0
- package/dist/services/llm/core/ILlmProvider.d.ts +86 -0
- package/dist/services/llm/core/ILlmProvider.d.ts.map +1 -0
- package/dist/services/llm/core/ILlmProvider.js +2 -0
- package/dist/services/llm/core/ILlmProvider.js.map +1 -0
- package/dist/services/llm/providers/openrouter.provider.d.ts +187 -0
- package/dist/services/llm/providers/openrouter.provider.d.ts.map +1 -0
- package/dist/services/llm/providers/openrouter.provider.js +302 -0
- package/dist/services/llm/providers/openrouter.provider.js.map +1 -0
- package/dist/services/llm/types.d.ts +16 -0
- package/dist/services/llm/types.d.ts.map +1 -0
- package/dist/services/llm/types.js +9 -0
- package/dist/services/llm/types.js.map +1 -0
- package/dist/services/speech/core/ISpeechProvider.d.ts +92 -0
- package/dist/services/speech/core/ISpeechProvider.d.ts.map +1 -0
- package/dist/services/speech/core/ISpeechProvider.js +34 -0
- package/dist/services/speech/core/ISpeechProvider.js.map +1 -0
- package/dist/services/speech/core/SpeechService.d.ts +87 -0
- package/dist/services/speech/core/SpeechService.d.ts.map +1 -0
- package/dist/services/speech/core/SpeechService.js +135 -0
- package/dist/services/speech/core/SpeechService.js.map +1 -0
- package/dist/services/speech/providers/elevenlabs.provider.d.ts +77 -0
- package/dist/services/speech/providers/elevenlabs.provider.d.ts.map +1 -0
- package/dist/services/speech/providers/elevenlabs.provider.js +199 -0
- package/dist/services/speech/providers/elevenlabs.provider.js.map +1 -0
- package/dist/services/speech/providers/whisper.provider.d.ts +94 -0
- package/dist/services/speech/providers/whisper.provider.d.ts.map +1 -0
- package/dist/services/speech/providers/whisper.provider.js +240 -0
- package/dist/services/speech/providers/whisper.provider.js.map +1 -0
- package/dist/services/speech/types.d.ts +173 -0
- package/dist/services/speech/types.d.ts.map +1 -0
- package/dist/services/speech/types.js +8 -0
- package/dist/services/speech/types.js.map +1 -0
- package/dist/storage/core/IStorageProvider.d.ts +159 -0
- package/dist/storage/core/IStorageProvider.d.ts.map +1 -0
- package/dist/storage/core/IStorageProvider.js +2 -0
- package/dist/storage/core/IStorageProvider.js.map +1 -0
- package/dist/storage/core/StorageService.d.ts +22 -0
- package/dist/storage/core/StorageService.d.ts.map +1 -0
- package/dist/storage/core/StorageService.js +151 -0
- package/dist/storage/core/StorageService.js.map +1 -0
- package/dist/storage/core/storageFactory.d.ts +66 -0
- package/dist/storage/core/storageFactory.d.ts.map +1 -0
- package/dist/storage/core/storageFactory.js +122 -0
- package/dist/storage/core/storageFactory.js.map +1 -0
- package/dist/storage/core/storageValidation.d.ts +77 -0
- package/dist/storage/core/storageValidation.d.ts.map +1 -0
- package/dist/storage/core/storageValidation.js +303 -0
- package/dist/storage/core/storageValidation.js.map +1 -0
- package/dist/storage/providers/cloudflare/d1Provider.d.ts +94 -0
- package/dist/storage/providers/cloudflare/d1Provider.d.ts.map +1 -0
- package/dist/storage/providers/cloudflare/d1Provider.js +347 -0
- package/dist/storage/providers/cloudflare/d1Provider.js.map +1 -0
- package/dist/storage/providers/cloudflare/kvProvider.d.ts +21 -0
- package/dist/storage/providers/cloudflare/kvProvider.d.ts.map +1 -0
- package/dist/storage/providers/cloudflare/kvProvider.js +183 -0
- package/dist/storage/providers/cloudflare/kvProvider.js.map +1 -0
- package/dist/storage/providers/cloudflare/r2Provider.d.ts +28 -0
- package/dist/storage/providers/cloudflare/r2Provider.d.ts.map +1 -0
- package/dist/storage/providers/cloudflare/r2Provider.js +222 -0
- package/dist/storage/providers/cloudflare/r2Provider.js.map +1 -0
- package/dist/storage/providers/fileSystem/fileSystemProvider.d.ts +20 -0
- package/dist/storage/providers/fileSystem/fileSystemProvider.d.ts.map +1 -0
- package/dist/storage/providers/fileSystem/fileSystemProvider.js +282 -0
- package/dist/storage/providers/fileSystem/fileSystemProvider.js.map +1 -0
- package/dist/storage/providers/inMemory/inMemoryProvider.d.ts +21 -0
- package/dist/storage/providers/inMemory/inMemoryProvider.d.ts.map +1 -0
- package/dist/storage/providers/inMemory/inMemoryProvider.js +139 -0
- package/dist/storage/providers/inMemory/inMemoryProvider.js.map +1 -0
- package/dist/storage/providers/supabase/supabase.types.d.ts +49 -0
- package/dist/storage/providers/supabase/supabase.types.d.ts.map +1 -0
- package/dist/storage/providers/supabase/supabase.types.js +8 -0
- package/dist/storage/providers/supabase/supabase.types.js.map +1 -0
- package/dist/storage/providers/supabase/supabaseProvider.d.ts +24 -0
- package/dist/storage/providers/supabase/supabaseProvider.d.ts.map +1 -0
- package/dist/storage/providers/supabase/supabaseProvider.js +209 -0
- package/dist/storage/providers/supabase/supabaseProvider.js.map +1 -0
- package/dist/testing/index.d.ts +53 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +132 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/types-global/errors.d.ts +83 -0
- package/dist/types-global/errors.d.ts.map +1 -0
- package/dist/types-global/errors.js +113 -0
- package/dist/types-global/errors.js.map +1 -0
- package/dist/utils/formatting/diffFormatter.d.ts +227 -0
- package/dist/utils/formatting/diffFormatter.d.ts.map +1 -0
- package/dist/utils/formatting/diffFormatter.js +369 -0
- package/dist/utils/formatting/diffFormatter.js.map +1 -0
- package/dist/utils/formatting/index.d.ts +9 -0
- package/dist/utils/formatting/index.d.ts.map +1 -0
- package/dist/utils/formatting/index.js +9 -0
- package/dist/utils/formatting/index.js.map +1 -0
- package/dist/utils/formatting/markdownBuilder.d.ts +543 -0
- package/dist/utils/formatting/markdownBuilder.d.ts.map +1 -0
- package/dist/utils/formatting/markdownBuilder.js +674 -0
- package/dist/utils/formatting/markdownBuilder.js.map +1 -0
- package/dist/utils/formatting/tableFormatter.d.ts +261 -0
- package/dist/utils/formatting/tableFormatter.d.ts.map +1 -0
- package/dist/utils/formatting/tableFormatter.js +456 -0
- package/dist/utils/formatting/tableFormatter.js.map +1 -0
- package/dist/utils/formatting/treeFormatter.d.ts +344 -0
- package/dist/utils/formatting/treeFormatter.d.ts.map +1 -0
- package/dist/utils/formatting/treeFormatter.js +400 -0
- package/dist/utils/formatting/treeFormatter.js.map +1 -0
- package/dist/utils/internal/encoding.d.ts +42 -0
- package/dist/utils/internal/encoding.d.ts.map +1 -0
- package/dist/utils/internal/encoding.js +87 -0
- package/dist/utils/internal/encoding.js.map +1 -0
- package/dist/utils/internal/error-handler/errorHandler.d.ts +140 -0
- package/dist/utils/internal/error-handler/errorHandler.d.ts.map +1 -0
- package/dist/utils/internal/error-handler/errorHandler.js +318 -0
- package/dist/utils/internal/error-handler/errorHandler.js.map +1 -0
- package/dist/utils/internal/error-handler/helpers.d.ts +98 -0
- package/dist/utils/internal/error-handler/helpers.d.ts.map +1 -0
- package/dist/utils/internal/error-handler/helpers.js +214 -0
- package/dist/utils/internal/error-handler/helpers.js.map +1 -0
- package/dist/utils/internal/error-handler/mappings.d.ts +85 -0
- package/dist/utils/internal/error-handler/mappings.d.ts.map +1 -0
- package/dist/utils/internal/error-handler/mappings.js +234 -0
- package/dist/utils/internal/error-handler/mappings.js.map +1 -0
- package/dist/utils/internal/error-handler/types.d.ts +160 -0
- package/dist/utils/internal/error-handler/types.d.ts.map +1 -0
- package/dist/utils/internal/error-handler/types.js +6 -0
- package/dist/utils/internal/error-handler/types.js.map +1 -0
- package/dist/utils/internal/health.d.ts +60 -0
- package/dist/utils/internal/health.d.ts.map +1 -0
- package/dist/utils/internal/health.js +46 -0
- package/dist/utils/internal/health.js.map +1 -0
- package/dist/utils/internal/logger.d.ts +300 -0
- package/dist/utils/internal/logger.d.ts.map +1 -0
- package/dist/utils/internal/logger.js +573 -0
- package/dist/utils/internal/logger.js.map +1 -0
- package/dist/utils/internal/performance.d.ts +78 -0
- package/dist/utils/internal/performance.d.ts.map +1 -0
- package/dist/utils/internal/performance.js +227 -0
- package/dist/utils/internal/performance.js.map +1 -0
- package/dist/utils/internal/requestContext.d.ts +200 -0
- package/dist/utils/internal/requestContext.d.ts.map +1 -0
- package/dist/utils/internal/requestContext.js +163 -0
- package/dist/utils/internal/requestContext.js.map +1 -0
- package/dist/utils/internal/runtime.d.ts +49 -0
- package/dist/utils/internal/runtime.d.ts.map +1 -0
- package/dist/utils/internal/runtime.js +90 -0
- package/dist/utils/internal/runtime.js.map +1 -0
- package/dist/utils/internal/startupBanner.d.ts +23 -0
- package/dist/utils/internal/startupBanner.d.ts.map +1 -0
- package/dist/utils/internal/startupBanner.js +34 -0
- package/dist/utils/internal/startupBanner.js.map +1 -0
- package/dist/utils/metrics/tokenCounter.d.ts +97 -0
- package/dist/utils/metrics/tokenCounter.d.ts.map +1 -0
- package/dist/utils/metrics/tokenCounter.js +162 -0
- package/dist/utils/metrics/tokenCounter.js.map +1 -0
- package/dist/utils/network/fetchWithTimeout.d.ts +91 -0
- package/dist/utils/network/fetchWithTimeout.d.ts.map +1 -0
- package/dist/utils/network/fetchWithTimeout.js +305 -0
- package/dist/utils/network/fetchWithTimeout.js.map +1 -0
- package/dist/utils/pagination/pagination.d.ts +157 -0
- package/dist/utils/pagination/pagination.d.ts.map +1 -0
- package/dist/utils/pagination/pagination.js +191 -0
- package/dist/utils/pagination/pagination.js.map +1 -0
- package/dist/utils/parsing/csvParser.d.ts +84 -0
- package/dist/utils/parsing/csvParser.d.ts.map +1 -0
- package/dist/utils/parsing/csvParser.js +132 -0
- package/dist/utils/parsing/csvParser.js.map +1 -0
- package/dist/utils/parsing/dateParser.d.ts +103 -0
- package/dist/utils/parsing/dateParser.d.ts.map +1 -0
- package/dist/utils/parsing/dateParser.js +142 -0
- package/dist/utils/parsing/dateParser.js.map +1 -0
- package/dist/utils/parsing/frontmatterParser.d.ts +91 -0
- package/dist/utils/parsing/frontmatterParser.d.ts.map +1 -0
- package/dist/utils/parsing/frontmatterParser.js +163 -0
- package/dist/utils/parsing/frontmatterParser.js.map +1 -0
- package/dist/utils/parsing/index.d.ts +15 -0
- package/dist/utils/parsing/index.d.ts.map +1 -0
- package/dist/utils/parsing/index.js +15 -0
- package/dist/utils/parsing/index.js.map +1 -0
- package/dist/utils/parsing/jsonParser.d.ts +115 -0
- package/dist/utils/parsing/jsonParser.d.ts.map +1 -0
- package/dist/utils/parsing/jsonParser.js +177 -0
- package/dist/utils/parsing/jsonParser.js.map +1 -0
- package/dist/utils/parsing/pdfParser.d.ts +563 -0
- package/dist/utils/parsing/pdfParser.d.ts.map +1 -0
- package/dist/utils/parsing/pdfParser.js +775 -0
- package/dist/utils/parsing/pdfParser.js.map +1 -0
- package/dist/utils/parsing/thinkBlock.d.ts +31 -0
- package/dist/utils/parsing/thinkBlock.d.ts.map +1 -0
- package/dist/utils/parsing/thinkBlock.js +31 -0
- package/dist/utils/parsing/thinkBlock.js.map +1 -0
- package/dist/utils/parsing/xmlParser.d.ts +69 -0
- package/dist/utils/parsing/xmlParser.d.ts.map +1 -0
- package/dist/utils/parsing/xmlParser.js +140 -0
- package/dist/utils/parsing/xmlParser.js.map +1 -0
- package/dist/utils/parsing/yamlParser.d.ts +64 -0
- package/dist/utils/parsing/yamlParser.d.ts.map +1 -0
- package/dist/utils/parsing/yamlParser.js +129 -0
- package/dist/utils/parsing/yamlParser.js.map +1 -0
- package/dist/utils/scheduling/scheduler.d.ts +174 -0
- package/dist/utils/scheduling/scheduler.d.ts.map +1 -0
- package/dist/utils/scheduling/scheduler.js +248 -0
- package/dist/utils/scheduling/scheduler.js.map +1 -0
- package/dist/utils/security/idGenerator.d.ts +189 -0
- package/dist/utils/security/idGenerator.d.ts.map +1 -0
- package/dist/utils/security/idGenerator.js +301 -0
- package/dist/utils/security/idGenerator.js.map +1 -0
- package/dist/utils/security/index.d.ts +8 -0
- package/dist/utils/security/index.d.ts.map +1 -0
- package/dist/utils/security/index.js +8 -0
- package/dist/utils/security/index.js.map +1 -0
- package/dist/utils/security/rateLimiter.d.ts +171 -0
- package/dist/utils/security/rateLimiter.d.ts.map +1 -0
- package/dist/utils/security/rateLimiter.js +294 -0
- package/dist/utils/security/rateLimiter.js.map +1 -0
- package/dist/utils/security/sanitization.d.ts +430 -0
- package/dist/utils/security/sanitization.d.ts.map +1 -0
- package/dist/utils/security/sanitization.js +759 -0
- package/dist/utils/security/sanitization.js.map +1 -0
- package/dist/utils/telemetry/index.d.ts +12 -0
- package/dist/utils/telemetry/index.d.ts.map +1 -0
- package/dist/utils/telemetry/index.js +12 -0
- package/dist/utils/telemetry/index.js.map +1 -0
- package/dist/utils/telemetry/instrumentation.d.ts +62 -0
- package/dist/utils/telemetry/instrumentation.d.ts.map +1 -0
- package/dist/utils/telemetry/instrumentation.js +223 -0
- package/dist/utils/telemetry/instrumentation.js.map +1 -0
- package/dist/utils/telemetry/metrics.d.ts +170 -0
- package/dist/utils/telemetry/metrics.d.ts.map +1 -0
- package/dist/utils/telemetry/metrics.js +205 -0
- package/dist/utils/telemetry/metrics.js.map +1 -0
- package/dist/utils/telemetry/semconv.d.ts +147 -0
- package/dist/utils/telemetry/semconv.d.ts.map +1 -0
- package/dist/utils/telemetry/semconv.js +159 -0
- package/dist/utils/telemetry/semconv.js.map +1 -0
- package/dist/utils/telemetry/trace.d.ts +141 -0
- package/dist/utils/telemetry/trace.d.ts.map +1 -0
- package/dist/utils/telemetry/trace.js +193 -0
- package/dist/utils/telemetry/trace.js.map +1 -0
- package/dist/utils/types/guards.d.ts +209 -0
- package/dist/utils/types/guards.d.ts.map +1 -0
- package/dist/utils/types/guards.js +229 -0
- package/dist/utils/types/guards.js.map +1 -0
- package/dist/utils/types/index.d.ts +6 -0
- package/dist/utils/types/index.d.ts.map +1 -0
- package/dist/utils/types/index.js +6 -0
- package/dist/utils/types/index.js.map +1 -0
- package/dist/worker.d.ts +59 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +216 -0
- package/dist/worker.js.map +1 -0
- package/package.json +377 -0
- package/skills/README.md +38 -0
- package/skills/add-export/SKILL.md +49 -0
- package/skills/add-prompt/SKILL.md +97 -0
- package/skills/add-provider/SKILL.md +53 -0
- package/skills/add-resource/SKILL.md +107 -0
- package/skills/add-service/SKILL.md +113 -0
- package/skills/add-tool/SKILL.md +110 -0
- package/skills/api-auth/SKILL.md +173 -0
- package/skills/api-config/SKILL.md +68 -0
- package/skills/api-context/SKILL.md +321 -0
- package/skills/api-errors/SKILL.md +146 -0
- package/skills/api-services/SKILL.md +24 -0
- package/skills/api-services/references/graph.md +124 -0
- package/skills/api-services/references/llm.md +46 -0
- package/skills/api-services/references/speech.md +72 -0
- package/skills/api-testing/SKILL.md +263 -0
- package/skills/api-utils/SKILL.md +106 -0
- package/skills/api-utils/references/formatting.md +237 -0
- package/skills/api-utils/references/parsing.md +263 -0
- package/skills/api-utils/references/security.md +226 -0
- package/skills/api-workers/SKILL.md +165 -0
- package/skills/devcheck/SKILL.md +31 -0
- package/skills/maintenance/SKILL.md +52 -0
- package/skills/migrate-mcp-ts-template/SKILL.md +131 -0
- package/skills/release/SKILL.md +67 -0
- package/skills/setup/SKILL.md +89 -0
- package/skills/walkthrough-init/SKILL.md +50 -0
- package/templates/.env.example +17 -0
- package/templates/AGENTS.md +113 -0
- package/templates/CLAUDE.md +113 -0
- package/templates/_tsconfig.json +33 -0
- package/templates/biome.template.json +43 -0
- package/templates/package.json +26 -0
- package/templates/src/index.ts +16 -0
- package/templates/src/mcp-server/prompts/definitions/echo.prompt.ts +19 -0
- package/templates/src/mcp-server/resources/definitions/echo.resource.ts +30 -0
- package/templates/src/mcp-server/tools/definitions/echo.tool.ts +24 -0
- package/templates/vitest.config.ts +12 -0
- package/tsconfig.base.json +44 -0
- package/vitest.config.base.ts +38 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Provides a utility function to make fetch requests with a specified timeout
|
|
3
|
+
* and optional SSRF protection including DNS resolution validation and redirect following.
|
|
4
|
+
* @module src/utils/network/fetchWithTimeout
|
|
5
|
+
*/
|
|
6
|
+
import { JsonRpcErrorCode, McpError } from '../../types-global/errors.js';
|
|
7
|
+
import { logger } from '../../utils/internal/logger.js';
|
|
8
|
+
import { runtimeCaps } from '../../utils/internal/runtime.js';
|
|
9
|
+
/**
|
|
10
|
+
* IPv4 patterns for private/reserved ranges that should be blocked when
|
|
11
|
+
* `rejectPrivateIPs` is enabled. Covers RFC 1918, loopback, link-local,
|
|
12
|
+
* and cloud metadata endpoints.
|
|
13
|
+
*/
|
|
14
|
+
const PRIVATE_IP_PATTERNS = [
|
|
15
|
+
/^127\./, // Loopback
|
|
16
|
+
/^10\./, // RFC 1918 Class A
|
|
17
|
+
/^172\.(1[6-9]|2\d|3[01])\./, // RFC 1918 Class B
|
|
18
|
+
/^192\.168\./, // RFC 1918 Class C
|
|
19
|
+
/^169\.254\./, // Link-local / cloud metadata
|
|
20
|
+
/^0\./, // Current network
|
|
21
|
+
/^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./, // RFC 6598 (CGNAT)
|
|
22
|
+
];
|
|
23
|
+
/** IPv6 prefixes for private/reserved ranges (checked against DNS-resolved addresses). */
|
|
24
|
+
const PRIVATE_IPV6_PREFIXES = [
|
|
25
|
+
'fe80:', // Link-local
|
|
26
|
+
'fc', // Unique local (fc00::/7)
|
|
27
|
+
'fd', // Unique local (fc00::/7)
|
|
28
|
+
'::1', // Loopback
|
|
29
|
+
'::ffff:127.', // IPv4-mapped loopback
|
|
30
|
+
'::ffff:10.', // IPv4-mapped RFC 1918
|
|
31
|
+
'::ffff:192.168.', // IPv4-mapped RFC 1918
|
|
32
|
+
'::ffff:172.16.', // IPv4-mapped RFC 1918 (partial)
|
|
33
|
+
'::ffff:169.254.', // IPv4-mapped link-local
|
|
34
|
+
];
|
|
35
|
+
const PRIVATE_HOSTNAMES = new Set(['localhost', 'metadata.google.internal', 'metadata.internal']);
|
|
36
|
+
/** Maximum number of redirects to follow when rejectPrivateIPs is enabled. */
|
|
37
|
+
const MAX_SSRF_REDIRECTS = 5;
|
|
38
|
+
/**
|
|
39
|
+
* Checks whether a resolved IP address (v4 or v6) falls within a private or
|
|
40
|
+
* reserved range as defined by {@link PRIVATE_IP_PATTERNS} and
|
|
41
|
+
* {@link PRIVATE_IPV6_PREFIXES}.
|
|
42
|
+
*
|
|
43
|
+
* @param ip - The IP address string to check (bare, no brackets).
|
|
44
|
+
* @returns `true` if the address is private or reserved, `false` otherwise.
|
|
45
|
+
*/
|
|
46
|
+
function isPrivateIP(ip) {
|
|
47
|
+
if (PRIVATE_IP_PATTERNS.some((pattern) => pattern.test(ip)))
|
|
48
|
+
return true;
|
|
49
|
+
const lower = ip.toLowerCase();
|
|
50
|
+
if (PRIVATE_IPV6_PREFIXES.some((prefix) => lower.startsWith(prefix)))
|
|
51
|
+
return true;
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validates that a URL string does not target private or reserved IP space.
|
|
56
|
+
*
|
|
57
|
+
* Performs three checks in order:
|
|
58
|
+
* 1. Known private hostnames (`localhost`, `metadata.google.internal`, etc.)
|
|
59
|
+
* 2. Literal IPv4/IPv6 addresses in the URL hostname
|
|
60
|
+
* 3. DNS resolution (Node.js only) — resolves A and AAAA records and validates each
|
|
61
|
+
*
|
|
62
|
+
* DNS resolution failures (ENOTFOUND, etc.) are swallowed and left for the native
|
|
63
|
+
* `fetch` to handle; only confirmed private IPs cause rejection.
|
|
64
|
+
*
|
|
65
|
+
* @param urlString - The fully-qualified URL string to validate.
|
|
66
|
+
* @throws {McpError} `ValidationError` if the URL is malformed, the hostname is a known
|
|
67
|
+
* internal name, the literal IP is private, or DNS resolves to a private address.
|
|
68
|
+
*/
|
|
69
|
+
async function assertNotPrivateUrl(urlString) {
|
|
70
|
+
let parsed;
|
|
71
|
+
try {
|
|
72
|
+
parsed = new URL(urlString);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
throw new McpError(JsonRpcErrorCode.ValidationError, `Invalid URL: ${urlString}`);
|
|
76
|
+
}
|
|
77
|
+
const hostname = parsed.hostname.replace(/^\[|\]$/g, ''); // Strip IPv6 brackets
|
|
78
|
+
// Check known private hostnames
|
|
79
|
+
if (PRIVATE_HOSTNAMES.has(hostname.toLowerCase())) {
|
|
80
|
+
throw new McpError(JsonRpcErrorCode.ValidationError, `Request to private/internal hostname blocked: ${hostname}`);
|
|
81
|
+
}
|
|
82
|
+
// Check IPv6 loopback
|
|
83
|
+
if (hostname === '::1' || hostname === '0:0:0:0:0:0:0:1') {
|
|
84
|
+
throw new McpError(JsonRpcErrorCode.ValidationError, `Request to loopback address blocked: ${hostname}`);
|
|
85
|
+
}
|
|
86
|
+
// Check IPv4 private ranges (hostname as literal IP)
|
|
87
|
+
if (PRIVATE_IP_PATTERNS.some((pattern) => pattern.test(hostname))) {
|
|
88
|
+
throw new McpError(JsonRpcErrorCode.ValidationError, `Request to private/reserved IP blocked: ${hostname}`);
|
|
89
|
+
}
|
|
90
|
+
// DNS resolution check (Node.js only — Workers have no DNS API)
|
|
91
|
+
if (runtimeCaps.isNode) {
|
|
92
|
+
await assertDnsNotPrivate(hostname);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Resolves DNS for a hostname (A and AAAA records in parallel) and confirms
|
|
97
|
+
* that none of the resolved IP addresses fall within private or reserved ranges.
|
|
98
|
+
*
|
|
99
|
+
* DNS resolution errors (e.g., `ENOTFOUND`) are silently swallowed — they are
|
|
100
|
+
* not an SSRF signal and are better handled by the native `fetch` call.
|
|
101
|
+
*
|
|
102
|
+
* @param hostname - The bare hostname to resolve (no brackets, no port).
|
|
103
|
+
* @throws {McpError} `ValidationError` if any resolved address is private.
|
|
104
|
+
*/
|
|
105
|
+
async function assertDnsNotPrivate(hostname) {
|
|
106
|
+
try {
|
|
107
|
+
const dns = await import('node:dns/promises');
|
|
108
|
+
const [ipv4Results, ipv6Results] = await Promise.allSettled([
|
|
109
|
+
dns.resolve4(hostname),
|
|
110
|
+
dns.resolve6(hostname),
|
|
111
|
+
]);
|
|
112
|
+
const resolvedIPs = [
|
|
113
|
+
...(ipv4Results.status === 'fulfilled' ? ipv4Results.value : []),
|
|
114
|
+
...(ipv6Results.status === 'fulfilled' ? ipv6Results.value : []),
|
|
115
|
+
];
|
|
116
|
+
for (const ip of resolvedIPs) {
|
|
117
|
+
if (isPrivateIP(ip)) {
|
|
118
|
+
throw new McpError(JsonRpcErrorCode.ValidationError, `DNS resolved ${hostname} to private IP ${ip} — SSRF blocked`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
if (error instanceof McpError)
|
|
124
|
+
throw error;
|
|
125
|
+
// DNS resolution failures (ENOTFOUND, etc.) are not SSRF — let fetch handle them
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Fetches a resource with a configurable timeout and optional SSRF protection.
|
|
130
|
+
*
|
|
131
|
+
* Internally manages an `AbortController` that fires after `timeoutMs`. An optional
|
|
132
|
+
* external `signal` (e.g., `ctx.signal`) can be passed via `options` to support
|
|
133
|
+
* early cancellation by the caller. The two signals are composed — whichever fires
|
|
134
|
+
* first wins.
|
|
135
|
+
*
|
|
136
|
+
* When `options.rejectPrivateIPs` is `true`, the target URL is validated before the
|
|
137
|
+
* request is sent, and all redirects are followed manually with per-hop SSRF checks
|
|
138
|
+
* (up to 5 hops). This mode forces `redirect: 'manual'` on the underlying fetch.
|
|
139
|
+
*
|
|
140
|
+
* Non-2xx responses are treated as errors: the response body is read, logged, and
|
|
141
|
+
* wrapped in a `McpError` with code `ServiceUnavailable`.
|
|
142
|
+
*
|
|
143
|
+
* @param url - The URL to fetch (string or `URL` instance).
|
|
144
|
+
* @param timeoutMs - Maximum duration in milliseconds before the request is aborted.
|
|
145
|
+
* @param context - Request context used for structured logging (requestId, operation, etc.).
|
|
146
|
+
* @param options - Optional fetch configuration extending `RequestInit`.
|
|
147
|
+
* - `rejectPrivateIPs`: Block requests to private/internal IP space (SSRF protection).
|
|
148
|
+
* - `signal`: External `AbortSignal` to cancel the request independently of the timeout.
|
|
149
|
+
* - All other standard `RequestInit` fields (method, headers, body, etc.) are forwarded.
|
|
150
|
+
* @returns A promise resolving to the `Response` object on HTTP 2xx.
|
|
151
|
+
* @throws {McpError} `ValidationError` if the URL targets a private/reserved address
|
|
152
|
+
* and `rejectPrivateIPs` is enabled.
|
|
153
|
+
* @throws {McpError} `Timeout` if the request exceeds `timeoutMs`.
|
|
154
|
+
* @throws {McpError} `InternalError` if the request is cancelled via the external signal.
|
|
155
|
+
* @throws {McpError} `ServiceUnavailable` if the server returns a non-2xx status or a
|
|
156
|
+
* network-level error occurs.
|
|
157
|
+
* @example
|
|
158
|
+
* ```ts
|
|
159
|
+
* // Basic GET with a 5-second timeout
|
|
160
|
+
* const response = await fetchWithTimeout(
|
|
161
|
+
* 'https://api.example.com/data',
|
|
162
|
+
* 5000,
|
|
163
|
+
* ctx,
|
|
164
|
+
* );
|
|
165
|
+
* const data = await response.json();
|
|
166
|
+
*
|
|
167
|
+
* // POST with SSRF protection and caller-cancellable signal
|
|
168
|
+
* const response = await fetchWithTimeout(
|
|
169
|
+
* userProvidedUrl,
|
|
170
|
+
* 10_000,
|
|
171
|
+
* ctx,
|
|
172
|
+
* {
|
|
173
|
+
* method: 'POST',
|
|
174
|
+
* headers: { 'Content-Type': 'application/json' },
|
|
175
|
+
* body: JSON.stringify(payload),
|
|
176
|
+
* rejectPrivateIPs: true,
|
|
177
|
+
* signal: ctx.signal,
|
|
178
|
+
* },
|
|
179
|
+
* );
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
export async function fetchWithTimeout(url, timeoutMs, context, options) {
|
|
183
|
+
const urlString = url.toString();
|
|
184
|
+
// SSRF protection: reject private/internal targets when enabled
|
|
185
|
+
if (options?.rejectPrivateIPs) {
|
|
186
|
+
await assertNotPrivateUrl(urlString);
|
|
187
|
+
}
|
|
188
|
+
const operationDescription = `fetch ${options?.method || 'GET'} ${urlString}`;
|
|
189
|
+
logger.debug(`Attempting ${operationDescription} with ${timeoutMs}ms timeout.`, context);
|
|
190
|
+
// Strip custom options before passing to native fetch
|
|
191
|
+
const { rejectPrivateIPs: rejectPrivate, signal: externalSignal, ...fetchInit } = options ?? {};
|
|
192
|
+
// When SSRF protection is active, handle redirects manually to validate each hop
|
|
193
|
+
if (rejectPrivate) {
|
|
194
|
+
fetchInit.redirect = 'manual';
|
|
195
|
+
}
|
|
196
|
+
// Use AbortController instead of AbortSignal.timeout() for cross-runtime compatibility
|
|
197
|
+
// (AbortSignal.timeout() can fail in Bun's stdio transport due to realm mismatch)
|
|
198
|
+
const controller = new AbortController();
|
|
199
|
+
const timeoutSentinel = 'FETCH_TIMEOUT';
|
|
200
|
+
const timeoutId = setTimeout(() => controller.abort(timeoutSentinel), timeoutMs);
|
|
201
|
+
// If an external signal is provided (e.g., client disconnect), forward its abort
|
|
202
|
+
if (externalSignal) {
|
|
203
|
+
if (externalSignal.aborted) {
|
|
204
|
+
controller.abort(externalSignal.reason);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
externalSignal.addEventListener('abort', () => controller.abort(externalSignal.reason), {
|
|
208
|
+
once: true,
|
|
209
|
+
signal: controller.signal,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
let currentUrl = url;
|
|
215
|
+
let redirectCount = 0;
|
|
216
|
+
for (;;) {
|
|
217
|
+
const response = await fetch(currentUrl, {
|
|
218
|
+
...fetchInit,
|
|
219
|
+
signal: controller.signal,
|
|
220
|
+
});
|
|
221
|
+
// Handle redirects manually when SSRF protection is active
|
|
222
|
+
if (rejectPrivate && response.status >= 300 && response.status < 400) {
|
|
223
|
+
const location = response.headers.get('location');
|
|
224
|
+
if (!location) {
|
|
225
|
+
throw new McpError(JsonRpcErrorCode.ServiceUnavailable, `Redirect response missing Location header from ${String(currentUrl)}`);
|
|
226
|
+
}
|
|
227
|
+
redirectCount++;
|
|
228
|
+
if (redirectCount > MAX_SSRF_REDIRECTS) {
|
|
229
|
+
throw new McpError(JsonRpcErrorCode.ValidationError, `Too many redirects (${MAX_SSRF_REDIRECTS}) — possible SSRF redirect loop`);
|
|
230
|
+
}
|
|
231
|
+
// Resolve relative redirect URLs against the current URL
|
|
232
|
+
const redirectUrl = new URL(location, currentUrl.toString()).toString();
|
|
233
|
+
// Validate the redirect target against SSRF rules
|
|
234
|
+
await assertNotPrivateUrl(redirectUrl);
|
|
235
|
+
logger.debug(`Following validated redirect ${redirectCount}: ${redirectUrl}`, context);
|
|
236
|
+
currentUrl = redirectUrl;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (!response.ok) {
|
|
240
|
+
const errorBody = await response.text().catch(() => 'Could not read response body');
|
|
241
|
+
logger.error(`Fetch failed for ${String(currentUrl)} with status ${response.status}.`, {
|
|
242
|
+
...context,
|
|
243
|
+
statusCode: response.status,
|
|
244
|
+
statusText: response.statusText,
|
|
245
|
+
responseBody: errorBody,
|
|
246
|
+
errorSource: 'FetchHttpError',
|
|
247
|
+
});
|
|
248
|
+
throw new McpError(JsonRpcErrorCode.ServiceUnavailable, `Fetch failed for ${String(currentUrl)}. Status: ${response.status}`, {
|
|
249
|
+
requestId: context.requestId,
|
|
250
|
+
operation: context.operation,
|
|
251
|
+
statusCode: response.status,
|
|
252
|
+
statusText: response.statusText,
|
|
253
|
+
responseBody: errorBody,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
logger.debug(`Successfully fetched ${String(currentUrl)}. Status: ${response.status}`, context);
|
|
257
|
+
return response;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
if (error instanceof Error && (error.name === 'TimeoutError' || error.name === 'AbortError')) {
|
|
262
|
+
const isTimeout = error.name === 'TimeoutError' || controller.signal.reason === timeoutSentinel;
|
|
263
|
+
if (isTimeout) {
|
|
264
|
+
logger.error(`${operationDescription} timed out after ${timeoutMs}ms.`, {
|
|
265
|
+
...context,
|
|
266
|
+
errorSource: 'FetchTimeout',
|
|
267
|
+
});
|
|
268
|
+
throw new McpError(JsonRpcErrorCode.Timeout, `${operationDescription} timed out.`, {
|
|
269
|
+
requestId: context.requestId,
|
|
270
|
+
operation: context.operation,
|
|
271
|
+
errorSource: 'FetchTimeout',
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
// External signal abort (e.g., client disconnect) — not a timeout
|
|
275
|
+
logger.info(`${operationDescription} aborted by caller.`, {
|
|
276
|
+
...context,
|
|
277
|
+
errorSource: 'FetchAborted',
|
|
278
|
+
});
|
|
279
|
+
throw new McpError(JsonRpcErrorCode.InternalError, `${operationDescription} was aborted.`, {
|
|
280
|
+
requestId: context.requestId,
|
|
281
|
+
operation: context.operation,
|
|
282
|
+
errorSource: 'FetchAborted',
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
286
|
+
logger.error(`Network error during ${operationDescription}: ${errorMessage}`, {
|
|
287
|
+
...context,
|
|
288
|
+
originalErrorName: error instanceof Error ? error.name : 'UnknownError',
|
|
289
|
+
errorSource: 'FetchNetworkError',
|
|
290
|
+
});
|
|
291
|
+
if (error instanceof McpError) {
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
throw new McpError(JsonRpcErrorCode.ServiceUnavailable, `Network error during ${operationDescription}: ${errorMessage}`, {
|
|
295
|
+
requestId: context.requestId,
|
|
296
|
+
operation: context.operation,
|
|
297
|
+
originalErrorName: error instanceof Error ? error.name : 'UnknownError',
|
|
298
|
+
errorSource: 'FetchNetworkErrorWrapper',
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
finally {
|
|
302
|
+
clearTimeout(timeoutId);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
//# sourceMappingURL=fetchWithTimeout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetchWithTimeout.js","sourceRoot":"","sources":["../../../src/utils/network/fetchWithTimeout.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pagination utilities for MCP list operations.
|
|
3
|
+
* Implements cursor-based pagination per MCP specification 2025-06-18.
|
|
4
|
+
*
|
|
5
|
+
* MCP Pagination Model:
|
|
6
|
+
* - Opaque cursor-based approach (not numbered pages)
|
|
7
|
+
* - Cursor is an opaque string token representing a position in the result set
|
|
8
|
+
* - Page size is determined by server (clients MUST NOT assume fixed page size)
|
|
9
|
+
* - Invalid cursors should result in error code -32602 (Invalid params)
|
|
10
|
+
*
|
|
11
|
+
* @see {@link https://modelcontextprotocol.io/specification/2025-06-18/utils/pagination | MCP Pagination Spec}
|
|
12
|
+
* @module src/utils/pagination/pagination
|
|
13
|
+
*/
|
|
14
|
+
import type { RequestContext } from '../../utils/internal/requestContext.js';
|
|
15
|
+
/**
|
|
16
|
+
* Generic pagination state that can be encoded into a cursor.
|
|
17
|
+
* Implementations can extend this with additional fields as needed.
|
|
18
|
+
* This is the data structure serialized into the opaque cursor token.
|
|
19
|
+
*/
|
|
20
|
+
export interface PaginationState {
|
|
21
|
+
/** Maximum number of items per page; must be a positive integer */
|
|
22
|
+
limit: number;
|
|
23
|
+
/** Zero-based index of the first item in the current page */
|
|
24
|
+
offset: number;
|
|
25
|
+
/** Optional additional state preserved across pages (implementation-specific) */
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Result of a paginated operation.
|
|
30
|
+
* Returned by {@link paginateArray} and should be the shape returned by MCP list handlers.
|
|
31
|
+
* Per MCP spec, `nextCursor` must be omitted (not set to `null` or `""`) when no further pages exist.
|
|
32
|
+
*/
|
|
33
|
+
export interface PaginatedResult<T> {
|
|
34
|
+
/** Items for the current page */
|
|
35
|
+
items: T[];
|
|
36
|
+
/**
|
|
37
|
+
* Opaque cursor token for the next page.
|
|
38
|
+
* Omitted entirely when this is the last page — do not set to `null` or `""`.
|
|
39
|
+
*/
|
|
40
|
+
nextCursor?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Total item count across all pages, if cheaply available.
|
|
43
|
+
* Optional — some backends cannot compute this efficiently.
|
|
44
|
+
*/
|
|
45
|
+
totalCount?: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Encodes pagination state into an opaque base64url cursor string.
|
|
49
|
+
* Serializes `state` to JSON, then encodes as base64url (URL-safe, no padding).
|
|
50
|
+
* The format is an implementation detail — callers must treat the returned string as opaque.
|
|
51
|
+
*
|
|
52
|
+
* @param state - Pagination state to encode; must have `offset >= 0` and `limit > 0`
|
|
53
|
+
* @returns Base64url-encoded cursor string (no `+`, `/`, or `=` characters)
|
|
54
|
+
* @throws {McpError} With code `InternalError` (-32603) if JSON serialization fails
|
|
55
|
+
* @example
|
|
56
|
+
* const cursor = encodeCursor({ offset: 50, limit: 25 });
|
|
57
|
+
* // cursor is an opaque string like "eyJvZmZzZXQiOjUwLCJsaW1pdCI6MjV9"
|
|
58
|
+
*/
|
|
59
|
+
export declare function encodeCursor(state: PaginationState): string;
|
|
60
|
+
/**
|
|
61
|
+
* Decodes an opaque cursor string back into pagination state.
|
|
62
|
+
* Reverses base64url encoding, parses the JSON, and validates the required fields:
|
|
63
|
+
* `offset` must be a non-negative number and `limit` must be a positive number.
|
|
64
|
+
* Logs a warning and throws `InvalidParams` on any failure, per MCP spec.
|
|
65
|
+
*
|
|
66
|
+
* @param cursor - Opaque cursor string previously returned by {@link encodeCursor}
|
|
67
|
+
* @param context - Request context used to correlate warning log entries
|
|
68
|
+
* @returns Validated `PaginationState` decoded from the cursor
|
|
69
|
+
* @throws {McpError} With code `InvalidParams` (-32602) if the cursor is malformed,
|
|
70
|
+
* base64-invalid, not valid JSON, or has an invalid `offset`/`limit` structure
|
|
71
|
+
* @example
|
|
72
|
+
* const state = decodeCursor(req.params.cursor, ctx);
|
|
73
|
+
* // state.offset and state.limit are safe to use directly
|
|
74
|
+
*/
|
|
75
|
+
export declare function decodeCursor(cursor: string, context: RequestContext): PaginationState;
|
|
76
|
+
/**
|
|
77
|
+
* Extracts the cursor parameter from an MCP request's params object.
|
|
78
|
+
* Checks `params.cursor` first, then falls back to `params._meta.cursor`,
|
|
79
|
+
* matching both locations where MCP clients may supply the cursor token.
|
|
80
|
+
*
|
|
81
|
+
* @param params - Optional request params object; may contain `cursor` at the top
|
|
82
|
+
* level or nested under `_meta`
|
|
83
|
+
* @returns The cursor string if found in either location, `undefined` otherwise
|
|
84
|
+
* @example
|
|
85
|
+
* // Top-level cursor
|
|
86
|
+
* extractCursor({ cursor: 'abc123' }); // => 'abc123'
|
|
87
|
+
*
|
|
88
|
+
* // Nested under _meta
|
|
89
|
+
* extractCursor({ _meta: { cursor: 'abc123' } }); // => 'abc123'
|
|
90
|
+
*
|
|
91
|
+
* // No cursor present
|
|
92
|
+
* extractCursor({}); // => undefined
|
|
93
|
+
* extractCursor(undefined); // => undefined
|
|
94
|
+
*/
|
|
95
|
+
export declare function extractCursor(params?: {
|
|
96
|
+
cursor?: string;
|
|
97
|
+
_meta?: {
|
|
98
|
+
cursor?: string;
|
|
99
|
+
};
|
|
100
|
+
}): string | undefined;
|
|
101
|
+
/**
|
|
102
|
+
* Paginates an in-memory array using opaque cursor-based pagination.
|
|
103
|
+
* Decodes the cursor (if provided) to determine offset and limit, slices the array,
|
|
104
|
+
* and returns a {@link PaginatedResult} with `nextCursor` set only when more items follow.
|
|
105
|
+
* When `cursorStr` is `undefined`, pagination starts from the beginning using `defaultPageSize`.
|
|
106
|
+
* When the decoded cursor's offset is beyond the array length, returns an empty items array.
|
|
107
|
+
* The cursor's `limit` is clamped to `maxPageSize` even if the encoded value is larger.
|
|
108
|
+
*
|
|
109
|
+
* @param items - Complete array of items to paginate; not mutated
|
|
110
|
+
* @param cursorStr - Opaque cursor token from the client, or `undefined` for the first page
|
|
111
|
+
* @param defaultPageSize - Page size to use when no cursor is present; must be a positive integer
|
|
112
|
+
* @param maxPageSize - Upper bound on items per page; cursor limit values are clamped to this
|
|
113
|
+
* @param context - Request context used for logging when cursor decoding fails
|
|
114
|
+
* @returns `PaginatedResult<T>` with the current page's items, `totalCount` set to `items.length`,
|
|
115
|
+
* and `nextCursor` included only if additional pages remain
|
|
116
|
+
* @throws {McpError} With code `InvalidParams` (-32602) if `cursorStr` is present but invalid,
|
|
117
|
+
* propagated from {@link decodeCursor}
|
|
118
|
+
* @example
|
|
119
|
+
* const allItems = ['a', 'b', 'c', 'd', 'e'];
|
|
120
|
+
*
|
|
121
|
+
* // First page (no cursor)
|
|
122
|
+
* const page1 = paginateArray(allItems, undefined, 2, 100, ctx);
|
|
123
|
+
* // => { items: ['a', 'b'], nextCursor: '<opaque>', totalCount: 5 }
|
|
124
|
+
*
|
|
125
|
+
* // Second page (using cursor from page1)
|
|
126
|
+
* const page2 = paginateArray(allItems, page1.nextCursor, 2, 100, ctx);
|
|
127
|
+
* // => { items: ['c', 'd'], nextCursor: '<opaque>', totalCount: 5 }
|
|
128
|
+
*
|
|
129
|
+
* // Last page
|
|
130
|
+
* const page3 = paginateArray(allItems, page2.nextCursor, 2, 100, ctx);
|
|
131
|
+
* // => { items: ['e'], totalCount: 5 } (no nextCursor)
|
|
132
|
+
*/
|
|
133
|
+
export declare function paginateArray<T>(items: T[], cursorStr: string | undefined, defaultPageSize: number, maxPageSize: number, context: RequestContext): PaginatedResult<T>;
|
|
134
|
+
/**
|
|
135
|
+
* Default pagination configuration values used by {@link paginateArray} callers.
|
|
136
|
+
* These serve as sensible defaults; individual call sites may pass different values
|
|
137
|
+
* or defer to environment-variable-driven config parsed in the config module.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* import { DEFAULT_PAGINATION_CONFIG, paginateArray } from '../../utils/pagination/pagination.js';
|
|
141
|
+
* const result = paginateArray(
|
|
142
|
+
* items,
|
|
143
|
+
* cursorStr,
|
|
144
|
+
* DEFAULT_PAGINATION_CONFIG.DEFAULT_PAGE_SIZE,
|
|
145
|
+
* DEFAULT_PAGINATION_CONFIG.MAX_PAGE_SIZE,
|
|
146
|
+
* ctx,
|
|
147
|
+
* );
|
|
148
|
+
*/
|
|
149
|
+
export declare const DEFAULT_PAGINATION_CONFIG: {
|
|
150
|
+
/** Default number of items per page */
|
|
151
|
+
readonly DEFAULT_PAGE_SIZE: 50;
|
|
152
|
+
/** Maximum allowed items per page */
|
|
153
|
+
readonly MAX_PAGE_SIZE: 1000;
|
|
154
|
+
/** Minimum allowed items per page */
|
|
155
|
+
readonly MIN_PAGE_SIZE: 1;
|
|
156
|
+
};
|
|
157
|
+
//# sourceMappingURL=pagination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../../../src/utils/pagination/pagination.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,iCAAiC;IACjC,KAAK,EAAE,CAAC,EAAE,CAAC;IACX;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAc3D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,eAAe,CA8BrF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7B,GAAG,MAAM,GAAG,SAAS,CAErB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,CAAC,EAAE,EACV,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,cAAc,GACtB,eAAe,CAAC,CAAC,CAAC,CAmCpB;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,yBAAyB;IACpC,uCAAuC;;IAEvC,qCAAqC;;IAErC,qCAAqC;;CAE7B,CAAC"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pagination utilities for MCP list operations.
|
|
3
|
+
* Implements cursor-based pagination per MCP specification 2025-06-18.
|
|
4
|
+
*
|
|
5
|
+
* MCP Pagination Model:
|
|
6
|
+
* - Opaque cursor-based approach (not numbered pages)
|
|
7
|
+
* - Cursor is an opaque string token representing a position in the result set
|
|
8
|
+
* - Page size is determined by server (clients MUST NOT assume fixed page size)
|
|
9
|
+
* - Invalid cursors should result in error code -32602 (Invalid params)
|
|
10
|
+
*
|
|
11
|
+
* @see {@link https://modelcontextprotocol.io/specification/2025-06-18/utils/pagination | MCP Pagination Spec}
|
|
12
|
+
* @module src/utils/pagination/pagination
|
|
13
|
+
*/
|
|
14
|
+
import { JsonRpcErrorCode, McpError } from '../../types-global/errors.js';
|
|
15
|
+
import { base64ToString, stringToBase64 } from '../../utils/internal/encoding.js';
|
|
16
|
+
import { logger } from '../../utils/internal/logger.js';
|
|
17
|
+
/**
|
|
18
|
+
* Encodes pagination state into an opaque base64url cursor string.
|
|
19
|
+
* Serializes `state` to JSON, then encodes as base64url (URL-safe, no padding).
|
|
20
|
+
* The format is an implementation detail — callers must treat the returned string as opaque.
|
|
21
|
+
*
|
|
22
|
+
* @param state - Pagination state to encode; must have `offset >= 0` and `limit > 0`
|
|
23
|
+
* @returns Base64url-encoded cursor string (no `+`, `/`, or `=` characters)
|
|
24
|
+
* @throws {McpError} With code `InternalError` (-32603) if JSON serialization fails
|
|
25
|
+
* @example
|
|
26
|
+
* const cursor = encodeCursor({ offset: 50, limit: 25 });
|
|
27
|
+
* // cursor is an opaque string like "eyJvZmZzZXQiOjUwLCJsaW1pdCI6MjV9"
|
|
28
|
+
*/
|
|
29
|
+
export function encodeCursor(state) {
|
|
30
|
+
try {
|
|
31
|
+
const jsonString = JSON.stringify(state);
|
|
32
|
+
// Use cross-platform encoding, then convert standard base64 to base64url
|
|
33
|
+
const base64 = stringToBase64(jsonString)
|
|
34
|
+
.replace(/\+/g, '-')
|
|
35
|
+
.replace(/\//g, '_')
|
|
36
|
+
.replace(/=+$/, '');
|
|
37
|
+
return base64;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
throw new McpError(JsonRpcErrorCode.InternalError, 'Failed to encode pagination cursor', {
|
|
41
|
+
error: error instanceof Error ? error.message : String(error),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Decodes an opaque cursor string back into pagination state.
|
|
47
|
+
* Reverses base64url encoding, parses the JSON, and validates the required fields:
|
|
48
|
+
* `offset` must be a non-negative number and `limit` must be a positive number.
|
|
49
|
+
* Logs a warning and throws `InvalidParams` on any failure, per MCP spec.
|
|
50
|
+
*
|
|
51
|
+
* @param cursor - Opaque cursor string previously returned by {@link encodeCursor}
|
|
52
|
+
* @param context - Request context used to correlate warning log entries
|
|
53
|
+
* @returns Validated `PaginationState` decoded from the cursor
|
|
54
|
+
* @throws {McpError} With code `InvalidParams` (-32602) if the cursor is malformed,
|
|
55
|
+
* base64-invalid, not valid JSON, or has an invalid `offset`/`limit` structure
|
|
56
|
+
* @example
|
|
57
|
+
* const state = decodeCursor(req.params.cursor, ctx);
|
|
58
|
+
* // state.offset and state.limit are safe to use directly
|
|
59
|
+
*/
|
|
60
|
+
export function decodeCursor(cursor, context) {
|
|
61
|
+
try {
|
|
62
|
+
// Convert base64url back to standard base64, then decode cross-platform
|
|
63
|
+
const standardBase64 = cursor.replace(/-/g, '+').replace(/_/g, '/');
|
|
64
|
+
const jsonString = base64ToString(standardBase64);
|
|
65
|
+
const state = JSON.parse(jsonString);
|
|
66
|
+
// Validate required fields
|
|
67
|
+
if (typeof state.offset !== 'number' ||
|
|
68
|
+
typeof state.limit !== 'number' ||
|
|
69
|
+
state.offset < 0 ||
|
|
70
|
+
state.limit <= 0) {
|
|
71
|
+
throw new Error('Invalid pagination state structure');
|
|
72
|
+
}
|
|
73
|
+
return state;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
logger.warning('Failed to decode pagination cursor', {
|
|
77
|
+
...context,
|
|
78
|
+
cursor,
|
|
79
|
+
error: error instanceof Error ? error.message : String(error),
|
|
80
|
+
});
|
|
81
|
+
throw new McpError(JsonRpcErrorCode.InvalidParams, 'Invalid pagination cursor. The cursor may be expired, corrupted, or from a different request.', { cursor });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Extracts the cursor parameter from an MCP request's params object.
|
|
86
|
+
* Checks `params.cursor` first, then falls back to `params._meta.cursor`,
|
|
87
|
+
* matching both locations where MCP clients may supply the cursor token.
|
|
88
|
+
*
|
|
89
|
+
* @param params - Optional request params object; may contain `cursor` at the top
|
|
90
|
+
* level or nested under `_meta`
|
|
91
|
+
* @returns The cursor string if found in either location, `undefined` otherwise
|
|
92
|
+
* @example
|
|
93
|
+
* // Top-level cursor
|
|
94
|
+
* extractCursor({ cursor: 'abc123' }); // => 'abc123'
|
|
95
|
+
*
|
|
96
|
+
* // Nested under _meta
|
|
97
|
+
* extractCursor({ _meta: { cursor: 'abc123' } }); // => 'abc123'
|
|
98
|
+
*
|
|
99
|
+
* // No cursor present
|
|
100
|
+
* extractCursor({}); // => undefined
|
|
101
|
+
* extractCursor(undefined); // => undefined
|
|
102
|
+
*/
|
|
103
|
+
export function extractCursor(params) {
|
|
104
|
+
return params?.cursor ?? params?._meta?.cursor;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Paginates an in-memory array using opaque cursor-based pagination.
|
|
108
|
+
* Decodes the cursor (if provided) to determine offset and limit, slices the array,
|
|
109
|
+
* and returns a {@link PaginatedResult} with `nextCursor` set only when more items follow.
|
|
110
|
+
* When `cursorStr` is `undefined`, pagination starts from the beginning using `defaultPageSize`.
|
|
111
|
+
* When the decoded cursor's offset is beyond the array length, returns an empty items array.
|
|
112
|
+
* The cursor's `limit` is clamped to `maxPageSize` even if the encoded value is larger.
|
|
113
|
+
*
|
|
114
|
+
* @param items - Complete array of items to paginate; not mutated
|
|
115
|
+
* @param cursorStr - Opaque cursor token from the client, or `undefined` for the first page
|
|
116
|
+
* @param defaultPageSize - Page size to use when no cursor is present; must be a positive integer
|
|
117
|
+
* @param maxPageSize - Upper bound on items per page; cursor limit values are clamped to this
|
|
118
|
+
* @param context - Request context used for logging when cursor decoding fails
|
|
119
|
+
* @returns `PaginatedResult<T>` with the current page's items, `totalCount` set to `items.length`,
|
|
120
|
+
* and `nextCursor` included only if additional pages remain
|
|
121
|
+
* @throws {McpError} With code `InvalidParams` (-32602) if `cursorStr` is present but invalid,
|
|
122
|
+
* propagated from {@link decodeCursor}
|
|
123
|
+
* @example
|
|
124
|
+
* const allItems = ['a', 'b', 'c', 'd', 'e'];
|
|
125
|
+
*
|
|
126
|
+
* // First page (no cursor)
|
|
127
|
+
* const page1 = paginateArray(allItems, undefined, 2, 100, ctx);
|
|
128
|
+
* // => { items: ['a', 'b'], nextCursor: '<opaque>', totalCount: 5 }
|
|
129
|
+
*
|
|
130
|
+
* // Second page (using cursor from page1)
|
|
131
|
+
* const page2 = paginateArray(allItems, page1.nextCursor, 2, 100, ctx);
|
|
132
|
+
* // => { items: ['c', 'd'], nextCursor: '<opaque>', totalCount: 5 }
|
|
133
|
+
*
|
|
134
|
+
* // Last page
|
|
135
|
+
* const page3 = paginateArray(allItems, page2.nextCursor, 2, 100, ctx);
|
|
136
|
+
* // => { items: ['e'], totalCount: 5 } (no nextCursor)
|
|
137
|
+
*/
|
|
138
|
+
export function paginateArray(items, cursorStr, defaultPageSize, maxPageSize, context) {
|
|
139
|
+
let offset = 0;
|
|
140
|
+
let limit = defaultPageSize;
|
|
141
|
+
// Decode cursor if provided
|
|
142
|
+
if (cursorStr) {
|
|
143
|
+
const state = decodeCursor(cursorStr, context);
|
|
144
|
+
offset = state.offset;
|
|
145
|
+
limit = Math.min(state.limit, maxPageSize); // Enforce max page size
|
|
146
|
+
}
|
|
147
|
+
// Validate bounds
|
|
148
|
+
if (offset >= items.length) {
|
|
149
|
+
return {
|
|
150
|
+
items: [],
|
|
151
|
+
totalCount: items.length,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
// Extract page
|
|
155
|
+
const pageItems = items.slice(offset, offset + limit);
|
|
156
|
+
const hasMore = offset + limit < items.length;
|
|
157
|
+
// Build result, conditionally adding nextCursor only if it exists
|
|
158
|
+
const result = {
|
|
159
|
+
items: pageItems,
|
|
160
|
+
totalCount: items.length,
|
|
161
|
+
};
|
|
162
|
+
// Only add nextCursor if more results exist
|
|
163
|
+
if (hasMore) {
|
|
164
|
+
result.nextCursor = encodeCursor({ offset: offset + limit, limit });
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Default pagination configuration values used by {@link paginateArray} callers.
|
|
170
|
+
* These serve as sensible defaults; individual call sites may pass different values
|
|
171
|
+
* or defer to environment-variable-driven config parsed in the config module.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* import { DEFAULT_PAGINATION_CONFIG, paginateArray } from '../../utils/pagination/pagination.js';
|
|
175
|
+
* const result = paginateArray(
|
|
176
|
+
* items,
|
|
177
|
+
* cursorStr,
|
|
178
|
+
* DEFAULT_PAGINATION_CONFIG.DEFAULT_PAGE_SIZE,
|
|
179
|
+
* DEFAULT_PAGINATION_CONFIG.MAX_PAGE_SIZE,
|
|
180
|
+
* ctx,
|
|
181
|
+
* );
|
|
182
|
+
*/
|
|
183
|
+
export const DEFAULT_PAGINATION_CONFIG = {
|
|
184
|
+
/** Default number of items per page */
|
|
185
|
+
DEFAULT_PAGE_SIZE: 50,
|
|
186
|
+
/** Maximum allowed items per page */
|
|
187
|
+
MAX_PAGE_SIZE: 1000,
|
|
188
|
+
/** Minimum allowed items per page */
|
|
189
|
+
MIN_PAGE_SIZE: 1,
|
|
190
|
+
};
|
|
191
|
+
//# sourceMappingURL=pagination.js.map
|