Skip to content
Zayne Lovecraft
All posts

AI · · 3 min read · Updated

Building Custom MCP Servers in TypeScript for Cursor

How to build a custom Model Context Protocol (MCP) server in TypeScript and wire it into multi-step Vercel AI SDK and Markdown publishing workflows.

Zayne Lovecraft

Software Engineer III

From below of long thin blue cables connected to row of small white connectors on system block in data center
Photo by Brett Sayles on Pexels

Out of the box, coding agents in Cursor and Claude Code can read files and run shell commands. Where they become exponentially more useful is when you give them domain-specific tools via the Model Context Protocol (MCP)—so turning external knowledge into shippable code or content happens in a single tool call.

Here is how I built three small open-source tools—yt-skill-mcp (case study), ai-content-workflow, and md-cover—and how they fit together inside an agent workflow.

1. Anatomy of a stdio MCP server (yt-skill-mcp)

Whenever I watch a great conference talk or deep-dive tutorial on YouTube, I want the architectural checklist saved as a reusable .cursor/skills/<slug>/SKILL.md file inside my Agent OS workspace.

Instead of copying transcripts by hand, yt-skill-mcp exposes two MCP tools over stdio using @modelcontextprotocol/sdk:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { buildSkillMarkdown, fetchTranscript } from "./transcript.js";

const server = new Server(
  { name: "yt-skill-mcp", version: "0.1.0" },
  { capabilities: { tools: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "fetch_youtube_transcript",
      description: "Fetch timestamped transcript segments from a YouTube URL or video ID.",
      inputSchema: {
        type: "object",
        properties: {
          url: { type: "string", description: "YouTube watch URL, youtu.be link, or 11-char ID" },
        },
        required: ["url"],
      },
    },
    {
      name: "compile_cursor_skill",
      description: "Format extracted video frameworks into a Cursor Agent SKILL.md file.",
      inputSchema: {
        type: "object",
        properties: {
          skillName: { type: "string" },
          description: { type: "string" },
          videoId: { type: "string" },
          title: { type: "string" },
          steps: { type: "array", items: { type: "string" } },
        },
        required: ["skillName", "description", "videoId", "title", "steps"],
      },
    },
  ],
}));

Once registered in .cursor/mcp.json, dropping a YouTube URL into Cursor lets the agent pull the timestamped transcript ([141s] ...), extract the engineering rules, and write .cursor/skills/<name>/SKILL.md in seconds.

2. Schema-enforced editorial pipelines (ai-content-workflow)

When you move from interactive editor tools to automated publishing (like the multi-site pipeline I built for SynthPress), a single LLM prompt isn't enough. One-shot prompts produce generic prose and miss metadata constraints like 65-character title limits.

In ai-content-workflow, I open-sourced the three-stage pattern I use with the Vercel AI SDK (generateObject + generateText) and Zod:

StageFunctionGuardrail
1. BriefplanArticleBrief()Zod-enforced title (20–65 chars), meta description (80–160 chars), section outline, and FAQ schema
2. DraftdraftArticleMarkdown()Grounded technical prose with strict anti-filler system constraints
3. CriticverifyArticleQuality()Structured quality gate that flags generic AI phrases and scores 0–100 before publish
export const QualityGateSchema = z.object({
  passed: z.boolean(),
  score: z.number().min(0).max(100),
  aiSlopDetected: z.array(z.string()),
  missingEntities: z.array(z.string()),
  revisedMetaDescription: z.string().optional(),
});

Separating the writer from the critic catches weak intros and missing technical specifics before a human ever reviews the draft.

3. Closing the last mile with md-cover

Even if an agent writes clean Markdown, publishing a blog post usually stalls on assets: finding a landscape photo, cropping it to Open Graph dimensions, saving it to public/blog/<slug>/cover.jpg, and crediting the photographer in the frontmatter.

I extracted the script that powers this blog into md-cover:

npx github:zaynelovecraft/md-cover building-custom-mcp-servers "server racks data center"

In one command, it queries the Pexels API, downloads a 1600×840 cropped JPEG, and writes cover, coverAlt, coverCredit, and coverSourceUrl straight into the Markdown frontmatter using gray-matter.

Takeaway

The best AI developer tools do one narrow job deterministically and expose clean inputs and outputs so an agent can chain them together. Check out the source for yt-skill-mcp, ai-content-workflow, and md-cover on GitHub—or read how I audit the finished pages in Generative Engine Optimization (GEO) & CWV Gates in Next.js.

  • MCP
  • TypeScript
  • Cursor
  • AI SDK
  • AI Automation

Keep reading