Skip to content

Commit b802249

Browse files
committed
progress
1 parent e9f1fbe commit b802249

File tree

34 files changed

+2256
-400
lines changed

34 files changed

+2256
-400
lines changed

epicshop/.diffignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
worker-configuration.d.ts
2-
**/test/**
2+
# **/test/**
33
vitest.config.ts

exercises/01.discovery/01.problem.cors/test/globalSetup.ts

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ export default async function setup(project: TestProject) {
1313

1414
project.provide('mcpServerPort', mcpServerPort)
1515

16+
let appServerProcess: ReturnType<typeof execa> | null = null
1617
let mcpServerProcess: ReturnType<typeof execa> | null = null
1718

1819
// Buffers to store output for potential error display
20+
const appServerOutput: Array<string> = []
1921
const mcpServerOutput: Array<string> = []
2022

2123
/**
@@ -72,6 +74,12 @@ export default async function setup(project: TestProject) {
7274
* Display buffered output when there's a failure
7375
*/
7476
function displayBufferedOutput() {
77+
if (appServerOutput.length > 0) {
78+
console.log('=== App Server Output ===')
79+
for (const line of appServerOutput) {
80+
process.stdout.write(line)
81+
}
82+
}
7583
if (mcpServerOutput.length > 0) {
7684
console.log('=== MCP Server Output ===')
7785
for (const line of mcpServerOutput) {
@@ -80,9 +88,42 @@ export default async function setup(project: TestProject) {
8088
}
8189
}
8290

91+
async function startAppServerIfNecessary() {
92+
const isAppRunning = await fetch('http://localhost:7788/healthcheck').catch(
93+
() => ({ ok: false }),
94+
)
95+
if (isAppRunning.ok) {
96+
return
97+
}
98+
99+
const rootDir = process.cwd().replace(/exercises\/.*$/, '')
100+
101+
// Start the app server from the root directory
102+
console.log(`Starting app server on port 7788...`)
103+
appServerProcess = execa(
104+
'npm',
105+
[
106+
'run',
107+
'dev',
108+
'--prefix',
109+
'./epicshop/epic-me',
110+
'--',
111+
'--clearScreen=false',
112+
'--strictPort',
113+
],
114+
{
115+
cwd: rootDir,
116+
stdio: ['ignore', 'pipe', 'pipe'],
117+
},
118+
)
119+
}
120+
83121
async function startServers() {
84122
console.log('Starting servers...')
85123

124+
// Start app server if necessary
125+
await startAppServerIfNecessary()
126+
86127
// Start the MCP server from the exercise directory
87128
console.log(`Starting MCP server on port ${mcpServerPort}...`)
88129
mcpServerProcess = execa(
@@ -99,13 +140,23 @@ export default async function setup(project: TestProject) {
99140
)
100141

101142
try {
102-
// Wait for MCP server to be ready
103-
await waitForServerReady({
104-
process: mcpServerProcess,
105-
textMatch: mcpServerPort.toString(),
106-
name: '[MCP-SERVER]',
107-
outputBuffer: mcpServerOutput,
108-
})
143+
// Wait for both servers to be ready simultaneously
144+
await Promise.all([
145+
appServerProcess
146+
? waitForServerReady({
147+
process: appServerProcess,
148+
textMatch: '7788',
149+
name: '[APP-SERVER]',
150+
outputBuffer: appServerOutput,
151+
})
152+
: Promise.resolve(),
153+
waitForServerReady({
154+
process: mcpServerProcess,
155+
textMatch: mcpServerPort.toString(),
156+
name: '[MCP-SERVER]',
157+
outputBuffer: mcpServerOutput,
158+
}),
159+
])
109160

110161
console.log('Servers started successfully')
111162
} catch (error) {
@@ -142,6 +193,28 @@ export default async function setup(project: TestProject) {
142193
)
143194
}
144195

196+
if (appServerProcess && !appServerProcess.killed) {
197+
cleanupPromises.push(
198+
(async () => {
199+
appServerProcess.kill('SIGTERM')
200+
// Give it 2 seconds to gracefully shutdown, then force kill
201+
const timeout = setTimeout(() => {
202+
if (appServerProcess && !appServerProcess.killed) {
203+
appServerProcess.kill('SIGKILL')
204+
}
205+
}, 2000)
206+
207+
try {
208+
await appServerProcess
209+
} catch {
210+
// Process was killed, which is expected
211+
} finally {
212+
clearTimeout(timeout)
213+
}
214+
})(),
215+
)
216+
}
217+
145218
// Wait for all cleanup to complete, but with an overall timeout
146219
try {
147220
await Promise.race([
@@ -159,6 +232,9 @@ export default async function setup(project: TestProject) {
159232
if (mcpServerProcess && !mcpServerProcess.killed) {
160233
mcpServerProcess.kill('SIGKILL')
161234
}
235+
if (appServerProcess && !appServerProcess.killed) {
236+
appServerProcess.kill('SIGKILL')
237+
}
162238
}
163239

164240
console.log('Servers cleaned up')
Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,30 @@
1-
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
2-
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
31
import { test, expect, inject } from 'vitest'
42

53
const mcpServerPort = inject('mcpServerPort')
4+
const mcpServerUrl = `http://localhost:${mcpServerPort}`
65

7-
async function setupClient() {
8-
const client = new Client(
9-
{
10-
name: 'EpicMeTester',
11-
version: '1.0.0',
12-
},
13-
{ capabilities: {} },
6+
test(`The CORS headers are set correctly for /.well-known/oauth-authorization-server`, async () => {
7+
const resourceMetadataResponse = await fetch(
8+
`${mcpServerUrl}/.well-known/oauth-authorization-server`,
149
)
15-
16-
const transport = new StreamableHTTPClientTransport(
17-
new URL(`http://localhost:${mcpServerPort}/mcp`),
18-
)
19-
20-
await client.connect(transport)
21-
22-
return {
23-
client,
24-
async [Symbol.asyncDispose]() {
25-
await client.transport?.close()
26-
},
27-
}
28-
}
29-
30-
test('listing tools works', async () => {
31-
await using setup = await setupClient()
32-
const { client } = setup
33-
34-
const result = await client.listTools()
35-
expect(result.tools.length).toBeGreaterThan(0)
10+
expect(
11+
resourceMetadataResponse.ok,
12+
'🚨 fetching authorization server metadata should succeed',
13+
).toBe(false)
14+
expect(
15+
resourceMetadataResponse.status,
16+
`🚨 fetching authorization server metadata should fail (we haven't implemented the endpoint yet)`,
17+
).toBe(404)
18+
expect(
19+
resourceMetadataResponse.headers.get('Access-Control-Allow-Origin'),
20+
'🚨 Access-Control-Allow-Origin header should be set',
21+
).toBe('*')
22+
expect(
23+
resourceMetadataResponse.headers.get('Access-Control-Allow-Methods'),
24+
'🚨 Access-Control-Allow-Methods header should be set',
25+
).toBe('GET, HEAD, OPTIONS')
26+
expect(
27+
resourceMetadataResponse.headers.get('Access-Control-Allow-Headers'),
28+
'🚨 Access-Control-Allow-Headers header should be set',
29+
).toBe('mcp-protocol-version')
3630
})

exercises/01.discovery/01.solution.cors/test/globalSetup.ts

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ export default async function setup(project: TestProject) {
1313

1414
project.provide('mcpServerPort', mcpServerPort)
1515

16+
let appServerProcess: ReturnType<typeof execa> | null = null
1617
let mcpServerProcess: ReturnType<typeof execa> | null = null
1718

1819
// Buffers to store output for potential error display
20+
const appServerOutput: Array<string> = []
1921
const mcpServerOutput: Array<string> = []
2022

2123
/**
@@ -72,6 +74,12 @@ export default async function setup(project: TestProject) {
7274
* Display buffered output when there's a failure
7375
*/
7476
function displayBufferedOutput() {
77+
if (appServerOutput.length > 0) {
78+
console.log('=== App Server Output ===')
79+
for (const line of appServerOutput) {
80+
process.stdout.write(line)
81+
}
82+
}
7583
if (mcpServerOutput.length > 0) {
7684
console.log('=== MCP Server Output ===')
7785
for (const line of mcpServerOutput) {
@@ -80,9 +88,42 @@ export default async function setup(project: TestProject) {
8088
}
8189
}
8290

91+
async function startAppServerIfNecessary() {
92+
const isAppRunning = await fetch('http://localhost:7788/healthcheck').catch(
93+
() => ({ ok: false }),
94+
)
95+
if (isAppRunning.ok) {
96+
return
97+
}
98+
99+
const rootDir = process.cwd().replace(/exercises\/.*$/, '')
100+
101+
// Start the app server from the root directory
102+
console.log(`Starting app server on port 7788...`)
103+
appServerProcess = execa(
104+
'npm',
105+
[
106+
'run',
107+
'dev',
108+
'--prefix',
109+
'./epicshop/epic-me',
110+
'--',
111+
'--clearScreen=false',
112+
'--strictPort',
113+
],
114+
{
115+
cwd: rootDir,
116+
stdio: ['ignore', 'pipe', 'pipe'],
117+
},
118+
)
119+
}
120+
83121
async function startServers() {
84122
console.log('Starting servers...')
85123

124+
// Start app server if necessary
125+
await startAppServerIfNecessary()
126+
86127
// Start the MCP server from the exercise directory
87128
console.log(`Starting MCP server on port ${mcpServerPort}...`)
88129
mcpServerProcess = execa(
@@ -99,13 +140,23 @@ export default async function setup(project: TestProject) {
99140
)
100141

101142
try {
102-
// Wait for MCP server to be ready
103-
await waitForServerReady({
104-
process: mcpServerProcess,
105-
textMatch: mcpServerPort.toString(),
106-
name: '[MCP-SERVER]',
107-
outputBuffer: mcpServerOutput,
108-
})
143+
// Wait for both servers to be ready simultaneously
144+
await Promise.all([
145+
appServerProcess
146+
? waitForServerReady({
147+
process: appServerProcess,
148+
textMatch: '7788',
149+
name: '[APP-SERVER]',
150+
outputBuffer: appServerOutput,
151+
})
152+
: Promise.resolve(),
153+
waitForServerReady({
154+
process: mcpServerProcess,
155+
textMatch: mcpServerPort.toString(),
156+
name: '[MCP-SERVER]',
157+
outputBuffer: mcpServerOutput,
158+
}),
159+
])
109160

110161
console.log('Servers started successfully')
111162
} catch (error) {
@@ -142,6 +193,28 @@ export default async function setup(project: TestProject) {
142193
)
143194
}
144195

196+
if (appServerProcess && !appServerProcess.killed) {
197+
cleanupPromises.push(
198+
(async () => {
199+
appServerProcess.kill('SIGTERM')
200+
// Give it 2 seconds to gracefully shutdown, then force kill
201+
const timeout = setTimeout(() => {
202+
if (appServerProcess && !appServerProcess.killed) {
203+
appServerProcess.kill('SIGKILL')
204+
}
205+
}, 2000)
206+
207+
try {
208+
await appServerProcess
209+
} catch {
210+
// Process was killed, which is expected
211+
} finally {
212+
clearTimeout(timeout)
213+
}
214+
})(),
215+
)
216+
}
217+
145218
// Wait for all cleanup to complete, but with an overall timeout
146219
try {
147220
await Promise.race([
@@ -159,6 +232,9 @@ export default async function setup(project: TestProject) {
159232
if (mcpServerProcess && !mcpServerProcess.killed) {
160233
mcpServerProcess.kill('SIGKILL')
161234
}
235+
if (appServerProcess && !appServerProcess.killed) {
236+
appServerProcess.kill('SIGKILL')
237+
}
162238
}
163239

164240
console.log('Servers cleaned up')

0 commit comments

Comments
 (0)