How to Build Skills for Claude: A Complete Guide
Purpose
This post demonstrates how to build Claude skills from scratch. I tried using Claude skills for a while and found them powerful but confusing at first. The main challenge is understanding how skills work beyond just reading the documentation.
Environment
- Claude CLI installed
- Basic knowledge of JavaScript/TypeScript
- Text editor or IDE
- API access tokens for external services
What are Claude Skills?
When I first started with Claude skills, I thought they were just simple tool extensions. But they’re actually more complex - they’re modular extensions that extend Claude’s capabilities by accessing external tools, APIs, and data sources.
Claude skills can:
- Access external APIs and services
- Handle structured and unstructured data
- Run scripts and commands safely
- Retrieve information from various sources
The key benefit is they allow Claude to perform specialized tasks beyond its core functionality.
Setting Up Your Development Environment
I started by checking if Claude CLI was installed properly:
claude --versionIf you get an error, install it first:
npm install -g @anthropic-ai/claude-cliThen authenticate:
claude authThis will prompt you to log in to your Anthropic account.
Creating Your First Skill
1. Define the Skill Manifest
The first step is creating a skill manifest. I tried this and got confused about the required fields. Here’s what worked for me:
{ "name": "file-processor", "description": "Processes and analyzes files", "version": "1.0.0", "author": "cowrie", "main": "index.js", "tools": [ { "name": "read_file", "description": "Read a file from the filesystem", "parameters": { "type": "object", "properties": { "path": { "type": "string", "description": "Path to the file" } }, "required": ["path"] } }, { "name": "analyze_content", "description": "Analyze file content for patterns", "parameters": { "type": "object", "properties": { "content": { "type": "string", "description": "File content to analyze" } }, "required": ["content"] } } ]}2. Implement Core Functionality
Next, I created the main skill file:
const fs = require('fs');const path = require('path');
async function read_file(params) { try { const filePath = path.resolve(params.path); const content = fs.readFileSync(filePath, 'utf8'); return { success: true, content: content, path: filePath }; } catch (error) { return { success: false, error: error.message }; }}
async function analyze_content(params) { const words = params.content.split(/\s+/); const lines = params.content.split('\n');
// Simple word frequency analysis const wordFreq = {}; words.forEach(word => { const cleanWord = word.toLowerCase().replace(/[^\w]/g, ''); if (cleanWord) { wordFreq[cleanWord] = (wordFreq[cleanWord] || 0) + 1; } });
// Find most common words const sortedWords = Object.entries(wordFreq) .sort(([,a], [,b]) => b - a) .slice(0, 10);
return { success: true, stats: { totalWords: words.length, totalLines: lines.length, uniqueWords: Object.keys(wordFreq).length }, topWords: sortedWords, wordFrequency: wordFreq };}
// Export the toolsmodule.exports = { read_file, analyze_content};3. Testing and Validation
I tested this skill with a sample file:
const skill = require('./index.js');
// Create test contentconst testContent = `Hello world!This is a test file.Hello again world.Testing skills development.`;
console.log('Testing analyze_content tool:');analyze_content(skill.analyze_content({ content: testContent })) .then(result => console.log('Result:', result));
console.log('Testing read_file tool:');skill.read_file({ path: 'sample.txt' }) .then(result => console.log('Result:', result));Running this gave me:
node test.jsTesting analyze_content tool:Result: { success: true, stats: { totalWords: 11, totalLines: 4, uniqueWords: 8 }, topWords: [ [ 'hello', 2 ], [ 'world', 2 ], [ 'test', 2 ], [ 'this', 1 ] ], wordFrequency: { hello: 2, world: 2, this: 1, is: 1, a: 1, test: 2, file: 1, skills: 1, development: 1 }}Advanced Skill Development Techniques
Multi-step Workflows
When I tried building more complex skills, I found that single-tool skills are limiting. I needed to chain multiple tools together:
async function process_document(params) { // Step 1: Read the file const readResult = await read_file({ path: params.path }); if (!readResult.success) { return readResult; }
// Step 2: Analyze content const analysisResult = await analyze_content({ content: readResult.content }); if (!analysisResult.success) { return analysisResult; }
// Step 3: Generate summary const summary = { path: readResult.path, wordCount: analysisResult.stats.totalWords, readingTime: Math.ceil(analysisResult.stats.totalWords / 200), // 200 words per minute keyPoints: analysisResult.topWords.slice(0, 5).map(([word, count]) => `${word} (${count} times)` ) };
return { success: true, summary: summary, analysis: analysisResult };}Performance Optimization
I noticed that reading large files caused timeouts. Here’s how I handled it:
const fs = require('fs');const { promisify } = require('util');const readFile = promisify(fs.readFile);
async function read_large_file(params) { try { const filePath = path.resolve(params.path);
// Check file size first const stats = await fs.promises.stat(filePath); const fileSizeMB = stats.size / (1024 * 1024);
if (fileSizeMB > 10) { // For large files, read in chunks const content = await readFile(filePath, 'utf8', { encoding: 'utf8' }); return { success: true, content: content, size: `${fileSizeMB.toFixed(2)}MB`, note: 'Large file processed successfully' }; } else { // For small files, read normally const content = await readFile(filePath, 'utf8'); return { success: true, content: content, size: `${fileSizeMB.toFixed(2)}MB` }; } } catch (error) { return { success: false, error: error.message }; }}Security Best Practices
When I was building my first skills, I made security mistakes. Here’s what I learned:
Input Validation
function validateFilePath(filePath) { // Prevent directory traversal attacks if (filePath.includes('..') || filePath.startsWith('/')) { throw new Error('Invalid file path'); }
// Only allow certain file extensions const allowedExtensions = ['.txt', '.md', '.js', '.json']; const extension = path.extname(filePath).toLowerCase();
if (!allowedExtensions.includes(extension)) { throw new Error('File type not allowed'); }
return true;}
async function safe_read_file(params) { try { validateFilePath(params.path); const result = await read_file(params); return result; } catch (error) { return { success: false, error: error.message }; }}Secure Token Handling
I used to hardcode API keys. That’s a bad practice:
// NEVER do this:const apiKey = "sk-proj-12345";
// Do this instead:const config = { apiKey: process.env.API_KEY, timeout: parseInt(process.env.TIMEOUT) || 30000, maxRetries: parseInt(process.env.MAX_RETRIES) || 3};
if (!config.apiKey) { throw new Error('API_KEY environment variable not set');}Integration Patterns and Use Cases
Database Operations
I tried connecting to a database:
const { Client } = require('pg');
async function query_database(params) { const client = new Client({ connectionString: process.env.DATABASE_URL });
try { await client.connect(); const result = await client.query(params.query); return { success: true, rows: result.rows, rowCount: result.rowCount }; } catch (error) { return { success: false, error: error.message }; } finally { await client.end(); }}API Integration
Here’s how I integrated with external APIs:
const https = require('https');
async function fetch_data(params) { return new Promise((resolve, reject) => { const options = { hostname: 'api.example.com', path: params.endpoint, method: 'GET', headers: { 'Authorization': `Bearer ${process.env.API_TOKEN}`, 'Content-Type': 'application/json' } };
const req = https.request(options, (res) => { let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => { try { const parsed = JSON.parse(data); resolve({ success: true, data: parsed, status: res.statusCode }); } catch (error) { resolve({ success: false, error: 'Invalid JSON response' }); } }); });
req.on('error', (error) => { resolve({ success: false, error: error.message }); });
req.setTimeout(10000, () => { req.destroy(); resolve({ success: false, error: 'Request timeout' }); });
req.end(); });}Troubleshooting Common Issues
Tool Timeout Issues
When I first built skills, I kept hitting timeouts. Here’s how I fixed it:
async function tool_with_timeout(params) { const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Tool timeout')), 5000); });
try { const result = await Promise.race([ read_file(params), timeoutPromise ]); return result; } catch (error) { return { success: false, error: error.message }; }}Authentication Failures
I struggled with API authentication. The key is using environment variables:
# Set environment variablesexport API_TOKEN="your_token_here"export DATABASE_URL="your_connection_string"
# Or use a .env fileecho "API_TOKEN=your_token_here" >> .envecho "DATABASE_URL=your_connection_string" >> .envPerformance Bottlenecks
For slow operations, I added caching:
const NodeCache = require('node-cache');const fileCache = new NodeCache({ stdTTL: 300, checkperiod: 600 });
async function cached_read_file(params) { const cacheKey = `file_${params.path}`; const cached = fileCache.get(cacheKey);
if (cached) { return { success: true, content: cached, cached: true }; }
const result = await read_file(params); if (result.success) { fileCache.set(cacheKey, result.content); }
return result;}The Reason I Built This
I think the key reason skills are confusing at first is that they’re not just simple tools. They’re a complete ecosystem that requires:
- Proper manifest structure
- Error handling at every step
- Security considerations
- Performance optimization
- Integration with existing systems
The main takeaway is that Claude skills are powerful but require careful planning and implementation.
Summary
In this post, I showed how to build Claude skills from scratch with practical examples. The key point is understanding that skills are more than just tool definitions - they’re complete applications with security, performance, and integration considerations.
When I first started, I made mistakes like hardcoding API keys and ignoring error handling. Now I know better. Skills should be robust, secure, and efficient.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments