Release v0.3.3
This commit is contained in:
@@ -66,6 +66,33 @@ function httpJson(port, payload, headers = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
function httpGet(port, path = '/', headers = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = http.request(
|
||||
{
|
||||
host: '127.0.0.1',
|
||||
port,
|
||||
method: 'GET',
|
||||
path,
|
||||
headers,
|
||||
},
|
||||
(response) => {
|
||||
const chunks = [];
|
||||
response.on('data', (chunk) => chunks.push(chunk));
|
||||
response.on('end', () => {
|
||||
resolve({
|
||||
statusCode: response.statusCode,
|
||||
headers: response.headers,
|
||||
body: Buffer.concat(chunks).toString('utf8'),
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
request.on('error', reject);
|
||||
request.end();
|
||||
});
|
||||
}
|
||||
|
||||
test('initialize negotiates the current MCP protocol version by default', async () => {
|
||||
const server = createServer();
|
||||
const response = await server.handleRpcRequest({
|
||||
@@ -222,6 +249,43 @@ test('HTTP notifications return 202 Accepted with no body', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('HTTP GET /tools returns debug tool metadata and curl examples', async () => {
|
||||
const server = createServer({
|
||||
listTools: () => [
|
||||
{
|
||||
name: 'get_project_info',
|
||||
description: 'Return project info.',
|
||||
inputSchema: { type: 'object', properties: {} },
|
||||
},
|
||||
],
|
||||
listToolCatalog: () => [
|
||||
{
|
||||
name: 'get_project_info',
|
||||
description: 'Return project info.',
|
||||
category: 'project',
|
||||
profile: 'core',
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
}, { port: 0 });
|
||||
await server.start();
|
||||
try {
|
||||
const response = await httpGet(server.getPort(), '/tools?catalog=1');
|
||||
const payload = JSON.parse(response.body);
|
||||
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.equal(payload.ok, true);
|
||||
assert.equal(payload.name, 'test-server');
|
||||
assert.equal(payload.version, '0.0.0-test');
|
||||
assert.equal(payload.count, 1);
|
||||
assert.equal(payload.tools[0].category, 'project');
|
||||
assert.match(payload.examples.health, /curl http:\/\/127\.0\.0\.1:\d+\/health/);
|
||||
assert.match(payload.examples.tools, /curl http:\/\/127\.0\.0\.1:\d+\/tools/);
|
||||
} finally {
|
||||
await server.stop();
|
||||
}
|
||||
});
|
||||
|
||||
test('HTTP initialize can return an optional session id when sessions are enabled', async () => {
|
||||
const server = createServer({}, { port: 0, enableSessions: true });
|
||||
await server.start();
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
const { buildSnippet, createFileTools } = require('../lib/tools/files');
|
||||
|
||||
function createSchema(properties, required) {
|
||||
return { type: 'object', properties, required };
|
||||
}
|
||||
|
||||
function createTools(projectPath) {
|
||||
return createFileTools({
|
||||
createSchema,
|
||||
getRuntimeContext: () => ({ projectPath }),
|
||||
});
|
||||
}
|
||||
|
||||
function getTool(tools, name) {
|
||||
const tool = tools.find((item) => item.name === name);
|
||||
assert.ok(tool, `Expected ${name} to exist`);
|
||||
return tool;
|
||||
}
|
||||
|
||||
test('buildSnippet returns focused line-numbered context', () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'funplay-cocos-files-'));
|
||||
try {
|
||||
const filePath = path.join(root, 'sample.ts');
|
||||
fs.writeFileSync(filePath, ['alpha', 'beta', 'gamma', 'delta'].join('\n'), 'utf8');
|
||||
|
||||
const snippet = buildSnippet(filePath, 2, 1);
|
||||
|
||||
assert.match(snippet, / 1 \| alpha/);
|
||||
assert.match(snippet, />\s+2 \| beta/);
|
||||
assert.match(snippet, / 3 \| gamma/);
|
||||
assert.doesNotMatch(snippet, /delta/);
|
||||
} finally {
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('file tools write, read, replace, search, list, and check project files', async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'funplay-cocos-files-'));
|
||||
try {
|
||||
fs.mkdirSync(path.join(root, 'assets'), { recursive: true });
|
||||
const tools = createTools(root);
|
||||
|
||||
const writeResult = await getTool(tools, 'write_file').handler({
|
||||
path: 'assets/player.ts',
|
||||
content: 'const name = "Hero";\nconst clone = "Hero";\n',
|
||||
});
|
||||
assert.match(writeResult, /Wrote \d+ chars/);
|
||||
|
||||
const readResult = await getTool(tools, 'read_file').handler({ path: 'assets/player.ts' });
|
||||
assert.match(readResult, /const name = "Hero"/);
|
||||
|
||||
await getTool(tools, 'replace_in_file').handler({
|
||||
path: 'assets/player.ts',
|
||||
search: 'Hero',
|
||||
replace: 'Player',
|
||||
replaceAll: true,
|
||||
});
|
||||
assert.equal(fs.readFileSync(path.join(root, 'assets', 'player.ts'), 'utf8').includes('Hero'), false);
|
||||
|
||||
const searchResult = await getTool(tools, 'search_files').handler({ pattern: '*.ts', directory: 'assets' });
|
||||
assert.deepEqual(searchResult.files, ['assets/player.ts']);
|
||||
|
||||
const listResult = await getTool(tools, 'list_directory').handler({ path: 'assets' });
|
||||
assert.deepEqual(listResult.entries, [{ name: 'player.ts', type: 'file' }]);
|
||||
|
||||
const existsResult = await getTool(tools, 'exists').handler({ path: 'assets/player.ts' });
|
||||
assert.deepEqual(existsResult, {
|
||||
path: 'assets/player.ts',
|
||||
exists: true,
|
||||
isFile: true,
|
||||
isDirectory: false,
|
||||
});
|
||||
} finally {
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user