@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.
Files changed (485) hide show
  1. package/CLAUDE.md +583 -0
  2. package/LICENSE +201 -0
  3. package/README.md +287 -0
  4. package/biome.json +103 -0
  5. package/dist/app.d.ts +82 -0
  6. package/dist/app.d.ts.map +1 -0
  7. package/dist/app.js +222 -0
  8. package/dist/app.js.map +1 -0
  9. package/dist/cli/init.d.ts +8 -0
  10. package/dist/cli/init.d.ts.map +1 -0
  11. package/dist/cli/init.js +161 -0
  12. package/dist/cli/init.js.map +1 -0
  13. package/dist/config/index.d.ts +349 -0
  14. package/dist/config/index.d.ts.map +1 -0
  15. package/dist/config/index.js +464 -0
  16. package/dist/config/index.js.map +1 -0
  17. package/dist/context.d.ts +119 -0
  18. package/dist/context.d.ts.map +1 -0
  19. package/dist/context.js +144 -0
  20. package/dist/context.js.map +1 -0
  21. package/dist/index.d.ts +8 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +9 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/mcp-server/prompts/prompt-registration.d.ts +33 -0
  26. package/dist/mcp-server/prompts/prompt-registration.d.ts.map +1 -0
  27. package/dist/mcp-server/prompts/prompt-registration.js +91 -0
  28. package/dist/mcp-server/prompts/prompt-registration.js.map +1 -0
  29. package/dist/mcp-server/prompts/utils/newPromptDefinition.d.ts +49 -0
  30. package/dist/mcp-server/prompts/utils/newPromptDefinition.d.ts.map +1 -0
  31. package/dist/mcp-server/prompts/utils/newPromptDefinition.js +39 -0
  32. package/dist/mcp-server/prompts/utils/newPromptDefinition.js.map +1 -0
  33. package/dist/mcp-server/prompts/utils/promptDefinition.d.ts +37 -0
  34. package/dist/mcp-server/prompts/utils/promptDefinition.d.ts.map +1 -0
  35. package/dist/mcp-server/prompts/utils/promptDefinition.js +2 -0
  36. package/dist/mcp-server/prompts/utils/promptDefinition.js.map +1 -0
  37. package/dist/mcp-server/resources/resource-registration.d.ts +27 -0
  38. package/dist/mcp-server/resources/resource-registration.d.ts.map +1 -0
  39. package/dist/mcp-server/resources/resource-registration.js +85 -0
  40. package/dist/mcp-server/resources/resource-registration.js.map +1 -0
  41. package/dist/mcp-server/resources/utils/newResourceDefinition.d.ts +84 -0
  42. package/dist/mcp-server/resources/utils/newResourceDefinition.d.ts.map +1 -0
  43. package/dist/mcp-server/resources/utils/newResourceDefinition.js +40 -0
  44. package/dist/mcp-server/resources/utils/newResourceDefinition.js.map +1 -0
  45. package/dist/mcp-server/resources/utils/newResourceHandlerFactory.d.ts +32 -0
  46. package/dist/mcp-server/resources/utils/newResourceHandlerFactory.d.ts.map +1 -0
  47. package/dist/mcp-server/resources/utils/newResourceHandlerFactory.js +103 -0
  48. package/dist/mcp-server/resources/utils/newResourceHandlerFactory.js.map +1 -0
  49. package/dist/mcp-server/resources/utils/resourceDefinition.d.ts +94 -0
  50. package/dist/mcp-server/resources/utils/resourceDefinition.d.ts.map +1 -0
  51. package/dist/mcp-server/resources/utils/resourceDefinition.js +2 -0
  52. package/dist/mcp-server/resources/utils/resourceDefinition.js.map +1 -0
  53. package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts +14 -0
  54. package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -0
  55. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +111 -0
  56. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -0
  57. package/dist/mcp-server/roots/roots-registration.d.ts +22 -0
  58. package/dist/mcp-server/roots/roots-registration.d.ts.map +1 -0
  59. package/dist/mcp-server/roots/roots-registration.js +25 -0
  60. package/dist/mcp-server/roots/roots-registration.js.map +1 -0
  61. package/dist/mcp-server/server.d.ts +34 -0
  62. package/dist/mcp-server/server.d.ts.map +1 -0
  63. package/dist/mcp-server/server.js +62 -0
  64. package/dist/mcp-server/server.js.map +1 -0
  65. package/dist/mcp-server/tasks/core/sessionAwareTaskStore.d.ts +42 -0
  66. package/dist/mcp-server/tasks/core/sessionAwareTaskStore.d.ts.map +1 -0
  67. package/dist/mcp-server/tasks/core/sessionAwareTaskStore.js +70 -0
  68. package/dist/mcp-server/tasks/core/sessionAwareTaskStore.js.map +1 -0
  69. package/dist/mcp-server/tasks/core/storageBackedTaskStore.d.ts +109 -0
  70. package/dist/mcp-server/tasks/core/storageBackedTaskStore.d.ts.map +1 -0
  71. package/dist/mcp-server/tasks/core/storageBackedTaskStore.js +209 -0
  72. package/dist/mcp-server/tasks/core/storageBackedTaskStore.js.map +1 -0
  73. package/dist/mcp-server/tasks/core/taskManager.d.ts +103 -0
  74. package/dist/mcp-server/tasks/core/taskManager.d.ts.map +1 -0
  75. package/dist/mcp-server/tasks/core/taskManager.js +144 -0
  76. package/dist/mcp-server/tasks/core/taskManager.js.map +1 -0
  77. package/dist/mcp-server/tasks/core/taskTypes.d.ts +11 -0
  78. package/dist/mcp-server/tasks/core/taskTypes.d.ts.map +1 -0
  79. package/dist/mcp-server/tasks/core/taskTypes.js +13 -0
  80. package/dist/mcp-server/tasks/core/taskTypes.js.map +1 -0
  81. package/dist/mcp-server/tasks/utils/taskToolDefinition.d.ts +108 -0
  82. package/dist/mcp-server/tasks/utils/taskToolDefinition.d.ts.map +1 -0
  83. package/dist/mcp-server/tasks/utils/taskToolDefinition.js +14 -0
  84. package/dist/mcp-server/tasks/utils/taskToolDefinition.js.map +1 -0
  85. package/dist/mcp-server/tools/tool-registration.d.ts +49 -0
  86. package/dist/mcp-server/tools/tool-registration.d.ts.map +1 -0
  87. package/dist/mcp-server/tools/tool-registration.js +269 -0
  88. package/dist/mcp-server/tools/tool-registration.js.map +1 -0
  89. package/dist/mcp-server/tools/utils/newToolDefinition.d.ts +73 -0
  90. package/dist/mcp-server/tools/utils/newToolDefinition.d.ts.map +1 -0
  91. package/dist/mcp-server/tools/utils/newToolDefinition.js +45 -0
  92. package/dist/mcp-server/tools/utils/newToolDefinition.js.map +1 -0
  93. package/dist/mcp-server/tools/utils/newToolHandlerFactory.d.ts +33 -0
  94. package/dist/mcp-server/tools/utils/newToolHandlerFactory.d.ts.map +1 -0
  95. package/dist/mcp-server/tools/utils/newToolHandlerFactory.js +107 -0
  96. package/dist/mcp-server/tools/utils/newToolHandlerFactory.js.map +1 -0
  97. package/dist/mcp-server/tools/utils/toolDefinition.d.ts +118 -0
  98. package/dist/mcp-server/tools/utils/toolDefinition.d.ts.map +1 -0
  99. package/dist/mcp-server/tools/utils/toolDefinition.js +2 -0
  100. package/dist/mcp-server/tools/utils/toolDefinition.js.map +1 -0
  101. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +34 -0
  102. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -0
  103. package/dist/mcp-server/tools/utils/toolHandlerFactory.js +68 -0
  104. package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -0
  105. package/dist/mcp-server/transports/ITransport.d.ts +15 -0
  106. package/dist/mcp-server/transports/ITransport.d.ts.map +1 -0
  107. package/dist/mcp-server/transports/ITransport.js +2 -0
  108. package/dist/mcp-server/transports/ITransport.js.map +1 -0
  109. package/dist/mcp-server/transports/auth/authFactory.d.ts +11 -0
  110. package/dist/mcp-server/transports/auth/authFactory.d.ts.map +1 -0
  111. package/dist/mcp-server/transports/auth/authFactory.js +43 -0
  112. package/dist/mcp-server/transports/auth/authFactory.js.map +1 -0
  113. package/dist/mcp-server/transports/auth/authMiddleware.d.ts +24 -0
  114. package/dist/mcp-server/transports/auth/authMiddleware.d.ts.map +1 -0
  115. package/dist/mcp-server/transports/auth/authMiddleware.js +69 -0
  116. package/dist/mcp-server/transports/auth/authMiddleware.js.map +1 -0
  117. package/dist/mcp-server/transports/auth/lib/authContext.d.ts +34 -0
  118. package/dist/mcp-server/transports/auth/lib/authContext.d.ts.map +1 -0
  119. package/dist/mcp-server/transports/auth/lib/authContext.js +25 -0
  120. package/dist/mcp-server/transports/auth/lib/authContext.js.map +1 -0
  121. package/dist/mcp-server/transports/auth/lib/authTypes.d.ts +19 -0
  122. package/dist/mcp-server/transports/auth/lib/authTypes.d.ts.map +1 -0
  123. package/dist/mcp-server/transports/auth/lib/authTypes.js +2 -0
  124. package/dist/mcp-server/transports/auth/lib/authTypes.js.map +1 -0
  125. package/dist/mcp-server/transports/auth/lib/authUtils.d.ts +18 -0
  126. package/dist/mcp-server/transports/auth/lib/authUtils.d.ts.map +1 -0
  127. package/dist/mcp-server/transports/auth/lib/authUtils.js +64 -0
  128. package/dist/mcp-server/transports/auth/lib/authUtils.js.map +1 -0
  129. package/dist/mcp-server/transports/auth/lib/checkScopes.d.ts +25 -0
  130. package/dist/mcp-server/transports/auth/lib/checkScopes.d.ts.map +1 -0
  131. package/dist/mcp-server/transports/auth/lib/checkScopes.js +34 -0
  132. package/dist/mcp-server/transports/auth/lib/checkScopes.js.map +1 -0
  133. package/dist/mcp-server/transports/auth/lib/claimParser.d.ts +34 -0
  134. package/dist/mcp-server/transports/auth/lib/claimParser.d.ts.map +1 -0
  135. package/dist/mcp-server/transports/auth/lib/claimParser.js +58 -0
  136. package/dist/mcp-server/transports/auth/lib/claimParser.js.map +1 -0
  137. package/dist/mcp-server/transports/auth/lib/withAuth.d.ts +25 -0
  138. package/dist/mcp-server/transports/auth/lib/withAuth.d.ts.map +1 -0
  139. package/dist/mcp-server/transports/auth/lib/withAuth.js +30 -0
  140. package/dist/mcp-server/transports/auth/lib/withAuth.js.map +1 -0
  141. package/dist/mcp-server/transports/auth/strategies/authStrategy.d.ts +18 -0
  142. package/dist/mcp-server/transports/auth/strategies/authStrategy.d.ts.map +1 -0
  143. package/dist/mcp-server/transports/auth/strategies/authStrategy.js +2 -0
  144. package/dist/mcp-server/transports/auth/strategies/authStrategy.js.map +1 -0
  145. package/dist/mcp-server/transports/auth/strategies/jwtStrategy.d.ts +14 -0
  146. package/dist/mcp-server/transports/auth/strategies/jwtStrategy.d.ts.map +1 -0
  147. package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js +86 -0
  148. package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js.map +1 -0
  149. package/dist/mcp-server/transports/auth/strategies/oauthStrategy.d.ts +14 -0
  150. package/dist/mcp-server/transports/auth/strategies/oauthStrategy.d.ts.map +1 -0
  151. package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js +113 -0
  152. package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js.map +1 -0
  153. package/dist/mcp-server/transports/http/httpErrorHandler.d.ts +25 -0
  154. package/dist/mcp-server/transports/http/httpErrorHandler.d.ts.map +1 -0
  155. package/dist/mcp-server/transports/http/httpErrorHandler.js +112 -0
  156. package/dist/mcp-server/transports/http/httpErrorHandler.js.map +1 -0
  157. package/dist/mcp-server/transports/http/httpTransport.d.ts +47 -0
  158. package/dist/mcp-server/transports/http/httpTransport.d.ts.map +1 -0
  159. package/dist/mcp-server/transports/http/httpTransport.js +396 -0
  160. package/dist/mcp-server/transports/http/httpTransport.js.map +1 -0
  161. package/dist/mcp-server/transports/http/httpTypes.d.ts +17 -0
  162. package/dist/mcp-server/transports/http/httpTypes.d.ts.map +1 -0
  163. package/dist/mcp-server/transports/http/httpTypes.js +2 -0
  164. package/dist/mcp-server/transports/http/httpTypes.js.map +1 -0
  165. package/dist/mcp-server/transports/http/protectedResourceMetadata.d.ts +21 -0
  166. package/dist/mcp-server/transports/http/protectedResourceMetadata.d.ts.map +1 -0
  167. package/dist/mcp-server/transports/http/protectedResourceMetadata.js +44 -0
  168. package/dist/mcp-server/transports/http/protectedResourceMetadata.js.map +1 -0
  169. package/dist/mcp-server/transports/http/sessionIdUtils.d.ts +33 -0
  170. package/dist/mcp-server/transports/http/sessionIdUtils.d.ts.map +1 -0
  171. package/dist/mcp-server/transports/http/sessionIdUtils.js +54 -0
  172. package/dist/mcp-server/transports/http/sessionIdUtils.js.map +1 -0
  173. package/dist/mcp-server/transports/http/sessionStore.d.ts +87 -0
  174. package/dist/mcp-server/transports/http/sessionStore.d.ts.map +1 -0
  175. package/dist/mcp-server/transports/http/sessionStore.js +209 -0
  176. package/dist/mcp-server/transports/http/sessionStore.js.map +1 -0
  177. package/dist/mcp-server/transports/manager.d.ts +22 -0
  178. package/dist/mcp-server/transports/manager.d.ts.map +1 -0
  179. package/dist/mcp-server/transports/manager.js +62 -0
  180. package/dist/mcp-server/transports/manager.js.map +1 -0
  181. package/dist/mcp-server/transports/stdio/stdioTransport.d.ts +44 -0
  182. package/dist/mcp-server/transports/stdio/stdioTransport.d.ts.map +1 -0
  183. package/dist/mcp-server/transports/stdio/stdioTransport.js +63 -0
  184. package/dist/mcp-server/transports/stdio/stdioTransport.js.map +1 -0
  185. package/dist/services/graph/core/GraphService.d.ts +205 -0
  186. package/dist/services/graph/core/GraphService.d.ts.map +1 -0
  187. package/dist/services/graph/core/GraphService.js +231 -0
  188. package/dist/services/graph/core/GraphService.js.map +1 -0
  189. package/dist/services/graph/core/IGraphProvider.d.ts +295 -0
  190. package/dist/services/graph/core/IGraphProvider.d.ts.map +1 -0
  191. package/dist/services/graph/core/IGraphProvider.js +8 -0
  192. package/dist/services/graph/core/IGraphProvider.js.map +1 -0
  193. package/dist/services/graph/types.d.ts +107 -0
  194. package/dist/services/graph/types.d.ts.map +1 -0
  195. package/dist/services/graph/types.js +8 -0
  196. package/dist/services/graph/types.js.map +1 -0
  197. package/dist/services/llm/core/ILlmProvider.d.ts +86 -0
  198. package/dist/services/llm/core/ILlmProvider.d.ts.map +1 -0
  199. package/dist/services/llm/core/ILlmProvider.js +2 -0
  200. package/dist/services/llm/core/ILlmProvider.js.map +1 -0
  201. package/dist/services/llm/providers/openrouter.provider.d.ts +187 -0
  202. package/dist/services/llm/providers/openrouter.provider.d.ts.map +1 -0
  203. package/dist/services/llm/providers/openrouter.provider.js +302 -0
  204. package/dist/services/llm/providers/openrouter.provider.js.map +1 -0
  205. package/dist/services/llm/types.d.ts +16 -0
  206. package/dist/services/llm/types.d.ts.map +1 -0
  207. package/dist/services/llm/types.js +9 -0
  208. package/dist/services/llm/types.js.map +1 -0
  209. package/dist/services/speech/core/ISpeechProvider.d.ts +92 -0
  210. package/dist/services/speech/core/ISpeechProvider.d.ts.map +1 -0
  211. package/dist/services/speech/core/ISpeechProvider.js +34 -0
  212. package/dist/services/speech/core/ISpeechProvider.js.map +1 -0
  213. package/dist/services/speech/core/SpeechService.d.ts +87 -0
  214. package/dist/services/speech/core/SpeechService.d.ts.map +1 -0
  215. package/dist/services/speech/core/SpeechService.js +135 -0
  216. package/dist/services/speech/core/SpeechService.js.map +1 -0
  217. package/dist/services/speech/providers/elevenlabs.provider.d.ts +77 -0
  218. package/dist/services/speech/providers/elevenlabs.provider.d.ts.map +1 -0
  219. package/dist/services/speech/providers/elevenlabs.provider.js +199 -0
  220. package/dist/services/speech/providers/elevenlabs.provider.js.map +1 -0
  221. package/dist/services/speech/providers/whisper.provider.d.ts +94 -0
  222. package/dist/services/speech/providers/whisper.provider.d.ts.map +1 -0
  223. package/dist/services/speech/providers/whisper.provider.js +240 -0
  224. package/dist/services/speech/providers/whisper.provider.js.map +1 -0
  225. package/dist/services/speech/types.d.ts +173 -0
  226. package/dist/services/speech/types.d.ts.map +1 -0
  227. package/dist/services/speech/types.js +8 -0
  228. package/dist/services/speech/types.js.map +1 -0
  229. package/dist/storage/core/IStorageProvider.d.ts +159 -0
  230. package/dist/storage/core/IStorageProvider.d.ts.map +1 -0
  231. package/dist/storage/core/IStorageProvider.js +2 -0
  232. package/dist/storage/core/IStorageProvider.js.map +1 -0
  233. package/dist/storage/core/StorageService.d.ts +22 -0
  234. package/dist/storage/core/StorageService.d.ts.map +1 -0
  235. package/dist/storage/core/StorageService.js +151 -0
  236. package/dist/storage/core/StorageService.js.map +1 -0
  237. package/dist/storage/core/storageFactory.d.ts +66 -0
  238. package/dist/storage/core/storageFactory.d.ts.map +1 -0
  239. package/dist/storage/core/storageFactory.js +122 -0
  240. package/dist/storage/core/storageFactory.js.map +1 -0
  241. package/dist/storage/core/storageValidation.d.ts +77 -0
  242. package/dist/storage/core/storageValidation.d.ts.map +1 -0
  243. package/dist/storage/core/storageValidation.js +303 -0
  244. package/dist/storage/core/storageValidation.js.map +1 -0
  245. package/dist/storage/providers/cloudflare/d1Provider.d.ts +94 -0
  246. package/dist/storage/providers/cloudflare/d1Provider.d.ts.map +1 -0
  247. package/dist/storage/providers/cloudflare/d1Provider.js +347 -0
  248. package/dist/storage/providers/cloudflare/d1Provider.js.map +1 -0
  249. package/dist/storage/providers/cloudflare/kvProvider.d.ts +21 -0
  250. package/dist/storage/providers/cloudflare/kvProvider.d.ts.map +1 -0
  251. package/dist/storage/providers/cloudflare/kvProvider.js +183 -0
  252. package/dist/storage/providers/cloudflare/kvProvider.js.map +1 -0
  253. package/dist/storage/providers/cloudflare/r2Provider.d.ts +28 -0
  254. package/dist/storage/providers/cloudflare/r2Provider.d.ts.map +1 -0
  255. package/dist/storage/providers/cloudflare/r2Provider.js +222 -0
  256. package/dist/storage/providers/cloudflare/r2Provider.js.map +1 -0
  257. package/dist/storage/providers/fileSystem/fileSystemProvider.d.ts +20 -0
  258. package/dist/storage/providers/fileSystem/fileSystemProvider.d.ts.map +1 -0
  259. package/dist/storage/providers/fileSystem/fileSystemProvider.js +282 -0
  260. package/dist/storage/providers/fileSystem/fileSystemProvider.js.map +1 -0
  261. package/dist/storage/providers/inMemory/inMemoryProvider.d.ts +21 -0
  262. package/dist/storage/providers/inMemory/inMemoryProvider.d.ts.map +1 -0
  263. package/dist/storage/providers/inMemory/inMemoryProvider.js +139 -0
  264. package/dist/storage/providers/inMemory/inMemoryProvider.js.map +1 -0
  265. package/dist/storage/providers/supabase/supabase.types.d.ts +49 -0
  266. package/dist/storage/providers/supabase/supabase.types.d.ts.map +1 -0
  267. package/dist/storage/providers/supabase/supabase.types.js +8 -0
  268. package/dist/storage/providers/supabase/supabase.types.js.map +1 -0
  269. package/dist/storage/providers/supabase/supabaseProvider.d.ts +24 -0
  270. package/dist/storage/providers/supabase/supabaseProvider.d.ts.map +1 -0
  271. package/dist/storage/providers/supabase/supabaseProvider.js +209 -0
  272. package/dist/storage/providers/supabase/supabaseProvider.js.map +1 -0
  273. package/dist/testing/index.d.ts +53 -0
  274. package/dist/testing/index.d.ts.map +1 -0
  275. package/dist/testing/index.js +132 -0
  276. package/dist/testing/index.js.map +1 -0
  277. package/dist/types-global/errors.d.ts +83 -0
  278. package/dist/types-global/errors.d.ts.map +1 -0
  279. package/dist/types-global/errors.js +113 -0
  280. package/dist/types-global/errors.js.map +1 -0
  281. package/dist/utils/formatting/diffFormatter.d.ts +227 -0
  282. package/dist/utils/formatting/diffFormatter.d.ts.map +1 -0
  283. package/dist/utils/formatting/diffFormatter.js +369 -0
  284. package/dist/utils/formatting/diffFormatter.js.map +1 -0
  285. package/dist/utils/formatting/index.d.ts +9 -0
  286. package/dist/utils/formatting/index.d.ts.map +1 -0
  287. package/dist/utils/formatting/index.js +9 -0
  288. package/dist/utils/formatting/index.js.map +1 -0
  289. package/dist/utils/formatting/markdownBuilder.d.ts +543 -0
  290. package/dist/utils/formatting/markdownBuilder.d.ts.map +1 -0
  291. package/dist/utils/formatting/markdownBuilder.js +674 -0
  292. package/dist/utils/formatting/markdownBuilder.js.map +1 -0
  293. package/dist/utils/formatting/tableFormatter.d.ts +261 -0
  294. package/dist/utils/formatting/tableFormatter.d.ts.map +1 -0
  295. package/dist/utils/formatting/tableFormatter.js +456 -0
  296. package/dist/utils/formatting/tableFormatter.js.map +1 -0
  297. package/dist/utils/formatting/treeFormatter.d.ts +344 -0
  298. package/dist/utils/formatting/treeFormatter.d.ts.map +1 -0
  299. package/dist/utils/formatting/treeFormatter.js +400 -0
  300. package/dist/utils/formatting/treeFormatter.js.map +1 -0
  301. package/dist/utils/internal/encoding.d.ts +42 -0
  302. package/dist/utils/internal/encoding.d.ts.map +1 -0
  303. package/dist/utils/internal/encoding.js +87 -0
  304. package/dist/utils/internal/encoding.js.map +1 -0
  305. package/dist/utils/internal/error-handler/errorHandler.d.ts +140 -0
  306. package/dist/utils/internal/error-handler/errorHandler.d.ts.map +1 -0
  307. package/dist/utils/internal/error-handler/errorHandler.js +318 -0
  308. package/dist/utils/internal/error-handler/errorHandler.js.map +1 -0
  309. package/dist/utils/internal/error-handler/helpers.d.ts +98 -0
  310. package/dist/utils/internal/error-handler/helpers.d.ts.map +1 -0
  311. package/dist/utils/internal/error-handler/helpers.js +214 -0
  312. package/dist/utils/internal/error-handler/helpers.js.map +1 -0
  313. package/dist/utils/internal/error-handler/mappings.d.ts +85 -0
  314. package/dist/utils/internal/error-handler/mappings.d.ts.map +1 -0
  315. package/dist/utils/internal/error-handler/mappings.js +234 -0
  316. package/dist/utils/internal/error-handler/mappings.js.map +1 -0
  317. package/dist/utils/internal/error-handler/types.d.ts +160 -0
  318. package/dist/utils/internal/error-handler/types.d.ts.map +1 -0
  319. package/dist/utils/internal/error-handler/types.js +6 -0
  320. package/dist/utils/internal/error-handler/types.js.map +1 -0
  321. package/dist/utils/internal/health.d.ts +60 -0
  322. package/dist/utils/internal/health.d.ts.map +1 -0
  323. package/dist/utils/internal/health.js +46 -0
  324. package/dist/utils/internal/health.js.map +1 -0
  325. package/dist/utils/internal/logger.d.ts +300 -0
  326. package/dist/utils/internal/logger.d.ts.map +1 -0
  327. package/dist/utils/internal/logger.js +573 -0
  328. package/dist/utils/internal/logger.js.map +1 -0
  329. package/dist/utils/internal/performance.d.ts +78 -0
  330. package/dist/utils/internal/performance.d.ts.map +1 -0
  331. package/dist/utils/internal/performance.js +227 -0
  332. package/dist/utils/internal/performance.js.map +1 -0
  333. package/dist/utils/internal/requestContext.d.ts +200 -0
  334. package/dist/utils/internal/requestContext.d.ts.map +1 -0
  335. package/dist/utils/internal/requestContext.js +163 -0
  336. package/dist/utils/internal/requestContext.js.map +1 -0
  337. package/dist/utils/internal/runtime.d.ts +49 -0
  338. package/dist/utils/internal/runtime.d.ts.map +1 -0
  339. package/dist/utils/internal/runtime.js +90 -0
  340. package/dist/utils/internal/runtime.js.map +1 -0
  341. package/dist/utils/internal/startupBanner.d.ts +23 -0
  342. package/dist/utils/internal/startupBanner.d.ts.map +1 -0
  343. package/dist/utils/internal/startupBanner.js +34 -0
  344. package/dist/utils/internal/startupBanner.js.map +1 -0
  345. package/dist/utils/metrics/tokenCounter.d.ts +97 -0
  346. package/dist/utils/metrics/tokenCounter.d.ts.map +1 -0
  347. package/dist/utils/metrics/tokenCounter.js +162 -0
  348. package/dist/utils/metrics/tokenCounter.js.map +1 -0
  349. package/dist/utils/network/fetchWithTimeout.d.ts +91 -0
  350. package/dist/utils/network/fetchWithTimeout.d.ts.map +1 -0
  351. package/dist/utils/network/fetchWithTimeout.js +305 -0
  352. package/dist/utils/network/fetchWithTimeout.js.map +1 -0
  353. package/dist/utils/pagination/pagination.d.ts +157 -0
  354. package/dist/utils/pagination/pagination.d.ts.map +1 -0
  355. package/dist/utils/pagination/pagination.js +191 -0
  356. package/dist/utils/pagination/pagination.js.map +1 -0
  357. package/dist/utils/parsing/csvParser.d.ts +84 -0
  358. package/dist/utils/parsing/csvParser.d.ts.map +1 -0
  359. package/dist/utils/parsing/csvParser.js +132 -0
  360. package/dist/utils/parsing/csvParser.js.map +1 -0
  361. package/dist/utils/parsing/dateParser.d.ts +103 -0
  362. package/dist/utils/parsing/dateParser.d.ts.map +1 -0
  363. package/dist/utils/parsing/dateParser.js +142 -0
  364. package/dist/utils/parsing/dateParser.js.map +1 -0
  365. package/dist/utils/parsing/frontmatterParser.d.ts +91 -0
  366. package/dist/utils/parsing/frontmatterParser.d.ts.map +1 -0
  367. package/dist/utils/parsing/frontmatterParser.js +163 -0
  368. package/dist/utils/parsing/frontmatterParser.js.map +1 -0
  369. package/dist/utils/parsing/index.d.ts +15 -0
  370. package/dist/utils/parsing/index.d.ts.map +1 -0
  371. package/dist/utils/parsing/index.js +15 -0
  372. package/dist/utils/parsing/index.js.map +1 -0
  373. package/dist/utils/parsing/jsonParser.d.ts +115 -0
  374. package/dist/utils/parsing/jsonParser.d.ts.map +1 -0
  375. package/dist/utils/parsing/jsonParser.js +177 -0
  376. package/dist/utils/parsing/jsonParser.js.map +1 -0
  377. package/dist/utils/parsing/pdfParser.d.ts +563 -0
  378. package/dist/utils/parsing/pdfParser.d.ts.map +1 -0
  379. package/dist/utils/parsing/pdfParser.js +775 -0
  380. package/dist/utils/parsing/pdfParser.js.map +1 -0
  381. package/dist/utils/parsing/thinkBlock.d.ts +31 -0
  382. package/dist/utils/parsing/thinkBlock.d.ts.map +1 -0
  383. package/dist/utils/parsing/thinkBlock.js +31 -0
  384. package/dist/utils/parsing/thinkBlock.js.map +1 -0
  385. package/dist/utils/parsing/xmlParser.d.ts +69 -0
  386. package/dist/utils/parsing/xmlParser.d.ts.map +1 -0
  387. package/dist/utils/parsing/xmlParser.js +140 -0
  388. package/dist/utils/parsing/xmlParser.js.map +1 -0
  389. package/dist/utils/parsing/yamlParser.d.ts +64 -0
  390. package/dist/utils/parsing/yamlParser.d.ts.map +1 -0
  391. package/dist/utils/parsing/yamlParser.js +129 -0
  392. package/dist/utils/parsing/yamlParser.js.map +1 -0
  393. package/dist/utils/scheduling/scheduler.d.ts +174 -0
  394. package/dist/utils/scheduling/scheduler.d.ts.map +1 -0
  395. package/dist/utils/scheduling/scheduler.js +248 -0
  396. package/dist/utils/scheduling/scheduler.js.map +1 -0
  397. package/dist/utils/security/idGenerator.d.ts +189 -0
  398. package/dist/utils/security/idGenerator.d.ts.map +1 -0
  399. package/dist/utils/security/idGenerator.js +301 -0
  400. package/dist/utils/security/idGenerator.js.map +1 -0
  401. package/dist/utils/security/index.d.ts +8 -0
  402. package/dist/utils/security/index.d.ts.map +1 -0
  403. package/dist/utils/security/index.js +8 -0
  404. package/dist/utils/security/index.js.map +1 -0
  405. package/dist/utils/security/rateLimiter.d.ts +171 -0
  406. package/dist/utils/security/rateLimiter.d.ts.map +1 -0
  407. package/dist/utils/security/rateLimiter.js +294 -0
  408. package/dist/utils/security/rateLimiter.js.map +1 -0
  409. package/dist/utils/security/sanitization.d.ts +430 -0
  410. package/dist/utils/security/sanitization.d.ts.map +1 -0
  411. package/dist/utils/security/sanitization.js +759 -0
  412. package/dist/utils/security/sanitization.js.map +1 -0
  413. package/dist/utils/telemetry/index.d.ts +12 -0
  414. package/dist/utils/telemetry/index.d.ts.map +1 -0
  415. package/dist/utils/telemetry/index.js +12 -0
  416. package/dist/utils/telemetry/index.js.map +1 -0
  417. package/dist/utils/telemetry/instrumentation.d.ts +62 -0
  418. package/dist/utils/telemetry/instrumentation.d.ts.map +1 -0
  419. package/dist/utils/telemetry/instrumentation.js +223 -0
  420. package/dist/utils/telemetry/instrumentation.js.map +1 -0
  421. package/dist/utils/telemetry/metrics.d.ts +170 -0
  422. package/dist/utils/telemetry/metrics.d.ts.map +1 -0
  423. package/dist/utils/telemetry/metrics.js +205 -0
  424. package/dist/utils/telemetry/metrics.js.map +1 -0
  425. package/dist/utils/telemetry/semconv.d.ts +147 -0
  426. package/dist/utils/telemetry/semconv.d.ts.map +1 -0
  427. package/dist/utils/telemetry/semconv.js +159 -0
  428. package/dist/utils/telemetry/semconv.js.map +1 -0
  429. package/dist/utils/telemetry/trace.d.ts +141 -0
  430. package/dist/utils/telemetry/trace.d.ts.map +1 -0
  431. package/dist/utils/telemetry/trace.js +193 -0
  432. package/dist/utils/telemetry/trace.js.map +1 -0
  433. package/dist/utils/types/guards.d.ts +209 -0
  434. package/dist/utils/types/guards.d.ts.map +1 -0
  435. package/dist/utils/types/guards.js +229 -0
  436. package/dist/utils/types/guards.js.map +1 -0
  437. package/dist/utils/types/index.d.ts +6 -0
  438. package/dist/utils/types/index.d.ts.map +1 -0
  439. package/dist/utils/types/index.js +6 -0
  440. package/dist/utils/types/index.js.map +1 -0
  441. package/dist/worker.d.ts +59 -0
  442. package/dist/worker.d.ts.map +1 -0
  443. package/dist/worker.js +216 -0
  444. package/dist/worker.js.map +1 -0
  445. package/package.json +377 -0
  446. package/skills/README.md +38 -0
  447. package/skills/add-export/SKILL.md +49 -0
  448. package/skills/add-prompt/SKILL.md +97 -0
  449. package/skills/add-provider/SKILL.md +53 -0
  450. package/skills/add-resource/SKILL.md +107 -0
  451. package/skills/add-service/SKILL.md +113 -0
  452. package/skills/add-tool/SKILL.md +110 -0
  453. package/skills/api-auth/SKILL.md +173 -0
  454. package/skills/api-config/SKILL.md +68 -0
  455. package/skills/api-context/SKILL.md +321 -0
  456. package/skills/api-errors/SKILL.md +146 -0
  457. package/skills/api-services/SKILL.md +24 -0
  458. package/skills/api-services/references/graph.md +124 -0
  459. package/skills/api-services/references/llm.md +46 -0
  460. package/skills/api-services/references/speech.md +72 -0
  461. package/skills/api-testing/SKILL.md +263 -0
  462. package/skills/api-utils/SKILL.md +106 -0
  463. package/skills/api-utils/references/formatting.md +237 -0
  464. package/skills/api-utils/references/parsing.md +263 -0
  465. package/skills/api-utils/references/security.md +226 -0
  466. package/skills/api-workers/SKILL.md +165 -0
  467. package/skills/devcheck/SKILL.md +31 -0
  468. package/skills/maintenance/SKILL.md +52 -0
  469. package/skills/migrate-mcp-ts-template/SKILL.md +131 -0
  470. package/skills/release/SKILL.md +67 -0
  471. package/skills/setup/SKILL.md +89 -0
  472. package/skills/walkthrough-init/SKILL.md +50 -0
  473. package/templates/.env.example +17 -0
  474. package/templates/AGENTS.md +113 -0
  475. package/templates/CLAUDE.md +113 -0
  476. package/templates/_tsconfig.json +33 -0
  477. package/templates/biome.template.json +43 -0
  478. package/templates/package.json +26 -0
  479. package/templates/src/index.ts +16 -0
  480. package/templates/src/mcp-server/prompts/definitions/echo.prompt.ts +19 -0
  481. package/templates/src/mcp-server/resources/definitions/echo.resource.ts +30 -0
  482. package/templates/src/mcp-server/tools/definitions/echo.tool.ts +24 -0
  483. package/templates/vitest.config.ts +12 -0
  484. package/tsconfig.base.json +44 -0
  485. package/vitest.config.base.ts +38 -0
@@ -0,0 +1,759 @@
1
+ import { JsonRpcErrorCode, McpError } from '../../types-global/errors.js';
2
+ import { logger } from '../../utils/internal/logger.js';
3
+ import { requestContextService } from '../../utils/internal/requestContext.js';
4
+ import { runtimeCaps } from '../../utils/internal/runtime.js';
5
+ import { isRecord } from '../../utils/types/guards.js';
6
+ let _sanitizeHtmlFn;
7
+ async function loadSanitizeHtml() {
8
+ _sanitizeHtmlFn ??= (await import('sanitize-html').catch(() => {
9
+ throw new McpError(JsonRpcErrorCode.ConfigurationError, 'Install "sanitize-html" to use HTML sanitization: bun add sanitize-html');
10
+ })).default;
11
+ return _sanitizeHtmlFn;
12
+ }
13
+ let _validator;
14
+ async function loadValidator() {
15
+ _validator ??= (await import('validator').catch(() => {
16
+ throw new McpError(JsonRpcErrorCode.ConfigurationError, 'Install "validator" to use input validation: bun add validator');
17
+ })).default;
18
+ return _validator;
19
+ }
20
+ // Dynamically import 'path' only in Node.js environments.
21
+ // Top-level await ensures the module is loaded before any sanitizePath call.
22
+ let pathModule;
23
+ if (runtimeCaps.isNode) {
24
+ try {
25
+ pathModule = (await import('node:path')).default;
26
+ }
27
+ catch {
28
+ // May fail in some bundlers; sanitizePath guards against undefined pathModule.
29
+ }
30
+ }
31
+ /**
32
+ * Singleton class providing input sanitization across multiple categories:
33
+ * HTML (XSS prevention), URLs, file paths (traversal prevention), JSON, numbers,
34
+ * and log-safe redaction of sensitive fields.
35
+ *
36
+ * Obtain the singleton via `Sanitization.getInstance()` or use the pre-exported
37
+ * `sanitization` constant.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * import { sanitization } from '../../utils/security/sanitization.js';
42
+ *
43
+ * const clean = await sanitization.sanitizeHtml('<script>alert(1)</script><b>hi</b>');
44
+ * // => '<b>hi</b>'
45
+ * ```
46
+ */
47
+ export class Sanitization {
48
+ /** @private */
49
+ static instance;
50
+ sensitiveFields = [
51
+ 'password',
52
+ 'token',
53
+ 'secret',
54
+ 'apiKey',
55
+ 'credential',
56
+ 'jwt',
57
+ 'ssn',
58
+ 'cvv',
59
+ 'authorization',
60
+ 'cookie',
61
+ 'clientsecret',
62
+ 'client_secret',
63
+ 'private_key',
64
+ 'privatekey',
65
+ ];
66
+ /**
67
+ * Default configuration for HTML sanitization.
68
+ * @private
69
+ */
70
+ defaultHtmlSanitizeConfig = {
71
+ allowedTags: [
72
+ // === Structure & Sectioning ===
73
+ 'div',
74
+ 'span',
75
+ 'p',
76
+ 'br',
77
+ 'hr',
78
+ 'header',
79
+ 'footer',
80
+ 'nav',
81
+ 'article',
82
+ 'section',
83
+ 'aside',
84
+ // === Headings & Text Content ===
85
+ 'h1',
86
+ 'h2',
87
+ 'h3',
88
+ 'h4',
89
+ 'h5',
90
+ 'h6',
91
+ 'strong',
92
+ 'em',
93
+ 'b',
94
+ 'i',
95
+ 'strike',
96
+ 'blockquote',
97
+ // === Code ===
98
+ 'code',
99
+ 'pre',
100
+ // === Lists ===
101
+ 'ul',
102
+ 'ol',
103
+ 'li',
104
+ // === Tables ===
105
+ 'table',
106
+ 'thead',
107
+ 'tbody',
108
+ 'tr',
109
+ 'th',
110
+ 'td',
111
+ // === Media & Links ===
112
+ 'a',
113
+ 'img',
114
+ 'figure',
115
+ 'figcaption',
116
+ ],
117
+ allowedAttributes: {
118
+ a: ['href', 'name', 'target', 'rel', 'title'],
119
+ img: ['src', 'alt', 'title', 'width', 'height', 'loading'],
120
+ // Allow data attributes, class, and id on all tags.
121
+ // Note: 'style' is intentionally excluded — sanitize-html does not sanitize
122
+ // CSS property values, so allowing it enables CSS injection (data exfiltration
123
+ // via background:url(), UI redress via positioning, content injection via
124
+ // ::before/::after). Use 'class' with external stylesheets instead.
125
+ '*': ['class', 'id', 'data-*'],
126
+ // Table-specific attributes
127
+ th: ['scope'],
128
+ td: ['colspan', 'rowspan'],
129
+ },
130
+ preserveComments: true,
131
+ // transformTags is built lazily in sanitizeHtml() when the dep is loaded
132
+ };
133
+ /** @private */
134
+ constructor() {
135
+ this.rebuildSensitiveSets();
136
+ }
137
+ normalizedSensitiveSet;
138
+ wordSensitiveSet;
139
+ /**
140
+ * Returns the singleton instance of `Sanitization`, creating it on first call.
141
+ *
142
+ * @returns The singleton `Sanitization` instance.
143
+ * @example
144
+ * ```ts
145
+ * const san = Sanitization.getInstance();
146
+ * const safe = await san.sanitizeHtml(userInput);
147
+ * ```
148
+ */
149
+ static getInstance() {
150
+ if (!Sanitization.instance) {
151
+ Sanitization.instance = new Sanitization();
152
+ }
153
+ return Sanitization.instance;
154
+ }
155
+ /**
156
+ * Extends the list of sensitive field names used by `sanitizeForLogging`.
157
+ * New names are merged with the existing list (deduplication applied, case-insensitive).
158
+ * Changes take effect immediately on subsequent `sanitizeForLogging` calls.
159
+ *
160
+ * @param fields - Field names to add to the sensitive list (e.g., `['myApiKey', 'session_id']`).
161
+ * @returns `void`
162
+ * @example
163
+ * ```ts
164
+ * sanitization.setSensitiveFields(['myApiKey', 'session_id']);
165
+ * sanitization.sanitizeForLogging({ myApiKey: 'abc123' });
166
+ * // => { myApiKey: '[REDACTED]' }
167
+ * ```
168
+ */
169
+ setSensitiveFields(fields) {
170
+ this.sensitiveFields = [
171
+ ...new Set([...this.sensitiveFields, ...fields.map((f) => f.toLowerCase())]),
172
+ ];
173
+ this.rebuildSensitiveSets();
174
+ const logContext = requestContextService.createRequestContext({
175
+ operation: 'Sanitization.setSensitiveFields',
176
+ additionalContext: {
177
+ newSensitiveFieldCount: this.sensitiveFields.length,
178
+ },
179
+ });
180
+ logger.debug('Updated sensitive fields list for log sanitization', logContext);
181
+ }
182
+ /**
183
+ * Returns a copy of the current sensitive field names list.
184
+ * All names are lowercased. Mutating the returned array has no effect on internal state.
185
+ *
186
+ * @returns Array of lowercase sensitive field name strings.
187
+ * @example
188
+ * ```ts
189
+ * const fields = sanitization.getSensitiveFields();
190
+ * // => ['password', 'token', 'secret', ...]
191
+ * ```
192
+ */
193
+ getSensitiveFields() {
194
+ return [...this.sensitiveFields];
195
+ }
196
+ /**
197
+ * Returns pino-compatible redact path patterns covering sensitive field names at three
198
+ * nesting depths: top-level, one level deep, and two levels deep.
199
+ *
200
+ * For example, the field `'token'` generates:
201
+ * - `'token'` — matches `{ token: '...' }`
202
+ * - `'*.token'` — matches `{ auth: { token: '...' } }`
203
+ * - `'*.*.token'` — matches `{ context: { auth: { token: '...' } } }`
204
+ *
205
+ * Pass the result directly to pino's `redact.paths` option.
206
+ *
207
+ * @returns Array of fast-redact-compatible path strings for use in pino's `redact.paths`.
208
+ * @example
209
+ * ```ts
210
+ * import pino from 'pino';
211
+ * const log = pino({ redact: { paths: sanitization.getSensitivePinoFields(), censor: '[REDACTED]' } });
212
+ * ```
213
+ */
214
+ getSensitivePinoFields() {
215
+ return this.sensitiveFields.flatMap((field) => [
216
+ field, // top-level: { password: '...' }
217
+ `*.${field}`, // one level deep: { auth: { token: '...' } }
218
+ `*.*.${field}`, // two levels deep: { context: { auth: { secret: '...' } } }
219
+ ]);
220
+ }
221
+ /**
222
+ * Sanitizes an HTML string using `sanitize-html`, stripping disallowed tags and attributes.
223
+ *
224
+ * This method is **async** because it lazy-loads the `sanitize-html` peer dependency on
225
+ * first call. By default, `<a>` tags receive `rel="noopener noreferrer"` automatically.
226
+ * The `style` attribute is intentionally excluded from the default allowlist to prevent
227
+ * CSS injection attacks.
228
+ *
229
+ * @param input - The HTML string to sanitize. Returns `''` immediately if falsy.
230
+ * @param config - Optional config overriding the default tag/attribute allowlists.
231
+ * If omitted, the built-in defaults are used (see `defaultHtmlSanitizeConfig`).
232
+ * @returns Promise resolving to the sanitized HTML string.
233
+ * @throws {McpError} With `ConfigurationError` if `sanitize-html` is not installed.
234
+ * @example
235
+ * ```ts
236
+ * const safe = await sanitization.sanitizeHtml('<script>alert(1)</script><b>Hello</b>');
237
+ * // => '<b>Hello</b>'
238
+ *
239
+ * const custom = await sanitization.sanitizeHtml('<div class="x"><b>ok</b></div>', {
240
+ * allowedTags: ['b'],
241
+ * });
242
+ * // => '<b>ok</b>'
243
+ * ```
244
+ */
245
+ async sanitizeHtml(input, config) {
246
+ if (!input)
247
+ return '';
248
+ const sanitizeHtmlFn = await loadSanitizeHtml();
249
+ // Build default transformTags lazily now that the dep is loaded
250
+ const defaultTransformTags = config?.transformTags ??
251
+ this.defaultHtmlSanitizeConfig.transformTags ?? {
252
+ a: sanitizeHtmlFn.simpleTransform('a', { rel: 'noopener noreferrer' }),
253
+ };
254
+ const effectiveConfig = {
255
+ allowedTags: config?.allowedTags ?? this.defaultHtmlSanitizeConfig.allowedTags,
256
+ allowedAttributes: config?.allowedAttributes ?? this.defaultHtmlSanitizeConfig.allowedAttributes,
257
+ transformTags: defaultTransformTags,
258
+ preserveComments: config?.preserveComments ?? this.defaultHtmlSanitizeConfig.preserveComments,
259
+ };
260
+ const options = {
261
+ allowedTags: effectiveConfig.allowedTags,
262
+ allowedAttributes: effectiveConfig.allowedAttributes,
263
+ transformTags: effectiveConfig.transformTags,
264
+ };
265
+ if (effectiveConfig.preserveComments) {
266
+ const baseTags = Array.isArray(options.allowedTags) ? options.allowedTags : [];
267
+ options.allowedTags = [...baseTags, '!--'];
268
+ }
269
+ return sanitizeHtmlFn(input, options);
270
+ }
271
+ /**
272
+ * Sanitizes a string according to its intended usage context.
273
+ *
274
+ * This method is **async** because it lazy-loads `sanitize-html` and/or `validator`
275
+ * depending on the requested context.
276
+ *
277
+ * | `context` | Behavior |
278
+ * |----------------|----------|
279
+ * | `'text'` | Strips all HTML tags and attributes (default). |
280
+ * | `'html'` | Runs full HTML sanitization via `sanitizeHtml` (respects `allowedTags`/`allowedAttributes`). |
281
+ * | `'attribute'` | Strips all tags and attributes — safe for use inside an HTML attribute value. |
282
+ * | `'url'` | Validates the URL with `validator.isURL` (http/https only); returns `''` if invalid. |
283
+ * | `'javascript'` | **Disallowed.** Always throws `McpError`. |
284
+ *
285
+ * @param input - The string to sanitize. Returns `''` immediately if falsy.
286
+ * @param options - Context and optional allowlist overrides.
287
+ * @returns Promise resolving to the sanitized string, or `''` for invalid URLs.
288
+ * @throws {McpError} With `ValidationError` if `context` is `'javascript'`.
289
+ * @throws {McpError} With `ConfigurationError` if a required peer dep is not installed.
290
+ * @example
291
+ * ```ts
292
+ * await sanitization.sanitizeString('<b>hello</b>', { context: 'text' });
293
+ * // => 'hello'
294
+ *
295
+ * await sanitization.sanitizeString('https://example.com', { context: 'url' });
296
+ * // => 'https://example.com'
297
+ *
298
+ * await sanitization.sanitizeString('javascript:alert(1)', { context: 'url' });
299
+ * // => '' (logged as warning, not thrown)
300
+ * ```
301
+ */
302
+ async sanitizeString(input, options = {}) {
303
+ if (!input)
304
+ return '';
305
+ const context = options.context ?? 'text';
306
+ switch (context) {
307
+ case 'html': {
308
+ const config = {};
309
+ if (options.allowedTags) {
310
+ config.allowedTags = options.allowedTags;
311
+ }
312
+ if (options.allowedAttributes) {
313
+ config.allowedAttributes = this.convertAttributesFormat(options.allowedAttributes);
314
+ }
315
+ return await this.sanitizeHtml(input, config);
316
+ }
317
+ case 'attribute': {
318
+ const sanitizeHtmlFn = await loadSanitizeHtml();
319
+ return sanitizeHtmlFn(input, { allowedTags: [], allowedAttributes: {} });
320
+ }
321
+ case 'url': {
322
+ const v = await loadValidator();
323
+ if (!v.isURL(input, {
324
+ protocols: ['http', 'https'],
325
+ require_protocol: true,
326
+ require_host: true,
327
+ })) {
328
+ logger.warning('Potentially invalid URL detected during string sanitization (context: url)', requestContextService.createRequestContext({
329
+ operation: 'Sanitization.sanitizeString.urlWarning',
330
+ additionalContext: { invalidUrlAttempt: input },
331
+ }));
332
+ return '';
333
+ }
334
+ return v.trim(input);
335
+ }
336
+ case 'javascript':
337
+ logger.error('Attempted JavaScript sanitization via sanitizeString, which is disallowed.', requestContextService.createRequestContext({
338
+ operation: 'Sanitization.sanitizeString.jsAttempt',
339
+ additionalContext: { inputSnippet: input.substring(0, 50) },
340
+ }));
341
+ throw new McpError(JsonRpcErrorCode.ValidationError, 'JavaScript sanitization is not supported through sanitizeString due to security risks.');
342
+ default: {
343
+ const sanitizeHtmlFn = await loadSanitizeHtml();
344
+ return sanitizeHtmlFn(input, { allowedTags: [], allowedAttributes: {} });
345
+ }
346
+ }
347
+ }
348
+ /**
349
+ * Converts attribute format for `sanitizeHtml`.
350
+ * @param attrs - Attributes in `{ tagName: ['attr1'] }` format.
351
+ * @returns Attributes in `sanitize-html` expected format.
352
+ * @private
353
+ */
354
+ convertAttributesFormat(attrs) {
355
+ return attrs;
356
+ }
357
+ /**
358
+ * Validates and sanitizes a URL string.
359
+ *
360
+ * This method is **async** because it lazy-loads the `validator` peer dependency on first call.
361
+ *
362
+ * Validation requires a protocol and host. Even if a protocol appears in `allowedProtocols`,
363
+ * the pseudo-protocols `javascript:`, `data:`, and `vbscript:` are always rejected.
364
+ *
365
+ * @param input - The URL string to sanitize. Leading/trailing whitespace is trimmed.
366
+ * @param allowedProtocols - URL schemes that are permitted. Defaults to `['http', 'https']`.
367
+ * @returns Promise resolving to the trimmed, validated URL string.
368
+ * @throws {McpError} With `ValidationError` if the URL is invalid, uses a disallowed protocol,
369
+ * or uses a blocked pseudo-protocol (`javascript:`, `data:`, `vbscript:`).
370
+ * @throws {McpError} With `ConfigurationError` if `validator` is not installed.
371
+ * @example
372
+ * ```ts
373
+ * await sanitization.sanitizeUrl('https://example.com/path');
374
+ * // => 'https://example.com/path'
375
+ *
376
+ * await sanitization.sanitizeUrl('ftp://files.example.com', ['ftp', 'sftp']);
377
+ * // => 'ftp://files.example.com'
378
+ *
379
+ * await sanitization.sanitizeUrl('javascript:alert(1)');
380
+ * // throws McpError (ValidationError)
381
+ * ```
382
+ */
383
+ async sanitizeUrl(input, allowedProtocols = ['http', 'https']) {
384
+ try {
385
+ const v = await loadValidator();
386
+ const trimmedInput = input.trim();
387
+ if (!v.isURL(trimmedInput, {
388
+ protocols: allowedProtocols,
389
+ require_protocol: true,
390
+ require_host: true,
391
+ })) {
392
+ throw new Error('Invalid URL format or protocol not in allowed list.');
393
+ }
394
+ const lowercasedInput = trimmedInput.toLowerCase();
395
+ if (lowercasedInput.startsWith('javascript:') ||
396
+ lowercasedInput.startsWith('data:') ||
397
+ lowercasedInput.startsWith('vbscript:')) {
398
+ throw new Error('Disallowed pseudo-protocol (javascript:, data:, or vbscript:) in URL.');
399
+ }
400
+ return trimmedInput;
401
+ }
402
+ catch (error) {
403
+ throw new McpError(JsonRpcErrorCode.ValidationError, error instanceof Error ? error.message : 'Invalid or unsafe URL provided.', { input });
404
+ }
405
+ }
406
+ /**
407
+ * Sanitizes a file path, preventing path traversal attacks and normalizing format.
408
+ *
409
+ * This method is **synchronous** and only available in Node.js (uses `node:path`).
410
+ * Calling it in a non-Node.js environment (e.g., Cloudflare Workers) throws immediately.
411
+ *
412
+ * Traversal detection:
413
+ * - With `rootDir`: resolves the full path and asserts it starts within the root.
414
+ * - Without `rootDir`: resolves the relative path against CWD and asserts containment.
415
+ * - Null bytes (`\0`) in paths are always rejected.
416
+ *
417
+ * @param input - The file path string to sanitize.
418
+ * @param options - Options controlling absolute path permission, root directory, and POSIX normalization.
419
+ * @returns A `SanitizedPathInfo` object with the sanitized path and operation metadata.
420
+ * @throws {McpError} With `InternalError` if called outside a Node.js environment.
421
+ * @throws {McpError} With `ValidationError` if the path is empty, contains a null byte,
422
+ * attempts traversal beyond `rootDir` or CWD, or is absolute when `allowAbsolute` is `false`.
423
+ * @example
424
+ * ```ts
425
+ * const result = sanitization.sanitizePath('../../etc/passwd', { rootDir: '/app/data' });
426
+ * // throws McpError (path traversal detected)
427
+ *
428
+ * const result = sanitization.sanitizePath('uploads/file.txt', { rootDir: '/app/data' });
429
+ * // => { sanitizedPath: 'uploads/file.txt', wasAbsolute: false, convertedToRelative: false, ... }
430
+ *
431
+ * const result = sanitization.sanitizePath('C:\\Users\\foo\\bar', { toPosix: true, allowAbsolute: true });
432
+ * // => { sanitizedPath: 'C:/Users/foo/bar', wasAbsolute: true, ... }
433
+ * ```
434
+ */
435
+ sanitizePath(input, options = {}) {
436
+ if (!runtimeCaps.isNode || !pathModule) {
437
+ throw new McpError(JsonRpcErrorCode.InternalError, 'File-based path sanitization is not supported in this environment.');
438
+ }
439
+ const path = pathModule;
440
+ const originalInput = input;
441
+ const resolvedRootDir = options.rootDir ? path.resolve(options.rootDir) : undefined;
442
+ const effectiveOptions = {
443
+ toPosix: options.toPosix ?? false,
444
+ allowAbsolute: options.allowAbsolute ?? false,
445
+ ...(resolvedRootDir && { rootDir: resolvedRootDir }),
446
+ };
447
+ let wasAbsoluteInitially = false;
448
+ try {
449
+ if (!input || typeof input !== 'string')
450
+ throw new Error('Invalid path input: must be a non-empty string.');
451
+ if (input.includes('\0'))
452
+ throw new Error('Path contains null byte, which is disallowed.');
453
+ let normalized = path.normalize(input);
454
+ wasAbsoluteInitially = path.isAbsolute(normalized);
455
+ if (effectiveOptions.toPosix) {
456
+ normalized = normalized.replace(/\\/g, '/');
457
+ }
458
+ let finalSanitizedPath;
459
+ if (resolvedRootDir) {
460
+ const fullPath = path.resolve(resolvedRootDir, normalized);
461
+ if (!fullPath.startsWith(resolvedRootDir + path.sep) && fullPath !== resolvedRootDir) {
462
+ throw new Error('Path traversal detected: attempts to escape the defined root directory.');
463
+ }
464
+ finalSanitizedPath = path.relative(resolvedRootDir, fullPath);
465
+ finalSanitizedPath = finalSanitizedPath === '' ? '.' : finalSanitizedPath;
466
+ if (path.isAbsolute(finalSanitizedPath) && !effectiveOptions.allowAbsolute) {
467
+ throw new Error('Path resolved to absolute outside root when absolute paths are disallowed.');
468
+ }
469
+ }
470
+ else {
471
+ if (path.isAbsolute(normalized)) {
472
+ if (!effectiveOptions.allowAbsolute) {
473
+ throw new Error('Absolute paths are disallowed by current options.');
474
+ }
475
+ else {
476
+ finalSanitizedPath = normalized;
477
+ }
478
+ }
479
+ else {
480
+ const resolvedAgainstCwd = path.resolve(normalized);
481
+ const currentWorkingDir = path.resolve('.');
482
+ if (!resolvedAgainstCwd.startsWith(currentWorkingDir + path.sep) &&
483
+ resolvedAgainstCwd !== currentWorkingDir) {
484
+ throw new Error('Relative path traversal detected (escapes current working directory context).');
485
+ }
486
+ finalSanitizedPath = normalized;
487
+ }
488
+ }
489
+ return {
490
+ sanitizedPath: finalSanitizedPath,
491
+ originalInput,
492
+ wasAbsolute: wasAbsoluteInitially,
493
+ convertedToRelative: wasAbsoluteInitially &&
494
+ !path.isAbsolute(finalSanitizedPath) &&
495
+ !effectiveOptions.allowAbsolute,
496
+ optionsUsed: effectiveOptions,
497
+ };
498
+ }
499
+ catch (error) {
500
+ logger.warning('Path sanitization error', requestContextService.createRequestContext({
501
+ operation: 'Sanitization.sanitizePath.error',
502
+ additionalContext: {
503
+ originalPathInput: originalInput,
504
+ pathOptionsUsed: effectiveOptions,
505
+ errorMessage: error instanceof Error ? error.message : String(error),
506
+ },
507
+ }));
508
+ throw new McpError(JsonRpcErrorCode.ValidationError, error instanceof Error ? error.message : 'Invalid or unsafe path provided.', { input: originalInput });
509
+ }
510
+ }
511
+ /**
512
+ * Validates and parses a JSON string, with an optional maximum byte-size guard.
513
+ *
514
+ * This method is **synchronous**. Byte length is computed via `Buffer.byteLength` in
515
+ * Node.js, `TextEncoder` in environments that support it, or falls back to `string.length`.
516
+ *
517
+ * @template T - Expected type of the parsed value. Defaults to `unknown`.
518
+ * @param input - The JSON string to validate and parse. Must be a `string`.
519
+ * @param maxSize - Optional maximum allowed UTF-8 byte length. Throws if exceeded.
520
+ * @returns The parsed JavaScript value cast to `T`.
521
+ * @throws {McpError} With `ValidationError` if:
522
+ * - `input` is not a string
523
+ * - `input` exceeds `maxSize` bytes
524
+ * - `input` is not valid JSON
525
+ * @example
526
+ * ```ts
527
+ * const obj = sanitization.sanitizeJson<{ id: number }>('{"id":1}');
528
+ * // => { id: 1 }
529
+ *
530
+ * sanitization.sanitizeJson('{"big":"value"}', 5);
531
+ * // throws McpError (exceeds maxSize)
532
+ *
533
+ * sanitization.sanitizeJson('{bad json}');
534
+ * // throws McpError (invalid JSON)
535
+ * ```
536
+ */
537
+ sanitizeJson(input, maxSize) {
538
+ try {
539
+ if (typeof input !== 'string')
540
+ throw new Error('Invalid input: expected a JSON string.');
541
+ // Cross-environment byte length computation
542
+ const computeBytes = (s) => {
543
+ if (runtimeCaps.hasBuffer && typeof Buffer.byteLength === 'function') {
544
+ return Buffer.byteLength(s, 'utf8');
545
+ }
546
+ if (runtimeCaps.hasTextEncoder) {
547
+ return new TextEncoder().encode(s).length;
548
+ }
549
+ return s.length;
550
+ };
551
+ if (maxSize !== undefined && computeBytes(input) > maxSize) {
552
+ throw new McpError(JsonRpcErrorCode.ValidationError, `JSON string exceeds maximum allowed size of ${maxSize} bytes.`, { actualSize: computeBytes(input), maxSize });
553
+ }
554
+ return JSON.parse(input);
555
+ }
556
+ catch (error) {
557
+ if (error instanceof McpError)
558
+ throw error;
559
+ throw new McpError(JsonRpcErrorCode.ValidationError, error instanceof Error ? error.message : 'Invalid JSON format.', {
560
+ inputPreview: input.length > 100 ? `${input.substring(0, 100)}...` : input,
561
+ });
562
+ }
563
+ }
564
+ /**
565
+ * Validates a numeric input and optionally clamps it to a range.
566
+ *
567
+ * This method is **async** because string inputs are validated using the `validator` peer
568
+ * dependency, which is lazy-loaded on first call. Numeric inputs bypass the lazy load.
569
+ *
570
+ * - String inputs: trimmed and checked with `validator.isNumeric`, then parsed with `parseFloat`.
571
+ * - Number inputs: used directly.
572
+ * - `NaN` and `Infinity` are always rejected.
573
+ * - If `min` or `max` are provided, the value is silently clamped (a debug log is emitted).
574
+ *
575
+ * @param input - The number or numeric string to validate.
576
+ * @param min - Inclusive lower bound. If the value is below this, it is clamped to `min`.
577
+ * @param max - Inclusive upper bound. If the value is above this, it is clamped to `max`.
578
+ * @returns Promise resolving to the validated (and potentially clamped) number.
579
+ * @throws {McpError} With `ValidationError` if the input is not numeric, is `NaN`, or is `Infinity`.
580
+ * @throws {McpError} With `ConfigurationError` if `validator` is not installed (string input only).
581
+ * @example
582
+ * ```ts
583
+ * await sanitization.sanitizeNumber('42.5');
584
+ * // => 42.5
585
+ *
586
+ * await sanitization.sanitizeNumber(150, 0, 100);
587
+ * // => 100 (clamped to max)
588
+ *
589
+ * await sanitization.sanitizeNumber('abc');
590
+ * // throws McpError (ValidationError)
591
+ * ```
592
+ */
593
+ async sanitizeNumber(input, min, max) {
594
+ let value;
595
+ if (typeof input === 'string') {
596
+ const v = await loadValidator();
597
+ const trimmedInput = input.trim();
598
+ if (trimmedInput === '' || !v.isNumeric(trimmedInput)) {
599
+ throw new McpError(JsonRpcErrorCode.ValidationError, 'Invalid number format: input is empty or not numeric.', { input });
600
+ }
601
+ value = parseFloat(trimmedInput);
602
+ }
603
+ else if (typeof input === 'number') {
604
+ value = input;
605
+ }
606
+ else {
607
+ throw new McpError(JsonRpcErrorCode.ValidationError, 'Invalid input type: expected number or string.', { input: String(input) });
608
+ }
609
+ if (Number.isNaN(value) || !Number.isFinite(value)) {
610
+ throw new McpError(JsonRpcErrorCode.ValidationError, 'Invalid number value (NaN or Infinity).', { input });
611
+ }
612
+ let clamped = false;
613
+ const originalValueForLog = value;
614
+ if (min !== undefined && value < min) {
615
+ value = min;
616
+ clamped = true;
617
+ }
618
+ if (max !== undefined && value > max) {
619
+ value = max;
620
+ clamped = true;
621
+ }
622
+ if (clamped) {
623
+ logger.debug('Number clamped to range.', requestContextService.createRequestContext({
624
+ operation: 'Sanitization.sanitizeNumber.clamped',
625
+ additionalContext: {
626
+ originalInput: String(input),
627
+ parsedValue: originalValueForLog,
628
+ minValue: min,
629
+ maxValue: max,
630
+ clampedValue: value,
631
+ },
632
+ }));
633
+ }
634
+ return value;
635
+ }
636
+ /**
637
+ * Produces a log-safe deep clone of `input` with sensitive field values replaced by `'[REDACTED]'`.
638
+ *
639
+ * This method is **synchronous**. It uses `structuredClone` for deep cloning. Sensitive field
640
+ * detection combines two strategies:
641
+ * - **Exact match**: the normalized key (lowercased, non-alphanumeric stripped) matches a
642
+ * sensitive field name.
643
+ * - **Word match**: splitting the key by camelCase/snake_case/kebab-case tokens and checking
644
+ * each token against the sensitive word set.
645
+ *
646
+ * Non-object/non-array inputs (primitives, `null`) are returned as-is without cloning.
647
+ * If `structuredClone` itself throws (e.g., circular reference, uncloneable type), the method
648
+ * returns the string `'[Log Sanitization Failed]'` and emits an error log rather than throwing.
649
+ *
650
+ * @param input - The value to sanitize. Non-objects are returned unchanged.
651
+ * @returns A sanitized deep clone of `input` (with sensitive values redacted),
652
+ * the original primitive if not an object, or `'[Log Sanitization Failed]'` on clone error.
653
+ * @example
654
+ * ```ts
655
+ * sanitization.sanitizeForLogging({ user: 'alice', password: 'secret', nested: { token: 'abc' } });
656
+ * // => { user: 'alice', password: '[REDACTED]', nested: { token: '[REDACTED]' } }
657
+ *
658
+ * sanitization.sanitizeForLogging('just a string');
659
+ * // => 'just a string' (returned as-is)
660
+ * ```
661
+ */
662
+ sanitizeForLogging(input) {
663
+ try {
664
+ if (!input || typeof input !== 'object')
665
+ return input;
666
+ const clonedInput = structuredClone(input);
667
+ this.redactSensitiveFields(clonedInput);
668
+ return clonedInput;
669
+ }
670
+ catch (error) {
671
+ logger.error('Error during log sanitization, returning placeholder.', requestContextService.createRequestContext({
672
+ operation: 'Sanitization.sanitizeForLogging.error',
673
+ additionalContext: {
674
+ errorMessage: error instanceof Error ? error.message : String(error),
675
+ },
676
+ }));
677
+ return '[Log Sanitization Failed]';
678
+ }
679
+ }
680
+ /**
681
+ * Recursively redacts sensitive fields in an object or array in place.
682
+ * @param obj - The object or array to redact.
683
+ * @private
684
+ */
685
+ redactSensitiveFields(obj) {
686
+ if (!obj || typeof obj !== 'object')
687
+ return;
688
+ if (Array.isArray(obj)) {
689
+ for (const item of obj)
690
+ this.redactSensitiveFields(item);
691
+ return;
692
+ }
693
+ // Type guard ensures obj is a Record<string, unknown>
694
+ if (!isRecord(obj))
695
+ return;
696
+ for (const key in obj) {
697
+ if (Object.hasOwn(obj, key)) {
698
+ const value = obj[key];
699
+ const normalizedKey = Sanitization.normalizeName(key);
700
+ // Split into words for token-based matching (camelCase, snake_case, kebab-case)
701
+ const keyWords = key
702
+ .replace(/([A-Z])/g, ' $1')
703
+ .toLowerCase()
704
+ .split(/[\s_-]+/)
705
+ .filter(Boolean);
706
+ const isExactSensitive = this.normalizedSensitiveSet.has(normalizedKey);
707
+ const isWordSensitive = keyWords.some((w) => this.wordSensitiveSet.has(w));
708
+ const isSensitive = isExactSensitive || isWordSensitive;
709
+ if (isSensitive) {
710
+ obj[key] = '[REDACTED]';
711
+ }
712
+ else if (value && typeof value === 'object') {
713
+ this.redactSensitiveFields(value);
714
+ }
715
+ }
716
+ }
717
+ }
718
+ /**
719
+ * Normalizes a field name for sensitive-key lookup by lowercasing and stripping
720
+ * all non-alphanumeric characters. Used for exact-match detection in `redactSensitiveFields`.
721
+ * @param str - The raw field name string.
722
+ * @returns Lowercased alphanumeric-only version of `str`.
723
+ * @private
724
+ */
725
+ static normalizeName(str) {
726
+ return str.toLowerCase().replace(/[^a-z0-9]/g, '');
727
+ }
728
+ rebuildSensitiveSets() {
729
+ this.normalizedSensitiveSet = new Set(this.sensitiveFields.map((f) => Sanitization.normalizeName(f)).filter(Boolean));
730
+ this.wordSensitiveSet = new Set(this.sensitiveFields.map((f) => f.toLowerCase()).filter(Boolean));
731
+ }
732
+ }
733
+ /**
734
+ * Pre-constructed singleton instance of `Sanitization`.
735
+ * Use this for all input sanitization tasks rather than calling `Sanitization.getInstance()` directly.
736
+ *
737
+ * @example
738
+ * ```ts
739
+ * import { sanitization } from '../../utils/security/sanitization.js';
740
+ * const safe = await sanitization.sanitizeHtml(userHtml);
741
+ * ```
742
+ */
743
+ export const sanitization = Sanitization.getInstance();
744
+ /**
745
+ * Convenience wrapper around `sanitization.sanitizeForLogging`.
746
+ * Produces a log-safe deep clone of `input` with sensitive field values replaced by `'[REDACTED]'`.
747
+ *
748
+ * @param input - The value to sanitize. Non-objects are returned unchanged.
749
+ * @returns A sanitized deep clone of `input`, the original primitive if not an object,
750
+ * or `'[Log Sanitization Failed]'` on clone error.
751
+ * @example
752
+ * ```ts
753
+ * import { sanitizeInputForLogging } from '../../utils/security/sanitization.js';
754
+ * logger.info('Request', sanitizeInputForLogging({ user: 'alice', token: 'secret' }));
755
+ * // logs: { user: 'alice', token: '[REDACTED]' }
756
+ * ```
757
+ */
758
+ export const sanitizeInputForLogging = (input) => sanitization.sanitizeForLogging(input);
759
+ //# sourceMappingURL=sanitization.js.map