index.ts•6.4 kB
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
// Import fetch for Node.js compatibility
import fetch from 'node-fetch';
import https from 'https';
// Create an agent that bypasses SSL verification for local development
const httpsAgent = new https.Agent({
rejectUnauthorized: false
});
// Server configuration
const server = new Server(
{
name: "frontendleap-challenge-generator",
version: "0.1.0",
}
);
// FrontendLeap API configuration
const FRONTENDLEAP_API_URL = process.env.FRONTENDLEAP_API_URL || 'https://frontendleap.com';
const FRONTENDLEAP_API_KEY = process.env.FRONTENDLEAP_API_KEY;
if (!FRONTENDLEAP_API_KEY) {
console.error("⚠️ FRONTENDLEAP_API_KEY environment variable is required");
process.exit(1);
}
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "create_challenge",
description: "Create a complete coding challenge with all content generated by Claude and save it to FrontendLeap",
inputSchema: {
type: "object",
properties: {
title: {
type: "string",
description: "The challenge title (e.g., 'Advanced CSS Flexbox Centering Challenge')"
},
description: {
type: "string",
description: "Brief description of what the challenge teaches"
},
explanation: {
type: "string",
description: "Detailed markdown explanation of the concept, including examples and learning objectives"
},
starter_code: {
type: "string",
description: "The initial code template that users start with - should be relevant to the challenge"
},
test_code: {
type: "string",
description: "JavaScript test code (using Jasmine) that validates the user's solution"
},
solution: {
type: "string",
description: "Optional markdown explanation of the solution approach and key concepts"
},
language: {
type: "string",
enum: ["javascript", "html", "css", "typescript"],
description: "Programming language for the challenge"
},
difficulty: {
type: "string",
enum: ["beginner", "intermediate", "advanced"],
description: "Challenge difficulty level"
}
},
required: ["title", "description", "explanation", "starter_code", "test_code", "language", "difficulty"]
}
}
]
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "create_challenge") {
try {
if (!args) {
throw new Error("Missing arguments for challenge creation");
}
const challengeData = args as {
title: string;
description: string;
explanation: string;
starter_code: string;
test_code: string;
solution?: string;
language: string;
difficulty: string;
};
console.error(`🚀 Creating challenge: ${challengeData.title} (${challengeData.difficulty}, ${challengeData.language})`);
const response = await fetch(`${FRONTENDLEAP_API_URL}/api/mcp/challenges/create`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${FRONTENDLEAP_API_KEY}`,
'User-Agent': 'FrontendLeap-MCP-Server/0.1.0'
},
body: JSON.stringify(challengeData),
// Bypass SSL verification for local development
agent: FRONTENDLEAP_API_URL.includes('.test') || FRONTENDLEAP_API_URL.includes('localhost') ? httpsAgent : undefined
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorText}`);
}
const data = await response.json() as {
success: boolean;
error?: string;
challenge_url: string;
challenge: {
id: number;
title: string;
slug: string;
difficulty: string;
language: string;
};
};
if (!data.success) {
throw new Error(data.error || 'Challenge generation failed');
}
console.error(`✅ Challenge created: ${data.challenge.title}`);
return {
content: [
{
type: "text",
text: `🎯 **Challenge Created Successfully!**
**${data.challenge.title}**
- **Difficulty:** ${challengeData.difficulty}
- **Language:** ${challengeData.language}
🔗 **Access your challenge here:** ${data.challenge_url}
Your custom coding challenge is ready! The challenge includes:
- Claude-generated explanation and learning objectives
- Contextually relevant starter code
- Comprehensive test cases
- Interactive Monaco code editor
The challenge is now live and ready for users to solve!`
}
]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
console.error(`❌ Error creating challenge: ${errorMessage}`);
return {
content: [
{
type: "text",
text: `❌ **Error creating challenge**
${errorMessage}
Please try again or check if the FrontendLeap API is accessible.`
}
],
isError: true
};
}
}
throw new Error(`Unknown tool: ${name}`);
});
// Start the server
async function main() {
try {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("🚀 FrontendLeap MCP server running on stdio");
} catch (error) {
console.error("Failed to start server:", error);
process.exit(1);
}
}
main().catch((error) => {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
console.error("Server error:", errorMessage);
process.exit(1);
});