From 91101ee9671eb8489d7cb69d2b596d201a204cb1 Mon Sep 17 00:00:00 2001 From: Alex Andru Date: Tue, 10 Dec 2024 01:31:58 +0100 Subject: [PATCH 1/2] feat: add cli tool --- package-lock.json | 37 ++++-- package.json | 8 +- src/cli/{ => framework}/build.ts | 12 +- src/cli/index.ts | 49 ++++++++ src/cli/project/add-prompt.ts | 86 ++++++++++++++ src/cli/project/add-resource.ts | 73 ++++++++++++ src/cli/project/add-tool.ts | 77 +++++++++++++ src/cli/project/create.ts | 180 ++++++++++++++++++++++++++++++ src/cli/templates/readme.ts | 151 +++++++++++++++++++++++++ src/cli/utils/string-utils.ts | 6 + src/cli/utils/validate-project.ts | 22 ++++ 11 files changed, 684 insertions(+), 17 deletions(-) rename src/cli/{ => framework}/build.ts (89%) create mode 100644 src/cli/index.ts create mode 100644 src/cli/project/add-prompt.ts create mode 100644 src/cli/project/add-resource.ts create mode 100644 src/cli/project/add-tool.ts create mode 100644 src/cli/project/create.ts create mode 100644 src/cli/templates/readme.ts create mode 100644 src/cli/utils/string-utils.ts create mode 100644 src/cli/utils/validate-project.ts diff --git a/package-lock.json b/package-lock.json index dd09a4b..2e1d9e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,22 @@ { "name": "mcp-framework", - "version": "0.1.0", + "version": "0.1.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mcp-framework", - "version": "0.1.0", + "version": "0.1.8", "dependencies": { + "@types/prompts": "^2.4.9", + "commander": "^12.1.0", + "prompts": "^2.4.2", "zod": "^3.23.8" }, + "bin": { + "mcp": "dist/cli/index.js", + "mcp-build": "dist/cli/framework/build.js" + }, "devDependencies": { "@modelcontextprotocol/sdk": "^0.6.0", "@types/jest": "^29.5.12", @@ -961,11 +968,19 @@ "version": "20.17.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.9.tgz", "integrity": "sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw==", - "dev": true, "dependencies": { "undici-types": "~6.19.2" } }, + "node_modules/@types/prompts": { + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.4.9.tgz", + "integrity": "sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==", + "dependencies": { + "@types/node": "*", + "kleur": "^3.0.3" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -1392,6 +1407,14 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "engines": { + "node": ">=18" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2707,7 +2730,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, "engines": { "node": ">=6" } @@ -3076,7 +3098,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -3229,8 +3250,7 @@ "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" }, "node_modules/slash": { "version": "3.0.0", @@ -3518,8 +3538,7 @@ "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, "node_modules/unpipe": { "version": "1.0.0", diff --git a/package.json b/package.json index 3db11bc..06a6e51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-framework", - "version": "0.1.8", + "version": "0.1.9", "description": "Framework for building Model Context Protocol (MCP) servers in Typescript", "type": "module", "author": "Alex Andru ", @@ -16,7 +16,8 @@ "dist" ], "bin": { - "mcp-build": "./dist/cli/build.js" + "mcp": "./dist/cli/index.js", + "mcp-build": "./dist/cli/framework/build.js" }, "scripts": { "build": "tsc", @@ -36,6 +37,9 @@ "@modelcontextprotocol/sdk": "^0.6.0" }, "dependencies": { + "@types/prompts": "^2.4.9", + "commander": "^12.1.0", + "prompts": "^2.4.2", "zod": "^3.23.8" }, "devDependencies": { diff --git a/src/cli/build.ts b/src/cli/framework/build.ts similarity index 89% rename from src/cli/build.ts rename to src/cli/framework/build.ts index 3ae7d00..bb1ae32 100644 --- a/src/cli/build.ts +++ b/src/cli/framework/build.ts @@ -3,6 +3,12 @@ import { spawnSync } from "child_process"; import { readFileSync, writeFileSync } from "fs"; import { join } from "path"; +export function buildFramework() { + runTsc(); + addShebang(); + console.log("MCP Build complete"); +} + function runTsc() { const tscPath = join(process.cwd(), "node_modules", ".bin", "tsc"); const tsc = spawnSync(tscPath, [], { @@ -30,9 +36,3 @@ function addShebang() { process.exit(1); } } - -runTsc(); - -addShebang(); - -console.log("MCP Build complete"); diff --git a/src/cli/index.ts b/src/cli/index.ts new file mode 100644 index 0000000..2c93d6b --- /dev/null +++ b/src/cli/index.ts @@ -0,0 +1,49 @@ +#!/usr/bin/env node +import { Command } from "commander"; +import { createProject } from "./project/create.js"; +import { addTool } from "./project/add-tool.js"; +import { addPrompt } from "./project/add-prompt.js"; +import { addResource } from "./project/add-resource.js"; +import { buildFramework } from "./framework/build.js"; + +const program = new Command(); + +program + .name("mcp") + .description("CLI for managing MCP server projects") + .version("0.1.8"); + +program + .command("build-framework", { hidden: true }) + .description("Build the MCP framework") + .action(buildFramework); + +program + .command("create") + .description("Create a new MCP server project") + .argument("[name]", "project name") + .action(createProject); + +program + .command("add") + .description("Add a new component to your MCP server") + .addCommand( + new Command("tool") + .description("Add a new tool") + .argument("[name]", "tool name") + .action(addTool) + ) + .addCommand( + new Command("prompt") + .description("Add a new prompt") + .argument("[name]", "prompt name") + .action(addPrompt) + ) + .addCommand( + new Command("resource") + .description("Add a new resource") + .argument("[name]", "resource name") + .action(addResource) + ); + +program.parse(); diff --git a/src/cli/project/add-prompt.ts b/src/cli/project/add-prompt.ts new file mode 100644 index 0000000..1c5dd39 --- /dev/null +++ b/src/cli/project/add-prompt.ts @@ -0,0 +1,86 @@ +import { writeFile, mkdir } from "fs/promises"; +import { join } from "path"; +import prompts from "prompts"; +import { toPascalCase } from "../utils/string-utils.js"; +import { validateMCPProject } from "../utils/validate-project.js"; + +export async function addPrompt(name?: string) { + await validateMCPProject(); + + let promptName = name; + if (!promptName) { + const response = await prompts([ + { + type: "text", + name: "name", + message: "What is the name of your prompt?", + validate: (value: string) => + /^[a-z0-9-]+$/.test(value) + ? true + : "Prompt name can only contain lowercase letters, numbers, and hyphens", + }, + ]); + + if (!response.name) { + console.log("Prompt creation cancelled"); + process.exit(1); + } + + promptName = response.name; + } + + if (!promptName) { + throw new Error("Prompt name is required"); + } + + const className = toPascalCase(promptName); + const fileName = `${className}Prompt.ts`; + const promptsDir = join(process.cwd(), "src/prompts"); + + try { + await mkdir(promptsDir, { recursive: true }); + + const promptContent = `import { MCPPrompt } from "mcp-framework"; +import { z } from "zod"; + +interface ${className}Input { + message: string; +} + +class ${className}Prompt extends MCPPrompt<${className}Input> { + name = "${promptName}"; + description = "${className} prompt description"; + + schema = { + message: { + type: z.string(), + description: "Message to process", + required: true, + }, + }; + + async generateMessages({ message }: ${className}Input) { + return [ + { + role: "user", + content: { + type: "text", + text: message, + }, + }, + ]; + } +} + +export default ${className}Prompt;`; + + await writeFile(join(promptsDir, fileName), promptContent); + + console.log( + `Prompt ${promptName} created successfully at src/prompts/${fileName}` + ); + } catch (error) { + console.error("Error creating prompt:", error); + process.exit(1); + } +} diff --git a/src/cli/project/add-resource.ts b/src/cli/project/add-resource.ts new file mode 100644 index 0000000..d5d2316 --- /dev/null +++ b/src/cli/project/add-resource.ts @@ -0,0 +1,73 @@ +import { writeFile, mkdir } from "fs/promises"; +import { join } from "path"; +import prompts from "prompts"; +import { validateMCPProject } from "../utils/validate-project.js"; +import { toPascalCase } from "../utils/string-utils.js"; + +export async function addResource(name?: string) { + await validateMCPProject(); + + let resourceName = name; + if (!resourceName) { + const response = await prompts([ + { + type: "text", + name: "name", + message: "What is the name of your resource?", + validate: (value: string) => + /^[a-z0-9-]+$/.test(value) + ? true + : "Resource name can only contain lowercase letters, numbers, and hyphens", + }, + ]); + + if (!response.name) { + console.log("Resource creation cancelled"); + process.exit(1); + } + + resourceName = response.name; + } + + if (!resourceName) { + throw new Error("Resource name is required"); + } + + const className = toPascalCase(resourceName); + const fileName = `${className}Resource.ts`; + const resourcesDir = join(process.cwd(), "src/resources"); + + try { + await mkdir(resourcesDir, { recursive: true }); + + const resourceContent = `import { MCPResource, ResourceContent } from "mcp-framework"; + +class ${className}Resource extends MCPResource { + uri = "resource://${resourceName}"; + name = "${className}"; + description = "${className} resource description"; + mimeType = "application/json"; + + async read(): Promise { + return [ + { + uri: this.uri, + mimeType: this.mimeType, + text: JSON.stringify({ message: "Hello from ${className} resource" }), + }, + ]; + } +} + +export default ${className}Resource;`; + + await writeFile(join(resourcesDir, fileName), resourceContent); + + console.log( + `Resource ${resourceName} created successfully at src/resources/${fileName}` + ); + } catch (error) { + console.error("Error creating resource:", error); + process.exit(1); + } +} diff --git a/src/cli/project/add-tool.ts b/src/cli/project/add-tool.ts new file mode 100644 index 0000000..638aeb4 --- /dev/null +++ b/src/cli/project/add-tool.ts @@ -0,0 +1,77 @@ +import { writeFile, mkdir } from "fs/promises"; +import { join } from "path"; +import prompts from "prompts"; +import { validateMCPProject } from "../utils/validate-project.js"; +import { toPascalCase } from "../utils/string-utils.js"; + +export async function addTool(name?: string) { + await validateMCPProject(); + + let toolName = name; + if (!toolName) { + const response = await prompts([ + { + type: "text", + name: "name", + message: "What is the name of your tool?", + validate: (value: string) => + /^[a-z0-9-]+$/.test(value) + ? true + : "Tool name can only contain lowercase letters, numbers, and hyphens", + }, + ]); + + if (!response.name) { + console.log("Tool creation cancelled"); + process.exit(1); + } + + toolName = response.name; + } + + if (!toolName) { + throw new Error("Tool name is required"); + } + + const className = toPascalCase(toolName); + const fileName = `${className}Tool.ts`; + const toolsDir = join(process.cwd(), "src/tools"); + + try { + await mkdir(toolsDir, { recursive: true }); + + const toolContent = `import { MCPTool } from "mcp-framework"; +import { z } from "zod"; + +interface ${className}Input { + message: string; +} + +class ${className}Tool extends MCPTool<${className}Input> { + name = "${toolName}"; + description = "${className} tool description"; + + schema = { + message: { + type: z.string(), + description: "Message to process", + }, + }; + + async execute(input: ${className}Input) { + return \`Processed: \${input.message}\`; + } +} + +export default ${className}Tool;`; + + await writeFile(join(toolsDir, fileName), toolContent); + + console.log( + `Tool ${toolName} created successfully at src/tools/${fileName}` + ); + } catch (error) { + console.error("Error creating tool:", error); + process.exit(1); + } +} diff --git a/src/cli/project/create.ts b/src/cli/project/create.ts new file mode 100644 index 0000000..a9a2d0c --- /dev/null +++ b/src/cli/project/create.ts @@ -0,0 +1,180 @@ +import { spawnSync } from "child_process"; +import { mkdir, writeFile } from "fs/promises"; +import { join } from "path"; +import prompts from "prompts"; +import { generateReadme } from "../templates/readme.js"; + +export async function createProject(name?: string) { + let projectName: string; + + if (!name) { + const response = await prompts([ + { + type: "text", + name: "projectName", + message: "What is the name of your MCP server project?", + validate: (value: string) => + /^[a-z0-9-]+$/.test(value) + ? true + : "Project name can only contain lowercase letters, numbers, and hyphens", + }, + ]); + + if (!response.projectName) { + console.log("Project creation cancelled"); + process.exit(1); + } + + projectName = response.projectName as string; + } else { + projectName = name; + } + + if (!projectName) { + throw new Error("Project name is required"); + } + + const projectDir = join(process.cwd(), projectName); + const srcDir = join(projectDir, "src"); + const toolsDir = join(srcDir, "tools"); + + try { + console.log("Creating project structure..."); + await mkdir(projectDir); + await mkdir(srcDir); + await mkdir(toolsDir); + + const packageJson = { + name: projectName, + version: "0.0.1", + description: `${projectName} MCP server`, + type: "module", + bin: { + [projectName]: "./dist/index.js", + }, + files: ["dist"], + scripts: { + build: "mcp-build", + prepare: "npm run build", + watch: "tsc --watch", + }, + dependencies: { + "mcp-framework": "^0.1.8", + }, + devDependencies: { + "@types/node": "^20.11.24", + typescript: "^5.3.3", + }, + }; + + const tsconfig = { + compilerOptions: { + target: "ESNext", + module: "ESNext", + moduleResolution: "node", + outDir: "./dist", + rootDir: "./src", + strict: true, + esModuleInterop: true, + skipLibCheck: true, + forceConsistentCasingInFileNames: true, + }, + include: ["src/**/*"], + exclude: ["node_modules"], + }; + + const indexTs = `import { MCPServer } from "mcp-framework"; + +const server = new MCPServer(); + +server.start().catch((error) => { + console.error("Server error:", error); + process.exit(1); +});`; + + const exampleToolTs = `import { MCPTool } from "mcp-framework"; +import { z } from "zod"; + +interface ExampleInput { + message: string; +} + +class ExampleTool extends MCPTool { + name = "example_tool"; + description = "An example tool that processes messages"; + + schema = { + message: { + type: z.string(), + description: "Message to process", + }, + }; + + async execute(input: ExampleInput) { + return \`Processed: \${input.message}\`; + } +} + +export default ExampleTool;`; + + console.log("Creating project files..."); + await Promise.all([ + writeFile( + join(projectDir, "package.json"), + JSON.stringify(packageJson, null, 2) + ), + writeFile( + join(projectDir, "tsconfig.json"), + JSON.stringify(tsconfig, null, 2) + ), + writeFile(join(projectDir, "README.md"), generateReadme(projectName)), + writeFile(join(srcDir, "index.ts"), indexTs), + writeFile(join(toolsDir, "ExampleTool.ts"), exampleToolTs), + ]); + + console.log("Initializing git repository..."); + const gitInit = spawnSync("git", ["init"], { + cwd: projectDir, + stdio: "inherit", + shell: true, + }); + + if (gitInit.status !== 0) { + throw new Error("Failed to initialize git repository"); + } + + console.log("Installing dependencies..."); + const npmInstall = spawnSync("npm", ["install"], { + cwd: projectDir, + stdio: "inherit", + shell: true, + }); + + if (npmInstall.status !== 0) { + throw new Error("Failed to install dependencies"); + } + + console.log("Building project..."); + const npmBuild = spawnSync("npm", ["run", "build"], { + cwd: projectDir, + stdio: "inherit", + shell: true, + }); + + if (npmBuild.status !== 0) { + throw new Error("Failed to build project"); + } + + console.log(` +Project ${projectName} created and built successfully! + +You can now: +1. cd ${projectName} +2. Add more tools using: + mcp add tool + `); + } catch (error) { + console.error("Error creating project:", error); + process.exit(1); + } +} diff --git a/src/cli/templates/readme.ts b/src/cli/templates/readme.ts new file mode 100644 index 0000000..89ce031 --- /dev/null +++ b/src/cli/templates/readme.ts @@ -0,0 +1,151 @@ +export function generateReadme(projectName: string): string { + return `# ${projectName} + +A Model Context Protocol (MCP) server built with mcp-framework. + +## Quick Start + +\`\`\`bash +# Install dependencies +npm install + +# Build the project +npm run build + +\`\`\` + +## Project Structure + +\`\`\` +${projectName}/ +├── src/ +│ ├── tools/ # MCP Tools +│ │ └── ExampleTool.ts +│ └── index.ts # Server entry point +├── package.json +└── tsconfig.json +\`\`\` + +## Adding Components + +The project comes with an example tool in \`src/tools/ExampleTool.ts\`. You can add more tools using the CLI: + +\`\`\`bash +# Add a new tool +mcp add tool my-tool + +# Example tools you might create: +mcp add tool data-processor +mcp add tool api-client +mcp add tool file-handler +\`\`\` + +## Tool Development + +Example tool structure: + +\`\`\`typescript +import { MCPTool } from "mcp-framework"; +import { z } from "zod"; + +interface MyToolInput { + message: string; +} + +class MyTool extends MCPTool { + name = "my_tool"; + description = "Describes what your tool does"; + + schema = { + message: { + type: z.string(), + description: "Description of this input parameter", + }, + }; + + async execute(input: MyToolInput) { + // Your tool logic here + return \`Processed: \${input.message}\`; + } +} + +export default MyTool; +\`\`\` + +## Publishing to npm + +1. Update your package.json: + - Ensure \`name\` is unique and follows npm naming conventions + - Set appropriate \`version\` + - Add \`description\`, \`author\`, \`license\`, etc. + - Check \`bin\` points to the correct entry file + +2. Build and test locally: + \`\`\`bash + npm run build + npm link + ${projectName} # Test your CLI locally + \`\`\` + +3. Login to npm (create account if necessary): + \`\`\`bash + npm login + \`\`\` + +4. Publish your package: + \`\`\`bash + npm publish + \`\`\` + +After publishing, users can add it to their claude desktop client (read below) or run it with npx +\`\`\` + +## Using with Claude Desktop + +### Local Development + +Add this configuration to your Claude Desktop config file: + +**MacOS**: \`~/Library/Application Support/Claude/claude_desktop_config.json\` +**Windows**: \`%APPDATA%/Claude/claude_desktop_config.json\` + +\`\`\`json +{ + "mcpServers": { + "${projectName}": { + "command": "/absolute/path/to/${projectName}/dist/index.js" + } + } +} +\`\`\` + +### After Publishing + +Add this configuration to your Claude Desktop config file: + +**MacOS**: \`~/Library/Application Support/Claude/claude_desktop_config.json\` +**Windows**: \`%APPDATA%/Claude/claude_desktop_config.json\` + +\`\`\`json +{ + "mcpServers": { + "${projectName}": { + "command": "npx", + "args": ["${projectName}"] + } + } +} +\`\`\` + +## Building and Testing + +1. Make changes to your tools +2. Run \`npm run build\` to compile +3. The server will automatically load your tools on startup + +## Learn More + +- [MCP Framework Documentation](https://github.com/your-org/mcp-framework) +- [Model Context Protocol Specification](https://github.com/anthropics/model-context-protocol) +`; +} diff --git a/src/cli/utils/string-utils.ts b/src/cli/utils/string-utils.ts new file mode 100644 index 0000000..481f363 --- /dev/null +++ b/src/cli/utils/string-utils.ts @@ -0,0 +1,6 @@ +export function toPascalCase(str: string): string { + return str + .split(/[-_]/) + .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(""); +} diff --git a/src/cli/utils/validate-project.ts b/src/cli/utils/validate-project.ts new file mode 100644 index 0000000..7265699 --- /dev/null +++ b/src/cli/utils/validate-project.ts @@ -0,0 +1,22 @@ +import { access } from "fs/promises"; +import { join } from "path"; + +export async function validateMCPProject() { + try { + const packageJsonPath = join(process.cwd(), "package.json"); + await access(packageJsonPath); + + const package_json = ( + await import(packageJsonPath, { assert: { type: "json" } }) + ).default; + + if (!package_json.dependencies?.["mcp-framework"]) { + throw new Error( + "This directory is not an MCP project (mcp-framework not found in dependencies)" + ); + } + } catch (error) { + console.error("Error: Must be run from an MCP project directory"); + process.exit(1); + } +} From 752024ed92213c3c27a2b9a5a702f823e835e167 Mon Sep 17 00:00:00 2001 From: Alex Andru Date: Tue, 10 Dec 2024 01:38:56 +0100 Subject: [PATCH 2/2] feat: add readme --- README.md | 105 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 86 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 9025ba5..6f49561 100644 --- a/README.md +++ b/README.md @@ -7,31 +7,94 @@ Get started fast with mcp-framework ⚡⚡⚡ ## Features - 🛠️ Automatic directory-based discovery and loading for tools, prompts, and resources -- 🏗️ Powerful abstractions +- 🏗️ Powerful abstractions with full type safety - 🚀 Simple server setup and configuration +- 📦 CLI for rapid development and project scaffolding -## Installation +## Quick Start + +### Using the CLI (Recommended) + +```bash +# Install the framework globally +npm install -g mcp-framework + +# Create a new MCP server project +mcp create my-mcp-server + +# Navigate to your project +cd my-mcp-server + +# Your server is ready to use! +``` + +### Manual Installation ```bash npm install mcp-framework ``` -## Quick Start +## CLI Usage -### 1. Create your MCP server: +The framework provides a powerful CLI for managing your MCP server projects: -```typescript -import { MCPServer } from "mcp-framework"; +### Project Creation + +```bash +# Create a new project +mcp create +``` -const server = new MCPServer(); +### Adding a Tool -server.start().catch((error) => { - console.error("Server failed to start:", error); - process.exit(1); -}); +```bash +# Add a new tool +mcp add tool price-fetcher ``` -### 2. Create a Tool: +### Adding a Prompt + +```bash +# Add a new prompt +mcp add prompt price-analysis +``` + +### Adding a Resource + +```bash +# Add a new prompt +mcp add resource market-data +``` + +## Development Workflow + +1. Create your project: + +```bash + mcp create my-mcp-server + cd my-mcp-server +``` + +2. Add tools as needed: + + ```bash + mcp add tool data-fetcher + mcp add tool data-processor + mcp add tool report-generator + ``` + +3. Build and run: + ```bash + npm run build + # or + npm run watch # for development + ``` + +## Components Overview + +### 1. Tools (Main Component) + +Tools are the primary way to extend an LLM's capabilities. Each tool should perform a specific function: ```typescript import { MCPTool } from "mcp-framework"; @@ -60,7 +123,9 @@ class ExampleTool extends MCPTool { export default ExampleTool; ``` -### 3. Create a Prompt: +### 2. Prompts (Optional) + +Prompts help structure conversations with Claude: ```typescript import { MCPPrompt } from "mcp-framework"; @@ -104,7 +169,9 @@ class GreetingPrompt extends MCPPrompt { export default GreetingPrompt; ``` -### 4. Create a Resource: +### 3. Resources (Optional) + +Resources provide data access capabilities: ```typescript import { MCPResource, ResourceContent } from "mcp-framework"; @@ -139,11 +206,11 @@ export default ConfigResource; ``` your-project/ ├── src/ -│ ├── tools/ # Tool implementations +│ ├── tools/ # Tool implementations (Required) │ │ └── ExampleTool.ts -│ ├── prompts/ # Prompt implementations +│ ├── prompts/ # Prompt implementations (Optional) │ │ └── GreetingPrompt.ts -│ ├── resources/ # Resource implementations +│ ├── resources/ # Resource implementations (Optional) │ │ └── ConfigResource.ts │ └── index.ts ├── package.json @@ -155,8 +222,8 @@ your-project/ The framework automatically discovers and loads: - Tools from the `src/tools` directory -- Prompts from the `src/prompts` directory -- Resources from the `src/resources` directory +- Prompts from the `src/prompts` directory (if present) +- Resources from the `src/resources` directory (if present) Each feature should be in its own file and export a default class that extends the appropriate base class: