@j0hanz/code-review-analyst-mcp 1.5.2 → 1.6.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 +1 -35
- package/dist/lib/cli.d.ts +1 -0
- package/dist/lib/cli.js +35 -0
- package/dist/lib/context-budget.js +3 -3
- package/dist/lib/diff-budget.js +3 -6
- package/dist/lib/diff-cleaner.js +36 -15
- package/dist/lib/diff-parser.js +37 -50
- package/dist/lib/diff-store.js +21 -11
- package/dist/lib/env-config.js +4 -3
- package/dist/lib/errors.js +7 -5
- package/dist/lib/gemini-schema.js +2 -0
- package/dist/lib/gemini.js +106 -96
- package/dist/lib/model-config.js +2 -4
- package/dist/lib/tool-contracts.d.ts +10 -3
- package/dist/lib/tool-contracts.js +16 -2
- package/dist/lib/tool-factory.d.ts +31 -0
- package/dist/lib/tool-factory.js +203 -128
- package/dist/lib/tool-response.js +5 -8
- package/dist/prompts/index.js +16 -24
- package/dist/resources/index.js +8 -4
- package/dist/resources/instructions.js +25 -25
- package/dist/resources/tool-info.js +4 -11
- package/dist/schemas/inputs.js +8 -8
- package/dist/schemas/outputs.js +42 -66
- package/dist/server.js +13 -26
- package/dist/tools/analyze-complexity.js +8 -15
- package/dist/tools/analyze-pr-impact.js +7 -15
- package/dist/tools/detect-api-breaking.js +9 -16
- package/dist/tools/generate-diff.js +17 -4
- package/dist/tools/generate-review-summary.js +7 -14
- package/dist/tools/generate-test-plan.js +8 -15
- package/dist/tools/index.js +1 -4
- package/dist/tools/inspect-code-quality.js +19 -26
- package/dist/tools/suggest-search-replace.js +8 -15
- package/package.json +1 -1
package/dist/lib/gemini.js
CHANGED
|
@@ -35,6 +35,8 @@ const DEFAULT_BATCH_MODE = 'off';
|
|
|
35
35
|
const UNKNOWN_REQUEST_CONTEXT_VALUE = 'unknown';
|
|
36
36
|
const RETRYABLE_NUMERIC_CODES = new Set([429, 500, 502, 503, 504]);
|
|
37
37
|
const DIGITS_ONLY_PATTERN = /^\d+$/;
|
|
38
|
+
const TRUE_ENV_VALUES = new Set(['1', 'true', 'yes', 'on']);
|
|
39
|
+
const FALSE_ENV_VALUES = new Set(['0', 'false', 'no', 'off']);
|
|
38
40
|
const SLEEP_UNREF_OPTIONS = { ref: false };
|
|
39
41
|
const maxConcurrentCallsConfig = createCachedEnvInt('MAX_CONCURRENT_CALLS', 10);
|
|
40
42
|
const maxConcurrentBatchCallsConfig = createCachedEnvInt('MAX_CONCURRENT_BATCH_CALLS', 2);
|
|
@@ -129,16 +131,10 @@ function parseBooleanEnv(value) {
|
|
|
129
131
|
if (normalized.length === 0) {
|
|
130
132
|
return undefined;
|
|
131
133
|
}
|
|
132
|
-
if (normalized
|
|
133
|
-
normalized === 'true' ||
|
|
134
|
-
normalized === 'yes' ||
|
|
135
|
-
normalized === 'on') {
|
|
134
|
+
if (TRUE_ENV_VALUES.has(normalized)) {
|
|
136
135
|
return true;
|
|
137
136
|
}
|
|
138
|
-
if (normalized
|
|
139
|
-
normalized === 'false' ||
|
|
140
|
-
normalized === 'no' ||
|
|
141
|
-
normalized === 'off') {
|
|
137
|
+
if (FALSE_ENV_VALUES.has(normalized)) {
|
|
142
138
|
return false;
|
|
143
139
|
}
|
|
144
140
|
return undefined;
|
|
@@ -240,7 +236,7 @@ function logEvent(event, details) {
|
|
|
240
236
|
...details,
|
|
241
237
|
});
|
|
242
238
|
}
|
|
243
|
-
function
|
|
239
|
+
function toRecord(value) {
|
|
244
240
|
if (typeof value !== 'object' || value === null) {
|
|
245
241
|
return undefined;
|
|
246
242
|
}
|
|
@@ -262,12 +258,12 @@ async function emitGeminiLog(onLog, level, payload) {
|
|
|
262
258
|
});
|
|
263
259
|
}
|
|
264
260
|
function getNestedError(error) {
|
|
265
|
-
const record =
|
|
261
|
+
const record = toRecord(error);
|
|
266
262
|
if (!record) {
|
|
267
263
|
return undefined;
|
|
268
264
|
}
|
|
269
265
|
const nested = record.error;
|
|
270
|
-
const nestedRecord =
|
|
266
|
+
const nestedRecord = toRecord(nested);
|
|
271
267
|
if (!nestedRecord) {
|
|
272
268
|
return record;
|
|
273
269
|
}
|
|
@@ -372,6 +368,14 @@ function parseStructuredResponse(responseText) {
|
|
|
372
368
|
try {
|
|
373
369
|
return JSON.parse(responseText);
|
|
374
370
|
}
|
|
371
|
+
catch {
|
|
372
|
+
// fast-path failed; try extracting from markdown block
|
|
373
|
+
}
|
|
374
|
+
const jsonMatch = /```(?:json)?\n?([\s\S]*?)(?=\n?```)/u.exec(responseText);
|
|
375
|
+
const jsonText = jsonMatch?.[1] ?? responseText;
|
|
376
|
+
try {
|
|
377
|
+
return JSON.parse(jsonText);
|
|
378
|
+
}
|
|
375
379
|
catch (error) {
|
|
376
380
|
throw new Error(`Model produced invalid JSON: ${getErrorMessage(error)}`);
|
|
377
381
|
}
|
|
@@ -571,88 +575,94 @@ async function waitForBatchConcurrencySlot(limit, requestSignal) {
|
|
|
571
575
|
activeBatchCalls += 1;
|
|
572
576
|
}, batchSlotWaiters, requestSignal);
|
|
573
577
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
578
|
+
const BatchHelper = {
|
|
579
|
+
getState(payload) {
|
|
580
|
+
const record = toRecord(payload);
|
|
581
|
+
if (!record) {
|
|
582
|
+
return undefined;
|
|
583
|
+
}
|
|
584
|
+
const directState = toUpperStringCode(record.state);
|
|
585
|
+
if (directState) {
|
|
586
|
+
return directState;
|
|
587
|
+
}
|
|
588
|
+
const metadata = toRecord(record.metadata);
|
|
589
|
+
if (!metadata) {
|
|
590
|
+
return undefined;
|
|
591
|
+
}
|
|
592
|
+
return toUpperStringCode(metadata.state);
|
|
593
|
+
},
|
|
594
|
+
getResponseText(payload) {
|
|
595
|
+
const record = toRecord(payload);
|
|
596
|
+
if (!record) {
|
|
597
|
+
return undefined;
|
|
598
|
+
}
|
|
599
|
+
const inlineResponse = toRecord(record.inlineResponse);
|
|
600
|
+
const inlineText = typeof inlineResponse?.text === 'string'
|
|
601
|
+
? inlineResponse.text
|
|
602
|
+
: undefined;
|
|
603
|
+
if (inlineText) {
|
|
604
|
+
return inlineText;
|
|
605
|
+
}
|
|
606
|
+
const response = toRecord(record.response);
|
|
607
|
+
if (!response) {
|
|
608
|
+
return undefined;
|
|
609
|
+
}
|
|
610
|
+
const responseText = typeof response.text === 'string' ? response.text : undefined;
|
|
611
|
+
if (responseText) {
|
|
612
|
+
return responseText;
|
|
613
|
+
}
|
|
614
|
+
const { inlineResponses } = response;
|
|
615
|
+
if (!Array.isArray(inlineResponses) || inlineResponses.length === 0) {
|
|
616
|
+
return undefined;
|
|
617
|
+
}
|
|
618
|
+
const firstInline = toRecord(inlineResponses[0]);
|
|
619
|
+
return typeof firstInline?.text === 'string' ? firstInline.text : undefined;
|
|
620
|
+
},
|
|
621
|
+
getErrorDetail(payload) {
|
|
622
|
+
const record = toRecord(payload);
|
|
623
|
+
if (!record) {
|
|
624
|
+
return undefined;
|
|
625
|
+
}
|
|
626
|
+
const directError = toRecord(record.error);
|
|
627
|
+
const directMessage = typeof directError?.message === 'string'
|
|
628
|
+
? directError.message
|
|
629
|
+
: undefined;
|
|
630
|
+
if (directMessage) {
|
|
631
|
+
return directMessage;
|
|
632
|
+
}
|
|
633
|
+
const metadata = toRecord(record.metadata);
|
|
634
|
+
const metadataError = toRecord(metadata?.error);
|
|
635
|
+
const metadataMessage = typeof metadataError?.message === 'string'
|
|
636
|
+
? metadataError.message
|
|
637
|
+
: undefined;
|
|
638
|
+
if (metadataMessage) {
|
|
639
|
+
return metadataMessage;
|
|
640
|
+
}
|
|
641
|
+
const response = toRecord(record.response);
|
|
642
|
+
const responseError = toRecord(response?.error);
|
|
643
|
+
return typeof responseError?.message === 'string'
|
|
644
|
+
? responseError.message
|
|
645
|
+
: undefined;
|
|
646
|
+
},
|
|
647
|
+
getSuccessResponseText(polled) {
|
|
648
|
+
const responseText = this.getResponseText(polled);
|
|
649
|
+
if (!responseText) {
|
|
650
|
+
const errorDetail = this.getErrorDetail(polled);
|
|
651
|
+
throw new Error(errorDetail
|
|
652
|
+
? `Gemini batch request succeeded but returned no response text: ${errorDetail}`
|
|
653
|
+
: 'Gemini batch request succeeded but returned no response text.');
|
|
654
|
+
}
|
|
605
655
|
return responseText;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
if (!record) {
|
|
617
|
-
return undefined;
|
|
618
|
-
}
|
|
619
|
-
const directError = asRecord(record.error);
|
|
620
|
-
const directMessage = typeof directError?.message === 'string' ? directError.message : undefined;
|
|
621
|
-
if (directMessage) {
|
|
622
|
-
return directMessage;
|
|
623
|
-
}
|
|
624
|
-
const metadata = asRecord(record.metadata);
|
|
625
|
-
const metadataError = asRecord(metadata?.error);
|
|
626
|
-
const metadataMessage = typeof metadataError?.message === 'string'
|
|
627
|
-
? metadataError.message
|
|
628
|
-
: undefined;
|
|
629
|
-
if (metadataMessage) {
|
|
630
|
-
return metadataMessage;
|
|
631
|
-
}
|
|
632
|
-
const response = asRecord(record.response);
|
|
633
|
-
const responseError = asRecord(response?.error);
|
|
634
|
-
return typeof responseError?.message === 'string'
|
|
635
|
-
? responseError.message
|
|
636
|
-
: undefined;
|
|
637
|
-
}
|
|
638
|
-
function getBatchSuccessResponseText(polled) {
|
|
639
|
-
const responseText = extractBatchResponseText(polled);
|
|
640
|
-
if (!responseText) {
|
|
641
|
-
const errorDetail = extractBatchErrorDetail(polled);
|
|
642
|
-
throw new Error(errorDetail
|
|
643
|
-
? `Gemini batch request succeeded but returned no response text: ${errorDetail}`
|
|
644
|
-
: 'Gemini batch request succeeded but returned no response text.');
|
|
645
|
-
}
|
|
646
|
-
return responseText;
|
|
647
|
-
}
|
|
648
|
-
function handleBatchTerminalState(state, payload) {
|
|
649
|
-
if (state === 'JOB_STATE_FAILED' || state === 'JOB_STATE_CANCELLED') {
|
|
650
|
-
const errorDetail = extractBatchErrorDetail(payload);
|
|
651
|
-
throw new Error(errorDetail
|
|
652
|
-
? `Gemini batch request ended with state ${state}: ${errorDetail}`
|
|
653
|
-
: `Gemini batch request ended with state ${state}.`);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
+
},
|
|
657
|
+
handleTerminalState(state, payload) {
|
|
658
|
+
if (state === 'JOB_STATE_FAILED' || state === 'JOB_STATE_CANCELLED') {
|
|
659
|
+
const errorDetail = this.getErrorDetail(payload);
|
|
660
|
+
throw new Error(errorDetail
|
|
661
|
+
? `Gemini batch request ended with state ${state}: ${errorDetail}`
|
|
662
|
+
: `Gemini batch request ended with state ${state}.`);
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
};
|
|
656
666
|
async function pollBatchStatusWithRetries(batches, batchName, onLog, requestSignal) {
|
|
657
667
|
const maxPollRetries = 2;
|
|
658
668
|
for (let attempt = 0; attempt <= maxPollRetries; attempt += 1) {
|
|
@@ -717,7 +727,7 @@ async function runInlineBatchWithPolling(request, model, onLog) {
|
|
|
717
727
|
],
|
|
718
728
|
};
|
|
719
729
|
const createdJob = await batches.create(createPayload);
|
|
720
|
-
const createdRecord =
|
|
730
|
+
const createdRecord = toRecord(createdJob);
|
|
721
731
|
batchName =
|
|
722
732
|
typeof createdRecord?.name === 'string' ? createdRecord.name : undefined;
|
|
723
733
|
if (!batchName) {
|
|
@@ -740,13 +750,13 @@ async function runInlineBatchWithPolling(request, model, onLog) {
|
|
|
740
750
|
throw new Error(`Gemini batch request timed out after ${formatNumber(timeoutMs)}ms.`);
|
|
741
751
|
}
|
|
742
752
|
const polled = await pollBatchStatusWithRetries(batches, batchName, onLog, request.signal);
|
|
743
|
-
const state =
|
|
753
|
+
const state = BatchHelper.getState(polled);
|
|
744
754
|
if (state === 'JOB_STATE_SUCCEEDED') {
|
|
745
|
-
const responseText =
|
|
755
|
+
const responseText = BatchHelper.getSuccessResponseText(polled);
|
|
746
756
|
completed = true;
|
|
747
757
|
return parseStructuredResponse(responseText);
|
|
748
758
|
}
|
|
749
|
-
|
|
759
|
+
BatchHelper.handleTerminalState(state, polled);
|
|
750
760
|
await sleep(pollIntervalMs, undefined, request.signal
|
|
751
761
|
? { ...SLEEP_UNREF_OPTIONS, signal: request.signal }
|
|
752
762
|
: SLEEP_UNREF_OPTIONS);
|
package/dist/lib/model-config.js
CHANGED
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
export const FLASH_MODEL = 'gemini-3-flash-preview';
|
|
3
3
|
/** High-capability model for deep reasoning, quality inspection, and reliable code generation. */
|
|
4
4
|
export const PRO_MODEL = 'gemini-3-pro-preview';
|
|
5
|
-
/** Default hint for auto-detection. */
|
|
6
|
-
const DEFAULT_DETECT_HINT = 'detect';
|
|
7
5
|
/** Default language hint. */
|
|
8
|
-
export const DEFAULT_LANGUAGE =
|
|
6
|
+
export const DEFAULT_LANGUAGE = 'detect';
|
|
9
7
|
/** Default test-framework hint. */
|
|
10
|
-
export const DEFAULT_FRAMEWORK =
|
|
8
|
+
export const DEFAULT_FRAMEWORK = 'detect';
|
|
11
9
|
/** Extended timeout for Pro model calls (ms). */
|
|
12
10
|
export const DEFAULT_TIMEOUT_PRO_MS = 120_000;
|
|
13
11
|
export const MODEL_TIMEOUT_MS = {
|
|
@@ -29,6 +29,12 @@ export interface ToolContract {
|
|
|
29
29
|
crossToolFlow: readonly string[];
|
|
30
30
|
constraints?: readonly string[];
|
|
31
31
|
}
|
|
32
|
+
interface StructuredToolRuntimeOptions {
|
|
33
|
+
thinkingLevel?: NonNullable<ToolContract['thinkingLevel']>;
|
|
34
|
+
temperature?: NonNullable<ToolContract['temperature']>;
|
|
35
|
+
deterministicJson?: NonNullable<ToolContract['deterministicJson']>;
|
|
36
|
+
}
|
|
37
|
+
export declare function buildStructuredToolRuntimeOptions(contract: Pick<ToolContract, 'thinkingLevel' | 'temperature' | 'deterministicJson'>): StructuredToolRuntimeOptions;
|
|
32
38
|
export declare const TOOL_CONTRACTS: readonly [{
|
|
33
39
|
readonly name: "generate_diff";
|
|
34
40
|
readonly purpose: "Generate a diff of current changes and cache it server-side. MUST be called before any other tool. Uses git to capture unstaged or staged changes in the current working directory.";
|
|
@@ -97,7 +103,7 @@ export declare const TOOL_CONTRACTS: readonly [{
|
|
|
97
103
|
readonly crossToolFlow: readonly ["Use before deep review to decide whether Pro analysis is needed."];
|
|
98
104
|
}, {
|
|
99
105
|
readonly name: "inspect_code_quality";
|
|
100
|
-
readonly purpose: "Deep code review
|
|
106
|
+
readonly purpose: "Deep code review over the cached diff; files are optional supplementary excerpts only.";
|
|
101
107
|
readonly model: "gemini-3-pro-preview";
|
|
102
108
|
readonly timeoutMs: 120000;
|
|
103
109
|
readonly thinkingLevel: "high";
|
|
@@ -133,10 +139,10 @@ export declare const TOOL_CONTRACTS: readonly [{
|
|
|
133
139
|
readonly type: "object[]";
|
|
134
140
|
readonly required: false;
|
|
135
141
|
readonly constraints: "1-20 files, 100K chars/file";
|
|
136
|
-
readonly description: "Optional full
|
|
142
|
+
readonly description: "Optional short excerpts for supplementary context only; avoid full files — the diff is the primary source.";
|
|
137
143
|
}];
|
|
138
144
|
readonly outputShape: "{summary, overallRisk, findings[], testsNeeded[], contextualInsights[], totalFindings}";
|
|
139
|
-
readonly gotchas: readonly ["Requires generate_diff to be called first.", "Combined diff + file context is bounded by MAX_CONTEXT_CHARS.", "maxFindings caps output after generation."];
|
|
145
|
+
readonly gotchas: readonly ["Requires generate_diff to be called first.", "Combined diff + file context is bounded by MAX_CONTEXT_CHARS.", "maxFindings caps output after generation.", "files[] is token-expensive — omit unless the diff lacks critical structural context (e.g. class hierarchy, imports). Never pass full files."];
|
|
140
146
|
readonly crossToolFlow: readonly ["findings[].title -> suggest_search_replace.findingTitle", "findings[].explanation -> suggest_search_replace.findingDetails"];
|
|
141
147
|
readonly constraints: readonly ["Context budget (diff + files) < 500K chars."];
|
|
142
148
|
}, {
|
|
@@ -245,3 +251,4 @@ export declare function getToolContracts(): readonly ToolContract[];
|
|
|
245
251
|
export declare function getToolContract(toolName: string): ToolContract | undefined;
|
|
246
252
|
export declare function requireToolContract(toolName: string): ToolContract;
|
|
247
253
|
export declare function getToolContractNames(): string[];
|
|
254
|
+
export {};
|
|
@@ -9,6 +9,19 @@ export const INSPECTION_FOCUS_AREAS = [
|
|
|
9
9
|
'maintainability',
|
|
10
10
|
'concurrency',
|
|
11
11
|
];
|
|
12
|
+
export function buildStructuredToolRuntimeOptions(contract) {
|
|
13
|
+
const options = {};
|
|
14
|
+
if (contract.thinkingLevel !== undefined) {
|
|
15
|
+
options.thinkingLevel = contract.thinkingLevel;
|
|
16
|
+
}
|
|
17
|
+
if (contract.temperature !== undefined) {
|
|
18
|
+
options.temperature = contract.temperature;
|
|
19
|
+
}
|
|
20
|
+
if (contract.deterministicJson !== undefined) {
|
|
21
|
+
options.deterministicJson = contract.deterministicJson;
|
|
22
|
+
}
|
|
23
|
+
return options;
|
|
24
|
+
}
|
|
12
25
|
export const TOOL_CONTRACTS = [
|
|
13
26
|
{
|
|
14
27
|
name: 'generate_diff',
|
|
@@ -105,7 +118,7 @@ export const TOOL_CONTRACTS = [
|
|
|
105
118
|
},
|
|
106
119
|
{
|
|
107
120
|
name: 'inspect_code_quality',
|
|
108
|
-
purpose: 'Deep code review
|
|
121
|
+
purpose: 'Deep code review over the cached diff; files are optional supplementary excerpts only.',
|
|
109
122
|
model: PRO_MODEL,
|
|
110
123
|
timeoutMs: DEFAULT_TIMEOUT_PRO_MS,
|
|
111
124
|
thinkingLevel: PRO_THINKING_LEVEL,
|
|
@@ -146,7 +159,7 @@ export const TOOL_CONTRACTS = [
|
|
|
146
159
|
type: 'object[]',
|
|
147
160
|
required: false,
|
|
148
161
|
constraints: '1-20 files, 100K chars/file',
|
|
149
|
-
description: 'Optional full
|
|
162
|
+
description: 'Optional short excerpts for supplementary context only; avoid full files — the diff is the primary source.',
|
|
150
163
|
},
|
|
151
164
|
],
|
|
152
165
|
outputShape: '{summary, overallRisk, findings[], testsNeeded[], contextualInsights[], totalFindings}',
|
|
@@ -154,6 +167,7 @@ export const TOOL_CONTRACTS = [
|
|
|
154
167
|
'Requires generate_diff to be called first.',
|
|
155
168
|
'Combined diff + file context is bounded by MAX_CONTEXT_CHARS.',
|
|
156
169
|
'maxFindings caps output after generation.',
|
|
170
|
+
'files[] is token-expensive — omit unless the diff lacks critical structural context (e.g. class hierarchy, imports). Never pass full files.',
|
|
157
171
|
],
|
|
158
172
|
crossToolFlow: [
|
|
159
173
|
'findings[].title -> suggest_search_replace.findingTitle',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CreateTaskRequestHandlerExtra } from '@modelcontextprotocol/sdk/experimental/tasks/interfaces.js';
|
|
1
2
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
3
|
import type { ZodRawShapeCompat } from '@modelcontextprotocol/sdk/server/zod-compat.js';
|
|
3
4
|
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
@@ -34,6 +35,12 @@ interface ProgressExtra {
|
|
|
34
35
|
params: ProgressNotificationParams;
|
|
35
36
|
}) => Promise<void>;
|
|
36
37
|
}
|
|
38
|
+
export interface ToolAnnotations {
|
|
39
|
+
readOnlyHint?: boolean;
|
|
40
|
+
idempotentHint?: boolean;
|
|
41
|
+
openWorldHint?: boolean;
|
|
42
|
+
destructiveHint?: boolean;
|
|
43
|
+
}
|
|
37
44
|
export interface StructuredToolTaskConfig<TInput extends object = Record<string, unknown>, TResult extends object = Record<string, unknown>, TFinal extends TResult = TResult> {
|
|
38
45
|
/** Tool name registered with the MCP server (e.g. 'analyze_pr_impact'). */
|
|
39
46
|
name: string;
|
|
@@ -84,6 +91,8 @@ export interface StructuredToolTaskConfig<TInput extends object = Record<string,
|
|
|
84
91
|
progressContext?: (input: TInput) => string;
|
|
85
92
|
/** Optional short outcome suffix for the completion progress message (e.g., "3 findings"). */
|
|
86
93
|
formatOutcome?: (result: TFinal) => string;
|
|
94
|
+
/** Optional MCP annotation overrides for this tool. */
|
|
95
|
+
annotations?: ToolAnnotations;
|
|
87
96
|
/** Builds the system instruction and user prompt from parsed tool input. */
|
|
88
97
|
buildPrompt: (input: TInput, ctx: ToolExecutionContext) => PromptParts;
|
|
89
98
|
}
|
|
@@ -92,5 +101,27 @@ export declare function wrapToolHandler<TInput, TResult extends CallToolResult>(
|
|
|
92
101
|
toolName: string;
|
|
93
102
|
progressContext?: (input: TInput) => string;
|
|
94
103
|
}, handler: (input: TInput, extra: ProgressExtra) => Promise<TResult> | TResult): (input: TInput, extra: ProgressExtra) => Promise<TResult>;
|
|
104
|
+
interface TaskLike {
|
|
105
|
+
taskId: string;
|
|
106
|
+
}
|
|
107
|
+
export declare class ToolTaskRunner<TInput extends object, TResult extends object, TFinal extends TResult> {
|
|
108
|
+
private readonly server;
|
|
109
|
+
private readonly config;
|
|
110
|
+
private readonly extra;
|
|
111
|
+
private readonly task;
|
|
112
|
+
private diffSlotSnapshot;
|
|
113
|
+
private hasSnapshot;
|
|
114
|
+
private readonly responseSchema;
|
|
115
|
+
private readonly onLog;
|
|
116
|
+
private readonly reportProgress;
|
|
117
|
+
private progressContext;
|
|
118
|
+
constructor(server: McpServer, config: StructuredToolTaskConfig<TInput, TResult, TFinal>, extra: CreateTaskRequestHandlerExtra, task: TaskLike);
|
|
119
|
+
setDiffSlotSnapshot(diffSlotSnapshot: DiffSlot | undefined): void;
|
|
120
|
+
private updateStatusMessage;
|
|
121
|
+
private storeResultSafely;
|
|
122
|
+
private executeValidation;
|
|
123
|
+
private executeModelCall;
|
|
124
|
+
run(input: unknown): Promise<void>;
|
|
125
|
+
}
|
|
95
126
|
export declare function registerStructuredToolTask<TInput extends object, TResult extends object = Record<string, unknown>, TFinal extends TResult = TResult>(server: McpServer, config: StructuredToolTaskConfig<TInput, TResult, TFinal>): void;
|
|
96
127
|
export {};
|