@j0hanz/code-review-analyst-mcp 1.1.0 → 1.2.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/README.md +203 -193
- package/dist/index.js +18 -15
- package/dist/instructions.md +83 -58
- package/dist/lib/context-budget.d.ts +8 -0
- package/dist/lib/context-budget.js +30 -0
- package/dist/lib/diff-budget.d.ts +3 -1
- package/dist/lib/diff-budget.js +16 -19
- package/dist/lib/diff-parser.d.ts +34 -0
- package/dist/lib/diff-parser.js +114 -0
- package/dist/lib/env-config.d.ts +5 -0
- package/dist/lib/env-config.js +24 -0
- package/dist/lib/errors.d.ts +1 -0
- package/dist/lib/errors.js +9 -6
- package/dist/lib/gemini-schema.d.ts +3 -1
- package/dist/lib/gemini-schema.js +18 -17
- package/dist/lib/gemini.d.ts +1 -0
- package/dist/lib/gemini.js +216 -111
- package/dist/lib/model-config.d.ts +17 -0
- package/dist/lib/model-config.js +19 -0
- package/dist/lib/tool-factory.d.ts +20 -8
- package/dist/lib/tool-factory.js +264 -67
- package/dist/lib/tool-response.d.ts +9 -2
- package/dist/lib/tool-response.js +29 -14
- package/dist/lib/types.d.ts +8 -3
- package/dist/prompts/index.js +35 -15
- package/dist/resources/index.js +10 -9
- package/dist/schemas/inputs.d.ts +27 -15
- package/dist/schemas/inputs.js +59 -21
- package/dist/schemas/outputs.d.ts +130 -7
- package/dist/schemas/outputs.js +170 -40
- package/dist/server.d.ts +5 -1
- package/dist/server.js +32 -24
- package/dist/tools/analyze-pr-impact.d.ts +2 -0
- package/dist/tools/analyze-pr-impact.js +46 -0
- package/dist/tools/generate-review-summary.d.ts +2 -0
- package/dist/tools/generate-review-summary.js +67 -0
- package/dist/tools/generate-test-plan.d.ts +2 -0
- package/dist/tools/generate-test-plan.js +56 -0
- package/dist/tools/index.js +10 -6
- package/dist/tools/inspect-code-quality.d.ts +4 -0
- package/dist/tools/inspect-code-quality.js +107 -0
- package/dist/tools/suggest-search-replace.d.ts +2 -0
- package/dist/tools/suggest-search-replace.js +46 -0
- package/package.json +3 -2
- package/dist/tools/review-diff.d.ts +0 -2
- package/dist/tools/review-diff.js +0 -42
- package/dist/tools/risk-score.d.ts +0 -2
- package/dist/tools/risk-score.js +0 -34
- package/dist/tools/suggest-patch.d.ts +0 -2
- package/dist/tools/suggest-patch.js +0 -35
package/dist/lib/tool-factory.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { DefaultOutputSchema } from '../schemas/outputs.js';
|
|
3
|
-
import { getErrorMessage } from './errors.js';
|
|
3
|
+
import { getErrorMessage, RETRYABLE_UPSTREAM_ERROR_PATTERN } from './errors.js';
|
|
4
4
|
import { stripJsonSchemaConstraints } from './gemini-schema.js';
|
|
5
|
-
import { generateStructuredJson } from './gemini.js';
|
|
5
|
+
import { generateStructuredJson, getCurrentRequestId } from './gemini.js';
|
|
6
6
|
import { createErrorToolResponse, createToolResponse, } from './tool-response.js';
|
|
7
|
+
const DEFAULT_TASK_TTL_MS = 30 * 60 * 1_000;
|
|
8
|
+
const TASK_PROGRESS_TOTAL = 4;
|
|
9
|
+
const INPUT_VALIDATION_FAILED = 'Input validation failed';
|
|
10
|
+
const DEFAULT_PROGRESS_CONTEXT = 'request';
|
|
11
|
+
const CANCELLED_ERROR_PATTERN = /cancelled|canceled/i;
|
|
12
|
+
const TIMEOUT_ERROR_PATTERN = /timed out|timeout/i;
|
|
13
|
+
const BUDGET_ERROR_PATTERN = /exceeds limit|max allowed size|input too large/i;
|
|
14
|
+
const BUSY_ERROR_PATTERN = /too many concurrent/i;
|
|
7
15
|
function createGeminiResponseSchema(config) {
|
|
8
16
|
const sourceSchema = config.geminiSchema ?? config.resultSchema;
|
|
9
17
|
return stripJsonSchemaConstraints(z.toJSONSchema(sourceSchema));
|
|
@@ -11,6 +19,173 @@ function createGeminiResponseSchema(config) {
|
|
|
11
19
|
function parseToolInput(input, fullInputSchema) {
|
|
12
20
|
return fullInputSchema.parse(input);
|
|
13
21
|
}
|
|
22
|
+
function createGenerationRequest(config, promptParts, responseSchema, onLog, signal) {
|
|
23
|
+
const request = {
|
|
24
|
+
systemInstruction: promptParts.systemInstruction,
|
|
25
|
+
prompt: promptParts.prompt,
|
|
26
|
+
responseSchema,
|
|
27
|
+
onLog,
|
|
28
|
+
};
|
|
29
|
+
if (config.model !== undefined) {
|
|
30
|
+
request.model = config.model;
|
|
31
|
+
}
|
|
32
|
+
if (config.thinkingBudget !== undefined) {
|
|
33
|
+
request.thinkingBudget = config.thinkingBudget;
|
|
34
|
+
}
|
|
35
|
+
if (config.timeoutMs !== undefined) {
|
|
36
|
+
request.timeoutMs = config.timeoutMs;
|
|
37
|
+
}
|
|
38
|
+
if (signal !== undefined) {
|
|
39
|
+
request.signal = signal;
|
|
40
|
+
}
|
|
41
|
+
return request;
|
|
42
|
+
}
|
|
43
|
+
function classifyErrorMeta(error, message) {
|
|
44
|
+
if (error instanceof z.ZodError || /validation/i.test(message)) {
|
|
45
|
+
return {
|
|
46
|
+
kind: 'validation',
|
|
47
|
+
retryable: false,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (CANCELLED_ERROR_PATTERN.test(message)) {
|
|
51
|
+
return {
|
|
52
|
+
kind: 'cancelled',
|
|
53
|
+
retryable: false,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (TIMEOUT_ERROR_PATTERN.test(message)) {
|
|
57
|
+
return {
|
|
58
|
+
kind: 'timeout',
|
|
59
|
+
retryable: true,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (BUDGET_ERROR_PATTERN.test(message)) {
|
|
63
|
+
return {
|
|
64
|
+
kind: 'budget',
|
|
65
|
+
retryable: false,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (RETRYABLE_UPSTREAM_ERROR_PATTERN.test(message)) {
|
|
69
|
+
return {
|
|
70
|
+
kind: 'upstream',
|
|
71
|
+
retryable: true,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (BUSY_ERROR_PATTERN.test(message)) {
|
|
75
|
+
return {
|
|
76
|
+
kind: 'upstream',
|
|
77
|
+
retryable: true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
kind: 'internal',
|
|
82
|
+
retryable: false,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async function sendTaskProgress(extra, payload) {
|
|
86
|
+
const progressToken = extra._meta?.progressToken;
|
|
87
|
+
if (typeof progressToken !== 'string' && typeof progressToken !== 'number') {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const params = {
|
|
92
|
+
progressToken,
|
|
93
|
+
progress: payload.current,
|
|
94
|
+
};
|
|
95
|
+
if (payload.total !== undefined) {
|
|
96
|
+
params.total = payload.total;
|
|
97
|
+
}
|
|
98
|
+
if (payload.message !== undefined) {
|
|
99
|
+
params.message = payload.message;
|
|
100
|
+
}
|
|
101
|
+
await extra.sendNotification({
|
|
102
|
+
method: 'notifications/progress',
|
|
103
|
+
params,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Progress is best-effort; never fail the tool call.
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function createProgressReporter(extra) {
|
|
111
|
+
let lastCurrent = 0;
|
|
112
|
+
let didSendTerminal = false;
|
|
113
|
+
return async (payload) => {
|
|
114
|
+
if (didSendTerminal) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const current = Math.max(payload.current, lastCurrent);
|
|
118
|
+
const total = payload.total !== undefined
|
|
119
|
+
? Math.max(payload.total, current)
|
|
120
|
+
: undefined;
|
|
121
|
+
await sendTaskProgress(extra, {
|
|
122
|
+
current,
|
|
123
|
+
...(total !== undefined ? { total } : {}),
|
|
124
|
+
...(payload.message !== undefined ? { message: payload.message } : {}),
|
|
125
|
+
});
|
|
126
|
+
lastCurrent = current;
|
|
127
|
+
if (total !== undefined && total === current) {
|
|
128
|
+
didSendTerminal = true;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function normalizeProgressContext(context) {
|
|
133
|
+
const compact = context?.replace(/\s+/g, ' ').trim();
|
|
134
|
+
if (!compact) {
|
|
135
|
+
return DEFAULT_PROGRESS_CONTEXT;
|
|
136
|
+
}
|
|
137
|
+
if (compact.length <= 80) {
|
|
138
|
+
return compact;
|
|
139
|
+
}
|
|
140
|
+
return `${compact.slice(0, 77)}...`;
|
|
141
|
+
}
|
|
142
|
+
function formatProgressStep(toolName, context, metadata) {
|
|
143
|
+
const prefix = metadata === 'start' ? '▸' : '◆';
|
|
144
|
+
return `${prefix} ${toolName}: ${context} [${metadata}]`;
|
|
145
|
+
}
|
|
146
|
+
function formatProgressCompletion(toolName, context, outcome) {
|
|
147
|
+
const prefix = outcome === 'completed' ? '◈' : '‣';
|
|
148
|
+
return `${prefix} ${toolName}: ${context} • ${outcome}`;
|
|
149
|
+
}
|
|
150
|
+
function toLoggingLevel(level) {
|
|
151
|
+
switch (level) {
|
|
152
|
+
case 'debug':
|
|
153
|
+
case 'info':
|
|
154
|
+
case 'notice':
|
|
155
|
+
case 'warning':
|
|
156
|
+
case 'error':
|
|
157
|
+
case 'critical':
|
|
158
|
+
case 'alert':
|
|
159
|
+
case 'emergency':
|
|
160
|
+
return level;
|
|
161
|
+
default:
|
|
162
|
+
return 'error';
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function asObjectRecord(value) {
|
|
166
|
+
if (typeof value === 'object' && value !== null) {
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
return { payload: value };
|
|
170
|
+
}
|
|
171
|
+
function createGeminiLogger(server, taskId) {
|
|
172
|
+
return async (level, data) => {
|
|
173
|
+
try {
|
|
174
|
+
await server.sendLoggingMessage({
|
|
175
|
+
level: toLoggingLevel(level),
|
|
176
|
+
logger: 'gemini',
|
|
177
|
+
data: {
|
|
178
|
+
requestId: getCurrentRequestId(),
|
|
179
|
+
taskId,
|
|
180
|
+
...asObjectRecord(data),
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// Logging is best-effort; never fail the tool call.
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
14
189
|
export function registerStructuredToolTask(server, config) {
|
|
15
190
|
const responseSchema = createGeminiResponseSchema({
|
|
16
191
|
geminiSchema: config.geminiSchema,
|
|
@@ -25,83 +200,105 @@ export function registerStructuredToolTask(server, config) {
|
|
|
25
200
|
readOnlyHint: true,
|
|
26
201
|
openWorldHint: true,
|
|
27
202
|
},
|
|
28
|
-
execution: {
|
|
29
|
-
taskSupport: 'optional',
|
|
30
|
-
},
|
|
31
203
|
}, {
|
|
32
204
|
createTask: async (input, extra) => {
|
|
33
205
|
const task = await extra.taskStore.createTask({
|
|
34
|
-
ttl: extra.taskRequestedTtl ??
|
|
206
|
+
ttl: extra.taskRequestedTtl ?? DEFAULT_TASK_TTL_MS,
|
|
35
207
|
});
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
await extra.sendNotification({
|
|
42
|
-
method: 'notifications/progress',
|
|
43
|
-
params: { progressToken, progress, total },
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
// Progress is best-effort; never fail the tool call.
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
const updateStatusMessage = async (message) => {
|
|
51
|
-
try {
|
|
52
|
-
await extra.taskStore.updateTaskStatus(task.taskId, 'working', message);
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
// statusMessage is best-effort; task may already be terminal.
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
try {
|
|
59
|
-
const onLog = async (level, data) => {
|
|
208
|
+
const runTask = async () => {
|
|
209
|
+
const reportProgress = createProgressReporter(extra);
|
|
210
|
+
let progressContext = DEFAULT_PROGRESS_CONTEXT;
|
|
211
|
+
const updateStatusMessage = async (message) => {
|
|
60
212
|
try {
|
|
61
|
-
await
|
|
62
|
-
level: level,
|
|
63
|
-
logger: 'gemini',
|
|
64
|
-
data,
|
|
65
|
-
});
|
|
213
|
+
await extra.taskStore.updateTaskStatus(task.taskId, 'working', message);
|
|
66
214
|
}
|
|
67
215
|
catch {
|
|
68
|
-
//
|
|
216
|
+
// statusMessage is best-effort; task may already be terminal.
|
|
69
217
|
}
|
|
70
218
|
};
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
219
|
+
const storeResultSafely = async (status, result) => {
|
|
220
|
+
try {
|
|
221
|
+
await extra.taskStore.storeTaskResult(task.taskId, status, result);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// storing the result failed, possibly because the task is already marked as failed due to an uncaught error. There's not much we can do at this point, so we swallow the error to avoid unhandled rejections.
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
try {
|
|
228
|
+
await reportProgress({
|
|
229
|
+
current: 0,
|
|
230
|
+
total: TASK_PROGRESS_TOTAL,
|
|
231
|
+
message: formatProgressStep(config.name, progressContext, 'start'),
|
|
232
|
+
});
|
|
233
|
+
const onLog = createGeminiLogger(server, task.taskId);
|
|
234
|
+
const inputRecord = parseToolInput(input, config.fullInputSchema);
|
|
235
|
+
progressContext = normalizeProgressContext(config.progressContext?.(inputRecord));
|
|
236
|
+
if (config.validateInput) {
|
|
237
|
+
const validationError = await config.validateInput(inputRecord);
|
|
238
|
+
if (validationError) {
|
|
239
|
+
const validationMessage = validationError.structuredContent.error?.message ??
|
|
240
|
+
INPUT_VALIDATION_FAILED;
|
|
241
|
+
await updateStatusMessage(validationMessage);
|
|
242
|
+
await reportProgress({
|
|
243
|
+
current: TASK_PROGRESS_TOTAL,
|
|
244
|
+
total: TASK_PROGRESS_TOTAL,
|
|
245
|
+
message: formatProgressCompletion(config.name, progressContext, 'failed'),
|
|
246
|
+
});
|
|
247
|
+
await storeResultSafely('completed', validationError);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
80
250
|
}
|
|
251
|
+
await reportProgress({
|
|
252
|
+
current: 1,
|
|
253
|
+
total: TASK_PROGRESS_TOTAL,
|
|
254
|
+
message: formatProgressStep(config.name, progressContext, 'input validated'),
|
|
255
|
+
});
|
|
256
|
+
const { systemInstruction, prompt } = config.buildPrompt(inputRecord);
|
|
257
|
+
await reportProgress({
|
|
258
|
+
current: 2,
|
|
259
|
+
total: TASK_PROGRESS_TOTAL,
|
|
260
|
+
message: formatProgressStep(config.name, progressContext, 'prompt prepared'),
|
|
261
|
+
});
|
|
262
|
+
const raw = await generateStructuredJson(createGenerationRequest(config, { systemInstruction, prompt }, responseSchema, onLog, extra.signal));
|
|
263
|
+
await reportProgress({
|
|
264
|
+
current: 3,
|
|
265
|
+
total: TASK_PROGRESS_TOTAL,
|
|
266
|
+
message: formatProgressStep(config.name, progressContext, 'model response received'),
|
|
267
|
+
});
|
|
268
|
+
const parsed = config.resultSchema.parse(raw);
|
|
269
|
+
const finalResult = (config.transformResult
|
|
270
|
+
? config.transformResult(inputRecord, parsed)
|
|
271
|
+
: parsed);
|
|
272
|
+
const textContent = config.formatOutput
|
|
273
|
+
? config.formatOutput(finalResult)
|
|
274
|
+
: undefined;
|
|
275
|
+
await reportProgress({
|
|
276
|
+
current: TASK_PROGRESS_TOTAL,
|
|
277
|
+
total: TASK_PROGRESS_TOTAL,
|
|
278
|
+
message: formatProgressCompletion(config.name, progressContext, 'completed'),
|
|
279
|
+
});
|
|
280
|
+
await storeResultSafely('completed', createToolResponse({
|
|
281
|
+
ok: true,
|
|
282
|
+
result: finalResult,
|
|
283
|
+
}, textContent));
|
|
81
284
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
285
|
+
catch (error) {
|
|
286
|
+
const errorMessage = getErrorMessage(error);
|
|
287
|
+
const errorMeta = classifyErrorMeta(error, errorMessage);
|
|
288
|
+
await reportProgress({
|
|
289
|
+
current: TASK_PROGRESS_TOTAL,
|
|
290
|
+
total: TASK_PROGRESS_TOTAL,
|
|
291
|
+
message: formatProgressCompletion(config.name, progressContext, 'failed'),
|
|
292
|
+
});
|
|
293
|
+
await updateStatusMessage(errorMessage);
|
|
294
|
+
await storeResultSafely('failed', createErrorToolResponse(config.errorCode, errorMessage, undefined, errorMeta));
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
queueMicrotask(() => {
|
|
298
|
+
void runTask().catch((error) => {
|
|
299
|
+
console.error(`[task-runner:${config.name}] ${getErrorMessage(error)}`);
|
|
91
300
|
});
|
|
92
|
-
|
|
93
|
-
const parsed = config.resultSchema.parse(raw);
|
|
94
|
-
await extra.taskStore.storeTaskResult(task.taskId, 'completed', createToolResponse({
|
|
95
|
-
ok: true,
|
|
96
|
-
result: parsed,
|
|
97
|
-
}));
|
|
98
|
-
await sendProgress(4, 4);
|
|
99
|
-
}
|
|
100
|
-
catch (error) {
|
|
101
|
-
const errorMessage = getErrorMessage(error);
|
|
102
|
-
await updateStatusMessage(errorMessage);
|
|
103
|
-
await extra.taskStore.storeTaskResult(task.taskId, 'failed', createErrorToolResponse(config.errorCode, errorMessage));
|
|
104
|
-
}
|
|
301
|
+
});
|
|
105
302
|
return { task };
|
|
106
303
|
},
|
|
107
304
|
getTask: async (_input, extra) => {
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
export type ErrorKind = 'validation' | 'budget' | 'upstream' | 'timeout' | 'cancelled' | 'internal';
|
|
2
|
+
export interface ErrorMeta {
|
|
3
|
+
retryable?: boolean;
|
|
4
|
+
kind?: ErrorKind;
|
|
5
|
+
}
|
|
1
6
|
interface ToolError {
|
|
2
7
|
code: string;
|
|
3
8
|
message: string;
|
|
9
|
+
retryable?: boolean;
|
|
10
|
+
kind?: ErrorKind;
|
|
4
11
|
}
|
|
5
12
|
interface ToolTextContent {
|
|
6
13
|
type: 'text';
|
|
@@ -20,6 +27,6 @@ interface ToolResponse<TStructuredContent extends ToolStructuredContent> {
|
|
|
20
27
|
interface ErrorToolResponse extends ToolResponse<ToolStructuredContent> {
|
|
21
28
|
isError: true;
|
|
22
29
|
}
|
|
23
|
-
export declare function createToolResponse<TStructuredContent extends ToolStructuredContent>(structured: TStructuredContent): ToolResponse<TStructuredContent>;
|
|
24
|
-
export declare function createErrorToolResponse(code: string, message: string, result?: unknown): ErrorToolResponse;
|
|
30
|
+
export declare function createToolResponse<TStructuredContent extends ToolStructuredContent>(structured: TStructuredContent, textContent?: string): ToolResponse<TStructuredContent>;
|
|
31
|
+
export declare function createErrorToolResponse(code: string, message: string, result?: unknown, meta?: ErrorMeta): ErrorToolResponse;
|
|
25
32
|
export {};
|
|
@@ -1,23 +1,38 @@
|
|
|
1
|
-
function toTextContent(structured) {
|
|
2
|
-
|
|
1
|
+
function toTextContent(structured, textContent) {
|
|
2
|
+
const text = textContent ?? JSON.stringify(structured);
|
|
3
|
+
return [{ type: 'text', text }];
|
|
3
4
|
}
|
|
4
|
-
function
|
|
5
|
-
if (result === undefined) {
|
|
6
|
-
return { ok: false, error: { code, message } };
|
|
7
|
-
}
|
|
8
|
-
return { ok: false, error: { code, message }, result };
|
|
9
|
-
}
|
|
10
|
-
export function createToolResponse(structured) {
|
|
5
|
+
function buildToolResponse(structured, textContent) {
|
|
11
6
|
return {
|
|
12
|
-
content: toTextContent(structured),
|
|
7
|
+
content: toTextContent(structured, textContent),
|
|
13
8
|
structuredContent: structured,
|
|
14
9
|
};
|
|
15
10
|
}
|
|
16
|
-
|
|
17
|
-
const
|
|
11
|
+
function createErrorStructuredContent(code, message, result, meta) {
|
|
12
|
+
const error = {
|
|
13
|
+
code,
|
|
14
|
+
message,
|
|
15
|
+
};
|
|
16
|
+
if (meta?.retryable !== undefined) {
|
|
17
|
+
error.retryable = meta.retryable;
|
|
18
|
+
}
|
|
19
|
+
if (meta?.kind !== undefined) {
|
|
20
|
+
error.kind = meta.kind;
|
|
21
|
+
}
|
|
22
|
+
if (result === undefined) {
|
|
23
|
+
return { ok: false, error };
|
|
24
|
+
}
|
|
25
|
+
return { ok: false, error, result };
|
|
26
|
+
}
|
|
27
|
+
export function createToolResponse(structured, textContent) {
|
|
28
|
+
return buildToolResponse(structured, textContent);
|
|
29
|
+
}
|
|
30
|
+
export function createErrorToolResponse(code, message, result, meta) {
|
|
31
|
+
const structured = createErrorStructuredContent(code, message, result, meta);
|
|
32
|
+
const base = buildToolResponse(structured);
|
|
18
33
|
return {
|
|
19
|
-
content:
|
|
20
|
-
structuredContent:
|
|
34
|
+
content: base.content,
|
|
35
|
+
structuredContent: base.structuredContent,
|
|
21
36
|
isError: true,
|
|
22
37
|
};
|
|
23
38
|
}
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
export type JsonObject = Record<string, unknown>;
|
|
2
|
-
export
|
|
3
|
-
|
|
2
|
+
export type GeminiLogHandler = (level: string, data: unknown) => Promise<void>;
|
|
3
|
+
interface GeminiRequestExecutionOptions {
|
|
4
4
|
maxRetries?: number;
|
|
5
5
|
timeoutMs?: number;
|
|
6
6
|
temperature?: number;
|
|
7
7
|
maxOutputTokens?: number;
|
|
8
|
+
thinkingBudget?: number;
|
|
8
9
|
signal?: AbortSignal;
|
|
9
|
-
onLog?:
|
|
10
|
+
onLog?: GeminiLogHandler;
|
|
11
|
+
}
|
|
12
|
+
export interface GeminiStructuredRequestOptions extends GeminiRequestExecutionOptions {
|
|
13
|
+
model?: string;
|
|
10
14
|
}
|
|
11
15
|
export interface GeminiStructuredRequest extends GeminiStructuredRequestOptions {
|
|
12
16
|
systemInstruction?: string;
|
|
13
17
|
prompt: string;
|
|
14
18
|
responseSchema: JsonObject;
|
|
15
19
|
}
|
|
20
|
+
export {};
|
package/dist/prompts/index.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { completable } from '@modelcontextprotocol/sdk/server/completable.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
const HELP_PROMPT_NAME = 'get-help';
|
|
4
|
+
const HELP_PROMPT_TITLE = 'Get Help';
|
|
4
5
|
const HELP_PROMPT_DESCRIPTION = 'Return the server usage instructions.';
|
|
5
6
|
const REVIEW_GUIDE_PROMPT_NAME = 'review-guide';
|
|
7
|
+
const REVIEW_GUIDE_PROMPT_TITLE = 'Review Guide';
|
|
6
8
|
const REVIEW_GUIDE_PROMPT_DESCRIPTION = 'Guided workflow instructions for a specific code review tool and focus area.';
|
|
7
|
-
const TOOLS = [
|
|
9
|
+
const TOOLS = [
|
|
10
|
+
'analyze_pr_impact',
|
|
11
|
+
'generate_review_summary',
|
|
12
|
+
'inspect_code_quality',
|
|
13
|
+
'suggest_search_replace',
|
|
14
|
+
'generate_test_plan',
|
|
15
|
+
];
|
|
8
16
|
const FOCUS_AREAS = [
|
|
9
17
|
'security',
|
|
10
18
|
'correctness',
|
|
@@ -13,14 +21,13 @@ const FOCUS_AREAS = [
|
|
|
13
21
|
'tests',
|
|
14
22
|
];
|
|
15
23
|
const TOOL_GUIDES = {
|
|
16
|
-
|
|
17
|
-
'
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
'
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
'Optional: `patchStyle` (minimal, balanced, defensive). One finding per call.',
|
|
24
|
+
analyze_pr_impact: 'Call `analyze_pr_impact` with `diff` and `repository`. ' +
|
|
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.',
|
|
24
31
|
};
|
|
25
32
|
const FOCUS_AREA_GUIDES = {
|
|
26
33
|
security: 'Audit for injection vulnerabilities, insecure data handling, broken authentication, ' +
|
|
@@ -34,15 +41,28 @@ const FOCUS_AREA_GUIDES = {
|
|
|
34
41
|
tests: 'Assess test coverage gaps, missing edge case tests, flaky test patterns, ' +
|
|
35
42
|
'and untested error paths.',
|
|
36
43
|
};
|
|
44
|
+
function completeByPrefix(values, prefix) {
|
|
45
|
+
const matches = [];
|
|
46
|
+
for (const value of values) {
|
|
47
|
+
if (value.startsWith(prefix)) {
|
|
48
|
+
matches.push(value);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return matches;
|
|
52
|
+
}
|
|
53
|
+
function getGuide(guides, value, fallback) {
|
|
54
|
+
const guide = guides[value];
|
|
55
|
+
return guide ?? fallback(value);
|
|
56
|
+
}
|
|
37
57
|
function getToolGuide(tool) {
|
|
38
|
-
return TOOL_GUIDES
|
|
58
|
+
return getGuide(TOOL_GUIDES, tool, (toolName) => `Use \`${toolName}\` to analyze your code changes.`);
|
|
39
59
|
}
|
|
40
60
|
function getFocusAreaGuide(focusArea) {
|
|
41
|
-
return FOCUS_AREA_GUIDES
|
|
61
|
+
return getGuide(FOCUS_AREA_GUIDES, focusArea, (area) => `Focus on ${area} concerns.`);
|
|
42
62
|
}
|
|
43
63
|
export function registerAllPrompts(server, instructions) {
|
|
44
64
|
server.registerPrompt(HELP_PROMPT_NAME, {
|
|
45
|
-
title:
|
|
65
|
+
title: HELP_PROMPT_TITLE,
|
|
46
66
|
description: 'Return the server usage instructions.',
|
|
47
67
|
}, () => ({
|
|
48
68
|
description: HELP_PROMPT_DESCRIPTION,
|
|
@@ -57,15 +77,15 @@ export function registerAllPrompts(server, instructions) {
|
|
|
57
77
|
],
|
|
58
78
|
}));
|
|
59
79
|
server.registerPrompt(REVIEW_GUIDE_PROMPT_NAME, {
|
|
60
|
-
title:
|
|
80
|
+
title: REVIEW_GUIDE_PROMPT_TITLE,
|
|
61
81
|
description: REVIEW_GUIDE_PROMPT_DESCRIPTION,
|
|
62
82
|
argsSchema: {
|
|
63
83
|
tool: completable(z
|
|
64
84
|
.string()
|
|
65
|
-
.describe('Which review tool to use:
|
|
85
|
+
.describe('Which review tool to use: analyze_pr_impact, generate_review_summary, etc.'), (value) => completeByPrefix(TOOLS, value)),
|
|
66
86
|
focusArea: completable(z
|
|
67
87
|
.string()
|
|
68
|
-
.describe('Focus area: security, correctness, performance, regressions, or tests'), (value) => FOCUS_AREAS
|
|
88
|
+
.describe('Focus area: security, correctness, performance, regressions, or tests'), (value) => completeByPrefix(FOCUS_AREAS, value)),
|
|
69
89
|
},
|
|
70
90
|
}, ({ tool, focusArea }) => ({
|
|
71
91
|
description: `Code review guide: ${tool} / ${focusArea}`,
|
package/dist/resources/index.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
const RESOURCE_ID = 'server-instructions';
|
|
2
2
|
const RESOURCE_URI = 'internal://instructions';
|
|
3
3
|
const RESOURCE_MIME_TYPE = 'text/markdown';
|
|
4
|
+
const RESOURCE_METADATA = {
|
|
5
|
+
title: 'Server Instructions',
|
|
6
|
+
description: 'Guidance for using the MCP tools effectively.',
|
|
7
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
8
|
+
annotations: {
|
|
9
|
+
audience: ['assistant'],
|
|
10
|
+
priority: 0.8,
|
|
11
|
+
},
|
|
12
|
+
};
|
|
4
13
|
export function registerAllResources(server, instructions) {
|
|
5
|
-
server.registerResource(RESOURCE_ID, RESOURCE_URI, {
|
|
6
|
-
title: 'Server Instructions',
|
|
7
|
-
description: 'Guidance for using the MCP tools effectively.',
|
|
8
|
-
mimeType: RESOURCE_MIME_TYPE,
|
|
9
|
-
annotations: {
|
|
10
|
-
audience: ['assistant'],
|
|
11
|
-
priority: 0.8,
|
|
12
|
-
},
|
|
13
|
-
}, (uri) => ({
|
|
14
|
+
server.registerResource(RESOURCE_ID, RESOURCE_URI, RESOURCE_METADATA, (uri) => ({
|
|
14
15
|
contents: [
|
|
15
16
|
{
|
|
16
17
|
uri: uri.href,
|
package/dist/schemas/inputs.d.ts
CHANGED
|
@@ -1,26 +1,38 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
export declare const
|
|
2
|
+
export declare const FileContextSchema: z.ZodObject<{
|
|
3
|
+
path: z.ZodString;
|
|
4
|
+
content: z.ZodString;
|
|
5
|
+
}, z.core.$strict>;
|
|
6
|
+
export declare const AnalyzePrImpactInputSchema: z.ZodObject<{
|
|
3
7
|
diff: z.ZodString;
|
|
4
8
|
repository: z.ZodString;
|
|
5
9
|
language: z.ZodOptional<z.ZodString>;
|
|
6
|
-
focusAreas: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
7
|
-
maxFindings: z.ZodOptional<z.ZodNumber>;
|
|
8
10
|
}, z.core.$strict>;
|
|
9
|
-
export declare const
|
|
11
|
+
export declare const GenerateReviewSummaryInputSchema: z.ZodObject<{
|
|
10
12
|
diff: z.ZodString;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
medium: "medium";
|
|
14
|
-
high: "high";
|
|
15
|
-
}>>;
|
|
13
|
+
repository: z.ZodString;
|
|
14
|
+
language: z.ZodOptional<z.ZodString>;
|
|
16
15
|
}, z.core.$strict>;
|
|
17
|
-
export declare const
|
|
16
|
+
export declare const InspectCodeQualityInputSchema: z.ZodObject<{
|
|
17
|
+
diff: z.ZodString;
|
|
18
|
+
repository: z.ZodString;
|
|
19
|
+
language: z.ZodOptional<z.ZodString>;
|
|
20
|
+
focusAreas: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
21
|
+
maxFindings: z.ZodOptional<z.ZodNumber>;
|
|
22
|
+
files: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
23
|
+
path: z.ZodString;
|
|
24
|
+
content: z.ZodString;
|
|
25
|
+
}, z.core.$strict>>>;
|
|
26
|
+
}, z.core.$strict>;
|
|
27
|
+
export declare const SuggestSearchReplaceInputSchema: z.ZodObject<{
|
|
18
28
|
diff: z.ZodString;
|
|
19
29
|
findingTitle: z.ZodString;
|
|
20
30
|
findingDetails: z.ZodString;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
}, z.core.$strict>;
|
|
32
|
+
export declare const GenerateTestPlanInputSchema: z.ZodObject<{
|
|
33
|
+
diff: z.ZodString;
|
|
34
|
+
repository: z.ZodString;
|
|
35
|
+
language: z.ZodOptional<z.ZodString>;
|
|
36
|
+
testFramework: z.ZodOptional<z.ZodString>;
|
|
37
|
+
maxTestCases: z.ZodOptional<z.ZodNumber>;
|
|
26
38
|
}, z.core.$strict>;
|