@codewithdan/ai-repo-adventure-mcp
Version:
MCP server for AI-powered code repository exploration through interactive storytelling
162 lines (156 loc) ⢠6.18 kB
JavaScript
/**
* Choose Theme Tool
*
* Creates a complete themed adventure experience using advanced LLM prompt
* engineering and optional adventure.config.json guidance.
*/
import { z } from 'zod';
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
import { repoAnalyzer } from '@ai-repo-adventures/core/analyzer';
import { parseTheme, getAllThemes, validateTheme } from '@ai-repo-adventures/core/shared';
import { createProjectInfo } from '@ai-repo-adventures/core';
import { adventureManager } from '@ai-repo-adventures/core/adventure';
// Schema
const chooseThemeSchema = z.object({
theme: z.string().describe(`The story theme - use theme names or numbers: ${getAllThemes().map(t => `"${t.key}"`).join('/')} or ${getAllThemes().map(t => t.id).join('/')}`),
customTheme: z.object({
name: z.string().describe('Custom theme name (e.g., "Cyberpunk", "Pirate Adventure")'),
description: z.string().describe('Custom theme description explaining the style and setting'),
keywords: z.array(z.string()).describe('Array of keywords that define the theme vocabulary (e.g., ["cyber", "neon", "matrix", "digital"])')
}).optional().describe('Custom theme data - required only when theme is "custom"')
});
/**
* Validate and parse the theme input
*/
function validateAndParseTheme(themeInput) {
let validatedTheme;
try {
validatedTheme = validateTheme(themeInput);
}
catch (error) {
throw new McpError(ErrorCode.InvalidParams, error instanceof Error ? error.message : 'Invalid theme');
}
const selectedTheme = parseTheme(validatedTheme);
if (!selectedTheme) {
const allThemes = getAllThemes();
const validOptions = [...allThemes.map(t => `'${t.key}'`), ...allThemes.map(t => t.id.toString())];
throw new McpError(ErrorCode.InvalidParams, `Invalid theme: ${themeInput}. Please choose ${validOptions.join(', ')}.`);
}
return selectedTheme;
}
/**
* Validate custom theme data
*/
function validateCustomTheme(selectedTheme, customThemeData) {
if (selectedTheme !== 'custom')
return;
if (!customThemeData) {
// Provide helpful guidance for custom theme creation
const helpMessage = `
⨠**Creating a Custom Theme**
To create a custom themed adventure, please call choose_theme again with:
\`\`\`json
{
"theme": "custom",
"customTheme": {
"name": "Your Theme Name",
"description": "Description of your theme's style and setting",
"keywords": ["keyword1", "keyword2", "keyword3"]
}
}
\`\`\`
**Examples:**
š **Pirate Adventure:**
\`\`\`json
{
"theme": "custom",
"customTheme": {
"name": "Pirate Adventure",
"description": "A swashbuckling journey across the seven seas of code",
"keywords": ["pirate", "ship", "treasure", "ocean", "captain", "crew"]
}
}
\`\`\`
š **Cyberpunk:**
\`\`\`json
{
"theme": "custom",
"customTheme": {
"name": "Cyberpunk",
"description": "Navigate neon-lit digital streets where code is power",
"keywords": ["cyber", "neon", "hacker", "matrix", "digital", "tech"]
}
}
\`\`\`
šµļø **Detective Mystery:**
\`\`\`json
{
"theme": "custom",
"customTheme": {
"name": "Detective Mystery",
"description": "Solve coding mysteries as a detective investigating bugs",
"keywords": ["detective", "mystery", "clue", "investigate", "case", "evidence"]
}
}
\`\`\``;
throw new McpError(ErrorCode.InvalidParams, helpMessage);
}
if (!customThemeData.name?.trim()) {
throw new McpError(ErrorCode.InvalidParams, 'Custom theme name is required');
}
if (!customThemeData.description?.trim()) {
throw new McpError(ErrorCode.InvalidParams, 'Custom theme description is required');
}
if (!customThemeData.keywords || customThemeData.keywords.length === 0) {
throw new McpError(ErrorCode.InvalidParams, 'Custom theme keywords array is required (provide at least 3 keywords)');
}
}
/**
* Generate project info from repomix content
*/
async function generateProjectInfo() {
const projectPath = process.cwd();
const repomixContent = await repoAnalyzer.generateRepomixContext(projectPath);
const projectInfo = createProjectInfo(repomixContent);
return { projectPath, projectInfo };
}
// Tool Definition
export const chooseTheme = {
description: `Generate themed story and quests for selected adventure theme. Creates immersive narrative with codebase-specific learning objectives.`,
schema: chooseThemeSchema,
handler: async (args) => {
if (!adventureManager) {
throw new McpError(ErrorCode.InternalError, 'Adventure manager not initialized');
}
try {
const selectedTheme = validateAndParseTheme(args.theme);
validateCustomTheme(selectedTheme, args.customTheme);
// Check if we already have project info from start-adventure
let projectInfo = adventureManager.getProjectInfo();
let projectPath = adventureManager.getProjectPath();
// Only regenerate if not already initialized (e.g., if choose-theme is called directly)
if (!projectInfo || !projectPath) {
const generated = await generateProjectInfo();
projectPath = generated.projectPath;
projectInfo = generated.projectInfo;
}
// Ensure we have valid project info and path
if (!projectInfo || !projectPath) {
throw new McpError(ErrorCode.InternalError, 'Failed to generate project information');
}
const storyWithQuests = await adventureManager.initializeAdventure(projectInfo, selectedTheme, projectPath, args.customTheme);
return {
content: [
{
type: 'text',
text: storyWithQuests
}
]
};
}
catch (error) {
throw new McpError(ErrorCode.InternalError, `Failed to choose theme: ${error instanceof Error ? error.message : String(error)}`);
}
}
};
//# sourceMappingURL=choose-theme.js.map