shivprasad/hello-world-mcp-server
If you are the rightful owner of hello-world-mcp-server and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to dayong@mcphub.com.
The Model Context Protocol (MCP) server is a standardized protocol server that facilitates communication between AI applications and external systems, enabling real-time data access, function execution, and resource management.
Hello World MCP Server - Complete Beginner's Guide
A comprehensive tutorial for building your first Model Context Protocol (MCP) server from scratch.
Table of Contents
- What is MCP?
- Prerequisites
- Project Setup
- Understanding MCP Architecture
- Building the Server
- Testing Your Server
- Integration with Claude
- Advanced Features
- Best Practices
- Troubleshooting
What is MCP?
The Model Context Protocol (MCP) is a standardized protocol that allows AI applications (like Claude) to connect to external systems and data sources. It enables LLMs to:
- Access real-time data
- Execute functions
- Read files and resources
- Use custom prompts and templates
Core Concepts
MCP Servers provide three main capabilities:
- Resources 📄 - Static or dynamic data (like files, databases)
- Tools 🔧 - Functions that the LLM can execute
- Prompts 💬 - Pre-written templates for common tasks
Prerequisites
Required Software
- Node.js (v18 or higher)
- npm (comes with Node.js)
- TypeScript (for development)
- Text editor (VS Code recommended)
Required Knowledge
- Basic JavaScript/TypeScript
- Understanding of JSON
- Command line basics
- API concepts (requests/responses)
Project Setup
1. Initialize Project
mkdir hello-world-mcp-server
cd hello-world-mcp-server
npm init -y
2. Install Dependencies
# Core MCP dependencies
npm install @modelcontextprotocol/sdk zod
# Development dependencies
npm install -D typescript @types/node
3. Configure TypeScript
Create tsconfig.json:
{
"compilerOptions": {
"target": "es2020",
"module": "es2020",
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
4. Update package.json
{
"type": "module",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsc --watch",
"inspect": "npx @modelcontextprotocol/inspector dist/index.js"
}
}
Understanding MCP Architecture
Transport Layer
MCP uses stdio (standard input/output) for communication:
┌─────────────┐ stdio ┌─────────────┐
│ Claude │ ◄────────► │ MCP Server │
│ (Client) │ │ │
└─────────────┘ └─────────────┘
Message Format
All communication uses JSON-RPC 2.0:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "hello",
"arguments": { "name": "World" }
}
}
Server Lifecycle
- Initialization - Client connects and negotiates capabilities
- Capability Exchange - Server advertises what it can do
- Request Handling - Server responds to client requests
- Cleanup - Connection terminates gracefully
Building the Server
1. Basic Server Structure
Create src/index.ts:
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
// Initialize server with metadata
const server = new Server(
{
name: "hello-world-mcp-server",
version: "1.0.0",
},
{
capabilities: {
resources: {},
tools: {},
prompts: {},
},
}
);
2. Implementing Tools
Tools are functions the LLM can call:
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "hello",
description: "Returns a greeting message",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name to greet (optional)",
},
},
},
},
],
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case "hello": {
const nameArg = args?.name as string | undefined;
const greeting = nameArg ? `Hello, ${nameArg}!` : "Hello, World!";
return {
content: [
{
type: "text",
text: greeting,
},
],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
});
3. Implementing Resources
Resources provide data the LLM can read:
import {
ListResourcesRequestSchema,
ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
// List available resources
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: "greeting://hello-world",
name: "Hello World Message",
description: "A simple hello world greeting",
mimeType: "text/plain",
},
],
};
});
// Handle resource reads
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
switch (uri) {
case "greeting://hello-world":
return {
contents: [
{
uri,
mimeType: "text/plain",
text: "Hello, World! This is a resource from the MCP server.",
},
],
};
default:
throw new Error(`Unknown resource: ${uri}`);
}
});
4. Implementing Prompts
Prompts are templates for common tasks:
import {
ListPromptsRequestSchema,
GetPromptRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
// List available prompts
server.setRequestHandler(ListPromptsRequestSchema, async () => {
return {
prompts: [
{
name: "greeting-template",
description: "A template for generating personalized greetings",
arguments: [
{
name: "name",
description: "The name of the person to greet",
required: true,
},
],
},
],
};
});
// Handle prompt requests
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case "greeting-template": {
const personName = args?.name as string;
if (!personName) {
throw new Error("Name argument is required");
}
return {
description: `Personalized greeting for ${personName}`,
messages: [
{
role: "user",
content: {
type: "text",
text: `Hello, ${personName}! How can I assist you today?`,
},
},
],
};
}
default:
throw new Error(`Unknown prompt: ${name}`);
}
});
5. Starting the Server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Hello World MCP Server running on stdio");
}
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});
}
Testing Your Server
1. Build the Server
npm run build
2. Test with MCP Inspector
npm run inspect
This opens a web interface where you can:
- View server capabilities
- Test tools interactively
- Debug message flow
- Validate responses
3. Manual Testing
Create a simple test script to verify functionality:
#!/usr/bin/env node
import { spawn } from 'child_process';
const serverProcess = spawn('node', ['dist/index.js'], {
stdio: ['pipe', 'pipe', 'inherit']
});
// Send initialization message
const initMessage = {
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: {
protocolVersion: '2025-06-18',
capabilities: {},
clientInfo: { name: 'test-client', version: '1.0.0' }
}
};
serverProcess.stdin.write(JSON.stringify(initMessage) + '\\n');
Integration with AI Assistants
1. Claude Desktop Configuration
Add to Claude Desktop's configuration file:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%/Claude/claude_desktop_config.json
{
"mcpServers": {
"hello-world": {
"command": "node",
"args": ["/path/to/your/project/dist/index.js"]
}
}
}
2. Cline Configuration
To use this MCP server with Cline (VS Code extension):
Step 1: Build the Server
npm run build
Step 2: Configure Cline
- Open VS Code with the Cline extension installed
- Open Cline settings (Command Palette → "Cline: Open Settings")
- Navigate to the "MCP Servers" section
- Add a new MCP server configuration:
{
"name": "hello-world-mcp-server",
"command": "node",
"args": ["/absolute/path/to/your/project/dist/index.js"],
"env": {}
}
Important: Use the absolute path to your compiled dist/index.js file.
Step 3: Restart Cline
After adding the configuration, restart Cline to load the new MCP server.
Step 4: Test the Integration
Once configured, Cline can use your MCP server's capabilities:
- Tools: Cline can call the
hellotool to generate greetings - Resources: Cline can read the greeting resource for context
- Prompts: Cline can use the greeting template for structured responses
Example usage in Cline:
User: Use the hello tool to greet Sarah
Cline: I'll use the hello tool from the MCP server to greet Sarah.
[MCP Tool Call: hello with name="Sarah"]
Result: Hello, Sarah!
Troubleshooting Cline Integration
Server Not Found:
- Verify the absolute path to
dist/index.jsis correct - Ensure the server builds successfully with
npm run build - Check Cline's output panel for error messages
Permission Issues:
- Make sure the compiled JavaScript file is executable
- On Unix systems, you may need:
chmod +x dist/index.js
Connection Issues:
- Restart VS Code completely after configuration changes
- Check that Node.js is in your system PATH
- Verify no other processes are using the same MCP server
3. Testing in Claude
Once configured, you can test in Claude:
User: Use the hello tool to greet John
Claude: I'll use the hello tool to greet John.
[Tool execution: hello with name="John"]
Result: Hello, John!
Advanced Features
Error Handling
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
// Tool logic here
} catch (error) {
return {
content: [
{
type: "text",
text: `Error: ${error.message}`,
},
],
isError: true,
};
}
});
Input Validation with Zod
import { z } from "zod";
const HelloArgsSchema = z.object({
name: z.string().optional(),
greeting: z.enum(["hello", "hi", "hey"]).default("hello"),
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "hello") {
const validatedArgs = HelloArgsSchema.parse(args);
// Use validatedArgs.name, validatedArgs.greeting
}
});
Progress Reporting
return {
content: [
{
type: "text",
text: "Processing request...",
},
],
progress: {
progress: 50,
total: 100,
},
};
Best Practices
1. Security
- ✅ Validate all inputs
- ✅ Use allowlists for file paths
- ✅ Sanitize data before processing
- ❌ Never execute arbitrary code
- ❌ Don't expose sensitive information
2. Performance
- Use async/await for I/O operations
- Implement caching for expensive operations
- Stream large responses when possible
- Set appropriate timeouts
3. Error Handling
- Provide meaningful error messages
- Use structured logging
- Gracefully handle edge cases
- Document error conditions
4. Documentation
- Document all tools, resources, and prompts
- Provide usage examples
- Explain input/output formats
- Include troubleshooting guides
Troubleshooting
Common Issues
Permission Denied (EACCES)
chmod +x dist/index.js
Module Resolution Errors
- Check
package.jsonhas"type": "module" - Ensure TypeScript config uses correct module settings
Server Not Responding
- Check stdio transport is working
- Verify server initialization
- Look for error messages in stderr
Tool Not Found
- Ensure tool is listed in ListToolsRequestSchema handler
- Check tool name matches exactly
- Verify CallToolRequestSchema handler includes the tool
Debugging Tips
- Use console.error() for debugging (stdout is reserved for MCP protocol)
- Test with MCP Inspector before integrating with Claude
- Check Claude Desktop logs for configuration issues
- Validate JSON-RPC messages are properly formatted
Next Steps
Now that you have a working MCP server, consider:
- Adding real functionality (database access, API calls, file operations)
- Building more complex tools with multiple parameters
- Implementing dynamic resources that change over time
- Creating specialized prompts for your use case
- Contributing to the MCP ecosystem with your own servers
Resources
Happy coding! 🚀