Skip to content

Commit ca78d24

Browse files
committed
good progress
1 parent 91bb2b7 commit ca78d24

28 files changed

+18017
-37
lines changed

epicshop/package-lock.json

+496-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

epicshop/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"@epic-web/workshop-utils": "^5.13.6",
66
"chokidar": "^4.0.3",
77
"execa": "^9.5.2",
8-
"fs-extra": "^11.3.0"
8+
"fs-extra": "^11.3.0",
9+
"tsx": "^4.19.3"
910
}
1011
}

exercises/01.start/01.problem/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"type": "module",
55
"scripts": {
66
"dev": "tsx src/index.ts",
7+
"test": "tsx src/index.test.ts",
8+
"test:watch": "tsx --watch-path=src src/index.test.ts",
79
"typecheck": "tsc",
810
"inspect": "mcp-inspector"
911
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { test, beforeEach, afterEach } from 'node:test'
2+
import { invariant } from '@epic-web/invariant'
3+
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
4+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
5+
import { z } from 'zod'
6+
7+
let client: Client
8+
9+
beforeEach(async () => {
10+
client = new Client({
11+
name: 'EpicMathTester',
12+
version: '1.0.0',
13+
})
14+
const transport = new StdioClientTransport({
15+
command: 'tsx',
16+
args: ['src/index.ts'],
17+
})
18+
await client.connect(transport)
19+
})
20+
21+
afterEach(async () => {
22+
await client.transport?.close()
23+
})
24+
25+
await test('Tool Definition', async (t) => {
26+
const list = await client.listTools()
27+
const [firstTool] = list.tools
28+
invariant(firstTool, '🚨 No tools found')
29+
30+
const expectedToolFormatSchema = z.object({
31+
name: z.string().regex(/^add$/i),
32+
description: z.string().regex(/^add two numbers$/i),
33+
inputSchema: z.object({
34+
type: z.literal('object'),
35+
properties: z.object({
36+
firstNumber: z.object({
37+
type: z.literal('number'),
38+
description: z.string().regex(/first/i),
39+
}),
40+
secondNumber: z.object({
41+
type: z.literal('number'),
42+
description: z.string().regex(/second/i),
43+
}),
44+
}),
45+
}),
46+
})
47+
assertSchema(expectedToolFormatSchema, firstTool)
48+
})
49+
50+
await test('Tool Call', async (t) => {
51+
const result = await client.callTool({
52+
name: 'add',
53+
arguments: {
54+
firstNumber: 1,
55+
secondNumber: 2,
56+
},
57+
})
58+
59+
assertSchema(
60+
z.object({
61+
content: z.array(
62+
z.object({ type: z.literal('text'), text: z.string().regex(/3/) }),
63+
),
64+
}),
65+
result,
66+
)
67+
})
68+
69+
// TODO: maybe there's a way within Zod to handle the error message so we can
70+
// just use parse and let it throw its own error.
71+
function assertSchema(
72+
schema: z.ZodSchema,
73+
value: unknown,
74+
): asserts value is z.infer<typeof schema> {
75+
const result = schema.safeParse(value)
76+
if (!result.success) {
77+
console.error('🚨 The following value is invalid:')
78+
console.dir(value, { depth: 8, colors: true })
79+
throw result.error
80+
}
81+
}

exercises/01.start/01.problem/src/index.ts

+21-35
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,29 @@
1-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
3-
import { DB } from './db'
4-
import { initializeTools } from './tools.ts'
1+
// 💰 you're gonna want these imports
2+
// import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
3+
// import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
4+
// import { z } from 'zod'
55

6-
export class EpicMeMCP {
7-
db: DB
8-
server = new McpServer(
9-
{
10-
name: 'EpicMe',
11-
version: '1.0.0',
12-
},
13-
{
14-
capabilities: {
15-
tools: {},
16-
},
17-
instructions: `
18-
EpicMe is a journaling app that allows users to write about and review their experiences, thoughts, and reflections.
6+
// 🐨 create a new McpServer
7+
// - it should have a name of 'EpicMath' and a version of '1.0.0'
8+
// - it should have a capabilities object with a tools property that is an empty object
9+
// - it should have instructions for the LLM to know what this server can be used to do
1910

20-
These tools are the user's window into their journal. With these tools and your help, they can create, read, and manage their journal entries and associated tags.
11+
// 🐨 add a tool to the server with the server.tool API
12+
// - it should be named 'add'
13+
// - it should have a description explaining what it can be used to do
14+
// - provide an input schema object with two properties which are validated with zod (give them descriptions as well):
15+
// - firstNumber: a number
16+
// - secondNumber: a number
17+
// - it should return a standard text response with the sum of the two numbers
2118

22-
You can also help users add tags to their entries and get all tags for an entry.
23-
`.trim(),
24-
},
25-
)
19+
async function main() {
20+
// 🐨 create a new StdioServerTransport
21+
// 🐨 connect the server to the transport
2622

27-
constructor(path: string) {
28-
this.db = DB.getInstance(path)
29-
}
30-
async init() {
31-
await initializeTools(this)
32-
}
33-
}
23+
// 🐨 add a log (using console.error) to the console to let the user know the server is running
3424

35-
async function main() {
36-
const agent = new EpicMeMCP('./db.sqlite')
37-
await agent.init()
38-
const transport = new StdioServerTransport()
39-
await agent.server.connect(transport)
40-
console.error('EpicMe MCP Server running on stdio')
25+
// 💣 you can delete this once you're done
26+
throw new Error('Not implemented')
4127
}
4228

4329
main().catch((error) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Start

0 commit comments

Comments
 (0)