mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-30 11:22:34 +00:00
refactor(mcp): extract buildToolDefs helper for subagent tool registry reuse
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>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
5fd9cd2644
commit
b44a31cc0d
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user