mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-31 04:07:52 +00:00
The inline operations.map(...) block in src/mcp/server.ts became the only source of truth for agent-facing tool definitions. Extract into a reusable exported helper so the v0.15 subagent tool registry can call it with a filtered OPERATIONS subset instead of duplicating the shape. Byte-for-byte equivalence regression pinned in test/mcp-tool-defs.test.ts — legacy inline mapping kept verbatim inside the test so any future drift between the new helper and the pre-extraction MCP schema fails loudly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
68 lines
2.4 KiB
TypeScript
68 lines
2.4 KiB
TypeScript
/**
|
|
* Regression test for the MCP tool-def extraction (v0.15.0 Lane 1A).
|
|
*
|
|
* Before v0.15 the mapping lived inline in src/mcp/server.ts. After the
|
|
* extraction, buildToolDefs is the single source of truth; the subagent tool
|
|
* registry calls it with a filtered OPERATIONS subset. This test pins the
|
|
* extracted output to the pre-extraction shape byte-for-byte so we don't
|
|
* silently drift the MCP-facing tool schema.
|
|
*/
|
|
|
|
import { describe, test, expect } from 'bun:test';
|
|
import { operations } from '../src/core/operations.ts';
|
|
import { buildToolDefs } from '../src/mcp/tool-defs.ts';
|
|
|
|
// Pre-extraction inline shape — lifted verbatim from the original
|
|
// src/mcp/server.ts block so any future drift fails this test loudly.
|
|
function legacyInlineMap(ops: typeof operations) {
|
|
return ops.map(op => ({
|
|
name: op.name,
|
|
description: op.description,
|
|
inputSchema: {
|
|
type: 'object' as const,
|
|
properties: Object.fromEntries(
|
|
Object.entries(op.params).map(([k, v]) => [k, {
|
|
type: v.type === 'array' ? 'array' : v.type,
|
|
...(v.description ? { description: v.description } : {}),
|
|
...(v.enum ? { enum: v.enum } : {}),
|
|
...(v.items ? { items: { type: v.items.type } } : {}),
|
|
}]),
|
|
),
|
|
required: Object.entries(op.params)
|
|
.filter(([, v]) => v.required)
|
|
.map(([k]) => k),
|
|
},
|
|
}));
|
|
}
|
|
|
|
describe('buildToolDefs', () => {
|
|
test('output equals pre-extraction inline mapping byte-for-byte', () => {
|
|
const extracted = buildToolDefs(operations);
|
|
const inline = legacyInlineMap(operations);
|
|
expect(JSON.stringify(extracted)).toBe(JSON.stringify(inline));
|
|
});
|
|
|
|
test('preserves operation count', () => {
|
|
expect(buildToolDefs(operations).length).toBe(operations.length);
|
|
});
|
|
|
|
test('accepts an arbitrary Operation subset (for subagent tool registry)', () => {
|
|
const subset = operations.slice(0, 3);
|
|
const defs = buildToolDefs(subset);
|
|
expect(defs.length).toBe(3);
|
|
expect(defs.map(d => d.name)).toEqual(subset.map(o => o.name));
|
|
});
|
|
|
|
test('empty input returns empty array', () => {
|
|
expect(buildToolDefs([])).toEqual([]);
|
|
});
|
|
|
|
test('every def has object inputSchema with properties + required array', () => {
|
|
for (const def of buildToolDefs(operations)) {
|
|
expect(def.inputSchema.type).toBe('object');
|
|
expect(typeof def.inputSchema.properties).toBe('object');
|
|
expect(Array.isArray(def.inputSchema.required)).toBe(true);
|
|
}
|
|
});
|
|
});
|