overseer-mcp 0.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/.env.example +40 -0
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/configure-overseer.bat +239 -0
- package/dist/config.js +150 -0
- package/dist/index.js +64 -0
- package/dist/init/codexConfig.js +27 -0
- package/dist/init/codexConfigCommand.js +78 -0
- package/dist/init/command.js +98 -0
- package/dist/init/config.js +204 -0
- package/dist/init/instructions.js +137 -0
- package/dist/init/instructionsCommand.js +11 -0
- package/dist/prompts.js +144 -0
- package/dist/providers/anthropic.js +50 -0
- package/dist/providers/cli.js +52 -0
- package/dist/providers/openaiCompatible.js +42 -0
- package/dist/providers/types.js +12 -0
- package/dist/repo/files.js +47 -0
- package/dist/repo/findRoot.js +19 -0
- package/dist/repo/git.js +63 -0
- package/dist/repo/retrieve.js +118 -0
- package/dist/repo/tests.js +21 -0
- package/dist/tools/consultExpert.js +44 -0
- package/dist/tools/getPlan.js +82 -0
- package/dist/tools/requestReview.js +48 -0
- package/dist/tools/shared.js +35 -0
- package/package.json +44 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { withRepoRoot } from "../config.js";
|
|
3
|
+
import { buildGetPlanUserPayload, GET_PLAN_FULL_SYSTEM_PROMPT, GET_PLAN_LITE_SYSTEM_PROMPT } from "../prompts.js";
|
|
4
|
+
import { readFiles } from "../repo/files.js";
|
|
5
|
+
import { gitStatus, gitTrackedFileList, gitTrackedFiles } from "../repo/git.js";
|
|
6
|
+
import { extractTerms, outlineFile, selectRelevantFiles } from "../repo/retrieve.js";
|
|
7
|
+
import { errorResult, LITE_PLAN_MAX_TOKENS, PLAN_MAX_TOKENS, TEMPERATURE, textResult } from "./shared.js";
|
|
8
|
+
export const getPlanInputSchema = {
|
|
9
|
+
task: z.string().min(1).describe("The user's request, verbatim."),
|
|
10
|
+
notes: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("Optional: the executor's own summary or hypothesis of what/where the change is."),
|
|
14
|
+
file_summaries: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Executor-provided summaries of relevant files. When provided, the server skips reading files from disk and forwards these summaries directly to the expert. Use this to save tokens and let the executor control what context the expert sees."),
|
|
18
|
+
focus_paths: z.array(z.string()).optional().describe("Optional seed files the executor suspects are relevant."),
|
|
19
|
+
repo_root: z.string().optional().describe("Absolute path to the project root. Overrides the server default."),
|
|
20
|
+
mode: z.enum(["lite", "full"]).optional().default("full").describe("Use full for a complete contract, lite for a shorter orientation."),
|
|
21
|
+
};
|
|
22
|
+
export async function handleGetPlan(input, config) {
|
|
23
|
+
try {
|
|
24
|
+
const mode = input.mode ?? "full";
|
|
25
|
+
const effective = input.repo_root ? withRepoRoot(config, input.repo_root) : config;
|
|
26
|
+
const [status, repoMap] = await Promise.all([
|
|
27
|
+
gitStatus(effective.repoRoot),
|
|
28
|
+
gitTrackedFiles(effective.repoRoot, 400),
|
|
29
|
+
]);
|
|
30
|
+
let snapshots = [];
|
|
31
|
+
let outlines = [];
|
|
32
|
+
let autoSelected = [];
|
|
33
|
+
if (input.file_summaries) {
|
|
34
|
+
// Summary mode: executor provides its own file analysis.
|
|
35
|
+
// Server skips disk reads and forwards summaries directly.
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
// Disk-read mode: server selects and reads files from disk.
|
|
39
|
+
const focus = (input.focus_paths ?? []).slice(0, effective.maxFiles);
|
|
40
|
+
const tracked = await gitTrackedFileList(effective.repoRoot, 2000);
|
|
41
|
+
const terms = extractTerms(`${input.task} ${input.notes ?? ""}`);
|
|
42
|
+
const remaining = effective.maxFiles - focus.length;
|
|
43
|
+
const relevant = remaining > 0
|
|
44
|
+
? await selectRelevantFiles(effective.repoRoot, tracked, terms, new Set(focus), remaining)
|
|
45
|
+
: [];
|
|
46
|
+
const candidates = [...focus, ...relevant];
|
|
47
|
+
const fullCount = effective.planFullFiles ?? 0;
|
|
48
|
+
const fullPaths = candidates.slice(0, fullCount);
|
|
49
|
+
const outlinePaths = candidates.slice(fullCount);
|
|
50
|
+
const [fullSnapshots, outlineSources] = await Promise.all([
|
|
51
|
+
readFiles(effective.repoRoot, fullPaths, effective.maxFileBytes),
|
|
52
|
+
readFiles(effective.repoRoot, outlinePaths, effective.maxFileBytes),
|
|
53
|
+
]);
|
|
54
|
+
snapshots = fullSnapshots;
|
|
55
|
+
outlines = outlineSources.map((file) => ({
|
|
56
|
+
path: file.path,
|
|
57
|
+
outline: file.error ? `(could not read: ${file.error})` : outlineFile(file.content ?? ""),
|
|
58
|
+
}));
|
|
59
|
+
autoSelected = fullPaths.filter((p) => relevant.includes(p));
|
|
60
|
+
}
|
|
61
|
+
const user = buildGetPlanUserPayload({
|
|
62
|
+
task: input.task,
|
|
63
|
+
notes: input.notes,
|
|
64
|
+
gitStatus: status,
|
|
65
|
+
repoMap,
|
|
66
|
+
snapshots,
|
|
67
|
+
autoSelected,
|
|
68
|
+
outlines,
|
|
69
|
+
fileSummaries: input.file_summaries,
|
|
70
|
+
});
|
|
71
|
+
const text = await effective.provider.complete({
|
|
72
|
+
system: mode === "lite" ? GET_PLAN_LITE_SYSTEM_PROMPT : GET_PLAN_FULL_SYSTEM_PROMPT,
|
|
73
|
+
user,
|
|
74
|
+
maxTokens: mode === "lite" ? LITE_PLAN_MAX_TOKENS : PLAN_MAX_TOKENS,
|
|
75
|
+
temperature: TEMPERATURE,
|
|
76
|
+
});
|
|
77
|
+
return textResult(text);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
return errorResult("get_plan", err);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { withRepoRoot } from "../config.js";
|
|
3
|
+
import { buildRequestReviewUserPayload, renderTestResult, REQUEST_REVIEW_SYSTEM_PROMPT } from "../prompts.js";
|
|
4
|
+
import { gitDiff, gitDiffStaged } from "../repo/git.js";
|
|
5
|
+
import { runTests } from "../repo/tests.js";
|
|
6
|
+
import { errorResult, REVIEW_MAX_TOKENS, TEMPERATURE, textResult, truncateBytes } from "./shared.js";
|
|
7
|
+
export const requestReviewInputSchema = {
|
|
8
|
+
task: z.string().optional().describe("Original task for reference."),
|
|
9
|
+
diff_summary: z.string().optional().describe("Executor-provided summary of changes made. When provided, the server skips running git diff."),
|
|
10
|
+
test_output_summary: z.string().optional().describe("Executor-provided summary of test/validation results. When provided, the server skips running the test command."),
|
|
11
|
+
repo_root: z.string().optional().describe("Absolute path to the project root. Overrides the server default."),
|
|
12
|
+
run_tests: z.boolean().optional().default(true).describe("Run OVERSEER_TEST_COMMAND when configured. Ignored when test_output_summary is provided."),
|
|
13
|
+
};
|
|
14
|
+
async function getTestOutput(input, config) {
|
|
15
|
+
if (input.run_tests === false) {
|
|
16
|
+
return "(not run: run_tests was false)";
|
|
17
|
+
}
|
|
18
|
+
if (!config.testCommand) {
|
|
19
|
+
return "(not run: OVERSEER_TEST_COMMAND is not set)";
|
|
20
|
+
}
|
|
21
|
+
return renderTestResult(await runTests(config.repoRoot, config.testCommand, config.testTimeoutMs));
|
|
22
|
+
}
|
|
23
|
+
export async function handleRequestReview(input, config) {
|
|
24
|
+
try {
|
|
25
|
+
const effective = input.repo_root ? withRepoRoot(config, input.repo_root) : config;
|
|
26
|
+
const diff = input.diff_summary ? "" : await gitDiff(effective.repoRoot);
|
|
27
|
+
const stagedDiff = input.diff_summary ? "" : await gitDiffStaged(effective.repoRoot);
|
|
28
|
+
const testOutput = input.test_output_summary ? "" : await getTestOutput(input, effective);
|
|
29
|
+
const user = buildRequestReviewUserPayload({
|
|
30
|
+
task: input.task,
|
|
31
|
+
gitDiff: diff ? truncateBytes(diff, effective.maxDiffBytes) : "",
|
|
32
|
+
gitDiffStaged: stagedDiff ? truncateBytes(stagedDiff, effective.maxDiffBytes) : "",
|
|
33
|
+
testOutput,
|
|
34
|
+
diffSummary: input.diff_summary,
|
|
35
|
+
testOutputSummary: input.test_output_summary,
|
|
36
|
+
});
|
|
37
|
+
const text = await effective.provider.complete({
|
|
38
|
+
system: REQUEST_REVIEW_SYSTEM_PROMPT,
|
|
39
|
+
user,
|
|
40
|
+
maxTokens: REVIEW_MAX_TOKENS,
|
|
41
|
+
temperature: TEMPERATURE,
|
|
42
|
+
});
|
|
43
|
+
return textResult(text);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
return errorResult("request_review", err);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readFiles } from "../repo/files.js";
|
|
2
|
+
export const CONSULT_MAX_TOKENS = 1500;
|
|
3
|
+
export const PLAN_MAX_TOKENS = 2500;
|
|
4
|
+
export const LITE_PLAN_MAX_TOKENS = 1500;
|
|
5
|
+
export const REVIEW_MAX_TOKENS = 2500;
|
|
6
|
+
export const TEMPERATURE = 0.2;
|
|
7
|
+
export function textResult(text) {
|
|
8
|
+
return { content: [{ type: "text", text }] };
|
|
9
|
+
}
|
|
10
|
+
export function errorResult(toolName, err) {
|
|
11
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
12
|
+
return {
|
|
13
|
+
content: [{ type: "text", text: `${toolName} failed: ${message}` }],
|
|
14
|
+
isError: true,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export async function readCappedFiles(config, paths) {
|
|
18
|
+
const filePaths = paths ?? [];
|
|
19
|
+
const selected = filePaths.slice(0, config.maxFiles);
|
|
20
|
+
const snapshots = await readFiles(config.repoRoot, selected, config.maxFileBytes);
|
|
21
|
+
if (filePaths.length > selected.length) {
|
|
22
|
+
snapshots.push({
|
|
23
|
+
path: "(omitted files)",
|
|
24
|
+
error: `${filePaths.length - selected.length} file(s) omitted by OVERSEER_MAX_FILES=${config.maxFiles}`,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return snapshots;
|
|
28
|
+
}
|
|
29
|
+
export function truncateBytes(text, maxBytes) {
|
|
30
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
31
|
+
if (bytes <= maxBytes)
|
|
32
|
+
return text;
|
|
33
|
+
const head = Buffer.from(text, "utf8").subarray(0, maxBytes).toString("utf8");
|
|
34
|
+
return `${head}\n... (truncated at ${maxBytes} bytes; content is ${bytes} bytes)`;
|
|
35
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "overseer-mcp",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "An MCP server that lets a cheaper executor agent consult a more capable expert model at the three moments it tends to derail: planning, mid-implementation decisions, and final review. The expert reads ground truth (git + disk), never the executor's summary.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"overseer-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE",
|
|
13
|
+
".env.example",
|
|
14
|
+
"configure-overseer.bat"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"dev": "tsc --watch",
|
|
19
|
+
"test": "npm run build && node --test test/*.test.mjs",
|
|
20
|
+
"start": "node dist/index.js",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"mcp",
|
|
25
|
+
"model-context-protocol",
|
|
26
|
+
"agents",
|
|
27
|
+
"multi-agent",
|
|
28
|
+
"llm",
|
|
29
|
+
"orchestration",
|
|
30
|
+
"code-review"
|
|
31
|
+
],
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
38
|
+
"zod": "^3.23.8"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^20.14.0",
|
|
42
|
+
"typescript": "^5.5.4"
|
|
43
|
+
}
|
|
44
|
+
}
|