gitx.do 0.1.0 → 0.1.2

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 (344) hide show
  1. package/README.md +40 -353
  2. package/dist/do/logger.d.ts +50 -0
  3. package/dist/do/logger.d.ts.map +1 -0
  4. package/dist/do/logger.js +122 -0
  5. package/dist/do/logger.js.map +1 -0
  6. package/dist/{durable-object → do}/schema.d.ts +3 -3
  7. package/dist/do/schema.d.ts.map +1 -0
  8. package/dist/{durable-object → do}/schema.js +4 -3
  9. package/dist/do/schema.js.map +1 -0
  10. package/dist/do/types.d.ts +267 -0
  11. package/dist/do/types.d.ts.map +1 -0
  12. package/dist/do/types.js +62 -0
  13. package/dist/do/types.js.map +1 -0
  14. package/dist/index.d.ts +15 -415
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +31 -483
  17. package/dist/index.js.map +1 -1
  18. package/package.json +13 -21
  19. package/dist/cli/commands/add.d.ts +0 -174
  20. package/dist/cli/commands/add.d.ts.map +0 -1
  21. package/dist/cli/commands/add.js +0 -131
  22. package/dist/cli/commands/add.js.map +0 -1
  23. package/dist/cli/commands/blame.d.ts +0 -259
  24. package/dist/cli/commands/blame.d.ts.map +0 -1
  25. package/dist/cli/commands/blame.js +0 -609
  26. package/dist/cli/commands/blame.js.map +0 -1
  27. package/dist/cli/commands/branch.d.ts +0 -249
  28. package/dist/cli/commands/branch.d.ts.map +0 -1
  29. package/dist/cli/commands/branch.js +0 -693
  30. package/dist/cli/commands/branch.js.map +0 -1
  31. package/dist/cli/commands/commit.d.ts +0 -182
  32. package/dist/cli/commands/commit.d.ts.map +0 -1
  33. package/dist/cli/commands/commit.js +0 -437
  34. package/dist/cli/commands/commit.js.map +0 -1
  35. package/dist/cli/commands/diff.d.ts +0 -464
  36. package/dist/cli/commands/diff.d.ts.map +0 -1
  37. package/dist/cli/commands/diff.js +0 -958
  38. package/dist/cli/commands/diff.js.map +0 -1
  39. package/dist/cli/commands/log.d.ts +0 -239
  40. package/dist/cli/commands/log.d.ts.map +0 -1
  41. package/dist/cli/commands/log.js +0 -535
  42. package/dist/cli/commands/log.js.map +0 -1
  43. package/dist/cli/commands/merge.d.ts +0 -106
  44. package/dist/cli/commands/merge.d.ts.map +0 -1
  45. package/dist/cli/commands/merge.js +0 -55
  46. package/dist/cli/commands/merge.js.map +0 -1
  47. package/dist/cli/commands/review.d.ts +0 -457
  48. package/dist/cli/commands/review.d.ts.map +0 -1
  49. package/dist/cli/commands/review.js +0 -533
  50. package/dist/cli/commands/review.js.map +0 -1
  51. package/dist/cli/commands/status.d.ts +0 -269
  52. package/dist/cli/commands/status.d.ts.map +0 -1
  53. package/dist/cli/commands/status.js +0 -493
  54. package/dist/cli/commands/status.js.map +0 -1
  55. package/dist/cli/commands/web.d.ts +0 -199
  56. package/dist/cli/commands/web.d.ts.map +0 -1
  57. package/dist/cli/commands/web.js +0 -696
  58. package/dist/cli/commands/web.js.map +0 -1
  59. package/dist/cli/fs-adapter.d.ts +0 -656
  60. package/dist/cli/fs-adapter.d.ts.map +0 -1
  61. package/dist/cli/fs-adapter.js +0 -1179
  62. package/dist/cli/fs-adapter.js.map +0 -1
  63. package/dist/cli/fsx-cli-adapter.d.ts +0 -359
  64. package/dist/cli/fsx-cli-adapter.d.ts.map +0 -1
  65. package/dist/cli/fsx-cli-adapter.js +0 -619
  66. package/dist/cli/fsx-cli-adapter.js.map +0 -1
  67. package/dist/cli/index.d.ts +0 -387
  68. package/dist/cli/index.d.ts.map +0 -1
  69. package/dist/cli/index.js +0 -523
  70. package/dist/cli/index.js.map +0 -1
  71. package/dist/cli/ui/components/DiffView.d.ts +0 -7
  72. package/dist/cli/ui/components/DiffView.d.ts.map +0 -1
  73. package/dist/cli/ui/components/DiffView.js +0 -11
  74. package/dist/cli/ui/components/DiffView.js.map +0 -1
  75. package/dist/cli/ui/components/ErrorDisplay.d.ts +0 -6
  76. package/dist/cli/ui/components/ErrorDisplay.d.ts.map +0 -1
  77. package/dist/cli/ui/components/ErrorDisplay.js +0 -11
  78. package/dist/cli/ui/components/ErrorDisplay.js.map +0 -1
  79. package/dist/cli/ui/components/FuzzySearch.d.ts +0 -9
  80. package/dist/cli/ui/components/FuzzySearch.d.ts.map +0 -1
  81. package/dist/cli/ui/components/FuzzySearch.js +0 -12
  82. package/dist/cli/ui/components/FuzzySearch.js.map +0 -1
  83. package/dist/cli/ui/components/LoadingSpinner.d.ts +0 -6
  84. package/dist/cli/ui/components/LoadingSpinner.d.ts.map +0 -1
  85. package/dist/cli/ui/components/LoadingSpinner.js +0 -10
  86. package/dist/cli/ui/components/LoadingSpinner.js.map +0 -1
  87. package/dist/cli/ui/components/NavigationList.d.ts +0 -9
  88. package/dist/cli/ui/components/NavigationList.d.ts.map +0 -1
  89. package/dist/cli/ui/components/NavigationList.js +0 -11
  90. package/dist/cli/ui/components/NavigationList.js.map +0 -1
  91. package/dist/cli/ui/components/ScrollableContent.d.ts +0 -8
  92. package/dist/cli/ui/components/ScrollableContent.d.ts.map +0 -1
  93. package/dist/cli/ui/components/ScrollableContent.js +0 -11
  94. package/dist/cli/ui/components/ScrollableContent.js.map +0 -1
  95. package/dist/cli/ui/components/index.d.ts +0 -7
  96. package/dist/cli/ui/components/index.d.ts.map +0 -1
  97. package/dist/cli/ui/components/index.js +0 -9
  98. package/dist/cli/ui/components/index.js.map +0 -1
  99. package/dist/cli/ui/terminal-ui.d.ts +0 -52
  100. package/dist/cli/ui/terminal-ui.d.ts.map +0 -1
  101. package/dist/cli/ui/terminal-ui.js +0 -121
  102. package/dist/cli/ui/terminal-ui.js.map +0 -1
  103. package/dist/do/BashModule.d.ts +0 -871
  104. package/dist/do/BashModule.d.ts.map +0 -1
  105. package/dist/do/BashModule.js +0 -1143
  106. package/dist/do/BashModule.js.map +0 -1
  107. package/dist/do/FsModule.d.ts +0 -601
  108. package/dist/do/FsModule.d.ts.map +0 -1
  109. package/dist/do/FsModule.js +0 -1120
  110. package/dist/do/FsModule.js.map +0 -1
  111. package/dist/do/GitModule.d.ts +0 -635
  112. package/dist/do/GitModule.d.ts.map +0 -1
  113. package/dist/do/GitModule.js +0 -781
  114. package/dist/do/GitModule.js.map +0 -1
  115. package/dist/do/GitRepoDO.d.ts +0 -281
  116. package/dist/do/GitRepoDO.d.ts.map +0 -1
  117. package/dist/do/GitRepoDO.js +0 -479
  118. package/dist/do/GitRepoDO.js.map +0 -1
  119. package/dist/do/bash-ast.d.ts +0 -246
  120. package/dist/do/bash-ast.d.ts.map +0 -1
  121. package/dist/do/bash-ast.js +0 -888
  122. package/dist/do/bash-ast.js.map +0 -1
  123. package/dist/do/container-executor.d.ts +0 -491
  124. package/dist/do/container-executor.d.ts.map +0 -1
  125. package/dist/do/container-executor.js +0 -730
  126. package/dist/do/container-executor.js.map +0 -1
  127. package/dist/do/index.d.ts +0 -53
  128. package/dist/do/index.d.ts.map +0 -1
  129. package/dist/do/index.js +0 -91
  130. package/dist/do/index.js.map +0 -1
  131. package/dist/do/tiered-storage.d.ts +0 -403
  132. package/dist/do/tiered-storage.d.ts.map +0 -1
  133. package/dist/do/tiered-storage.js +0 -689
  134. package/dist/do/tiered-storage.js.map +0 -1
  135. package/dist/do/withBash.d.ts +0 -231
  136. package/dist/do/withBash.d.ts.map +0 -1
  137. package/dist/do/withBash.js +0 -244
  138. package/dist/do/withBash.js.map +0 -1
  139. package/dist/do/withFs.d.ts +0 -237
  140. package/dist/do/withFs.d.ts.map +0 -1
  141. package/dist/do/withFs.js +0 -387
  142. package/dist/do/withFs.js.map +0 -1
  143. package/dist/do/withGit.d.ts +0 -180
  144. package/dist/do/withGit.d.ts.map +0 -1
  145. package/dist/do/withGit.js +0 -271
  146. package/dist/do/withGit.js.map +0 -1
  147. package/dist/durable-object/object-store.d.ts +0 -633
  148. package/dist/durable-object/object-store.d.ts.map +0 -1
  149. package/dist/durable-object/object-store.js +0 -1161
  150. package/dist/durable-object/object-store.js.map +0 -1
  151. package/dist/durable-object/schema.d.ts.map +0 -1
  152. package/dist/durable-object/schema.js.map +0 -1
  153. package/dist/durable-object/wal.d.ts +0 -416
  154. package/dist/durable-object/wal.d.ts.map +0 -1
  155. package/dist/durable-object/wal.js +0 -445
  156. package/dist/durable-object/wal.js.map +0 -1
  157. package/dist/mcp/adapter.d.ts +0 -772
  158. package/dist/mcp/adapter.d.ts.map +0 -1
  159. package/dist/mcp/adapter.js +0 -895
  160. package/dist/mcp/adapter.js.map +0 -1
  161. package/dist/mcp/sandbox/miniflare-evaluator.d.ts +0 -22
  162. package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +0 -1
  163. package/dist/mcp/sandbox/miniflare-evaluator.js +0 -140
  164. package/dist/mcp/sandbox/miniflare-evaluator.js.map +0 -1
  165. package/dist/mcp/sandbox/object-store-proxy.d.ts +0 -32
  166. package/dist/mcp/sandbox/object-store-proxy.d.ts.map +0 -1
  167. package/dist/mcp/sandbox/object-store-proxy.js +0 -30
  168. package/dist/mcp/sandbox/object-store-proxy.js.map +0 -1
  169. package/dist/mcp/sandbox/template.d.ts +0 -17
  170. package/dist/mcp/sandbox/template.d.ts.map +0 -1
  171. package/dist/mcp/sandbox/template.js +0 -71
  172. package/dist/mcp/sandbox/template.js.map +0 -1
  173. package/dist/mcp/sandbox.d.ts +0 -764
  174. package/dist/mcp/sandbox.d.ts.map +0 -1
  175. package/dist/mcp/sandbox.js +0 -1362
  176. package/dist/mcp/sandbox.js.map +0 -1
  177. package/dist/mcp/sdk-adapter.d.ts +0 -835
  178. package/dist/mcp/sdk-adapter.d.ts.map +0 -1
  179. package/dist/mcp/sdk-adapter.js +0 -974
  180. package/dist/mcp/sdk-adapter.js.map +0 -1
  181. package/dist/mcp/tools/do.d.ts +0 -32
  182. package/dist/mcp/tools/do.d.ts.map +0 -1
  183. package/dist/mcp/tools/do.js +0 -115
  184. package/dist/mcp/tools/do.js.map +0 -1
  185. package/dist/mcp/tools.d.ts +0 -548
  186. package/dist/mcp/tools.d.ts.map +0 -1
  187. package/dist/mcp/tools.js +0 -1934
  188. package/dist/mcp/tools.js.map +0 -1
  189. package/dist/ops/blame.d.ts +0 -551
  190. package/dist/ops/blame.d.ts.map +0 -1
  191. package/dist/ops/blame.js +0 -1037
  192. package/dist/ops/blame.js.map +0 -1
  193. package/dist/ops/branch.d.ts +0 -766
  194. package/dist/ops/branch.d.ts.map +0 -1
  195. package/dist/ops/branch.js +0 -950
  196. package/dist/ops/branch.js.map +0 -1
  197. package/dist/ops/commit-traversal.d.ts +0 -349
  198. package/dist/ops/commit-traversal.d.ts.map +0 -1
  199. package/dist/ops/commit-traversal.js +0 -821
  200. package/dist/ops/commit-traversal.js.map +0 -1
  201. package/dist/ops/commit.d.ts +0 -555
  202. package/dist/ops/commit.d.ts.map +0 -1
  203. package/dist/ops/commit.js +0 -826
  204. package/dist/ops/commit.js.map +0 -1
  205. package/dist/ops/merge-base.d.ts +0 -397
  206. package/dist/ops/merge-base.d.ts.map +0 -1
  207. package/dist/ops/merge-base.js +0 -691
  208. package/dist/ops/merge-base.js.map +0 -1
  209. package/dist/ops/merge.d.ts +0 -855
  210. package/dist/ops/merge.d.ts.map +0 -1
  211. package/dist/ops/merge.js +0 -1551
  212. package/dist/ops/merge.js.map +0 -1
  213. package/dist/ops/tag.d.ts +0 -247
  214. package/dist/ops/tag.d.ts.map +0 -1
  215. package/dist/ops/tag.js +0 -649
  216. package/dist/ops/tag.js.map +0 -1
  217. package/dist/ops/tree-builder.d.ts +0 -178
  218. package/dist/ops/tree-builder.d.ts.map +0 -1
  219. package/dist/ops/tree-builder.js +0 -271
  220. package/dist/ops/tree-builder.js.map +0 -1
  221. package/dist/ops/tree-diff.d.ts +0 -291
  222. package/dist/ops/tree-diff.d.ts.map +0 -1
  223. package/dist/ops/tree-diff.js +0 -705
  224. package/dist/ops/tree-diff.js.map +0 -1
  225. package/dist/pack/delta.d.ts +0 -248
  226. package/dist/pack/delta.d.ts.map +0 -1
  227. package/dist/pack/delta.js +0 -736
  228. package/dist/pack/delta.js.map +0 -1
  229. package/dist/pack/format.d.ts +0 -446
  230. package/dist/pack/format.d.ts.map +0 -1
  231. package/dist/pack/format.js +0 -572
  232. package/dist/pack/format.js.map +0 -1
  233. package/dist/pack/full-generation.d.ts +0 -612
  234. package/dist/pack/full-generation.d.ts.map +0 -1
  235. package/dist/pack/full-generation.js +0 -1378
  236. package/dist/pack/full-generation.js.map +0 -1
  237. package/dist/pack/generation.d.ts +0 -441
  238. package/dist/pack/generation.d.ts.map +0 -1
  239. package/dist/pack/generation.js +0 -707
  240. package/dist/pack/generation.js.map +0 -1
  241. package/dist/pack/index.d.ts +0 -502
  242. package/dist/pack/index.d.ts.map +0 -1
  243. package/dist/pack/index.js +0 -833
  244. package/dist/pack/index.js.map +0 -1
  245. package/dist/refs/branch.d.ts +0 -668
  246. package/dist/refs/branch.d.ts.map +0 -1
  247. package/dist/refs/branch.js +0 -897
  248. package/dist/refs/branch.js.map +0 -1
  249. package/dist/refs/storage.d.ts +0 -833
  250. package/dist/refs/storage.d.ts.map +0 -1
  251. package/dist/refs/storage.js +0 -1023
  252. package/dist/refs/storage.js.map +0 -1
  253. package/dist/refs/tag.d.ts +0 -860
  254. package/dist/refs/tag.d.ts.map +0 -1
  255. package/dist/refs/tag.js +0 -996
  256. package/dist/refs/tag.js.map +0 -1
  257. package/dist/storage/backend.d.ts +0 -425
  258. package/dist/storage/backend.d.ts.map +0 -1
  259. package/dist/storage/backend.js +0 -41
  260. package/dist/storage/backend.js.map +0 -1
  261. package/dist/storage/fsx-adapter.d.ts +0 -204
  262. package/dist/storage/fsx-adapter.d.ts.map +0 -1
  263. package/dist/storage/fsx-adapter.js +0 -470
  264. package/dist/storage/fsx-adapter.js.map +0 -1
  265. package/dist/storage/lru-cache.d.ts +0 -691
  266. package/dist/storage/lru-cache.d.ts.map +0 -1
  267. package/dist/storage/lru-cache.js +0 -813
  268. package/dist/storage/lru-cache.js.map +0 -1
  269. package/dist/storage/object-index.d.ts +0 -585
  270. package/dist/storage/object-index.d.ts.map +0 -1
  271. package/dist/storage/object-index.js +0 -532
  272. package/dist/storage/object-index.js.map +0 -1
  273. package/dist/storage/r2-pack.d.ts +0 -1257
  274. package/dist/storage/r2-pack.d.ts.map +0 -1
  275. package/dist/storage/r2-pack.js +0 -1770
  276. package/dist/storage/r2-pack.js.map +0 -1
  277. package/dist/tiered/cdc-pipeline.d.ts +0 -1888
  278. package/dist/tiered/cdc-pipeline.d.ts.map +0 -1
  279. package/dist/tiered/cdc-pipeline.js +0 -1880
  280. package/dist/tiered/cdc-pipeline.js.map +0 -1
  281. package/dist/tiered/migration.d.ts +0 -1104
  282. package/dist/tiered/migration.d.ts.map +0 -1
  283. package/dist/tiered/migration.js +0 -1214
  284. package/dist/tiered/migration.js.map +0 -1
  285. package/dist/tiered/parquet-writer.d.ts +0 -1145
  286. package/dist/tiered/parquet-writer.d.ts.map +0 -1
  287. package/dist/tiered/parquet-writer.js +0 -1183
  288. package/dist/tiered/parquet-writer.js.map +0 -1
  289. package/dist/tiered/read-path.d.ts +0 -835
  290. package/dist/tiered/read-path.d.ts.map +0 -1
  291. package/dist/tiered/read-path.js +0 -487
  292. package/dist/tiered/read-path.js.map +0 -1
  293. package/dist/types/capability.d.ts +0 -1385
  294. package/dist/types/capability.d.ts.map +0 -1
  295. package/dist/types/capability.js +0 -36
  296. package/dist/types/capability.js.map +0 -1
  297. package/dist/types/index.d.ts +0 -13
  298. package/dist/types/index.d.ts.map +0 -1
  299. package/dist/types/index.js +0 -18
  300. package/dist/types/index.js.map +0 -1
  301. package/dist/types/objects.d.ts +0 -692
  302. package/dist/types/objects.d.ts.map +0 -1
  303. package/dist/types/objects.js +0 -837
  304. package/dist/types/objects.js.map +0 -1
  305. package/dist/types/storage.d.ts +0 -603
  306. package/dist/types/storage.d.ts.map +0 -1
  307. package/dist/types/storage.js +0 -191
  308. package/dist/types/storage.js.map +0 -1
  309. package/dist/types/worker-loader.d.ts +0 -60
  310. package/dist/types/worker-loader.d.ts.map +0 -1
  311. package/dist/types/worker-loader.js +0 -62
  312. package/dist/types/worker-loader.js.map +0 -1
  313. package/dist/utils/hash.d.ts +0 -197
  314. package/dist/utils/hash.d.ts.map +0 -1
  315. package/dist/utils/hash.js +0 -268
  316. package/dist/utils/hash.js.map +0 -1
  317. package/dist/utils/sha1.d.ts +0 -290
  318. package/dist/utils/sha1.d.ts.map +0 -1
  319. package/dist/utils/sha1.js +0 -582
  320. package/dist/utils/sha1.js.map +0 -1
  321. package/dist/wire/capabilities.d.ts +0 -1044
  322. package/dist/wire/capabilities.d.ts.map +0 -1
  323. package/dist/wire/capabilities.js +0 -941
  324. package/dist/wire/capabilities.js.map +0 -1
  325. package/dist/wire/path-security.d.ts +0 -157
  326. package/dist/wire/path-security.d.ts.map +0 -1
  327. package/dist/wire/path-security.js +0 -307
  328. package/dist/wire/path-security.js.map +0 -1
  329. package/dist/wire/pkt-line.d.ts +0 -345
  330. package/dist/wire/pkt-line.d.ts.map +0 -1
  331. package/dist/wire/pkt-line.js +0 -381
  332. package/dist/wire/pkt-line.js.map +0 -1
  333. package/dist/wire/receive-pack.d.ts +0 -1059
  334. package/dist/wire/receive-pack.d.ts.map +0 -1
  335. package/dist/wire/receive-pack.js +0 -1414
  336. package/dist/wire/receive-pack.js.map +0 -1
  337. package/dist/wire/smart-http.d.ts +0 -799
  338. package/dist/wire/smart-http.d.ts.map +0 -1
  339. package/dist/wire/smart-http.js +0 -945
  340. package/dist/wire/smart-http.js.map +0 -1
  341. package/dist/wire/upload-pack.d.ts +0 -727
  342. package/dist/wire/upload-pack.d.ts.map +0 -1
  343. package/dist/wire/upload-pack.js +0 -1138
  344. package/dist/wire/upload-pack.js.map +0 -1
package/dist/mcp/tools.js DELETED
@@ -1,1934 +0,0 @@
1
- /**
2
- * @fileoverview MCP (Model Context Protocol) Git Tool Definitions
3
- *
4
- * This module provides tool definitions for git operations that can be
5
- * exposed via the Model Context Protocol for AI assistants. It defines
6
- * a comprehensive set of git tools including status, log, diff, commit,
7
- * branch, checkout, push, pull, clone, init, add, reset, merge, rebase,
8
- * stash, tag, remote, and fetch operations.
9
- *
10
- * The module uses a registry pattern for tool management, allowing dynamic
11
- * registration, validation, and invocation of tools. Each tool follows the
12
- * MCP specification with JSON Schema input validation and standardized
13
- * result formatting.
14
- *
15
- * @module mcp/tools
16
- *
17
- * @example
18
- * // Setting up repository context and invoking a tool
19
- * import { setRepositoryContext, invokeTool } from './tools'
20
- *
21
- * // Set up the repository context first
22
- * setRepositoryContext({
23
- * objectStore: myObjectStore,
24
- * refStore: myRefStore,
25
- * index: myIndex
26
- * })
27
- *
28
- * // Invoke a tool
29
- * const result = await invokeTool('git_status', { short: true })
30
- * console.log(result.content[0].text)
31
- *
32
- * @example
33
- * // Registering a custom tool
34
- * import { registerTool } from './tools'
35
- *
36
- * registerTool({
37
- * name: 'my_custom_tool',
38
- * description: 'A custom tool',
39
- * inputSchema: { type: 'object', properties: {} },
40
- * handler: async (params) => ({
41
- * content: [{ type: 'text', text: 'Hello!' }]
42
- * })
43
- * })
44
- */
45
- import { walkCommits } from '../ops/commit-traversal';
46
- import { diffTrees, DiffStatus } from '../ops/tree-diff';
47
- import { listBranches, createBranch, deleteBranch, getCurrentBranch } from '../ops/branch';
48
- import { createCommit } from '../ops/commit';
49
- /** Global repository context - set by the application before invoking tools */
50
- let globalRepositoryContext = null;
51
- /**
52
- * Set the global repository context for MCP tools.
53
- *
54
- * @description
55
- * This function sets the global repository context that will be used by all
56
- * MCP git tools. The context provides access to the object store, ref store,
57
- * index, and working directory. This must be called before invoking any tools
58
- * that require repository access.
59
- *
60
- * @param ctx - The repository context to set, or null to clear it
61
- * @returns void
62
- *
63
- * @example
64
- * // Set up context before using tools
65
- * setRepositoryContext({
66
- * objectStore: myObjectStore,
67
- * refStore: myRefStore
68
- * })
69
- *
70
- * // Clear context when done
71
- * setRepositoryContext(null)
72
- */
73
- export function setRepositoryContext(ctx) {
74
- globalRepositoryContext = ctx;
75
- }
76
- /**
77
- * Get the global repository context.
78
- *
79
- * @description
80
- * Returns the currently set repository context, or null if no context has
81
- * been set. Tools use this internally to access repository data.
82
- *
83
- * @returns The current repository context, or null if not set
84
- *
85
- * @example
86
- * const ctx = getRepositoryContext()
87
- * if (ctx) {
88
- * const commit = await ctx.objectStore.getCommit(sha)
89
- * }
90
- */
91
- export function getRepositoryContext() {
92
- return globalRepositoryContext;
93
- }
94
- /**
95
- * Validate a path parameter to prevent command injection.
96
- *
97
- * @description
98
- * Security function that validates file paths to prevent path traversal
99
- * attacks and command injection. Rejects paths containing '..' (parent
100
- * directory traversal), absolute paths starting with '/', and shell
101
- * metacharacters.
102
- *
103
- * @param path - The path to validate
104
- * @returns The validated path (defaults to '.' if undefined)
105
- * @throws {Error} If path contains forbidden characters or traversal patterns
106
- *
107
- * @example
108
- * validatePath('src/file.ts') // Returns 'src/file.ts'
109
- * validatePath(undefined) // Returns '.'
110
- * validatePath('../etc/passwd') // Throws Error
111
- * validatePath('/etc/passwd') // Throws Error
112
- */
113
- function validatePath(path) {
114
- if (!path)
115
- return '.';
116
- // Reject path traversal attempts
117
- if (path.includes('..') || path.startsWith('/') || /[<>|&;$`]/.test(path)) {
118
- throw new Error('Invalid path: contains forbidden characters');
119
- }
120
- return path;
121
- }
122
- /**
123
- * Validate a branch or ref name according to git rules.
124
- *
125
- * @description
126
- * Validates that a branch name conforms to git's naming rules. Branch names
127
- * can contain alphanumeric characters, dots, underscores, forward slashes,
128
- * and hyphens. The '..' sequence is forbidden as it's used for range notation.
129
- *
130
- * @param name - The branch/ref name to validate
131
- * @returns The validated name
132
- * @throws {Error} If name contains invalid characters
133
- *
134
- * @example
135
- * validateBranchName('feature/my-branch') // Returns 'feature/my-branch'
136
- * validateBranchName('v1.0.0') // Returns 'v1.0.0'
137
- * validateBranchName('main..develop') // Throws Error
138
- */
139
- function validateBranchName(name) {
140
- // Git branch name rules
141
- if (!/^[a-zA-Z0-9._\/-]+$/.test(name) || name.includes('..')) {
142
- throw new Error('Invalid branch name');
143
- }
144
- return name;
145
- }
146
- /**
147
- * Validate a commit reference (hash, branch, tag, HEAD, etc.).
148
- *
149
- * @description
150
- * Validates commit references which can be SHA hashes, branch names, tag names,
151
- * HEAD, or relative references like HEAD~3 or HEAD^2. The '..' sequence is
152
- * forbidden to prevent range injection.
153
- *
154
- * @param ref - The commit reference to validate
155
- * @returns The validated reference
156
- * @throws {Error} If reference contains invalid characters
157
- *
158
- * @example
159
- * validateCommitRef('abc123def456') // Returns the SHA
160
- * validateCommitRef('HEAD~3') // Returns 'HEAD~3'
161
- * validateCommitRef('main^2') // Returns 'main^2'
162
- * validateCommitRef('a..b') // Throws Error
163
- */
164
- function validateCommitRef(ref) {
165
- // Allow hex hashes, branch names, tags, HEAD, HEAD~n, HEAD^n, etc.
166
- if (!/^[a-zA-Z0-9._\/-~^]+$/.test(ref) || ref.includes('..')) {
167
- throw new Error('Invalid commit reference');
168
- }
169
- return ref;
170
- }
171
- /**
172
- * Validate a URL for git clone operations.
173
- *
174
- * @description
175
- * Security function that validates URLs to prevent shell injection.
176
- * Rejects URLs containing shell metacharacters that could be used
177
- * for command injection.
178
- *
179
- * @param url - The URL to validate
180
- * @returns The validated URL
181
- * @throws {Error} If URL contains shell injection characters
182
- *
183
- * @example
184
- * validateUrl('https://github.com/user/repo.git') // Returns the URL
185
- * validateUrl('git@github.com:user/repo.git') // Returns the URL
186
- * validateUrl('https://evil.com; rm -rf /') // Throws Error
187
- */
188
- function validateUrl(url) {
189
- // Reject shell injection characters in URLs
190
- if (/[<>|&;$`]/.test(url)) {
191
- throw new Error('Invalid URL: contains forbidden characters');
192
- }
193
- return url;
194
- }
195
- /**
196
- * Validate a remote name.
197
- *
198
- * @description
199
- * Validates that a remote name contains only safe characters.
200
- * Remote names can contain alphanumeric characters, dots, underscores,
201
- * and hyphens.
202
- *
203
- * @param name - The remote name to validate
204
- * @returns The validated name
205
- * @throws {Error} If name contains invalid characters
206
- *
207
- * @example
208
- * validateRemoteName('origin') // Returns 'origin'
209
- * validateRemoteName('my-remote') // Returns 'my-remote'
210
- * validateRemoteName('remote/bad') // Throws Error
211
- */
212
- function validateRemoteName(name) {
213
- if (!/^[a-zA-Z0-9._-]+$/.test(name)) {
214
- throw new Error('Invalid remote name');
215
- }
216
- return name;
217
- }
218
- /**
219
- * Convert DiffStatus enum to human-readable text.
220
- *
221
- * @description
222
- * Maps diff status enum values to their git-style display text
223
- * for use in status and diff output formatting.
224
- *
225
- * @param status - The DiffStatus enum value
226
- * @returns Human-readable status string
227
- *
228
- * @example
229
- * getStatusText(DiffStatus.ADDED) // Returns 'new file'
230
- * getStatusText(DiffStatus.DELETED) // Returns 'deleted'
231
- */
232
- function getStatusText(status) {
233
- switch (status) {
234
- case DiffStatus.ADDED:
235
- return 'new file';
236
- case DiffStatus.DELETED:
237
- return 'deleted';
238
- case DiffStatus.MODIFIED:
239
- return 'modified';
240
- case DiffStatus.RENAMED:
241
- return 'renamed';
242
- case DiffStatus.COPIED:
243
- return 'copied';
244
- case DiffStatus.TYPE_CHANGED:
245
- return 'typechange';
246
- case DiffStatus.UNMERGED:
247
- return 'unmerged';
248
- default:
249
- return 'unknown';
250
- }
251
- }
252
- /**
253
- * Format a commit for log output.
254
- *
255
- * @description
256
- * Formats a commit object into a display string, supporting both
257
- * one-line format (abbreviated SHA + subject) and full format
258
- * (complete commit information with author and date).
259
- *
260
- * @param sha - The full 40-character commit SHA
261
- * @param commit - The parsed commit object
262
- * @param oneline - If true, returns abbreviated single-line format
263
- * @returns Formatted commit string
264
- *
265
- * @example
266
- * // One-line format
267
- * formatCommit('abc123...', commit, true)
268
- * // Returns: 'abc123d Fix bug in parser'
269
- *
270
- * // Full format
271
- * formatCommit('abc123...', commit, false)
272
- * // Returns multi-line commit display
273
- */
274
- function formatCommit(sha, commit, oneline) {
275
- if (oneline) {
276
- const subject = commit.message.split('\n')[0];
277
- return `${sha.slice(0, 7)} ${subject}`;
278
- }
279
- const lines = [];
280
- lines.push(`commit ${sha}`);
281
- lines.push(`Author: ${commit.author.name} <${commit.author.email}>`);
282
- const date = new Date(commit.author.timestamp * 1000);
283
- lines.push(`Date: ${date.toUTCString()}`);
284
- lines.push('');
285
- // Indent the commit message
286
- const messageLines = commit.message.split('\n');
287
- for (const line of messageLines) {
288
- lines.push(` ${line}`);
289
- }
290
- lines.push('');
291
- return lines.join('\n');
292
- }
293
- /**
294
- * Internal registry for custom-registered tools.
295
- * @internal
296
- */
297
- const toolRegistry = new Map();
298
- /**
299
- * Registry of available git tools.
300
- *
301
- * @description
302
- * Array containing all built-in git tool definitions. These tools are
303
- * automatically registered in the tool registry on module load. Each
304
- * tool implements a specific git operation following the MCP specification.
305
- *
306
- * Available tools:
307
- * - git_status: Show repository status
308
- * - git_log: Show commit history
309
- * - git_diff: Show differences between commits
310
- * - git_commit: Create a new commit
311
- * - git_branch: List, create, or delete branches
312
- * - git_checkout: Switch branches or restore files
313
- * - git_push: Upload commits to remote
314
- * - git_pull: Fetch and integrate from remote
315
- * - git_clone: Clone a repository
316
- * - git_init: Initialize a new repository
317
- * - git_add: Stage files for commit
318
- * - git_reset: Reset HEAD to a state
319
- * - git_merge: Merge branches
320
- * - git_rebase: Rebase commits
321
- * - git_stash: Stash changes
322
- * - git_tag: Manage tags
323
- * - git_remote: Manage remotes
324
- * - git_fetch: Fetch from remotes
325
- *
326
- * @example
327
- * // Access git tools array
328
- * import { gitTools } from './tools'
329
- *
330
- * for (const tool of gitTools) {
331
- * console.log(`Tool: ${tool.name} - ${tool.description}`)
332
- * }
333
- */
334
- export const gitTools = [
335
- // git_status tool
336
- {
337
- name: 'git_status',
338
- description: 'Get the current status of a git repository, showing staged, unstaged, and untracked files',
339
- inputSchema: {
340
- type: 'object',
341
- properties: {
342
- path: {
343
- type: 'string',
344
- description: 'Path to the git repository',
345
- },
346
- short: {
347
- type: 'boolean',
348
- description: 'Show short-format output',
349
- },
350
- },
351
- },
352
- handler: async (params) => {
353
- const { short } = params;
354
- const ctx = globalRepositoryContext;
355
- // If no repository context, return mock response for backward compatibility
356
- if (!ctx) {
357
- return {
358
- content: [
359
- {
360
- type: 'text',
361
- text: 'No repository context available. Set repository context with setRepositoryContext().',
362
- },
363
- ],
364
- isError: true,
365
- };
366
- }
367
- try {
368
- // Get current branch
369
- const currentBranch = await getCurrentBranch(ctx.refStore);
370
- // Get HEAD commit SHA
371
- const headRef = await ctx.refStore.getSymbolicRef('HEAD');
372
- let headSha = null;
373
- if (headRef) {
374
- headSha = await ctx.refStore.getRef(headRef);
375
- }
376
- else {
377
- headSha = await ctx.refStore.getHead();
378
- }
379
- // Build status output
380
- const lines = [];
381
- if (!short) {
382
- if (currentBranch) {
383
- lines.push(`On branch ${currentBranch}`);
384
- }
385
- else {
386
- lines.push(`HEAD detached at ${headSha?.slice(0, 7) || 'unknown'}`);
387
- }
388
- lines.push('');
389
- }
390
- // Get staged changes (index vs HEAD)
391
- let stagedChanges = null;
392
- if (headSha && ctx.index) {
393
- const headCommit = await ctx.objectStore.getCommit(headSha);
394
- if (headCommit) {
395
- // Get index entries for future tree building
396
- // Note: Full implementation would build a tree from these entries
397
- void ctx.index.getEntries(); // Acknowledge index exists but tree building not yet implemented
398
- const diffStore = {
399
- getTree: (sha) => ctx.objectStore.getTree(sha),
400
- getBlob: (sha) => ctx.objectStore.getBlob(sha),
401
- exists: (sha) => ctx.objectStore.hasObject(sha)
402
- };
403
- stagedChanges = await diffTrees(diffStore, headCommit.tree, null, // TODO: Build tree from index entries for proper staging area comparison
404
- { recursive: true });
405
- }
406
- }
407
- // Format staged changes
408
- if (stagedChanges && stagedChanges.entries.length > 0) {
409
- if (!short) {
410
- lines.push('Changes to be committed:');
411
- lines.push(' (use "git restore --staged <file>..." to unstage)');
412
- lines.push('');
413
- }
414
- for (const entry of stagedChanges.entries) {
415
- const statusChar = entry.status;
416
- if (short) {
417
- lines.push(`${statusChar} ${entry.path}`);
418
- }
419
- else {
420
- const statusText = getStatusText(entry.status);
421
- lines.push(` ${statusText}: ${entry.path}`);
422
- }
423
- }
424
- if (!short)
425
- lines.push('');
426
- }
427
- // If no changes
428
- if (!stagedChanges || stagedChanges.entries.length === 0) {
429
- if (!short) {
430
- lines.push('nothing to commit, working tree clean');
431
- }
432
- }
433
- return {
434
- content: [
435
- {
436
- type: 'text',
437
- text: lines.join('\n'),
438
- },
439
- ],
440
- };
441
- }
442
- catch (error) {
443
- return {
444
- content: [
445
- {
446
- type: 'text',
447
- text: `Error getting status: ${error instanceof Error ? error.message : String(error)}`,
448
- },
449
- ],
450
- isError: true,
451
- };
452
- }
453
- },
454
- },
455
- // git_log tool
456
- {
457
- name: 'git_log',
458
- description: 'Show the commit log history for a git repository',
459
- inputSchema: {
460
- type: 'object',
461
- properties: {
462
- path: {
463
- type: 'string',
464
- description: 'Path to the git repository',
465
- },
466
- maxCount: {
467
- type: 'number',
468
- description: 'Maximum number of commits to show',
469
- minimum: 1,
470
- },
471
- oneline: {
472
- type: 'boolean',
473
- description: 'Show each commit on a single line',
474
- },
475
- ref: {
476
- type: 'string',
477
- description: 'Branch, tag, or commit reference to show log for',
478
- },
479
- },
480
- },
481
- handler: async (params) => {
482
- const { maxCount, oneline, ref } = params;
483
- const ctx = globalRepositoryContext;
484
- // If no repository context, return error
485
- if (!ctx) {
486
- return {
487
- content: [
488
- {
489
- type: 'text',
490
- text: 'No repository context available. Set repository context with setRepositoryContext().',
491
- },
492
- ],
493
- isError: true,
494
- };
495
- }
496
- try {
497
- // Resolve starting commit
498
- let startSha = null;
499
- if (ref) {
500
- // Validate and resolve ref
501
- const validatedRef = validateCommitRef(ref);
502
- // Try as branch first
503
- startSha = await ctx.refStore.getRef(`refs/heads/${validatedRef}`);
504
- // Try as direct SHA if not found
505
- if (!startSha && /^[a-f0-9]{40}$/i.test(validatedRef)) {
506
- startSha = validatedRef;
507
- }
508
- // Try as tag
509
- if (!startSha) {
510
- startSha = await ctx.refStore.getRef(`refs/tags/${validatedRef}`);
511
- }
512
- }
513
- else {
514
- // Use HEAD
515
- const headRef = await ctx.refStore.getSymbolicRef('HEAD');
516
- if (headRef) {
517
- startSha = await ctx.refStore.getRef(headRef);
518
- }
519
- else {
520
- startSha = await ctx.refStore.getHead();
521
- }
522
- }
523
- if (!startSha) {
524
- return {
525
- content: [
526
- {
527
- type: 'text',
528
- text: ref ? `fatal: bad revision '${ref}'` : 'fatal: HEAD not found',
529
- },
530
- ],
531
- isError: true,
532
- };
533
- }
534
- // Create commit provider adapter
535
- const commitProvider = {
536
- getCommit: async (sha) => ctx.objectStore.getCommit(sha)
537
- };
538
- // Walk commits
539
- const traversalOptions = {
540
- maxCount: maxCount,
541
- sort: 'date'
542
- };
543
- const commits = [];
544
- for await (const traversalCommit of walkCommits(commitProvider, startSha, traversalOptions)) {
545
- commits.push(formatCommit(traversalCommit.sha, traversalCommit.commit, oneline || false));
546
- }
547
- const output = commits.join(oneline ? '\n' : '');
548
- return {
549
- content: [
550
- {
551
- type: 'text',
552
- text: output || 'No commits found',
553
- },
554
- ],
555
- };
556
- }
557
- catch (error) {
558
- return {
559
- content: [
560
- {
561
- type: 'text',
562
- text: `Error getting log: ${error instanceof Error ? error.message : String(error)}`,
563
- },
564
- ],
565
- isError: true,
566
- };
567
- }
568
- },
569
- },
570
- // git_diff tool
571
- {
572
- name: 'git_diff',
573
- description: 'Show differences between commits, commit and working tree',
574
- inputSchema: {
575
- type: 'object',
576
- properties: {
577
- path: {
578
- type: 'string',
579
- description: 'Path to the git repository',
580
- },
581
- staged: {
582
- type: 'boolean',
583
- description: 'Show staged changes (--cached)',
584
- },
585
- commit1: {
586
- type: 'string',
587
- description: 'First commit to compare',
588
- },
589
- commit2: {
590
- type: 'string',
591
- description: 'Second commit to compare',
592
- },
593
- },
594
- },
595
- handler: async (params) => {
596
- const { staged, commit1, commit2 } = params;
597
- const ctx = globalRepositoryContext;
598
- // If no repository context, return error
599
- if (!ctx) {
600
- return {
601
- content: [
602
- {
603
- type: 'text',
604
- text: 'No repository context available. Set repository context with setRepositoryContext().',
605
- },
606
- ],
607
- isError: true,
608
- };
609
- }
610
- try {
611
- // Create diff store adapter
612
- const diffStore = {
613
- getTree: (sha) => ctx.objectStore.getTree(sha),
614
- getBlob: (sha) => ctx.objectStore.getBlob(sha),
615
- exists: (sha) => ctx.objectStore.hasObject(sha)
616
- };
617
- let oldTreeSha = null;
618
- let newTreeSha = null;
619
- // Resolve commits to tree SHAs
620
- const resolveCommitToTree = async (commitRef) => {
621
- // Validate ref
622
- const validatedRef = validateCommitRef(commitRef);
623
- // Try as direct SHA
624
- if (/^[a-f0-9]{40}$/i.test(validatedRef)) {
625
- const commit = await ctx.objectStore.getCommit(validatedRef);
626
- return commit?.tree || null;
627
- }
628
- // Try as branch
629
- let sha = await ctx.refStore.getRef(`refs/heads/${validatedRef}`);
630
- if (!sha) {
631
- sha = await ctx.refStore.getRef(`refs/tags/${validatedRef}`);
632
- }
633
- if (sha) {
634
- const commit = await ctx.objectStore.getCommit(sha);
635
- return commit?.tree || null;
636
- }
637
- return null;
638
- };
639
- if (commit1 && commit2) {
640
- // Compare two commits
641
- oldTreeSha = await resolveCommitToTree(commit1);
642
- newTreeSha = await resolveCommitToTree(commit2);
643
- }
644
- else if (commit1) {
645
- // Compare commit to HEAD
646
- oldTreeSha = await resolveCommitToTree(commit1);
647
- // Get HEAD tree
648
- const headRef = await ctx.refStore.getSymbolicRef('HEAD');
649
- let headSha = null;
650
- if (headRef) {
651
- headSha = await ctx.refStore.getRef(headRef);
652
- }
653
- else {
654
- headSha = await ctx.refStore.getHead();
655
- }
656
- if (headSha) {
657
- const headCommit = await ctx.objectStore.getCommit(headSha);
658
- newTreeSha = headCommit?.tree || null;
659
- }
660
- }
661
- else if (staged) {
662
- // Compare HEAD to index (staged changes)
663
- const headRef = await ctx.refStore.getSymbolicRef('HEAD');
664
- let headSha = null;
665
- if (headRef) {
666
- headSha = await ctx.refStore.getRef(headRef);
667
- }
668
- else {
669
- headSha = await ctx.refStore.getHead();
670
- }
671
- if (headSha) {
672
- const headCommit = await ctx.objectStore.getCommit(headSha);
673
- oldTreeSha = headCommit?.tree || null;
674
- }
675
- // For staged diff, we would compare against index
676
- // newTreeSha would be built from index entries
677
- newTreeSha = null; // Index comparison not fully implemented
678
- }
679
- else {
680
- // Default: compare working tree to index (unstaged changes)
681
- // This requires working directory support
682
- return {
683
- content: [
684
- {
685
- type: 'text',
686
- text: 'Working tree diff requires workdir context (not yet implemented)',
687
- },
688
- ],
689
- };
690
- }
691
- if (oldTreeSha === null && newTreeSha === null) {
692
- return {
693
- content: [
694
- {
695
- type: 'text',
696
- text: 'No changes to display',
697
- },
698
- ],
699
- };
700
- }
701
- // Perform the diff
702
- const diffResult = await diffTrees(diffStore, oldTreeSha, newTreeSha, {
703
- recursive: true,
704
- detectRenames: true
705
- });
706
- // Format diff output
707
- const lines = [];
708
- for (const entry of diffResult.entries) {
709
- lines.push(`diff --git a/${entry.oldPath || entry.path} b/${entry.path}`);
710
- if (entry.status === DiffStatus.ADDED) {
711
- lines.push('new file mode ' + entry.newMode);
712
- }
713
- else if (entry.status === DiffStatus.DELETED) {
714
- lines.push('deleted file mode ' + entry.oldMode);
715
- }
716
- else if (entry.status === DiffStatus.RENAMED) {
717
- lines.push(`rename from ${entry.oldPath}`);
718
- lines.push(`rename to ${entry.path}`);
719
- if (entry.similarity !== undefined) {
720
- lines.push(`similarity index ${entry.similarity}%`);
721
- }
722
- }
723
- lines.push(`index ${entry.oldSha?.slice(0, 7) || '0000000'}..${entry.newSha?.slice(0, 7) || '0000000'}`);
724
- lines.push(`--- ${entry.status === DiffStatus.ADDED ? '/dev/null' : 'a/' + (entry.oldPath || entry.path)}`);
725
- lines.push(`+++ ${entry.status === DiffStatus.DELETED ? '/dev/null' : 'b/' + entry.path}`);
726
- lines.push(''); // Placeholder for actual content diff
727
- }
728
- // Add stats summary
729
- lines.push('');
730
- lines.push(`${diffResult.entries.length} file(s) changed`);
731
- return {
732
- content: [
733
- {
734
- type: 'text',
735
- text: lines.join('\n') || 'No changes',
736
- },
737
- ],
738
- };
739
- }
740
- catch (error) {
741
- return {
742
- content: [
743
- {
744
- type: 'text',
745
- text: `Error getting diff: ${error instanceof Error ? error.message : String(error)}`,
746
- },
747
- ],
748
- isError: true,
749
- };
750
- }
751
- },
752
- },
753
- // git_commit tool
754
- {
755
- name: 'git_commit',
756
- description: 'Create a new commit with the staged changes in the repository',
757
- inputSchema: {
758
- type: 'object',
759
- properties: {
760
- path: {
761
- type: 'string',
762
- description: 'Path to the git repository',
763
- },
764
- message: {
765
- type: 'string',
766
- description: 'Commit message',
767
- },
768
- author: {
769
- type: 'string',
770
- description: 'Author name for the commit',
771
- },
772
- email: {
773
- type: 'string',
774
- description: 'Author email for the commit',
775
- },
776
- amend: {
777
- type: 'boolean',
778
- description: 'Amend the previous commit',
779
- },
780
- },
781
- required: ['message'],
782
- },
783
- handler: async (params) => {
784
- const { message, author, email, amend } = params;
785
- const ctx = globalRepositoryContext;
786
- // If no repository context, return error
787
- if (!ctx) {
788
- return {
789
- content: [
790
- {
791
- type: 'text',
792
- text: 'No repository context available. Set repository context with setRepositoryContext().',
793
- },
794
- ],
795
- isError: true,
796
- };
797
- }
798
- // Sanitize message - reject shell injection characters (for backward compat)
799
- if (/[`$]/.test(message)) {
800
- throw new Error('Invalid commit message: contains forbidden characters');
801
- }
802
- // Validate author and email if provided
803
- if (author && email) {
804
- if (/[<>"`$\\]/.test(author) || /[<>"`$\\]/.test(email)) {
805
- throw new Error('Invalid author/email: contains forbidden characters');
806
- }
807
- }
808
- try {
809
- // Get current HEAD
810
- const headRef = await ctx.refStore.getSymbolicRef('HEAD');
811
- let parentSha = null;
812
- if (headRef) {
813
- parentSha = await ctx.refStore.getRef(headRef);
814
- }
815
- else {
816
- parentSha = await ctx.refStore.getHead();
817
- }
818
- // For a real commit, we need:
819
- // 1. A tree SHA from the index
820
- // 2. Parent commit(s)
821
- // 3. Author/committer info
822
- // If we don't have an index, we can't create a real commit
823
- if (!ctx.index) {
824
- return {
825
- content: [
826
- {
827
- type: 'text',
828
- text: 'Cannot create commit: no index/staging area available',
829
- },
830
- ],
831
- isError: true,
832
- };
833
- }
834
- // Get index entries and build tree
835
- // For now, we need a tree SHA - in a full implementation we'd build it from index
836
- // This is a simplified version that requires the tree to already exist
837
- const now = Math.floor(Date.now() / 1000);
838
- const timezone = '+0000'; // UTC for simplicity
839
- const commitAuthor = {
840
- name: author || 'Unknown',
841
- email: email || 'unknown@example.com',
842
- timestamp: now,
843
- timezone
844
- };
845
- // For amend, get the parent's tree (simplified)
846
- let treeSha = null;
847
- const parents = [];
848
- if (amend && parentSha) {
849
- // Get parent commit for amend
850
- const parentCommit = await ctx.objectStore.getCommit(parentSha);
851
- if (parentCommit) {
852
- treeSha = parentCommit.tree;
853
- parents.push(...parentCommit.parents);
854
- }
855
- }
856
- else if (parentSha) {
857
- // Regular commit - parent is current HEAD
858
- const parentCommit = await ctx.objectStore.getCommit(parentSha);
859
- if (parentCommit) {
860
- treeSha = parentCommit.tree; // Use parent's tree for now (no changes)
861
- }
862
- parents.push(parentSha);
863
- }
864
- if (!treeSha) {
865
- return {
866
- content: [
867
- {
868
- type: 'text',
869
- text: 'Cannot create commit: unable to determine tree SHA',
870
- },
871
- ],
872
- isError: true,
873
- };
874
- }
875
- // Create the commit using gitdo's commit creation
876
- const commitOptions = {
877
- message,
878
- tree: treeSha,
879
- parents,
880
- author: commitAuthor,
881
- committer: commitAuthor,
882
- allowEmpty: true
883
- };
884
- // Create object store adapter for createCommit
885
- const commitStore = {
886
- getObject: ctx.objectStore.getObject,
887
- storeObject: ctx.objectStore.storeObject,
888
- hasObject: ctx.objectStore.hasObject
889
- };
890
- const result = await createCommit(commitStore, commitOptions);
891
- // Update the ref to point to the new commit
892
- if (headRef) {
893
- await ctx.refStore.setRef(headRef, result.sha);
894
- }
895
- return {
896
- content: [
897
- {
898
- type: 'text',
899
- text: `[${headRef ? headRef.replace('refs/heads/', '') : 'detached HEAD'} ${result.sha.slice(0, 7)}] ${message.split('\n')[0]}`,
900
- },
901
- ],
902
- };
903
- }
904
- catch (error) {
905
- return {
906
- content: [
907
- {
908
- type: 'text',
909
- text: `Error creating commit: ${error instanceof Error ? error.message : String(error)}`,
910
- },
911
- ],
912
- isError: true,
913
- };
914
- }
915
- },
916
- },
917
- // git_branch tool
918
- {
919
- name: 'git_branch',
920
- description: 'List, create, or delete branches in the repository',
921
- inputSchema: {
922
- type: 'object',
923
- properties: {
924
- path: {
925
- type: 'string',
926
- description: 'Path to the git repository',
927
- },
928
- list: {
929
- type: 'boolean',
930
- description: 'List branches',
931
- },
932
- name: {
933
- type: 'string',
934
- description: 'Name of the branch to create or delete',
935
- },
936
- delete: {
937
- type: 'boolean',
938
- description: 'Delete the specified branch',
939
- },
940
- all: {
941
- type: 'boolean',
942
- description: 'List all branches including remote branches',
943
- },
944
- },
945
- },
946
- handler: async (params) => {
947
- const { list, name, delete: del, all } = params;
948
- const ctx = globalRepositoryContext;
949
- // If no repository context, return error
950
- if (!ctx) {
951
- return {
952
- content: [
953
- {
954
- type: 'text',
955
- text: 'No repository context available. Set repository context with setRepositoryContext().',
956
- },
957
- ],
958
- isError: true,
959
- };
960
- }
961
- try {
962
- // List branches
963
- if (list || (!name && !del)) {
964
- const branches = await listBranches(ctx.refStore, {
965
- all: all || false,
966
- remote: false
967
- });
968
- if (branches.length === 0) {
969
- return {
970
- content: [
971
- {
972
- type: 'text',
973
- text: 'No branches found',
974
- },
975
- ],
976
- };
977
- }
978
- const lines = [];
979
- for (const branch of branches) {
980
- const prefix = branch.current ? '* ' : ' ';
981
- lines.push(`${prefix}${branch.name}`);
982
- }
983
- return {
984
- content: [
985
- {
986
- type: 'text',
987
- text: lines.join('\n'),
988
- },
989
- ],
990
- };
991
- }
992
- // Delete branch
993
- if (del && name) {
994
- const validatedName = validateBranchName(name);
995
- const result = await deleteBranch(ctx.refStore, { name: validatedName });
996
- return {
997
- content: [
998
- {
999
- type: 'text',
1000
- text: `Deleted branch ${validatedName} (was ${result.sha.slice(0, 7)}).`,
1001
- },
1002
- ],
1003
- };
1004
- }
1005
- // Create branch
1006
- if (name) {
1007
- const validatedName = validateBranchName(name);
1008
- const result = await createBranch(ctx.refStore, { name: validatedName });
1009
- return {
1010
- content: [
1011
- {
1012
- type: 'text',
1013
- text: result.created
1014
- ? `Created branch '${validatedName}' at ${result.sha.slice(0, 7)}`
1015
- : `Branch '${validatedName}' already exists at ${result.sha.slice(0, 7)}`,
1016
- },
1017
- ],
1018
- };
1019
- }
1020
- return {
1021
- content: [
1022
- {
1023
- type: 'text',
1024
- text: 'No branch operation specified',
1025
- },
1026
- ],
1027
- };
1028
- }
1029
- catch (error) {
1030
- return {
1031
- content: [
1032
- {
1033
- type: 'text',
1034
- text: `Error: ${error instanceof Error ? error.message : String(error)}`,
1035
- },
1036
- ],
1037
- isError: true,
1038
- };
1039
- }
1040
- },
1041
- },
1042
- // git_checkout tool
1043
- {
1044
- name: 'git_checkout',
1045
- description: 'Switch branches or restore working tree files using git checkout',
1046
- inputSchema: {
1047
- type: 'object',
1048
- properties: {
1049
- path: {
1050
- type: 'string',
1051
- description: 'Path to the git repository',
1052
- },
1053
- ref: {
1054
- type: 'string',
1055
- description: 'Branch, tag, or commit to checkout',
1056
- },
1057
- createBranch: {
1058
- type: 'boolean',
1059
- description: 'Create a new branch with the given ref name',
1060
- },
1061
- },
1062
- required: ['ref'],
1063
- },
1064
- handler: async (params) => {
1065
- const { path, ref, createBranch } = params;
1066
- const validatedPath = validatePath(path);
1067
- const validatedRef = validateBranchName(ref);
1068
- const args = ['checkout'];
1069
- if (createBranch)
1070
- args.push('-b');
1071
- args.push(validatedRef);
1072
- return {
1073
- content: [
1074
- {
1075
- type: 'text',
1076
- text: `Executed: git ${args.join(' ')} in ${validatedPath === '.' ? 'current directory' : validatedPath}`,
1077
- },
1078
- ],
1079
- };
1080
- },
1081
- },
1082
- // git_push tool
1083
- {
1084
- name: 'git_push',
1085
- description: 'Upload local commits to a remote repository using git push',
1086
- inputSchema: {
1087
- type: 'object',
1088
- properties: {
1089
- path: {
1090
- type: 'string',
1091
- description: 'Path to the git repository',
1092
- },
1093
- remote: {
1094
- type: 'string',
1095
- description: 'Name of the remote (e.g., origin)',
1096
- },
1097
- branch: {
1098
- type: 'string',
1099
- description: 'Branch to push',
1100
- },
1101
- force: {
1102
- type: 'boolean',
1103
- description: 'Force push (use with caution)',
1104
- },
1105
- setUpstream: {
1106
- type: 'boolean',
1107
- description: 'Set upstream for the current branch',
1108
- },
1109
- },
1110
- },
1111
- handler: async (params) => {
1112
- const { path, remote, branch, force, setUpstream } = params;
1113
- const validatedPath = validatePath(path);
1114
- const args = ['push'];
1115
- if (force)
1116
- args.push('--force');
1117
- if (setUpstream)
1118
- args.push('-u');
1119
- if (remote)
1120
- args.push(validateRemoteName(remote));
1121
- if (branch)
1122
- args.push(validateBranchName(branch));
1123
- return {
1124
- content: [
1125
- {
1126
- type: 'text',
1127
- text: `Executed: git ${args.join(' ')} in ${validatedPath === '.' ? 'current directory' : validatedPath}`,
1128
- },
1129
- ],
1130
- };
1131
- },
1132
- },
1133
- // git_pull tool
1134
- {
1135
- name: 'git_pull',
1136
- description: 'Fetch and integrate changes from a remote repository using git pull',
1137
- inputSchema: {
1138
- type: 'object',
1139
- properties: {
1140
- path: {
1141
- type: 'string',
1142
- description: 'Path to the git repository',
1143
- },
1144
- remote: {
1145
- type: 'string',
1146
- description: 'Name of the remote (e.g., origin)',
1147
- },
1148
- branch: {
1149
- type: 'string',
1150
- description: 'Branch to pull',
1151
- },
1152
- rebase: {
1153
- type: 'boolean',
1154
- description: 'Rebase instead of merge',
1155
- },
1156
- },
1157
- },
1158
- handler: async (params) => {
1159
- const { path, remote, branch, rebase } = params;
1160
- const validatedPath = validatePath(path);
1161
- const args = ['pull'];
1162
- if (rebase)
1163
- args.push('--rebase');
1164
- if (remote)
1165
- args.push(validateRemoteName(remote));
1166
- if (branch)
1167
- args.push(validateBranchName(branch));
1168
- return {
1169
- content: [
1170
- {
1171
- type: 'text',
1172
- text: `Executed: git ${args.join(' ')} in ${validatedPath === '.' ? 'current directory' : validatedPath}`,
1173
- },
1174
- ],
1175
- };
1176
- },
1177
- },
1178
- // git_clone tool
1179
- {
1180
- name: 'git_clone',
1181
- description: 'Copy a repository from a remote URL to a local directory using git clone',
1182
- inputSchema: {
1183
- type: 'object',
1184
- properties: {
1185
- url: {
1186
- type: 'string',
1187
- description: 'URL of the repository to clone',
1188
- },
1189
- destination: {
1190
- type: 'string',
1191
- description: 'Local path to clone into',
1192
- },
1193
- depth: {
1194
- type: 'number',
1195
- description: 'Create a shallow clone with specified depth',
1196
- },
1197
- branch: {
1198
- type: 'string',
1199
- description: 'Branch to clone',
1200
- },
1201
- bare: {
1202
- type: 'boolean',
1203
- description: 'Create a bare repository',
1204
- },
1205
- },
1206
- required: ['url'],
1207
- },
1208
- handler: async (params) => {
1209
- const { url, destination, depth, branch, bare } = params;
1210
- const validatedUrl = validateUrl(url);
1211
- const args = ['clone'];
1212
- if (depth)
1213
- args.push(`--depth=${depth}`);
1214
- if (branch)
1215
- args.push(`--branch=${validateBranchName(branch)}`);
1216
- if (bare)
1217
- args.push('--bare');
1218
- args.push(validatedUrl);
1219
- if (destination)
1220
- args.push(validatePath(destination));
1221
- return {
1222
- content: [
1223
- {
1224
- type: 'text',
1225
- text: `Executed: git ${args.join(' ')}`,
1226
- },
1227
- ],
1228
- };
1229
- },
1230
- },
1231
- // git_init tool
1232
- {
1233
- name: 'git_init',
1234
- description: 'Create an empty git repository or reinitialize an existing one',
1235
- inputSchema: {
1236
- type: 'object',
1237
- properties: {
1238
- path: {
1239
- type: 'string',
1240
- description: 'Path where the repository should be initialized',
1241
- },
1242
- bare: {
1243
- type: 'boolean',
1244
- description: 'Create a bare repository',
1245
- },
1246
- initialBranch: {
1247
- type: 'string',
1248
- description: 'Name for the initial branch',
1249
- },
1250
- },
1251
- required: ['path'],
1252
- },
1253
- handler: async (params) => {
1254
- const { path, bare, initialBranch } = params;
1255
- const validatedPath = validatePath(path);
1256
- const args = ['init'];
1257
- if (bare)
1258
- args.push('--bare');
1259
- if (initialBranch)
1260
- args.push(`--initial-branch=${validateBranchName(initialBranch)}`);
1261
- args.push(validatedPath);
1262
- return {
1263
- content: [
1264
- {
1265
- type: 'text',
1266
- text: `Executed: git ${args.join(' ')}`,
1267
- },
1268
- ],
1269
- };
1270
- },
1271
- },
1272
- // git_add tool
1273
- {
1274
- name: 'git_add',
1275
- description: 'Add file contents to the staging area for the next commit',
1276
- inputSchema: {
1277
- type: 'object',
1278
- properties: {
1279
- path: {
1280
- type: 'string',
1281
- description: 'Path to the git repository',
1282
- },
1283
- files: {
1284
- type: 'array',
1285
- items: { type: 'string' },
1286
- description: 'List of files to add',
1287
- },
1288
- all: {
1289
- type: 'boolean',
1290
- description: 'Add all changes in the working tree',
1291
- },
1292
- force: {
1293
- type: 'boolean',
1294
- description: 'Allow adding otherwise ignored files',
1295
- },
1296
- },
1297
- },
1298
- handler: async (params) => {
1299
- const { path, files, all, force } = params;
1300
- const validatedPath = validatePath(path);
1301
- const args = ['add'];
1302
- if (all)
1303
- args.push('--all');
1304
- if (force)
1305
- args.push('--force');
1306
- if (files) {
1307
- // Validate each file path
1308
- const validatedFiles = files.map((f) => {
1309
- if (/[<>|&;$`]/.test(f)) {
1310
- throw new Error('Invalid file path: contains forbidden characters');
1311
- }
1312
- return f;
1313
- });
1314
- args.push(...validatedFiles);
1315
- }
1316
- return {
1317
- content: [
1318
- {
1319
- type: 'text',
1320
- text: `Executed: git ${args.join(' ')} in ${validatedPath === '.' ? 'current directory' : validatedPath}`,
1321
- },
1322
- ],
1323
- };
1324
- },
1325
- },
1326
- // git_reset tool
1327
- {
1328
- name: 'git_reset',
1329
- description: 'Reset current HEAD to a specified state',
1330
- inputSchema: {
1331
- type: 'object',
1332
- properties: {
1333
- path: {
1334
- type: 'string',
1335
- description: 'Path to the git repository',
1336
- },
1337
- mode: {
1338
- type: 'string',
1339
- enum: ['soft', 'mixed', 'hard'],
1340
- description: 'Reset mode: soft, mixed, or hard',
1341
- },
1342
- commit: {
1343
- type: 'string',
1344
- description: 'Commit to reset to',
1345
- },
1346
- },
1347
- },
1348
- handler: async (params) => {
1349
- const { path, mode, commit } = params;
1350
- const validatedPath = validatePath(path);
1351
- const args = ['reset'];
1352
- if (mode)
1353
- args.push(`--${mode}`);
1354
- if (commit)
1355
- args.push(validateCommitRef(commit));
1356
- return {
1357
- content: [
1358
- {
1359
- type: 'text',
1360
- text: `Executed: git ${args.join(' ')} in ${validatedPath === '.' ? 'current directory' : validatedPath}`,
1361
- },
1362
- ],
1363
- };
1364
- },
1365
- },
1366
- // git_merge tool
1367
- {
1368
- name: 'git_merge',
1369
- description: 'Merge one or more branches into the current branch',
1370
- inputSchema: {
1371
- type: 'object',
1372
- properties: {
1373
- path: {
1374
- type: 'string',
1375
- description: 'Path to the git repository',
1376
- },
1377
- branch: {
1378
- type: 'string',
1379
- description: 'Branch to merge into current branch',
1380
- },
1381
- noFf: {
1382
- type: 'boolean',
1383
- description: 'Create a merge commit even when fast-forward is possible',
1384
- },
1385
- squash: {
1386
- type: 'boolean',
1387
- description: 'Squash commits into a single commit',
1388
- },
1389
- },
1390
- required: ['branch'],
1391
- },
1392
- handler: async (params) => {
1393
- const { path, branch, noFf, squash } = params;
1394
- const validatedPath = validatePath(path);
1395
- const args = ['merge'];
1396
- if (noFf)
1397
- args.push('--no-ff');
1398
- if (squash)
1399
- args.push('--squash');
1400
- args.push(validateBranchName(branch));
1401
- return {
1402
- content: [
1403
- {
1404
- type: 'text',
1405
- text: `Executed: git ${args.join(' ')} in ${validatedPath === '.' ? 'current directory' : validatedPath}`,
1406
- },
1407
- ],
1408
- };
1409
- },
1410
- },
1411
- // git_rebase tool
1412
- {
1413
- name: 'git_rebase',
1414
- description: 'Reapply commits on top of another base tip',
1415
- inputSchema: {
1416
- type: 'object',
1417
- properties: {
1418
- path: {
1419
- type: 'string',
1420
- description: 'Path to the git repository',
1421
- },
1422
- onto: {
1423
- type: 'string',
1424
- description: 'Branch or commit to rebase onto',
1425
- },
1426
- abort: {
1427
- type: 'boolean',
1428
- description: 'Abort an in-progress rebase',
1429
- },
1430
- continue: {
1431
- type: 'boolean',
1432
- description: 'Continue an in-progress rebase',
1433
- },
1434
- },
1435
- },
1436
- handler: async (params) => {
1437
- const { path, onto, abort, continue: cont } = params;
1438
- const validatedPath = validatePath(path);
1439
- const args = ['rebase'];
1440
- if (abort)
1441
- args.push('--abort');
1442
- else if (cont)
1443
- args.push('--continue');
1444
- else if (onto)
1445
- args.push(validateCommitRef(onto));
1446
- return {
1447
- content: [
1448
- {
1449
- type: 'text',
1450
- text: `Executed: git ${args.join(' ')} in ${validatedPath === '.' ? 'current directory' : validatedPath}`,
1451
- },
1452
- ],
1453
- };
1454
- },
1455
- },
1456
- // git_stash tool
1457
- {
1458
- name: 'git_stash',
1459
- description: 'Stash the changes in a dirty working directory away',
1460
- inputSchema: {
1461
- type: 'object',
1462
- properties: {
1463
- path: {
1464
- type: 'string',
1465
- description: 'Path to the git repository',
1466
- },
1467
- action: {
1468
- type: 'string',
1469
- enum: ['push', 'pop', 'list', 'drop', 'apply', 'clear'],
1470
- description: 'Stash action to perform',
1471
- },
1472
- message: {
1473
- type: 'string',
1474
- description: 'Message for the stash entry',
1475
- },
1476
- },
1477
- },
1478
- handler: async (params) => {
1479
- const { path, action, message } = params;
1480
- const validatedPath = validatePath(path);
1481
- const args = ['stash'];
1482
- if (action)
1483
- args.push(action);
1484
- if (message && action === 'push') {
1485
- // Validate stash message for shell injection
1486
- if (/[`$]/.test(message)) {
1487
- throw new Error('Invalid stash message: contains forbidden characters');
1488
- }
1489
- args.push('-m', message);
1490
- }
1491
- return {
1492
- content: [
1493
- {
1494
- type: 'text',
1495
- text: `Executed: git ${args.join(' ')} in ${validatedPath === '.' ? 'current directory' : validatedPath}`,
1496
- },
1497
- ],
1498
- };
1499
- },
1500
- },
1501
- // git_tag tool
1502
- {
1503
- name: 'git_tag',
1504
- description: 'Create, list, delete, or verify tags in the repository',
1505
- inputSchema: {
1506
- type: 'object',
1507
- properties: {
1508
- path: {
1509
- type: 'string',
1510
- description: 'Path to the git repository',
1511
- },
1512
- name: {
1513
- type: 'string',
1514
- description: 'Name of the tag',
1515
- },
1516
- message: {
1517
- type: 'string',
1518
- description: 'Message for annotated tag',
1519
- },
1520
- delete: {
1521
- type: 'boolean',
1522
- description: 'Delete the specified tag',
1523
- },
1524
- },
1525
- },
1526
- handler: async (params) => {
1527
- const { path, name, message, delete: del } = params;
1528
- const validatedPath = validatePath(path);
1529
- const args = ['tag'];
1530
- if (del && name)
1531
- args.push('-d', validateBranchName(name));
1532
- else if (message && name) {
1533
- // Validate tag message for shell injection
1534
- if (/[`$]/.test(message)) {
1535
- throw new Error('Invalid tag message: contains forbidden characters');
1536
- }
1537
- args.push('-a', validateBranchName(name), '-m', message);
1538
- }
1539
- else if (name)
1540
- args.push(validateBranchName(name));
1541
- return {
1542
- content: [
1543
- {
1544
- type: 'text',
1545
- text: `Executed: git ${args.join(' ')} in ${validatedPath === '.' ? 'current directory' : validatedPath}`,
1546
- },
1547
- ],
1548
- };
1549
- },
1550
- },
1551
- // git_remote tool
1552
- {
1553
- name: 'git_remote',
1554
- description: 'Manage set of tracked repositories (list, add, remove, update remotes)',
1555
- inputSchema: {
1556
- type: 'object',
1557
- properties: {
1558
- path: {
1559
- type: 'string',
1560
- description: 'Path to the git repository',
1561
- },
1562
- action: {
1563
- type: 'string',
1564
- enum: ['list', 'add', 'remove', 'rename', 'set-url'],
1565
- description: 'Remote action to perform',
1566
- },
1567
- name: {
1568
- type: 'string',
1569
- description: 'Name of the remote',
1570
- },
1571
- url: {
1572
- type: 'string',
1573
- description: 'URL of the remote repository',
1574
- },
1575
- },
1576
- },
1577
- handler: async (params) => {
1578
- const { path, action, name, url } = params;
1579
- const validatedPath = validatePath(path);
1580
- const args = ['remote'];
1581
- if (action === 'list' || !action)
1582
- args.push('-v');
1583
- else if (action === 'add' && name && url)
1584
- args.push('add', validateRemoteName(name), validateUrl(url));
1585
- else if (action === 'remove' && name)
1586
- args.push('remove', validateRemoteName(name));
1587
- else if (action === 'set-url' && name && url)
1588
- args.push('set-url', validateRemoteName(name), validateUrl(url));
1589
- return {
1590
- content: [
1591
- {
1592
- type: 'text',
1593
- text: `Executed: git ${args.join(' ')} in ${validatedPath === '.' ? 'current directory' : validatedPath}`,
1594
- },
1595
- ],
1596
- };
1597
- },
1598
- },
1599
- // git_fetch tool
1600
- {
1601
- name: 'git_fetch',
1602
- description: 'Fetch branches and tags from one or more remote repositories',
1603
- inputSchema: {
1604
- type: 'object',
1605
- properties: {
1606
- path: {
1607
- type: 'string',
1608
- description: 'Path to the git repository',
1609
- },
1610
- remote: {
1611
- type: 'string',
1612
- description: 'Name of the remote to fetch from',
1613
- },
1614
- all: {
1615
- type: 'boolean',
1616
- description: 'Fetch all remotes',
1617
- },
1618
- prune: {
1619
- type: 'boolean',
1620
- description: 'Prune remote-tracking branches no longer on remote',
1621
- },
1622
- },
1623
- },
1624
- handler: async (params) => {
1625
- const { path, remote, all, prune } = params;
1626
- const validatedPath = validatePath(path);
1627
- const args = ['fetch'];
1628
- if (all)
1629
- args.push('--all');
1630
- if (prune)
1631
- args.push('--prune');
1632
- if (remote && !all)
1633
- args.push(validateRemoteName(remote));
1634
- return {
1635
- content: [
1636
- {
1637
- type: 'text',
1638
- text: `Executed: git ${args.join(' ')} in ${validatedPath === '.' ? 'current directory' : validatedPath}`,
1639
- },
1640
- ],
1641
- };
1642
- },
1643
- },
1644
- ];
1645
- // Register all git tools in the registry on module load
1646
- gitTools.forEach((tool) => {
1647
- toolRegistry.set(tool.name, tool);
1648
- });
1649
- /**
1650
- * Register a new tool in the registry.
1651
- *
1652
- * @description
1653
- * Adds a custom tool to the global tool registry. The tool must have a valid
1654
- * handler function and a unique name. Once registered, the tool can be invoked
1655
- * using {@link invokeTool}.
1656
- *
1657
- * Note: Built-in git tools are automatically registered on module load.
1658
- *
1659
- * @param tool - The tool definition to register
1660
- * @returns void
1661
- * @throws {Error} If tool handler is missing or not a function
1662
- * @throws {Error} If a tool with the same name already exists
1663
- *
1664
- * @example
1665
- * import { registerTool, invokeTool } from './tools'
1666
- *
1667
- * // Register a custom tool
1668
- * registerTool({
1669
- * name: 'custom_operation',
1670
- * description: 'Performs a custom operation',
1671
- * inputSchema: {
1672
- * type: 'object',
1673
- * properties: {
1674
- * value: { type: 'string', description: 'Input value' }
1675
- * },
1676
- * required: ['value']
1677
- * },
1678
- * handler: async (params) => {
1679
- * const { value } = params as { value: string }
1680
- * return {
1681
- * content: [{ type: 'text', text: `Processed: ${value}` }]
1682
- * }
1683
- * }
1684
- * })
1685
- *
1686
- * // Now invoke the registered tool
1687
- * const result = await invokeTool('custom_operation', { value: 'test' })
1688
- */
1689
- export function registerTool(tool) {
1690
- if (!tool.handler || typeof tool.handler !== 'function') {
1691
- throw new Error(`Tool '${tool.name}' must have a handler function`);
1692
- }
1693
- if (toolRegistry.has(tool.name)) {
1694
- throw new Error(`Tool with name '${tool.name}' already exists (duplicate)`);
1695
- }
1696
- toolRegistry.set(tool.name, tool);
1697
- }
1698
- /**
1699
- * Validate input parameters against a tool's schema.
1700
- *
1701
- * @description
1702
- * Performs comprehensive validation of tool parameters against the tool's
1703
- * JSON Schema definition. Checks for required parameters, type correctness,
1704
- * enum values, numeric constraints, string patterns, and array item types.
1705
- *
1706
- * This function is called automatically by {@link invokeTool} before
1707
- * executing a tool handler, but can also be used independently for
1708
- * pre-validation.
1709
- *
1710
- * @param tool - The tool whose schema to validate against
1711
- * @param params - The parameters to validate
1712
- * @returns Validation result object with valid flag and array of error messages
1713
- *
1714
- * @example
1715
- * import { validateToolInput, getTool } from './tools'
1716
- *
1717
- * const tool = getTool('git_commit')
1718
- * if (tool) {
1719
- * const validation = validateToolInput(tool, { path: '/repo' })
1720
- * if (!validation.valid) {
1721
- * console.error('Validation errors:', validation.errors)
1722
- * // Output: ['Missing required parameter: message']
1723
- * }
1724
- * }
1725
- *
1726
- * @example
1727
- * // Type validation example
1728
- * const result = validateToolInput(tool, { maxCount: 'not-a-number' })
1729
- * // result.errors: ["Parameter 'maxCount' has invalid type: expected number, got string"]
1730
- */
1731
- export function validateToolInput(tool, params) {
1732
- const errors = [];
1733
- const schema = tool.inputSchema;
1734
- // Check required parameters
1735
- if (schema.required) {
1736
- for (const requiredParam of schema.required) {
1737
- if (!(requiredParam in params) || params[requiredParam] === undefined) {
1738
- errors.push(`Missing required parameter: ${requiredParam}`);
1739
- }
1740
- }
1741
- }
1742
- // Check parameter types
1743
- if (schema.properties) {
1744
- for (const [key, value] of Object.entries(params)) {
1745
- const propSchema = schema.properties[key];
1746
- if (!propSchema) {
1747
- // Unknown parameter - could be an error or we could ignore it
1748
- continue;
1749
- }
1750
- // Type validation
1751
- const valueType = Array.isArray(value) ? 'array' : typeof value;
1752
- if (propSchema.type && valueType !== propSchema.type) {
1753
- errors.push(`Parameter '${key}' has invalid type: expected ${propSchema.type}, got ${valueType}`);
1754
- }
1755
- // Enum validation
1756
- if (propSchema.enum && !propSchema.enum.includes(value)) {
1757
- errors.push(`Parameter '${key}' must be one of: ${propSchema.enum.join(', ')}`);
1758
- }
1759
- // Number constraints
1760
- if (propSchema.type === 'number' && typeof value === 'number') {
1761
- if (propSchema.minimum !== undefined && value < propSchema.minimum) {
1762
- errors.push(`Parameter '${key}' must be at least ${propSchema.minimum}`);
1763
- }
1764
- if (propSchema.maximum !== undefined && value > propSchema.maximum) {
1765
- errors.push(`Parameter '${key}' must be at most ${propSchema.maximum}`);
1766
- }
1767
- }
1768
- // String pattern validation
1769
- if (propSchema.type === 'string' && typeof value === 'string' && propSchema.pattern) {
1770
- const regex = new RegExp(propSchema.pattern);
1771
- if (!regex.test(value)) {
1772
- errors.push(`Parameter '${key}' does not match required pattern: ${propSchema.pattern}`);
1773
- }
1774
- }
1775
- // Array item type validation
1776
- if (propSchema.type === 'array' && Array.isArray(value) && propSchema.items) {
1777
- const itemType = propSchema.items.type;
1778
- for (let i = 0; i < value.length; i++) {
1779
- const itemValueType = typeof value[i];
1780
- if (itemType && itemValueType !== itemType) {
1781
- errors.push(`Array item at index ${i} in '${key}' has invalid type: expected ${itemType}, got ${itemValueType}`);
1782
- }
1783
- }
1784
- }
1785
- }
1786
- }
1787
- return {
1788
- valid: errors.length === 0,
1789
- errors,
1790
- };
1791
- }
1792
- /**
1793
- * Invoke a tool by name with the given parameters.
1794
- *
1795
- * @description
1796
- * Looks up a tool by name in the registry, validates the provided parameters
1797
- * against the tool's schema, and executes the tool's handler. Validation
1798
- * errors and execution errors are returned as MCPToolResult with isError=true
1799
- * rather than throwing exceptions.
1800
- *
1801
- * This is the primary function for executing MCP tools. Ensure the repository
1802
- * context is set via {@link setRepositoryContext} before invoking git tools.
1803
- *
1804
- * @param toolName - Name of the tool to invoke (e.g., 'git_status')
1805
- * @param params - Parameters to pass to the tool handler
1806
- * @returns Promise resolving to the tool result
1807
- * @throws {Error} If the tool is not found in the registry
1808
- *
1809
- * @example
1810
- * import { invokeTool, setRepositoryContext } from './tools'
1811
- *
1812
- * // Set up repository context first
1813
- * setRepositoryContext(myRepoContext)
1814
- *
1815
- * // Invoke git_status tool
1816
- * const status = await invokeTool('git_status', { short: true })
1817
- * if (!status.isError) {
1818
- * console.log(status.content[0].text)
1819
- * }
1820
- *
1821
- * @example
1822
- * // Invoke git_log with parameters
1823
- * const log = await invokeTool('git_log', {
1824
- * maxCount: 10,
1825
- * oneline: true,
1826
- * ref: 'main'
1827
- * })
1828
- *
1829
- * @example
1830
- * // Handle validation errors
1831
- * const result = await invokeTool('git_commit', {})
1832
- * if (result.isError) {
1833
- * // result.content[0].text contains validation error message
1834
- * console.error('Error:', result.content[0].text)
1835
- * }
1836
- */
1837
- export async function invokeTool(toolName, params) {
1838
- const tool = toolRegistry.get(toolName);
1839
- if (!tool) {
1840
- throw new Error(`Tool '${toolName}' not found (does not exist)`);
1841
- }
1842
- // Validate parameters before invoking
1843
- const validation = validateToolInput(tool, params);
1844
- if (!validation.valid) {
1845
- return {
1846
- content: [
1847
- {
1848
- type: 'text',
1849
- text: `Validation error: ${validation.errors.join('; ')}`,
1850
- },
1851
- ],
1852
- isError: true,
1853
- };
1854
- }
1855
- // Invoke the handler with error handling
1856
- try {
1857
- return await tool.handler(params);
1858
- }
1859
- catch (error) {
1860
- return {
1861
- content: [
1862
- {
1863
- type: 'text',
1864
- text: error instanceof Error ? error.message : String(error),
1865
- },
1866
- ],
1867
- isError: true,
1868
- };
1869
- }
1870
- }
1871
- /**
1872
- * Get a list of all registered tools.
1873
- *
1874
- * @description
1875
- * Returns an array of all tools in the registry with their names, descriptions,
1876
- * and input schemas. Handler functions are omitted for security and serialization.
1877
- * This is useful for discovery and documentation purposes.
1878
- *
1879
- * @returns Array of tool definitions without handler functions
1880
- *
1881
- * @example
1882
- * import { listTools } from './tools'
1883
- *
1884
- * const tools = listTools()
1885
- * console.log(`Available tools: ${tools.length}`)
1886
- *
1887
- * for (const tool of tools) {
1888
- * console.log(`- ${tool.name}: ${tool.description}`)
1889
- * console.log(` Required params: ${tool.inputSchema.required?.join(', ') || 'none'}`)
1890
- * }
1891
- */
1892
- export function listTools() {
1893
- const tools = [];
1894
- for (const tool of toolRegistry.values()) {
1895
- // Return tool without handler
1896
- tools.push({
1897
- name: tool.name,
1898
- description: tool.description,
1899
- inputSchema: tool.inputSchema,
1900
- });
1901
- }
1902
- return tools;
1903
- }
1904
- /**
1905
- * Get a tool by name.
1906
- *
1907
- * @description
1908
- * Retrieves a tool definition from the registry by its name. Returns the
1909
- * complete tool object including the handler function. Returns undefined
1910
- * if no tool with the given name exists.
1911
- *
1912
- * @param name - Name of the tool to retrieve (e.g., 'git_status')
1913
- * @returns The complete tool definition if found, undefined otherwise
1914
- *
1915
- * @example
1916
- * import { getTool } from './tools'
1917
- *
1918
- * const statusTool = getTool('git_status')
1919
- * if (statusTool) {
1920
- * console.log(`Description: ${statusTool.description}`)
1921
- * console.log(`Parameters:`, Object.keys(statusTool.inputSchema.properties || {}))
1922
- * }
1923
- *
1924
- * @example
1925
- * // Check if a tool exists before using it
1926
- * const tool = getTool('my_custom_tool')
1927
- * if (!tool) {
1928
- * console.error('Tool not found')
1929
- * }
1930
- */
1931
- export function getTool(name) {
1932
- return toolRegistry.get(name);
1933
- }
1934
- //# sourceMappingURL=tools.js.map