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