Build Your First MCP Server in TypeScript

A
Author
20 Sep, 2026 1 min read 745 views

In this tutorial you'll build an MCP server that exposes a get_github_repo tool, and run it over stdio so desktop AI apps can launch it.

1. Set up the project

mkdir github-mcp && cd github-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
npx tsc --init

In package.json set "type": "module".

2. Write the server

// src/index.ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';

const server = new McpServer({ name: 'github-info', version: '1.0.0' });

server.registerTool(
  'get_github_repo',
  {
    title: 'Get GitHub repository',
    description: 'Get stars, forks, open issues and description of a public GitHub repository.',
    inputSchema: {
      owner: z.string().describe('Repository owner, e.g. facebook'),
      repo: z.string().describe('Repository name, e.g. react'),
    },
  },
  async ({ owner, repo }) => {
    const res = await fetch(`https://api.github.com/repos/${owner}/${repo}`);
    if (!res.ok) {
      return { content: [{ type: 'text', text: `GitHub returned ${res.status} for ${owner}/${repo}` }], isError: true };
    }
    const r = await res.json();
    const summary = `${r.full_name}: ${r.description}\nStars: ${r.stargazers_count}, forks: ${r.forks_count}, open issues: ${r.open_issues_count}`;
    return { content: [{ type: 'text', text: summary }] };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);
console.error('github-info MCP server running on stdio'); // stderr, never stdout

3. Build and try it

npx tsc
npx @modelcontextprotocol/inspector node build/index.js

The MCP Inspector opens in your browser, lists the tool and lets you call it with test arguments.

Things that trip people up

  • Never write to stdout in a stdio server (no console.log). Stdout carries the protocol; use console.error for logs.
  • Return tool failures with isError: true so the model sees the problem and can recover.
  • Imports end in .js because the SDK is published as ES modules.
Was this article helpful?

Comments

No comments yet. Have a question or a tip? Be the first to share it.

Leave a comment

Comments are reviewed before they appear.