@j0hanz/code-review-analyst-mcp 1.2.1 → 1.4.0
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/dist/index.js +12 -3
- package/dist/lib/context-budget.d.ts +2 -2
- package/dist/lib/context-budget.js +12 -6
- package/dist/lib/diff-budget.js +6 -2
- package/dist/lib/diff-cleaner.d.ts +12 -0
- package/dist/lib/diff-cleaner.js +51 -0
- package/dist/lib/diff-parser.js +31 -36
- package/dist/lib/diff-store.d.ts +22 -0
- package/dist/lib/diff-store.js +28 -0
- package/dist/lib/env-config.d.ts +1 -0
- package/dist/lib/env-config.js +9 -3
- package/dist/lib/errors.d.ts +1 -0
- package/dist/lib/errors.js +5 -5
- package/dist/lib/gemini-schema.js +2 -1
- package/dist/lib/gemini.js +135 -67
- package/dist/lib/model-config.d.ts +14 -2
- package/dist/lib/model-config.js +30 -6
- package/dist/lib/tool-contracts.d.ts +222 -0
- package/dist/lib/tool-contracts.js +289 -0
- package/dist/lib/tool-factory.d.ts +5 -1
- package/dist/lib/tool-factory.js +48 -54
- package/dist/lib/tool-response.js +10 -12
- package/dist/lib/types.d.ts +3 -3
- package/dist/prompts/index.js +47 -41
- package/dist/resources/index.d.ts +1 -1
- package/dist/resources/index.js +99 -18
- package/dist/resources/instructions.d.ts +1 -0
- package/dist/resources/instructions.js +69 -0
- package/dist/resources/server-config.d.ts +1 -0
- package/dist/resources/server-config.js +71 -0
- package/dist/resources/tool-catalog.d.ts +1 -0
- package/dist/resources/tool-catalog.js +39 -0
- package/dist/resources/tool-info.d.ts +5 -0
- package/dist/resources/tool-info.js +122 -0
- package/dist/resources/workflows.d.ts +1 -0
- package/dist/resources/workflows.js +72 -0
- package/dist/schemas/inputs.d.ts +6 -5
- package/dist/schemas/inputs.js +17 -29
- package/dist/schemas/outputs.d.ts +17 -1
- package/dist/schemas/outputs.js +84 -52
- package/dist/server.js +28 -27
- package/dist/tools/analyze-complexity.d.ts +2 -0
- package/dist/tools/analyze-complexity.js +51 -0
- package/dist/tools/analyze-pr-impact.js +32 -20
- package/dist/tools/detect-api-breaking.d.ts +2 -0
- package/dist/tools/detect-api-breaking.js +48 -0
- package/dist/tools/generate-diff.d.ts +2 -0
- package/dist/tools/generate-diff.js +71 -0
- package/dist/tools/generate-review-summary.js +34 -29
- package/dist/tools/generate-test-plan.js +38 -28
- package/dist/tools/index.js +11 -2
- package/dist/tools/inspect-code-quality.js +47 -36
- package/dist/tools/suggest-search-replace.js +34 -20
- package/package.json +1 -2
- package/dist/instructions.md +0 -149
package/dist/prompts/index.js
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
import { completable } from '@modelcontextprotocol/sdk/server/completable.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { getToolContract, getToolContractNames, } from '../lib/tool-contracts.js';
|
|
3
4
|
const HELP_PROMPT_NAME = 'get-help';
|
|
4
5
|
const HELP_PROMPT_TITLE = 'Get Help';
|
|
5
|
-
const HELP_PROMPT_DESCRIPTION = '
|
|
6
|
+
const HELP_PROMPT_DESCRIPTION = 'Server instructions.';
|
|
6
7
|
const REVIEW_GUIDE_PROMPT_NAME = 'review-guide';
|
|
7
8
|
const REVIEW_GUIDE_PROMPT_TITLE = 'Review Guide';
|
|
8
|
-
const REVIEW_GUIDE_PROMPT_DESCRIPTION = '
|
|
9
|
-
const TOOLS =
|
|
10
|
-
'analyze_pr_impact',
|
|
11
|
-
'generate_review_summary',
|
|
12
|
-
'inspect_code_quality',
|
|
13
|
-
'suggest_search_replace',
|
|
14
|
-
'generate_test_plan',
|
|
15
|
-
];
|
|
9
|
+
const REVIEW_GUIDE_PROMPT_DESCRIPTION = 'Workflow guide for tool/focus area.';
|
|
10
|
+
const TOOLS = getToolContractNames();
|
|
16
11
|
const FOCUS_AREAS = [
|
|
17
12
|
'security',
|
|
18
13
|
'correctness',
|
|
@@ -20,26 +15,14 @@ const FOCUS_AREAS = [
|
|
|
20
15
|
'regressions',
|
|
21
16
|
'tests',
|
|
22
17
|
];
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
'Get severity rating and categorization.',
|
|
26
|
-
generate_review_summary: 'Call `generate_review_summary` for a concise digest and merge recommendation.',
|
|
27
|
-
inspect_code_quality: 'Call `inspect_code_quality` for deep review with optional file context. ' +
|
|
28
|
-
'Uses thinking model for complex reasoning.',
|
|
29
|
-
suggest_search_replace: 'Call `suggest_search_replace` to generate verbatim search/replace fixes.',
|
|
30
|
-
generate_test_plan: 'Call `generate_test_plan` to create a verification strategy.',
|
|
31
|
-
};
|
|
18
|
+
const TOOL_DESCRIPTION_TEXT = 'Select tool for review guide.';
|
|
19
|
+
const FOCUS_DESCRIPTION_TEXT = 'Select focus area.';
|
|
32
20
|
const FOCUS_AREA_GUIDES = {
|
|
33
|
-
security: '
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
'and database query inefficiencies.',
|
|
39
|
-
regressions: 'Look for changes that could break existing behavior, removed guards, altered return types, ' +
|
|
40
|
-
'or contract changes in public APIs.',
|
|
41
|
-
tests: 'Assess test coverage gaps, missing edge case tests, flaky test patterns, ' +
|
|
42
|
-
'and untested error paths.',
|
|
21
|
+
security: 'Focus: Injection, auth, crypto, OWASP.',
|
|
22
|
+
correctness: 'Focus: Logic, edge cases, algorithms, contracts.',
|
|
23
|
+
performance: 'Focus: Complexity, allocations, I/O, queries.',
|
|
24
|
+
regressions: 'Focus: Behavior changes, guards, types, breaks.',
|
|
25
|
+
tests: 'Focus: Coverage, edge cases, flakes, error paths.',
|
|
43
26
|
};
|
|
44
27
|
function completeByPrefix(values, prefix) {
|
|
45
28
|
const matches = [];
|
|
@@ -55,15 +38,23 @@ function getGuide(guides, value, fallback) {
|
|
|
55
38
|
return guide ?? fallback(value);
|
|
56
39
|
}
|
|
57
40
|
function getToolGuide(tool) {
|
|
58
|
-
|
|
41
|
+
const contract = getToolContract(tool);
|
|
42
|
+
if (!contract) {
|
|
43
|
+
return `Use \`${tool}\` to analyze your code changes.`;
|
|
44
|
+
}
|
|
45
|
+
const { thinkingBudget } = contract;
|
|
46
|
+
const modelLine = thinkingBudget !== undefined
|
|
47
|
+
? `Model: ${contract.model} (thinking budget ${thinkingBudget}, output cap ${contract.maxOutputTokens}).`
|
|
48
|
+
: `Model: ${contract.model} (output cap ${contract.maxOutputTokens}).`;
|
|
49
|
+
return `Tool: ${contract.name}\n${modelLine}\nOutput: ${contract.outputShape}\nUse: ${contract.purpose}`;
|
|
59
50
|
}
|
|
60
51
|
function getFocusAreaGuide(focusArea) {
|
|
61
52
|
return getGuide(FOCUS_AREA_GUIDES, focusArea, (area) => `Focus on ${area} concerns.`);
|
|
62
53
|
}
|
|
63
|
-
|
|
54
|
+
function registerHelpPrompt(server, instructions) {
|
|
64
55
|
server.registerPrompt(HELP_PROMPT_NAME, {
|
|
65
56
|
title: HELP_PROMPT_TITLE,
|
|
66
|
-
description:
|
|
57
|
+
description: HELP_PROMPT_DESCRIPTION,
|
|
67
58
|
}, () => ({
|
|
68
59
|
description: HELP_PROMPT_DESCRIPTION,
|
|
69
60
|
messages: [
|
|
@@ -76,16 +67,30 @@ export function registerAllPrompts(server, instructions) {
|
|
|
76
67
|
},
|
|
77
68
|
],
|
|
78
69
|
}));
|
|
70
|
+
}
|
|
71
|
+
function buildReviewGuideText(tool, focusArea) {
|
|
72
|
+
return (`# Code Review Guide\n\n` +
|
|
73
|
+
`## Tool: \`${tool}\`\n${getToolGuide(tool)}\n\n` +
|
|
74
|
+
`## Focus Area: ${focusArea}\n${getFocusAreaGuide(focusArea)}\n\n` +
|
|
75
|
+
`## Example: Finding → Patch\n\n` +
|
|
76
|
+
`Given a finding from \`inspect_code_quality\`:\n` +
|
|
77
|
+
`- **title:** "Uncaught promise rejection in retry loop"\n` +
|
|
78
|
+
`- **details:** "The catch block swallows errors without logging."\n\n` +
|
|
79
|
+
`Call \`suggest_search_replace\` with those values. It returns:\n` +
|
|
80
|
+
'```\n' +
|
|
81
|
+
`blocks[0].search: " } catch {\\n }"\n` +
|
|
82
|
+
`blocks[0].replace: " } catch (err) {\\n logger.error(err);\\n }"\n` +
|
|
83
|
+
'```\n\n' +
|
|
84
|
+
`Validate that \`blocks[].search\` matches file content verbatim before applying.\n\n` +
|
|
85
|
+
`> Tip: Run \`get-help\` for full server documentation.`);
|
|
86
|
+
}
|
|
87
|
+
function registerReviewGuidePrompt(server) {
|
|
79
88
|
server.registerPrompt(REVIEW_GUIDE_PROMPT_NAME, {
|
|
80
89
|
title: REVIEW_GUIDE_PROMPT_TITLE,
|
|
81
90
|
description: REVIEW_GUIDE_PROMPT_DESCRIPTION,
|
|
82
91
|
argsSchema: {
|
|
83
|
-
tool: completable(z
|
|
84
|
-
|
|
85
|
-
.describe('Which review tool to use: analyze_pr_impact, generate_review_summary, etc.'), (value) => completeByPrefix(TOOLS, value)),
|
|
86
|
-
focusArea: completable(z
|
|
87
|
-
.string()
|
|
88
|
-
.describe('Focus area: security, correctness, performance, regressions, or tests'), (value) => completeByPrefix(FOCUS_AREAS, value)),
|
|
92
|
+
tool: completable(z.string().describe(TOOL_DESCRIPTION_TEXT), (value) => completeByPrefix(TOOLS, value)),
|
|
93
|
+
focusArea: completable(z.string().describe(FOCUS_DESCRIPTION_TEXT), (value) => completeByPrefix(FOCUS_AREAS, value)),
|
|
89
94
|
},
|
|
90
95
|
}, ({ tool, focusArea }) => ({
|
|
91
96
|
description: `Code review guide: ${tool} / ${focusArea}`,
|
|
@@ -94,12 +99,13 @@ export function registerAllPrompts(server, instructions) {
|
|
|
94
99
|
role: 'user',
|
|
95
100
|
content: {
|
|
96
101
|
type: 'text',
|
|
97
|
-
text:
|
|
98
|
-
`## Tool: \`${tool}\`\n${getToolGuide(tool)}\n\n` +
|
|
99
|
-
`## Focus Area: ${focusArea}\n${getFocusAreaGuide(focusArea)}\n\n` +
|
|
100
|
-
`> Tip: Run \`get-help\` for full server documentation.`,
|
|
102
|
+
text: buildReviewGuideText(tool, focusArea),
|
|
101
103
|
},
|
|
102
104
|
},
|
|
103
105
|
],
|
|
104
106
|
}));
|
|
105
107
|
}
|
|
108
|
+
export function registerAllPrompts(server, instructions) {
|
|
109
|
+
registerHelpPrompt(server, instructions);
|
|
110
|
+
registerReviewGuidePrompt(server);
|
|
111
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
export declare function registerAllResources(server: McpServer, instructions: string): void;
|
package/dist/resources/index.js
CHANGED
|
@@ -1,23 +1,104 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { ResourceTemplate, } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { DIFF_RESOURCE_URI, getDiff } from '../lib/diff-store.js';
|
|
3
|
+
import { buildServerConfig } from './server-config.js';
|
|
4
|
+
import { buildToolCatalog } from './tool-catalog.js';
|
|
5
|
+
import { getToolInfo, getToolInfoNames } from './tool-info.js';
|
|
6
|
+
import { buildWorkflowGuide } from './workflows.js';
|
|
3
7
|
const RESOURCE_MIME_TYPE = 'text/markdown';
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const RESOURCE_AUDIENCE = ['assistant'];
|
|
9
|
+
function createMarkdownContent(uri, text) {
|
|
10
|
+
return { uri: uri.href, mimeType: RESOURCE_MIME_TYPE, text };
|
|
11
|
+
}
|
|
12
|
+
const STATIC_RESOURCES = [
|
|
13
|
+
{
|
|
14
|
+
id: 'server-instructions',
|
|
15
|
+
uri: 'internal://instructions',
|
|
16
|
+
title: 'Server Instructions',
|
|
17
|
+
description: 'Complete server usage instructions.',
|
|
10
18
|
priority: 0.8,
|
|
19
|
+
content: () => '', // placeholder — resolved at registration time
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'tool-catalog',
|
|
23
|
+
uri: 'internal://tool-catalog',
|
|
24
|
+
title: 'Tool Catalog',
|
|
25
|
+
description: 'Tool reference: models, params, outputs, data flow.',
|
|
26
|
+
priority: 1.0,
|
|
27
|
+
content: buildToolCatalog,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'workflows',
|
|
31
|
+
uri: 'internal://workflows',
|
|
32
|
+
title: 'Workflow Reference',
|
|
33
|
+
description: 'Recommended workflows and tool sequences.',
|
|
34
|
+
priority: 0.9,
|
|
35
|
+
content: buildWorkflowGuide,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 'server-config',
|
|
39
|
+
uri: 'internal://server-config',
|
|
40
|
+
title: 'Server Configuration',
|
|
41
|
+
description: 'Runtime configuration and limits.',
|
|
42
|
+
priority: 0.7,
|
|
43
|
+
content: buildServerConfig,
|
|
11
44
|
},
|
|
12
|
-
|
|
45
|
+
];
|
|
46
|
+
function registerStaticResource(server, def, contentOverride) {
|
|
47
|
+
const content = contentOverride ?? def.content();
|
|
48
|
+
server.registerResource(def.id, def.uri, {
|
|
49
|
+
title: def.title,
|
|
50
|
+
description: def.description,
|
|
51
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
52
|
+
annotations: {
|
|
53
|
+
audience: RESOURCE_AUDIENCE,
|
|
54
|
+
priority: def.priority,
|
|
55
|
+
},
|
|
56
|
+
}, (uri) => ({ contents: [createMarkdownContent(uri, content)] }));
|
|
57
|
+
}
|
|
58
|
+
function registerToolInfoResources(server) {
|
|
59
|
+
const toolNames = getToolInfoNames();
|
|
60
|
+
server.registerResource('tool-info', new ResourceTemplate('internal://tool-info/{toolName}', {
|
|
61
|
+
list: undefined,
|
|
62
|
+
complete: {
|
|
63
|
+
toolName: (value) => toolNames.filter((name) => name.startsWith(value)),
|
|
64
|
+
},
|
|
65
|
+
}), {
|
|
66
|
+
title: 'Tool Info',
|
|
67
|
+
description: 'Per-tool reference: model, params, output, gotchas.',
|
|
68
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
69
|
+
annotations: {
|
|
70
|
+
audience: RESOURCE_AUDIENCE,
|
|
71
|
+
priority: 0.6,
|
|
72
|
+
},
|
|
73
|
+
}, (uri, { toolName }) => {
|
|
74
|
+
const name = typeof toolName === 'string' ? toolName : '';
|
|
75
|
+
const info = getToolInfo(name);
|
|
76
|
+
const text = info ?? `Unknown tool: ${name}`;
|
|
77
|
+
return { contents: [createMarkdownContent(uri, text)] };
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function registerDiffResource(server) {
|
|
81
|
+
server.registerResource('diff-current', new ResourceTemplate(DIFF_RESOURCE_URI, { list: undefined }), {
|
|
82
|
+
title: 'Current Diff',
|
|
83
|
+
description: 'The most recently generated diff, cached by generate_diff. Read by all review tools automatically.',
|
|
84
|
+
mimeType: 'text/x-patch',
|
|
85
|
+
annotations: {
|
|
86
|
+
audience: ['assistant'],
|
|
87
|
+
priority: 1.0,
|
|
88
|
+
},
|
|
89
|
+
}, (uri) => {
|
|
90
|
+
const slot = getDiff();
|
|
91
|
+
const text = slot
|
|
92
|
+
? `# Diff — ${slot.mode} — ${slot.generatedAt}\n# ${slot.stats.files} file(s), +${slot.stats.added} -${slot.stats.deleted}\n\n${slot.diff}`
|
|
93
|
+
: '# No diff cached. Call generate_diff first.';
|
|
94
|
+
return { contents: [{ uri: uri.href, mimeType: 'text/x-patch', text }] };
|
|
95
|
+
});
|
|
96
|
+
}
|
|
13
97
|
export function registerAllResources(server, instructions) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
}));
|
|
98
|
+
for (const def of STATIC_RESOURCES) {
|
|
99
|
+
const override = def.id === 'server-instructions' ? instructions : undefined;
|
|
100
|
+
registerStaticResource(server, def, override);
|
|
101
|
+
}
|
|
102
|
+
registerToolInfoResources(server);
|
|
103
|
+
registerDiffResource(server);
|
|
23
104
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildServerInstructions(): string;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { getToolContracts } from '../lib/tool-contracts.js';
|
|
2
|
+
import { getSharedConstraints } from './tool-info.js';
|
|
3
|
+
const PROMPT_LIST = [
|
|
4
|
+
'- `get-help`: Returns these server instructions.',
|
|
5
|
+
'- `review-guide`: Workflow guide for a selected tool and focus area.',
|
|
6
|
+
];
|
|
7
|
+
const RESOURCE_LIST = [
|
|
8
|
+
'- `internal://instructions`: This document.',
|
|
9
|
+
'- `internal://tool-catalog`: Tool matrix and cross-tool data flow.',
|
|
10
|
+
'- `internal://workflows`: Recommended multi-step tool workflows.',
|
|
11
|
+
'- `internal://server-config`: Runtime limits and model configuration.',
|
|
12
|
+
'- `internal://tool-info/{toolName}`: Per-tool contract details.',
|
|
13
|
+
'- `diff://current`: Cached diff from the most recent generate_diff run.',
|
|
14
|
+
];
|
|
15
|
+
function formatParameterLine(parameter) {
|
|
16
|
+
const required = parameter.required ? 'required' : 'optional';
|
|
17
|
+
return `- \`${parameter.name}\` (${parameter.type}, ${required}; ${parameter.constraints})`;
|
|
18
|
+
}
|
|
19
|
+
function formatToolSection(contract) {
|
|
20
|
+
const parameterLines = contract.params.map((parameter) => formatParameterLine(parameter));
|
|
21
|
+
if (contract.model === 'none') {
|
|
22
|
+
// Synchronous built-in tool (no Gemini call)
|
|
23
|
+
return `### \`${contract.name}\`
|
|
24
|
+
- Purpose: ${contract.purpose}
|
|
25
|
+
- Model: \`none\` (synchronous built-in)
|
|
26
|
+
- Parameters:
|
|
27
|
+
${parameterLines.join('\n')}
|
|
28
|
+
- Output shape: \`${contract.outputShape}\``;
|
|
29
|
+
}
|
|
30
|
+
const thinkingLine = contract.thinkingBudget === undefined
|
|
31
|
+
? '- Thinking budget: disabled'
|
|
32
|
+
: `- Thinking budget: ${contract.thinkingBudget}`;
|
|
33
|
+
return `### \`${contract.name}\`
|
|
34
|
+
- Purpose: ${contract.purpose}
|
|
35
|
+
- Model: \`${contract.model}\`
|
|
36
|
+
- Timeout: ${Math.round(contract.timeoutMs / 1_000)}s
|
|
37
|
+
${thinkingLine}
|
|
38
|
+
- Max output tokens: ${contract.maxOutputTokens}
|
|
39
|
+
- Parameters:
|
|
40
|
+
${parameterLines.join('\n')}
|
|
41
|
+
- Output shape: \`${contract.outputShape}\``;
|
|
42
|
+
}
|
|
43
|
+
export function buildServerInstructions() {
|
|
44
|
+
const contracts = getToolContracts();
|
|
45
|
+
const toolNames = contracts
|
|
46
|
+
.map((contract) => `\`${contract.name}\``)
|
|
47
|
+
.join(', ');
|
|
48
|
+
const toolSections = contracts.map((contract) => formatToolSection(contract));
|
|
49
|
+
const constraintLines = getSharedConstraints().map((constraint) => `- ${constraint}`);
|
|
50
|
+
return `# CODE REVIEW ANALYST MCP INSTRUCTIONS
|
|
51
|
+
|
|
52
|
+
## CORE CAPABILITY
|
|
53
|
+
- Domain: Gemini-powered code review analysis over unified diffs.
|
|
54
|
+
- Tools: ${toolNames}
|
|
55
|
+
- Transport: stdio with task lifecycle support.
|
|
56
|
+
|
|
57
|
+
## PROMPTS
|
|
58
|
+
${PROMPT_LIST.join('\n')}
|
|
59
|
+
|
|
60
|
+
## RESOURCES
|
|
61
|
+
${RESOURCE_LIST.join('\n')}
|
|
62
|
+
|
|
63
|
+
## TOOL CONTRACTS
|
|
64
|
+
${toolSections.join('\n\n')}
|
|
65
|
+
|
|
66
|
+
## SHARED CONSTRAINTS
|
|
67
|
+
${constraintLines.join('\n')}
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildServerConfig(): string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createCachedEnvInt } from '../lib/env-config.js';
|
|
2
|
+
import { FLASH_MODEL } from '../lib/model-config.js';
|
|
3
|
+
import { getToolContracts } from '../lib/tool-contracts.js';
|
|
4
|
+
const DEFAULT_MAX_DIFF_CHARS = 120_000;
|
|
5
|
+
const DEFAULT_MAX_CONTEXT_CHARS = 500_000;
|
|
6
|
+
const DEFAULT_MAX_CONCURRENT_CALLS = 10;
|
|
7
|
+
const DEFAULT_CONCURRENT_WAIT_MS = 2_000;
|
|
8
|
+
const DEFAULT_SAFETY_THRESHOLD = 'BLOCK_NONE';
|
|
9
|
+
const GEMINI_HARM_BLOCK_THRESHOLD_ENV_VAR = 'GEMINI_HARM_BLOCK_THRESHOLD';
|
|
10
|
+
const GEMINI_MODEL_ENV_VAR = 'GEMINI_MODEL';
|
|
11
|
+
const diffCharsConfig = createCachedEnvInt('MAX_DIFF_CHARS', DEFAULT_MAX_DIFF_CHARS);
|
|
12
|
+
const contextCharsConfig = createCachedEnvInt('MAX_CONTEXT_CHARS', DEFAULT_MAX_CONTEXT_CHARS);
|
|
13
|
+
const concurrentCallsConfig = createCachedEnvInt('MAX_CONCURRENT_CALLS', DEFAULT_MAX_CONCURRENT_CALLS);
|
|
14
|
+
const concurrentWaitConfig = createCachedEnvInt('MAX_CONCURRENT_CALLS_WAIT_MS', DEFAULT_CONCURRENT_WAIT_MS);
|
|
15
|
+
function getModelOverride() {
|
|
16
|
+
return process.env[GEMINI_MODEL_ENV_VAR] ?? FLASH_MODEL;
|
|
17
|
+
}
|
|
18
|
+
function getSafetyThreshold() {
|
|
19
|
+
return (process.env[GEMINI_HARM_BLOCK_THRESHOLD_ENV_VAR] ?? DEFAULT_SAFETY_THRESHOLD);
|
|
20
|
+
}
|
|
21
|
+
function formatNumber(value) {
|
|
22
|
+
return new Intl.NumberFormat('en-US').format(value);
|
|
23
|
+
}
|
|
24
|
+
function formatTimeout(ms) {
|
|
25
|
+
return `${Math.round(ms / 1_000)}s`;
|
|
26
|
+
}
|
|
27
|
+
function formatThinkingBudget(budget) {
|
|
28
|
+
return budget !== undefined ? formatNumber(budget) : '—';
|
|
29
|
+
}
|
|
30
|
+
export function buildServerConfig() {
|
|
31
|
+
const maxDiffChars = diffCharsConfig.get();
|
|
32
|
+
const maxContextChars = contextCharsConfig.get();
|
|
33
|
+
const maxConcurrent = concurrentCallsConfig.get();
|
|
34
|
+
const concurrentWaitMs = concurrentWaitConfig.get();
|
|
35
|
+
const defaultModel = getModelOverride();
|
|
36
|
+
const safetyThreshold = getSafetyThreshold();
|
|
37
|
+
const toolRows = getToolContracts()
|
|
38
|
+
.filter((contract) => contract.model !== 'none')
|
|
39
|
+
.map((contract) => {
|
|
40
|
+
return `| \`${contract.name}\` | \`${contract.model}\` | ${formatThinkingBudget(contract.thinkingBudget)} | ${formatTimeout(contract.timeoutMs)} | ${formatNumber(contract.maxOutputTokens)} |`;
|
|
41
|
+
})
|
|
42
|
+
.join('\n');
|
|
43
|
+
return `# Server Configuration
|
|
44
|
+
|
|
45
|
+
## Input Limits
|
|
46
|
+
|
|
47
|
+
| Limit | Value | Env |
|
|
48
|
+
|-------|-------|-----|
|
|
49
|
+
| Diff limit | ${formatNumber(maxDiffChars)} chars | \`MAX_DIFF_CHARS\` |
|
|
50
|
+
| Context limit (inspect) | ${formatNumber(maxContextChars)} chars | \`MAX_CONTEXT_CHARS\` |
|
|
51
|
+
| Concurrency limit | ${maxConcurrent} | \`MAX_CONCURRENT_CALLS\` |
|
|
52
|
+
| Wait timeout | ${formatNumber(concurrentWaitMs)}ms | \`MAX_CONCURRENT_CALLS_WAIT_MS\` |
|
|
53
|
+
|
|
54
|
+
## Model Assignments
|
|
55
|
+
|
|
56
|
+
Default model: \`${defaultModel}\` (override with \`GEMINI_MODEL\`)
|
|
57
|
+
|
|
58
|
+
| Tool | Model | Thinking Budget | Timeout | Max Output Tokens |
|
|
59
|
+
|------|-------|----------------|---------|-------------------|
|
|
60
|
+
${toolRows}
|
|
61
|
+
|
|
62
|
+
## Safety
|
|
63
|
+
|
|
64
|
+
- Harm block threshold: \`${safetyThreshold}\`
|
|
65
|
+
- Override with \`GEMINI_HARM_BLOCK_THRESHOLD\` (BLOCK_NONE, BLOCK_ONLY_HIGH, BLOCK_MEDIUM_AND_ABOVE, BLOCK_LOW_AND_ABOVE)
|
|
66
|
+
|
|
67
|
+
## API Keys
|
|
68
|
+
|
|
69
|
+
- Set \`GEMINI_API_KEY\` or \`GOOGLE_API_KEY\` environment variable (required)
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildToolCatalog(): string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { buildCoreContextPack } from './tool-info.js';
|
|
2
|
+
const TOOL_CATALOG_CONTENT = `# Tool Catalog Details
|
|
3
|
+
|
|
4
|
+
## Optional Parameters
|
|
5
|
+
|
|
6
|
+
- \`language\`: Primary language hint (auto-detects). All tools except \`suggest_search_replace\`.
|
|
7
|
+
- \`focusAreas\`: Focus tags (security, performance, etc.). \`inspect_code_quality\` only.
|
|
8
|
+
- \`maxFindings\`: Output cap (1–25). \`inspect_code_quality\` only.
|
|
9
|
+
- \`files\`: File context (max 20 files, 100K chars/file). \`inspect_code_quality\` only.
|
|
10
|
+
- \`testFramework\`: Framework hint. \`generate_test_plan\` only.
|
|
11
|
+
- \`maxTestCases\`: Output cap (1–30). \`generate_test_plan\` only.
|
|
12
|
+
|
|
13
|
+
## Cross-Tool Data Flow
|
|
14
|
+
|
|
15
|
+
\`\`\`
|
|
16
|
+
analyze_pr_impact ──→ severity/categories ──→ triage decision
|
|
17
|
+
│
|
|
18
|
+
generate_review_summary ──→ overallRisk ──────┤
|
|
19
|
+
▼
|
|
20
|
+
inspect_code_quality
|
|
21
|
+
│
|
|
22
|
+
findings[].title ──→ suggest_search_replace.findingTitle
|
|
23
|
+
findings[].explanation ──→ suggest_search_replace.findingDetails
|
|
24
|
+
│
|
|
25
|
+
diff ─────┴──→ generate_test_plan
|
|
26
|
+
\`\`\`
|
|
27
|
+
|
|
28
|
+
## When to Use Each Tool
|
|
29
|
+
|
|
30
|
+
- **Triage**: \`analyze_pr_impact\`, \`generate_review_summary\` (Flash).
|
|
31
|
+
- **Inspection**: \`inspect_code_quality\` (Pro).
|
|
32
|
+
- **Fixes**: \`suggest_search_replace\` (one finding/call).
|
|
33
|
+
- **Tests**: \`generate_test_plan\`.
|
|
34
|
+
- **Complexity**: \`analyze_time_space_complexity\`.
|
|
35
|
+
- **Breaking API**: \`detect_api_breaking_changes\`.
|
|
36
|
+
`;
|
|
37
|
+
export function buildToolCatalog() {
|
|
38
|
+
return `${buildCoreContextPack()}\n\n${TOOL_CATALOG_CONTENT}`;
|
|
39
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function buildCoreContextPack(): string;
|
|
2
|
+
export declare function getSharedConstraints(): readonly string[];
|
|
3
|
+
export declare function getToolInfoNames(): string[];
|
|
4
|
+
export declare function getToolInfo(toolName: string): string | undefined;
|
|
5
|
+
export declare function getToolPurpose(toolName: string): string | undefined;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { getToolContract, getToolContracts } from '../lib/tool-contracts.js';
|
|
2
|
+
const GLOBAL_CONSTRAINTS = [
|
|
3
|
+
'Diff budget: <= 120K chars.',
|
|
4
|
+
'Structured output: tools return both `structuredContent` and JSON text `content`.',
|
|
5
|
+
];
|
|
6
|
+
const numberFormatter = new Intl.NumberFormat('en-US');
|
|
7
|
+
function formatNumber(value) {
|
|
8
|
+
return numberFormatter.format(value);
|
|
9
|
+
}
|
|
10
|
+
function formatTimeout(timeoutMs) {
|
|
11
|
+
return `${Math.round(timeoutMs / 1_000)}s`;
|
|
12
|
+
}
|
|
13
|
+
function formatThinkingBudget(thinkingBudget) {
|
|
14
|
+
return thinkingBudget === undefined ? '-' : formatNumber(thinkingBudget);
|
|
15
|
+
}
|
|
16
|
+
function formatOutputTokens(maxOutputTokens) {
|
|
17
|
+
return formatNumber(maxOutputTokens);
|
|
18
|
+
}
|
|
19
|
+
function formatParameterRow(entry) {
|
|
20
|
+
return `| ${entry.name} | ${entry.type} | ${entry.required ? 'Yes' : 'No'} | ${entry.constraints} | ${entry.description} |`;
|
|
21
|
+
}
|
|
22
|
+
function toToolInfoEntry(contract) {
|
|
23
|
+
const parameterRows = [
|
|
24
|
+
'| Param | Type | Required | Constraints | Description |',
|
|
25
|
+
'|-------|------|----------|-------------|-------------|',
|
|
26
|
+
...contract.params.map((parameter) => formatParameterRow(parameter)),
|
|
27
|
+
];
|
|
28
|
+
return {
|
|
29
|
+
name: contract.name,
|
|
30
|
+
purpose: contract.purpose,
|
|
31
|
+
model: contract.model,
|
|
32
|
+
thinkingBudget: formatThinkingBudget(contract.thinkingBudget),
|
|
33
|
+
timeout: formatTimeout(contract.timeoutMs),
|
|
34
|
+
maxOutputTokens: formatOutputTokens(contract.maxOutputTokens),
|
|
35
|
+
params: parameterRows.join('\n'),
|
|
36
|
+
outputShape: `\`${contract.outputShape}\``,
|
|
37
|
+
gotchas: contract.gotchas,
|
|
38
|
+
crossToolFlow: contract.crossToolFlow,
|
|
39
|
+
...(contract.constraints
|
|
40
|
+
? { constraints: contract.constraints }
|
|
41
|
+
: undefined),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const TOOL_INFO_ENTRIES = Object.fromEntries(getToolContracts().map((contract) => [
|
|
45
|
+
contract.name,
|
|
46
|
+
toToolInfoEntry(contract),
|
|
47
|
+
]));
|
|
48
|
+
const TOOL_NAMES = Object.keys(TOOL_INFO_ENTRIES).sort((a, b) => a.localeCompare(b));
|
|
49
|
+
function collectToolConstraints(entries) {
|
|
50
|
+
const constraints = new Set();
|
|
51
|
+
for (const [toolName, entry] of Object.entries(entries)) {
|
|
52
|
+
for (const constraint of entry.constraints ?? []) {
|
|
53
|
+
constraints.add(`\`${toolName}\`: ${constraint}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return Array.from(constraints).sort((a, b) => a.localeCompare(b));
|
|
57
|
+
}
|
|
58
|
+
function formatToolInfo(entry) {
|
|
59
|
+
return `# ${entry.name}
|
|
60
|
+
|
|
61
|
+
## Purpose
|
|
62
|
+
${entry.purpose}
|
|
63
|
+
|
|
64
|
+
## Model
|
|
65
|
+
\`${entry.model}\` (thinking budget: ${entry.thinkingBudget}, timeout: ${entry.timeout}, max output tokens: ${entry.maxOutputTokens})
|
|
66
|
+
|
|
67
|
+
## Parameters
|
|
68
|
+
${entry.params}
|
|
69
|
+
|
|
70
|
+
## Output Shape
|
|
71
|
+
${entry.outputShape}
|
|
72
|
+
|
|
73
|
+
## Gotchas
|
|
74
|
+
${entry.gotchas.map((g) => `- ${g}`).join('\n')}
|
|
75
|
+
|
|
76
|
+
## Cross-Tool Flow
|
|
77
|
+
${entry.crossToolFlow.map((f) => `- ${f}`).join('\n')}
|
|
78
|
+
`;
|
|
79
|
+
}
|
|
80
|
+
function formatCompactToolRow(entry) {
|
|
81
|
+
return `| \`${entry.name}\` | ${entry.model} | ${entry.timeout} | ${entry.maxOutputTokens} | ${entry.purpose} |`;
|
|
82
|
+
}
|
|
83
|
+
export function buildCoreContextPack() {
|
|
84
|
+
const rows = TOOL_NAMES.flatMap((toolName) => {
|
|
85
|
+
const entry = TOOL_INFO_ENTRIES[toolName];
|
|
86
|
+
return entry ? [formatCompactToolRow(entry)] : [];
|
|
87
|
+
});
|
|
88
|
+
return `# Core Context Pack
|
|
89
|
+
|
|
90
|
+
## Server Essentials
|
|
91
|
+
- Domain: Gemini-powered MCP server for diff-based code review.
|
|
92
|
+
- Surface: 7 review tools + internal resources + guided prompts.
|
|
93
|
+
- Transport: stdio with task lifecycle support.
|
|
94
|
+
|
|
95
|
+
## Tool Matrix
|
|
96
|
+
| Tool | Model | Timeout | Max Output Tokens | Purpose |
|
|
97
|
+
|------|-------|---------|-------------------|---------|
|
|
98
|
+
${rows.join('\n')}
|
|
99
|
+
|
|
100
|
+
## Shared Constraints
|
|
101
|
+
${getSharedConstraints()
|
|
102
|
+
.map((constraint) => `- ${constraint}`)
|
|
103
|
+
.join('\n')}
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
export function getSharedConstraints() {
|
|
107
|
+
const toolConstraints = collectToolConstraints(TOOL_INFO_ENTRIES);
|
|
108
|
+
return [...GLOBAL_CONSTRAINTS, ...toolConstraints];
|
|
109
|
+
}
|
|
110
|
+
export function getToolInfoNames() {
|
|
111
|
+
return TOOL_NAMES;
|
|
112
|
+
}
|
|
113
|
+
export function getToolInfo(toolName) {
|
|
114
|
+
const entry = TOOL_INFO_ENTRIES[toolName];
|
|
115
|
+
if (!entry) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
return formatToolInfo(entry);
|
|
119
|
+
}
|
|
120
|
+
export function getToolPurpose(toolName) {
|
|
121
|
+
return getToolContract(toolName)?.purpose;
|
|
122
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildWorkflowGuide(): string;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { getToolContracts } from '../lib/tool-contracts.js';
|
|
2
|
+
import { getSharedConstraints } from './tool-info.js';
|
|
3
|
+
function buildWorkflowToolReference() {
|
|
4
|
+
const contracts = getToolContracts();
|
|
5
|
+
return contracts
|
|
6
|
+
.map((c) => `### \`${c.name}\`\n- **Purpose:** ${c.purpose}\n- **Model:** ${c.model}\n- **Output:** \`${c.outputShape}\``)
|
|
7
|
+
.join('\n\n');
|
|
8
|
+
}
|
|
9
|
+
export function buildWorkflowGuide() {
|
|
10
|
+
return `# Workflow Reference
|
|
11
|
+
|
|
12
|
+
## A: Full PR Review
|
|
13
|
+
|
|
14
|
+
1. \`generate_review_summary\` → \`{overallRisk, keyChanges[], recommendation, stats}\`
|
|
15
|
+
2. \`inspect_code_quality\` → \`{findings[], overallRisk, contextualInsights[]}\`
|
|
16
|
+
3. For each finding: \`suggest_search_replace\` → \`{blocks[]}\`
|
|
17
|
+
|
|
18
|
+
> One finding per \`suggest_search_replace\` call.
|
|
19
|
+
|
|
20
|
+
## B: Impact Assessment
|
|
21
|
+
|
|
22
|
+
1. \`analyze_pr_impact\` → \`{severity, categories[], breakingChanges[], rollbackComplexity}\`
|
|
23
|
+
2. \`generate_review_summary\` → complementary merge recommendation
|
|
24
|
+
|
|
25
|
+
> Use when categorization (breaking, api) or rollback assessment needed.
|
|
26
|
+
|
|
27
|
+
## C: Remediation Loop
|
|
28
|
+
|
|
29
|
+
1. \`inspect_code_quality\` → \`{findings[]}\`
|
|
30
|
+
2. Pick one finding. \`suggest_search_replace\` → \`{blocks[]}\`
|
|
31
|
+
3. Validate \`blocks[].search\` matches file content verbatim.
|
|
32
|
+
|
|
33
|
+
> Never batch findings.
|
|
34
|
+
|
|
35
|
+
## D: Test Coverage
|
|
36
|
+
|
|
37
|
+
1. \`generate_test_plan\` → \`{testCases[], coverageSummary}\`
|
|
38
|
+
2. Review by priority: \`must_have\` → \`should_have\` → \`nice_to_have\`
|
|
39
|
+
|
|
40
|
+
> Combine with \`inspect_code_quality\`.
|
|
41
|
+
|
|
42
|
+
## E: Complexity & Breaking Changes
|
|
43
|
+
|
|
44
|
+
1. \`analyze_time_space_complexity\` → \`{timeComplexity, spaceComplexity, isDegradation}\`
|
|
45
|
+
2. \`detect_api_breaking_changes\` → \`{hasBreakingChanges, breakingChanges[]}\`
|
|
46
|
+
|
|
47
|
+
> Use for algorithm or API changes. Diff-only input.
|
|
48
|
+
|
|
49
|
+
## Shared Constraints
|
|
50
|
+
${getSharedConstraints()
|
|
51
|
+
.map((constraint) => `- ${constraint}`)
|
|
52
|
+
.join('\n')}
|
|
53
|
+
|
|
54
|
+
## Tool Reference
|
|
55
|
+
|
|
56
|
+
${buildWorkflowToolReference()}
|
|
57
|
+
|
|
58
|
+
## Output Shape Reference
|
|
59
|
+
|
|
60
|
+
### Finding
|
|
61
|
+
\`{severity, file, line, title, explanation, recommendation}\`
|
|
62
|
+
|
|
63
|
+
### Search/Replace Block
|
|
64
|
+
\`{file, search, replace, explanation}\`
|
|
65
|
+
|
|
66
|
+
### Test Case
|
|
67
|
+
\`{name, type, file, description, pseudoCode, priority}\`
|
|
68
|
+
|
|
69
|
+
### Breaking Change
|
|
70
|
+
\`{element, natureOfChange, consumerImpact, suggestedMitigation}\`
|
|
71
|
+
`;
|
|
72
|
+
}
|