WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 7f5b220

Browse files
olaservoclaude
andcommitted
test(filesystem): add integration tests for structuredContent schema compliance
Adds tests to verify that directory_tree, list_directory_with_sizes, and move_file tools return structuredContent.content as a string (matching outputSchema) rather than an array. These tests prevent regression of issues #3110, #3106, #3093. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 8e931b7 commit 7f5b220

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2+
import * as fs from 'fs/promises';
3+
import * as path from 'path';
4+
import * as os from 'os';
5+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
6+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
7+
import { spawn } from 'child_process';
8+
9+
/**
10+
* Integration tests to verify that tool handlers return structuredContent
11+
* that matches the declared outputSchema.
12+
*
13+
* These tests address issues #3110, #3106, #3093 where tools were returning
14+
* structuredContent: { content: [contentBlock] } (array) instead of
15+
* structuredContent: { content: string } as declared in outputSchema.
16+
*/
17+
describe('structuredContent schema compliance', () => {
18+
let client: Client;
19+
let transport: StdioClientTransport;
20+
let testDir: string;
21+
22+
beforeEach(async () => {
23+
// Create a temp directory for testing
24+
testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-fs-test-'));
25+
26+
// Create test files
27+
await fs.writeFile(path.join(testDir, 'test.txt'), 'test content');
28+
await fs.mkdir(path.join(testDir, 'subdir'));
29+
await fs.writeFile(path.join(testDir, 'subdir', 'nested.txt'), 'nested content');
30+
31+
// Start the MCP server
32+
const serverPath = path.resolve(__dirname, '../dist/index.js');
33+
transport = new StdioClientTransport({
34+
command: 'node',
35+
args: [serverPath, testDir],
36+
});
37+
38+
client = new Client({
39+
name: 'test-client',
40+
version: '1.0.0',
41+
}, {
42+
capabilities: {}
43+
});
44+
45+
await client.connect(transport);
46+
});
47+
48+
afterEach(async () => {
49+
await client?.close();
50+
await fs.rm(testDir, { recursive: true, force: true });
51+
});
52+
53+
describe('directory_tree', () => {
54+
it('should return structuredContent.content as a string, not an array', async () => {
55+
const result = await client.callTool({
56+
name: 'directory_tree',
57+
arguments: { path: testDir }
58+
});
59+
60+
// The result should have structuredContent
61+
expect(result.structuredContent).toBeDefined();
62+
63+
// structuredContent.content should be a string (matching outputSchema: { content: z.string() })
64+
const structuredContent = result.structuredContent as { content: unknown };
65+
expect(typeof structuredContent.content).toBe('string');
66+
67+
// It should NOT be an array
68+
expect(Array.isArray(structuredContent.content)).toBe(false);
69+
70+
// The content should be valid JSON representing the tree
71+
const treeData = JSON.parse(structuredContent.content as string);
72+
expect(Array.isArray(treeData)).toBe(true);
73+
});
74+
});
75+
76+
describe('list_directory_with_sizes', () => {
77+
it('should return structuredContent.content as a string, not an array', async () => {
78+
const result = await client.callTool({
79+
name: 'list_directory_with_sizes',
80+
arguments: { path: testDir }
81+
});
82+
83+
// The result should have structuredContent
84+
expect(result.structuredContent).toBeDefined();
85+
86+
// structuredContent.content should be a string (matching outputSchema: { content: z.string() })
87+
const structuredContent = result.structuredContent as { content: unknown };
88+
expect(typeof structuredContent.content).toBe('string');
89+
90+
// It should NOT be an array
91+
expect(Array.isArray(structuredContent.content)).toBe(false);
92+
93+
// The content should contain directory listing info
94+
expect(structuredContent.content).toContain('[FILE]');
95+
});
96+
});
97+
98+
describe('move_file', () => {
99+
it('should return structuredContent.content as a string, not an array', async () => {
100+
const sourcePath = path.join(testDir, 'test.txt');
101+
const destPath = path.join(testDir, 'moved.txt');
102+
103+
const result = await client.callTool({
104+
name: 'move_file',
105+
arguments: {
106+
source: sourcePath,
107+
destination: destPath
108+
}
109+
});
110+
111+
// The result should have structuredContent
112+
expect(result.structuredContent).toBeDefined();
113+
114+
// structuredContent.content should be a string (matching outputSchema: { content: z.string() })
115+
const structuredContent = result.structuredContent as { content: unknown };
116+
expect(typeof structuredContent.content).toBe('string');
117+
118+
// It should NOT be an array
119+
expect(Array.isArray(structuredContent.content)).toBe(false);
120+
121+
// The content should contain success message
122+
expect(structuredContent.content).toContain('Successfully moved');
123+
});
124+
});
125+
126+
describe('list_directory (control - already working)', () => {
127+
it('should return structuredContent.content as a string', async () => {
128+
const result = await client.callTool({
129+
name: 'list_directory',
130+
arguments: { path: testDir }
131+
});
132+
133+
expect(result.structuredContent).toBeDefined();
134+
135+
const structuredContent = result.structuredContent as { content: unknown };
136+
expect(typeof structuredContent.content).toBe('string');
137+
expect(Array.isArray(structuredContent.content)).toBe(false);
138+
});
139+
});
140+
141+
describe('search_files (control - already working)', () => {
142+
it('should return structuredContent.content as a string', async () => {
143+
const result = await client.callTool({
144+
name: 'search_files',
145+
arguments: {
146+
path: testDir,
147+
pattern: '*.txt'
148+
}
149+
});
150+
151+
expect(result.structuredContent).toBeDefined();
152+
153+
const structuredContent = result.structuredContent as { content: unknown };
154+
expect(typeof structuredContent.content).toBe('string');
155+
expect(Array.isArray(structuredContent.content)).toBe(false);
156+
});
157+
});
158+
});

0 commit comments

Comments
 (0)