How to Build Your First MCP Skill
A practical tutorial for publishing a skill on SkillExchange.
If you can build an API endpoint, you can build an MCP skill. This tutorial walks you through the entire process — from concept to publication — in about 30 minutes.
We'll build a text summarization skill that takes a long text and returns a concise summary. It's a useful capability, easy to implement, and demonstrates all the key concepts.
What You'll Need
- Node.js 18+ installed
- A code editor
- An HTTPS endpoint (we'll use localhost with a tunnel for testing)
- A SkillExchange account (free to create)
Optional but Recommended
- A tunneling tool like
ngrokorcloudflaredfor local testing - Docker for production deployment
Step 1: Define Your Skill
Before writing code, clarify three things:
- Name:
text-summarizer(kebab-case, unique) - Description: "Summarizes long text into concise summaries with configurable length"
- Pricing: Per-use, €0.50 per invocation
Input/Output Design
Input:
{
"text": "Long text to summarize...",
"maxLength": 150,
"language": "en"
}
Output:
{
"summary": "Concise summary of the text",
"originalLength": 2340,
"summaryLength": 142,
"compressionRatio": 0.06
}
Clear schemas make it easy for agents to use your skill correctly.
Step 2: Build the API Endpoint
Create a new project:
mkdir text-summarizer && cd text-summarizer
npm init -y
npm install express zod
Create src/index.ts:
import express from 'express';
import { z } from 'zod';
const app = express();
app.use(express.json());
// Input validation schema
const InputSchema = z.object({
text: z.string().min(10).max(50000),
maxLength: z.number().min(20).max(1000).default(150),
language: z.enum(['en', 'de', 'fr', 'es']).default('en')
});
// Health check endpoint
app.get('/health', (_req, res) => {
res.json({ status: 'ok', version: '1.0.0' });
});
// Main execution endpoint
app.post('/execute', async (req, res) => {
try {
// Validate input
const input = InputSchema.parse(req.body.input);
// Your summarization logic here
// This example uses a simple extractive approach
const summary = summarize(input.text, input.maxLength);
res.json({
output: {
summary,
originalLength: input.text.length,
summaryLength: summary.length,
compressionRatio: Number((summary.length / input.text.length).toFixed(2))
},
metadata: {
executedAt: new Date().toISOString(),
version: '1.0.0'
}
});
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({
error: 'validation_error',
message: error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')
});
return;
}
res.status(500).json({
error: 'execution_error',
message: 'An unexpected error occurred during summarization'
});
}
});
// Simple extractive summarization (replace with your actual logic)
function summarize(text: string, maxLength: number): string {
const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
// Score sentences by word frequency (simplified)
const wordFreq: Record<string, number> = {};
text.toLowerCase().split(/\s+/).forEach(w => {
wordFreq[w] = (wordFreq[w] || 0) + 1;
});
const scored = sentences.map(s => ({
text: s.trim(),
score: s.toLowerCase().split(/\s+/).reduce((sum, w) => sum + (wordFreq[w] || 0), 0)
}));
scored.sort((a, b) => b.score - a.score);
let result = '';
for (const s of scored) {
if ((result + ' ' + s.text).length > maxLength) break;
result += (result ? ' ' : '') + s.text;
}
return result;
}
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Text Summarizer skill running on port ${PORT}`);
});
Why This Structure?
- Input validation: Zod ensures the input matches the schema. Agents get clear error messages.
- Health check: SkillExchange monitors
/healthto verify your skill is running. - Structured output: Consistent response format with metadata.
- Error handling: Meaningful error responses help agents (and developers) debug issues.
Step 3: Create the MCP Manifest
Create mcp-manifest.json in your project root:
{
"name": "text-summarizer",
"version": "1.0.0",
"description": "Summarizes long text into concise summaries with configurable length. Supports English, German, French, and Spanish.",
"author": "your-name",
"endpoint": "https://your-skill.example.com/execute",
"healthEndpoint": "https://your-skill.example.com/health",
"inputSchema": {
"type": "object",
"properties": {
"text": {
"type": "string",
"minLength": 10,
"maxLength": 50000,
"description": "The text to summarize"
},
"maxLength": {
"type": "number",
"minimum": 20,
"maximum": 1000,
"default": 150,
"description": "Maximum length of the summary in characters"
},
"language": {
"type": "string",
"enum": ["en", "de", "fr", "es"],
"default": "en",
"description": "Language of the input text"
}
},
"required": ["text"]
},
"outputSchema": {
"type": "object",
"properties": {
"summary": {
"type": "string",
"description": "The generated summary"
},
"originalLength": {
"type": "number",
"description": "Length of the original text in characters"
},
"summaryLength": {
"type": "number",
"description": "Length of the summary in characters"
},
"compressionRatio": {
"type": "number",
"description": "Ratio of summary length to original length"
}
}
},
"pricing": {
"model": "per_use",
"amount": 50,
"currency": "EUR"
},
"tags": ["nlp", "summarization", "text-processing"],
"timeout": 10000
}
Manifest Checklist
Before publishing, verify:
-
nameis unique and kebab-case -
endpointis a valid HTTPS URL -
inputSchemamatches your actual validation logic -
outputSchemamatches your actual response shape -
pricingis set correctly (amount is in cents) -
descriptionis clear and specific
Step 4: Test Locally
Start your skill:
npx tsx src/index.ts
Test with curl:
curl -X POST http://localhost:3000/execute \
-H "Content-Type: application/json" \
-d '{
"input": {
"text": "Artificial intelligence is transforming how we work, live, and interact with technology. From healthcare diagnostics to autonomous vehicles, AI systems are becoming increasingly capable of performing complex tasks that were once the exclusive domain of human intelligence. The rapid advancement of large language models has opened new possibilities in natural language processing, content generation, and automated reasoning. As these systems become more sophisticated, questions about ethics, bias, and the societal impact of AI continue to grow in importance.",
"maxLength": 200,
"language": "en"
}
}'
Verify the response matches your output schema.
Test with SkillExchange SDK
npx skillexchange test --manifest ./mcp-manifest.json --endpoint http://localhost:3000
This runs a suite of validation checks:
- Manifest schema validation
- Health check connectivity
- Sample invocation with valid input
- Error handling with invalid input
- Response schema conformance
Fix any issues before publishing.
Step 5: Deploy and Publish
Deploy Your Skill
Deploy to any hosting platform that provides HTTPS:
- Railway:
railway up - Fly.io:
fly deploy - Vercel (serverless): Wrap your Express app as a serverless function
- Docker: Deploy to any container host
Update the endpoint and healthEndpoint in your manifest to point to your production URL.
Publish on SkillExchange
npx skillexchange publish --manifest ./mcp-manifest.json
Or through the creator dashboard:
- Navigate to
skillexchange.market/dashboard - Click "New Skill"
- Paste your manifest or upload the JSON file
- SkillExchange validates the manifest and performs a health check
- Your skill is live and discoverable
Set Up Payouts
Before your skill generates revenue, complete the Stripe Connect onboarding from your dashboard. See the Creator Guide for details.
Step 6: Let Agents Use Your Skill
Once published, your skill is discoverable via:
- SkillExchange registry: Agents query the marketplace for skills matching their needs
- A2A Agent Card: Your skill's machine-readable profile is available for autonomous discovery
- Direct URL: Agents can be configured to use your skill directly via its manifest URL
Monitoring
Track your skill's performance in the creator dashboard:
- Invocations: Total and per-day
- Success rate: Percentage of successful executions
- Latency: Average response time
- Revenue: Total earned and per-day breakdown
Use this data to optimize your skill, adjust pricing, and identify issues early.
Next Steps
- Improve the summarization: Replace the extractive approach with an LLM-based abstractive method
- Add more languages: Extend the
languageenum in both your code and manifest - Add batch processing: Accept an array of texts and return summaries for each
- Create more skills: Each distinct capability should be its own skill
Common Pitfalls
| Issue | Solution |
|---|---|
| Manifest validation fails | Check all required fields; validate JSON syntax |
| Health check timeout | Ensure your /health endpoint responds within 5 seconds |
| Input schema mismatch | Your Zod validation and manifest inputSchema must be identical |
| Pricing in wrong unit | Remember: amount is in cents. 50 = €0.50 |
| HTTPS required | Use a reverse proxy or hosting platform that provides TLS |
Resources
- Full API Documentation:
skillexchange.market/docs - Creator Guide: Available in the SkillExchange documentation
- Example Skills:
github.com/skillexchange/examples - Community: Join the SkillExchange Discord for help and feedback
Your first skill is the hardest. Every skill after that gets easier. Start building.