Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,11 @@ A command - line tool for quickly creating standardized MCP (Model Context Proto
🔧 Intelligent Configuration
- Automatically generates package.json and TypeScript configurations

## Quick Start
```bash
npx create-ts-mcp-server your-mcp-server-name
```



## 🚀 Usage Guide
## 🚀 Quick Start
### Create a Project
```bash

# by npx

npx create-ts-mcp-server your-server-name

```
Expand All @@ -49,6 +41,15 @@ npx create-ts-mcp-server your-server-name
Low-Level use: It is a low-level interface, which is suitable for developers who need to customize the implementation details. (Use arrow keys)
❯ High-Level API
Low-Level API
# config your server transport type
? What is the transport type of your server?
Standard Input/Output (stdio):The stdio transport enables communication through standard
input and output streams. This is particularly useful for local integrations and command-line
tools.
Server-Sent Events (SSE):SSE transport enables server-to-client streaming with HTTP POST
requests for client-to-server communication.
❯ Standard Input/Output (stdio)
Server-Sent Events (SSE)
# success message
✔ MCP server created successfully!

Expand All @@ -68,16 +69,19 @@ npm install # install dependencies

npm run build # build project

npm run inspector # debug your server
npm run inspector # stdio: type debug your server

npx tsx ./src/index.ts # sse: run your sse server

```
### Directory Structure
The typical project structure generated is as follows:
```
your-server-name/
├── src/
│   ├── index.ts      # Service entry file
├── test/             
│   ├── server    
│    ├── server.ts # mcp server
│   ├── index.ts     # Service entry file
├── package.json
└── tsconfig.json
```
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "create-ts-mcp-server",
"version": "0.0.12",
"version": "0.0.13",
"description": "CLI tool to create new MCP servers",
"type": "module",

"types": "build/index.d.ts",
"scripts": {
"build": "tsc && shx chmod +x build/index.js",
"prepare": "npm run build",
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const program = new Command()
.option("-n, --name <name>", "Name of the server")
.option("-d, --description <description>", "Description of the server")
.option("-lv, --level <level>", "API level of the server")
.option("-t, --transport <transport>", "Transport type of the server")
.action(createServer);

program.parse();
37 changes: 31 additions & 6 deletions src/utils/createServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,24 @@ export async function createServer(directory: string, options: any = {}) {
],
when: !options.level,
},
{
type: "list",
name: "transport",
message: "What is the transport type of your server?\n Standard Input/Output (stdio):The stdio transport enables communication through standard input and output streams. This is particularly useful for local integrations and command-line tools.\n Server-Sent Events (SSE):SSE transport enables server-to-client streaming with HTTP POST requests for client-to-server communication.",
choices:[
"Standard Input/Output (stdio)",
"Server-Sent Events (SSE)"
],
when: !options.transport,
}
];

const answers = await inquirer.prompt(questions);
const config = {
name: options.name || answers.name,
description: options.description || answers.description,
level: options.level || answers.level,
transport: options.transport || answers.transport,
};
const spinner = ora("Creating MCP server...").start();

Expand All @@ -68,6 +79,7 @@ export async function createServer(directory: string, options: any = {}) {
? `.${(file.slice(8) as string).replace(".ejs", "")}`
: (file as string).replace(".ejs", "")
);

const targetDir = path.dirname(targetPath);
// Create subdirectories if needed
fs.mkdirSync(targetDir, { recursive: true });
Expand All @@ -77,19 +89,32 @@ export async function createServer(directory: string, options: any = {}) {
// Use EJS to render the template
content = ejs.render(content, config);

// Write processed file
// shake files
if(file.includes(".ejs")){
if(config.level === "Low-Level API"&& file.includes("high.ts.ejs")){
continue;
};
if(config.level === "High-Level API"&& file.includes("low.ts.ejs")){
continue;
}
if(config.transport === "Standard Input/Output (stdio)"&& file.includes("sse.ts.ejs")){
continue;
}
if(config.transport === "Server-Sent Events (SSE)"&& file.includes("stdio.ts.ejs")){
continue;
}
}
fs.writeFileSync(targetPath, content);

if(file.includes("high")||file.includes("low")){
fs.renameSync(targetPath,targetPath.replace(/(high|low)/,"index"))
// Write processed file
fs.writeFileSync(targetPath, content);
// rename files
const lowOrHigh = new RegExp(/(high|low)/)
if(lowOrHigh.test(file as string)){
fs.renameSync(targetPath,targetPath.replace(lowOrHigh,"server"))
}
const sseOrStdio = new RegExp(/(sse|stdio)/)
if(sseOrStdio.test(file as string)){
fs.renameSync(targetPath,targetPath.replace(sseOrStdio,"index"))
}
}

Expand All @@ -110,8 +135,8 @@ export async function createServer(directory: string, options: any = {}) {
)
);
} catch (e) {
// spinner.fail(chalk.red("Failed to create MCP server"));
console.log(e);
spinner.fail(chalk.red("Failed to create MCP server"));
console.error(e);
process.exit(1);
}
}
6 changes: 6 additions & 0 deletions template/README.md.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,16 @@ npm run watch

### Debugging

#### Stdio
Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), which is available as a package script:

```bash
npm run inspector
```

The Inspector will provide a URL to access debugging tools in your browser.

#### SSE
```
npx tsx src/index.ts
```
23 changes: 19 additions & 4 deletions template/package.json.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,32 @@
"build"
],
"scripts": {
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
"prepare": "npm run build",
"watch": "tsc --watch",
"inspector": "npx @modelcontextprotocol/inspector build/index.js"
<% if (transport==='Server-Sent Events (SSE)') { %>
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
"build": "tsc",
"start": "node build/index.js"
<% } %>
<% if (transport==='Standard Input/Output (stdio)') { %>
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
"prepare": "npm run build",
"watch": "tsc --watch",
"inspector": "npx @modelcontextprotocol/inspector build/index.js"
<% } %>
},
"dependencies": {
<% if (transport==='Server-Sent Events (SSE)') { %>
"express": "^5.0.1",
<% } %>
"@modelcontextprotocol/sdk": "1.11.4",
"@modelcontextprotocol/inspector": "0.10.2",
"zod": "^3.24.3"
},
"devDependencies": {
<% if (transport==='Server-Sent Events (SSE)') { %>
"@types/express": "^5.0.1",
"ts-node-dev":"^2.0.0",
<% } %>
"tsx": "^4.16.5",
"@types/node": "^20.11.24",
"typescript": "^5.3.3"
}
Expand Down
16 changes: 1 addition & 15 deletions template/src/high.ts.ejs → template/src/server/high.ts.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import { z } from "zod";

// Create an MCP server
const server = new McpServer({
export const server = new McpServer({
name: "Demo",
version: "1.0.0"
});
Expand Down Expand Up @@ -44,17 +44,3 @@ server.prompt(
}]
})
);

/**
* Start the server using stdio transport.
* This allows the server to communicate via standard input/output streams.
*/
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}

main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});
15 changes: 1 addition & 14 deletions template/src/low.ts.ejs → template/src/server/low.ts.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const notes: { [id: string]: Note } = {
* Create an MCP server with capabilities for resources (to list/read notes),
* tools (to create new notes), and prompts (to summarize notes).
*/
const server = new Server(
export const server = new Server(
{
name: "<%= name %>",
version: "0.1.0",
Expand Down Expand Up @@ -207,16 +207,3 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
};
});

/**
* Start the server using stdio transport.
* This allows the server to communicate via standard input/output streams.
*/
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}

main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});
98 changes: 98 additions & 0 deletions template/src/sse.ts.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import express, { Request, Response } from 'express';
import { z } from 'zod';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { server } from './server/server.js'

const app = express();
app.use(express.json());

// Store transports by session ID
const transports: Record<string, SSEServerTransport> = {};

// SSE endpoint for establishing the stream
app.get('/mcp', async (req: Request, res: Response) => {
console.log('Received GET request to /sse (establishing SSE stream)');

try {
// Create a new SSE transport for the client
// The endpoint for POST messages is '/messages'
const transport = new SSEServerTransport('/messages', res);

// Store the transport by session ID
const sessionId = transport.sessionId;
transports[sessionId] = transport;

// Set up onclose handler to clean up transport when closed
transport.onclose = () => {
console.log(`SSE transport closed for session ${sessionId}`);
delete transports[sessionId];
};

// Connect the transport to the MCP server
await server.connect(transport);

console.log(`Established SSE stream with session ID: ${sessionId}`);
} catch (error) {
console.error('Error establishing SSE stream:', error);
if (!res.headersSent) {
res.status(500).send('Error establishing SSE stream');
}
}
});

// Messages endpoint for receiving client JSON-RPC requests
app.post('/messages', async (req: Request, res: Response) => {
console.log('Received POST request to /messages');

// Extract session ID from URL query parameter
// In the SSE protocol, this is added by the client based on the endpoint event
const sessionId = req.query.sessionId as string | undefined;

if (!sessionId) {
console.error('No session ID provided in request URL');
res.status(400).send('Missing sessionId parameter');
return;
}

const transport = transports[sessionId];
if (!transport) {
console.error(`No active transport found for session ID: ${sessionId}`);
res.status(404).send('Session not found');
return;
}

try {
// Handle the POST message with the transport
await transport.handlePostMessage(req, res, req.body);
} catch (error) {
console.error('Error handling request:', error);
if (!res.headersSent) {
res.status(500).send('Error handling request');
}
}
});

// Start the server
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Simple SSE Server (deprecated protocol version 2024-11-05) listening on port ${PORT}`);
});

// Handle server shutdown
process.on('SIGINT', async () => {
console.log('Shutting down server...');

// Close all active transports to properly clean up resources
for (const sessionId in transports) {
try {
console.log(`Closing transport for session ${sessionId}`);
await transports[sessionId].close();
delete transports[sessionId];
} catch (error) {
console.error(`Error closing transport for session ${sessionId}:`, error);
}
}
console.log('Server shutdown complete');
process.exit(0);
});
17 changes: 17 additions & 0 deletions template/src/stdio.ts.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env node
import { server } from './server/server.js'
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

/**
* Start the server using stdio transport.
* This allows the server to communicate via standard input/output streams.
*/
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}

main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});
Loading