Chris McKenzie

Jun 02, 2025 • 10 min read

Getting Started: Build a Model Context Protocol Server

Streamlining LLM Integration: Building a JavaScript MCP Server for Hacker News

Getting Started: Build a Model Context Protocol Server

Integrating LLMs into real products still feels messier than it should be. Instead of clean patterns or shared infrastructure, most devs end up hacking together one-off code to connect models with APIs, databases, or business logic — none of it reusable, scalable, or easy to maintain.

The Model Context Protocol (MCP) aims to fix that. It’s a minimal, open standard from Anthropic. It provides a unified way for exposing tools, data, and prompts to language models in a structured, predictable way. Instead of building a new integration layer for every app or agent, MCP gives you a common interface — and it already works with applications like Claude Desktop, Cursor, and Windsurf.

In this article, we’ll walk through the basic steps of building a simple MCP server in JavaScript that will expose two tools for interacting with Hacker News.

TL;DR on Model Context Protocol

If you’re already familiar with MCP, you can skip ahead to the “Demo” section.

The Model Context Protocol (MCP) is an open standard for how applications provide data and tools to an LLM. Think of it like an electrical outlet for AI — one universal way to connect models to content, business tools, standard prompts. Instead of custom integrations for every data source, MCP enables secure, scalable, and efficient AI connections, so developers can focus on building amazing products instead of maintaining endless adapters.

Clients

MCP clients sit between your app (like a chatbot or AI-powered IDE) and an MCP server. They handle the connection, manage discovery and security, and translate the app’s requests into something the server can understand. Popular clients include Claude Desktop, Cursor, and Windsurf. You can find the full list in the docs.

Servers

An MCP server connects to your data — files, APIs, databases — and exposes it to the model through a standard interface. It can provide tools (functions the model can call), resources (data the model can read), and prompts (predefined inputs to guide model behavior).

Under the hood, both clients and servers use JSON-RPC 2.0 for communication and support multiple transport layers like stdio and HTTP via Server-Sent Events (SSE).

You can browse the server implementations on GitHub, or dive deeper with the Getting Started guide or the official docs.

Key Concepts

  • ToolsFunctions exposed to the model — can be called by the LLM

  • ResourcesFiles, APIs, or datasets the LLM can access

  • PromptsPredefined templates that help the LLM act consistently

For this demo, we’ll focus on tools.

Demo: Build a Hacker News MCP Server

Let’s build an MCP server that exposes two tools:

  1. list-top-stories – returns top 10 Hacker News posts

  2. story-details – fetches detailed info on a selected post

We’ll use @modelcontextprotocol/sdk and wire it up with a simple HN API client.

I’ve provided a starting point which handles some of the boilerplate of getting a project up and running, as well as a simple Hacker News API Client so we can fetch the data.

Step 1: Setup Starter Project

  1. Clone the repo

git clone git@github.com:kenzic/mcp-hacker-news-demo.git

2. The main branch contains the final project, so we’ll switch to the demo branch for our starting point.

cd mcp-hackernews-demo && git checkout -b origin/demo demo

3. Finally, let’s install the dependencies

npm i

Now that we have the boilerplate setup, we’re ready to dive in. Open the project in your editor, and notice two files: index.js and hacker-client.js. The client is complete as is, but I encourage you to build on top of it to add new functionality. The index.js file is where we'll be doing all our coding.

Step 2: Build Our Server

For this, we’re going to use McpServer from the modelcontextprotocol SDK. The SDK supports: Resources, Tools, Prompts. However for this demo we'll only need tools.

💡 If you need more control over the server implementation you can use the Server class.

At the top add the following imports:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

Next, create a new instance of the server:

const server = new McpServer(
    {
        name: "Hacker News",
        version: "1.0.0",
    },
    {
        capabilities: {
            logging: {},
        },
    }
);

Step 3: Create Tools

In this demo we’ll be crating two tools: List Top Stories, and Story Details.

  • List Top Stories provides the MCP client with a list of the top 10 stories from Hacker News.

  • Story Details provides in-depth details of the Hacker News post, including the contents of the url the post links to.

To create a tool, simply add the following code:

server.tool("TOOL_NAME", { toolParam: z.string() }, async () => {
    // logic
    return {
        content: [{ type: "text", text: "RETURN TEXT" }],
    };
});

As defined above, this tool isn’t very helpful.

💡 For both tools we’ll be using the content return type of text, however, the specifications support: text, image, resource. Additionally, you can return multiple objects — even of different types.

List Top Stories Tool: Let’s update the tool to support listing the top stories on Hacker News. We’ll start by giving it a easy-to-understand name: list-top-stories. Next, we'll want to fetch the top stories and format the results as text:

const topStories = await client.listTopStories();
const formattedTopStories = formatTopStories(topStories);

Lastly, we’ll return the results.

The final code should look something like:

server.tool("list-top-stories", {}, async () => {
    const topStories = await client.listTopStories();
    const formattedTopStories = formatTopStories(topStories);
    return {
        content: [{ type: "text", text: formattedTopStories }],
    };
});

Great! Now that we have a list of the top ten stories on Hacker News, let’s build a tool that will allow us to retrieve the details of the story.

Story Detail Tool: We’ll create a new tool called story-details, then use the getStoryDetails method on the Hacker News client to fetch and format the story. But how does it know which story to fetch? MCP tools let you define params. The second argument to tool takes an object with the params name and types. We'll add the param{ id: z.number() } so the client can pass the article id to the tool. Your final code should look something like:

server.tool("story-details", { id: z.number() }, async ({ id }) => {
    const story = await client.getStoryDetails(id);
    const formattedStory = formatStoryDetail(story);
    return {
        content: [{ type: "text", text: formattedStory }],
    };
});

Transports: We’re almost done! Now we need to provide a method for our server to communicate with the client. As of writing, MCP SDK supports two modes of communication: Standard Input/Output (stdio) and Server-Sent Events (SSE). For this demo we’ll use stdio.

💡 See the docs for more info on the protocols, or how to define a Custom Protocol.

Start by importing StdioServerTransport at the top of your file:

import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

Next, we’ll create an instance of StdioServerTransport and connect our server. Drop the following at the bottom of the index.js file:

const transport = new StdioServerTransport();
await server.connect(transport);

Boom — you’ve created a valid MCP server.

Connecting our server to a client

That’s it — we’ve got a working MCP server. Now we just need to tell a client where to find it. If you’re using Claude Desktop, follow the steps below to connect it. If not, skip ahead to the Debugging section to inspect your server using the MCP Inspector tool.

⚠️ We’re running this server locally. You may notice most servers are configured to run using npx, but we’ll be using node to run the server locally. Because of this you’ll need to make sure you npm install the dependencies (which you already did if you followed the setup instructions).

Claude Desktop: In Claude, go to your settings, then Developer tab and click Edit Config. Once there add the “hackernews” config:

{
    "mcpServers": {
        "hackernews": {
            "command": "node",
                "args": [
                    "/path-to-folder/mcp-hacker-news-demo/index.js"
                ]
            }
        }
    }
}

Restart Claude, and prompt,

“tell me about the top stories on hacker news.”

If everything worked you should (after giving permission to use the tool) see a list of stories provided by the server. Next, you can ask it to tell you more about a specific post.

Debugging & Dev Tools

As important as knowing how to build something is knowing how to fix it when it’s not working as expected. I’ll cover a few methods for debugging, however, this is not exhaustive.

Logging

Adding logging to your server is straightforward. To log a message, simply call server.server.sendLoggingMessage.

💡 You might notice we type server twice. This is because McpServer does not expose a method for logging, and we must rely on the low-level implementation of Server, which is accessible on the property server

Creating logs

server.tool("list-top-stories", {}, async () => {
    try {
        const topStories = await client.listTopStories();
        const formattedTopStories = formatTopStories(topStories);

        // Add logging
        server.server.sendLoggingMessage({
            level: "info",
            data: `Fetched ${topStories.length} top stories.`,
        });

        return {
            content: [{ type: "text", text: formattedTopStories }],
        };
    } catch (error) {
        server.server.sendLoggingMessage({
            level: "error",
            data: `Failed to fetch stories: ${error.message}`,
        });
        throw error; // Inform the client about the failure
    }
});

Reading Logs: If you’re using the Claude Desktop app, you can view all event messages in the logs folder: tail -n 20 -F ~/Library/Logs/Claude/mcp-server-hackernews.log

MCP Inspector

The MCP Inspector is a simple GUI devtool for testing and debugging MCP servers.

To run the inspector on your Hacker News server, simply run: npx @modelcontextprotocol/inspector node index.js Once it's ready, open the url in your browser

Connect and click List tools.

Click list-top-stories and then Run Tool. Grab an ID from the results.

Click story-details, then drop in the ID and click Run Tool

If everything worked you should see the results, and the history of calls. You can expand the history items to see the detailed view of the request and response.

Next Steps

Want to keep building?

  • Add support for Ask HN and Polls
    Right now we’re only pulling top stories, but Hacker News has other types of content like Ask HN posts and polls. Add endpoints to fetch those and expose them as new tools — same pattern, just different data.

  • Let the user choose how many stories to list
    Instead of hardcoding 10 stories, update the list-top-stories tool to accept a count parameter. It’s a small change, but it makes the tool more flexible and helps illustrate how you can pass arguments into your MCP tools.

  • Explore Resources and Prompts
    This demo focused on tools, but MCP also supports resources (like documents or database queries) and prompts (predefined input templates). Try exposing a local file or API response as a resource, or create a prompt that wraps HN data in a consistent format the model can use.

Final Thoughts

MCP is one of those things that just makes sense once you try it. Instead of hacking together context with custom glue code, you get a simple, predictable way to expose tools, data, and prompts to language models — without reinventing the wheel every time.

This demo is intentionally simple, but the same approach scales. Want to let a model trigger internal workflows? Query production data? Run functions securely? MCP gives you the interface without locking you into someone else’s stack.

If you’re building anything serious with LLMs, it’s worth getting familiar with the protocol now. The ecosystem’s moving fast — and it’s a lot easier to build the right abstractions when you start with the right foundation.


To stay connected and share your journey, feel free to reach out through the following channels:

  • 👨‍💼 LinkedIn: Join me for more insights into AI development and tech innovations.

  • 🤖 JavaScript + AI: Join the JavaScript and AI group and share what you’re working on.

  • 💻 GitHub: Explore my projects and contribute to ongoing work.

  • 📚 Medium: Follow my articles for more in-depth discussions on LangSmith, LangChain, and other AI technologies.

Join Chris on Peerlist!

Join amazing folks like Chris and thousands of other people in tech.

Create Profile

Join with Chris’s personal invite link.

0

20

0