Skip to content

Commit bc6cb6a

Browse files
committed
in a pretty good spot so far
1 parent 1104b44 commit bc6cb6a

18 files changed

Lines changed: 839 additions & 725 deletions

File tree

epicshop/mcp-dev/dev.js

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const inspectorProcess = execa('mcp-inspector', [], {
2828
...process.env,
2929
SERVER_PORT: serverPort,
3030
CLIENT_PORT: clientPort,
31-
MCP_PROXY_AUTH_TOKEN: sessionToken,
31+
MCP_PROXY_TOKEN: sessionToken,
3232
ALLOWED_ORIGINS: [
3333
`http://localhost:${clientPort}`,
3434
`http://127.0.0.1:${clientPort}`,
@@ -39,20 +39,6 @@ const inspectorProcess = execa('mcp-inspector', [], {
3939
stdio: ['inherit', 'pipe', 'inherit'], // capture stdout
4040
})
4141

42-
/*
43-
Starting MCP inspector...
44-
45-
⚙️ Proxy server listening on 127.0.0.1:10000
46-
47-
🔑 Session token: 5c96a97c78de97283c838754ea89a74283d5ce87692dbe4a4903c416ae64fc6b
48-
Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth
49-
50-
🔗 Open inspector with token pre-filled:
51-
http://localhost:9000/?MCP_PROXY_AUTH_TOKEN=5c96a97c78de97283c838754ea89a74283d5ce87692dbe4a4903c416ae64fc6b
52-
(Auto-open is disabled when authentication is enabled)
53-
54-
*/
55-
5642
// Wait for the inspector to be up before starting the proxy server
5743
function waitForInspectorReady() {
5844
return new Promise((resolve) => {

epicshop/package-lock.json

Lines changed: 19 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

epicshop/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"type": "module",
33
"dependencies": {
4-
"@epic-web/workshop-app": "^6.0.0",
5-
"@epic-web/workshop-cli": "^6.0.0",
6-
"@epic-web/workshop-utils": "^6.0.0",
4+
"@epic-web/workshop-app": "^6.2.0",
5+
"@epic-web/workshop-cli": "^6.2.0",
6+
"@epic-web/workshop-utils": "^6.2.0",
77
"chokidar": "^4.0.3",
88
"enquirer": "^2.4.1",
99
"execa": "^9.6.0",

exercises/01.sampling/01.problem.simple/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@
1616
},
1717
"dependencies": {
1818
"@epic-web/invariant": "^1.0.0",
19-
"@modelcontextprotocol/sdk": "npm:@kentcdodds/tmp-modelcontextprotocol_sdk@1.13.1-alpha.0",
19+
"@modelcontextprotocol/sdk": "^1.15.0",
2020
"zod": "^3.25.67"
2121
},
2222
"devDependencies": {
2323
"@epic-web/config": "^1.21.0",
2424
"@epic-web/mcp-dev": "*",
2525
"@faker-js/faker": "^9.8.0",
26-
"@kentcdodds/tmp-modelcontextprotocol_inspector": "^0.14.3-alpha.0",
26+
"@modelcontextprotocol/inspector": "^0.15.0",
2727
"@types/node": "^24.0.3",
2828
"tsx": "^4.20.3",
2929
"typescript": "^5.8.3",

exercises/01.sampling/01.solution.simple/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@
1616
},
1717
"dependencies": {
1818
"@epic-web/invariant": "^1.0.0",
19-
"@modelcontextprotocol/sdk": "npm:@kentcdodds/tmp-modelcontextprotocol_sdk@1.13.1-alpha.0",
19+
"@modelcontextprotocol/sdk": "^1.15.0",
2020
"zod": "^3.25.67"
2121
},
2222
"devDependencies": {
2323
"@epic-web/config": "^1.21.0",
2424
"@epic-web/mcp-dev": "*",
2525
"@faker-js/faker": "^9.8.0",
26-
"@kentcdodds/tmp-modelcontextprotocol_inspector": "^0.14.3-alpha.0",
26+
"@modelcontextprotocol/inspector": "^0.15.0",
2727
"@types/node": "^24.0.3",
2828
"tsx": "^4.20.3",
2929
"typescript": "^5.8.3",

exercises/01.sampling/02.problem.advanced/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@
1616
},
1717
"dependencies": {
1818
"@epic-web/invariant": "^1.0.0",
19-
"@modelcontextprotocol/sdk": "npm:@kentcdodds/tmp-modelcontextprotocol_sdk@1.13.1-alpha.0",
19+
"@modelcontextprotocol/sdk": "^1.15.0",
2020
"zod": "^3.25.67"
2121
},
2222
"devDependencies": {
2323
"@epic-web/config": "^1.21.0",
2424
"@epic-web/mcp-dev": "*",
2525
"@faker-js/faker": "^9.8.0",
26-
"@kentcdodds/tmp-modelcontextprotocol_inspector": "^0.14.3-alpha.0",
26+
"@modelcontextprotocol/inspector": "^0.15.0",
2727
"@types/node": "^24.0.3",
2828
"tsx": "^4.20.3",
2929
"typescript": "^5.8.3",

exercises/01.sampling/02.solution.advanced/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@
1616
},
1717
"dependencies": {
1818
"@epic-web/invariant": "^1.0.0",
19-
"@modelcontextprotocol/sdk": "npm:@kentcdodds/tmp-modelcontextprotocol_sdk@1.13.1-alpha.0",
19+
"@modelcontextprotocol/sdk": "^1.15.0",
2020
"zod": "^3.25.67"
2121
},
2222
"devDependencies": {
2323
"@epic-web/config": "^1.21.0",
2424
"@epic-web/mcp-dev": "*",
2525
"@faker-js/faker": "^9.8.0",
26-
"@kentcdodds/tmp-modelcontextprotocol_inspector": "^0.14.3-alpha.0",
26+
"@modelcontextprotocol/inspector": "^0.15.0",
2727
"@types/node": "^24.0.3",
2828
"tsx": "^4.20.3",
2929
"typescript": "^5.8.3",

exercises/99.finished/01.solution.finished/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@
1616
},
1717
"dependencies": {
1818
"@epic-web/invariant": "^1.0.0",
19-
"@modelcontextprotocol/sdk": "npm:@kentcdodds/tmp-modelcontextprotocol_sdk@1.13.1-alpha.0",
19+
"@modelcontextprotocol/sdk": "^1.15.0",
2020
"zod": "^3.25.67"
2121
},
2222
"devDependencies": {
2323
"@epic-web/config": "^1.21.0",
2424
"@epic-web/mcp-dev": "*",
2525
"@faker-js/faker": "^9.8.0",
26-
"@kentcdodds/tmp-modelcontextprotocol_inspector": "^0.14.3-alpha.0",
26+
"@modelcontextprotocol/inspector": "^0.15.0",
2727
"@types/node": "^24.0.3",
2828
"tsx": "^4.20.3",
2929
"typescript": "^5.8.3",

exercises/99.finished/01.solution.finished/src/db/index.ts

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
type Tag,
88
type NewTag,
99
type EntryTag,
10+
type EntryWithTags,
1011
entrySchema,
1112
newEntrySchema,
1213
tagSchema,
@@ -19,6 +20,25 @@ export type { Entry, NewEntry, Tag, NewTag, EntryTag }
1920

2021
export class DB {
2122
#db: DatabaseSync
23+
#subscribers = new Set<
24+
(changes: { tags?: number[]; entries?: number[] }) => void
25+
>()
26+
27+
subscribe(
28+
subscriber: (changes: { tags?: number[]; entries?: number[] }) => void,
29+
) {
30+
this.#subscribers.add(subscriber)
31+
return () => {
32+
this.#subscribers.delete(subscriber)
33+
}
34+
}
35+
36+
#notifySubscribers(changes: { tags?: number[]; entries?: number[] }) {
37+
for (const subscriber of this.#subscribers) {
38+
subscriber(changes)
39+
}
40+
}
41+
2242
constructor(db: DatabaseSync) {
2343
this.#db = db
2444
}
@@ -60,6 +80,7 @@ export class DB {
6080
if (!createdEntry) {
6181
throw new Error('Failed to query created entry')
6282
}
83+
this.#notifySubscribers({ entries: [id] })
6384
return createdEntry
6485
}
6586

@@ -87,7 +108,7 @@ export class DB {
87108
const tags = z
88109
.array(z.object({ id: z.number(), name: z.string() }))
89110
.parse(tagsResult)
90-
return { ...entry, tags }
111+
return { ...entry, tags } as EntryWithTags
91112
}
92113

93114
// TODO: listEntries to actually filter by tagIds
@@ -109,25 +130,25 @@ export class DB {
109130
}
110131
const updates = Object.entries(entry)
111132
.filter(([key, value]) => value !== undefined)
112-
.map(([key], index) => `${key} = ?${index + 2}`)
133+
.map(([key]) => `${key} = ?`)
113134
.join(', ')
114135
if (!updates) {
115136
return existingEntry
116137
}
117-
const stmt = this.#db.prepare(sql`
118-
UPDATE entries
119-
SET ${updates}, updated_at = CURRENT_TIMESTAMP
120-
WHERE id = ?1
121-
`)
122138
const updateValues = [
123-
id,
124139
...Object.entries(entry)
125140
.filter(([, value]) => value !== undefined)
126141
.map(([, value]) => value),
142+
id,
127143
]
128144
if (updateValues.some((v) => v === undefined)) {
129145
throw new Error('Undefined value in updateEntry parameters')
130146
}
147+
const stmt = this.#db.prepare(sql`
148+
UPDATE entries
149+
SET ${updates}, updated_at = CURRENT_TIMESTAMP
150+
WHERE id = ?
151+
`)
131152
const result = stmt.run(...updateValues)
132153
if (!result.changes) {
133154
throw new Error('Failed to update entry')
@@ -136,6 +157,7 @@ export class DB {
136157
if (!updatedEntry) {
137158
throw new Error('Failed to query updated entry')
138159
}
160+
this.#notifySubscribers({ entries: [id] })
139161
return updatedEntry
140162
}
141163

@@ -149,6 +171,7 @@ export class DB {
149171
if (!result.changes) {
150172
throw new Error('Failed to delete entry')
151173
}
174+
this.#notifySubscribers({ entries: [id] })
152175
return true
153176
}
154177

@@ -171,6 +194,7 @@ export class DB {
171194
if (!createdTag) {
172195
throw new Error('Failed to query created tag')
173196
}
197+
this.#notifySubscribers({ tags: [id] })
174198
return createdTag
175199
}
176200

@@ -202,25 +226,25 @@ export class DB {
202226
}
203227
const updates = Object.entries(tag)
204228
.filter(([, value]) => value !== undefined)
205-
.map(([key], index) => `${key} = ?${index + 2}`)
229+
.map(([key]) => `${key} = ?`)
206230
.join(', ')
207231
if (!updates) {
208232
return existingTag
209233
}
210-
const stmt = this.#db.prepare(sql`
211-
UPDATE tags
212-
SET ${updates}, updated_at = CURRENT_TIMESTAMP
213-
WHERE id = ?1
214-
`)
215234
const updateValues = [
216-
id,
217235
...Object.entries(tag)
218236
.filter(([, value]) => value !== undefined)
219237
.map(([, value]) => value),
238+
id,
220239
]
221240
if (updateValues.some((v) => v === undefined)) {
222241
throw new Error('Undefined value in updateTag parameters')
223242
}
243+
const stmt = this.#db.prepare(sql`
244+
UPDATE tags
245+
SET ${updates}, updated_at = CURRENT_TIMESTAMP
246+
WHERE id = ?
247+
`)
224248
const result = stmt.run(...updateValues)
225249
if (!result.changes) {
226250
throw new Error('Failed to update tag')
@@ -229,6 +253,7 @@ export class DB {
229253
if (!updatedTag) {
230254
throw new Error('Failed to query updated tag')
231255
}
256+
this.#notifySubscribers({ tags: [id] })
232257
return updatedTag
233258
}
234259

@@ -242,6 +267,7 @@ export class DB {
242267
if (!result.changes) {
243268
throw new Error('Failed to delete tag')
244269
}
270+
this.#notifySubscribers({ tags: [id] })
245271
return true
246272
}
247273

@@ -271,6 +297,7 @@ export class DB {
271297
if (!created) {
272298
throw new Error('Failed to query created entry tag')
273299
}
300+
this.#notifySubscribers({ entries: [entryId], tags: [tagId] })
274301
return created
275302
}
276303

exercises/99.finished/01.solution.finished/src/db/schema.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export const entrySchema = z.object({
2525
updatedAt: timestampSchema,
2626
})
2727

28+
export const entryWithTagsSchema = entrySchema.extend({
29+
tags: z.array(z.object({ id: z.number(), name: z.string() })),
30+
})
31+
2832
export const newEntrySchema = z.object({
2933
title: z.string(),
3034
content: z.string(),
@@ -159,3 +163,4 @@ export type NewEntry = z.infer<typeof newEntrySchema>
159163
export type Tag = z.infer<typeof tagSchema>
160164
export type NewTag = z.infer<typeof newTagSchema>
161165
export type EntryTag = z.infer<typeof entryTagSchema>
166+
export type EntryWithTags = z.infer<typeof entryWithTagsSchema>

0 commit comments

Comments
 (0)