How to Build Your First MCP Skill: A Step-by-Step Guide
Building an MCP skill is one of the highest-leverage things you can do as a developer in 2026. You build it once, publish it to a marketplace, and thousands of AI agents can discover and use it autonomously.
This tutorial walks you through building, testing, and deploying a production-ready MCP skill from scratch. By the end, you'll have a skill listed on SkillExchange that agents can invoke immediately.
Prerequisites
Before we start, make sure you have:
- Node.js 20+ installed
- A basic understanding of TypeScript
- A SkillExchange account (free to create)
- A Stripe account for receiving payouts (connected during onboarding)
What We're Building
We're going to build "SEO Analyzer" β an MCP skill that takes a URL, fetches the page content, and returns a structured SEO analysis including:
- Meta tag evaluation
- Heading structure analysis
- Keyword density breakdown
- Readability score
- Actionable improvement suggestions
This is a real, useful skill that agents would pay for. Let's build it.
Step 1: Initialize Your Project
mkdir seo-analyzer-skill
cd seo-analyzer-skill
npm init -y
npm install @modelcontextprotocol/sdk zot express
npm install -D typescript @types/node @types/express tsx
Create a tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
Step 2: Build the MCP Server
Create src/index.ts:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zot";
import express from "express";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
// Initialize the MCP server
const server = new McpServer({
name: "seo-analyzer",
version: "1.0.0",
});
// Define the SEO analysis tool
server.tool(
"analyze_seo",
"Analyze a webpage's SEO quality. Returns meta tags, heading structure, keyword density, readability score, and improvement suggestions.",
{
url: z.string().url().describe("The URL of the page to analyze"),
target_keyword: z.string().optional().describe("Optional target keyword to evaluate"),
},
async ({ url, target_keyword }) => {
try {
const analysis = await performSeoAnalysis(url, target_keyword);
return {
content: [
{
type: "text" as const,
text: JSON.stringify(analysis, null, 2),
},
],
};
} catch (error) {
return {
content: [
{
type: "text" as const,
text: JSON.stringify({ error: `Failed to analyze ${url}: ${error}` }),
},
],
isError: true,
};
}
}
);
// SEO analysis logic
async function performSeoAnalysis(url: string, targetKeyword?: string) {
// Fetch the page
const response = await fetch(url);
const html = await response.text();
// Extract meta tags
const titleMatch = html.match(/<title[^>]*>(.*?)<\/title>/i);
const descMatch = html.match(/<meta\s+name=["']description["']\s+content=["'](.*?)["']/i);
const h1Matches = html.match(/<h1[^>]*>(.*?)<\/h1>/gi) || [];
const h2Matches = html.match(/<h2[^>]*>(.*?)<\/h2>/gi) || [];
// Strip HTML for text analysis
const text = html.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
const wordCount = text.split(/\s+/).length;
// Keyword density
const keywords: Record<string, number> = {};
if (targetKeyword) {
const regex = new RegExp(targetKeyword, "gi");
const matches = text.match(regex);
keywords[targetKeyword] = matches ? (matches.length / wordCount) * 100 : 0;
}
// Build suggestions
const suggestions: string[] = [];
const title = titleMatch?.[1] || "";
const description = descMatch?.[1] || "";
if (!title) suggestions.push("Missing title tag β add a descriptive <title>");
else if (title.length < 30) suggestions.push("Title tag is too short (under 30 chars)");
else if (title.length > 60) suggestions.push("Title tag is too long (over 60 chars)");
if (!description) suggestions.push("Missing meta description β add one for better CTR");
else if (description.length < 120) suggestions.push("Meta description is short β aim for 150-160 chars");
if (h1Matches.length === 0) suggestions.push("No H1 tag found β add exactly one H1");
else if (h1Matches.length > 1) suggestions.push("Multiple H1 tags found β use only one");
if (targetKeyword && keywords[targetKeyword] < 0.5) {
suggestions.push(`Keyword "${targetKeyword}" density is low (${keywords[targetKeyword].toFixed(2)}%) β aim for 1-2%`);
}
return {
url,
title,
description,
heading_structure: {
h1_count: h1Matches.length,
h2_count: h2Matches.length,
},
word_count: wordCount,
keyword_density: keywords,
suggestions,
score: calculateScore(title, description, h1Matches.length, suggestions.length),
};
}
function calculateScore(title: string, desc: string, h1Count: number, issueCount: number): number {
let score = 100;
if (!title) score -= 20;
if (!desc) score -= 15;
if (h1Count === 0) score -= 15;
if (h1Count > 1) score -= 10;
score -= issueCount * 5;
return Math.max(0, Math.min(100, score));
}
// Start the HTTP server with SSE transport
const app = express();
app.get("/sse", async (req, res) => {
const transport = new SSEServerTransport("/messages", res);
await server.connect(transport);
});
app.post("/messages", express.json(), async (req, res) => {
// Handled by SSE transport
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.log(`SEO Analyzer MCP server running on port ${PORT}`);
});
This is a complete, functional MCP skill. Let's break down what we built:
- McpServer β The core MCP server that handles protocol compliance
- Tool definition β
analyze_seowith input schema and handler - SEO analysis logic β Fetches pages, extracts metadata, calculates scores
- SSE transport β Production-ready HTTP transport for remote access
- Error handling β Graceful error responses for robustness
Step 3: Test Locally
Add a test script to package.json:
{
"scripts": {
"dev": "tsx src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
}
}
Run it:
npm run dev
You can test the skill using the MCP inspector:
npx @modelcontextprotocol/inspector
Point it at http://localhost:3001/sse and invoke the analyze_seo tool with a URL. You should see structured SEO analysis results.
Step 4: Add Skill Metadata
Create skill.json at the project root β this is what SkillExchange uses for discovery:
{
"name": "seo-analyzer",
"display_name": "SEO Analyzer",
"description": "Analyze any webpage's SEO quality. Returns meta tags, heading structure, keyword density, readability score, and actionable improvement suggestions.",
"version": "1.0.0",
"category": "Analysis",
"tags": ["seo", "analysis", "marketing", "web"],
"pricing": {
"model": "per_invocation",
"price": 0.02,
"currency": "usd"
},
"tools": [
{
"name": "analyze_seo",
"description": "Analyze a webpage's SEO quality",
"inputs": ["url", "target_keyword"]
}
],
"auth": {
"type": "api_key",
"header": "X-API-Key"
},
"endpoint": "https://your-deployed-url.com/sse"
}
Step 5: Deploy
Deploy to any platform that supports Node.js. Here's a quick Vercel example:
# Install Vercel CLI
npm i -g vercel
# Deploy
vercel --prod
Or use Docker for more control:
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY dist/ ./dist/
EXPOSE 3001
CMD ["node", "dist/index.js"]
Step 6: Publish on SkillExchange
Now for the exciting part β listing your skill on the marketplace:
- Go to skillexchange.market/creator-guide
- Connect your Stripe account for payouts
- Submit your skill with the
skill.jsonmetadata - Set your pricing (we recommend $0.01-0.05 per invocation for analysis tools)
- Publish
Once published, your skill is immediately discoverable by every agent connected to SkillExchange. When an agent needs SEO analysis, it will find your skill, evaluate it against alternatives, and invoke it β all autonomously.
Best Practices for Production Skills
Based on the most successful skills on SkillExchange, here are key practices:
Error Handling
Always return structured error responses. Agents need predictable error formats to recover gracefully:
return {
content: [{ type: "text", text: JSON.stringify({ error: "Descriptive message" }) }],
isError: true,
};
Input Validation
Use Zod schemas rigorously. Never trust agent input:
{
url: z.string().url().describe("Must be a valid URL"),
max_results: z.number().min(1).max(100).default(10),
}
Rate Limiting
Protect your skill from abuse:
import rateLimit from "express-rate-limit";
const limiter = rateLimit({
windowMs: 60 * 1000,
max: 100, // 100 requests per minute
});
app.use(limiter);
Response Format
Return structured JSON that agents can parse and reason about:
{
score: 85,
suggestions: ["Fix title tag", "Add meta description"],
details: { /* structured data */ }
}
Pricing Strategy
- $0.001-0.005 for simple lookups and transformations
- $0.01-0.05 for analysis, generation, and complex processing
- $0.10+ for computationally expensive operations
Price low enough that agents use you frequently, high enough to earn real revenue.
Monitoring Your Skill
Once deployed, monitor key metrics:
- Invocation count β How often agents use your skill
- Error rate β Percentage of failed invocations
- Latency β Average response time
- Revenue β Earnings per day/week/month
SkillExchange provides a dashboard with all of these metrics. Use them to optimize your skill over time.
Scaling Considerations
If your skill becomes popular, you'll need to scale:
- Horizontal scaling β Deploy multiple instances behind a load balancer
- Caching β Cache results for identical inputs
- Queue processing β Use job queues for long-running analyses
- CDN β Cache static responses at the edge
A skill generating 1,000+ daily invocations should plan for auto-scaling from the start.
What's Next?
Congratulations β you've built and published your first MCP skill. Here are ideas for your next one:
- Competitor Analysis β Compare SEO metrics across competing domains
- Content Scorer β Evaluate content quality against top-ranking pages
- Backlink Checker β Analyze link profiles and domain authority
- Schema Validator β Check structured data markup and rich snippet eligibility
Each of these could be a standalone skill or combined into a "SEO Toolkit" that agents invoke as a package.
Ready to publish? Create your SkillExchange account and start earning from agents worldwide. The creator guide has everything you need to get your first skill live in under 30 minutes.
Frequently Asked Questions
How long does it take to build an MCP skill?
A simple skill takes 30-60 minutes. More complex skills with external integrations might take a few hours. The MCP SDK handles protocol compliance, so you can focus on your core logic.
Do I need to handle authentication myself?
SkillExchange handles API key distribution and validation. You just need to verify the key on each request β the platform provides middleware for this.
What's the revenue potential?
It depends on your skill's utility and pricing. Skills in the analysis category average $50-500/month in their first few months. Popular skills with 10,000+ daily invocations can earn $3,000+/month.
Can I update my skill after publishing?
Yes. You can deploy updated versions at any time. SkillExchange handles versioning and ensures agents always get the latest version.
What languages can I build skills in?
Any language that can serve HTTP endpoints. The official SDKs are TypeScript and Python, but community SDKs exist for Go, Rust, Java, and more. As long as you implement the MCP protocol correctly, the language doesn't matter.
Do I need to host the skill myself?
Yes β skills run on your infrastructure. SkillExchange is a discovery and billing layer, not a hosting platform. This gives you full control over your skill's performance and data.