@codemcp/workflows 3.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -0
- package/.vibe/conversation-state.sqlite +0 -0
- package/LICENSE +674 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +74 -0
- package/dist/index.js.map +1 -0
- package/dist/notification-service.d.ts +14 -0
- package/dist/notification-service.d.ts.map +1 -0
- package/dist/notification-service.js +18 -0
- package/dist/notification-service.js.map +1 -0
- package/dist/resource-handlers/conversation-state.d.ts +15 -0
- package/dist/resource-handlers/conversation-state.d.ts.map +1 -0
- package/dist/resource-handlers/conversation-state.js +40 -0
- package/dist/resource-handlers/conversation-state.js.map +1 -0
- package/dist/resource-handlers/development-plan.d.ts +14 -0
- package/dist/resource-handlers/development-plan.d.ts.map +1 -0
- package/dist/resource-handlers/development-plan.js +31 -0
- package/dist/resource-handlers/development-plan.js.map +1 -0
- package/dist/resource-handlers/index.d.ts +24 -0
- package/dist/resource-handlers/index.d.ts.map +1 -0
- package/dist/resource-handlers/index.js +62 -0
- package/dist/resource-handlers/index.js.map +1 -0
- package/dist/resource-handlers/system-prompt.d.ts +15 -0
- package/dist/resource-handlers/system-prompt.d.ts.map +1 -0
- package/dist/resource-handlers/system-prompt.js +40 -0
- package/dist/resource-handlers/system-prompt.js.map +1 -0
- package/dist/resource-handlers/workflow-resource.d.ts +15 -0
- package/dist/resource-handlers/workflow-resource.d.ts.map +1 -0
- package/dist/resource-handlers/workflow-resource.js +85 -0
- package/dist/resource-handlers/workflow-resource.js.map +1 -0
- package/dist/response-renderer.d.ts +30 -0
- package/dist/response-renderer.d.ts.map +1 -0
- package/dist/response-renderer.js +94 -0
- package/dist/response-renderer.js.map +1 -0
- package/dist/server-config.d.ts +34 -0
- package/dist/server-config.d.ts.map +1 -0
- package/dist/server-config.js +486 -0
- package/dist/server-config.js.map +1 -0
- package/dist/server-helpers.d.ts +62 -0
- package/dist/server-helpers.d.ts.map +1 -0
- package/dist/server-helpers.js +156 -0
- package/dist/server-helpers.js.map +1 -0
- package/dist/server-implementation.d.ts +74 -0
- package/dist/server-implementation.d.ts.map +1 -0
- package/dist/server-implementation.js +201 -0
- package/dist/server-implementation.js.map +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +5 -0
- package/dist/server.js.map +1 -0
- package/dist/tool-handlers/base-tool-handler.d.ts +50 -0
- package/dist/tool-handlers/base-tool-handler.d.ts.map +1 -0
- package/dist/tool-handlers/base-tool-handler.js +74 -0
- package/dist/tool-handlers/base-tool-handler.js.map +1 -0
- package/dist/tool-handlers/conduct-review.d.ts +49 -0
- package/dist/tool-handlers/conduct-review.d.ts.map +1 -0
- package/dist/tool-handlers/conduct-review.js +105 -0
- package/dist/tool-handlers/conduct-review.js.map +1 -0
- package/dist/tool-handlers/get-tool-info.d.ts +76 -0
- package/dist/tool-handlers/get-tool-info.d.ts.map +1 -0
- package/dist/tool-handlers/get-tool-info.js +168 -0
- package/dist/tool-handlers/get-tool-info.js.map +1 -0
- package/dist/tool-handlers/index.d.ts +42 -0
- package/dist/tool-handlers/index.d.ts.map +1 -0
- package/dist/tool-handlers/index.js +74 -0
- package/dist/tool-handlers/index.js.map +1 -0
- package/dist/tool-handlers/install-workflow.d.ts +48 -0
- package/dist/tool-handlers/install-workflow.d.ts.map +1 -0
- package/dist/tool-handlers/install-workflow.js +131 -0
- package/dist/tool-handlers/install-workflow.js.map +1 -0
- package/dist/tool-handlers/list-workflows.d.ts +47 -0
- package/dist/tool-handlers/list-workflows.d.ts.map +1 -0
- package/dist/tool-handlers/list-workflows.js +58 -0
- package/dist/tool-handlers/list-workflows.js.map +1 -0
- package/dist/tool-handlers/no-idea.d.ts +41 -0
- package/dist/tool-handlers/no-idea.d.ts.map +1 -0
- package/dist/tool-handlers/no-idea.js +29 -0
- package/dist/tool-handlers/no-idea.js.map +1 -0
- package/dist/tool-handlers/proceed-to-phase.d.ts +39 -0
- package/dist/tool-handlers/proceed-to-phase.d.ts.map +1 -0
- package/dist/tool-handlers/proceed-to-phase.js +109 -0
- package/dist/tool-handlers/proceed-to-phase.js.map +1 -0
- package/dist/tool-handlers/reset-development.d.ts +31 -0
- package/dist/tool-handlers/reset-development.d.ts.map +1 -0
- package/dist/tool-handlers/reset-development.js +48 -0
- package/dist/tool-handlers/reset-development.js.map +1 -0
- package/dist/tool-handlers/resume-workflow.d.ts +88 -0
- package/dist/tool-handlers/resume-workflow.d.ts.map +1 -0
- package/dist/tool-handlers/resume-workflow.js +213 -0
- package/dist/tool-handlers/resume-workflow.js.map +1 -0
- package/dist/tool-handlers/setup-project-docs.d.ts +36 -0
- package/dist/tool-handlers/setup-project-docs.d.ts.map +1 -0
- package/dist/tool-handlers/setup-project-docs.js +136 -0
- package/dist/tool-handlers/setup-project-docs.js.map +1 -0
- package/dist/tool-handlers/start-development.d.ts +82 -0
- package/dist/tool-handlers/start-development.d.ts.map +1 -0
- package/dist/tool-handlers/start-development.js +448 -0
- package/dist/tool-handlers/start-development.js.map +1 -0
- package/dist/tool-handlers/whats-next.d.ts +42 -0
- package/dist/tool-handlers/whats-next.d.ts.map +1 -0
- package/dist/tool-handlers/whats-next.js +118 -0
- package/dist/tool-handlers/whats-next.js.map +1 -0
- package/dist/types.d.ts +114 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +29 -0
- package/src/index.ts +93 -0
- package/src/notification-service.ts +23 -0
- package/src/resource-handlers/conversation-state.ts +55 -0
- package/src/resource-handlers/development-plan.ts +48 -0
- package/src/resource-handlers/index.ts +73 -0
- package/src/resource-handlers/system-prompt.ts +55 -0
- package/src/resource-handlers/workflow-resource.ts +126 -0
- package/src/response-renderer.ts +116 -0
- package/src/server-config.ts +744 -0
- package/src/server-helpers.ts +225 -0
- package/src/server-implementation.ts +277 -0
- package/src/server.ts +9 -0
- package/src/tool-handlers/base-tool-handler.ts +141 -0
- package/src/tool-handlers/conduct-review.ts +191 -0
- package/src/tool-handlers/get-tool-info.ts +274 -0
- package/src/tool-handlers/index.ts +117 -0
- package/src/tool-handlers/install-workflow.ts +185 -0
- package/src/tool-handlers/list-workflows.ts +94 -0
- package/src/tool-handlers/no-idea.ts +47 -0
- package/src/tool-handlers/proceed-to-phase.ts +205 -0
- package/src/tool-handlers/reset-development.ts +90 -0
- package/src/tool-handlers/resume-workflow.ts +380 -0
- package/src/tool-handlers/setup-project-docs.ts +226 -0
- package/src/tool-handlers/start-development.ts +685 -0
- package/src/tool-handlers/whats-next.ts +235 -0
- package/src/types.ts +130 -0
- package/test/e2e/core-functionality.test.ts +176 -0
- package/test/e2e/mcp-contract.test.ts +540 -0
- package/test/e2e/plan-management.test.ts +331 -0
- package/test/e2e/state-management.test.ts +392 -0
- package/test/e2e/workflow-integration.test.ts +506 -0
- package/test/unit/commit-behaviour-interface.test.ts +244 -0
- package/test/unit/conduct-review.test.ts +151 -0
- package/test/unit/reset-functionality.test.ts +72 -0
- package/test/unit/resume-workflow.test.ts +192 -0
- package/test/unit/server-tools.test.ts +311 -0
- package/test/unit/setup-project-docs-handler.test.ts +267 -0
- package/test/unit/start-development-artifact-detection.test.ts +387 -0
- package/test/unit/start-development-gitignore.test.ts +178 -0
- package/test/unit/system-prompt-resource.test.ts +101 -0
- package/test/unit/tool-handlers/no-idea.test.ts +40 -0
- package/test/utils/e2e-test-setup.ts +453 -0
- package/test/utils/run-server-in-dir.sh +27 -0
- package/test/utils/temp-files.ts +308 -0
- package/test/utils/test-access.ts +79 -0
- package/test/utils/test-helpers.ts +286 -0
- package/test/utils/test-setup.ts +78 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +12 -0
- package/vitest.config.ts +17 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
3
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { promises as fs } from 'node:fs';
|
|
6
|
+
import {
|
|
7
|
+
TempProject,
|
|
8
|
+
createTempProjectWithDefaultStateMachine,
|
|
9
|
+
} from '../utils/temp-files';
|
|
10
|
+
|
|
11
|
+
vi.unmock('fs');
|
|
12
|
+
vi.unmock('fs/promises');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* MCP Contract Validation Tests
|
|
16
|
+
*
|
|
17
|
+
* Tests the Model Context Protocol contract by using a real MCP TypeScript client
|
|
18
|
+
* from the official SDK to connect to our responsible-vibe-mcp-server and validate:
|
|
19
|
+
* - Server initialization and capability negotiation
|
|
20
|
+
* - All exposed resources (plan://current, state://current)
|
|
21
|
+
* - All exposed tools (whats_next, proceed_to_phase)
|
|
22
|
+
* - Protocol compliance and message formats
|
|
23
|
+
* - Error handling and edge cases
|
|
24
|
+
*/
|
|
25
|
+
describe('MCP Contract Validation', () => {
|
|
26
|
+
let client: Client;
|
|
27
|
+
let transport: StdioClientTransport;
|
|
28
|
+
let tempProject: TempProject;
|
|
29
|
+
let cleanup: () => Promise<void>;
|
|
30
|
+
|
|
31
|
+
beforeEach(async () => {
|
|
32
|
+
// Create temporary project for testing
|
|
33
|
+
const scenario = await createTempProjectWithDefaultStateMachine();
|
|
34
|
+
tempProject = scenario;
|
|
35
|
+
cleanup = async () => {
|
|
36
|
+
await scenario.cleanup();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Build the server if needed
|
|
40
|
+
const serverPath = path.resolve(__dirname, '../../../cli/dist/index.js');
|
|
41
|
+
const serverExists = await fs
|
|
42
|
+
.access(serverPath)
|
|
43
|
+
.then(() => true)
|
|
44
|
+
.catch(() => false);
|
|
45
|
+
|
|
46
|
+
if (!serverExists) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Server not built. Please run 'npm run build' first. Looking for: ${serverPath}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Create MCP client and transport (this will spawn the server process)
|
|
53
|
+
// Note: Due to limitations in StdioClientTransport, environment variables
|
|
54
|
+
// are not properly passed to the spawned server process, so this test
|
|
55
|
+
// may show INFO level logs despite our attempts to suppress them.
|
|
56
|
+
|
|
57
|
+
// Use a shell script wrapper that explicitly changes the working directory
|
|
58
|
+
// This ensures the server operates in the temporary directory and not the current project directory
|
|
59
|
+
// which is essential for clean test isolation
|
|
60
|
+
const wrapperScriptPath = path.resolve(
|
|
61
|
+
__dirname,
|
|
62
|
+
'../utils/run-server-in-dir.sh'
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
transport = new StdioClientTransport({
|
|
66
|
+
command: wrapperScriptPath,
|
|
67
|
+
args: [tempProject.projectPath, serverPath],
|
|
68
|
+
env: {
|
|
69
|
+
...process.env,
|
|
70
|
+
LOG_LEVEL: 'ERROR',
|
|
71
|
+
NODE_ENV: 'test',
|
|
72
|
+
VITEST: 'true',
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
client = new Client(
|
|
77
|
+
{
|
|
78
|
+
name: 'mcp-contract-test-client',
|
|
79
|
+
version: '1.0.0',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
capabilities: {
|
|
83
|
+
roots: {},
|
|
84
|
+
sampling: {},
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Connect to the server
|
|
90
|
+
await client.connect(transport);
|
|
91
|
+
|
|
92
|
+
// Start development for all MCP contract tests
|
|
93
|
+
await client.callTool({
|
|
94
|
+
name: 'start_development',
|
|
95
|
+
arguments: {
|
|
96
|
+
workflow: 'waterfall',
|
|
97
|
+
commit_behaviour: 'none', // Use 'none' for test isolation
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
afterEach(async () => {
|
|
103
|
+
// Clean up client and transport
|
|
104
|
+
if (client) {
|
|
105
|
+
await client.close();
|
|
106
|
+
}
|
|
107
|
+
if (transport) {
|
|
108
|
+
await transport.close();
|
|
109
|
+
}
|
|
110
|
+
if (cleanup) {
|
|
111
|
+
await cleanup();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('Server Initialization and Capabilities', () => {
|
|
116
|
+
it('should successfully initialize with proper server info', async () => {
|
|
117
|
+
// The connection in beforeEach validates basic initialization
|
|
118
|
+
// Here we verify the server info was properly exchanged
|
|
119
|
+
expect(client).toBeDefined();
|
|
120
|
+
|
|
121
|
+
// Verify we can make basic requests (this confirms initialization succeeded)
|
|
122
|
+
const tools = await client.listTools();
|
|
123
|
+
expect(tools).toBeDefined();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should expose correct capabilities', async () => {
|
|
127
|
+
// Test that server exposes the expected capabilities
|
|
128
|
+
// by verifying we can call the expected methods without errors
|
|
129
|
+
|
|
130
|
+
// Should support tools
|
|
131
|
+
const tools = await client.listTools();
|
|
132
|
+
expect(tools.tools).toBeDefined();
|
|
133
|
+
expect(Array.isArray(tools.tools)).toBe(true);
|
|
134
|
+
|
|
135
|
+
// Should support resources
|
|
136
|
+
const resources = await client.listResources();
|
|
137
|
+
expect(resources.resources).toBeDefined();
|
|
138
|
+
expect(Array.isArray(resources.resources)).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('Tools Contract Validation', () => {
|
|
143
|
+
it('should expose whats_next tool with correct schema', async () => {
|
|
144
|
+
const tools = await client.listTools();
|
|
145
|
+
|
|
146
|
+
const whatsNextTool = tools.tools.find(
|
|
147
|
+
tool => tool.name === 'whats_next'
|
|
148
|
+
);
|
|
149
|
+
expect(whatsNextTool).toBeDefined();
|
|
150
|
+
expect(whatsNextTool!.description).toBeTruthy();
|
|
151
|
+
expect(whatsNextTool!.inputSchema).toBeDefined();
|
|
152
|
+
|
|
153
|
+
// Verify the input schema allows the expected parameters
|
|
154
|
+
const schema = whatsNextTool!.inputSchema;
|
|
155
|
+
expect(schema.type).toBe('object');
|
|
156
|
+
expect(schema.properties).toBeDefined();
|
|
157
|
+
|
|
158
|
+
// Should accept optional parameters like user_input, context, etc.
|
|
159
|
+
const properties = schema.properties as Record<string, unknown>;
|
|
160
|
+
expect(properties.user_input).toBeDefined();
|
|
161
|
+
expect(properties.context).toBeDefined();
|
|
162
|
+
expect(properties.conversation_summary).toBeDefined();
|
|
163
|
+
expect(properties.recent_messages).toBeDefined();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should expose proceed_to_phase tool with correct schema', async () => {
|
|
167
|
+
const tools = await client.listTools();
|
|
168
|
+
|
|
169
|
+
const proceedTool = tools.tools.find(
|
|
170
|
+
tool => tool.name === 'proceed_to_phase'
|
|
171
|
+
);
|
|
172
|
+
expect(proceedTool).toBeDefined();
|
|
173
|
+
expect(proceedTool!.description).toBeTruthy();
|
|
174
|
+
expect(proceedTool!.inputSchema).toBeDefined();
|
|
175
|
+
|
|
176
|
+
const schema = proceedTool!.inputSchema;
|
|
177
|
+
expect(schema.type).toBe('object');
|
|
178
|
+
expect(schema.properties).toBeDefined();
|
|
179
|
+
|
|
180
|
+
const properties = schema.properties as Record<string, unknown>;
|
|
181
|
+
expect(properties.target_phase).toBeDefined();
|
|
182
|
+
expect(properties.reason).toBeDefined();
|
|
183
|
+
|
|
184
|
+
// target_phase should be required
|
|
185
|
+
expect(schema.required).toContain('target_phase');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should execute whats_next tool successfully', async () => {
|
|
189
|
+
const result = await client.callTool({
|
|
190
|
+
name: 'whats_next',
|
|
191
|
+
arguments: {
|
|
192
|
+
user_input: 'implement authentication system',
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
expect(result.content).toBeDefined();
|
|
197
|
+
expect(Array.isArray(result.content)).toBe(true);
|
|
198
|
+
expect(result.content.length).toBeGreaterThan(0);
|
|
199
|
+
|
|
200
|
+
// Should return text content with phase information
|
|
201
|
+
const textContent = result.content.find(c => c.type === 'text');
|
|
202
|
+
expect(textContent).toBeDefined();
|
|
203
|
+
expect(textContent!.text).toBeTruthy();
|
|
204
|
+
|
|
205
|
+
// Response should contain structured data about the phase
|
|
206
|
+
const responseText = textContent!.text;
|
|
207
|
+
expect(responseText).toContain('phase');
|
|
208
|
+
expect(responseText).toContain('instructions');
|
|
209
|
+
expect(responseText).toContain('conversation_id');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should execute proceed_to_phase tool successfully', async () => {
|
|
213
|
+
// First call whats_next to establish a conversation
|
|
214
|
+
await client.callTool({
|
|
215
|
+
name: 'whats_next',
|
|
216
|
+
arguments: {
|
|
217
|
+
user_input: 'start project',
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Then proceed to a different phase
|
|
222
|
+
const result = await client.callTool({
|
|
223
|
+
name: 'proceed_to_phase',
|
|
224
|
+
arguments: {
|
|
225
|
+
target_phase: 'design',
|
|
226
|
+
reason: 'requirements complete',
|
|
227
|
+
review_state: 'not-required',
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
expect(result.content).toBeDefined();
|
|
232
|
+
expect(Array.isArray(result.content)).toBe(true);
|
|
233
|
+
expect(result.content.length).toBeGreaterThan(0);
|
|
234
|
+
|
|
235
|
+
const textContent = result.content.find(c => c.type === 'text');
|
|
236
|
+
expect(textContent).toBeDefined();
|
|
237
|
+
expect(textContent!.text).toBeTruthy();
|
|
238
|
+
|
|
239
|
+
const responseText = textContent!.text;
|
|
240
|
+
expect(responseText).toContain('phase');
|
|
241
|
+
expect(responseText).toContain('design');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should handle tool errors gracefully', async () => {
|
|
245
|
+
// Test invalid phase transition
|
|
246
|
+
const result = await client.callTool({
|
|
247
|
+
name: 'proceed_to_phase',
|
|
248
|
+
arguments: {
|
|
249
|
+
target_phase: 'invalid_phase',
|
|
250
|
+
reason: 'test error handling',
|
|
251
|
+
review_state: 'not-required',
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Should return error information rather than throwing
|
|
256
|
+
expect(result.content).toBeDefined();
|
|
257
|
+
expect(result.isError).toBe(true);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe('Resources Contract Validation', () => {
|
|
262
|
+
it('should expose plan://current resource', async () => {
|
|
263
|
+
const resources = await client.listResources();
|
|
264
|
+
|
|
265
|
+
const planResource = resources.resources.find(
|
|
266
|
+
r => r.uri === 'plan://current'
|
|
267
|
+
);
|
|
268
|
+
expect(planResource).toBeDefined();
|
|
269
|
+
expect(planResource!.name).toBeTruthy();
|
|
270
|
+
expect(planResource!.description).toBeTruthy();
|
|
271
|
+
expect(planResource!.mimeType).toBe('text/markdown');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('should expose state://current resource', async () => {
|
|
275
|
+
const resources = await client.listResources();
|
|
276
|
+
|
|
277
|
+
const stateResource = resources.resources.find(
|
|
278
|
+
r => r.uri === 'state://current'
|
|
279
|
+
);
|
|
280
|
+
expect(stateResource).toBeDefined();
|
|
281
|
+
expect(stateResource!.name).toBeTruthy();
|
|
282
|
+
expect(stateResource!.description).toBeTruthy();
|
|
283
|
+
expect(stateResource!.mimeType).toBe('application/json');
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('should read plan resource successfully', async () => {
|
|
287
|
+
// First establish a conversation to ensure plan file exists
|
|
288
|
+
await client.callTool({
|
|
289
|
+
name: 'whats_next',
|
|
290
|
+
arguments: {
|
|
291
|
+
user_input: 'test plan resource',
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const result = await client.readResource({
|
|
296
|
+
uri: 'plan://current',
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
expect(result.contents).toBeDefined();
|
|
300
|
+
expect(Array.isArray(result.contents)).toBe(true);
|
|
301
|
+
expect(result.contents.length).toBeGreaterThan(0);
|
|
302
|
+
|
|
303
|
+
const content = result.contents[0];
|
|
304
|
+
expect(content.uri).toBe('plan://current');
|
|
305
|
+
expect(content.mimeType).toBe('text/markdown');
|
|
306
|
+
expect(content.text).toBeTruthy();
|
|
307
|
+
|
|
308
|
+
// Should contain markdown plan structure
|
|
309
|
+
expect(content.text).toContain('# Development Plan');
|
|
310
|
+
expect(content.text).toContain('## Goal');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('should read state resource successfully', async () => {
|
|
314
|
+
// First establish a conversation to ensure state exists
|
|
315
|
+
await client.callTool({
|
|
316
|
+
name: 'whats_next',
|
|
317
|
+
arguments: {
|
|
318
|
+
user_input: 'test state resource',
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const result = await client.readResource({
|
|
323
|
+
uri: 'state://current',
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
expect(result.contents).toBeDefined();
|
|
327
|
+
expect(Array.isArray(result.contents)).toBe(true);
|
|
328
|
+
expect(result.contents.length).toBeGreaterThan(0);
|
|
329
|
+
|
|
330
|
+
const content = result.contents[0];
|
|
331
|
+
expect(content.uri).toBe('state://current');
|
|
332
|
+
expect(content.mimeType).toBe('application/json');
|
|
333
|
+
expect(content.text).toBeTruthy();
|
|
334
|
+
|
|
335
|
+
// Should contain valid JSON with state information
|
|
336
|
+
const stateData = JSON.parse(content.text);
|
|
337
|
+
expect(stateData.conversationId).toBeTruthy();
|
|
338
|
+
expect(stateData.currentPhase).toBeTruthy();
|
|
339
|
+
expect(stateData.projectPath).toBeTruthy();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('should handle resource read errors gracefully', async () => {
|
|
343
|
+
// Test reading non-existent resource
|
|
344
|
+
try {
|
|
345
|
+
await client.readResource({
|
|
346
|
+
uri: 'nonexistent://resource',
|
|
347
|
+
});
|
|
348
|
+
// Should not reach here
|
|
349
|
+
expect(true).toBe(false);
|
|
350
|
+
} catch (error) {
|
|
351
|
+
// Should throw an appropriate error
|
|
352
|
+
expect(error).toBeDefined();
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
describe('Protocol Compliance', () => {
|
|
358
|
+
it('should handle concurrent requests properly', async () => {
|
|
359
|
+
// Make multiple concurrent requests to test protocol handling
|
|
360
|
+
const promises = [
|
|
361
|
+
client.callTool({
|
|
362
|
+
name: 'whats_next',
|
|
363
|
+
arguments: { user_input: 'concurrent test 1' },
|
|
364
|
+
}),
|
|
365
|
+
client.callTool({
|
|
366
|
+
name: 'whats_next',
|
|
367
|
+
arguments: { user_input: 'concurrent test 2' },
|
|
368
|
+
}),
|
|
369
|
+
client.listTools(),
|
|
370
|
+
client.listResources(),
|
|
371
|
+
];
|
|
372
|
+
|
|
373
|
+
const results = await Promise.all(promises);
|
|
374
|
+
|
|
375
|
+
// All requests should succeed
|
|
376
|
+
for (const result of results) {
|
|
377
|
+
expect(result).toBeDefined();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Tool results should have content
|
|
381
|
+
expect(results[0].content).toBeDefined();
|
|
382
|
+
expect(results[1].content).toBeDefined();
|
|
383
|
+
|
|
384
|
+
// List results should have arrays
|
|
385
|
+
expect(Array.isArray(results[2].tools)).toBe(true);
|
|
386
|
+
expect(Array.isArray(results[3].resources)).toBe(true);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('should maintain conversation state across multiple interactions', async () => {
|
|
390
|
+
// First interaction
|
|
391
|
+
const result1 = await client.callTool({
|
|
392
|
+
name: 'whats_next',
|
|
393
|
+
arguments: {
|
|
394
|
+
user_input: 'start new project',
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const response1 = JSON.parse(result1.content[0].text);
|
|
399
|
+
const conversationId1 = response1.conversation_id;
|
|
400
|
+
|
|
401
|
+
// Second interaction in the same session
|
|
402
|
+
const result2 = await client.callTool({
|
|
403
|
+
name: 'whats_next',
|
|
404
|
+
arguments: {
|
|
405
|
+
user_input: 'continue project',
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const response2 = JSON.parse(result2.content[0].text);
|
|
410
|
+
const conversationId2 = response2.conversation_id;
|
|
411
|
+
|
|
412
|
+
// Should maintain same conversation ID within the same MCP session
|
|
413
|
+
// Note: Each MCP client connection maintains its own conversation context
|
|
414
|
+
expect(conversationId1).toBe(conversationId2);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it('should handle malformed requests appropriately', async () => {
|
|
418
|
+
// Test with missing required parameters
|
|
419
|
+
try {
|
|
420
|
+
await client.callTool({
|
|
421
|
+
name: 'proceed_to_phase',
|
|
422
|
+
arguments: {
|
|
423
|
+
// Missing required target_phase
|
|
424
|
+
reason: 'test malformed request',
|
|
425
|
+
review_state: 'not-required',
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
// Should not reach here if validation works
|
|
429
|
+
expect(true).toBe(false);
|
|
430
|
+
} catch (error) {
|
|
431
|
+
// Should handle validation error appropriately
|
|
432
|
+
expect(error).toBeDefined();
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
describe('Integration Scenarios', () => {
|
|
438
|
+
it('should support complete development workflow', async () => {
|
|
439
|
+
// Start with requirements
|
|
440
|
+
const start = await client.callTool({
|
|
441
|
+
name: 'whats_next',
|
|
442
|
+
arguments: {
|
|
443
|
+
user_input: 'implement user authentication system',
|
|
444
|
+
context: 'new feature development',
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
const startResponse = JSON.parse(start.content[0].text);
|
|
449
|
+
|
|
450
|
+
// Check if we're using a custom state machine by looking at the phase
|
|
451
|
+
// Default state machine uses: idle, requirements, design, implementation, qa, testing, complete
|
|
452
|
+
// If we get other phases, skip the test as it's using a custom state machine
|
|
453
|
+
const defaultPhases = [
|
|
454
|
+
'idle',
|
|
455
|
+
'requirements',
|
|
456
|
+
'design',
|
|
457
|
+
'implementation',
|
|
458
|
+
'qa',
|
|
459
|
+
'testing',
|
|
460
|
+
'complete',
|
|
461
|
+
];
|
|
462
|
+
|
|
463
|
+
if (!defaultPhases.includes(startResponse.phase)) {
|
|
464
|
+
console.log(
|
|
465
|
+
`Skipping test: Custom state machine detected (phase: ${startResponse.phase})`
|
|
466
|
+
);
|
|
467
|
+
return; // Skip test
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// The server intelligently determines the appropriate starting phase
|
|
471
|
+
expect(['requirements', 'design']).toContain(startResponse.phase);
|
|
472
|
+
|
|
473
|
+
// Transition to design (if not already there)
|
|
474
|
+
let currentPhase = startResponse.phase;
|
|
475
|
+
if (currentPhase !== 'design') {
|
|
476
|
+
const design = await client.callTool({
|
|
477
|
+
name: 'proceed_to_phase',
|
|
478
|
+
arguments: {
|
|
479
|
+
target_phase: 'design',
|
|
480
|
+
reason: 'requirements analysis complete',
|
|
481
|
+
review_state: 'not-required',
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
const designResponse = JSON.parse(design.content[0].text);
|
|
486
|
+
expect(designResponse.phase).toBe('design');
|
|
487
|
+
currentPhase = 'design';
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Verify state resource reflects the current phase
|
|
491
|
+
const stateResult = await client.readResource({
|
|
492
|
+
uri: 'state://current',
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
const stateData = JSON.parse(stateResult.contents[0].text);
|
|
496
|
+
expect(stateData.currentPhase).toBe(currentPhase);
|
|
497
|
+
|
|
498
|
+
// Verify plan resource contains relevant phase information
|
|
499
|
+
const planResult = await client.readResource({
|
|
500
|
+
uri: 'plan://current',
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
const planContent = planResult.contents[0].text;
|
|
504
|
+
expect(planContent).toContain('# Development Plan');
|
|
505
|
+
expect(planContent).toContain('## Goal');
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('should handle complex conversation context', async () => {
|
|
509
|
+
const result = await client.callTool({
|
|
510
|
+
name: 'whats_next',
|
|
511
|
+
arguments: {
|
|
512
|
+
user_input: 'implement OAuth integration',
|
|
513
|
+
context: 'user wants third-party authentication',
|
|
514
|
+
conversation_summary:
|
|
515
|
+
'Discussed authentication options, user prefers OAuth with Google and GitHub',
|
|
516
|
+
recent_messages: [
|
|
517
|
+
{
|
|
518
|
+
role: 'user',
|
|
519
|
+
content: 'What authentication options do we have?',
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
role: 'assistant',
|
|
523
|
+
content: 'We can use OAuth, JWT, or traditional sessions',
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
role: 'user',
|
|
527
|
+
content: 'OAuth sounds good, especially Google and GitHub',
|
|
528
|
+
},
|
|
529
|
+
],
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
expect(result.content).toBeDefined();
|
|
534
|
+
const response = JSON.parse(result.content[0].text);
|
|
535
|
+
expect(response.phase).toBeTruthy();
|
|
536
|
+
expect(response.instructions).toBeTruthy();
|
|
537
|
+
expect(response.conversation_id).toBeTruthy();
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
});
|