@epic-web/workshop-mcp 5.15.0 → 5.16.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.
- package/dist/esm/cli.js +44 -0
- package/dist/esm/cli.js.map +1 -1
- package/package.json +2 -2
package/dist/esm/cli.js
CHANGED
|
@@ -25,6 +25,50 @@ The user's work in progress is in the \`playground\` directory. Any changes they
|
|
|
25
25
|
ask you to make should be in this directory.
|
|
26
26
|
`.trim(),
|
|
27
27
|
});
|
|
28
|
+
server.tool('get_diff_between_apps', `
|
|
29
|
+
Intended to give context about the changes between two apps.
|
|
30
|
+
|
|
31
|
+
The output is a git diff of the playground directory as BASE (their work in
|
|
32
|
+
progress) against the solution directory as HEAD (the final state they're trying
|
|
33
|
+
to achieve).
|
|
34
|
+
|
|
35
|
+
The output is formatted as a git diff.
|
|
36
|
+
|
|
37
|
+
App IDs are formatted as \`{exerciseNumber}.{stepNumber}.{type}\`.
|
|
38
|
+
|
|
39
|
+
If the user asks for the diff for 2.3, then use 02.03.problem for app1 and 02.03.solution for app2.
|
|
40
|
+
`, {
|
|
41
|
+
workshopDirectory: z.string().describe('The workshop directory'),
|
|
42
|
+
app1: z.string().describe('The ID of the first app'),
|
|
43
|
+
app2: z.string().describe('The ID of the second app'),
|
|
44
|
+
}, async ({ workshopDirectory, app1, app2 }) => {
|
|
45
|
+
try {
|
|
46
|
+
await handleWorkshopDirectory(workshopDirectory);
|
|
47
|
+
const { getDiffOutputWithRelativePaths } = await import('@epic-web/workshop-utils/diff.server');
|
|
48
|
+
const app1Name = extractNumbersAndTypeFromAppNameOrPath(app1);
|
|
49
|
+
const app2Name = extractNumbersAndTypeFromAppNameOrPath(app2);
|
|
50
|
+
const apps = await getApps();
|
|
51
|
+
const app1App = apps
|
|
52
|
+
.filter(isExerciseStepApp)
|
|
53
|
+
.find((a) => a.exerciseNumber === Number(app1Name?.exerciseNumber) &&
|
|
54
|
+
a.stepNumber === Number(app1Name?.stepNumber) &&
|
|
55
|
+
a.type === app1Name?.type);
|
|
56
|
+
const app2App = apps
|
|
57
|
+
.filter(isExerciseStepApp)
|
|
58
|
+
.find((a) => a.exerciseNumber === Number(app2Name?.exerciseNumber) &&
|
|
59
|
+
a.stepNumber === Number(app2Name?.stepNumber) &&
|
|
60
|
+
a.type === app2Name?.type);
|
|
61
|
+
invariant(app1App, `No app found for ${app1}`);
|
|
62
|
+
invariant(app2App, `No app found for ${app2}`);
|
|
63
|
+
const diffCode = await getDiffOutputWithRelativePaths(app1App, app2App);
|
|
64
|
+
if (!diffCode)
|
|
65
|
+
return replyWithText('No changes');
|
|
66
|
+
return replyWithText(diffCode);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return replyWithError(error);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
28
72
|
server.tool('get_exercise_step_progress_diff', `
|
|
29
73
|
Intended to help a student understand what work they still have to complete.
|
|
30
74
|
|
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\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"]}
|
|
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_diff_between_apps',\n\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`,\n\t{\n\t\tworkshopDirectory: z.string().describe('The workshop directory'),\n\t\tapp1: z.string().describe('The ID of the first app'),\n\t\tapp2: z.string().describe('The ID of the second app'),\n\t},\n\tasync ({ workshopDirectory, app1, app2 }) => {\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 app1Name = extractNumbersAndTypeFromAppNameOrPath(app1)\n\t\t\tconst app2Name = extractNumbersAndTypeFromAppNameOrPath(app2)\n\n\t\t\tconst apps = await getApps()\n\t\t\tconst app1App = apps\n\t\t\t\t.filter(isExerciseStepApp)\n\t\t\t\t.find(\n\t\t\t\t\t(a) =>\n\t\t\t\t\t\ta.exerciseNumber === Number(app1Name?.exerciseNumber) &&\n\t\t\t\t\t\ta.stepNumber === Number(app1Name?.stepNumber) &&\n\t\t\t\t\t\ta.type === app1Name?.type,\n\t\t\t\t)\n\t\t\tconst app2App = apps\n\t\t\t\t.filter(isExerciseStepApp)\n\t\t\t\t.find(\n\t\t\t\t\t(a) =>\n\t\t\t\t\t\ta.exerciseNumber === Number(app2Name?.exerciseNumber) &&\n\t\t\t\t\t\ta.stepNumber === Number(app2Name?.stepNumber) &&\n\t\t\t\t\t\ta.type === app2Name?.type,\n\t\t\t\t)\n\n\t\t\tinvariant(app1App, `No app found for ${app1}`)\n\t\t\tinvariant(app2App, `No app found for ${app2}`)\n\n\t\t\tconst diffCode = await getDiffOutputWithRelativePaths(app1App, app2App)\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_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.
|
|
3
|
+
"version": "5.16.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.
|
|
33
|
+
"@epic-web/workshop-utils": "5.16.0",
|
|
34
34
|
"@epic-web/invariant": "^1.0.0",
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.9.0",
|
|
36
36
|
"zod": "^3.24.2"
|