Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ export function createServer(configPath: string): McpServer {
"Create a normal commit in an authorized repository. This tool mutates repository state, requires the current branch to match configured full-match patterns, rejects empty commit messages, and rejects empty commits.",
{
repo_path: z.string().min(1),
message: z.string(),
message: z.string().min(1),
},
CLOSED_WORLD_ADDITIVE_MUTATION_TOOL,
async ({ repo_path, message }) => {
Expand All @@ -219,7 +219,7 @@ export function createServer(configPath: string): McpServer {
"Create a new local branch from an explicit or detected upstream base branch for an authorized repository, then switch to it. This tool requires a clean worktree, requires feature_branch in the effective allowed workflow modes, resolves the remote at runtime, fetches the chosen base branch first, and does not accept arbitrary source refs or detached targets.",
{
repo_path: z.string().min(1),
new_branch: z.string(),
new_branch: z.string().min(1),
branch: z.string().min(1).optional(),
},
CLOSED_WORLD_POTENTIALLY_DESTRUCTIVE_MUTATION_TOOL,
Expand All @@ -245,7 +245,7 @@ export function createServer(configPath: string): McpServer {
"Switch to an existing local branch in an authorized repository. This tool mutates repository state, requires the worktree to be clean, requires the target branch name to match configured allowed patterns, requires feature_branch in the effective allowed workflow modes, only accepts an explicit local branch name, and does not create branches or allow detached checkouts.",
{
repo_path: z.string().min(1),
branch: z.string(),
branch: z.string().min(1),
},
CLOSED_WORLD_POTENTIALLY_DESTRUCTIVE_MUTATION_TOOL,
async ({ repo_path, branch }) => {
Expand Down Expand Up @@ -336,7 +336,7 @@ export function createServer(configPath: string): McpServer {
{
repo_path: z.string().min(1),
path: z.string().min(1),
new_branch: z.string(),
new_branch: z.string().min(1),
branch: z.string().min(1).optional(),
},
CLOSED_WORLD_ADDITIVE_MUTATION_TOOL,
Expand Down Expand Up @@ -384,8 +384,8 @@ export function createServer(configPath: string): McpServer {
"Create a draft pull request for the current branch in an authorized repository. This tool mutates repository state via GitHub, requires the current branch to match configured full-match patterns, is draft-only, requires a non-empty title, and uses either an explicit base or a runtime-detected default branch.",
{
repo_path: z.string().min(1),
title: z.string(),
body: z.string(),
title: z.string().min(1),
body: z.string().min(1),
base: z.string().min(1).optional(),
},
CLOSED_WORLD_ADDITIVE_MUTATION_TOOL,
Expand Down
39 changes: 39 additions & 0 deletions tests/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,45 @@ describe("createServer", () => {
);
});

it("rejects empty required string inputs at the schema boundary", () => {
const server = createServer("/tmp/config.yaml") as unknown as {
_registeredTools: Record<string, { inputSchema: { safeParse: (input: unknown) => { success: boolean } } }>;
};

expect(
server._registeredTools.git_commit!.inputSchema.safeParse({
repo_path: "/tmp/repo",
message: "",
}).success,
).toBe(false);
expect(
server._registeredTools.git_branch_create_and_switch!.inputSchema.safeParse({
repo_path: "/tmp/repo",
new_branch: "",
}).success,
).toBe(false);
expect(
server._registeredTools.git_branch_switch!.inputSchema.safeParse({
repo_path: "/tmp/repo",
branch: "",
}).success,
).toBe(false);
expect(
server._registeredTools.git_worktree_add!.inputSchema.safeParse({
repo_path: "/tmp/repo",
path: "/tmp/worktree",
new_branch: "",
}).success,
).toBe(false);
expect(
server._registeredTools.gh_pr_create_draft!.inputSchema.safeParse({
repo_path: "/tmp/repo",
title: "",
body: "",
}).success,
).toBe(false);
});

it("omits restartRequired from config tool responses", async () => {
const configPath = path.join(os.tmpdir(), `git-mcp-config-tools-${Date.now()}.yaml`);
const repoDir = await fs.mkdtemp(path.join(os.tmpdir(), "git-mcp-config-tools-repo-"));
Expand Down
Loading