@epic-web/workshop-mcp 5.21.0 → 5.22.0

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.
@@ -9,8 +9,30 @@ export declare function getWorkshopContext({ workshopDirectory, }: InputSchemaTy
9
9
  meta: {
10
10
  'README.md': string | null;
11
11
  config: any;
12
- instructions: string | null;
13
- finishedInstructions: string | null;
12
+ instructions: {
13
+ content: string | null;
14
+ progress: ({
15
+ epicLessonUrl: string;
16
+ epicLessonSlug: string;
17
+ epicCompletedAt: string | null;
18
+ } & {
19
+ readonly type: "instructions";
20
+ readonly exerciseNumber: number;
21
+ readonly stepNumber?: undefined;
22
+ }) | undefined;
23
+ };
24
+ finishedInstructions: {
25
+ content: string | null;
26
+ progress: ({
27
+ epicLessonUrl: string;
28
+ epicLessonSlug: string;
29
+ epicCompletedAt: string | null;
30
+ } & {
31
+ readonly type: "finished";
32
+ readonly exerciseNumber: number;
33
+ readonly stepNumber?: undefined;
34
+ }) | undefined;
35
+ };
14
36
  };
15
37
  exercises: Array<any>;
16
38
  }>;
@@ -68,6 +90,57 @@ export declare const exerciseStepProgressDiffResource: {
68
90
  workshopDirectory: z.ZodString;
69
91
  };
70
92
  };
93
+ declare const getUserInfoInputSchema: {
94
+ workshopDirectory: z.ZodString;
95
+ };
96
+ declare function getUserInfoResource({ workshopDirectory, }: InputSchemaType<typeof getUserInfoInputSchema>): Promise<{
97
+ uri: string;
98
+ mimeType: string;
99
+ text: string;
100
+ }>;
101
+ export declare const userInfoResource: {
102
+ name: string;
103
+ description: string;
104
+ uriTemplate: ResourceTemplate;
105
+ getResource: typeof getUserInfoResource;
106
+ inputSchema: {
107
+ workshopDirectory: z.ZodString;
108
+ };
109
+ };
110
+ declare const getUserAccessInputSchema: {
111
+ workshopDirectory: z.ZodString;
112
+ };
113
+ declare function getUserAccessResource({ workshopDirectory, }: InputSchemaType<typeof getUserAccessInputSchema>): Promise<{
114
+ uri: string;
115
+ mimeType: string;
116
+ text: string;
117
+ }>;
118
+ export declare const userAccessResource: {
119
+ name: string;
120
+ description: string;
121
+ uriTemplate: ResourceTemplate;
122
+ getResource: typeof getUserAccessResource;
123
+ inputSchema: {
124
+ workshopDirectory: z.ZodString;
125
+ };
126
+ };
127
+ declare const userProgressInputSchema: {
128
+ workshopDirectory: z.ZodString;
129
+ };
130
+ declare function getUserProgressResource({ workshopDirectory, }: InputSchemaType<typeof userProgressInputSchema>): Promise<{
131
+ uri: string;
132
+ mimeType: string;
133
+ text: string;
134
+ }>;
135
+ export declare const userProgressResource: {
136
+ name: string;
137
+ description: string;
138
+ uriTemplate: ResourceTemplate;
139
+ getResource: typeof getUserProgressResource;
140
+ inputSchema: {
141
+ workshopDirectory: z.ZodString;
142
+ };
143
+ };
71
144
  export declare function initResources(server: McpServer): void;
72
145
  export {};
73
146
  //# sourceMappingURL=resources.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../src/resources.ts"],"names":[],"mappings":"AAkBA,OAAO,EACN,KAAK,SAAS,EACd,gBAAgB,EAChB,MAAM,yCAAyC,CAAA;AAChD,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AAC5E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAEN,KAAK,eAAe,EAEpB,MAAM,YAAY,CAAA;AAEnB,eAAO,MAAM,6BAA6B;;CAMzC,CAAA;AAED,wBAAsB,kBAAkB,CAAC,EACxC,iBAAiB,GACjB,EAAE,eAAe,CAAC,OAAO,6BAA6B,CAAC;;;;;;;eAcrC,KAAK,CAAC,GAAG,CAAC;GAwC5B;AAOD,wBAAsB,0BAA0B,CAAC,EAChD,iBAAiB,GACjB,EAAE,eAAe,CAAC,OAAO,6BAA6B,CAAC,GAAG,OAAO,CACjE,kBAAkB,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CACtC,CAQA;AAED,eAAO,MAAM,uBAAuB;;;;;;;;CAMnC,CAAA;AAiLD,iBAAe,0BAA0B,CAAC,EACzC,iBAAiB,EACjB,cAAc,GACd,EAAE;IACF,iBAAiB,EAAE,MAAM,CAAA;IACzB,cAAc,CAAC,EAAE,MAAM,CAAA;CACvB,GAAG,OAAO,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAalD;AAED,eAAO,MAAM,uBAAuB;;;;;;;;;CAMnC,CAAA;AAED,QAAA,MAAM,0BAA0B;;;;CAI/B,CAAA;AA4CD,iBAAe,0BAA0B,CAAC,EACzC,iBAAiB,EACjB,IAAI,EACJ,IAAI,GACJ,EAAE,eAAe,CAAC,OAAO,0BAA0B,CAAC,GAAG,OAAO,CAC9D,kBAAkB,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CACtC,CAYA;AAOD,eAAO,MAAM,uBAAuB;;;;;;;;;;CAMnC,CAAA;AAED,QAAA,MAAM,sCAAsC;;CAE3C,CAAA;AA+BD,iBAAe,mCAAmC,CAAC,EAClD,iBAAiB,GACjB,EAAE,eAAe,CAAC,OAAO,sCAAsC,CAAC,GAAG,OAAO,CAC1E,kBAAkB,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CACtC,CAUA;AAOD,eAAO,MAAM,gCAAgC;;;;;;;;CAM5C,CAAA;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,QA2F9C"}
1
+ {"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../src/resources.ts"],"names":[],"mappings":"AAoBA,OAAO,EACN,KAAK,SAAS,EACd,gBAAgB,EAChB,MAAM,yCAAyC,CAAA;AAChD,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AAC5E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAEN,KAAK,eAAe,EAEpB,MAAM,YAAY,CAAA;AAEnB,eAAO,MAAM,6BAA6B;;CAMzC,CAAA;AAED,wBAAsB,kBAAkB,CAAC,EACxC,iBAAiB,GACjB,EAAE,eAAe,CAAC,OAAO,6BAA6B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAqBrC,KAAK,CAAC,GAAG,CAAC;GA0D5B;AAOD,wBAAsB,0BAA0B,CAAC,EAChD,iBAAiB,GACjB,EAAE,eAAe,CAAC,OAAO,6BAA6B,CAAC,GAAG,OAAO,CACjE,kBAAkB,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CACtC,CAQA;AAED,eAAO,MAAM,uBAAuB;;;;;;;;CAMnC,CAAA;AA+LD,iBAAe,0BAA0B,CAAC,EACzC,iBAAiB,EACjB,cAAc,GACd,EAAE;IACF,iBAAiB,EAAE,MAAM,CAAA;IACzB,cAAc,CAAC,EAAE,MAAM,CAAA;CACvB,GAAG,OAAO,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAalD;AAED,eAAO,MAAM,uBAAuB;;;;;;;;;CAMnC,CAAA;AAED,QAAA,MAAM,0BAA0B;;;;CAI/B,CAAA;AA4CD,iBAAe,0BAA0B,CAAC,EACzC,iBAAiB,EACjB,IAAI,EACJ,IAAI,GACJ,EAAE,eAAe,CAAC,OAAO,0BAA0B,CAAC,GAAG,OAAO,CAC9D,kBAAkB,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CACtC,CAYA;AAOD,eAAO,MAAM,uBAAuB;;;;;;;;;;CAMnC,CAAA;AAED,QAAA,MAAM,sCAAsC;;CAE3C,CAAA;AAoCD,iBAAe,mCAAmC,CAAC,EAClD,iBAAiB,GACjB,EAAE,eAAe,CAAC,OAAO,sCAAsC,CAAC,GAAG,OAAO,CAC1E,kBAAkB,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CACtC,CAUA;AAED,eAAO,MAAM,gCAAgC;;;;;;;;CAM5C,CAAA;AAED,QAAA,MAAM,sBAAsB;;CAE3B,CAAA;AAOD,iBAAe,mBAAmB,CAAC,EAClC,iBAAiB,GACjB,EAAE,eAAe,CAAC,OAAO,sBAAsB,CAAC;;;;GAShD;AAED,eAAO,MAAM,gBAAgB;;;;;;;;CAM5B,CAAA;AAED,QAAA,MAAM,wBAAwB;;CAE7B,CAAA;AAOD,iBAAe,qBAAqB,CAAC,EACpC,iBAAiB,GACjB,EAAE,eAAe,CAAC,OAAO,wBAAwB,CAAC;;;;GASlD;AAED,eAAO,MAAM,kBAAkB;;;;;;;;CAM9B,CAAA;AAED,QAAA,MAAM,uBAAuB;;CAE5B,CAAA;AAOD,iBAAe,uBAAuB,CAAC,EACtC,iBAAiB,GACjB,EAAE,eAAe,CAAC,OAAO,uBAAuB,CAAC;;;;GASjD;AAED,eAAO,MAAM,oBAAoB;;;;;;;;CAMhC,CAAA;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,QA0I9C"}
@@ -2,7 +2,7 @@ import path from 'node:path';
2
2
  import { invariant } from '@epic-web/invariant';
3
3
  import { extractNumbersAndTypeFromAppNameOrPath, findSolutionDir, getApps, getExercise, getExercises, getFullPathFromAppName, getPlaygroundApp, isExerciseStepApp, isPlaygroundApp, } from '@epic-web/workshop-utils/apps.server';
4
4
  import { getAuthInfo } from '@epic-web/workshop-utils/db.server';
5
- import { getEpicVideoInfos, userHasAccessToWorkshop, } from '@epic-web/workshop-utils/epic-api.server';
5
+ import { getEpicVideoInfos, userHasAccessToWorkshop, getUserInfo, getProgress, } from '@epic-web/workshop-utils/epic-api.server';
6
6
  import { ResourceTemplate, } from '@modelcontextprotocol/sdk/server/mcp.js';
7
7
  import { z } from 'zod';
8
8
  import { handleWorkshopDirectory, safeReadFile, } from './utils.js';
@@ -15,12 +15,19 @@ export async function getWorkshopContext({ workshopDirectory, }) {
15
15
  const workshopRoot = await handleWorkshopDirectory(workshopDirectory);
16
16
  const inWorkshop = (...d) => path.join(workshopRoot, ...d);
17
17
  const readInWorkshop = (...d) => safeReadFile(inWorkshop(...d));
18
+ const progress = await getProgress();
18
19
  const output = {
19
20
  meta: {
20
21
  'README.md': await readInWorkshop('README.md'),
21
22
  config: JSON.parse((await readInWorkshop('package.json')) || '{}').epicshop,
22
- instructions: await readInWorkshop('exercise', 'README.mdx'),
23
- finishedInstructions: await readInWorkshop('exercise', 'FINISHED.mdx'),
23
+ instructions: {
24
+ content: await readInWorkshop('exercise', 'README.mdx'),
25
+ progress: progress.find((p) => p.type === 'instructions'),
26
+ },
27
+ finishedInstructions: {
28
+ content: await readInWorkshop('exercise', 'FINISHED.mdx'),
29
+ progress: progress.find((p) => p.type === 'finished'),
30
+ },
24
31
  },
25
32
  exercises: [],
26
33
  };
@@ -30,11 +37,22 @@ export async function getWorkshopContext({ workshopDirectory, }) {
30
37
  fullPath: exercise.fullPath,
31
38
  exerciseNumber: exercise.exerciseNumber,
32
39
  title: exercise.title,
33
- instructions: await safeReadFile(path.join(exercise.fullPath, 'README.mdx')),
34
- finishedInstructions: await safeReadFile(path.join(exercise.fullPath, 'FINISHED.mdx')),
40
+ instructions: {
41
+ content: await safeReadFile(path.join(exercise.fullPath, 'README.mdx')),
42
+ progress: progress.find((p) => p.type === 'instructions' &&
43
+ p.exerciseNumber === exercise.exerciseNumber),
44
+ },
45
+ finishedInstructions: {
46
+ content: await safeReadFile(path.join(exercise.fullPath, 'FINISHED.mdx')),
47
+ progress: progress.find((p) => p.type === 'finished' &&
48
+ p.exerciseNumber === exercise.exerciseNumber),
49
+ },
35
50
  steps: exercise.steps.map((step) => {
36
51
  return {
37
52
  stepNumber: step.stepNumber,
53
+ progress: progress.find((p) => p.type === 'step' &&
54
+ p.exerciseNumber === exercise.exerciseNumber &&
55
+ p.stepNumber === step.stepNumber),
38
56
  title: step.problem?.title ?? step.solution?.title ?? null,
39
57
  problem: step.problem
40
58
  ? {
@@ -87,6 +105,7 @@ async function getExerciseContext({ workshopDirectory, exerciseNumber, }) {
87
105
  await handleWorkshopDirectory(workshopDirectory);
88
106
  const userHasAccess = await userHasAccessToWorkshop();
89
107
  const authInfo = await getAuthInfo();
108
+ const progress = await getProgress();
90
109
  let stepNumber = 1;
91
110
  const playgroundApp = await getPlaygroundApp();
92
111
  invariant(playgroundApp, 'No playground app found');
@@ -177,21 +196,26 @@ async function getExerciseContext({ workshopDirectory, exerciseNumber, }) {
177
196
  }
178
197
  : 'currently set to a different exercise',
179
198
  },
180
- exerciseBackground: {
199
+ exerciseInfo: {
181
200
  number: exerciseNumber,
182
201
  intro: {
183
202
  content: await getFileContent(path.join(exercise.fullPath, 'README.mdx')),
184
203
  transcripts: getTranscripts(exercise.instructionsEpicVideoEmbeds),
204
+ progress: progress.find((p) => p.type === 'instructions' && p.exerciseNumber === exerciseNumber),
185
205
  },
186
206
  outro: {
187
207
  content: await getFileContent(path.join(exercise.fullPath, 'FINISHED.mdx')),
188
208
  transcripts: getTranscripts(exercise.finishedEpicVideoEmbeds),
209
+ progress: progress.find((p) => p.type === 'finished' && p.exerciseNumber === exerciseNumber),
189
210
  },
190
211
  },
191
212
  steps: exercise.steps
192
213
  ? await Promise.all(exercise.steps.map(async (app) => ({
193
214
  number: app.stepNumber,
194
215
  isCurrent: isCurrentExercise && app.stepNumber === stepNumber,
216
+ progress: progress.find((p) => p.type === 'step' &&
217
+ p.exerciseNumber === exerciseNumber &&
218
+ p.stepNumber === app.stepNumber),
195
219
  problem: {
196
220
  content: app.problem
197
221
  ? await getFileContent(path.join(app.problem.fullPath, 'README.mdx'))
@@ -227,7 +251,7 @@ async function getExerciseContextResource({ workshopDirectory, exerciseNumber, }
227
251
  return {
228
252
  uri: exerciseContextUriTemplate.uriTemplate.expand({
229
253
  workshopDirectory,
230
- exerciseNumber: String(context.exerciseBackground.number),
254
+ exerciseNumber: String(context.exerciseInfo.number),
231
255
  }),
232
256
  mimeType: 'application/json',
233
257
  text: JSON.stringify(context),
@@ -307,6 +331,7 @@ async function getExerciseStepProgressDiff({ workshopDirectory, }) {
307
331
  return 'No changes';
308
332
  return diffCode;
309
333
  }
334
+ const exerciseStepProgressDiffUriTemplate = new ResourceTemplate('epicshop://{workshopDirectory}/exercise-step-progress-diff', { list: undefined });
310
335
  async function getExerciseStepProgressDiffResource({ workshopDirectory, }) {
311
336
  return {
312
337
  uri: exerciseStepProgressDiffUriTemplate.uriTemplate.expand({
@@ -316,7 +341,6 @@ async function getExerciseStepProgressDiffResource({ workshopDirectory, }) {
316
341
  text: JSON.stringify(await getExerciseStepProgressDiff({ workshopDirectory })),
317
342
  };
318
343
  }
319
- const exerciseStepProgressDiffUriTemplate = new ResourceTemplate('epicshop://{workshopDirectory}/exercise-step-progress-diff', { list: undefined });
320
344
  export const exerciseStepProgressDiffResource = {
321
345
  name: 'exercise_step_progress_diff',
322
346
  description: 'The diff between the current exercise step and the solution',
@@ -324,6 +348,69 @@ export const exerciseStepProgressDiffResource = {
324
348
  getResource: getExerciseStepProgressDiffResource,
325
349
  inputSchema: getExerciseStepProgressDiffInputSchema,
326
350
  };
351
+ const getUserInfoInputSchema = {
352
+ workshopDirectory: z.string().describe('The workshop directory'),
353
+ };
354
+ const userInfoUri = new ResourceTemplate('epicshop://{workshopDirectory}/user/info', { list: undefined });
355
+ async function getUserInfoResource({ workshopDirectory, }) {
356
+ const userInfo = await getUserInfo();
357
+ return {
358
+ uri: userInfoUri.uriTemplate.expand({
359
+ workshopDirectory,
360
+ }),
361
+ mimeType: 'application/json',
362
+ text: JSON.stringify(userInfo),
363
+ };
364
+ }
365
+ export const userInfoResource = {
366
+ name: 'user_info',
367
+ description: 'Information about the current user',
368
+ uriTemplate: userInfoUri,
369
+ getResource: getUserInfoResource,
370
+ inputSchema: getUserInfoInputSchema,
371
+ };
372
+ const getUserAccessInputSchema = {
373
+ workshopDirectory: z.string().describe('The workshop directory'),
374
+ };
375
+ const userAccessUriTemplate = new ResourceTemplate('epicshop://{workshopDirectory}/user/access', { list: undefined });
376
+ async function getUserAccessResource({ workshopDirectory, }) {
377
+ const userHasAccess = await userHasAccessToWorkshop();
378
+ return {
379
+ uri: userAccessUriTemplate.uriTemplate.expand({
380
+ workshopDirectory,
381
+ }),
382
+ mimeType: 'application/json',
383
+ text: JSON.stringify({ userHasAccess }),
384
+ };
385
+ }
386
+ export const userAccessResource = {
387
+ name: 'user_access',
388
+ description: 'Whether the current user has access to the workshop',
389
+ uriTemplate: userAccessUriTemplate,
390
+ getResource: getUserAccessResource,
391
+ inputSchema: getUserAccessInputSchema,
392
+ };
393
+ const userProgressInputSchema = {
394
+ workshopDirectory: z.string().describe('The workshop directory'),
395
+ };
396
+ const userProgressUriTemplate = new ResourceTemplate('epicshop://{workshopDirectory}/user/progress', { list: undefined });
397
+ async function getUserProgressResource({ workshopDirectory, }) {
398
+ const userProgress = await getProgress();
399
+ return {
400
+ uri: userProgressUriTemplate.uriTemplate.expand({
401
+ workshopDirectory,
402
+ }),
403
+ mimeType: 'application/json',
404
+ text: JSON.stringify(userProgress),
405
+ };
406
+ }
407
+ export const userProgressResource = {
408
+ name: 'user_progress',
409
+ description: 'The progress of the current user',
410
+ uriTemplate: userProgressUriTemplate,
411
+ getResource: getUserProgressResource,
412
+ inputSchema: userProgressInputSchema,
413
+ };
327
414
  export function initResources(server) {
328
415
  server.resource(workshopContextResource.name, workshopContextResource.uriTemplate, { description: workshopContextResource.description }, async (_uri, { workshopDirectory }) => {
329
416
  invariant(typeof workshopDirectory === 'string', 'A single workshopDirectory is required');
@@ -371,5 +458,25 @@ export function initResources(server) {
371
458
  ],
372
459
  };
373
460
  });
461
+ server.resource(userInfoResource.name, userInfoResource.uriTemplate, { description: userInfoResource.description }, async (_uri, { workshopDirectory }) => {
462
+ invariant(typeof workshopDirectory === 'string', 'A single workshopDirectory is required');
463
+ return {
464
+ contents: [await userInfoResource.getResource({ workshopDirectory })],
465
+ };
466
+ });
467
+ server.resource(userAccessResource.name, userAccessResource.uriTemplate, { description: userAccessResource.description }, async (_uri, { workshopDirectory }) => {
468
+ invariant(typeof workshopDirectory === 'string', 'A single workshopDirectory is required');
469
+ return {
470
+ contents: [await userAccessResource.getResource({ workshopDirectory })],
471
+ };
472
+ });
473
+ server.resource(userProgressResource.name, userProgressResource.uriTemplate, { description: userProgressResource.description }, async (_uri, { workshopDirectory }) => {
474
+ invariant(typeof workshopDirectory === 'string', 'A single workshopDirectory is required');
475
+ return {
476
+ contents: [
477
+ await userProgressResource.getResource({ workshopDirectory }),
478
+ ],
479
+ };
480
+ });
374
481
  }
375
482
  //# sourceMappingURL=resources.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"resources.js","sourceRoot":"","sources":["../../src/resources.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path'\nimport { invariant } from '@epic-web/invariant'\nimport {\n\textractNumbersAndTypeFromAppNameOrPath,\n\tfindSolutionDir,\n\tgetApps,\n\tgetExercise,\n\tgetExercises,\n\tgetFullPathFromAppName,\n\tgetPlaygroundApp,\n\tisExerciseStepApp,\n\tisPlaygroundApp,\n} from '@epic-web/workshop-utils/apps.server'\nimport { getAuthInfo } from '@epic-web/workshop-utils/db.server'\nimport {\n\tgetEpicVideoInfos,\n\tuserHasAccessToWorkshop,\n} from '@epic-web/workshop-utils/epic-api.server'\nimport {\n\ttype McpServer,\n\tResourceTemplate,\n} from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { type ReadResourceResult } from '@modelcontextprotocol/sdk/types.js'\nimport { z } from 'zod'\nimport {\n\thandleWorkshopDirectory,\n\ttype InputSchemaType,\n\tsafeReadFile,\n} from './utils.js'\n\nexport const getWorkshopContextInputSchema = {\n\tworkshopDirectory: z\n\t\t.string()\n\t\t.describe(\n\t\t\t'The workshop directory (the root directory of the workshop repo.).',\n\t\t),\n}\n\nexport async function getWorkshopContext({\n\tworkshopDirectory,\n}: InputSchemaType<typeof getWorkshopContextInputSchema>) {\n\tconst workshopRoot = await handleWorkshopDirectory(workshopDirectory)\n\tconst inWorkshop = (...d: Array<string>) => path.join(workshopRoot, ...d)\n\tconst readInWorkshop = (...d: Array<string>) => safeReadFile(inWorkshop(...d))\n\n\tconst output = {\n\t\tmeta: {\n\t\t\t'README.md': await readInWorkshop('README.md'),\n\t\t\tconfig: (\n\t\t\t\tJSON.parse((await readInWorkshop('package.json')) || '{}') as any\n\t\t\t).epicshop,\n\t\t\tinstructions: await readInWorkshop('exercise', 'README.mdx'),\n\t\t\tfinishedInstructions: await readInWorkshop('exercise', 'FINISHED.mdx'),\n\t\t},\n\t\texercises: [] as Array<any>,\n\t}\n\n\tconst exercises = await getExercises()\n\tfor (const exercise of exercises) {\n\t\tconst exerciseInfo = {\n\t\t\tfullPath: exercise.fullPath,\n\t\t\texerciseNumber: exercise.exerciseNumber,\n\t\t\ttitle: exercise.title,\n\t\t\tinstructions: await safeReadFile(\n\t\t\t\tpath.join(exercise.fullPath, 'README.mdx'),\n\t\t\t),\n\t\t\tfinishedInstructions: await safeReadFile(\n\t\t\t\tpath.join(exercise.fullPath, 'FINISHED.mdx'),\n\t\t\t),\n\t\t\tsteps: exercise.steps.map((step) => {\n\t\t\t\treturn {\n\t\t\t\t\tstepNumber: step.stepNumber,\n\t\t\t\t\ttitle: step.problem?.title ?? step.solution?.title ?? null,\n\t\t\t\t\tproblem: step.problem\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tfullPath: step.problem.fullPath,\n\t\t\t\t\t\t\t\ttestConfig: step.problem.test,\n\t\t\t\t\t\t\t\tdevConfig: step.problem.dev,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: 'No problem app',\n\t\t\t\t\tsolution: step.solution\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tfullPath: step.solution.fullPath,\n\t\t\t\t\t\t\t\ttestConfig: step.solution.test,\n\t\t\t\t\t\t\t\tdevConfig: step.solution.dev,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: 'No solution app',\n\t\t\t\t}\n\t\t\t}),\n\t\t}\n\t\toutput.exercises.push(exerciseInfo)\n\t}\n\n\treturn output\n}\n\nconst workshopContextUriTemplate = new ResourceTemplate(\n\t'epicshop://{workshopDirectory}/workshop-context',\n\t{ list: undefined },\n)\n\nexport async function getWorkshopContextResource({\n\tworkshopDirectory,\n}: InputSchemaType<typeof getWorkshopContextInputSchema>): Promise<\n\tReadResourceResult['contents'][number]\n> {\n\treturn {\n\t\turi: workshopContextUriTemplate.uriTemplate.expand({\n\t\t\tworkshopDirectory,\n\t\t}),\n\t\tmimeType: 'application/json',\n\t\ttext: JSON.stringify(await getWorkshopContext({ workshopDirectory })),\n\t}\n}\n\nexport const workshopContextResource = {\n\tname: 'workshop_context',\n\tdescription: 'The context of the workshop',\n\turiTemplate: workshopContextUriTemplate,\n\tgetResource: getWorkshopContextResource,\n\tinputSchema: getWorkshopContextInputSchema,\n}\n\nconst getExerciseContextInputSchema = {\n\tworkshopDirectory: z\n\t\t.string()\n\t\t.describe(\n\t\t\t'The workshop directory (the root directory of the workshop repo.).',\n\t\t),\n\texerciseNumber: z.coerce\n\t\t.number()\n\t\t.optional()\n\t\t.describe(\n\t\t\t`The exercise number to get the context for (defaults to the exercise number the playground is currently set to)`,\n\t\t),\n}\n\nasync function getExerciseContext({\n\tworkshopDirectory,\n\texerciseNumber,\n}: {\n\tworkshopDirectory: string\n\texerciseNumber?: number\n}) {\n\tawait handleWorkshopDirectory(workshopDirectory)\n\tconst userHasAccess = await userHasAccessToWorkshop()\n\tconst authInfo = await getAuthInfo()\n\tlet stepNumber = 1\n\tconst playgroundApp = await getPlaygroundApp()\n\tinvariant(playgroundApp, 'No playground app found')\n\tconst numbers = extractNumbersAndTypeFromAppNameOrPath(playgroundApp.appName)\n\tconst isCurrentExercise =\n\t\texerciseNumber === undefined ||\n\t\texerciseNumber === Number(numbers?.exerciseNumber)\n\tif (exerciseNumber === undefined) {\n\t\tinvariant(numbers, 'No numbers found in playground app name')\n\t\texerciseNumber = Number(numbers.exerciseNumber)\n\t\tstepNumber = Number(numbers.stepNumber)\n\t}\n\tconst exercise = await getExercise(exerciseNumber)\n\tinvariant(exercise, `No exercise found for exercise number ${exerciseNumber}`)\n\n\tconst videoInfos = await getEpicVideoInfos([\n\t\t...(exercise.instructionsEpicVideoEmbeds ?? []),\n\t\t...exercise.steps.flatMap((s) => s.problem?.epicVideoEmbeds ?? []),\n\t\t...exercise.steps.flatMap((s) => s.solution?.epicVideoEmbeds ?? []),\n\t\t...(exercise.finishedEpicVideoEmbeds ?? []),\n\t])\n\n\tfunction getTranscripts(embeds?: Array<string>) {\n\t\tif (!embeds) return []\n\t\tif (!userHasAccess && embeds.length) {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tmessage: `User must upgrade before they can get access to ${embeds.length} transcript${embeds.length === 1 ? '' : 's'}.`,\n\t\t\t\t},\n\t\t\t]\n\t\t}\n\t\treturn embeds.map((embed) => {\n\t\t\tconst info = videoInfos[embed]\n\t\t\tif (info) {\n\t\t\t\tif (info.status === 'error') {\n\t\t\t\t\tif (info.type === 'region-restricted') {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tembed,\n\t\t\t\t\t\t\tstatus: 'error',\n\t\t\t\t\t\t\ttype: info.type,\n\t\t\t\t\t\t\trequestedCountry: info.requestCountry,\n\t\t\t\t\t\t\trestrictedCountry: info.restrictedCountry,\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tembed,\n\t\t\t\t\t\t\tstatus: 'error',\n\t\t\t\t\t\t\ttype: info.type,\n\t\t\t\t\t\t\tstatusCode: info.statusCode,\n\t\t\t\t\t\t\tstatusText: info.statusText,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tembed,\n\t\t\t\t\t\tstatus: 'success',\n\t\t\t\t\t\ttranscript: info.transcript,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn {\n\t\t\t\t\tembed,\n\t\t\t\t\tstatus: 'error',\n\t\t\t\t\ttype: 'not-found',\n\t\t\t\t\tmessage: 'No transcript found',\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\tasync function getFileContent(filePath: string) {\n\t\treturn {\n\t\t\tpath: filePath,\n\t\t\tcontent: (await safeReadFile(filePath)) ?? 'None found',\n\t\t}\n\t}\n\n\tconst context = {\n\t\tcurrentContext: {\n\t\t\tuser: {\n\t\t\t\thasAccess: userHasAccess,\n\t\t\t\tisAuthenticated: Boolean(authInfo),\n\t\t\t\temail: authInfo?.email,\n\t\t\t},\n\t\t\tplayground: isCurrentExercise\n\t\t\t\t? {\n\t\t\t\t\t\texerciseNumber,\n\t\t\t\t\t\tstepNumber,\n\t\t\t\t\t}\n\t\t\t\t: 'currently set to a different exercise',\n\t\t},\n\t\texerciseBackground: {\n\t\t\tnumber: exerciseNumber,\n\t\t\tintro: {\n\t\t\t\tcontent: await getFileContent(\n\t\t\t\t\tpath.join(exercise.fullPath, 'README.mdx'),\n\t\t\t\t),\n\t\t\t\ttranscripts: getTranscripts(exercise.instructionsEpicVideoEmbeds),\n\t\t\t},\n\t\t\toutro: {\n\t\t\t\tcontent: await getFileContent(\n\t\t\t\t\tpath.join(exercise.fullPath, 'FINISHED.mdx'),\n\t\t\t\t),\n\t\t\t\ttranscripts: getTranscripts(exercise.finishedEpicVideoEmbeds),\n\t\t\t},\n\t\t},\n\t\tsteps: exercise.steps\n\t\t\t? await Promise.all(\n\t\t\t\t\texercise.steps.map(async (app) => ({\n\t\t\t\t\t\tnumber: app.stepNumber,\n\t\t\t\t\t\tisCurrent: isCurrentExercise && app.stepNumber === stepNumber,\n\t\t\t\t\t\tproblem: {\n\t\t\t\t\t\t\tcontent: app.problem\n\t\t\t\t\t\t\t\t? await getFileContent(\n\t\t\t\t\t\t\t\t\t\tpath.join(app.problem.fullPath, 'README.mdx'),\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t: 'No problem found',\n\t\t\t\t\t\t\ttranscripts: getTranscripts(app.problem?.epicVideoEmbeds),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsolution: {\n\t\t\t\t\t\t\tcontent: app.solution\n\t\t\t\t\t\t\t\t? await getFileContent(\n\t\t\t\t\t\t\t\t\t\tpath.join(app.solution.fullPath, 'README.mdx'),\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t: 'No solution found',\n\t\t\t\t\t\t\ttranscripts: getTranscripts(app.solution?.epicVideoEmbeds),\n\t\t\t\t\t\t},\n\t\t\t\t\t})),\n\t\t\t\t)\n\t\t\t: [],\n\t\tnotes: [] as Array<string>,\n\t}\n\n\tif (exercise.steps) {\n\t\tif (isCurrentExercise) {\n\t\t\tcontext.notes.push(\n\t\t\t\t`Reminder, the current step is ${stepNumber} of ${exercise.steps.length + 1}. The most relevant information will be in the context above within the current step.`,\n\t\t\t)\n\t\t}\n\t} else {\n\t\tcontext.notes.push('Unusually, this exercise has no steps.')\n\t}\n\n\treturn context\n}\n\nconst exerciseContextUriTemplate = new ResourceTemplate(\n\t'epicshop://{workshopDirectory}/exercise/{exerciseNumber}',\n\t{ list: undefined },\n)\n\nasync function getExerciseContextResource({\n\tworkshopDirectory,\n\texerciseNumber,\n}: {\n\tworkshopDirectory: string\n\texerciseNumber?: number\n}): Promise<ReadResourceResult['contents'][number]> {\n\tconst context = await getExerciseContext({\n\t\tworkshopDirectory,\n\t\texerciseNumber,\n\t})\n\treturn {\n\t\turi: exerciseContextUriTemplate.uriTemplate.expand({\n\t\t\tworkshopDirectory,\n\t\t\texerciseNumber: String(context.exerciseBackground.number),\n\t\t}),\n\t\tmimeType: 'application/json',\n\t\ttext: JSON.stringify(context),\n\t}\n}\n\nexport const exerciseContextResource = {\n\tname: 'exercise_context',\n\tdescription: 'The context of the exercise',\n\turiTemplate: exerciseContextUriTemplate,\n\tgetResource: getExerciseContextResource,\n\tinputSchema: getExerciseContextInputSchema,\n}\n\nconst diffBetweenAppsInputSchema = {\n\tworkshopDirectory: z.string().describe('The workshop directory'),\n\tapp1: z.string().describe('The ID of the first app'),\n\tapp2: z.string().describe('The ID of the second app'),\n}\n\nasync function getDiffBetweenApps({\n\tworkshopDirectory,\n\tapp1,\n\tapp2,\n}: InputSchemaType<typeof diffBetweenAppsInputSchema>) {\n\tawait handleWorkshopDirectory(workshopDirectory)\n\n\tconst { getDiffOutputWithRelativePaths } = await import(\n\t\t'@epic-web/workshop-utils/diff.server'\n\t)\n\n\tconst app1Name = extractNumbersAndTypeFromAppNameOrPath(app1)\n\tconst app2Name = extractNumbersAndTypeFromAppNameOrPath(app2)\n\n\tconst apps = await getApps()\n\tconst app1App = apps\n\t\t.filter(isExerciseStepApp)\n\t\t.find(\n\t\t\t(a) =>\n\t\t\t\ta.exerciseNumber === Number(app1Name?.exerciseNumber) &&\n\t\t\t\ta.stepNumber === Number(app1Name?.stepNumber) &&\n\t\t\t\ta.type === app1Name?.type,\n\t\t)\n\tconst app2App = apps\n\t\t.filter(isExerciseStepApp)\n\t\t.find(\n\t\t\t(a) =>\n\t\t\t\ta.exerciseNumber === Number(app2Name?.exerciseNumber) &&\n\t\t\t\ta.stepNumber === Number(app2Name?.stepNumber) &&\n\t\t\t\ta.type === app2Name?.type,\n\t\t)\n\n\tinvariant(app1App, `No app found for ${app1}`)\n\tinvariant(app2App, `No app found for ${app2}`)\n\n\tconst diffCode = await getDiffOutputWithRelativePaths(app1App, app2App)\n\n\tif (!diffCode) return 'No changes'\n\n\treturn diffCode\n}\n\nasync function getDiffBetweenAppsResource({\n\tworkshopDirectory,\n\tapp1,\n\tapp2,\n}: InputSchemaType<typeof diffBetweenAppsInputSchema>): Promise<\n\tReadResourceResult['contents'][number]\n> {\n\treturn {\n\t\turi: diffBetweenAppsUriTemplate.uriTemplate.expand({\n\t\t\tworkshopDirectory,\n\t\t\tapp1,\n\t\t\tapp2,\n\t\t}),\n\t\tmimeType: 'application/json',\n\t\ttext: JSON.stringify(\n\t\t\tawait getDiffBetweenApps({ workshopDirectory, app1, app2 }),\n\t\t),\n\t}\n}\n\nconst diffBetweenAppsUriTemplate = new ResourceTemplate(\n\t'epicshop://{workshopDirectory}/diff-between-apps/{app1}__vs___{app2}',\n\t{ list: undefined },\n)\n\nexport const diffBetweenAppsResource = {\n\tname: 'diff_between_apps',\n\tdescription: 'The diff between two apps',\n\turiTemplate: diffBetweenAppsUriTemplate,\n\tgetResource: getDiffBetweenAppsResource,\n\tinputSchema: diffBetweenAppsInputSchema,\n}\n\nconst getExerciseStepProgressDiffInputSchema = {\n\tworkshopDirectory: z.string().describe('The workshop directory'),\n}\n\nasync function getExerciseStepProgressDiff({\n\tworkshopDirectory,\n}: InputSchemaType<typeof getExerciseStepProgressDiffInputSchema>) {\n\tawait handleWorkshopDirectory(workshopDirectory)\n\n\tconst { getDiffOutputWithRelativePaths } = await import(\n\t\t'@epic-web/workshop-utils/diff.server'\n\t)\n\n\tconst apps = await getApps()\n\tconst playgroundApp = apps.find(isPlaygroundApp)\n\n\tinvariant(playgroundApp, 'No playground app found')\n\n\tconst baseApp = playgroundApp\n\tconst solutionDir = await findSolutionDir({\n\t\tfullPath: await getFullPathFromAppName(playgroundApp.appName),\n\t})\n\tconst headApp = apps.find((a) => a.fullPath === solutionDir)\n\n\tinvariant(headApp, 'No playground solution app found')\n\n\tconst diffCode = await getDiffOutputWithRelativePaths(baseApp, headApp)\n\n\tif (!diffCode) return 'No changes'\n\n\treturn diffCode\n}\n\nasync function getExerciseStepProgressDiffResource({\n\tworkshopDirectory,\n}: InputSchemaType<typeof getExerciseStepProgressDiffInputSchema>): Promise<\n\tReadResourceResult['contents'][number]\n> {\n\treturn {\n\t\turi: exerciseStepProgressDiffUriTemplate.uriTemplate.expand({\n\t\t\tworkshopDirectory,\n\t\t}),\n\t\tmimeType: 'application/json',\n\t\ttext: JSON.stringify(\n\t\t\tawait getExerciseStepProgressDiff({ workshopDirectory }),\n\t\t),\n\t}\n}\n\nconst exerciseStepProgressDiffUriTemplate = new ResourceTemplate(\n\t'epicshop://{workshopDirectory}/exercise-step-progress-diff',\n\t{ list: undefined },\n)\n\nexport const exerciseStepProgressDiffResource = {\n\tname: 'exercise_step_progress_diff',\n\tdescription: 'The diff between the current exercise step and the solution',\n\turiTemplate: exerciseStepProgressDiffUriTemplate,\n\tgetResource: getExerciseStepProgressDiffResource,\n\tinputSchema: getExerciseStepProgressDiffInputSchema,\n}\n\nexport function initResources(server: McpServer) {\n\tserver.resource(\n\t\tworkshopContextResource.name,\n\t\tworkshopContextResource.uriTemplate,\n\t\t{ description: workshopContextResource.description },\n\t\tasync (_uri, { workshopDirectory }) => {\n\t\t\tinvariant(\n\t\t\t\ttypeof workshopDirectory === 'string',\n\t\t\t\t'A single workshopDirectory is required',\n\t\t\t)\n\t\t\tconst resource = await workshopContextResource.getResource({\n\t\t\t\tworkshopDirectory,\n\t\t\t})\n\t\t\treturn { contents: [resource] }\n\t\t},\n\t)\n\n\tserver.resource(\n\t\texerciseContextResource.name,\n\t\texerciseContextResource.uriTemplate,\n\t\t{ description: exerciseContextResource.description },\n\t\tasync (\n\t\t\t_uri,\n\t\t\t{ workshopDirectory, exerciseNumber: providedExerciseNumber },\n\t\t) => {\n\t\t\tinvariant(\n\t\t\t\ttypeof workshopDirectory === 'string',\n\t\t\t\t'A single workshopDirectory is required',\n\t\t\t)\n\t\t\tinvariant(\n\t\t\t\ttypeof providedExerciseNumber === 'string',\n\t\t\t\t'A single exerciseNumber is required',\n\t\t\t)\n\t\t\tconst exerciseNumber = Number(providedExerciseNumber)\n\t\t\tinvariant(!isNaN(exerciseNumber), 'exerciseNumber must be a number')\n\t\t\tinvariant(\n\t\t\t\texerciseNumber >= 0,\n\t\t\t\t'exerciseNumber must be greater than or equal to 0',\n\t\t\t)\n\t\t\treturn {\n\t\t\t\tcontents: [\n\t\t\t\t\tawait exerciseContextResource.getResource({\n\t\t\t\t\t\tworkshopDirectory,\n\t\t\t\t\t\texerciseNumber,\n\t\t\t\t\t}),\n\t\t\t\t],\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.resource(\n\t\tdiffBetweenAppsResource.name,\n\t\tdiffBetweenAppsResource.uriTemplate,\n\t\t{ description: diffBetweenAppsResource.description },\n\t\tasync (_uri, { workshopDirectory, app1, app2 }) => {\n\t\t\tinvariant(\n\t\t\t\ttypeof workshopDirectory === 'string',\n\t\t\t\t'A single workshopDirectory is required',\n\t\t\t)\n\t\t\tinvariant(typeof app1 === 'string', 'A single app1 is required')\n\t\t\tinvariant(typeof app2 === 'string', 'A single app2 is required')\n\t\t\treturn {\n\t\t\t\tcontents: [\n\t\t\t\t\tawait diffBetweenAppsResource.getResource({\n\t\t\t\t\t\tworkshopDirectory,\n\t\t\t\t\t\tapp1,\n\t\t\t\t\t\tapp2,\n\t\t\t\t\t}),\n\t\t\t\t],\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.resource(\n\t\texerciseStepProgressDiffResource.name,\n\t\texerciseStepProgressDiffResource.uriTemplate,\n\t\t{ description: exerciseStepProgressDiffResource.description },\n\t\tasync (_uri, { workshopDirectory }) => {\n\t\t\tinvariant(\n\t\t\t\ttypeof workshopDirectory === 'string',\n\t\t\t\t'A single workshopDirectory is required',\n\t\t\t)\n\t\t\treturn {\n\t\t\t\tcontents: [\n\t\t\t\t\tawait exerciseStepProgressDiffResource.getResource({\n\t\t\t\t\t\tworkshopDirectory,\n\t\t\t\t\t}),\n\t\t\t\t],\n\t\t\t}\n\t\t},\n\t)\n}\n"]}
1
+ {"version":3,"file":"resources.js","sourceRoot":"","sources":["../../src/resources.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path'\nimport { invariant } from '@epic-web/invariant'\nimport {\n\textractNumbersAndTypeFromAppNameOrPath,\n\tfindSolutionDir,\n\tgetApps,\n\tgetExercise,\n\tgetExercises,\n\tgetFullPathFromAppName,\n\tgetPlaygroundApp,\n\tisExerciseStepApp,\n\tisPlaygroundApp,\n} from '@epic-web/workshop-utils/apps.server'\nimport { getAuthInfo } from '@epic-web/workshop-utils/db.server'\nimport {\n\tgetEpicVideoInfos,\n\tuserHasAccessToWorkshop,\n\tgetUserInfo,\n\tgetProgress,\n} from '@epic-web/workshop-utils/epic-api.server'\nimport {\n\ttype McpServer,\n\tResourceTemplate,\n} from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { type ReadResourceResult } from '@modelcontextprotocol/sdk/types.js'\nimport { z } from 'zod'\nimport {\n\thandleWorkshopDirectory,\n\ttype InputSchemaType,\n\tsafeReadFile,\n} from './utils.js'\n\nexport const getWorkshopContextInputSchema = {\n\tworkshopDirectory: z\n\t\t.string()\n\t\t.describe(\n\t\t\t'The workshop directory (the root directory of the workshop repo.).',\n\t\t),\n}\n\nexport async function getWorkshopContext({\n\tworkshopDirectory,\n}: InputSchemaType<typeof getWorkshopContextInputSchema>) {\n\tconst workshopRoot = await handleWorkshopDirectory(workshopDirectory)\n\tconst inWorkshop = (...d: Array<string>) => path.join(workshopRoot, ...d)\n\tconst readInWorkshop = (...d: Array<string>) => safeReadFile(inWorkshop(...d))\n\tconst progress = await getProgress()\n\n\tconst output = {\n\t\tmeta: {\n\t\t\t'README.md': await readInWorkshop('README.md'),\n\t\t\tconfig: (\n\t\t\t\tJSON.parse((await readInWorkshop('package.json')) || '{}') as any\n\t\t\t).epicshop,\n\t\t\tinstructions: {\n\t\t\t\tcontent: await readInWorkshop('exercise', 'README.mdx'),\n\t\t\t\tprogress: progress.find((p) => p.type === 'instructions'),\n\t\t\t},\n\t\t\tfinishedInstructions: {\n\t\t\t\tcontent: await readInWorkshop('exercise', 'FINISHED.mdx'),\n\t\t\t\tprogress: progress.find((p) => p.type === 'finished'),\n\t\t\t},\n\t\t},\n\t\texercises: [] as Array<any>,\n\t}\n\n\tconst exercises = await getExercises()\n\tfor (const exercise of exercises) {\n\t\tconst exerciseInfo = {\n\t\t\tfullPath: exercise.fullPath,\n\t\t\texerciseNumber: exercise.exerciseNumber,\n\t\t\ttitle: exercise.title,\n\t\t\tinstructions: {\n\t\t\t\tcontent: await safeReadFile(path.join(exercise.fullPath, 'README.mdx')),\n\t\t\t\tprogress: progress.find(\n\t\t\t\t\t(p) =>\n\t\t\t\t\t\tp.type === 'instructions' &&\n\t\t\t\t\t\tp.exerciseNumber === exercise.exerciseNumber,\n\t\t\t\t),\n\t\t\t},\n\t\t\tfinishedInstructions: {\n\t\t\t\tcontent: await safeReadFile(\n\t\t\t\t\tpath.join(exercise.fullPath, 'FINISHED.mdx'),\n\t\t\t\t),\n\t\t\t\tprogress: progress.find(\n\t\t\t\t\t(p) =>\n\t\t\t\t\t\tp.type === 'finished' &&\n\t\t\t\t\t\tp.exerciseNumber === exercise.exerciseNumber,\n\t\t\t\t),\n\t\t\t},\n\t\t\tsteps: exercise.steps.map((step) => {\n\t\t\t\treturn {\n\t\t\t\t\tstepNumber: step.stepNumber,\n\t\t\t\t\tprogress: progress.find(\n\t\t\t\t\t\t(p) =>\n\t\t\t\t\t\t\tp.type === 'step' &&\n\t\t\t\t\t\t\tp.exerciseNumber === exercise.exerciseNumber &&\n\t\t\t\t\t\t\tp.stepNumber === step.stepNumber,\n\t\t\t\t\t),\n\t\t\t\t\ttitle: step.problem?.title ?? step.solution?.title ?? null,\n\t\t\t\t\tproblem: step.problem\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tfullPath: step.problem.fullPath,\n\t\t\t\t\t\t\t\ttestConfig: step.problem.test,\n\t\t\t\t\t\t\t\tdevConfig: step.problem.dev,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: 'No problem app',\n\t\t\t\t\tsolution: step.solution\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tfullPath: step.solution.fullPath,\n\t\t\t\t\t\t\t\ttestConfig: step.solution.test,\n\t\t\t\t\t\t\t\tdevConfig: step.solution.dev,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: 'No solution app',\n\t\t\t\t}\n\t\t\t}),\n\t\t}\n\t\toutput.exercises.push(exerciseInfo)\n\t}\n\n\treturn output\n}\n\nconst workshopContextUriTemplate = new ResourceTemplate(\n\t'epicshop://{workshopDirectory}/workshop-context',\n\t{ list: undefined },\n)\n\nexport async function getWorkshopContextResource({\n\tworkshopDirectory,\n}: InputSchemaType<typeof getWorkshopContextInputSchema>): Promise<\n\tReadResourceResult['contents'][number]\n> {\n\treturn {\n\t\turi: workshopContextUriTemplate.uriTemplate.expand({\n\t\t\tworkshopDirectory,\n\t\t}),\n\t\tmimeType: 'application/json',\n\t\ttext: JSON.stringify(await getWorkshopContext({ workshopDirectory })),\n\t}\n}\n\nexport const workshopContextResource = {\n\tname: 'workshop_context',\n\tdescription: 'The context of the workshop',\n\turiTemplate: workshopContextUriTemplate,\n\tgetResource: getWorkshopContextResource,\n\tinputSchema: getWorkshopContextInputSchema,\n}\n\nconst getExerciseContextInputSchema = {\n\tworkshopDirectory: z\n\t\t.string()\n\t\t.describe(\n\t\t\t'The workshop directory (the root directory of the workshop repo.).',\n\t\t),\n\texerciseNumber: z.coerce\n\t\t.number()\n\t\t.optional()\n\t\t.describe(\n\t\t\t`The exercise number to get the context for (defaults to the exercise number the playground is currently set to)`,\n\t\t),\n}\n\nasync function getExerciseContext({\n\tworkshopDirectory,\n\texerciseNumber,\n}: {\n\tworkshopDirectory: string\n\texerciseNumber?: number\n}) {\n\tawait handleWorkshopDirectory(workshopDirectory)\n\tconst userHasAccess = await userHasAccessToWorkshop()\n\tconst authInfo = await getAuthInfo()\n\tconst progress = await getProgress()\n\tlet stepNumber = 1\n\tconst playgroundApp = await getPlaygroundApp()\n\tinvariant(playgroundApp, 'No playground app found')\n\tconst numbers = extractNumbersAndTypeFromAppNameOrPath(playgroundApp.appName)\n\tconst isCurrentExercise =\n\t\texerciseNumber === undefined ||\n\t\texerciseNumber === Number(numbers?.exerciseNumber)\n\tif (exerciseNumber === undefined) {\n\t\tinvariant(numbers, 'No numbers found in playground app name')\n\t\texerciseNumber = Number(numbers.exerciseNumber)\n\t\tstepNumber = Number(numbers.stepNumber)\n\t}\n\tconst exercise = await getExercise(exerciseNumber)\n\tinvariant(exercise, `No exercise found for exercise number ${exerciseNumber}`)\n\n\tconst videoInfos = await getEpicVideoInfos([\n\t\t...(exercise.instructionsEpicVideoEmbeds ?? []),\n\t\t...exercise.steps.flatMap((s) => s.problem?.epicVideoEmbeds ?? []),\n\t\t...exercise.steps.flatMap((s) => s.solution?.epicVideoEmbeds ?? []),\n\t\t...(exercise.finishedEpicVideoEmbeds ?? []),\n\t])\n\n\tfunction getTranscripts(embeds?: Array<string>) {\n\t\tif (!embeds) return []\n\t\tif (!userHasAccess && embeds.length) {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tmessage: `User must upgrade before they can get access to ${embeds.length} transcript${embeds.length === 1 ? '' : 's'}.`,\n\t\t\t\t},\n\t\t\t]\n\t\t}\n\t\treturn embeds.map((embed) => {\n\t\t\tconst info = videoInfos[embed]\n\t\t\tif (info) {\n\t\t\t\tif (info.status === 'error') {\n\t\t\t\t\tif (info.type === 'region-restricted') {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tembed,\n\t\t\t\t\t\t\tstatus: 'error',\n\t\t\t\t\t\t\ttype: info.type,\n\t\t\t\t\t\t\trequestedCountry: info.requestCountry,\n\t\t\t\t\t\t\trestrictedCountry: info.restrictedCountry,\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tembed,\n\t\t\t\t\t\t\tstatus: 'error',\n\t\t\t\t\t\t\ttype: info.type,\n\t\t\t\t\t\t\tstatusCode: info.statusCode,\n\t\t\t\t\t\t\tstatusText: info.statusText,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tembed,\n\t\t\t\t\t\tstatus: 'success',\n\t\t\t\t\t\ttranscript: info.transcript,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn {\n\t\t\t\t\tembed,\n\t\t\t\t\tstatus: 'error',\n\t\t\t\t\ttype: 'not-found',\n\t\t\t\t\tmessage: 'No transcript found',\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\tasync function getFileContent(filePath: string) {\n\t\treturn {\n\t\t\tpath: filePath,\n\t\t\tcontent: (await safeReadFile(filePath)) ?? 'None found',\n\t\t}\n\t}\n\n\tconst context = {\n\t\tcurrentContext: {\n\t\t\tuser: {\n\t\t\t\thasAccess: userHasAccess,\n\t\t\t\tisAuthenticated: Boolean(authInfo),\n\t\t\t\temail: authInfo?.email,\n\t\t\t},\n\t\t\tplayground: isCurrentExercise\n\t\t\t\t? {\n\t\t\t\t\t\texerciseNumber,\n\t\t\t\t\t\tstepNumber,\n\t\t\t\t\t}\n\t\t\t\t: 'currently set to a different exercise',\n\t\t},\n\t\texerciseInfo: {\n\t\t\tnumber: exerciseNumber,\n\t\t\tintro: {\n\t\t\t\tcontent: await getFileContent(\n\t\t\t\t\tpath.join(exercise.fullPath, 'README.mdx'),\n\t\t\t\t),\n\t\t\t\ttranscripts: getTranscripts(exercise.instructionsEpicVideoEmbeds),\n\t\t\t\tprogress: progress.find(\n\t\t\t\t\t(p) =>\n\t\t\t\t\t\tp.type === 'instructions' && p.exerciseNumber === exerciseNumber,\n\t\t\t\t),\n\t\t\t},\n\t\t\toutro: {\n\t\t\t\tcontent: await getFileContent(\n\t\t\t\t\tpath.join(exercise.fullPath, 'FINISHED.mdx'),\n\t\t\t\t),\n\t\t\t\ttranscripts: getTranscripts(exercise.finishedEpicVideoEmbeds),\n\t\t\t\tprogress: progress.find(\n\t\t\t\t\t(p) => p.type === 'finished' && p.exerciseNumber === exerciseNumber,\n\t\t\t\t),\n\t\t\t},\n\t\t},\n\t\tsteps: exercise.steps\n\t\t\t? await Promise.all(\n\t\t\t\t\texercise.steps.map(async (app) => ({\n\t\t\t\t\t\tnumber: app.stepNumber,\n\t\t\t\t\t\tisCurrent: isCurrentExercise && app.stepNumber === stepNumber,\n\t\t\t\t\t\tprogress: progress.find(\n\t\t\t\t\t\t\t(p) =>\n\t\t\t\t\t\t\t\tp.type === 'step' &&\n\t\t\t\t\t\t\t\tp.exerciseNumber === exerciseNumber &&\n\t\t\t\t\t\t\t\tp.stepNumber === app.stepNumber,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tproblem: {\n\t\t\t\t\t\t\tcontent: app.problem\n\t\t\t\t\t\t\t\t? await getFileContent(\n\t\t\t\t\t\t\t\t\t\tpath.join(app.problem.fullPath, 'README.mdx'),\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t: 'No problem found',\n\t\t\t\t\t\t\ttranscripts: getTranscripts(app.problem?.epicVideoEmbeds),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsolution: {\n\t\t\t\t\t\t\tcontent: app.solution\n\t\t\t\t\t\t\t\t? await getFileContent(\n\t\t\t\t\t\t\t\t\t\tpath.join(app.solution.fullPath, 'README.mdx'),\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t: 'No solution found',\n\t\t\t\t\t\t\ttranscripts: getTranscripts(app.solution?.epicVideoEmbeds),\n\t\t\t\t\t\t},\n\t\t\t\t\t})),\n\t\t\t\t)\n\t\t\t: [],\n\t\tnotes: [] as Array<string>,\n\t}\n\n\tif (exercise.steps) {\n\t\tif (isCurrentExercise) {\n\t\t\tcontext.notes.push(\n\t\t\t\t`Reminder, the current step is ${stepNumber} of ${exercise.steps.length + 1}. The most relevant information will be in the context above within the current step.`,\n\t\t\t)\n\t\t}\n\t} else {\n\t\tcontext.notes.push('Unusually, this exercise has no steps.')\n\t}\n\n\treturn context\n}\n\nconst exerciseContextUriTemplate = new ResourceTemplate(\n\t'epicshop://{workshopDirectory}/exercise/{exerciseNumber}',\n\t{ list: undefined },\n)\n\nasync function getExerciseContextResource({\n\tworkshopDirectory,\n\texerciseNumber,\n}: {\n\tworkshopDirectory: string\n\texerciseNumber?: number\n}): Promise<ReadResourceResult['contents'][number]> {\n\tconst context = await getExerciseContext({\n\t\tworkshopDirectory,\n\t\texerciseNumber,\n\t})\n\treturn {\n\t\turi: exerciseContextUriTemplate.uriTemplate.expand({\n\t\t\tworkshopDirectory,\n\t\t\texerciseNumber: String(context.exerciseInfo.number),\n\t\t}),\n\t\tmimeType: 'application/json',\n\t\ttext: JSON.stringify(context),\n\t}\n}\n\nexport const exerciseContextResource = {\n\tname: 'exercise_context',\n\tdescription: 'The context of the exercise',\n\turiTemplate: exerciseContextUriTemplate,\n\tgetResource: getExerciseContextResource,\n\tinputSchema: getExerciseContextInputSchema,\n}\n\nconst diffBetweenAppsInputSchema = {\n\tworkshopDirectory: z.string().describe('The workshop directory'),\n\tapp1: z.string().describe('The ID of the first app'),\n\tapp2: z.string().describe('The ID of the second app'),\n}\n\nasync function getDiffBetweenApps({\n\tworkshopDirectory,\n\tapp1,\n\tapp2,\n}: InputSchemaType<typeof diffBetweenAppsInputSchema>) {\n\tawait handleWorkshopDirectory(workshopDirectory)\n\n\tconst { getDiffOutputWithRelativePaths } = await import(\n\t\t'@epic-web/workshop-utils/diff.server'\n\t)\n\n\tconst app1Name = extractNumbersAndTypeFromAppNameOrPath(app1)\n\tconst app2Name = extractNumbersAndTypeFromAppNameOrPath(app2)\n\n\tconst apps = await getApps()\n\tconst app1App = apps\n\t\t.filter(isExerciseStepApp)\n\t\t.find(\n\t\t\t(a) =>\n\t\t\t\ta.exerciseNumber === Number(app1Name?.exerciseNumber) &&\n\t\t\t\ta.stepNumber === Number(app1Name?.stepNumber) &&\n\t\t\t\ta.type === app1Name?.type,\n\t\t)\n\tconst app2App = apps\n\t\t.filter(isExerciseStepApp)\n\t\t.find(\n\t\t\t(a) =>\n\t\t\t\ta.exerciseNumber === Number(app2Name?.exerciseNumber) &&\n\t\t\t\ta.stepNumber === Number(app2Name?.stepNumber) &&\n\t\t\t\ta.type === app2Name?.type,\n\t\t)\n\n\tinvariant(app1App, `No app found for ${app1}`)\n\tinvariant(app2App, `No app found for ${app2}`)\n\n\tconst diffCode = await getDiffOutputWithRelativePaths(app1App, app2App)\n\n\tif (!diffCode) return 'No changes'\n\n\treturn diffCode\n}\n\nasync function getDiffBetweenAppsResource({\n\tworkshopDirectory,\n\tapp1,\n\tapp2,\n}: InputSchemaType<typeof diffBetweenAppsInputSchema>): Promise<\n\tReadResourceResult['contents'][number]\n> {\n\treturn {\n\t\turi: diffBetweenAppsUriTemplate.uriTemplate.expand({\n\t\t\tworkshopDirectory,\n\t\t\tapp1,\n\t\t\tapp2,\n\t\t}),\n\t\tmimeType: 'application/json',\n\t\ttext: JSON.stringify(\n\t\t\tawait getDiffBetweenApps({ workshopDirectory, app1, app2 }),\n\t\t),\n\t}\n}\n\nconst diffBetweenAppsUriTemplate = new ResourceTemplate(\n\t'epicshop://{workshopDirectory}/diff-between-apps/{app1}__vs___{app2}',\n\t{ list: undefined },\n)\n\nexport const diffBetweenAppsResource = {\n\tname: 'diff_between_apps',\n\tdescription: 'The diff between two apps',\n\turiTemplate: diffBetweenAppsUriTemplate,\n\tgetResource: getDiffBetweenAppsResource,\n\tinputSchema: diffBetweenAppsInputSchema,\n}\n\nconst getExerciseStepProgressDiffInputSchema = {\n\tworkshopDirectory: z.string().describe('The workshop directory'),\n}\n\nasync function getExerciseStepProgressDiff({\n\tworkshopDirectory,\n}: InputSchemaType<typeof getExerciseStepProgressDiffInputSchema>) {\n\tawait handleWorkshopDirectory(workshopDirectory)\n\n\tconst { getDiffOutputWithRelativePaths } = await import(\n\t\t'@epic-web/workshop-utils/diff.server'\n\t)\n\n\tconst apps = await getApps()\n\tconst playgroundApp = apps.find(isPlaygroundApp)\n\n\tinvariant(playgroundApp, 'No playground app found')\n\n\tconst baseApp = playgroundApp\n\tconst solutionDir = await findSolutionDir({\n\t\tfullPath: await getFullPathFromAppName(playgroundApp.appName),\n\t})\n\tconst headApp = apps.find((a) => a.fullPath === solutionDir)\n\n\tinvariant(headApp, 'No playground solution app found')\n\n\tconst diffCode = await getDiffOutputWithRelativePaths(baseApp, headApp)\n\n\tif (!diffCode) return 'No changes'\n\n\treturn diffCode\n}\n\nconst exerciseStepProgressDiffUriTemplate = new ResourceTemplate(\n\t'epicshop://{workshopDirectory}/exercise-step-progress-diff',\n\t{ list: undefined },\n)\n\nasync function getExerciseStepProgressDiffResource({\n\tworkshopDirectory,\n}: InputSchemaType<typeof getExerciseStepProgressDiffInputSchema>): Promise<\n\tReadResourceResult['contents'][number]\n> {\n\treturn {\n\t\turi: exerciseStepProgressDiffUriTemplate.uriTemplate.expand({\n\t\t\tworkshopDirectory,\n\t\t}),\n\t\tmimeType: 'application/json',\n\t\ttext: JSON.stringify(\n\t\t\tawait getExerciseStepProgressDiff({ workshopDirectory }),\n\t\t),\n\t}\n}\n\nexport const exerciseStepProgressDiffResource = {\n\tname: 'exercise_step_progress_diff',\n\tdescription: 'The diff between the current exercise step and the solution',\n\turiTemplate: exerciseStepProgressDiffUriTemplate,\n\tgetResource: getExerciseStepProgressDiffResource,\n\tinputSchema: getExerciseStepProgressDiffInputSchema,\n}\n\nconst getUserInfoInputSchema = {\n\tworkshopDirectory: z.string().describe('The workshop directory'),\n}\n\nconst userInfoUri = new ResourceTemplate(\n\t'epicshop://{workshopDirectory}/user/info',\n\t{ list: undefined },\n)\n\nasync function getUserInfoResource({\n\tworkshopDirectory,\n}: InputSchemaType<typeof getUserInfoInputSchema>) {\n\tconst userInfo = await getUserInfo()\n\treturn {\n\t\turi: userInfoUri.uriTemplate.expand({\n\t\t\tworkshopDirectory,\n\t\t}),\n\t\tmimeType: 'application/json',\n\t\ttext: JSON.stringify(userInfo),\n\t}\n}\n\nexport const userInfoResource = {\n\tname: 'user_info',\n\tdescription: 'Information about the current user',\n\turiTemplate: userInfoUri,\n\tgetResource: getUserInfoResource,\n\tinputSchema: getUserInfoInputSchema,\n}\n\nconst getUserAccessInputSchema = {\n\tworkshopDirectory: z.string().describe('The workshop directory'),\n}\n\nconst userAccessUriTemplate = new ResourceTemplate(\n\t'epicshop://{workshopDirectory}/user/access',\n\t{ list: undefined },\n)\n\nasync function getUserAccessResource({\n\tworkshopDirectory,\n}: InputSchemaType<typeof getUserAccessInputSchema>) {\n\tconst userHasAccess = await userHasAccessToWorkshop()\n\treturn {\n\t\turi: userAccessUriTemplate.uriTemplate.expand({\n\t\t\tworkshopDirectory,\n\t\t}),\n\t\tmimeType: 'application/json',\n\t\ttext: JSON.stringify({ userHasAccess }),\n\t}\n}\n\nexport const userAccessResource = {\n\tname: 'user_access',\n\tdescription: 'Whether the current user has access to the workshop',\n\turiTemplate: userAccessUriTemplate,\n\tgetResource: getUserAccessResource,\n\tinputSchema: getUserAccessInputSchema,\n}\n\nconst userProgressInputSchema = {\n\tworkshopDirectory: z.string().describe('The workshop directory'),\n}\n\nconst userProgressUriTemplate = new ResourceTemplate(\n\t'epicshop://{workshopDirectory}/user/progress',\n\t{ list: undefined },\n)\n\nasync function getUserProgressResource({\n\tworkshopDirectory,\n}: InputSchemaType<typeof userProgressInputSchema>) {\n\tconst userProgress = await getProgress()\n\treturn {\n\t\turi: userProgressUriTemplate.uriTemplate.expand({\n\t\t\tworkshopDirectory,\n\t\t}),\n\t\tmimeType: 'application/json',\n\t\ttext: JSON.stringify(userProgress),\n\t}\n}\n\nexport const userProgressResource = {\n\tname: 'user_progress',\n\tdescription: 'The progress of the current user',\n\turiTemplate: userProgressUriTemplate,\n\tgetResource: getUserProgressResource,\n\tinputSchema: userProgressInputSchema,\n}\n\nexport function initResources(server: McpServer) {\n\tserver.resource(\n\t\tworkshopContextResource.name,\n\t\tworkshopContextResource.uriTemplate,\n\t\t{ description: workshopContextResource.description },\n\t\tasync (_uri, { workshopDirectory }) => {\n\t\t\tinvariant(\n\t\t\t\ttypeof workshopDirectory === 'string',\n\t\t\t\t'A single workshopDirectory is required',\n\t\t\t)\n\t\t\tconst resource = await workshopContextResource.getResource({\n\t\t\t\tworkshopDirectory,\n\t\t\t})\n\t\t\treturn { contents: [resource] }\n\t\t},\n\t)\n\n\tserver.resource(\n\t\texerciseContextResource.name,\n\t\texerciseContextResource.uriTemplate,\n\t\t{ description: exerciseContextResource.description },\n\t\tasync (\n\t\t\t_uri,\n\t\t\t{ workshopDirectory, exerciseNumber: providedExerciseNumber },\n\t\t) => {\n\t\t\tinvariant(\n\t\t\t\ttypeof workshopDirectory === 'string',\n\t\t\t\t'A single workshopDirectory is required',\n\t\t\t)\n\t\t\tinvariant(\n\t\t\t\ttypeof providedExerciseNumber === 'string',\n\t\t\t\t'A single exerciseNumber is required',\n\t\t\t)\n\t\t\tconst exerciseNumber = Number(providedExerciseNumber)\n\t\t\tinvariant(!isNaN(exerciseNumber), 'exerciseNumber must be a number')\n\t\t\tinvariant(\n\t\t\t\texerciseNumber >= 0,\n\t\t\t\t'exerciseNumber must be greater than or equal to 0',\n\t\t\t)\n\t\t\treturn {\n\t\t\t\tcontents: [\n\t\t\t\t\tawait exerciseContextResource.getResource({\n\t\t\t\t\t\tworkshopDirectory,\n\t\t\t\t\t\texerciseNumber,\n\t\t\t\t\t}),\n\t\t\t\t],\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.resource(\n\t\tdiffBetweenAppsResource.name,\n\t\tdiffBetweenAppsResource.uriTemplate,\n\t\t{ description: diffBetweenAppsResource.description },\n\t\tasync (_uri, { workshopDirectory, app1, app2 }) => {\n\t\t\tinvariant(\n\t\t\t\ttypeof workshopDirectory === 'string',\n\t\t\t\t'A single workshopDirectory is required',\n\t\t\t)\n\t\t\tinvariant(typeof app1 === 'string', 'A single app1 is required')\n\t\t\tinvariant(typeof app2 === 'string', 'A single app2 is required')\n\t\t\treturn {\n\t\t\t\tcontents: [\n\t\t\t\t\tawait diffBetweenAppsResource.getResource({\n\t\t\t\t\t\tworkshopDirectory,\n\t\t\t\t\t\tapp1,\n\t\t\t\t\t\tapp2,\n\t\t\t\t\t}),\n\t\t\t\t],\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.resource(\n\t\texerciseStepProgressDiffResource.name,\n\t\texerciseStepProgressDiffResource.uriTemplate,\n\t\t{ description: exerciseStepProgressDiffResource.description },\n\t\tasync (_uri, { workshopDirectory }) => {\n\t\t\tinvariant(\n\t\t\t\ttypeof workshopDirectory === 'string',\n\t\t\t\t'A single workshopDirectory is required',\n\t\t\t)\n\t\t\treturn {\n\t\t\t\tcontents: [\n\t\t\t\t\tawait exerciseStepProgressDiffResource.getResource({\n\t\t\t\t\t\tworkshopDirectory,\n\t\t\t\t\t}),\n\t\t\t\t],\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.resource(\n\t\tuserInfoResource.name,\n\t\tuserInfoResource.uriTemplate,\n\t\t{ description: userInfoResource.description },\n\t\tasync (_uri, { workshopDirectory }) => {\n\t\t\tinvariant(\n\t\t\t\ttypeof workshopDirectory === 'string',\n\t\t\t\t'A single workshopDirectory is required',\n\t\t\t)\n\t\t\treturn {\n\t\t\t\tcontents: [await userInfoResource.getResource({ workshopDirectory })],\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.resource(\n\t\tuserAccessResource.name,\n\t\tuserAccessResource.uriTemplate,\n\t\t{ description: userAccessResource.description },\n\t\tasync (_uri, { workshopDirectory }) => {\n\t\t\tinvariant(\n\t\t\t\ttypeof workshopDirectory === 'string',\n\t\t\t\t'A single workshopDirectory is required',\n\t\t\t)\n\t\t\treturn {\n\t\t\t\tcontents: [await userAccessResource.getResource({ workshopDirectory })],\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.resource(\n\t\tuserProgressResource.name,\n\t\tuserProgressResource.uriTemplate,\n\t\t{ description: userProgressResource.description },\n\t\tasync (_uri, { workshopDirectory }) => {\n\t\t\tinvariant(\n\t\t\t\ttypeof workshopDirectory === 'string',\n\t\t\t\t'A single workshopDirectory is required',\n\t\t\t)\n\t\t\treturn {\n\t\t\t\tcontents: [\n\t\t\t\t\tawait userProgressResource.getResource({ workshopDirectory }),\n\t\t\t\t],\n\t\t\t}\n\t\t},\n\t)\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/tools.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAexE,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,QAiG1C;AAKD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,QAgHlD;AAGD,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,QAuBhD"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/tools.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAmBxE,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,QAiU1C;AAKD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,QAuLlD;AAGD,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,QAuBhD"}
package/dist/esm/tools.js CHANGED
@@ -1,12 +1,103 @@
1
1
  import { invariant } from '@epic-web/invariant';
2
- import { getApps, getPlaygroundAppName, isExerciseStepApp, isProblemApp, setPlayground, } from '@epic-web/workshop-utils/apps.server';
2
+ import { getApps, getExerciseApp, getPlaygroundAppName, isExerciseStepApp, isProblemApp, setPlayground, } from '@epic-web/workshop-utils/apps.server';
3
+ import { deleteCache } from '@epic-web/workshop-utils/cache.server';
4
+ import { getWorkshopConfig } from '@epic-web/workshop-utils/config.server';
5
+ import { getAuthInfo, logout, setAuthInfo, } from '@epic-web/workshop-utils/db.server';
6
+ import { getProgress, getUserInfo, updateProgress, } from '@epic-web/workshop-utils/epic-api.server';
7
+ import * as client from 'openid-client';
3
8
  import { z } from 'zod';
4
9
  import { quizMe, quizMeInputSchema } from './prompts.js';
5
- import { exerciseContextResource, workshopContextResource, diffBetweenAppsResource, exerciseStepProgressDiffResource, } from './resources.js';
10
+ import { diffBetweenAppsResource, exerciseContextResource, exerciseStepProgressDiffResource, userAccessResource, userInfoResource, userProgressResource, workshopContextResource, } from './resources.js';
6
11
  import { handleWorkshopDirectory } from './utils.js';
7
12
  // not enough support for this yet
8
13
  const clientSupportsEmbeddedResources = false;
9
14
  export function initTools(server) {
15
+ server.tool('login', `Allow the user to login (or sign up) to the workshop. First`.trim(), {
16
+ workshopDirectory: z.string().describe('The workshop directory'),
17
+ }, async ({ workshopDirectory }) => {
18
+ await handleWorkshopDirectory(workshopDirectory);
19
+ const { product: { host }, } = getWorkshopConfig();
20
+ const ISSUER = `https://${host}/oauth`;
21
+ const config = await client.discovery(new URL(ISSUER), 'EPICSHOP_APP');
22
+ const deviceResponse = await client.initiateDeviceAuthorization(config, {});
23
+ void handleAuthFlow();
24
+ return {
25
+ content: [
26
+ {
27
+ type: 'text',
28
+ text: `Please go to ${deviceResponse.verification_uri_complete}. Verify the code on the page is "${deviceResponse.user_code}" to login.`,
29
+ },
30
+ ],
31
+ };
32
+ async function handleAuthFlow() {
33
+ const UserInfoSchema = z.object({
34
+ id: z.string(),
35
+ email: z.string(),
36
+ name: z.string().nullable().optional(),
37
+ });
38
+ const timeout = setTimeout(() => {
39
+ void server.server.notification({
40
+ method: 'notification',
41
+ params: {
42
+ message: 'Device authorization timed out',
43
+ },
44
+ });
45
+ }, deviceResponse.expires_in * 1000);
46
+ try {
47
+ const tokenSet = await client.pollDeviceAuthorizationGrant(config, deviceResponse);
48
+ clearTimeout(timeout);
49
+ if (!tokenSet) {
50
+ await server.server.notification({
51
+ method: 'notification',
52
+ params: {
53
+ message: 'No token set',
54
+ },
55
+ });
56
+ return;
57
+ }
58
+ const protectedResourceResponse = await client.fetchProtectedResource(config, tokenSet.access_token, new URL(`${ISSUER}/userinfo`), 'GET');
59
+ const userinfoRaw = await protectedResourceResponse.json();
60
+ const userinfoResult = UserInfoSchema.safeParse(userinfoRaw);
61
+ if (!userinfoResult.success) {
62
+ await server.server.notification({
63
+ method: 'notification',
64
+ params: {
65
+ message: `Failed to parse user info: ${userinfoResult.error.message}`,
66
+ },
67
+ });
68
+ return;
69
+ }
70
+ const userinfo = userinfoResult.data;
71
+ await setAuthInfo({
72
+ id: userinfo.id,
73
+ tokenSet,
74
+ email: userinfo.email,
75
+ name: userinfo.name,
76
+ });
77
+ await getUserInfo({ forceFresh: true });
78
+ await server.server.notification({
79
+ method: 'notification',
80
+ params: {
81
+ message: 'Authentication successful',
82
+ },
83
+ });
84
+ }
85
+ catch (error) {
86
+ clearTimeout(timeout);
87
+ throw error;
88
+ }
89
+ }
90
+ });
91
+ server.tool('logout', `Allow the user to logout of the workshop (based on the workshop's host) and delete cache data.`, {
92
+ workshopDirectory: z.string().describe('The workshop directory'),
93
+ }, async ({ workshopDirectory }) => {
94
+ await handleWorkshopDirectory(workshopDirectory);
95
+ await logout();
96
+ await deleteCache();
97
+ return {
98
+ content: [{ type: 'text', text: 'Logged out' }],
99
+ };
100
+ });
10
101
  server.tool('set_playground', `
11
102
  Sets the playground environment so the user can continue to that exercise or see
12
103
  what that step looks like in their playground environment.
@@ -20,17 +111,19 @@ to start an exercise, specify stepNumber 1 and type 'problem' unless otherwise
20
111
  directed.
21
112
 
22
113
  Argument examples:
23
- A. Set to next exercise step from current (or first if there is none) - Most common
114
+ A. If logged in and there is an incomplete exercise step, set to next incomplete exercise step based on the user's progress - Most common
24
115
  - [No arguments]
25
- B. Set to a specific exercise step
116
+ B. If not logged in or all exercises are complete, set to next exercise step from current (or first if there is none)
117
+ - [No arguments]
118
+ C. Set to a specific exercise step
26
119
  - exerciseNumber: 1
27
120
  - stepNumber: 1
28
121
  - type: 'solution'
29
- C. Set to the solution of the current exercise step
122
+ D. Set to the solution of the current exercise step
30
123
  - type: 'solution'
31
- D. Set to the second step problem of the current exercise
124
+ E. Set to the second step problem of the current exercise
32
125
  - stepNumber: 2
33
- E. Set to the first step problem of the fifth exercise
126
+ F. Set to the first step problem of the fifth exercise
34
127
  - exerciseNumber: 5
35
128
 
36
129
  An error will be returned if no app is found for the given arguments.
@@ -50,6 +143,57 @@ An error will be returned if no app is found for the given arguments.
50
143
  .describe('The type of app to set the playground to'),
51
144
  }, async ({ workshopDirectory, exerciseNumber, stepNumber, type }) => {
52
145
  await handleWorkshopDirectory(workshopDirectory);
146
+ const authInfo = await getAuthInfo();
147
+ if (authInfo) {
148
+ const progress = await getProgress();
149
+ const scoreProgress = (a) => {
150
+ if (a.type === 'workshop-instructions')
151
+ return 0;
152
+ if (a.type === 'workshop-finished')
153
+ return 10000;
154
+ if (a.type === 'instructions')
155
+ return a.exerciseNumber * 100;
156
+ if (a.type === 'step')
157
+ return a.exerciseNumber * 100 + a.stepNumber;
158
+ if (a.type === 'finished')
159
+ return a.exerciseNumber * 100 + 100;
160
+ if (a.type === 'unknown')
161
+ return 100000;
162
+ return -1;
163
+ };
164
+ const sortedProgress = progress.sort((a, b) => {
165
+ return scoreProgress(a) - scoreProgress(b);
166
+ });
167
+ const nextProgress = sortedProgress.find((p) => !p.epicCompletedAt);
168
+ if (nextProgress) {
169
+ if (nextProgress.type === 'step') {
170
+ const exerciseApp = await getExerciseApp({
171
+ exerciseNumber: nextProgress.exerciseNumber.toString(),
172
+ stepNumber: nextProgress.stepNumber.toString(),
173
+ type: 'problem',
174
+ });
175
+ invariant(exerciseApp, 'No exercise app found');
176
+ await setPlayground(exerciseApp.fullPath);
177
+ return {
178
+ content: [
179
+ {
180
+ type: 'text',
181
+ text: `Playground set to ${exerciseApp.exerciseNumber}.${exerciseApp.stepNumber}.${exerciseApp.type}`,
182
+ },
183
+ ],
184
+ };
185
+ }
186
+ if (nextProgress.type === 'instructions' ||
187
+ nextProgress.type === 'finished') {
188
+ throw new Error(`The user needs to mark the ${nextProgress.exerciseNumber} ${nextProgress.type === 'instructions' ? 'instructions' : 'finished'} as complete before they can continue. Have them watch the video at ${nextProgress.epicLessonUrl}, then mark it as complete.`);
189
+ }
190
+ if (nextProgress.type === 'workshop-instructions' ||
191
+ nextProgress.type === 'workshop-finished') {
192
+ throw new Error(`The user needs to mark the ${nextProgress.exerciseNumber} ${nextProgress.type === 'workshop-instructions' ? 'Workshop instructions' : 'Workshop finished'} as complete before they can continue. Have them watch the video at ${nextProgress.epicLessonUrl}, then mark it as complete.`);
193
+ }
194
+ throw new Error(`The user needs to mark ${nextProgress.epicLessonSlug} as complete before they can continue. Have them watch the video at ${nextProgress.epicLessonUrl}, then mark it as complete.`);
195
+ }
196
+ }
53
197
  const apps = await getApps();
54
198
  const exerciseStepApps = apps.filter(isExerciseStepApp);
55
199
  const playgroundAppName = await getPlaygroundAppName();
@@ -75,15 +219,47 @@ An error will be returned if no app is found for the given arguments.
75
219
  }
76
220
  invariant(desiredApp, `No app found for values derived by the arguments: ${exerciseNumber}.${stepNumber}.${type}`);
77
221
  await setPlayground(desiredApp.fullPath);
222
+ const exerciseContext = await exerciseContextResource.getResource({
223
+ workshopDirectory,
224
+ exerciseNumber: desiredApp.exerciseNumber,
225
+ });
226
+ return {
227
+ content: [
228
+ {
229
+ type: 'text',
230
+ text: `Playground set to ${desiredApp.name}.`,
231
+ },
232
+ getEmbeddedResourceContent(exerciseContext),
233
+ ],
234
+ };
235
+ });
236
+ server.tool('update_progress', `
237
+ Intended to help you mark an Epic lesson as complete or incomplete.
238
+
239
+ This will mark the Epic lesson as complete or incomplete and update the user's progress (get updated progress with the \`get_user_progress\` tool, the \`get_exercise_context\` tool, or the \`get_workshop_context\` tool).
240
+ `.trim(), {
241
+ workshopDirectory: z.string().describe('The workshop directory'),
242
+ epicLessonSlug: z
243
+ .string()
244
+ .describe('The slug of the Epic lesson to mark as complete (can be retrieved from the `get_exercise_context` tool or the `get_workshop_context` tool)'),
245
+ complete: z
246
+ .boolean()
247
+ .optional()
248
+ .default(true)
249
+ .describe('Whether to mark the lesson as complete or incomplete (defaults to true)'),
250
+ }, async ({ workshopDirectory, epicLessonSlug, complete }) => {
251
+ await handleWorkshopDirectory(workshopDirectory);
252
+ await updateProgress({ lessonSlug: epicLessonSlug, complete });
78
253
  return {
79
254
  content: [
80
255
  {
81
256
  type: 'text',
82
- text: `Playground set to ${desiredApp.name}`,
257
+ text: `Lesson with slug ${epicLessonSlug} marked as ${complete ? 'complete' : 'incomplete'}`,
83
258
  },
84
259
  ],
85
260
  };
86
261
  });
262
+ // TODO: add a tool to run the dev/test script for the given app
87
263
  }
88
264
  // These are tools that retrieve resources. Not all resources should be
89
265
  // accessible via tools, but allowing the LLM to access them on demand is useful
@@ -95,6 +271,7 @@ workshop. This doesn't go into as much detail per exercise as the
95
271
  \`get_exercise_context\` tool, but it is a good starting point to orient
96
272
  yourself on the workshop as a whole.
97
273
  `.trim(), workshopContextResource.inputSchema, async ({ workshopDirectory }) => {
274
+ await handleWorkshopDirectory(workshopDirectory);
98
275
  const resource = await workshopContextResource.getResource({
99
276
  workshopDirectory,
100
277
  });
@@ -117,6 +294,7 @@ more than once.
117
294
  \`get_exercise_step_progress_diff\` tool to help a student understand what
118
295
  work they still need to do and answer any questions about the exercise.
119
296
  `.trim(), exerciseContextResource.inputSchema, async ({ workshopDirectory, exerciseNumber }) => {
297
+ await handleWorkshopDirectory(workshopDirectory);
120
298
  const resource = await exerciseContextResource.getResource({
121
299
  workshopDirectory,
122
300
  exerciseNumber,
@@ -138,6 +316,7 @@ App IDs are formatted as \`{exerciseNumber}.{stepNumber}.{type}\`.
138
316
 
139
317
  If the user asks for the diff for 2.3, then use 02.03.problem for app1 and 02.03.solution for app2.
140
318
  `, diffBetweenAppsResource.inputSchema, async ({ workshopDirectory, app1, app2 }) => {
319
+ await handleWorkshopDirectory(workshopDirectory);
141
320
  const resource = await diffBetweenAppsResource.getResource({
142
321
  workshopDirectory,
143
322
  app1,
@@ -170,6 +349,7 @@ For additional context, you can use the \`get_exercise_instructions\` tool
170
349
  to get the instructions for the current exercise step to help explain the
171
350
  significance of changes.
172
351
  `.trim(), exerciseStepProgressDiffResource.inputSchema, async ({ workshopDirectory }) => {
352
+ await handleWorkshopDirectory(workshopDirectory);
173
353
  const resource = await exerciseStepProgressDiffResource.getResource({
174
354
  workshopDirectory,
175
355
  });
@@ -177,6 +357,55 @@ significance of changes.
177
357
  content: [getEmbeddedResourceContent(resource)],
178
358
  };
179
359
  });
360
+ server.tool('get_user_info', `
361
+ Intended to help you get information about the current user.
362
+
363
+ This includes the user's name, email, etc. It's mostly useful to determine
364
+ whether the user is logged in and know who they are.
365
+
366
+ If the user is not logged in, tell them to log in by running the \`login\` tool.
367
+ `.trim(), userInfoResource.inputSchema, async ({ workshopDirectory }) => {
368
+ await handleWorkshopDirectory(workshopDirectory);
369
+ const resource = await userInfoResource.getResource({ workshopDirectory });
370
+ return {
371
+ content: [getEmbeddedResourceContent(resource)],
372
+ };
373
+ });
374
+ server.tool('get_user_access', `
375
+ Will tell you whether the user has access to the paid features of the workshop.
376
+
377
+ Paid features include:
378
+ - Transcripts
379
+ - Progress tracking
380
+ - Access to videos
381
+ - Access to the discord chat
382
+ - Test tab support
383
+ - Diff tab support
384
+
385
+ Encourage the user to upgrade if they need access to the paid features.
386
+ `.trim(), userAccessResource.inputSchema, async ({ workshopDirectory }) => {
387
+ await handleWorkshopDirectory(workshopDirectory);
388
+ const resource = await userAccessResource.getResource({
389
+ workshopDirectory,
390
+ });
391
+ return {
392
+ content: [getEmbeddedResourceContent(resource)],
393
+ };
394
+ });
395
+ server.tool('get_user_progress', `
396
+ Intended to help you get the progress of the current user. Can often be helpful
397
+ to know what the next step that needs to be completed is. Make sure to provide
398
+ the user with the URL of relevant incomplete lessons so they can watch them and
399
+ then mark them as complete.
400
+ `.trim(), userProgressResource.inputSchema, async ({ workshopDirectory }) => {
401
+ await handleWorkshopDirectory(workshopDirectory);
402
+ const resource = await userProgressResource.getResource({
403
+ workshopDirectory,
404
+ });
405
+ return {
406
+ content: [getEmbeddedResourceContent(resource)],
407
+ };
408
+ });
180
409
  }
181
410
  // Sometimes the user will ask the LLM to select a prompt to use so they don't have to.
182
411
  export function initPromptTools(server) {
@@ -1 +1 @@
1
- {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/tools.ts"],"names":[],"mappings":"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","sourcesContent":["import { invariant } from '@epic-web/invariant'\nimport {\n\tgetApps,\n\tgetPlaygroundAppName,\n\tisExerciseStepApp,\n\tisProblemApp,\n\tsetPlayground,\n\ttype ExerciseStepApp,\n} from '@epic-web/workshop-utils/apps.server'\nimport { type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { type ReadResourceResult } from '@modelcontextprotocol/sdk/types.js'\nimport { z } from 'zod'\nimport { quizMe, quizMeInputSchema } from './prompts.js'\nimport {\n\texerciseContextResource,\n\tworkshopContextResource,\n\tdiffBetweenAppsResource,\n\texerciseStepProgressDiffResource,\n} from './resources.js'\nimport { handleWorkshopDirectory } from './utils.js'\n\n// not enough support for this yet\nconst clientSupportsEmbeddedResources = false\n\nexport function initTools(server: McpServer) {\n\tserver.tool(\n\t\t'set_playground',\n\t\t`\nSets the playground environment so the user can continue to that exercise or see\nwhat that step looks like in their playground environment.\n\nNOTE: this will override their current exercise step work in the playground!\n\nGenerally, it is better to not provide an exerciseNumber, stepNumber, and type\nand let the user continue to the next exercise. Only provide these arguments if\nthe user explicitely asks to go to a specific exercise or step. If the user asks\nto start an exercise, specify stepNumber 1 and type 'problem' unless otherwise\ndirected.\n\nArgument examples:\nA. Set to next exercise step from current (or first if there is none) - Most common\n\t- [No arguments]\nB. Set to a specific exercise step\n\t- exerciseNumber: 1\n\t- stepNumber: 1\n\t- type: 'solution'\nC. Set to the solution of the current exercise step\n\t- type: 'solution'\nD. Set to the second step problem of the current exercise\n\t- stepNumber: 2\nE. Set to the first step problem of the fifth exercise\n\t- exerciseNumber: 5\n\nAn error will be returned if no app is found for the given arguments.\n\t`.trim(),\n\t\t{\n\t\t\tworkshopDirectory: z.string().describe('The workshop directory'),\n\t\t\texerciseNumber: z.coerce\n\t\t\t\t.number()\n\t\t\t\t.optional()\n\t\t\t\t.describe('The exercise number to set the playground to'),\n\t\t\tstepNumber: z.coerce\n\t\t\t\t.number()\n\t\t\t\t.optional()\n\t\t\t\t.describe('The step number to set the playground to'),\n\t\t\ttype: z\n\t\t\t\t.enum(['problem', 'solution'])\n\t\t\t\t.optional()\n\t\t\t\t.describe('The type of app to set the playground to'),\n\t\t},\n\t\tasync ({ workshopDirectory, exerciseNumber, stepNumber, type }) => {\n\t\t\tawait handleWorkshopDirectory(workshopDirectory)\n\n\t\t\tconst apps = await getApps()\n\t\t\tconst exerciseStepApps = apps.filter(isExerciseStepApp)\n\n\t\t\tconst playgroundAppName = await getPlaygroundAppName()\n\t\t\tconst currentExerciseStepAppIndex = exerciseStepApps.findIndex(\n\t\t\t\t(a) => a.name === playgroundAppName,\n\t\t\t)\n\n\t\t\tlet desiredApp: ExerciseStepApp | undefined\n\t\t\t// if nothing was provided, set to the next step problem app\n\t\t\tconst noArgumentsProvided = !exerciseNumber && !stepNumber && !type\n\t\t\tif (noArgumentsProvided) {\n\t\t\t\tdesiredApp = exerciseStepApps\n\t\t\t\t\t.slice(currentExerciseStepAppIndex + 1)\n\t\t\t\t\t.find(isProblemApp)\n\t\t\t\tinvariant(desiredApp, 'No next problem app found to set playground to')\n\t\t\t} else {\n\t\t\t\tconst currentExerciseStepApp =\n\t\t\t\t\texerciseStepApps[currentExerciseStepAppIndex]\n\n\t\t\t\t// otherwise, default to the current exercise step app for arguments\n\t\t\t\texerciseNumber ??= currentExerciseStepApp?.exerciseNumber\n\t\t\t\tstepNumber ??= currentExerciseStepApp?.stepNumber\n\t\t\t\ttype ??= currentExerciseStepApp?.type\n\n\t\t\t\tdesiredApp = exerciseStepApps.find(\n\t\t\t\t\t(a) =>\n\t\t\t\t\t\ta.exerciseNumber === exerciseNumber &&\n\t\t\t\t\t\ta.stepNumber === stepNumber &&\n\t\t\t\t\t\ta.type === type,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tinvariant(\n\t\t\t\tdesiredApp,\n\t\t\t\t`No app found for values derived by the arguments: ${exerciseNumber}.${stepNumber}.${type}`,\n\t\t\t)\n\t\t\tawait setPlayground(desiredApp.fullPath)\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'text',\n\t\t\t\t\t\ttext: `Playground set to ${desiredApp.name}`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}\n\t\t},\n\t)\n}\n\n// These are tools that retrieve resources. Not all resources should be\n// accessible via tools, but allowing the LLM to access them on demand is useful\n// for some situations.\nexport function initResourceTools(server: McpServer) {\n\tserver.tool(\n\t\t'get_workshop_context',\n\t\t`\nIndended to help you get wholistic context of the topics covered in this\nworkshop. This doesn't go into as much detail per exercise as the\n\\`get_exercise_context\\` tool, but it is a good starting point to orient\nyourself on the workshop as a whole.\n\t\t`.trim(),\n\t\tworkshopContextResource.inputSchema,\n\t\tasync ({ workshopDirectory }) => {\n\t\t\tconst resource = await workshopContextResource.getResource({\n\t\t\t\tworkshopDirectory,\n\t\t\t})\n\t\t\treturn {\n\t\t\t\tcontent: [getEmbeddedResourceContent(resource)],\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.tool(\n\t\t'get_exercise_context',\n\t\t`\nIntended to help a student understand what they need to do for the current\nexercise step.\n\nThis returns the instructions MDX content for the current exercise and each\nexercise step. If the user is has the paid version of the workshop, it will also\ninclude the transcript from each of the videos as well.\n\nThe output for this will rarely change, so it's unnecessary to call this tool\nmore than once.\n\n\\`get_exercise_context\\` is often best when used with the\n\\`get_exercise_step_progress_diff\\` tool to help a student understand what\nwork they still need to do and answer any questions about the exercise.\n\t\t`.trim(),\n\t\texerciseContextResource.inputSchema,\n\t\tasync ({ workshopDirectory, exerciseNumber }) => {\n\t\t\tconst resource = await exerciseContextResource.getResource({\n\t\t\t\tworkshopDirectory,\n\t\t\t\texerciseNumber,\n\t\t\t})\n\t\t\treturn {\n\t\t\t\tcontent: [getEmbeddedResourceContent(resource)],\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.tool(\n\t\t'get_diff_between_apps',\n\t\t`\nIntended to give context about the changes between two apps.\n\nThe output is a git diff of the playground directory as BASE (their work in\nprogress) against the solution directory as HEAD (the final state they're trying\nto achieve).\n\nThe output is formatted as a git diff.\n\nApp IDs are formatted as \\`{exerciseNumber}.{stepNumber}.{type}\\`.\n\nIf the user asks for the diff for 2.3, then use 02.03.problem for app1 and 02.03.solution for app2.\n\t\t`,\n\t\tdiffBetweenAppsResource.inputSchema,\n\t\tasync ({ workshopDirectory, app1, app2 }) => {\n\t\t\tconst resource = await diffBetweenAppsResource.getResource({\n\t\t\t\tworkshopDirectory,\n\t\t\t\tapp1,\n\t\t\t\tapp2,\n\t\t\t})\n\t\t\treturn {\n\t\t\t\tcontent: [getEmbeddedResourceContent(resource)],\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.tool(\n\t\t'get_exercise_step_progress_diff',\n\t\t`\nIntended to help a student understand what work they still have to complete.\n\nThis returns a git diff of the playground directory as BASE (their work in\nprogress) against the solution directory as HEAD (the final state they're trying\nto achieve). Meaning, if there are lines removed, it means they still need to\nadd those lines and if they are added, it means they still need to remove them.\n\nOnly tell the user they have more work to do if the diff output affects the\nrequired behavior, API, or user experience. If the differences are only\nstylistic or organizational, explain that things look different, but they are\nstill valid and ready to be tested.\n\nIf there's a diff with significant changes, you should explain what the changes\nare and their significance. Be brief. Let them tell you whether they need you to\nelaborate.\n\nThe output for this changes over time so it's useful to call multiple times.\n\nFor additional context, you can use the \\`get_exercise_instructions\\` tool\nto get the instructions for the current exercise step to help explain the\nsignificance of changes.\n\t\t`.trim(),\n\t\texerciseStepProgressDiffResource.inputSchema,\n\t\tasync ({ workshopDirectory }) => {\n\t\t\tconst resource = await exerciseStepProgressDiffResource.getResource({\n\t\t\t\tworkshopDirectory,\n\t\t\t})\n\t\t\treturn {\n\t\t\t\tcontent: [getEmbeddedResourceContent(resource)],\n\t\t\t}\n\t\t},\n\t)\n}\n\n// Sometimes the user will ask the LLM to select a prompt to use so they don't have to.\nexport function initPromptTools(server: McpServer) {\n\tserver.tool(\n\t\t'get_quiz_instructions',\n\t\t`\nIf the user asks you to quiz them on a topic from the workshop, use this tool to\nretrieve the instructions for how to do so.\n\t\t`.trim(),\n\t\tquizMeInputSchema,\n\t\tasync ({ workshopDirectory, exerciseNumber }) => {\n\t\t\tconst result = await quizMe({ workshopDirectory, exerciseNumber })\n\t\t\treturn {\n\t\t\t\t// QUESTION: will a prompt ever return messages that have role: 'assistant'?\n\t\t\t\t// if so, this may be a little confusing for the LLM, but I can't think of a\n\t\t\t\t// good use case for that so 🤷‍♂️\n\t\t\t\tcontent: result.messages.map((m) => {\n\t\t\t\t\tif (m.content.type === 'resource') {\n\t\t\t\t\t\treturn getEmbeddedResourceContent(m.content.resource)\n\t\t\t\t\t}\n\t\t\t\t\treturn m.content\n\t\t\t\t}),\n\t\t\t}\n\t\t},\n\t)\n}\n\nfunction getEmbeddedResourceContent(\n\tresource: ReadResourceResult['contents'][number],\n) {\n\tif (clientSupportsEmbeddedResources) {\n\t\treturn {\n\t\t\ttype: 'resource' as const,\n\t\t\tresource,\n\t\t}\n\t} else if (typeof resource.text === 'string') {\n\t\treturn {\n\t\t\ttype: 'text' as const,\n\t\t\ttext: resource.text,\n\t\t}\n\t} else {\n\t\tthrow new Error(\n\t\t\t`Unknown resource type: ${resource.type} for ${resource.uri}`,\n\t\t)\n\t}\n}\n"]}
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/tools.ts"],"names":[],"mappings":"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","sourcesContent":["import { invariant } from '@epic-web/invariant'\nimport {\n\tgetApps,\n\tgetExerciseApp,\n\tgetPlaygroundAppName,\n\tisExerciseStepApp,\n\tisProblemApp,\n\tsetPlayground,\n\ttype ExerciseStepApp,\n} from '@epic-web/workshop-utils/apps.server'\nimport { deleteCache } from '@epic-web/workshop-utils/cache.server'\nimport { getWorkshopConfig } from '@epic-web/workshop-utils/config.server'\nimport {\n\tgetAuthInfo,\n\tlogout,\n\tsetAuthInfo,\n} from '@epic-web/workshop-utils/db.server'\nimport {\n\tgetProgress,\n\tgetUserInfo,\n\tupdateProgress,\n} from '@epic-web/workshop-utils/epic-api.server'\nimport { type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { type ReadResourceResult } from '@modelcontextprotocol/sdk/types.js'\nimport * as client from 'openid-client'\nimport { z } from 'zod'\nimport { quizMe, quizMeInputSchema } from './prompts.js'\nimport {\n\tdiffBetweenAppsResource,\n\texerciseContextResource,\n\texerciseStepProgressDiffResource,\n\tuserAccessResource,\n\tuserInfoResource,\n\tuserProgressResource,\n\tworkshopContextResource,\n} from './resources.js'\nimport { handleWorkshopDirectory } from './utils.js'\n\n// not enough support for this yet\nconst clientSupportsEmbeddedResources = false\n\nexport function initTools(server: McpServer) {\n\tserver.tool(\n\t\t'login',\n\t\t`Allow the user to login (or sign up) to the workshop. First`.trim(),\n\t\t{\n\t\t\tworkshopDirectory: z.string().describe('The workshop directory'),\n\t\t},\n\t\tasync ({ workshopDirectory }) => {\n\t\t\tawait handleWorkshopDirectory(workshopDirectory)\n\t\t\tconst {\n\t\t\t\tproduct: { host },\n\t\t\t} = getWorkshopConfig()\n\t\t\tconst ISSUER = `https://${host}/oauth`\n\t\t\tconst config = await client.discovery(new URL(ISSUER), 'EPICSHOP_APP')\n\t\t\tconst deviceResponse = await client.initiateDeviceAuthorization(\n\t\t\t\tconfig,\n\t\t\t\t{},\n\t\t\t)\n\n\t\t\tvoid handleAuthFlow()\n\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'text',\n\t\t\t\t\t\ttext: `Please go to ${deviceResponse.verification_uri_complete}. Verify the code on the page is \"${deviceResponse.user_code}\" to login.`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}\n\n\t\t\tasync function handleAuthFlow() {\n\t\t\t\tconst UserInfoSchema = z.object({\n\t\t\t\t\tid: z.string(),\n\t\t\t\t\temail: z.string(),\n\t\t\t\t\tname: z.string().nullable().optional(),\n\t\t\t\t})\n\n\t\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\t\tvoid server.server.notification({\n\t\t\t\t\t\tmethod: 'notification',\n\t\t\t\t\t\tparams: {\n\t\t\t\t\t\t\tmessage: 'Device authorization timed out',\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t}, deviceResponse.expires_in * 1000)\n\n\t\t\t\ttry {\n\t\t\t\t\tconst tokenSet = await client.pollDeviceAuthorizationGrant(\n\t\t\t\t\t\tconfig,\n\t\t\t\t\t\tdeviceResponse,\n\t\t\t\t\t)\n\t\t\t\t\tclearTimeout(timeout)\n\n\t\t\t\t\tif (!tokenSet) {\n\t\t\t\t\t\tawait server.server.notification({\n\t\t\t\t\t\t\tmethod: 'notification',\n\t\t\t\t\t\t\tparams: {\n\t\t\t\t\t\t\t\tmessage: 'No token set',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tconst protectedResourceResponse = await client.fetchProtectedResource(\n\t\t\t\t\t\tconfig,\n\t\t\t\t\t\ttokenSet.access_token,\n\t\t\t\t\t\tnew URL(`${ISSUER}/userinfo`),\n\t\t\t\t\t\t'GET',\n\t\t\t\t\t)\n\t\t\t\t\tconst userinfoRaw = await protectedResourceResponse.json()\n\t\t\t\t\tconst userinfoResult = UserInfoSchema.safeParse(userinfoRaw)\n\t\t\t\t\tif (!userinfoResult.success) {\n\t\t\t\t\t\tawait server.server.notification({\n\t\t\t\t\t\t\tmethod: 'notification',\n\t\t\t\t\t\t\tparams: {\n\t\t\t\t\t\t\t\tmessage: `Failed to parse user info: ${userinfoResult.error.message}`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tconst userinfo = userinfoResult.data\n\n\t\t\t\t\tawait setAuthInfo({\n\t\t\t\t\t\tid: userinfo.id,\n\t\t\t\t\t\ttokenSet,\n\t\t\t\t\t\temail: userinfo.email,\n\t\t\t\t\t\tname: userinfo.name,\n\t\t\t\t\t})\n\n\t\t\t\t\tawait getUserInfo({ forceFresh: true })\n\n\t\t\t\t\tawait server.server.notification({\n\t\t\t\t\t\tmethod: 'notification',\n\t\t\t\t\t\tparams: {\n\t\t\t\t\t\t\tmessage: 'Authentication successful',\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t} catch (error) {\n\t\t\t\t\tclearTimeout(timeout)\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.tool(\n\t\t'logout',\n\t\t`Allow the user to logout of the workshop (based on the workshop's host) and delete cache data.`,\n\t\t{\n\t\t\tworkshopDirectory: z.string().describe('The workshop directory'),\n\t\t},\n\t\tasync ({ workshopDirectory }) => {\n\t\t\tawait handleWorkshopDirectory(workshopDirectory)\n\t\t\tawait logout()\n\t\t\tawait deleteCache()\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: 'text', text: 'Logged out' }],\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.tool(\n\t\t'set_playground',\n\t\t`\nSets the playground environment so the user can continue to that exercise or see\nwhat that step looks like in their playground environment.\n\nNOTE: this will override their current exercise step work in the playground!\n\nGenerally, it is better to not provide an exerciseNumber, stepNumber, and type\nand let the user continue to the next exercise. Only provide these arguments if\nthe user explicitely asks to go to a specific exercise or step. If the user asks\nto start an exercise, specify stepNumber 1 and type 'problem' unless otherwise\ndirected.\n\nArgument examples:\nA. If logged in and there is an incomplete exercise step, set to next incomplete exercise step based on the user's progress - Most common\n\t- [No arguments]\nB. If not logged in or all exercises are complete, set to next exercise step from current (or first if there is none)\n\t- [No arguments]\nC. Set to a specific exercise step\n\t- exerciseNumber: 1\n\t- stepNumber: 1\n\t- type: 'solution'\nD. Set to the solution of the current exercise step\n\t- type: 'solution'\nE. Set to the second step problem of the current exercise\n\t- stepNumber: 2\nF. Set to the first step problem of the fifth exercise\n\t- exerciseNumber: 5\n\nAn error will be returned if no app is found for the given arguments.\n\t`.trim(),\n\t\t{\n\t\t\tworkshopDirectory: z.string().describe('The workshop directory'),\n\t\t\texerciseNumber: z.coerce\n\t\t\t\t.number()\n\t\t\t\t.optional()\n\t\t\t\t.describe('The exercise number to set the playground to'),\n\t\t\tstepNumber: z.coerce\n\t\t\t\t.number()\n\t\t\t\t.optional()\n\t\t\t\t.describe('The step number to set the playground to'),\n\t\t\ttype: z\n\t\t\t\t.enum(['problem', 'solution'])\n\t\t\t\t.optional()\n\t\t\t\t.describe('The type of app to set the playground to'),\n\t\t},\n\t\tasync ({ workshopDirectory, exerciseNumber, stepNumber, type }) => {\n\t\t\tawait handleWorkshopDirectory(workshopDirectory)\n\t\t\tconst authInfo = await getAuthInfo()\n\n\t\t\tif (authInfo) {\n\t\t\t\tconst progress = await getProgress()\n\t\t\t\tconst scoreProgress = (a: (typeof progress)[number]) => {\n\t\t\t\t\tif (a.type === 'workshop-instructions') return 0\n\t\t\t\t\tif (a.type === 'workshop-finished') return 10000\n\t\t\t\t\tif (a.type === 'instructions') return a.exerciseNumber * 100\n\t\t\t\t\tif (a.type === 'step') return a.exerciseNumber * 100 + a.stepNumber\n\t\t\t\t\tif (a.type === 'finished') return a.exerciseNumber * 100 + 100\n\n\t\t\t\t\tif (a.type === 'unknown') return 100000\n\t\t\t\t\treturn -1\n\t\t\t\t}\n\t\t\t\tconst sortedProgress = progress.sort((a, b) => {\n\t\t\t\t\treturn scoreProgress(a) - scoreProgress(b)\n\t\t\t\t})\n\t\t\t\tconst nextProgress = sortedProgress.find((p) => !p.epicCompletedAt)\n\t\t\t\tif (nextProgress) {\n\t\t\t\t\tif (nextProgress.type === 'step') {\n\t\t\t\t\t\tconst exerciseApp = await getExerciseApp({\n\t\t\t\t\t\t\texerciseNumber: nextProgress.exerciseNumber.toString(),\n\t\t\t\t\t\t\tstepNumber: nextProgress.stepNumber.toString(),\n\t\t\t\t\t\t\ttype: 'problem',\n\t\t\t\t\t\t})\n\t\t\t\t\t\tinvariant(exerciseApp, 'No exercise app found')\n\t\t\t\t\t\tawait setPlayground(exerciseApp.fullPath)\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: 'text',\n\t\t\t\t\t\t\t\t\ttext: `Playground set to ${exerciseApp.exerciseNumber}.${exerciseApp.stepNumber}.${exerciseApp.type}`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (\n\t\t\t\t\t\tnextProgress.type === 'instructions' ||\n\t\t\t\t\t\tnextProgress.type === 'finished'\n\t\t\t\t\t) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`The user needs to mark the ${nextProgress.exerciseNumber} ${nextProgress.type === 'instructions' ? 'instructions' : 'finished'} as complete before they can continue. Have them watch the video at ${nextProgress.epicLessonUrl}, then mark it as complete.`,\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t\tif (\n\t\t\t\t\t\tnextProgress.type === 'workshop-instructions' ||\n\t\t\t\t\t\tnextProgress.type === 'workshop-finished'\n\t\t\t\t\t) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`The user needs to mark the ${nextProgress.exerciseNumber} ${nextProgress.type === 'workshop-instructions' ? 'Workshop instructions' : 'Workshop finished'} as complete before they can continue. Have them watch the video at ${nextProgress.epicLessonUrl}, then mark it as complete.`,\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`The user needs to mark ${nextProgress.epicLessonSlug} as complete before they can continue. Have them watch the video at ${nextProgress.epicLessonUrl}, then mark it as complete.`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst apps = await getApps()\n\t\t\tconst exerciseStepApps = apps.filter(isExerciseStepApp)\n\n\t\t\tconst playgroundAppName = await getPlaygroundAppName()\n\t\t\tconst currentExerciseStepAppIndex = exerciseStepApps.findIndex(\n\t\t\t\t(a) => a.name === playgroundAppName,\n\t\t\t)\n\n\t\t\tlet desiredApp: ExerciseStepApp | undefined\n\t\t\t// if nothing was provided, set to the next step problem app\n\t\t\tconst noArgumentsProvided = !exerciseNumber && !stepNumber && !type\n\t\t\tif (noArgumentsProvided) {\n\t\t\t\tdesiredApp = exerciseStepApps\n\t\t\t\t\t.slice(currentExerciseStepAppIndex + 1)\n\t\t\t\t\t.find(isProblemApp)\n\t\t\t\tinvariant(desiredApp, 'No next problem app found to set playground to')\n\t\t\t} else {\n\t\t\t\tconst currentExerciseStepApp =\n\t\t\t\t\texerciseStepApps[currentExerciseStepAppIndex]\n\n\t\t\t\t// otherwise, default to the current exercise step app for arguments\n\t\t\t\texerciseNumber ??= currentExerciseStepApp?.exerciseNumber\n\t\t\t\tstepNumber ??= currentExerciseStepApp?.stepNumber\n\t\t\t\ttype ??= currentExerciseStepApp?.type\n\n\t\t\t\tdesiredApp = exerciseStepApps.find(\n\t\t\t\t\t(a) =>\n\t\t\t\t\t\ta.exerciseNumber === exerciseNumber &&\n\t\t\t\t\t\ta.stepNumber === stepNumber &&\n\t\t\t\t\t\ta.type === type,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tinvariant(\n\t\t\t\tdesiredApp,\n\t\t\t\t`No app found for values derived by the arguments: ${exerciseNumber}.${stepNumber}.${type}`,\n\t\t\t)\n\t\t\tawait setPlayground(desiredApp.fullPath)\n\t\t\tconst exerciseContext = await exerciseContextResource.getResource({\n\t\t\t\tworkshopDirectory,\n\t\t\t\texerciseNumber: desiredApp.exerciseNumber,\n\t\t\t})\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'text',\n\t\t\t\t\t\ttext: `Playground set to ${desiredApp.name}.`,\n\t\t\t\t\t},\n\t\t\t\t\tgetEmbeddedResourceContent(exerciseContext),\n\t\t\t\t],\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.tool(\n\t\t'update_progress',\n\t\t`\nIntended to help you mark an Epic lesson as complete or incomplete.\n\nThis will mark the Epic lesson as complete or incomplete and update the user's progress (get updated progress with the \\`get_user_progress\\` tool, the \\`get_exercise_context\\` tool, or the \\`get_workshop_context\\` tool).\n\t\t`.trim(),\n\t\t{\n\t\t\tworkshopDirectory: z.string().describe('The workshop directory'),\n\t\t\tepicLessonSlug: z\n\t\t\t\t.string()\n\t\t\t\t.describe(\n\t\t\t\t\t'The slug of the Epic lesson to mark as complete (can be retrieved from the `get_exercise_context` tool or the `get_workshop_context` tool)',\n\t\t\t\t),\n\t\t\tcomplete: z\n\t\t\t\t.boolean()\n\t\t\t\t.optional()\n\t\t\t\t.default(true)\n\t\t\t\t.describe(\n\t\t\t\t\t'Whether to mark the lesson as complete or incomplete (defaults to true)',\n\t\t\t\t),\n\t\t},\n\t\tasync ({ workshopDirectory, epicLessonSlug, complete }) => {\n\t\t\tawait handleWorkshopDirectory(workshopDirectory)\n\t\t\tawait updateProgress({ lessonSlug: epicLessonSlug, complete })\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'text',\n\t\t\t\t\t\ttext: `Lesson with slug ${epicLessonSlug} marked as ${complete ? 'complete' : 'incomplete'}`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}\n\t\t},\n\t)\n\n\t// TODO: add a tool to run the dev/test script for the given app\n}\n\n// These are tools that retrieve resources. Not all resources should be\n// accessible via tools, but allowing the LLM to access them on demand is useful\n// for some situations.\nexport function initResourceTools(server: McpServer) {\n\tserver.tool(\n\t\t'get_workshop_context',\n\t\t`\nIndended to help you get wholistic context of the topics covered in this\nworkshop. This doesn't go into as much detail per exercise as the\n\\`get_exercise_context\\` tool, but it is a good starting point to orient\nyourself on the workshop as a whole.\n\t\t`.trim(),\n\t\tworkshopContextResource.inputSchema,\n\t\tasync ({ workshopDirectory }) => {\n\t\t\tawait handleWorkshopDirectory(workshopDirectory)\n\t\t\tconst resource = await workshopContextResource.getResource({\n\t\t\t\tworkshopDirectory,\n\t\t\t})\n\t\t\treturn {\n\t\t\t\tcontent: [getEmbeddedResourceContent(resource)],\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.tool(\n\t\t'get_exercise_context',\n\t\t`\nIntended to help a student understand what they need to do for the current\nexercise step.\n\nThis returns the instructions MDX content for the current exercise and each\nexercise step. If the user is has the paid version of the workshop, it will also\ninclude the transcript from each of the videos as well.\n\nThe output for this will rarely change, so it's unnecessary to call this tool\nmore than once.\n\n\\`get_exercise_context\\` is often best when used with the\n\\`get_exercise_step_progress_diff\\` tool to help a student understand what\nwork they still need to do and answer any questions about the exercise.\n\t\t`.trim(),\n\t\texerciseContextResource.inputSchema,\n\t\tasync ({ workshopDirectory, exerciseNumber }) => {\n\t\t\tawait handleWorkshopDirectory(workshopDirectory)\n\t\t\tconst resource = await exerciseContextResource.getResource({\n\t\t\t\tworkshopDirectory,\n\t\t\t\texerciseNumber,\n\t\t\t})\n\t\t\treturn {\n\t\t\t\tcontent: [getEmbeddedResourceContent(resource)],\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.tool(\n\t\t'get_diff_between_apps',\n\t\t`\nIntended to give context about the changes between two apps.\n\nThe output is a git diff of the playground directory as BASE (their work in\nprogress) against the solution directory as HEAD (the final state they're trying\nto achieve).\n\nThe output is formatted as a git diff.\n\nApp IDs are formatted as \\`{exerciseNumber}.{stepNumber}.{type}\\`.\n\nIf the user asks for the diff for 2.3, then use 02.03.problem for app1 and 02.03.solution for app2.\n\t\t`,\n\t\tdiffBetweenAppsResource.inputSchema,\n\t\tasync ({ workshopDirectory, app1, app2 }) => {\n\t\t\tawait handleWorkshopDirectory(workshopDirectory)\n\t\t\tconst resource = await diffBetweenAppsResource.getResource({\n\t\t\t\tworkshopDirectory,\n\t\t\t\tapp1,\n\t\t\t\tapp2,\n\t\t\t})\n\t\t\treturn {\n\t\t\t\tcontent: [getEmbeddedResourceContent(resource)],\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.tool(\n\t\t'get_exercise_step_progress_diff',\n\t\t`\nIntended to help a student understand what work they still have to complete.\n\nThis returns a git diff of the playground directory as BASE (their work in\nprogress) against the solution directory as HEAD (the final state they're trying\nto achieve). Meaning, if there are lines removed, it means they still need to\nadd those lines and if they are added, it means they still need to remove them.\n\nOnly tell the user they have more work to do if the diff output affects the\nrequired behavior, API, or user experience. If the differences are only\nstylistic or organizational, explain that things look different, but they are\nstill valid and ready to be tested.\n\nIf there's a diff with significant changes, you should explain what the changes\nare and their significance. Be brief. Let them tell you whether they need you to\nelaborate.\n\nThe output for this changes over time so it's useful to call multiple times.\n\nFor additional context, you can use the \\`get_exercise_instructions\\` tool\nto get the instructions for the current exercise step to help explain the\nsignificance of changes.\n\t\t`.trim(),\n\t\texerciseStepProgressDiffResource.inputSchema,\n\t\tasync ({ workshopDirectory }) => {\n\t\t\tawait handleWorkshopDirectory(workshopDirectory)\n\t\t\tconst resource = await exerciseStepProgressDiffResource.getResource({\n\t\t\t\tworkshopDirectory,\n\t\t\t})\n\t\t\treturn {\n\t\t\t\tcontent: [getEmbeddedResourceContent(resource)],\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.tool(\n\t\t'get_user_info',\n\t\t`\nIntended to help you get information about the current user.\n\nThis includes the user's name, email, etc. It's mostly useful to determine\nwhether the user is logged in and know who they are.\n\nIf the user is not logged in, tell them to log in by running the \\`login\\` tool.\n\t\t`.trim(),\n\t\tuserInfoResource.inputSchema,\n\t\tasync ({ workshopDirectory }) => {\n\t\t\tawait handleWorkshopDirectory(workshopDirectory)\n\t\t\tconst resource = await userInfoResource.getResource({ workshopDirectory })\n\t\t\treturn {\n\t\t\t\tcontent: [getEmbeddedResourceContent(resource)],\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.tool(\n\t\t'get_user_access',\n\t\t`\nWill tell you whether the user has access to the paid features of the workshop.\n\nPaid features include:\n- Transcripts\n- Progress tracking\n- Access to videos\n- Access to the discord chat\n- Test tab support\n- Diff tab support\n\nEncourage the user to upgrade if they need access to the paid features.\n\t\t`.trim(),\n\t\tuserAccessResource.inputSchema,\n\t\tasync ({ workshopDirectory }) => {\n\t\t\tawait handleWorkshopDirectory(workshopDirectory)\n\t\t\tconst resource = await userAccessResource.getResource({\n\t\t\t\tworkshopDirectory,\n\t\t\t})\n\t\t\treturn {\n\t\t\t\tcontent: [getEmbeddedResourceContent(resource)],\n\t\t\t}\n\t\t},\n\t)\n\n\tserver.tool(\n\t\t'get_user_progress',\n\t\t`\nIntended to help you get the progress of the current user. Can often be helpful\nto know what the next step that needs to be completed is. Make sure to provide\nthe user with the URL of relevant incomplete lessons so they can watch them and\nthen mark them as complete.\n\t\t`.trim(),\n\t\tuserProgressResource.inputSchema,\n\t\tasync ({ workshopDirectory }) => {\n\t\t\tawait handleWorkshopDirectory(workshopDirectory)\n\t\t\tconst resource = await userProgressResource.getResource({\n\t\t\t\tworkshopDirectory,\n\t\t\t})\n\t\t\treturn {\n\t\t\t\tcontent: [getEmbeddedResourceContent(resource)],\n\t\t\t}\n\t\t},\n\t)\n}\n\n// Sometimes the user will ask the LLM to select a prompt to use so they don't have to.\nexport function initPromptTools(server: McpServer) {\n\tserver.tool(\n\t\t'get_quiz_instructions',\n\t\t`\nIf the user asks you to quiz them on a topic from the workshop, use this tool to\nretrieve the instructions for how to do so.\n\t\t`.trim(),\n\t\tquizMeInputSchema,\n\t\tasync ({ workshopDirectory, exerciseNumber }) => {\n\t\t\tconst result = await quizMe({ workshopDirectory, exerciseNumber })\n\t\t\treturn {\n\t\t\t\t// QUESTION: will a prompt ever return messages that have role: 'assistant'?\n\t\t\t\t// if so, this may be a little confusing for the LLM, but I can't think of a\n\t\t\t\t// good use case for that so 🤷‍♂️\n\t\t\t\tcontent: result.messages.map((m) => {\n\t\t\t\t\tif (m.content.type === 'resource') {\n\t\t\t\t\t\treturn getEmbeddedResourceContent(m.content.resource)\n\t\t\t\t\t}\n\t\t\t\t\treturn m.content\n\t\t\t\t}),\n\t\t\t}\n\t\t},\n\t)\n}\n\nfunction getEmbeddedResourceContent(\n\tresource: ReadResourceResult['contents'][number],\n) {\n\tif (clientSupportsEmbeddedResources) {\n\t\treturn {\n\t\t\ttype: 'resource' as const,\n\t\t\tresource,\n\t\t}\n\t} else if (typeof resource.text === 'string') {\n\t\treturn {\n\t\t\ttype: 'text' as const,\n\t\t\ttext: resource.text,\n\t\t}\n\t} else {\n\t\tthrow new Error(\n\t\t\t`Unknown resource type: ${resource.type} for ${resource.uri}`,\n\t\t)\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epic-web/workshop-mcp",
3
- "version": "5.21.0",
3
+ "version": "5.22.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -30,7 +30,7 @@
30
30
  "build:watch": "nx watch --projects=@epic-web/workshop-mcp -- nx run \\$NX_PROJECT_NAME:build"
31
31
  },
32
32
  "dependencies": {
33
- "@epic-web/workshop-utils": "5.21.0",
33
+ "@epic-web/workshop-utils": "5.22.0",
34
34
  "@epic-web/invariant": "^1.0.0",
35
35
  "@modelcontextprotocol/sdk": "^1.9.0",
36
36
  "zod": "^3.24.2"