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.
- package/README.md +40 -353
- package/dist/do/logger.d.ts +50 -0
- package/dist/do/logger.d.ts.map +1 -0
- package/dist/do/logger.js +122 -0
- package/dist/do/logger.js.map +1 -0
- package/dist/{durable-object → do}/schema.d.ts +3 -3
- package/dist/do/schema.d.ts.map +1 -0
- package/dist/{durable-object → do}/schema.js +4 -3
- package/dist/do/schema.js.map +1 -0
- package/dist/do/types.d.ts +267 -0
- package/dist/do/types.d.ts.map +1 -0
- package/dist/do/types.js +62 -0
- package/dist/do/types.js.map +1 -0
- package/dist/index.d.ts +15 -415
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -483
- package/dist/index.js.map +1 -1
- package/package.json +13 -21
- package/dist/cli/commands/add.d.ts +0 -174
- package/dist/cli/commands/add.d.ts.map +0 -1
- package/dist/cli/commands/add.js +0 -131
- package/dist/cli/commands/add.js.map +0 -1
- package/dist/cli/commands/blame.d.ts +0 -259
- package/dist/cli/commands/blame.d.ts.map +0 -1
- package/dist/cli/commands/blame.js +0 -609
- package/dist/cli/commands/blame.js.map +0 -1
- package/dist/cli/commands/branch.d.ts +0 -249
- package/dist/cli/commands/branch.d.ts.map +0 -1
- package/dist/cli/commands/branch.js +0 -693
- package/dist/cli/commands/branch.js.map +0 -1
- package/dist/cli/commands/commit.d.ts +0 -182
- package/dist/cli/commands/commit.d.ts.map +0 -1
- package/dist/cli/commands/commit.js +0 -437
- package/dist/cli/commands/commit.js.map +0 -1
- package/dist/cli/commands/diff.d.ts +0 -464
- package/dist/cli/commands/diff.d.ts.map +0 -1
- package/dist/cli/commands/diff.js +0 -958
- package/dist/cli/commands/diff.js.map +0 -1
- package/dist/cli/commands/log.d.ts +0 -239
- package/dist/cli/commands/log.d.ts.map +0 -1
- package/dist/cli/commands/log.js +0 -535
- package/dist/cli/commands/log.js.map +0 -1
- package/dist/cli/commands/merge.d.ts +0 -106
- package/dist/cli/commands/merge.d.ts.map +0 -1
- package/dist/cli/commands/merge.js +0 -55
- package/dist/cli/commands/merge.js.map +0 -1
- package/dist/cli/commands/review.d.ts +0 -457
- package/dist/cli/commands/review.d.ts.map +0 -1
- package/dist/cli/commands/review.js +0 -533
- package/dist/cli/commands/review.js.map +0 -1
- package/dist/cli/commands/status.d.ts +0 -269
- package/dist/cli/commands/status.d.ts.map +0 -1
- package/dist/cli/commands/status.js +0 -493
- package/dist/cli/commands/status.js.map +0 -1
- package/dist/cli/commands/web.d.ts +0 -199
- package/dist/cli/commands/web.d.ts.map +0 -1
- package/dist/cli/commands/web.js +0 -696
- package/dist/cli/commands/web.js.map +0 -1
- package/dist/cli/fs-adapter.d.ts +0 -656
- package/dist/cli/fs-adapter.d.ts.map +0 -1
- package/dist/cli/fs-adapter.js +0 -1179
- package/dist/cli/fs-adapter.js.map +0 -1
- package/dist/cli/fsx-cli-adapter.d.ts +0 -359
- package/dist/cli/fsx-cli-adapter.d.ts.map +0 -1
- package/dist/cli/fsx-cli-adapter.js +0 -619
- package/dist/cli/fsx-cli-adapter.js.map +0 -1
- package/dist/cli/index.d.ts +0 -387
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -523
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/ui/components/DiffView.d.ts +0 -7
- package/dist/cli/ui/components/DiffView.d.ts.map +0 -1
- package/dist/cli/ui/components/DiffView.js +0 -11
- package/dist/cli/ui/components/DiffView.js.map +0 -1
- package/dist/cli/ui/components/ErrorDisplay.d.ts +0 -6
- package/dist/cli/ui/components/ErrorDisplay.d.ts.map +0 -1
- package/dist/cli/ui/components/ErrorDisplay.js +0 -11
- package/dist/cli/ui/components/ErrorDisplay.js.map +0 -1
- package/dist/cli/ui/components/FuzzySearch.d.ts +0 -9
- package/dist/cli/ui/components/FuzzySearch.d.ts.map +0 -1
- package/dist/cli/ui/components/FuzzySearch.js +0 -12
- package/dist/cli/ui/components/FuzzySearch.js.map +0 -1
- package/dist/cli/ui/components/LoadingSpinner.d.ts +0 -6
- package/dist/cli/ui/components/LoadingSpinner.d.ts.map +0 -1
- package/dist/cli/ui/components/LoadingSpinner.js +0 -10
- package/dist/cli/ui/components/LoadingSpinner.js.map +0 -1
- package/dist/cli/ui/components/NavigationList.d.ts +0 -9
- package/dist/cli/ui/components/NavigationList.d.ts.map +0 -1
- package/dist/cli/ui/components/NavigationList.js +0 -11
- package/dist/cli/ui/components/NavigationList.js.map +0 -1
- package/dist/cli/ui/components/ScrollableContent.d.ts +0 -8
- package/dist/cli/ui/components/ScrollableContent.d.ts.map +0 -1
- package/dist/cli/ui/components/ScrollableContent.js +0 -11
- package/dist/cli/ui/components/ScrollableContent.js.map +0 -1
- package/dist/cli/ui/components/index.d.ts +0 -7
- package/dist/cli/ui/components/index.d.ts.map +0 -1
- package/dist/cli/ui/components/index.js +0 -9
- package/dist/cli/ui/components/index.js.map +0 -1
- package/dist/cli/ui/terminal-ui.d.ts +0 -52
- package/dist/cli/ui/terminal-ui.d.ts.map +0 -1
- package/dist/cli/ui/terminal-ui.js +0 -121
- package/dist/cli/ui/terminal-ui.js.map +0 -1
- package/dist/do/BashModule.d.ts +0 -871
- package/dist/do/BashModule.d.ts.map +0 -1
- package/dist/do/BashModule.js +0 -1143
- package/dist/do/BashModule.js.map +0 -1
- package/dist/do/FsModule.d.ts +0 -601
- package/dist/do/FsModule.d.ts.map +0 -1
- package/dist/do/FsModule.js +0 -1120
- package/dist/do/FsModule.js.map +0 -1
- package/dist/do/GitModule.d.ts +0 -635
- package/dist/do/GitModule.d.ts.map +0 -1
- package/dist/do/GitModule.js +0 -781
- package/dist/do/GitModule.js.map +0 -1
- package/dist/do/GitRepoDO.d.ts +0 -281
- package/dist/do/GitRepoDO.d.ts.map +0 -1
- package/dist/do/GitRepoDO.js +0 -479
- package/dist/do/GitRepoDO.js.map +0 -1
- package/dist/do/bash-ast.d.ts +0 -246
- package/dist/do/bash-ast.d.ts.map +0 -1
- package/dist/do/bash-ast.js +0 -888
- package/dist/do/bash-ast.js.map +0 -1
- package/dist/do/container-executor.d.ts +0 -491
- package/dist/do/container-executor.d.ts.map +0 -1
- package/dist/do/container-executor.js +0 -730
- package/dist/do/container-executor.js.map +0 -1
- package/dist/do/index.d.ts +0 -53
- package/dist/do/index.d.ts.map +0 -1
- package/dist/do/index.js +0 -91
- package/dist/do/index.js.map +0 -1
- package/dist/do/tiered-storage.d.ts +0 -403
- package/dist/do/tiered-storage.d.ts.map +0 -1
- package/dist/do/tiered-storage.js +0 -689
- package/dist/do/tiered-storage.js.map +0 -1
- package/dist/do/withBash.d.ts +0 -231
- package/dist/do/withBash.d.ts.map +0 -1
- package/dist/do/withBash.js +0 -244
- package/dist/do/withBash.js.map +0 -1
- package/dist/do/withFs.d.ts +0 -237
- package/dist/do/withFs.d.ts.map +0 -1
- package/dist/do/withFs.js +0 -387
- package/dist/do/withFs.js.map +0 -1
- package/dist/do/withGit.d.ts +0 -180
- package/dist/do/withGit.d.ts.map +0 -1
- package/dist/do/withGit.js +0 -271
- package/dist/do/withGit.js.map +0 -1
- package/dist/durable-object/object-store.d.ts +0 -633
- package/dist/durable-object/object-store.d.ts.map +0 -1
- package/dist/durable-object/object-store.js +0 -1161
- package/dist/durable-object/object-store.js.map +0 -1
- package/dist/durable-object/schema.d.ts.map +0 -1
- package/dist/durable-object/schema.js.map +0 -1
- package/dist/durable-object/wal.d.ts +0 -416
- package/dist/durable-object/wal.d.ts.map +0 -1
- package/dist/durable-object/wal.js +0 -445
- package/dist/durable-object/wal.js.map +0 -1
- package/dist/mcp/adapter.d.ts +0 -772
- package/dist/mcp/adapter.d.ts.map +0 -1
- package/dist/mcp/adapter.js +0 -895
- package/dist/mcp/adapter.js.map +0 -1
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts +0 -22
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +0 -1
- package/dist/mcp/sandbox/miniflare-evaluator.js +0 -140
- package/dist/mcp/sandbox/miniflare-evaluator.js.map +0 -1
- package/dist/mcp/sandbox/object-store-proxy.d.ts +0 -32
- package/dist/mcp/sandbox/object-store-proxy.d.ts.map +0 -1
- package/dist/mcp/sandbox/object-store-proxy.js +0 -30
- package/dist/mcp/sandbox/object-store-proxy.js.map +0 -1
- package/dist/mcp/sandbox/template.d.ts +0 -17
- package/dist/mcp/sandbox/template.d.ts.map +0 -1
- package/dist/mcp/sandbox/template.js +0 -71
- package/dist/mcp/sandbox/template.js.map +0 -1
- package/dist/mcp/sandbox.d.ts +0 -764
- package/dist/mcp/sandbox.d.ts.map +0 -1
- package/dist/mcp/sandbox.js +0 -1362
- package/dist/mcp/sandbox.js.map +0 -1
- package/dist/mcp/sdk-adapter.d.ts +0 -835
- package/dist/mcp/sdk-adapter.d.ts.map +0 -1
- package/dist/mcp/sdk-adapter.js +0 -974
- package/dist/mcp/sdk-adapter.js.map +0 -1
- package/dist/mcp/tools/do.d.ts +0 -32
- package/dist/mcp/tools/do.d.ts.map +0 -1
- package/dist/mcp/tools/do.js +0 -115
- package/dist/mcp/tools/do.js.map +0 -1
- package/dist/mcp/tools.d.ts +0 -548
- package/dist/mcp/tools.d.ts.map +0 -1
- package/dist/mcp/tools.js +0 -1934
- package/dist/mcp/tools.js.map +0 -1
- package/dist/ops/blame.d.ts +0 -551
- package/dist/ops/blame.d.ts.map +0 -1
- package/dist/ops/blame.js +0 -1037
- package/dist/ops/blame.js.map +0 -1
- package/dist/ops/branch.d.ts +0 -766
- package/dist/ops/branch.d.ts.map +0 -1
- package/dist/ops/branch.js +0 -950
- package/dist/ops/branch.js.map +0 -1
- package/dist/ops/commit-traversal.d.ts +0 -349
- package/dist/ops/commit-traversal.d.ts.map +0 -1
- package/dist/ops/commit-traversal.js +0 -821
- package/dist/ops/commit-traversal.js.map +0 -1
- package/dist/ops/commit.d.ts +0 -555
- package/dist/ops/commit.d.ts.map +0 -1
- package/dist/ops/commit.js +0 -826
- package/dist/ops/commit.js.map +0 -1
- package/dist/ops/merge-base.d.ts +0 -397
- package/dist/ops/merge-base.d.ts.map +0 -1
- package/dist/ops/merge-base.js +0 -691
- package/dist/ops/merge-base.js.map +0 -1
- package/dist/ops/merge.d.ts +0 -855
- package/dist/ops/merge.d.ts.map +0 -1
- package/dist/ops/merge.js +0 -1551
- package/dist/ops/merge.js.map +0 -1
- package/dist/ops/tag.d.ts +0 -247
- package/dist/ops/tag.d.ts.map +0 -1
- package/dist/ops/tag.js +0 -649
- package/dist/ops/tag.js.map +0 -1
- package/dist/ops/tree-builder.d.ts +0 -178
- package/dist/ops/tree-builder.d.ts.map +0 -1
- package/dist/ops/tree-builder.js +0 -271
- package/dist/ops/tree-builder.js.map +0 -1
- package/dist/ops/tree-diff.d.ts +0 -291
- package/dist/ops/tree-diff.d.ts.map +0 -1
- package/dist/ops/tree-diff.js +0 -705
- package/dist/ops/tree-diff.js.map +0 -1
- package/dist/pack/delta.d.ts +0 -248
- package/dist/pack/delta.d.ts.map +0 -1
- package/dist/pack/delta.js +0 -736
- package/dist/pack/delta.js.map +0 -1
- package/dist/pack/format.d.ts +0 -446
- package/dist/pack/format.d.ts.map +0 -1
- package/dist/pack/format.js +0 -572
- package/dist/pack/format.js.map +0 -1
- package/dist/pack/full-generation.d.ts +0 -612
- package/dist/pack/full-generation.d.ts.map +0 -1
- package/dist/pack/full-generation.js +0 -1378
- package/dist/pack/full-generation.js.map +0 -1
- package/dist/pack/generation.d.ts +0 -441
- package/dist/pack/generation.d.ts.map +0 -1
- package/dist/pack/generation.js +0 -707
- package/dist/pack/generation.js.map +0 -1
- package/dist/pack/index.d.ts +0 -502
- package/dist/pack/index.d.ts.map +0 -1
- package/dist/pack/index.js +0 -833
- package/dist/pack/index.js.map +0 -1
- package/dist/refs/branch.d.ts +0 -668
- package/dist/refs/branch.d.ts.map +0 -1
- package/dist/refs/branch.js +0 -897
- package/dist/refs/branch.js.map +0 -1
- package/dist/refs/storage.d.ts +0 -833
- package/dist/refs/storage.d.ts.map +0 -1
- package/dist/refs/storage.js +0 -1023
- package/dist/refs/storage.js.map +0 -1
- package/dist/refs/tag.d.ts +0 -860
- package/dist/refs/tag.d.ts.map +0 -1
- package/dist/refs/tag.js +0 -996
- package/dist/refs/tag.js.map +0 -1
- package/dist/storage/backend.d.ts +0 -425
- package/dist/storage/backend.d.ts.map +0 -1
- package/dist/storage/backend.js +0 -41
- package/dist/storage/backend.js.map +0 -1
- package/dist/storage/fsx-adapter.d.ts +0 -204
- package/dist/storage/fsx-adapter.d.ts.map +0 -1
- package/dist/storage/fsx-adapter.js +0 -470
- package/dist/storage/fsx-adapter.js.map +0 -1
- package/dist/storage/lru-cache.d.ts +0 -691
- package/dist/storage/lru-cache.d.ts.map +0 -1
- package/dist/storage/lru-cache.js +0 -813
- package/dist/storage/lru-cache.js.map +0 -1
- package/dist/storage/object-index.d.ts +0 -585
- package/dist/storage/object-index.d.ts.map +0 -1
- package/dist/storage/object-index.js +0 -532
- package/dist/storage/object-index.js.map +0 -1
- package/dist/storage/r2-pack.d.ts +0 -1257
- package/dist/storage/r2-pack.d.ts.map +0 -1
- package/dist/storage/r2-pack.js +0 -1770
- package/dist/storage/r2-pack.js.map +0 -1
- package/dist/tiered/cdc-pipeline.d.ts +0 -1888
- package/dist/tiered/cdc-pipeline.d.ts.map +0 -1
- package/dist/tiered/cdc-pipeline.js +0 -1880
- package/dist/tiered/cdc-pipeline.js.map +0 -1
- package/dist/tiered/migration.d.ts +0 -1104
- package/dist/tiered/migration.d.ts.map +0 -1
- package/dist/tiered/migration.js +0 -1214
- package/dist/tiered/migration.js.map +0 -1
- package/dist/tiered/parquet-writer.d.ts +0 -1145
- package/dist/tiered/parquet-writer.d.ts.map +0 -1
- package/dist/tiered/parquet-writer.js +0 -1183
- package/dist/tiered/parquet-writer.js.map +0 -1
- package/dist/tiered/read-path.d.ts +0 -835
- package/dist/tiered/read-path.d.ts.map +0 -1
- package/dist/tiered/read-path.js +0 -487
- package/dist/tiered/read-path.js.map +0 -1
- package/dist/types/capability.d.ts +0 -1385
- package/dist/types/capability.d.ts.map +0 -1
- package/dist/types/capability.js +0 -36
- package/dist/types/capability.js.map +0 -1
- package/dist/types/index.d.ts +0 -13
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -18
- package/dist/types/index.js.map +0 -1
- package/dist/types/objects.d.ts +0 -692
- package/dist/types/objects.d.ts.map +0 -1
- package/dist/types/objects.js +0 -837
- package/dist/types/objects.js.map +0 -1
- package/dist/types/storage.d.ts +0 -603
- package/dist/types/storage.d.ts.map +0 -1
- package/dist/types/storage.js +0 -191
- package/dist/types/storage.js.map +0 -1
- package/dist/types/worker-loader.d.ts +0 -60
- package/dist/types/worker-loader.d.ts.map +0 -1
- package/dist/types/worker-loader.js +0 -62
- package/dist/types/worker-loader.js.map +0 -1
- package/dist/utils/hash.d.ts +0 -197
- package/dist/utils/hash.d.ts.map +0 -1
- package/dist/utils/hash.js +0 -268
- package/dist/utils/hash.js.map +0 -1
- package/dist/utils/sha1.d.ts +0 -290
- package/dist/utils/sha1.d.ts.map +0 -1
- package/dist/utils/sha1.js +0 -582
- package/dist/utils/sha1.js.map +0 -1
- package/dist/wire/capabilities.d.ts +0 -1044
- package/dist/wire/capabilities.d.ts.map +0 -1
- package/dist/wire/capabilities.js +0 -941
- package/dist/wire/capabilities.js.map +0 -1
- package/dist/wire/path-security.d.ts +0 -157
- package/dist/wire/path-security.d.ts.map +0 -1
- package/dist/wire/path-security.js +0 -307
- package/dist/wire/path-security.js.map +0 -1
- package/dist/wire/pkt-line.d.ts +0 -345
- package/dist/wire/pkt-line.d.ts.map +0 -1
- package/dist/wire/pkt-line.js +0 -381
- package/dist/wire/pkt-line.js.map +0 -1
- package/dist/wire/receive-pack.d.ts +0 -1059
- package/dist/wire/receive-pack.d.ts.map +0 -1
- package/dist/wire/receive-pack.js +0 -1414
- package/dist/wire/receive-pack.js.map +0 -1
- package/dist/wire/smart-http.d.ts +0 -799
- package/dist/wire/smart-http.d.ts.map +0 -1
- package/dist/wire/smart-http.js +0 -945
- package/dist/wire/smart-http.js.map +0 -1
- package/dist/wire/upload-pack.d.ts +0 -727
- package/dist/wire/upload-pack.d.ts.map +0 -1
- package/dist/wire/upload-pack.js +0 -1138
- 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
|