codex-review-mcp 1.0.0 → 1.1.1

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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 codex-review-mcp contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,156 @@
1
+ # Codex Review MCP Server
2
+
3
+ Get AI-powered code reviews without leaving your conversation. Keep coding with your AI assistant, and when you need a second opinion, just ask for a review - all in the same context window.
4
+
5
+ ## Why This Exists
6
+
7
+ You're coding with an AI assistant. It makes changes. You want a quick review. Normally you'd copy/paste code into another chat, wait, copy feedback back. **That breaks your flow.**
8
+
9
+ With this MCP server, your AI assistant can call out for a code review **right in the middle of your conversation**. No context switching. No copy/paste. The review comes back in the same chat.
10
+
11
+ ## Features
12
+
13
+ - 🎯 **Zero Configuration** - Just add your OpenAI API key
14
+ - 🔍 Automatically reviews your uncommitted changes
15
+ - 🤖 Powered by GPT-5 Codex for intelligent code analysis
16
+ - 📊 Returns actionable feedback in Markdown format
17
+ - ⚡ Works with any git repository
18
+ - 🔄 Stays in your conversation context
19
+
20
+ ## Installation
21
+
22
+ ### For Cursor Users
23
+
24
+ Add the following configuration to your Cursor MCP settings (`~/.cursor/mcp.json` or `%USERPROFILE%\.cursor\mcp.json` on Windows):
25
+
26
+ ```json
27
+ {
28
+ "mcpServers": {
29
+ "codex-reviewer": {
30
+ "command": "npx",
31
+ "args": ["-y", "codex-review-mcp@latest"],
32
+ "env": {
33
+ "OPENAI_API_KEY": "your-openai-api-key-here",
34
+ "CODEX_MODEL": "gpt-5-codex",
35
+ "WORKSPACE_ROOT": "${workspaceFolder}"
36
+ }
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ **Important**: Replace `your-openai-api-key-here` with your actual OpenAI API key.
43
+
44
+ ### For Claude Desktop Users
45
+
46
+ Add to your Claude Desktop MCP configuration:
47
+
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "codex-reviewer": {
52
+ "command": "npx",
53
+ "args": ["-y", "codex-review-mcp@latest"],
54
+ "env": {
55
+ "OPENAI_API_KEY": "your-openai-api-key-here",
56
+ "CODEX_MODEL": "gpt-5-codex"
57
+ }
58
+ }
59
+ }
60
+ }
61
+ ```
62
+
63
+ ## Usage
64
+
65
+ Once configured, just ask your AI assistant:
66
+
67
+ > "Do a code review with codex-reviewer"
68
+
69
+ That's it! The MCP server automatically figures out what to review:
70
+ - **If you have uncommitted changes**: Reviews your working tree vs HEAD (what you just coded)
71
+ - **If your working tree is clean**: Reviews your current branch vs the default branch (main/master)
72
+ - **If you're on the default branch with no changes**: Reports "No changes to review"
73
+
74
+ **Example conversation:**
75
+ ```
76
+ You: "Add a login form with email validation"
77
+ AI: [makes changes]
78
+ AI: "Let me get a code review on this..."
79
+ AI: [calls codex-reviewer MCP - automatically reviews uncommitted changes]
80
+ AI: "The review found a potential issue with the email regex..."
81
+ ```
82
+
83
+ No need to specify what to review - it just works!
84
+
85
+ ### Advanced Options (Optional)
86
+
87
+ Most users won't need these, but they're available if you want more control:
88
+
89
+ - `target` (default: `"head"`): What to review
90
+ - `"head"`: All uncommitted changes (default - what you just coded)
91
+ - `"staged"`: Only staged changes (`git add`ed files)
92
+ - `"range"`: Specific commit range (requires baseRef and headRef)
93
+ - `focus`: Ask the reviewer to focus on something specific (e.g., "security issues")
94
+ - `paths`: Review only specific files
95
+ - `baseRef` / `headRef`: For reviewing commit ranges
96
+ - `maxTokens`: Limit the length of the review
97
+ - `workspaceDir`: Override auto-detection of repository location
98
+
99
+ ## How It Works
100
+
101
+ 1. **Detects Repository**: Automatically finds your git repository root
102
+ 2. **Collects Diff**: Runs git diff to get the changes
103
+ 3. **Gathers Context**: Collects relevant context about your codebase
104
+ 4. **AI Review**: Sends to GPT-5 Codex for intelligent analysis
105
+ 5. **Returns Feedback**: Provides actionable Markdown feedback
106
+
107
+ ## Requirements
108
+
109
+ - Node.js 18+ (for running the MCP server)
110
+ - Git repository
111
+ - OpenAI API key with access to GPT-5 Codex
112
+ - Cursor or Claude Desktop (or any MCP-compatible client)
113
+
114
+ ## Environment Variables
115
+
116
+ - `OPENAI_API_KEY` (required): Your OpenAI API key
117
+ - `CODEX_MODEL` (optional, default: "gpt-5-codex"): The model to use for reviews
118
+ - `WORKSPACE_ROOT` (optional): Path to your workspace (auto-detected in most cases)
119
+
120
+ ## Development
121
+
122
+ ### Local Development Setup
123
+
124
+ ```bash
125
+ # Clone the repository
126
+ git clone <your-repo-url>
127
+ cd codex-review-mcp
128
+
129
+ # Install dependencies
130
+ npm install
131
+
132
+ # Build
133
+ npm run build
134
+
135
+ # For local testing, update your MCP config to point to the local build:
136
+ {
137
+ "codex-reviewer": {
138
+ "command": "node",
139
+ "args": ["/absolute/path/to/codex-review-mcp/dist/mcp-server.js"],
140
+ "env": {
141
+ "OPENAI_API_KEY": "your-key",
142
+ "CODEX_MODEL": "gpt-5-codex",
143
+ "WORKSPACE_ROOT": "${workspaceFolder}"
144
+ }
145
+ }
146
+ }
147
+ ```
148
+
149
+ ## License
150
+
151
+ MIT
152
+
153
+ ## Contributing
154
+
155
+ Contributions are welcome! Please open an issue or PR.
156
+
@@ -6,14 +6,15 @@ import { performCodeReview } from './tools/performCodeReview.js';
6
6
  const server = new McpServer({ name: 'codex-review-mcp', version: '0.1.0' });
7
7
  server.registerTool('perform_code_review', {
8
8
  title: 'Perform Code Review',
9
- description: 'Review git diffs in the current repo and return actionable Markdown feedback.',
9
+ description: 'Review git diffs in the current repo and return actionable Markdown feedback. In auto mode (default), reviews uncommitted changes if present, otherwise reviews current branch vs default branch. No need to commit changes first - the tool reviews your working tree.',
10
10
  inputSchema: {
11
- target: z.enum(['staged', 'head', 'range']).default('head'),
11
+ target: z.enum(['auto', 'staged', 'head', 'range']).default('auto'),
12
12
  baseRef: z.string().optional(),
13
13
  headRef: z.string().optional(),
14
14
  focus: z.string().optional(),
15
15
  paths: z.array(z.string()).optional(),
16
16
  maxTokens: z.number().optional(),
17
+ workspaceDir: z.string().optional().describe('Absolute path to the workspace/repository directory. If not provided, attempts to detect from environment or current working directory.'),
17
18
  },
18
19
  }, async (input, extra) => {
19
20
  const reviewInput = {
@@ -23,18 +24,19 @@ server.registerTool('perform_code_review', {
23
24
  focus: input.focus,
24
25
  paths: input.paths,
25
26
  maxTokens: input.maxTokens,
27
+ workspaceDir: input.workspaceDir,
26
28
  };
27
29
  const onProgress = async (message, progress, total) => {
28
30
  // Attach to tool-call request via related request ID so clients can map progress
29
31
  await server.server.notification({
30
32
  method: 'notifications/progress',
31
33
  params: {
32
- progressToken: extra?._meta?.progressToken ?? extra.requestId,
34
+ progressToken: extra?._meta?.progressToken ?? extra?.requestId,
33
35
  progress,
34
36
  total,
35
37
  message,
36
38
  },
37
- }, { relatedRequestId: extra.requestId });
39
+ }, extra?.requestId ? { relatedRequestId: extra.requestId } : undefined);
38
40
  };
39
41
  const markdown = await performCodeReview(reviewInput, onProgress);
40
42
  return { content: [{ type: 'text', text: markdown, mimeType: 'text/markdown' }] };
@@ -14,7 +14,7 @@ function filterPaths(paths) {
14
14
  return undefined;
15
15
  return paths.filter((p) => !DEFAULT_IGNORES.some((g) => minimatch(p, g)));
16
16
  }
17
- export async function collectDiff(input) {
17
+ export async function collectDiff(input, workspaceDir) {
18
18
  async function findRepoRoot(startDir) {
19
19
  let dir = startDir;
20
20
  // Walk up to filesystem root (max ~25 hops as a safety guard)
@@ -33,27 +33,97 @@ export async function collectDiff(input) {
33
33
  }
34
34
  return null;
35
35
  }
36
+ async function detectDefaultBranch(repoRoot) {
37
+ // Try to get default branch from remote
38
+ try {
39
+ const { stdout } = await exec('git', ['remote', 'show', 'origin'], { cwd: repoRoot, encoding: 'utf8' });
40
+ const match = stdout.match(/HEAD branch:\s*(\S+)/);
41
+ if (match?.[1])
42
+ return match[1];
43
+ }
44
+ catch {
45
+ // Remote not available or other error, continue to fallback
46
+ }
47
+ // Fallback: check if main or master exists locally
48
+ for (const branch of ['main', 'master']) {
49
+ try {
50
+ await exec('git', ['rev-parse', '--verify', branch], { cwd: repoRoot });
51
+ return branch;
52
+ }
53
+ catch {
54
+ // Branch doesn't exist, try next
55
+ }
56
+ }
57
+ // Last resort: use HEAD~1 as baseline
58
+ return 'HEAD~1';
59
+ }
60
+ async function hasUncommittedChanges(repoRoot) {
61
+ try {
62
+ const { stdout } = await exec('git', ['status', '--porcelain'], { cwd: repoRoot, encoding: 'utf8' });
63
+ return stdout.trim().length > 0;
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ }
69
+ async function getCurrentBranch(repoRoot) {
70
+ try {
71
+ const { stdout } = await exec('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: repoRoot, encoding: 'utf8' });
72
+ return stdout.trim();
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ }
78
+ // Priority order: explicit workspaceDir param > env vars > process.cwd()
79
+ const preferredStart = workspaceDir || process.env.CODEX_REPO_ROOT || process.env.WORKSPACE_ROOT || process.env.INIT_CWD || process.cwd();
80
+ const preferredRoot = await findRepoRoot(preferredStart);
81
+ // If workspaceDir was explicitly provided but no repo found, fail immediately
82
+ if (workspaceDir && !preferredRoot) {
83
+ throw new Error(`Could not locate a Git repository starting from workspaceDir "${workspaceDir}".`);
84
+ }
85
+ const repoRoot = preferredRoot || (await findRepoRoot(process.cwd())) || process.cwd();
36
86
  const args = ['diff', '--unified=0'];
37
- switch (input.target) {
38
- case 'staged':
39
- args.splice(1, 0, '--staged');
40
- break;
41
- case 'head':
87
+ if (input.target === 'auto') {
88
+ // Auto mode: detect what to review
89
+ const hasChanges = await hasUncommittedChanges(repoRoot);
90
+ if (hasChanges) {
91
+ // Review uncommitted changes vs HEAD
42
92
  args.push('HEAD');
43
- break;
44
- case 'range':
45
- if (!input.baseRef || !input.headRef) {
46
- throw new Error('range target requires baseRef and headRef');
93
+ }
94
+ else {
95
+ // No uncommitted changes, review branch vs default
96
+ const currentBranch = await getCurrentBranch(repoRoot);
97
+ const defaultBranch = await detectDefaultBranch(repoRoot);
98
+ if (currentBranch === defaultBranch) {
99
+ // On default branch with no changes - nothing to review
100
+ return '';
47
101
  }
48
- args.push(`${input.baseRef}...${input.headRef}`);
49
- break;
102
+ // Review current branch vs default branch
103
+ args.push(`${defaultBranch}...HEAD`);
104
+ }
105
+ }
106
+ else {
107
+ // Explicit target modes
108
+ switch (input.target) {
109
+ case 'staged':
110
+ args.splice(1, 0, '--staged');
111
+ break;
112
+ case 'head':
113
+ args.push('HEAD');
114
+ break;
115
+ case 'range':
116
+ if (!input.baseRef || !input.headRef) {
117
+ throw new Error('range target requires baseRef and headRef');
118
+ }
119
+ args.push(`${input.baseRef}...${input.headRef}`);
120
+ break;
121
+ }
50
122
  }
51
123
  const filtered = filterPaths(input.paths);
52
124
  if (filtered && filtered.length)
53
125
  args.push(...filtered);
54
126
  try {
55
- const preferredStart = process.env.CODEX_REPO_ROOT || process.env.WORKSPACE_ROOT || process.env.INIT_CWD || process.cwd();
56
- const repoRoot = (await findRepoRoot(preferredStart)) || (await findRepoRoot(process.cwd())) || process.cwd();
57
127
  const { stdout } = await exec('git', args, {
58
128
  encoding: 'utf8',
59
129
  maxBuffer: 10 * 1024 * 1024,
@@ -6,9 +6,9 @@ import { formatOutput } from '../review/formatOutput.js';
6
6
  import { debugLog } from '../util/debug.js';
7
7
  export async function performCodeReview(input, onProgress) {
8
8
  await onProgress?.('Collecting diff…', 10, 100);
9
- const diffText = await collectDiff(input);
9
+ const diffText = await collectDiff(input, input.workspaceDir);
10
10
  if (!diffText.trim()) {
11
- throw new Error('No diff found. Ensure you have changes or a valid git range.');
11
+ throw new Error('No changes to review. You are on the default branch with a clean working tree.');
12
12
  }
13
13
  await onProgress?.('Gathering context…', 30, 100);
14
14
  const context = await gatherContext();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-review-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "build": "tsc",
@@ -27,10 +27,19 @@
27
27
  "node": ">=18",
28
28
  "npm": ">=9"
29
29
  },
30
- "keywords": [],
30
+ "keywords": [
31
+ "mcp",
32
+ "model-context-protocol",
33
+ "code-review",
34
+ "ai",
35
+ "gpt",
36
+ "codex",
37
+ "cursor",
38
+ "claude"
39
+ ],
31
40
  "author": "",
32
- "license": "ISC",
33
- "description": "",
41
+ "license": "MIT",
42
+ "description": "MCP server for AI-powered code reviews using GPT-5 Codex",
34
43
  "type": "module",
35
44
  "bin": {
36
45
  "codex-review-mcp": "./bin/codex-review-mcp"