Building Your First MCP Server from Scratch
Before publishing to mcpm, you need an MCP server. This tutorial builds one from zero — no prior MCP experience required.
What You'll Build
A Todo MCP Server with two tools:
add_todo — add a task
list_todos — list all tasks
Prerequisites
- Node.js 18+
- Basic JavaScript knowledge
Step 1: Initialize the Project
<span class="hljs-built_in">mkdir</span> todo-mcp && <span class="hljs-built_in">cd</span> todo-mcp
npm init -y
npm install @modelcontextprotocol/sdk
Step 2: Write the Server
Create index.js:
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">Server</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"@modelcontextprotocol/sdk/server/index.js"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">StdioServerTransport</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"@modelcontextprotocol/sdk/server/stdio.js"</span>;
<span class="hljs-comment">// In-memory storage (replace with a database in production)</span>
<span class="hljs-keyword">const</span> todos = [];
<span class="hljs-keyword">const</span> server = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Server</span>({
<span class="hljs-attr">name</span>: <span class="hljs-string">"todo-mcp"</span>,
<span class="hljs-attr">version</span>: <span class="hljs-string">"1.0.0"</span>,
}, {
<span class="hljs-attr">capabilities</span>: { <span class="hljs-attr">tools</span>: {} }
});
<span class="hljs-comment">// ── List available tools ──────────────────────────</span>
server.<span class="hljs-title function_">setRequestHandler</span>(<span class="hljs-string">"tools/list"</span>, <span class="hljs-title function_">async</span> () => ({
<span class="hljs-attr">tools</span>: [
{
<span class="hljs-attr">name</span>: <span class="hljs-string">"add_todo"</span>,
<span class="hljs-attr">description</span>: <span class="hljs-string">"Add a new todo item to the list"</span>,
<span class="hljs-attr">inputSchema</span>: {
<span class="hljs-attr">type</span>: <span class="hljs-string">"object"</span>,
<span class="hljs-attr">properties</span>: {
<span class="hljs-attr">task</span>: {
<span class="hljs-attr">type</span>: <span class="hljs-string">"string"</span>,
<span class="hljs-attr">description</span>: <span class="hljs-string">"The task description"</span>
},
<span class="hljs-attr">priority</span>: {
<span class="hljs-attr">type</span>: <span class="hljs-string">"string"</span>,
<span class="hljs-attr">enum</span>: [<span class="hljs-string">"low"</span>, <span class="hljs-string">"medium"</span>, <span class="hljs-string">"high"</span>],
<span class="hljs-attr">description</span>: <span class="hljs-string">"Task priority (default: medium)"</span>
}
},
<span class="hljs-attr">required</span>: [<span class="hljs-string">"task"</span>]
}
},
{
<span class="hljs-attr">name</span>: <span class="hljs-string">"list_todos"</span>,
<span class="hljs-attr">description</span>: <span class="hljs-string">"List all current todos"</span>,
<span class="hljs-attr">inputSchema</span>: {
<span class="hljs-attr">type</span>: <span class="hljs-string">"object"</span>,
<span class="hljs-attr">properties</span>: {
<span class="hljs-attr">filter</span>: {
<span class="hljs-attr">type</span>: <span class="hljs-string">"string"</span>,
<span class="hljs-attr">enum</span>: [<span class="hljs-string">"all"</span>, <span class="hljs-string">"low"</span>, <span class="hljs-string">"medium"</span>, <span class="hljs-string">"high"</span>],
<span class="hljs-attr">description</span>: <span class="hljs-string">"Filter by priority (default: all)"</span>
}
}
}
}
]
}));
<span class="hljs-comment">// ── Handle tool calls ────────────────────────────</span>
server.<span class="hljs-title function_">setRequestHandler</span>(<span class="hljs-string">"tools/call"</span>, <span class="hljs-title function_">async</span> (request) => {
<span class="hljs-keyword">const</span> { name, <span class="hljs-attr">arguments</span>: args } = request.<span class="hljs-property">params</span>;
<span class="hljs-keyword">switch</span> (name) {
<span class="hljs-keyword">case</span> <span class="hljs-string">"add_todo"</span>: {
<span class="hljs-keyword">const</span> todo = {
<span class="hljs-attr">id</span>: todos.<span class="hljs-property">length</span> + <span class="hljs-number">1</span>,
<span class="hljs-attr">task</span>: args.<span class="hljs-property">task</span>,
<span class="hljs-attr">priority</span>: args.<span class="hljs-property">priority</span> || <span class="hljs-string">"medium"</span>,
<span class="hljs-attr">created</span>: <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>().<span class="hljs-title function_">toISOString</span>()
};
todos.<span class="hljs-title function_">push</span>(todo);
<span class="hljs-keyword">return</span> {
<span class="hljs-attr">content</span>: [{
<span class="hljs-attr">type</span>: <span class="hljs-string">"text"</span>,
<span class="hljs-attr">text</span>: <span class="hljs-string">`✅ Added: "<span class="hljs-subst">${args.task}</span>" (priority: <span class="hljs-subst">${todo.priority}</span>)`</span>
}]
};
}
<span class="hljs-keyword">case</span> <span class="hljs-string">"list_todos"</span>: {
<span class="hljs-keyword">const</span> filter = args.<span class="hljs-property">filter</span> || <span class="hljs-string">"all"</span>;
<span class="hljs-keyword">const</span> filtered = filter === <span class="hljs-string">"all"</span>
? todos
: todos.<span class="hljs-title function_">filter</span>(<span class="hljs-function"><span class="hljs-params">t</span> =></span> t.<span class="hljs-property">priority</span> === filter);
<span class="hljs-keyword">if</span> (filtered.<span class="hljs-property">length</span> === <span class="hljs-number">0</span>) {
<span class="hljs-keyword">return</span> {
<span class="hljs-attr">content</span>: [{ <span class="hljs-attr">type</span>: <span class="hljs-string">"text"</span>, <span class="hljs-attr">text</span>: <span class="hljs-string">"No todos found. 📭"</span> }]
};
}
<span class="hljs-keyword">const</span> list = filtered
.<span class="hljs-title function_">map</span>(<span class="hljs-function"><span class="hljs-params">t</span> =></span> <span class="hljs-string">`[<span class="hljs-subst">${t.priority.toUpperCase()}</span>] <span class="hljs-subst">${t.task}</span>`</span>)
.<span class="hljs-title function_">join</span>(<span class="hljs-string">"\n"</span>);
<span class="hljs-keyword">return</span> {
<span class="hljs-attr">content</span>: [{
<span class="hljs-attr">type</span>: <span class="hljs-string">"text"</span>,
<span class="hljs-attr">text</span>: <span class="hljs-string">`📋 Todos:\n<span class="hljs-subst">${list}</span>`</span>
}]
};
}
<span class="hljs-attr">default</span>:
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">`Unknown tool: <span class="hljs-subst">${name}</span>`</span>);
}
});
<span class="hljs-comment">// ── Start the server ─────────────────────────────</span>
<span class="hljs-keyword">const</span> transport = <span class="hljs-keyword">new</span> <span class="hljs-title class_">StdioServerTransport</span>();
<span class="hljs-keyword">await</span> server.<span class="hljs-title function_">connect</span>(transport);
Step 3: Test Locally
Run the server:
node index.js
The server starts and listens on stdio. To test with a real MCP client, configure Claude Desktop or Cursor to point to this server.
Claude Desktop Configuration
Add to claude_desktop_config.json:
<span class="hljs-punctuation">{</span>
<span class="hljs-attr">"mcpServers"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
<span class="hljs-attr">"todo"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
<span class="hljs-attr">"command"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"node"</span><span class="hljs-punctuation">,</span>
<span class="hljs-attr">"args"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">"/absolute/path/to/todo-mcp/index.js"</span><span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
Restart Claude Desktop. Ask: "Add a todo: buy groceries with high priority." Then: "List all todos."
Step 4: Prepare for mcpm
Write a README (see our Publishing tutorial) and ensure your package.json is complete. Then:
mcpm-dev publish
Key Takeaways
tools/list tells clients what tools are available
tools/call handles actual invocations
inputSchema defines parameters — make them clear and descriptive
- Return structured content — not raw text
Next Steps
- Add more tools (delete, update, search)
- Replace in-memory storage with SQLite or PostgreSQL
- Add authentication
- Publish to mcpm
#MCP #Tutorial #NodeJS #mcpm #AIAgents
Building Your First MCP Server from Scratch
Before publishing to mcpm, you need an MCP server. This tutorial builds one from zero — no prior MCP experience required.
What You'll Build
A Todo MCP Server with two tools:
add_todo— add a tasklist_todos— list all tasksPrerequisites
Step 1: Initialize the Project
<span class="hljs-built_in">mkdir</span> todo-mcp && <span class="hljs-built_in">cd</span> todo-mcp npm init -y npm install @modelcontextprotocol/sdkStep 2: Write the Server
Create
index.js:<span class="hljs-keyword">import</span> { <span class="hljs-title class_">Server</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"@modelcontextprotocol/sdk/server/index.js"</span>; <span class="hljs-keyword">import</span> { <span class="hljs-title class_">StdioServerTransport</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"@modelcontextprotocol/sdk/server/stdio.js"</span>; <span class="hljs-comment">// In-memory storage (replace with a database in production)</span> <span class="hljs-keyword">const</span> todos = []; <span class="hljs-keyword">const</span> server = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Server</span>({ <span class="hljs-attr">name</span>: <span class="hljs-string">"todo-mcp"</span>, <span class="hljs-attr">version</span>: <span class="hljs-string">"1.0.0"</span>, }, { <span class="hljs-attr">capabilities</span>: { <span class="hljs-attr">tools</span>: {} } }); <span class="hljs-comment">// ── List available tools ──────────────────────────</span> server.<span class="hljs-title function_">setRequestHandler</span>(<span class="hljs-string">"tools/list"</span>, <span class="hljs-title function_">async</span> () => ({ <span class="hljs-attr">tools</span>: [ { <span class="hljs-attr">name</span>: <span class="hljs-string">"add_todo"</span>, <span class="hljs-attr">description</span>: <span class="hljs-string">"Add a new todo item to the list"</span>, <span class="hljs-attr">inputSchema</span>: { <span class="hljs-attr">type</span>: <span class="hljs-string">"object"</span>, <span class="hljs-attr">properties</span>: { <span class="hljs-attr">task</span>: { <span class="hljs-attr">type</span>: <span class="hljs-string">"string"</span>, <span class="hljs-attr">description</span>: <span class="hljs-string">"The task description"</span> }, <span class="hljs-attr">priority</span>: { <span class="hljs-attr">type</span>: <span class="hljs-string">"string"</span>, <span class="hljs-attr">enum</span>: [<span class="hljs-string">"low"</span>, <span class="hljs-string">"medium"</span>, <span class="hljs-string">"high"</span>], <span class="hljs-attr">description</span>: <span class="hljs-string">"Task priority (default: medium)"</span> } }, <span class="hljs-attr">required</span>: [<span class="hljs-string">"task"</span>] } }, { <span class="hljs-attr">name</span>: <span class="hljs-string">"list_todos"</span>, <span class="hljs-attr">description</span>: <span class="hljs-string">"List all current todos"</span>, <span class="hljs-attr">inputSchema</span>: { <span class="hljs-attr">type</span>: <span class="hljs-string">"object"</span>, <span class="hljs-attr">properties</span>: { <span class="hljs-attr">filter</span>: { <span class="hljs-attr">type</span>: <span class="hljs-string">"string"</span>, <span class="hljs-attr">enum</span>: [<span class="hljs-string">"all"</span>, <span class="hljs-string">"low"</span>, <span class="hljs-string">"medium"</span>, <span class="hljs-string">"high"</span>], <span class="hljs-attr">description</span>: <span class="hljs-string">"Filter by priority (default: all)"</span> } } } } ] })); <span class="hljs-comment">// ── Handle tool calls ────────────────────────────</span> server.<span class="hljs-title function_">setRequestHandler</span>(<span class="hljs-string">"tools/call"</span>, <span class="hljs-title function_">async</span> (request) => { <span class="hljs-keyword">const</span> { name, <span class="hljs-attr">arguments</span>: args } = request.<span class="hljs-property">params</span>; <span class="hljs-keyword">switch</span> (name) { <span class="hljs-keyword">case</span> <span class="hljs-string">"add_todo"</span>: { <span class="hljs-keyword">const</span> todo = { <span class="hljs-attr">id</span>: todos.<span class="hljs-property">length</span> + <span class="hljs-number">1</span>, <span class="hljs-attr">task</span>: args.<span class="hljs-property">task</span>, <span class="hljs-attr">priority</span>: args.<span class="hljs-property">priority</span> || <span class="hljs-string">"medium"</span>, <span class="hljs-attr">created</span>: <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>().<span class="hljs-title function_">toISOString</span>() }; todos.<span class="hljs-title function_">push</span>(todo); <span class="hljs-keyword">return</span> { <span class="hljs-attr">content</span>: [{ <span class="hljs-attr">type</span>: <span class="hljs-string">"text"</span>, <span class="hljs-attr">text</span>: <span class="hljs-string">`✅ Added: "<span class="hljs-subst">${args.task}</span>" (priority: <span class="hljs-subst">${todo.priority}</span>)`</span> }] }; } <span class="hljs-keyword">case</span> <span class="hljs-string">"list_todos"</span>: { <span class="hljs-keyword">const</span> filter = args.<span class="hljs-property">filter</span> || <span class="hljs-string">"all"</span>; <span class="hljs-keyword">const</span> filtered = filter === <span class="hljs-string">"all"</span> ? todos : todos.<span class="hljs-title function_">filter</span>(<span class="hljs-function"><span class="hljs-params">t</span> =></span> t.<span class="hljs-property">priority</span> === filter); <span class="hljs-keyword">if</span> (filtered.<span class="hljs-property">length</span> === <span class="hljs-number">0</span>) { <span class="hljs-keyword">return</span> { <span class="hljs-attr">content</span>: [{ <span class="hljs-attr">type</span>: <span class="hljs-string">"text"</span>, <span class="hljs-attr">text</span>: <span class="hljs-string">"No todos found. 📭"</span> }] }; } <span class="hljs-keyword">const</span> list = filtered .<span class="hljs-title function_">map</span>(<span class="hljs-function"><span class="hljs-params">t</span> =></span> <span class="hljs-string">`[<span class="hljs-subst">${t.priority.toUpperCase()}</span>] <span class="hljs-subst">${t.task}</span>`</span>) .<span class="hljs-title function_">join</span>(<span class="hljs-string">"\n"</span>); <span class="hljs-keyword">return</span> { <span class="hljs-attr">content</span>: [{ <span class="hljs-attr">type</span>: <span class="hljs-string">"text"</span>, <span class="hljs-attr">text</span>: <span class="hljs-string">`📋 Todos:\n<span class="hljs-subst">${list}</span>`</span> }] }; } <span class="hljs-attr">default</span>: <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">`Unknown tool: <span class="hljs-subst">${name}</span>`</span>); } }); <span class="hljs-comment">// ── Start the server ─────────────────────────────</span> <span class="hljs-keyword">const</span> transport = <span class="hljs-keyword">new</span> <span class="hljs-title class_">StdioServerTransport</span>(); <span class="hljs-keyword">await</span> server.<span class="hljs-title function_">connect</span>(transport);Step 3: Test Locally
Run the server:
The server starts and listens on stdio. To test with a real MCP client, configure Claude Desktop or Cursor to point to this server.
Claude Desktop Configuration
Add to
claude_desktop_config.json:<span class="hljs-punctuation">{</span> <span class="hljs-attr">"mcpServers"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span> <span class="hljs-attr">"todo"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span> <span class="hljs-attr">"command"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"node"</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"args"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">"/absolute/path/to/todo-mcp/index.js"</span><span class="hljs-punctuation">]</span> <span class="hljs-punctuation">}</span> <span class="hljs-punctuation">}</span> <span class="hljs-punctuation">}</span>Restart Claude Desktop. Ask: "Add a todo: buy groceries with high priority." Then: "List all todos."
Step 4: Prepare for mcpm
Write a README (see our Publishing tutorial) and ensure your
package.jsonis complete. Then:Key Takeaways
tools/listtells clients what tools are availabletools/callhandles actual invocationsinputSchemadefines parameters — make them clear and descriptiveNext Steps
#MCP #Tutorial #NodeJS #mcpm #AIAgents