Skip to content

Commit 3f98fa2

Browse files
committed
feat: expose context feedback through MCP
1 parent 6f8c82b commit 3f98fa2

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

src/mcp/server.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@ import { packApplicationTask, planApplicationTask } from "../application/task-se
3434
import { inspectApplicationImpact, testApplicationChanges, verifyApplicationChanges } from "../application/verification-service.js";
3535
import { explainApplicationPath, retrieveApplicationContext } from "../application/retrieval-service.js";
3636
import { getContextFiles } from "../application/context-service.js";
37+
import { submitApplicationContextFeedback } from "../application/context-feedback-service.js";
38+
import type { ContextFeedbackLabel, ContextFeedbackTarget } from "../context-registry/types.js";
3739

3840
export const opencodePlusplusMcpToolNames = [
3941
"opencode_plusplus_build",
4042
"opencode_plusplus_plan",
4143
"opencode_plusplus_pack",
4244
"opencode_plusplus_retrieve",
45+
"opencode_plusplus_context_feedback",
4346
"opencode_plusplus_tests",
4447
"opencode_plusplus_impact",
4548
"opencode_plusplus_verify",
@@ -84,6 +87,19 @@ interface RetrieveArguments {
8487
includeStaleAnnotation?: boolean;
8588
}
8689

90+
interface ContextFeedbackInput {
91+
repo?: string;
92+
entryId: string;
93+
source: string;
94+
version?: string;
95+
revision: number;
96+
target: ContextFeedbackTarget;
97+
file?: string;
98+
retrievalId?: string;
99+
interventionId?: string;
100+
label: ContextFeedbackLabel;
101+
}
102+
87103
export function createOpenCodePlusplusMcpServer(): McpServer {
88104
const server = new McpServer(
89105
{
@@ -159,6 +175,26 @@ export function createOpenCodePlusplusMcpServer(): McpServer {
159175
async (args) => jsonToolResult(await runRetrieve(args))
160176
);
161177

178+
server.registerTool(
179+
"opencode_plusplus_context_feedback",
180+
{
181+
description: "Record local quality feedback for a Context entry without storing task or source content.",
182+
inputSchema: z.object({
183+
repo: z.string().optional().default("."),
184+
entryId: z.string(),
185+
source: z.string(),
186+
version: z.string().optional(),
187+
revision: z.number().int().nonnegative(),
188+
target: z.enum(["entry", "file", "retrieval-result", "intervention"]),
189+
file: z.string().optional(),
190+
retrievalId: z.string().optional(),
191+
interventionId: z.string().optional(),
192+
label: z.enum(["useful", "not-useful", "outdated", "inaccurate", "incomplete", "wrong-version", "wrong-example", "irrelevant"])
193+
})
194+
},
195+
async (args) => jsonToolResult(await runContextFeedback(args))
196+
);
197+
162198
server.registerTool(
163199
"opencode_plusplus_tests",
164200
{
@@ -320,6 +356,8 @@ export async function executeOpenCodePlusplusMcpTool(name: OpenCodePlusplusMcpTo
320356
return runTaskPack(args as PackInput);
321357
case "opencode_plusplus_retrieve":
322358
return runRetrieve(args as RetrieveArguments);
359+
case "opencode_plusplus_context_feedback":
360+
return runContextFeedback(args as ContextFeedbackInput);
323361
case "opencode_plusplus_tests":
324362
return runTests(args as TestsInput);
325363
case "opencode_plusplus_impact":
@@ -482,6 +520,17 @@ async function runRetrieve(args: RetrieveArguments): Promise<OpenCodePlusplusMcp
482520
return retrieveApplicationContext({ repo: args.repo ?? ".", ...args });
483521
}
484522

523+
async function runContextFeedback(args: ContextFeedbackInput): Promise<OpenCodePlusplusMcpResult> {
524+
const result = await submitApplicationContextFeedback({ ...args, repo: args.repo ?? "." });
525+
return {
526+
enabled: result.enabled,
527+
feedback: result.feedback,
528+
stats: result.stats,
529+
transport: result.transport,
530+
note: "Feedback is separate from local annotations and cannot satisfy evidence or change a decision."
531+
};
532+
}
533+
485534
async function runTests(args: TestsInput): Promise<OpenCodePlusplusMcpResult> {
486535
const { context: _context, ...result } = await testApplicationChanges({ repo: args.repo ?? ".", ...args });
487536
return result;

test/mcp.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ test("mcp server exposes repo context tools", async () => {
3737
"opencode_plusplus_plan",
3838
"opencode_plusplus_pack",
3939
"opencode_plusplus_retrieve",
40+
"opencode_plusplus_context_feedback",
4041
"opencode_plusplus_tests",
4142
"opencode_plusplus_impact",
4243
"opencode_plusplus_verify",
@@ -49,6 +50,26 @@ test("mcp server exposes repo context tools", async () => {
4950
]);
5051
});
5152

53+
test("MCP context feedback returns local stats and stays offline", async () => {
54+
const root = createMcpRepo();
55+
try {
56+
const result = await executeOpenCodePlusplusMcpTool("opencode_plusplus_context_feedback", {
57+
repo: root,
58+
entryId: "official/payments",
59+
source: "official",
60+
revision: 1,
61+
target: "entry",
62+
label: "useful"
63+
});
64+
assert.equal(result.enabled, true);
65+
assert.equal((result.stats as { total: number }).total, 1);
66+
assert.equal((result.transport as { status: string }).status, "disabled");
67+
assert.match(String(result.note), /cannot satisfy evidence/);
68+
} finally {
69+
rmSync(root, { recursive: true, force: true });
70+
}
71+
});
72+
5273
test("opencode_plusplus_retrieve returns hits and suggested commands", async () => {
5374
const root = createMcpRepo();
5475
try {

0 commit comments

Comments
 (0)