create-mcp-forge 0.1.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.
Files changed (31) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +370 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +160 -0
  5. package/dist/templates/empty/README.md +16 -0
  6. package/dist/templates/empty/package.json +17 -0
  7. package/dist/templates/empty/src/index.ts +26 -0
  8. package/dist/templates/empty/tests/index.test.ts +9 -0
  9. package/dist/templates/empty/tsconfig.json +12 -0
  10. package/dist/templates/filesystem/.env.example +1 -0
  11. package/dist/templates/filesystem/README.md +37 -0
  12. package/dist/templates/filesystem/package.json +18 -0
  13. package/dist/templates/filesystem/src/index.ts +56 -0
  14. package/dist/templates/filesystem/src/tools/filesystem.ts +52 -0
  15. package/dist/templates/filesystem/tests/filesystem.test.ts +12 -0
  16. package/dist/templates/filesystem/tsconfig.json +12 -0
  17. package/dist/templates/postgresql/.env.example +1 -0
  18. package/dist/templates/postgresql/README.md +33 -0
  19. package/dist/templates/postgresql/package.json +19 -0
  20. package/dist/templates/postgresql/src/index.ts +56 -0
  21. package/dist/templates/postgresql/src/tools/database.ts +49 -0
  22. package/dist/templates/postgresql/tests/database.test.ts +32 -0
  23. package/dist/templates/postgresql/tsconfig.json +12 -0
  24. package/dist/templates/rest-api/.env.example +2 -0
  25. package/dist/templates/rest-api/README.md +32 -0
  26. package/dist/templates/rest-api/package.json +19 -0
  27. package/dist/templates/rest-api/src/index.ts +52 -0
  28. package/dist/templates/rest-api/src/tools/api.ts +66 -0
  29. package/dist/templates/rest-api/tests/api.test.ts +32 -0
  30. package/dist/templates/rest-api/tsconfig.json +12 -0
  31. package/package.json +49 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hiyilmaz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,370 @@
1
+ # mcp-forge
2
+
3
+ ⚡ **The fastest way to build MCP servers**
4
+
5
+ A powerful CLI tool for scaffolding [Model Context Protocol](https://modelcontextprotocol.io) servers with production-ready templates.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/mcp-forge.svg)](https://www.npmjs.com/package/mcp-forge)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ ## Features
11
+
12
+ - 🚀 **Instant Setup** - Create production-ready MCP servers in seconds
13
+ - 📦 **Multiple Templates** - PostgreSQL, REST API, FileSystem, or start from scratch
14
+ - 🔧 **TypeScript First** - Full type safety and modern tooling
15
+ - ✅ **Test Ready** - Pre-configured testing with Vitest
16
+ - 🎯 **Claude Desktop Integration** - Automatic registration with Claude Desktop
17
+ - 🛠️ **Best Practices** - Error handling, validation, and security built-in
18
+
19
+ ## Quick Start
20
+
21
+ ```bash
22
+ npx mcp-forge init my-server
23
+ ```
24
+
25
+ Follow the interactive prompts to:
26
+
27
+ 1. Choose a template (empty, postgresql, rest-api, filesystem)
28
+ 2. Select language (TypeScript)
29
+ 3. Automatically register with Claude Desktop
30
+
31
+ Your MCP server is ready! 🎉
32
+
33
+ ## Installation
34
+
35
+ ### Global Installation (Optional)
36
+
37
+ ```bash
38
+ npm install -g mcp-forge
39
+ mcp-forge init my-server
40
+ ```
41
+
42
+ ### One-Time Use (Recommended)
43
+
44
+ ```bash
45
+ npx mcp-forge init my-server
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ ### Interactive Mode
51
+
52
+ ```bash
53
+ npx mcp-forge init my-server
54
+ ```
55
+
56
+ The CLI will guide you through:
57
+
58
+ - Template selection
59
+ - Language preference
60
+ - Claude Desktop registration
61
+
62
+ ### Non-Interactive Mode
63
+
64
+ ```bash
65
+ npx mcp-forge init my-server \
66
+ --template postgresql \
67
+ --language typescript \
68
+ --no-register
69
+ ```
70
+
71
+ ### CLI Options
72
+
73
+ ```bash
74
+ mcp-forge init <project-name> [options]
75
+
76
+ Options:
77
+ -t, --template <name> Template to use (empty|postgresql|rest-api|filesystem)
78
+ -l, --language <lang> Language to use (typescript)
79
+ --no-register Skip Claude Desktop registration
80
+ -h, --help Display help
81
+ ```
82
+
83
+ ## Templates
84
+
85
+ ### 🗂️ Empty Template
86
+
87
+ Minimal MCP server with basic structure. Perfect for custom implementations.
88
+
89
+ ```bash
90
+ npx mcp-forge init my-server --template empty
91
+ ```
92
+
93
+ **Includes:**
94
+
95
+ - Basic MCP server setup
96
+ - TypeScript configuration
97
+ - Test scaffolding
98
+
99
+ ---
100
+
101
+ ### 🐘 PostgreSQL Template
102
+
103
+ Full-featured database MCP server with connection pooling and query tools.
104
+
105
+ ```bash
106
+ npx mcp-forge init my-db-server --template postgresql
107
+ ```
108
+
109
+ **Tools Included:**
110
+
111
+ - `query_database` - Execute raw SQL queries
112
+ - `list_tables` - List all database tables
113
+ - `describe_table` - Get table schema information
114
+
115
+ **Features:**
116
+
117
+ - Connection pooling with `pg`
118
+ - Environment-based configuration
119
+ - Parameterized queries for security
120
+ - Comprehensive error handling
121
+
122
+ ---
123
+
124
+ ### 🌐 REST API Template
125
+
126
+ HTTP client MCP server for interacting with REST APIs.
127
+
128
+ ```bash
129
+ npx mcp-forge init my-api-server --template rest-api
130
+ ```
131
+
132
+ **Tools Included:**
133
+
134
+ - `fetch_api` - Make HTTP requests (GET, POST, PUT, DELETE)
135
+ - `get_api_status` - Check API health
136
+
137
+ **Features:**
138
+
139
+ - Built-in HTTP client
140
+ - Request/response validation
141
+ - Timeout handling
142
+ - Error recovery
143
+
144
+ ---
145
+
146
+ ### 📁 FileSystem Template
147
+
148
+ Secure file operations MCP server with path traversal protection.
149
+
150
+ ```bash
151
+ npx mcp-forge init my-fs-server --template filesystem
152
+ ```
153
+
154
+ **Tools Included:**
155
+
156
+ - `read_file` - Read file contents
157
+ - `write_file` - Write to files
158
+ - `list_directory` - List directory contents
159
+
160
+ **Features:**
161
+
162
+ - Path traversal protection
163
+ - Configurable allowed directories
164
+ - Safe file operations
165
+ - Comprehensive validation
166
+
167
+ ## Project Structure
168
+
169
+ ```
170
+ my-server/
171
+ ├── src/
172
+ │ ├── index.ts # Main server entry point
173
+ │ └── tools/ # Tool implementations
174
+ │ └── *.ts
175
+ ├── tests/
176
+ │ └── *.test.ts # Test files
177
+ ├── .env.example # Environment template
178
+ ├── package.json
179
+ ├── tsconfig.json
180
+ └── README.md
181
+ ```
182
+
183
+ ## Development Workflow
184
+
185
+ ### 1. Create Your Server
186
+
187
+ ```bash
188
+ npx mcp-forge init my-server --template postgresql
189
+ cd my-server
190
+ ```
191
+
192
+ ### 2. Configure Environment
193
+
194
+ ```bash
195
+ cp .env.example .env
196
+ # Edit .env with your configuration
197
+ ```
198
+
199
+ ### 3. Install Dependencies
200
+
201
+ ```bash
202
+ npm install
203
+ ```
204
+
205
+ ### 4. Start Development
206
+
207
+ ```bash
208
+ npm run dev
209
+ ```
210
+
211
+ ### 5. Run Tests
212
+
213
+ ```bash
214
+ npm test
215
+ ```
216
+
217
+ ### 6. Build for Production
218
+
219
+ ```bash
220
+ npm run build
221
+ ```
222
+
223
+ ## Claude Desktop Integration
224
+
225
+ mcp-forge automatically registers your server with Claude Desktop during initialization.
226
+
227
+ ### Manual Registration
228
+
229
+ If you skipped registration or need to register later:
230
+
231
+ 1. Open Claude Desktop configuration:
232
+ - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
233
+ - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
234
+
235
+ 2. Add your server:
236
+
237
+ ```json
238
+ {
239
+ "mcpServers": {
240
+ "my-server": {
241
+ "command": "node",
242
+ "args": ["/absolute/path/to/my-server/dist/index.js"]
243
+ }
244
+ }
245
+ }
246
+ ```
247
+
248
+ 3. Restart Claude Desktop
249
+
250
+ ## Testing
251
+
252
+ All templates include pre-configured testing with Vitest.
253
+
254
+ ```bash
255
+ # Run tests once
256
+ npm test
257
+
258
+ # Watch mode
259
+ npm run test:watch
260
+ ```
261
+
262
+ ### Example Test
263
+
264
+ ```typescript
265
+ import { describe, it, expect } from "vitest";
266
+
267
+ describe("My Tool", () => {
268
+ it("should process input correctly", () => {
269
+ const result = myTool({ input: "test" });
270
+ expect(result).toBe("expected");
271
+ });
272
+ });
273
+ ```
274
+
275
+ ## Requirements
276
+
277
+ - **Node.js**: >= 18.0.0
278
+ - **npm**: >= 8.0.0
279
+
280
+ ## Best Practices
281
+
282
+ ### Error Handling
283
+
284
+ All templates include comprehensive error handling:
285
+
286
+ ```typescript
287
+ try {
288
+ // Tool logic
289
+ return { content: [{ type: "text", text: result }] };
290
+ } catch (error) {
291
+ return {
292
+ content: [
293
+ {
294
+ type: "text",
295
+ text: `Error: ${error.message}`,
296
+ },
297
+ ],
298
+ isError: true,
299
+ };
300
+ }
301
+ ```
302
+
303
+ ### Input Validation
304
+
305
+ Validate all tool inputs:
306
+
307
+ ```typescript
308
+ if (!input || typeof input !== "string") {
309
+ throw new Error("Invalid input: expected string");
310
+ }
311
+ ```
312
+
313
+ ### Security
314
+
315
+ - Never expose sensitive data in error messages
316
+ - Use environment variables for credentials
317
+ - Validate and sanitize all user inputs
318
+ - Implement path traversal protection for file operations
319
+
320
+ ## Troubleshooting
321
+
322
+ ### Server Not Appearing in Claude Desktop
323
+
324
+ 1. Check configuration file syntax (valid JSON)
325
+ 2. Verify absolute paths in `args`
326
+ 3. Ensure server is built (`npm run build`)
327
+ 4. Restart Claude Desktop
328
+
329
+ ### TypeScript Errors
330
+
331
+ ```bash
332
+ # Clean and rebuild
333
+ rm -rf dist node_modules
334
+ npm install
335
+ npm run build
336
+ ```
337
+
338
+ ### Tests Failing
339
+
340
+ ```bash
341
+ # Update dependencies
342
+ npm update
343
+
344
+ # Clear cache
345
+ npm cache clean --force
346
+ ```
347
+
348
+ ## Contributing
349
+
350
+ Contributions are welcome! Please feel free to submit a Pull Request.
351
+
352
+ ## License
353
+
354
+ MIT © mcp-forge Maintainer
355
+
356
+ ## Resources
357
+
358
+ - [Model Context Protocol Documentation](https://modelcontextprotocol.io)
359
+ - [MCP SDK](https://github.com/modelcontextprotocol/sdk)
360
+ - [Claude Desktop](https://claude.ai/desktop)
361
+
362
+ ## Support
363
+
364
+ - 🐛 [Report Issues](https://github.com/hiyilmaz/mcp-forge/issues)
365
+ - 💬 [Discussions](https://github.com/hiyilmaz/mcp-forge/discussions)
366
+ - 📧 Email: mcpforge@grisis.com
367
+
368
+ ---
369
+
370
+ **Built with ❤️ for the MCP community**
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/init.ts
7
+ import * as clack2 from "@clack/prompts";
8
+ import { existsSync as existsSync2 } from "fs";
9
+ import { join as join3 } from "path";
10
+
11
+ // src/utils/scaffold.ts
12
+ import { cpSync, mkdirSync, readFileSync, writeFileSync } from "fs";
13
+ import { join, dirname } from "path";
14
+ import { fileURLToPath } from "url";
15
+ var __dirname = dirname(fileURLToPath(import.meta.url));
16
+ function scaffoldProject(projectName, template) {
17
+ const targetDir = join(process.cwd(), projectName);
18
+ const templateDir = join(__dirname, "../templates", template);
19
+ mkdirSync(targetDir, { recursive: true });
20
+ cpSync(templateDir, targetDir, { recursive: true });
21
+ replaceTokensInDirectory(targetDir, projectName);
22
+ }
23
+ function replaceTokensInDirectory(dir, projectName) {
24
+ const files = [
25
+ "package.json",
26
+ "README.md",
27
+ "src/index.ts",
28
+ "tests/index.test.ts"
29
+ ];
30
+ for (const file of files) {
31
+ const filePath = join(dir, file);
32
+ try {
33
+ let content = readFileSync(filePath, "utf-8");
34
+ content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
35
+ writeFileSync(filePath, content, "utf-8");
36
+ } catch (error) {
37
+ }
38
+ }
39
+ }
40
+
41
+ // src/utils/claude-config.ts
42
+ import { existsSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
43
+ import { join as join2, dirname as dirname2 } from "path";
44
+ import { homedir } from "os";
45
+ import * as clack from "@clack/prompts";
46
+ function getConfigPath() {
47
+ const platform = process.platform;
48
+ if (platform === "darwin") {
49
+ return join2(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
50
+ } else if (platform === "win32") {
51
+ return join2(process.env.APPDATA || "", "Claude", "claude_desktop_config.json");
52
+ } else {
53
+ return join2(homedir(), ".config", "Claude", "claude_desktop_config.json");
54
+ }
55
+ }
56
+ async function registerWithClaudeDesktop(projectName, projectPath) {
57
+ const configPath = getConfigPath();
58
+ let config = { mcpServers: {} };
59
+ if (existsSync(configPath)) {
60
+ try {
61
+ const content = readFileSync2(configPath, "utf-8");
62
+ config = JSON.parse(content);
63
+ if (!config.mcpServers) {
64
+ config.mcpServers = {};
65
+ }
66
+ } catch (error) {
67
+ clack.log.warn("Could not parse existing config, creating new one");
68
+ }
69
+ }
70
+ if (config.mcpServers[projectName]) {
71
+ const overwrite = await clack.confirm({
72
+ message: `Entry "${projectName}" already exists in Claude Desktop config. Overwrite?`,
73
+ initialValue: false
74
+ });
75
+ if (clack.isCancel(overwrite) || !overwrite) {
76
+ clack.log.info("Skipped Claude Desktop registration");
77
+ return;
78
+ }
79
+ }
80
+ config.mcpServers[projectName] = {
81
+ command: "node",
82
+ args: [join2(projectPath, "src", "index.js")]
83
+ };
84
+ try {
85
+ mkdirSync2(dirname2(configPath), { recursive: true });
86
+ writeFileSync2(configPath, JSON.stringify(config, null, 2), "utf-8");
87
+ clack.log.success("Registered with Claude Desktop");
88
+ clack.log.warn("\u26A0\uFE0F Restart Claude Desktop to apply changes");
89
+ } catch (error) {
90
+ clack.log.error(`Failed to register: ${error.message}`);
91
+ }
92
+ }
93
+
94
+ // src/commands/init.ts
95
+ async function init(projectName, options) {
96
+ clack2.intro("\u26A1 mcp-forge v0.1.0");
97
+ if (!projectName || projectName.length < 2 || !/^[a-z0-9-]+$/i.test(projectName)) {
98
+ clack2.outro("\u274C Invalid project name. Use alphanumeric characters and hyphens only (min 2 chars).");
99
+ process.exit(1);
100
+ }
101
+ const targetDir = join3(process.cwd(), projectName);
102
+ if (existsSync2(targetDir)) {
103
+ clack2.outro(`\u274C Directory "${projectName}" already exists.`);
104
+ process.exit(1);
105
+ }
106
+ const template = options.template || await clack2.select({
107
+ message: "Which template would you like to use?",
108
+ options: [
109
+ { value: "empty", label: "Empty MCP (clean starting point)" },
110
+ { value: "postgresql", label: "PostgreSQL MCP (database tools)" },
111
+ { value: "rest-api", label: "REST API Wrapper MCP (HTTP tools)" },
112
+ { value: "filesystem", label: "Local FileSystem MCP (file tools)" }
113
+ ]
114
+ });
115
+ if (clack2.isCancel(template)) {
116
+ clack2.cancel("Operation cancelled.");
117
+ process.exit(0);
118
+ }
119
+ const language = options.language || await clack2.select({
120
+ message: "Which language?",
121
+ options: [
122
+ { value: "typescript", label: "TypeScript" }
123
+ ]
124
+ });
125
+ if (clack2.isCancel(language)) {
126
+ clack2.cancel("Operation cancelled.");
127
+ process.exit(0);
128
+ }
129
+ const autoRegister = options.register !== void 0 ? options.register : await clack2.confirm({
130
+ message: "Auto-register with Claude Desktop? (recommended)",
131
+ initialValue: true
132
+ });
133
+ if (clack2.isCancel(autoRegister)) {
134
+ clack2.cancel("Operation cancelled.");
135
+ process.exit(0);
136
+ }
137
+ const spinner2 = clack2.spinner();
138
+ spinner2.start("Creating your MCP server...");
139
+ scaffoldProject(projectName, template);
140
+ spinner2.stop("\u2714 Project scaffolded");
141
+ if (autoRegister) {
142
+ await registerWithClaudeDesktop(projectName, targetDir);
143
+ }
144
+ clack2.outro(`\u2705 ${projectName} is ready!
145
+
146
+ Next steps:
147
+ cd ${projectName}
148
+ npm install
149
+ npm run dev
150
+
151
+ Docs: https://github.com/org/mcp-forge`);
152
+ }
153
+
154
+ // src/index.ts
155
+ var program = new Command();
156
+ program.name("mcp-forge").description("The fastest way to build MCP servers").version("0.1.0");
157
+ program.command("init <project-name>").description("Initialize a new MCP server project").option("--template <template>", "Template to use (empty, postgresql, rest-api, filesystem)").option("--language <language>", "Language to use (typescript)").option("--no-register", "Skip Claude Desktop auto-registration").action(async (projectName, options) => {
158
+ await init(projectName, options);
159
+ });
160
+ program.parse();
@@ -0,0 +1,16 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ MCP server built with mcp-forge.
4
+
5
+ ## Development
6
+
7
+ ```bash
8
+ npm install
9
+ npm run dev
10
+ ```
11
+
12
+ ## Testing
13
+
14
+ ```bash
15
+ npm test
16
+ ```
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "tsc",
7
+ "dev": "tsc && node src/index.js",
8
+ "test": "vitest run"
9
+ },
10
+ "dependencies": {
11
+ "@modelcontextprotocol/sdk": "^0.5.0"
12
+ },
13
+ "devDependencies": {
14
+ "typescript": "^5.3.3",
15
+ "vitest": "^1.2.0"
16
+ }
17
+ }
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+
5
+ export const server = new Server(
6
+ {
7
+ name: '{{PROJECT_NAME}}',
8
+ version: '0.1.0',
9
+ },
10
+ {
11
+ capabilities: {
12
+ tools: {},
13
+ },
14
+ }
15
+ );
16
+
17
+ async function main() {
18
+ const transport = new StdioServerTransport();
19
+ await server.connect(transport);
20
+ console.error('MCP server running on stdio');
21
+ }
22
+
23
+ main().catch((error) => {
24
+ console.error('Server error:', error);
25
+ process.exit(1);
26
+ });
@@ -0,0 +1,9 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { server } from '../src/index.js';
3
+
4
+ describe('MCP Server', () => {
5
+ it('server instance is defined', () => {
6
+ expect(server).toBeDefined();
7
+ expect(server).toBeInstanceOf(Object);
8
+ });
9
+ });
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true
10
+ },
11
+ "include": ["src/**/*", "tests/**/*"]
12
+ }
@@ -0,0 +1 @@
1
+ ALLOWED_BASE_PATH=/path/to/allowed/directory
@@ -0,0 +1,37 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ Local FileSystem MCP server built with mcp-forge.
4
+
5
+ ## Setup
6
+
7
+ 1. Copy `.env.example` to `.env` and configure allowed path:
8
+ ```bash
9
+ cp .env.example .env
10
+ ```
11
+
12
+ 2. Install dependencies:
13
+ ```bash
14
+ npm install
15
+ ```
16
+
17
+ ## Development
18
+
19
+ ```bash
20
+ npm run dev
21
+ ```
22
+
23
+ ## Testing
24
+
25
+ ```bash
26
+ npm test
27
+ ```
28
+
29
+ ## Tools
30
+
31
+ - `read_file`: Read file contents (restricted to ALLOWED_BASE_PATH)
32
+ - `write_file`: Write file contents (restricted to ALLOWED_BASE_PATH)
33
+ - `list_directory`: List directory contents (restricted to ALLOWED_BASE_PATH)
34
+
35
+ ## Security
36
+
37
+ All file operations are restricted to the path specified in `ALLOWED_BASE_PATH` environment variable. Path traversal attacks are blocked.
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "tsc",
7
+ "dev": "tsc && node src/index.js",
8
+ "test": "vitest run"
9
+ },
10
+ "dependencies": {
11
+ "@modelcontextprotocol/sdk": "^0.5.0"
12
+ },
13
+ "devDependencies": {
14
+ "@types/node": "^20.11.0",
15
+ "typescript": "^5.3.3",
16
+ "vitest": "^1.2.0"
17
+ }
18
+ }
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
+ import { readFile, writeFile, listDirectory } from './tools/filesystem.js';
6
+
7
+ export const server = new Server(
8
+ {
9
+ name: '{{PROJECT_NAME}}',
10
+ version: '0.1.0',
11
+ },
12
+ {
13
+ capabilities: {
14
+ tools: {},
15
+ },
16
+ }
17
+ );
18
+
19
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
20
+ try {
21
+ const { name, arguments: args } = request.params;
22
+
23
+ switch (name) {
24
+ case 'read_file': {
25
+ const content = readFile((args?.path as string) || '');
26
+ return { content: [{ type: 'text', text: content }] };
27
+ }
28
+ case 'write_file': {
29
+ writeFile((args?.path as string) || '', (args?.content as string) || '');
30
+ return { content: [{ type: 'text', text: 'File written successfully' }] };
31
+ }
32
+ case 'list_directory': {
33
+ const files = listDirectory((args?.path as string) || '');
34
+ return { content: [{ type: 'text', text: JSON.stringify(files, null, 2) }] };
35
+ }
36
+ default:
37
+ throw new Error(`Unknown tool: ${name}`);
38
+ }
39
+ } catch (error: any) {
40
+ return {
41
+ content: [{ type: 'text', text: `Error: ${error.message}` }],
42
+ isError: true,
43
+ };
44
+ }
45
+ });
46
+
47
+ async function main() {
48
+ const transport = new StdioServerTransport();
49
+ await server.connect(transport);
50
+ console.error('FileSystem MCP server running on stdio');
51
+ }
52
+
53
+ main().catch((error) => {
54
+ console.error('Server error:', error);
55
+ process.exit(1);
56
+ });
@@ -0,0 +1,52 @@
1
+ import { readFileSync, writeFileSync, readdirSync, mkdirSync } from 'fs';
2
+ import { join, resolve, dirname } from 'path';
3
+
4
+ const ALLOWED_BASE_PATH = resolve(process.env.ALLOWED_BASE_PATH || process.cwd());
5
+
6
+ function isPathAllowed(filePath: string): boolean {
7
+ const resolvedPath = resolve(filePath);
8
+ return resolvedPath.startsWith(ALLOWED_BASE_PATH);
9
+ }
10
+
11
+ export function readFile(path: string): string {
12
+ const fullPath = join(ALLOWED_BASE_PATH, path);
13
+
14
+ if (!isPathAllowed(fullPath)) {
15
+ throw new Error('Access denied: Path outside allowed directory');
16
+ }
17
+
18
+ try {
19
+ return readFileSync(fullPath, 'utf-8');
20
+ } catch (error: any) {
21
+ throw new Error(`File read error: ${error.message}`);
22
+ }
23
+ }
24
+
25
+ export function writeFile(path: string, content: string): void {
26
+ const fullPath = join(ALLOWED_BASE_PATH, path);
27
+
28
+ if (!isPathAllowed(fullPath)) {
29
+ throw new Error('Access denied: Path outside allowed directory');
30
+ }
31
+
32
+ try {
33
+ mkdirSync(dirname(fullPath), { recursive: true });
34
+ writeFileSync(fullPath, content, 'utf-8');
35
+ } catch (error: any) {
36
+ throw new Error(`File write error: ${error.message}`);
37
+ }
38
+ }
39
+
40
+ export function listDirectory(path: string): string[] {
41
+ const fullPath = join(ALLOWED_BASE_PATH, path);
42
+
43
+ if (!isPathAllowed(fullPath)) {
44
+ throw new Error('Access denied: Path outside allowed directory');
45
+ }
46
+
47
+ try {
48
+ return readdirSync(fullPath);
49
+ } catch (error: any) {
50
+ throw new Error(`Directory read error: ${error.message}`);
51
+ }
52
+ }
@@ -0,0 +1,12 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { readFile, writeFile } from '../src/tools/filesystem.js';
3
+
4
+ describe('FileSystem Tools', () => {
5
+ it('readFile blocks path traversal', () => {
6
+ expect(() => readFile('../../etc/passwd')).toThrow('Access denied');
7
+ });
8
+
9
+ it('writeFile blocks path traversal', () => {
10
+ expect(() => writeFile('../../etc/passwd', 'test')).toThrow('Access denied');
11
+ });
12
+ });
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true
10
+ },
11
+ "include": ["src/**/*", "tests/**/*"]
12
+ }
@@ -0,0 +1 @@
1
+ DATABASE_URL=postgresql://user:password@localhost:5432/database
@@ -0,0 +1,33 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ PostgreSQL MCP server built with mcp-forge.
4
+
5
+ ## Setup
6
+
7
+ 1. Copy `.env.example` to `.env` and configure your database:
8
+ ```bash
9
+ cp .env.example .env
10
+ ```
11
+
12
+ 2. Install dependencies:
13
+ ```bash
14
+ npm install
15
+ ```
16
+
17
+ ## Development
18
+
19
+ ```bash
20
+ npm run dev
21
+ ```
22
+
23
+ ## Testing
24
+
25
+ ```bash
26
+ npm test
27
+ ```
28
+
29
+ ## Tools
30
+
31
+ - `query_database`: Execute raw SQL queries
32
+ - `list_tables`: List all tables in the database
33
+ - `describe_table`: Get column information for a table
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "tsc",
7
+ "dev": "tsc && node src/index.js",
8
+ "test": "vitest run"
9
+ },
10
+ "dependencies": {
11
+ "@modelcontextprotocol/sdk": "^0.5.0",
12
+ "pg": "^8.11.0"
13
+ },
14
+ "devDependencies": {
15
+ "@types/pg": "^8.11.0",
16
+ "typescript": "^5.3.3",
17
+ "vitest": "^1.2.0"
18
+ }
19
+ }
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
+ import { queryDatabase, listTables, describeTable } from './tools/database.js';
6
+
7
+ export const server = new Server(
8
+ {
9
+ name: '{{PROJECT_NAME}}',
10
+ version: '0.1.0',
11
+ },
12
+ {
13
+ capabilities: {
14
+ tools: {},
15
+ },
16
+ }
17
+ );
18
+
19
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
20
+ try {
21
+ const { name, arguments: args } = request.params;
22
+
23
+ switch (name) {
24
+ case 'query_database': {
25
+ const result = await queryDatabase((args?.sql as string) || '');
26
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
27
+ }
28
+ case 'list_tables': {
29
+ const tables = await listTables();
30
+ return { content: [{ type: 'text', text: JSON.stringify(tables, null, 2) }] };
31
+ }
32
+ case 'describe_table': {
33
+ const columns = await describeTable((args?.tableName as string) || '');
34
+ return { content: [{ type: 'text', text: JSON.stringify(columns, null, 2) }] };
35
+ }
36
+ default:
37
+ throw new Error(`Unknown tool: ${name}`);
38
+ }
39
+ } catch (error: any) {
40
+ return {
41
+ content: [{ type: 'text', text: `Error: ${error.message}` }],
42
+ isError: true,
43
+ };
44
+ }
45
+ });
46
+
47
+ async function main() {
48
+ const transport = new StdioServerTransport();
49
+ await server.connect(transport);
50
+ console.error('PostgreSQL MCP server running on stdio');
51
+ }
52
+
53
+ main().catch((error) => {
54
+ console.error('Server error:', error);
55
+ process.exit(1);
56
+ });
@@ -0,0 +1,49 @@
1
+ import { Pool } from 'pg';
2
+
3
+ const pool = new Pool({
4
+ connectionString: process.env.DATABASE_URL,
5
+ });
6
+
7
+ export async function queryDatabase(sql: string) {
8
+ if (!sql || sql.trim().length === 0) {
9
+ throw new Error('SQL query cannot be empty');
10
+ }
11
+
12
+ try {
13
+ const result = await pool.query(sql);
14
+ return result.rows;
15
+ } catch (error: any) {
16
+ throw new Error(`Database error: ${error.message}`);
17
+ }
18
+ }
19
+
20
+ export async function listTables() {
21
+ try {
22
+ const result = await pool.query(`
23
+ SELECT tablename FROM pg_tables
24
+ WHERE schemaname = 'public'
25
+ ORDER BY tablename
26
+ `);
27
+ return result.rows.map(row => row.tablename);
28
+ } catch (error: any) {
29
+ throw new Error(`Database error: ${error.message}`);
30
+ }
31
+ }
32
+
33
+ export async function describeTable(tableName: string) {
34
+ if (!tableName || tableName.trim().length === 0) {
35
+ throw new Error('Table name cannot be empty');
36
+ }
37
+
38
+ try {
39
+ const result = await pool.query(`
40
+ SELECT column_name, data_type, is_nullable
41
+ FROM information_schema.columns
42
+ WHERE table_schema = 'public' AND table_name = $1
43
+ ORDER BY ordinal_position
44
+ `, [tableName]);
45
+ return result.rows;
46
+ } catch (error: any) {
47
+ throw new Error(`Database error: ${error.message}`);
48
+ }
49
+ }
@@ -0,0 +1,32 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { queryDatabase, listTables, describeTable } from '../src/tools/database.js';
3
+
4
+ vi.mock('pg', () => ({
5
+ Pool: vi.fn(() => ({
6
+ query: vi.fn((sql: string) => {
7
+ if (sql.includes('pg_tables')) {
8
+ return Promise.resolve({ rows: [{ tablename: 'users' }, { tablename: 'posts' }] });
9
+ }
10
+ if (sql.includes('information_schema')) {
11
+ return Promise.resolve({ rows: [{ column_name: 'id', data_type: 'integer', is_nullable: 'NO' }] });
12
+ }
13
+ return Promise.resolve({ rows: [{ id: 1, name: 'test' }] });
14
+ }),
15
+ })),
16
+ }));
17
+
18
+ describe('Database Tools', () => {
19
+ it('queryDatabase validates non-empty SQL', async () => {
20
+ await expect(queryDatabase('')).rejects.toThrow('SQL query cannot be empty');
21
+ });
22
+
23
+ it('listTables returns array of table names', async () => {
24
+ const tables = await listTables();
25
+ expect(Array.isArray(tables)).toBe(true);
26
+ expect(tables).toContain('users');
27
+ });
28
+
29
+ it('describeTable validates table name', async () => {
30
+ await expect(describeTable('')).rejects.toThrow('Table name cannot be empty');
31
+ });
32
+ });
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true
10
+ },
11
+ "include": ["src/**/*", "tests/**/*"]
12
+ }
@@ -0,0 +1,2 @@
1
+ API_BASE_URL=https://api.example.com
2
+ API_KEY=your_api_key_here
@@ -0,0 +1,32 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ REST API Wrapper MCP server built with mcp-forge.
4
+
5
+ ## Setup
6
+
7
+ 1. Copy `.env.example` to `.env` and configure your API:
8
+ ```bash
9
+ cp .env.example .env
10
+ ```
11
+
12
+ 2. Install dependencies:
13
+ ```bash
14
+ npm install
15
+ ```
16
+
17
+ ## Development
18
+
19
+ ```bash
20
+ npm run dev
21
+ ```
22
+
23
+ ## Testing
24
+
25
+ ```bash
26
+ npm test
27
+ ```
28
+
29
+ ## Tools
30
+
31
+ - `get_resource`: Make GET requests to the API
32
+ - `post_resource`: Make POST requests to the API
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "tsc",
7
+ "dev": "tsc && node src/index.js",
8
+ "test": "vitest run"
9
+ },
10
+ "dependencies": {
11
+ "@modelcontextprotocol/sdk": "^0.5.0",
12
+ "node-fetch": "^3.3.0"
13
+ },
14
+ "devDependencies": {
15
+ "@types/node": "^20.11.0",
16
+ "typescript": "^5.3.3",
17
+ "vitest": "^1.2.0"
18
+ }
19
+ }
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
+ import { getResource, postResource } from './tools/api.js';
6
+
7
+ export const server = new Server(
8
+ {
9
+ name: '{{PROJECT_NAME}}',
10
+ version: '0.1.0',
11
+ },
12
+ {
13
+ capabilities: {
14
+ tools: {},
15
+ },
16
+ }
17
+ );
18
+
19
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
20
+ try {
21
+ const { name, arguments: args } = request.params;
22
+
23
+ switch (name) {
24
+ case 'get_resource': {
25
+ const result = await getResource((args?.path as string) || '');
26
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
27
+ }
28
+ case 'post_resource': {
29
+ const result = await postResource((args?.path as string) || '', args?.data);
30
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
31
+ }
32
+ default:
33
+ throw new Error(`Unknown tool: ${name}`);
34
+ }
35
+ } catch (error: any) {
36
+ return {
37
+ content: [{ type: 'text', text: `Error: ${error.message}` }],
38
+ isError: true,
39
+ };
40
+ }
41
+ });
42
+
43
+ async function main() {
44
+ const transport = new StdioServerTransport();
45
+ await server.connect(transport);
46
+ console.error('REST API MCP server running on stdio');
47
+ }
48
+
49
+ main().catch((error) => {
50
+ console.error('Server error:', error);
51
+ process.exit(1);
52
+ });
@@ -0,0 +1,66 @@
1
+ import fetch from 'node-fetch';
2
+
3
+ const BASE_URL = process.env.API_BASE_URL || '';
4
+ const API_KEY = process.env.API_KEY || '';
5
+ const TIMEOUT = 10000;
6
+
7
+ export async function getResource(path: string) {
8
+ const controller = new AbortController();
9
+ const timeout = setTimeout(() => controller.abort(), TIMEOUT);
10
+
11
+ try {
12
+ const response = await fetch(`${BASE_URL}${path}`, {
13
+ method: 'GET',
14
+ headers: {
15
+ 'Authorization': `Bearer ${API_KEY}`,
16
+ 'Content-Type': 'application/json',
17
+ },
18
+ signal: controller.signal,
19
+ });
20
+
21
+ clearTimeout(timeout);
22
+
23
+ if (!response.ok) {
24
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
25
+ }
26
+
27
+ return await response.json();
28
+ } catch (error: any) {
29
+ clearTimeout(timeout);
30
+ if (error.name === 'AbortError') {
31
+ throw new Error('Request timeout');
32
+ }
33
+ throw new Error(`API error: ${error.message}`);
34
+ }
35
+ }
36
+
37
+ export async function postResource(path: string, data: any) {
38
+ const controller = new AbortController();
39
+ const timeout = setTimeout(() => controller.abort(), TIMEOUT);
40
+
41
+ try {
42
+ const response = await fetch(`${BASE_URL}${path}`, {
43
+ method: 'POST',
44
+ headers: {
45
+ 'Authorization': `Bearer ${API_KEY}`,
46
+ 'Content-Type': 'application/json',
47
+ },
48
+ body: JSON.stringify(data),
49
+ signal: controller.signal,
50
+ });
51
+
52
+ clearTimeout(timeout);
53
+
54
+ if (!response.ok) {
55
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
56
+ }
57
+
58
+ return await response.json();
59
+ } catch (error: any) {
60
+ clearTimeout(timeout);
61
+ if (error.name === 'AbortError') {
62
+ throw new Error('Request timeout');
63
+ }
64
+ throw new Error(`API error: ${error.message}`);
65
+ }
66
+ }
@@ -0,0 +1,32 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+
3
+ vi.mock('node-fetch', () => ({
4
+ default: vi.fn((url: string, options: any) => {
5
+ if (options?.signal?.aborted) {
6
+ return Promise.reject(new Error('AbortError'));
7
+ }
8
+ return Promise.resolve({
9
+ ok: true,
10
+ status: 200,
11
+ json: () => Promise.resolve({ success: true }),
12
+ });
13
+ }),
14
+ }));
15
+
16
+ describe('API Tools', () => {
17
+ beforeEach(() => {
18
+ vi.clearAllMocks();
19
+ });
20
+
21
+ it('getResource makes HTTP request', async () => {
22
+ const { getResource } = await import('../src/tools/api.js');
23
+ const result = await getResource('/test');
24
+ expect(result).toEqual({ success: true });
25
+ });
26
+
27
+ it('postResource makes HTTP POST request', async () => {
28
+ const { postResource } = await import('../src/tools/api.js');
29
+ const result = await postResource('/test', { data: 'test' });
30
+ expect(result).toEqual({ success: true });
31
+ });
32
+ });
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true
10
+ },
11
+ "include": ["src/**/*", "tests/**/*"]
12
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "create-mcp-forge",
3
+ "version": "0.1.0",
4
+ "description": "The fastest way to build MCP servers",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-mcp-forge": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsup && cp -r templates dist/",
11
+ "dev": "tsup --watch",
12
+ "test": "vitest run",
13
+ "test:watch": "vitest"
14
+ },
15
+ "keywords": [
16
+ "mcp",
17
+ "cli",
18
+ "scaffold",
19
+ "model-context-protocol",
20
+ "anthropic",
21
+ "claude"
22
+ ],
23
+ "author": "hiyilmaz",
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/hiyilmaz/mcp-forge.git"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/hiyilmaz/mcp-forge/issues"
31
+ },
32
+ "homepage": "https://github.com/hiyilmaz/mcp-forge#readme",
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "dependencies": {
37
+ "@clack/prompts": "^0.7.0",
38
+ "commander": "^12.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^20.11.0",
42
+ "tsup": "^8.0.1",
43
+ "typescript": "^5.3.3",
44
+ "vitest": "^1.2.0"
45
+ },
46
+ "engines": {
47
+ "node": ">=18.0.0"
48
+ }
49
+ }