Skip to content

Instantly share code, notes, and snippets.

@mattpocock

mattpocock/01.md Secret

Created April 7, 2025 16:09
Show Gist options
  • Save mattpocock/0aae0ed9b604750f07dee0ea75d8b03d to your computer and use it in GitHub Desktop.
Save mattpocock/0aae0ed9b604750f07dee0ea75d8b03d to your computer and use it in GitHub Desktop.

Problem

We want to set up a new MCP server, written in TypeScript. We are starting from an empty directory.

We are writing this in Cursor, so recording the important files in a .cursor/rules/important-files.mdc file is important.

We need to set up the basic file system for the project, install necessary dependencies, and set up the project structure.

Supporting Information

Tools

pnpm

Use pnpm as the package manager.

File Structure

Recommended file structure:

.cursor/rules/important-files.mdc

A file that lists the important files for the project, which should be included in every chat.

Use the mdc format, which is a markdown format with these frontmatter fields:

---
globs: **/**
alwaysApply: true
---

...content goes here...

Make sure to add a directive at the end of the file that if new files are added, they should be added to the important-files.mdc file.

package.json

The package.json file for the project.

Recommended scripts:

build: Builds the project using tsc. dev: Runs the project in development mode using tsx watch src/main.ts.

Dependencies:

  • @modelcontextprotocol/sdk: The MCP SDK. Latest version is 0.9.0.
  • zod: A schema declaration and validation library for TypeScript.

Dev dependencies:

  • tsx: A faster version of ts-node that is optimized for the CLI.
  • typescript: The TypeScript compiler, latest version: 5.8
  • @types/node: The types for Node.js, for 22+

bin should be set to dist/main.js.

type should be set to module.

tsconfig.json

The TypeScript configuration file for the project. Here is the recommended configuration from Matt Pocock's TSConfig cheat sheet.

{
  "compilerOptions": {
    /* Base Options: */
    "esModuleInterop": true,
    "skipLibCheck": true,
    "target": "es2022",
    "allowJs": true,
    "resolveJsonModule": true,
    "moduleDetection": "force",
    "isolatedModules": true,
    "verbatimModuleSyntax": true,

    /* Strictness */
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,

    /* If transpiling with TypeScript: */
    "module": "NodeNext",
    "outDir": "dist",
    "rootDir": "src",
    "sourceMap": true,

    /* AND if you're building for a library: */
    "declaration": true,

    /* If your code doesn't run in the DOM: */
    "lib": ["es2022"]
  },
  "include": ["src"]
}

src/main.ts

The entry point for the project.

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

// Create an MCP server
const server = new McpServer({
  name: "Demo",
  version: "1.0.0",
});

// Add an addition tool
server.tool("add", { a: z.number(), b: z.number() }, async ({ a, b }) => ({
  content: [{ type: "text", text: String(a + b) }],
}));

// Add a dynamic greeting resource
server.resource(
  "greeting",
  new ResourceTemplate("greeting://{name}", { list: undefined }),
  async (uri, { name }) => ({
    contents: [
      {
        uri: uri.href,
        text: `Hello, ${name}!`,
      },
    ],
  })
);

// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);

.gitignore

A file that lists the files to ignore in the project. dist should be ignored since it is the output directory.

Steps To Complete

  • Create the package.json file with the recommended scripts and dependencies.
  • Use a pnpm add command to install the dependencies so that they are pinned to the current version. Do NOT use latest or next.
  • Install the dependencies.
  • Create the tsconfig.json file with the recommended configuration.
  • Create the other files described above.
  • Run pnpm build to build the project.

Problem

Our MCP server currently doesn't do anything. We want to hook it up to GitHub so that it can respond to issues and pull requests.

Supporting Information

Octokit

We are using Octokit to interact with GitHub.

pnpm add octokit

Octokit methods should be put in their own file, in src/github.ts.

import { Octokit } from "octokit";

const octokit = new Octokit({ auth: `personal-access-token123` });

Environment variables can be put in a .env file.

tsx and .env

You can pass .env to tsx by using the --env-file flag.

tsx watch --env-file=.env src/main.ts

Always Declare Tool Descriptions.

When calling server.tool(), you can pass in both a name and a description. For instance:

server.tool(
  "createPullRequestComment",
  "Create a comment on a pull request",
  {
    owner: z.string(),
    repo: z.string(),
    pullNumber: z.number(),
    body: z.string(),
  },
  async ({ owner, repo, pullNumber, body }) => {
    const comment = await github.createPullRequestComment(
      { owner, repo },
      pullNumber,
      body
    );
    return {
      content: [{ type: "text", text: JSON.stringify(comment, null, 2) }],
    };
  }
);

This should be done for all tools, since it helps the MCP client understand the tool and its capabilities.

Steps To Complete

  • Ensure that .env is added to the .gitignore file.
  • Get the user to add a GitHub token to .env.
  • Ensure that .env is being loaded into the environment when running the dev script.
  • Install octokit as a dependency.
  • Create functions for issues and pull requests:
    • getIssue: Get an issue by number.
    • getPullRequest: Get a pull request by number.
    • createIssueComment: Create a comment on an issue.
    • createPullRequestComment: Create a comment on a pull request.
    • updateIssue: Update an issue.
    • updatePullRequest: Update a pull request.
    • listIssues: List issues for a repository.
    • listPullRequests: List pull requests for a repository.
  • Remove the demo tools from the existing MCP server.
  • Add those tools to the MCP server.
  • Ensure that all major files are added to the .cursor/rules/important-files.mdc file.

Problem

Our server currently only works over stdio. We may want to convert it to work over SSE instead.

Supporting Information

So far, I have only been able to get SSE working using the express framework.

Latest version is 5.1.0. Latest version of @express/types is 5.0.1.

pnpm add express
import express from "express";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";

const app = express();

let transport: SSEServerTransport | undefined = undefined;

app.get("/sse", async (req, res) => {
  transport = new SSEServerTransport("/messages", res);
  await server.connect(transport);
});

app.post("/messages", async (req, res) => {
  if (!transport) {
    res.status(400);
    res.json({ error: "No transport" });
    return;
  }
  await transport.handlePostMessage(req, res);
});

app.listen(8080, () => {
  console.log("Server is running on port 8080");
});

Steps To Complete

  • Install express
  • Remove the stdio transport.
  • Add the sse transport.
  • Ensure that all major files are added to the .cursor/rules/important-files.mdc file.

Problem

The files are getting a bit too big. We want to break them up into smaller chunks to make them easier for the Cursor agent to understand.

Supporting Information

We have two concepts in the repo: tools and github-functions.

  • github-functions are the way that we connect to GitHub
  • tools are the things that get attached to the MCP server. tools contain github-functions.

We want to break up the large files we have into smaller chunks, using a file structure which reflects the relationship between tools and github-functions.

Steps To Complete

  • Break up the large files into smaller chunks, using the file structure above.

  • Each file may contain multiple github-functions or tools, but they should be split by domain.

  • Ensure that all major files are added to the .cursor/rules/important-files.mdc file.

Problem

I want to be able to check the status of actions on the MCP server.

Supporting Information

Always ensure that page and per_page are included in the request parameters, even if the API doesn't require them.

Steps To Complete

  • Look at the existing implementations of functions and tools in the correct directory. Load them into your context window.

  • Add functions to:

    • List the actions in a repository.
    • Get the status of an action.
    • Get the details of an action.
    • Cancel an action.
    • Retry an action.
  • Add those functions as tools to the MCP server.

  • Ensure that all major files are added to the .cursor/rules/important-files.mdc file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment