import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; import { test } from 'node:test'; import { parse } from 'yaml'; import { buildTools } from '../src/tools.mjs'; // The document in this checkout: the one the application serves. const spec = parse(await readFile(new URL('../../public/openapi.yaml', import.meta.url), 'utf8')); const tools = buildTools(spec); const byName = new Map(tools.map((tool) => [tool.name, tool])); test('names follow the Laravel routes', () => { const expected = { public_site: 'GET /public/v1/site', public_good_first_issues: 'GET /public/v1/good-first-issues', public_projects_index: 'GET /public/v1/projects', public_projects_show: 'GET /public/v1/projects/{slug}', public_clients: 'GET /public/v1/clients', admin_me: 'GET /v1/me', admin_pages_index: 'GET /v1/pages', admin_pages_store: 'POST /v1/pages', admin_pages_update: 'PATCH /v1/pages/{page}', admin_pages_destroy: 'DELETE /v1/pages/{page}', admin_pages_restore: 'POST /v1/pages/{page}/restore', admin_roles_catalogue: 'GET /v1/roles/catalogue', }; for (const [name, route] of Object.entries(expected)) { const tool = byName.get(name); assert.ok(tool, `missing ${name}`); assert.equal(`${tool.operation.method} ${tool.operation.path}`, route); } }); test('names are valid MCP tool names', () => { for (const { name } of tools) assert.match(name, /^[a-z0-9_]{1,64}$/); }); test('leaves out what is not for an operator', () => { const routes = tools.map(({ operation }) => `${operation.method} ${operation.path}`); assert.ok(!routes.includes('POST /v1/auth/token')); assert.ok(!routes.some((route) => route.includes('/telemetry/'))); assert.ok(!routes.includes('POST /public/v1/contact')); assert.ok(!routes.includes('POST /public/v1/calls/{slug}/apply')); }); test('every other documented operation is a tool', () => { const documented = Object.values(spec.paths) .flatMap((item) => ['get', 'post', 'put', 'patch', 'delete'].filter((m) => item[m])).length; // auth/token, two telemetry endpoints, contact and apply. assert.equal(tools.length, documented - 5); }); test('schemas are self-contained JSON Schema', () => { for (const tool of tools) { const json = JSON.stringify(tool.inputSchema); assert.ok(!json.includes('"$ref"'), `${tool.name} still has a $ref`); assert.ok(!json.includes('"nullable"'), `${tool.name} still has nullable`); } }); test('path parameters are required, headers are not offered', () => { const show = byName.get('public_projects_show'); assert.deepEqual(show.inputSchema.required, ['slug']); assert.ok(!('X-Preview-Token' in show.inputSchema.properties)); }); test('a create requires its fields, an update does not', () => { assert.deepEqual(byName.get('admin_pages_store').inputSchema.properties.body.required, ['title', 'slug']); assert.equal(byName.get('admin_pages_update').inputSchema.properties.body.required, undefined); }); test('only the admin API is marked as needing a token', () => { for (const tool of tools) { assert.equal(tool.operation.requiresAuth, tool.name.startsWith('admin_'), tool.name); } }); test('over stdio an upload takes a local path, and the JSON body becomes optional', () => { const local = buildTools(spec, { localFiles: true }).find((tool) => tool.name === 'admin_media_store'); assert.deepEqual(local.operation.files, ['file']); assert.equal(local.inputSchema.properties.file_path.type, 'string'); assert.ok(local.inputSchema.properties.body.properties.url); assert.equal(local.inputSchema.required, undefined); }); test('over HTTP there is no local path to offer: only the URL', () => { const remote = byName.get('admin_media_store'); assert.deepEqual(remote.operation.files, []); assert.ok(!('file_path' in remote.inputSchema.properties)); assert.deepEqual(remote.inputSchema.required, ['body']); });