@epic-web/workshop-mcp 5.14.3 → 5.14.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/cli.js +22 -8
- package/dist/esm/cli.js.map +1 -1
- package/package.json +2 -2
package/dist/esm/cli.js
CHANGED
|
@@ -96,16 +96,26 @@ work they still need to do and answer any questions about the exercise.
|
|
|
96
96
|
workshopDirectory: z
|
|
97
97
|
.string()
|
|
98
98
|
.describe('The workshop directory (the root directory of the workshop repo. Best to not bother asking the user and just use the project root path).'),
|
|
99
|
-
|
|
99
|
+
exerciseNumber: z.coerce
|
|
100
|
+
.number()
|
|
101
|
+
.optional()
|
|
102
|
+
.describe(`The exercise number to get the context for (defaults to the exercise number the playground is currently set to)`),
|
|
103
|
+
}, async ({ workshopDirectory, exerciseNumber }) => {
|
|
100
104
|
try {
|
|
101
105
|
await handleWorkshopDirectory(workshopDirectory);
|
|
102
106
|
const userHasAccess = await userHasAccessToWorkshop();
|
|
103
107
|
const authInfo = await getAuthInfo();
|
|
108
|
+
let stepNumber = 1;
|
|
104
109
|
const playgroundApp = await getPlaygroundApp();
|
|
105
110
|
invariant(playgroundApp, 'No playground app found');
|
|
106
111
|
const numbers = extractNumbersAndTypeFromAppNameOrPath(playgroundApp.appName);
|
|
107
|
-
|
|
108
|
-
|
|
112
|
+
const isCurrentExercise = exerciseNumber === undefined ||
|
|
113
|
+
exerciseNumber === Number(numbers?.exerciseNumber);
|
|
114
|
+
if (exerciseNumber === undefined) {
|
|
115
|
+
invariant(numbers, 'No numbers found in playground app name');
|
|
116
|
+
exerciseNumber = Number(numbers.exerciseNumber);
|
|
117
|
+
stepNumber = Number(numbers.stepNumber);
|
|
118
|
+
}
|
|
109
119
|
const exercise = await getExercise(exerciseNumber);
|
|
110
120
|
invariant(exercise, `No exercise found for exercise number ${exerciseNumber}`);
|
|
111
121
|
const videoInfos = await getEpicVideoInfos([
|
|
@@ -171,10 +181,12 @@ Below is all the context for this exercise and each step.
|
|
|
171
181
|
|
|
172
182
|
<currentContext>
|
|
173
183
|
<user hasAccess="${userHasAccess}" isAuthenticated="${Boolean(authInfo)}" email="${authInfo?.email}" />
|
|
174
|
-
|
|
184
|
+
${isCurrentExercise
|
|
185
|
+
? `<playground>
|
|
175
186
|
<exerciseNumber>${exerciseNumber}</exerciseNumber>
|
|
176
187
|
<stepNumber>${stepNumber}</stepNumber>
|
|
177
|
-
</playground
|
|
188
|
+
</playground>`
|
|
189
|
+
: '<playground>currently set to a different exercise</playground>'}
|
|
178
190
|
</currentContext>
|
|
179
191
|
|
|
180
192
|
<exerciseBackground number="${exerciseNumber}">
|
|
@@ -192,7 +204,7 @@ Below is all the context for this exercise and each step.
|
|
|
192
204
|
text += '\n\n<steps>';
|
|
193
205
|
for (const app of exercise.steps) {
|
|
194
206
|
text += `
|
|
195
|
-
<step number="${app.stepNumber}" isCurrent="${app.stepNumber ===
|
|
207
|
+
<step number="${app.stepNumber}" isCurrent="${isCurrentExercise && app.stepNumber === stepNumber}">
|
|
196
208
|
<problem>
|
|
197
209
|
${app.problem ? await getFileContentElement(path.join(app.problem?.fullPath, `README.mdx`)) : 'No problem found'}
|
|
198
210
|
${getTranscriptsElement(app.problem?.epicVideoEmbeds ?? [])}
|
|
@@ -225,7 +237,9 @@ NOTE: this will override their current exercise step work in the playground!
|
|
|
225
237
|
|
|
226
238
|
Generally, it is better to not provide an exerciseNumber, stepNumber, and type
|
|
227
239
|
and let the user continue to the next exercise. Only provide these arguments if
|
|
228
|
-
the user explicitely asks to go to a specific exercise or step.
|
|
240
|
+
the user explicitely asks to go to a specific exercise or step. If the user asks
|
|
241
|
+
to start an exercise, specify stepNumber 1 and type 'problem' unless otherwise
|
|
242
|
+
directed.
|
|
229
243
|
|
|
230
244
|
Argument examples:
|
|
231
245
|
A. Set to next exercise step from current (or first if there is none) - Most common
|
|
@@ -277,7 +291,7 @@ An error will be returned if no app is found for the given arguments.
|
|
|
277
291
|
// otherwise, default to the current exercise step app for arguments
|
|
278
292
|
exerciseNumber ??= currentExerciseStepApp?.exerciseNumber;
|
|
279
293
|
stepNumber ??= currentExerciseStepApp?.stepNumber;
|
|
280
|
-
type ??=
|
|
294
|
+
type ??= currentExerciseStepApp?.type;
|
|
281
295
|
desiredApp = exerciseStepApps.find((a) => a.exerciseNumber === exerciseNumber &&
|
|
282
296
|
a.stepNumber === stepNumber &&
|
|
283
297
|
a.type === type);
|
package/dist/esm/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { invariant } from '@epic-web/invariant'\nimport {\n\tgetApps,\n\tisPlaygroundApp,\n\tfindSolutionDir,\n\tgetFullPathFromAppName,\n\tinit as initApps,\n\textractNumbersAndTypeFromAppNameOrPath,\n\tgetExercise,\n\tgetPlaygroundApp,\n\tgetPlaygroundAppName,\n\tisProblemApp,\n\tisExerciseStepApp,\n\tsetPlayground,\n\ttype ExerciseStepApp,\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 { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { type CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport { z } from 'zod'\n\n// Create server instance\nconst server = new McpServer(\n\t{\n\t\tname: 'epicshop',\n\t\tversion: '1.0.0',\n\t\tcapabilities: {\n\t\t\ttools: {},\n\t\t},\n\t},\n\t{\n\t\tinstructions: `\nThis is intended to be used within a workshop using the Epic Workshop App\n(@epic-web/workshop-app) to help learners in the process of completing the\nworkshop exercises and understanding the learning outcomes.\n\nThe user's work in progress is in the \\`playground\\` directory. Any changes they\nask you to make should be in this directory.\n\t\t`.trim(),\n\t},\n)\n\nserver.tool(\n\t'get_exercise_step_progress_diff',\n\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\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`.trim(),\n\t{\n\t\tworkshopDirectory: z\n\t\t\t.string()\n\t\t\t.describe(\n\t\t\t\t'The workshop directory (the root directory of the workshop repo. Best to not bother asking the user and just use the project root path).',\n\t\t\t),\n\t},\n\tasync ({ workshopDirectory }) => {\n\t\ttry {\n\t\t\tawait handleWorkshopDirectory(workshopDirectory)\n\n\t\t\tconst { getDiffOutputWithRelativePaths } = await import(\n\t\t\t\t'@epic-web/workshop-utils/diff.server'\n\t\t\t)\n\n\t\t\tconst apps = await getApps()\n\t\t\tconst playgroundApp = apps.find(isPlaygroundApp)\n\n\t\t\tif (!playgroundApp) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: 'text', text: 'No playground app found' }],\n\t\t\t\t\tisError: true,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst baseApp = playgroundApp\n\t\t\tconst solutionDir = await findSolutionDir({\n\t\t\t\tfullPath: await getFullPathFromAppName(playgroundApp.appName),\n\t\t\t})\n\t\t\tconst headApp = apps.find((a) => a.fullPath === solutionDir)\n\n\t\t\tif (!headApp) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: 'text', text: 'No playground solution app found' }],\n\t\t\t\t\tisError: true,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst diffCode = await getDiffOutputWithRelativePaths(baseApp, headApp)\n\n\t\t\tif (!diffCode) return replyWithText('No changes')\n\n\t\t\treturn replyWithText(diffCode)\n\t\t} catch (error) {\n\t\t\treturn replyWithError(error)\n\t\t}\n\t},\n)\n\nserver.tool(\n\t'get_exercise_context',\n\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`.trim(),\n\t{\n\t\tworkshopDirectory: z\n\t\t\t.string()\n\t\t\t.describe(\n\t\t\t\t'The workshop directory (the root directory of the workshop repo. Best to not bother asking the user and just use the project root path).',\n\t\t\t),\n\t},\n\tasync ({ workshopDirectory }) => {\n\t\ttry {\n\t\t\tawait handleWorkshopDirectory(workshopDirectory)\n\t\t\tconst userHasAccess = await userHasAccessToWorkshop()\n\t\t\tconst authInfo = await getAuthInfo()\n\t\t\tconst playgroundApp = await getPlaygroundApp()\n\t\t\tinvariant(playgroundApp, 'No playground app found')\n\t\t\tconst numbers = extractNumbersAndTypeFromAppNameOrPath(\n\t\t\t\tplaygroundApp.appName,\n\t\t\t)\n\t\t\tinvariant(numbers, 'No numbers found in playground app name')\n\t\t\tconst { exerciseNumber, stepNumber } = numbers\n\t\t\tconst exercise = await getExercise(exerciseNumber)\n\t\t\tinvariant(\n\t\t\t\texercise,\n\t\t\t\t`No exercise found for exercise number ${exerciseNumber}`,\n\t\t\t)\n\n\t\t\tconst videoInfos = await getEpicVideoInfos([\n\t\t\t\t...(exercise.instructionsEpicVideoEmbeds ?? []),\n\t\t\t\t...exercise.steps.flatMap((s) => s.problem?.epicVideoEmbeds ?? []),\n\t\t\t\t...exercise.steps.flatMap((s) => s.solution?.epicVideoEmbeds ?? []),\n\t\t\t\t...(exercise.finishedEpicVideoEmbeds ?? []),\n\t\t\t])\n\n\t\t\tfunction getTranscriptsElement(embeds?: Array<string>) {\n\t\t\t\tif (!embeds) return '<transcripts />'\n\t\t\t\tif (!userHasAccess && embeds.length) {\n\t\t\t\t\treturn `\n\t\t\t\t\t\t<transcripts>\n\t\t\t\t\t\t\tUser must upgrade before they can get access to ${embeds.length} transcript${embeds.length === 1 ? '' : 's'}.\n\t\t\t\t\t\t</transcripts>\n\t\t\t\t\t`.trim()\n\t\t\t\t}\n\t\t\t\tconst transcripts = ['<transcripts>']\n\t\t\t\tfor (const embed of embeds) {\n\t\t\t\t\tconst info = videoInfos[embed]\n\t\t\t\t\tif (info) {\n\t\t\t\t\t\tif (info.status === 'error') {\n\t\t\t\t\t\t\tif (info.type === 'region-restricted') {\n\t\t\t\t\t\t\t\ttranscripts.push(\n\t\t\t\t\t\t\t\t\t`\n\t\t\t\t\t\t\t\t\t<transcript\n\t\t\t\t\t\t\t\t\t\tembed=\"${embed}\"\n\t\t\t\t\t\t\t\t\t\tstatus=\"error\"\n\t\t\t\t\t\t\t\t\t\ttype=\"${info.type}\"\n\t\t\t\t\t\t\t\t\t\trequested-country=\"${info.requestCountry}\"\n\t\t\t\t\t\t\t\t\t\trestricted-country=\"${info.restrictedCountry}\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t`.trim(),\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ttranscripts.push(\n\t\t\t\t\t\t\t\t\t`\n\t\t\t\t\t\t\t\t\t<transcript\n\t\t\t\t\t\t\t\t\t\tembed=\"${embed}\"\n\t\t\t\t\t\t\t\t\t\tstatus=\"error\"\n\t\t\t\t\t\t\t\t\t\ttype=\"${info.type}\"\n\t\t\t\t\t\t\t\t\t\tstatus-code=\"${info.statusCode}\"\n\t\t\t\t\t\t\t\t\t\tstatus-text=\"${info.statusText}\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t`.trim(),\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttranscripts.push(\n\t\t\t\t\t\t\t\t`<transcript embed=\"${embed}\" status=\"success\">${info.transcript}</transcript>`,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttranscripts.push(\n\t\t\t\t\t\t\t`<transcript embed=\"${embed}\" status=\"error\" type=\"not-found\">No transcript found</transcript>`,\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttranscripts.push('</transcripts>')\n\t\t\t\treturn transcripts.join('\\n')\n\t\t\t}\n\n\t\t\tasync function getFileContentElement(filePath: string) {\n\t\t\t\treturn `<file path=\"${filePath}\">${(await safeReadFile(filePath)) ?? 'None found'}</file>`\n\t\t\t}\n\t\t\tlet text = `\nBelow is all the context for this exercise and each step.\n\n<currentContext>\n\t<user hasAccess=\"${userHasAccess}\" isAuthenticated=\"${Boolean(authInfo)}\" email=\"${authInfo?.email}\" />\n\t<playground>\n\t\t<exerciseNumber>${exerciseNumber}</exerciseNumber>\n\t\t<stepNumber>${stepNumber}</stepNumber>\n\t</playground>\n</currentContext>\n\n<exerciseBackground number=\"${exerciseNumber}\">\n\t<intro>\n\t\t${await getFileContentElement(path.join(exercise.fullPath, 'README.mdx'))}\n\t\t${getTranscriptsElement(exercise.instructionsEpicVideoEmbeds)}\n\t</intro>\n\t<outro>\n\t\t${await getFileContentElement(path.join(exercise.fullPath, 'FINISHED.mdx'))}\n\t\t${getTranscriptsElement(exercise.finishedEpicVideoEmbeds)}\n\t</outro>\n</exerciseBackground>\n\t\t\t`.trim()\n\n\t\t\tif (exercise.steps) {\n\t\t\t\ttext += '\\n\\n<steps>'\n\t\t\t\tfor (const app of exercise.steps) {\n\t\t\t\t\ttext += `\n<step number=\"${app.stepNumber}\" isCurrent=\"${app.stepNumber === Number(stepNumber)}\">\n\t<problem>\n\t\t${app.problem ? await getFileContentElement(path.join(app.problem?.fullPath, `README.mdx`)) : 'No problem found'}\n\t\t${getTranscriptsElement(app.problem?.epicVideoEmbeds ?? [])}\n\t</problem>\n\t<solution>\n\t\t${app.solution ? await getFileContentElement(path.join(app.solution?.fullPath, `README.mdx`)) : 'No solution found'}\n\t\t${getTranscriptsElement(app.solution?.epicVideoEmbeds ?? [])}\n\t</solution>\n</step>`\n\t\t\t\t}\n\t\t\t\ttext += '</steps>\\n\\n'\n\n\t\t\t\ttext += `Reminder, the current step is ${stepNumber} of ${exercise.steps.length + 1}. The most relevant information will be in the context abouve within the current step.`\n\t\t\t} else {\n\t\t\t\ttext += `Unusually, this exercise has no steps.`\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: 'text', text: text }],\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn replyWithError(error)\n\t\t}\n\t},\n)\n\nserver.tool(\n\t'set_playground',\n\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.\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{\n\t\tworkshopDirectory: z.string().describe('The workshop directory'),\n\t\texerciseNumber: z.coerce\n\t\t\t.number()\n\t\t\t.optional()\n\t\t\t.describe('The exercise number to set the playground to'),\n\t\tstepNumber: z.coerce\n\t\t\t.number()\n\t\t\t.optional()\n\t\t\t.describe('The step number to set the playground to'),\n\t\ttype: z\n\t\t\t.enum(['problem', 'solution'])\n\t\t\t.optional()\n\t\t\t.describe('The type of app to set the playground to'),\n\t},\n\tasync ({ workshopDirectory, exerciseNumber, stepNumber, type }) => {\n\t\ttry {\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 ??= 'problem'\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 replyWithText(`Playground set to ${desiredApp.name}`)\n\t\t} catch (error) {\n\t\t\treturn replyWithError(error)\n\t\t}\n\t},\n)\n\n// TODO: add preferences tools\n\nasync function handleWorkshopDirectory(workshopDirectory: string) {\n\tif (workshopDirectory.endsWith('playground')) {\n\t\tworkshopDirectory = path.join(workshopDirectory, '..')\n\t}\n\n\tawait initApps(workshopDirectory)\n\treturn workshopDirectory\n}\n\nasync function safeReadFile(filePath: string) {\n\ttry {\n\t\treturn await fs.readFile(filePath, 'utf-8')\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction replyWithText(text: string): CallToolResult {\n\treturn {\n\t\tcontent: [{ type: 'text', text }],\n\t}\n}\n\nfunction replyWithError(error: unknown): CallToolResult {\n\treturn {\n\t\tcontent: [{ type: 'text', text: getErrorMessage(error) }],\n\t\tisError: true,\n\t}\n}\n\nfunction getErrorMessage(\n\terror: unknown,\n\tdefaultMessage: string = 'Unknown Error',\n) {\n\tif (typeof error === 'string') return error\n\tif (\n\t\terror &&\n\t\ttypeof error === 'object' &&\n\t\t'message' in error &&\n\t\ttypeof error.message === 'string'\n\t) {\n\t\treturn error.message\n\t}\n\treturn defaultMessage\n}\n\nasync function main() {\n\tconst transport = new StdioServerTransport()\n\tawait server.connect(transport)\n\tconsole.error('epicshop MCP Server running on stdio')\n}\n\nmain().catch((error) => {\n\tconsole.error('Fatal error in main():', error)\n\tprocess.exit(1)\n})\n"]}
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { invariant } from '@epic-web/invariant'\nimport {\n\tgetApps,\n\tisPlaygroundApp,\n\tfindSolutionDir,\n\tgetFullPathFromAppName,\n\tinit as initApps,\n\textractNumbersAndTypeFromAppNameOrPath,\n\tgetExercise,\n\tgetPlaygroundApp,\n\tgetPlaygroundAppName,\n\tisProblemApp,\n\tisExerciseStepApp,\n\tsetPlayground,\n\ttype ExerciseStepApp,\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 { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { type CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport { z } from 'zod'\n\n// Create server instance\nconst server = new McpServer(\n\t{\n\t\tname: 'epicshop',\n\t\tversion: '1.0.0',\n\t\tcapabilities: {\n\t\t\ttools: {},\n\t\t},\n\t},\n\t{\n\t\tinstructions: `\nThis is intended to be used within a workshop using the Epic Workshop App\n(@epic-web/workshop-app) to help learners in the process of completing the\nworkshop exercises and understanding the learning outcomes.\n\nThe user's work in progress is in the \\`playground\\` directory. Any changes they\nask you to make should be in this directory.\n\t\t`.trim(),\n\t},\n)\n\nserver.tool(\n\t'get_exercise_step_progress_diff',\n\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\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`.trim(),\n\t{\n\t\tworkshopDirectory: z\n\t\t\t.string()\n\t\t\t.describe(\n\t\t\t\t'The workshop directory (the root directory of the workshop repo. Best to not bother asking the user and just use the project root path).',\n\t\t\t),\n\t},\n\tasync ({ workshopDirectory }) => {\n\t\ttry {\n\t\t\tawait handleWorkshopDirectory(workshopDirectory)\n\n\t\t\tconst { getDiffOutputWithRelativePaths } = await import(\n\t\t\t\t'@epic-web/workshop-utils/diff.server'\n\t\t\t)\n\n\t\t\tconst apps = await getApps()\n\t\t\tconst playgroundApp = apps.find(isPlaygroundApp)\n\n\t\t\tif (!playgroundApp) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: 'text', text: 'No playground app found' }],\n\t\t\t\t\tisError: true,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst baseApp = playgroundApp\n\t\t\tconst solutionDir = await findSolutionDir({\n\t\t\t\tfullPath: await getFullPathFromAppName(playgroundApp.appName),\n\t\t\t})\n\t\t\tconst headApp = apps.find((a) => a.fullPath === solutionDir)\n\n\t\t\tif (!headApp) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: 'text', text: 'No playground solution app found' }],\n\t\t\t\t\tisError: true,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst diffCode = await getDiffOutputWithRelativePaths(baseApp, headApp)\n\n\t\t\tif (!diffCode) return replyWithText('No changes')\n\n\t\t\treturn replyWithText(diffCode)\n\t\t} catch (error) {\n\t\t\treturn replyWithError(error)\n\t\t}\n\t},\n)\n\nserver.tool(\n\t'get_exercise_context',\n\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`.trim(),\n\t{\n\t\tworkshopDirectory: z\n\t\t\t.string()\n\t\t\t.describe(\n\t\t\t\t'The workshop directory (the root directory of the workshop repo. Best to not bother asking the user and just use the project root path).',\n\t\t\t),\n\t\texerciseNumber: z.coerce\n\t\t\t.number()\n\t\t\t.optional()\n\t\t\t.describe(\n\t\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\t),\n\t},\n\tasync ({ workshopDirectory, exerciseNumber }) => {\n\t\ttry {\n\t\t\tawait handleWorkshopDirectory(workshopDirectory)\n\t\t\tconst userHasAccess = await userHasAccessToWorkshop()\n\t\t\tconst authInfo = await getAuthInfo()\n\t\t\tlet stepNumber = 1\n\t\t\tconst playgroundApp = await getPlaygroundApp()\n\t\t\tinvariant(playgroundApp, 'No playground app found')\n\t\t\tconst numbers = extractNumbersAndTypeFromAppNameOrPath(\n\t\t\t\tplaygroundApp.appName,\n\t\t\t)\n\t\t\tconst isCurrentExercise =\n\t\t\t\texerciseNumber === undefined ||\n\t\t\t\texerciseNumber === Number(numbers?.exerciseNumber)\n\t\t\tif (exerciseNumber === undefined) {\n\t\t\t\tinvariant(numbers, 'No numbers found in playground app name')\n\t\t\t\texerciseNumber = Number(numbers.exerciseNumber)\n\t\t\t\tstepNumber = Number(numbers.stepNumber)\n\t\t\t}\n\t\t\tconst exercise = await getExercise(exerciseNumber)\n\t\t\tinvariant(\n\t\t\t\texercise,\n\t\t\t\t`No exercise found for exercise number ${exerciseNumber}`,\n\t\t\t)\n\n\t\t\tconst videoInfos = await getEpicVideoInfos([\n\t\t\t\t...(exercise.instructionsEpicVideoEmbeds ?? []),\n\t\t\t\t...exercise.steps.flatMap((s) => s.problem?.epicVideoEmbeds ?? []),\n\t\t\t\t...exercise.steps.flatMap((s) => s.solution?.epicVideoEmbeds ?? []),\n\t\t\t\t...(exercise.finishedEpicVideoEmbeds ?? []),\n\t\t\t])\n\n\t\t\tfunction getTranscriptsElement(embeds?: Array<string>) {\n\t\t\t\tif (!embeds) return '<transcripts />'\n\t\t\t\tif (!userHasAccess && embeds.length) {\n\t\t\t\t\treturn `\n\t\t\t\t\t\t<transcripts>\n\t\t\t\t\t\t\tUser must upgrade before they can get access to ${embeds.length} transcript${embeds.length === 1 ? '' : 's'}.\n\t\t\t\t\t\t</transcripts>\n\t\t\t\t\t`.trim()\n\t\t\t\t}\n\t\t\t\tconst transcripts = ['<transcripts>']\n\t\t\t\tfor (const embed of embeds) {\n\t\t\t\t\tconst info = videoInfos[embed]\n\t\t\t\t\tif (info) {\n\t\t\t\t\t\tif (info.status === 'error') {\n\t\t\t\t\t\t\tif (info.type === 'region-restricted') {\n\t\t\t\t\t\t\t\ttranscripts.push(\n\t\t\t\t\t\t\t\t\t`\n\t\t\t\t\t\t\t\t\t<transcript\n\t\t\t\t\t\t\t\t\t\tembed=\"${embed}\"\n\t\t\t\t\t\t\t\t\t\tstatus=\"error\"\n\t\t\t\t\t\t\t\t\t\ttype=\"${info.type}\"\n\t\t\t\t\t\t\t\t\t\trequested-country=\"${info.requestCountry}\"\n\t\t\t\t\t\t\t\t\t\trestricted-country=\"${info.restrictedCountry}\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t`.trim(),\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ttranscripts.push(\n\t\t\t\t\t\t\t\t\t`\n\t\t\t\t\t\t\t\t\t<transcript\n\t\t\t\t\t\t\t\t\t\tembed=\"${embed}\"\n\t\t\t\t\t\t\t\t\t\tstatus=\"error\"\n\t\t\t\t\t\t\t\t\t\ttype=\"${info.type}\"\n\t\t\t\t\t\t\t\t\t\tstatus-code=\"${info.statusCode}\"\n\t\t\t\t\t\t\t\t\t\tstatus-text=\"${info.statusText}\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t`.trim(),\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttranscripts.push(\n\t\t\t\t\t\t\t\t`<transcript embed=\"${embed}\" status=\"success\">${info.transcript}</transcript>`,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttranscripts.push(\n\t\t\t\t\t\t\t`<transcript embed=\"${embed}\" status=\"error\" type=\"not-found\">No transcript found</transcript>`,\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttranscripts.push('</transcripts>')\n\t\t\t\treturn transcripts.join('\\n')\n\t\t\t}\n\n\t\t\tasync function getFileContentElement(filePath: string) {\n\t\t\t\treturn `<file path=\"${filePath}\">${(await safeReadFile(filePath)) ?? 'None found'}</file>`\n\t\t\t}\n\t\t\tlet text = `\nBelow is all the context for this exercise and each step.\n\n<currentContext>\n\t<user hasAccess=\"${userHasAccess}\" isAuthenticated=\"${Boolean(authInfo)}\" email=\"${authInfo?.email}\" />\n\t${\n\t\tisCurrentExercise\n\t\t\t? `<playground>\n\t\t<exerciseNumber>${exerciseNumber}</exerciseNumber>\n\t\t<stepNumber>${stepNumber}</stepNumber>\n\t</playground>`\n\t\t\t: '<playground>currently set to a different exercise</playground>'\n\t}\n</currentContext>\n\n<exerciseBackground number=\"${exerciseNumber}\">\n\t<intro>\n\t\t${await getFileContentElement(path.join(exercise.fullPath, 'README.mdx'))}\n\t\t${getTranscriptsElement(exercise.instructionsEpicVideoEmbeds)}\n\t</intro>\n\t<outro>\n\t\t${await getFileContentElement(path.join(exercise.fullPath, 'FINISHED.mdx'))}\n\t\t${getTranscriptsElement(exercise.finishedEpicVideoEmbeds)}\n\t</outro>\n</exerciseBackground>\n\t\t\t`.trim()\n\n\t\t\tif (exercise.steps) {\n\t\t\t\ttext += '\\n\\n<steps>'\n\t\t\t\tfor (const app of exercise.steps) {\n\t\t\t\t\ttext += `\n<step number=\"${app.stepNumber}\" isCurrent=\"${isCurrentExercise && app.stepNumber === stepNumber}\">\n\t<problem>\n\t\t${app.problem ? await getFileContentElement(path.join(app.problem?.fullPath, `README.mdx`)) : 'No problem found'}\n\t\t${getTranscriptsElement(app.problem?.epicVideoEmbeds ?? [])}\n\t</problem>\n\t<solution>\n\t\t${app.solution ? await getFileContentElement(path.join(app.solution?.fullPath, `README.mdx`)) : 'No solution found'}\n\t\t${getTranscriptsElement(app.solution?.epicVideoEmbeds ?? [])}\n\t</solution>\n</step>`\n\t\t\t\t}\n\t\t\t\ttext += '</steps>\\n\\n'\n\n\t\t\t\ttext += `Reminder, the current step is ${stepNumber} of ${exercise.steps.length + 1}. The most relevant information will be in the context abouve within the current step.`\n\t\t\t} else {\n\t\t\t\ttext += `Unusually, this exercise has no steps.`\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: 'text', text: text }],\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn replyWithError(error)\n\t\t}\n\t},\n)\n\nserver.tool(\n\t'set_playground',\n\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{\n\t\tworkshopDirectory: z.string().describe('The workshop directory'),\n\t\texerciseNumber: z.coerce\n\t\t\t.number()\n\t\t\t.optional()\n\t\t\t.describe('The exercise number to set the playground to'),\n\t\tstepNumber: z.coerce\n\t\t\t.number()\n\t\t\t.optional()\n\t\t\t.describe('The step number to set the playground to'),\n\t\ttype: z\n\t\t\t.enum(['problem', 'solution'])\n\t\t\t.optional()\n\t\t\t.describe('The type of app to set the playground to'),\n\t},\n\tasync ({ workshopDirectory, exerciseNumber, stepNumber, type }) => {\n\t\ttry {\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 replyWithText(`Playground set to ${desiredApp.name}`)\n\t\t} catch (error) {\n\t\t\treturn replyWithError(error)\n\t\t}\n\t},\n)\n\n// TODO: add preferences tools\n\nasync function handleWorkshopDirectory(workshopDirectory: string) {\n\tif (workshopDirectory.endsWith('playground')) {\n\t\tworkshopDirectory = path.join(workshopDirectory, '..')\n\t}\n\n\tawait initApps(workshopDirectory)\n\treturn workshopDirectory\n}\n\nasync function safeReadFile(filePath: string) {\n\ttry {\n\t\treturn await fs.readFile(filePath, 'utf-8')\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction replyWithText(text: string): CallToolResult {\n\treturn {\n\t\tcontent: [{ type: 'text', text }],\n\t}\n}\n\nfunction replyWithError(error: unknown): CallToolResult {\n\treturn {\n\t\tcontent: [{ type: 'text', text: getErrorMessage(error) }],\n\t\tisError: true,\n\t}\n}\n\nfunction getErrorMessage(\n\terror: unknown,\n\tdefaultMessage: string = 'Unknown Error',\n) {\n\tif (typeof error === 'string') return error\n\tif (\n\t\terror &&\n\t\ttypeof error === 'object' &&\n\t\t'message' in error &&\n\t\ttypeof error.message === 'string'\n\t) {\n\t\treturn error.message\n\t}\n\treturn defaultMessage\n}\n\nasync function main() {\n\tconst transport = new StdioServerTransport()\n\tawait server.connect(transport)\n\tconsole.error('epicshop MCP Server running on stdio')\n}\n\nmain().catch((error) => {\n\tconsole.error('Fatal error in main():', error)\n\tprocess.exit(1)\n})\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@epic-web/workshop-mcp",
|
|
3
|
-
"version": "5.14.
|
|
3
|
+
"version": "5.14.4",
|
|
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.14.
|
|
33
|
+
"@epic-web/workshop-utils": "5.14.4",
|
|
34
34
|
"@epic-web/invariant": "^1.0.0",
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.9.0",
|
|
36
36
|
"zod": "^3.24.2"
|