Zum Hauptinhalt springen

Building Your First Agent Skill: A Practical Guide

· 11 Minuten Lesezeit

Grid banner

In my previous post about MCP servers, I showed how to build an MCP (Model Context Protocol) server for exposing APIs to Claude. Today, we'll explore an alternative approach: Agent Skills - a simpler, more flexible format also created by Anthropic.

While MCP provides strong process isolation and security boundaries, Agent Skills offers a lightweight, filesystem-based approach that's perfect for rapid prototyping and personal workflows. In this tutorial, we'll build a practical weather forecast skill that demonstrates the core concepts.

What Are Agent Skills?

Agent Skills are modular capabilities packaged as directories containing:

  • A SKILL.md file with YAML frontmatter and instructions
  • Optional Python/JavaScript scripts
  • Reference documentation and assets

The key difference from MCP: Skills are discovered and loaded from the filesystem, not run as separate processes. This makes them:

  • Simpler to create: No SDK or protocol knowledge required
  • Easier to debug: Just markdown and scripts
  • Portable: Work across Claude Code, VS Code Copilot, and other Agent Skills-compatible clients
  • Flexible: The AI can even write and modify skills on-the-fly

Let's build one.

Project Setup

tipp

The complete code for this tutorial is available on GitHub.

We'll create a weather forecast skill that fetches real-time weather data from the National Weather Service API (no API key required for US locations).

Directory Structure

mkdir -p ~/.claude/skills/weather-forecast
cd ~/.claude/skills/weather-forecast

The standard skill locations are:

  • Personal skills: ~/.claude/skills/ or ~/.copilot/skills/
  • Project skills: .claude/skills/ or .github/skills/ in your repo
tipp

Claude Code and VS Code Copilot scan these directories automatically at startup.

Creating the SKILL.md File

Create ~/.claude/skills/weather-forecast/SKILL.md:

SKILL.md
---
name: weather-forecast
description: Fetch real-time weather forecasts for US locations using the National Weather Service API. Use when the user asks about weather, temperature, or conditions for a specific US city or coordinates.
license: MIT
metadata:
author: Nils Friedrichs
version: "1.0.0"
capabilities:
- weather-data
- temperature-alerts
- forecast-summary
---

# Weather Forecast Skill

This skill provides access to real-time weather data from the National Weather Service (NWS) API.

## Capabilities

- **Current conditions**: Temperature, humidity, wind speed
- **7-day forecast**: Daily summaries with high/low temperatures
- **Weather alerts**: Active warnings and watches
- **Hourly forecast**: Detailed hour-by-hour predictions

## Usage

When the user asks about weather, follow this workflow:

1. **Get coordinates**:
- If the user provides a city name, use the geocoding script to get lat/lon
- If they provide coordinates directly, use those

2. **Fetch forecast**:
- Run `scripts/get_forecast.py --lat <latitude> --lon <longitude>`
- The script returns JSON with forecast data

3. **Present results**:
- Summarize the current conditions clearly
- Include the 7-day forecast if requested
- Highlight any active weather alerts

## Scripts

### `scripts/get_forecast.py`

Fetches weather data from NWS API. Requires latitude and longitude.

**Usage**:
```bash
python scripts/get_forecast.py --lat 40.7128 --lon -74.0060
```

**Output**: JSON with current conditions and forecast

### `scripts/geocode.py`

Converts city names to coordinates using Nominatim OpenStreetMap API.

**Usage**:
```bash
python scripts/geocode.py "New York, NY"
```

**Output**: JSON with latitude and longitude

## Error Handling

- **Invalid coordinates**: NWS API only covers US territories. Return helpful message if coordinates are outside coverage.
- **API errors**: If API is down, inform user and suggest trying again later.
- **Missing city**: If geocoding fails, ask user for coordinates directly.

## Examples

**User**: "What's the weather in Seattle?"

**Response**:
1. Geocode "Seattle, WA" → lat: 47.6062, lon: -122.3321
2. Fetch forecast for those coordinates
3. Present: "In Seattle, it's currently 52°F and partly cloudy. High of 58°F today with a 30% chance of rain this evening."

**User**: "Will it rain in Chicago this week?"

**Response**:
1. Geocode "Chicago, IL"
2. Fetch 7-day forecast
3. Analyze precipitation chances
4. Present: "Looking at Chicago's 7-day forecast, rain is likely on Thursday (80% chance) and Saturday (60% chance). The rest of the week looks clear."

Creating the Python Scripts

Install Dependencies

Create a virtual environment (recommended)

python -m venv ~/.claude/skills/weather-forecast/venv
source ~/.claude/skills/weather-forecast/venv/bin/activate

Install required packages

pip install requests

Geocoding Script

Create ~/.claude/skills/weather-forecast/scripts/geocode.py:

scripts/geocode.py
#!/usr/bin/env python3
"""
Geocode city names to coordinates using Nominatim OpenStreetMap API.
"""
import sys
import json
import requests

def geocode_city(city_name):
"""
Convert city name to coordinates.

Args:
city_name: City name (e.g., "New York, NY" or "Seattle")

Returns:
dict: {"lat": float, "lon": float, "display_name": str}
"""
url = "https://nominatim.openstreetmap.org/search"
params = {
"q": city_name,
"format": "json",
"limit": 1,
"countrycodes": "us" # Limit to US since NWS is US-only
}
headers = {
"User-Agent": "WeatherForecastSkill/1.0"
}

try:
response = requests.get(url, params=params, headers=headers, timeout=10)
response.raise_for_status()

results = response.json()
if not results:
return {"error": f"City not found: {city_name}"}

location = results[0]
return {
"lat": float(location["lat"]),
"lon": float(location["lon"]),
"display_name": location["display_name"]
}

except requests.RequestException as e:
return {"error": f"Geocoding failed: {str(e)}"}

if __name__ == "__main__":
if len(sys.argv) < 2:
print(json.dumps({"error": "Usage: geocode.py <city name>"}))
sys.exit(1)

city = " ".join(sys.argv[1:])
result = geocode_city(city)
print(json.dumps(result, indent=2))

Make it executable:

chmod +x ~/.claude/skills/weather-forecast/scripts/geocode.py

Weather Forecast Script

Create ~/.claude/skills/weather-forecast/scripts/get_forecast.py:

scripts/get_forecast.py
#!/usr/bin/env python3
"""
Fetch weather forecast from National Weather Service API.
"""
import sys
import json
import argparse
import requests

def get_forecast(latitude, longitude):
"""
Fetch weather forecast for given coordinates.

Args:
latitude: Latitude (decimal)
longitude: Longitude (decimal)

Returns:
dict: Weather data including current conditions and forecast
"""
try:
# Step 1: Get grid point data
points_url = f"https://api.weather.gov/points/{latitude},{longitude}"
headers = {"User-Agent": "WeatherForecastSkill/1.0"}

points_response = requests.get(points_url, headers=headers, timeout=10)
points_response.raise_for_status()
points_data = points_response.json()

# Step 2: Get forecast URL
forecast_url = points_data["properties"]["forecast"]
forecast_hourly_url = points_data["properties"]["forecastHourly"]

# Step 3: Fetch forecast
forecast_response = requests.get(forecast_url, headers=headers, timeout=10)
forecast_response.raise_for_status()
forecast_data = forecast_response.json()

# Step 4: Fetch hourly forecast
hourly_response = requests.get(forecast_hourly_url, headers=headers, timeout=10)
hourly_response.raise_for_status()
hourly_data = hourly_response.json()

# Step 5: Check for alerts
alerts_url = f"https://api.weather.gov/alerts/active?point={latitude},{longitude}"
alerts_response = requests.get(alerts_url, headers=headers, timeout=10)
alerts_response.raise_for_status()
alerts_data = alerts_response.json()

# Extract key information
current_period = forecast_data["properties"]["periods"][0]

result = {
"location": {
"lat": latitude,
"lon": longitude,
"city": points_data["properties"]["relativeLocation"]["properties"]["city"],
"state": points_data["properties"]["relativeLocation"]["properties"]["state"]
},
"current": {
"temperature": current_period["temperature"],
"temperatureUnit": current_period["temperatureUnit"],
"windSpeed": current_period["windSpeed"],
"windDirection": current_period["windDirection"],
"shortForecast": current_period["shortForecast"],
"detailedForecast": current_period["detailedForecast"]
},
"forecast": {
"periods": forecast_data["properties"]["periods"][:7] # 7-day forecast
},
"hourly": {
"periods": hourly_data["properties"]["periods"][:24] # Next 24 hours
},
"alerts": [
{
"event": alert["properties"]["event"],
"headline": alert["properties"]["headline"],
"severity": alert["properties"]["severity"],
"urgency": alert["properties"]["urgency"]
}
for alert in alerts_data.get("features", [])
]
}

return result

except requests.HTTPError as e:
if e.response.status_code == 404:
return {
"error": "Location not covered by National Weather Service. "
"This API only supports US territories."
}
return {"error": f"API error: {str(e)}"}

except requests.RequestException as e:
return {"error": f"Network error: {str(e)}"}

except (KeyError, IndexError) as e:
return {"error": f"Unexpected API response format: {str(e)}"}

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Get weather forecast")
parser.add_argument("--lat", type=float, required=True, help="Latitude")
parser.add_argument("--lon", type=float, required=True, help="Longitude")

args = parser.parse_args()

result = get_forecast(args.lat, args.lon)
print(json.dumps(result, indent=2))

Make it executable:

chmod +x ~/.claude/skills/weather-forecast/scripts/get_forecast.py

Testing the Scripts

Before using the skill with Claude, test the scripts independently:

Test geocoding

python ~/.claude/skills/weather-forecast/scripts/geocode.py "San Francisco, CA"

Expected output:

{
"lat": 37.7879363,
"lon": -122.4075201,
"display_name": "San Francisco, California, United States"
}

Test weather forecast

python ~/.claude/skills/weather-forecast/scripts/get_forecast.py \
--lat 37.7879363 --lon -122.4075201

Expected output: JSON with current conditions, forecast, and alerts

{
"location": {
"lat": 37.7879363,
"lon": -122.4075201,
"city": "Sausalito",
"state": "CA"
},
"current": {
"temperature": 67,
"temperatureUnit": "F",
"windSpeed": "10 mph",
"windDirection": "NNE",
"shortForecast": "Sunny",
"detailedForecast": "Sunny. High near 67, with temperatures falling to around 64 in the afternoon. North northeast wind around 10 mph."
},
"forecast": {
...
}
}

Using the Skill with Claude

Option 1: Claude Code

If you're using Claude Code, the skill is automatically available:

  1. Restart Claude Code to pick up new skills
  2. Ask: "What's the weather in Portland, Oregon?"
  3. Claude will:
    • Recognize the weather query matches the skill description
    • Load SKILL.md instructions
    • Execute geocode.py to get coordinates
    • Run get_forecast.py with those coordinates
    • Present the results in natural language

Option 2: VS Code with GitHub Copilot

Enable Agent Skills in VS Code:

  1. Open Settings (Cmd/Ctrl + ,)
  2. Search for chat.useAgentSkills
  3. Enable the setting
  4. Restart VS Code
  5. In Copilot Chat, ask about weather

Option 3: Claude API

You can also use skills via the Claude API by uploading them:

import anthropic
import os

# Upload the skill
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

# Create skill from directory
with open("~/.claude/skills/weather-forecast/SKILL.md", "r") as f:
skill_content = f.read()

# Upload skill (requires beta headers)
skill = client.beta.skills.create(
name="weather-forecast",
content=skill_content,
# scripts are uploaded separately as resources
)

print(f"Skill uploaded: {skill.skill_id}")

Comparing to MCP

Let's compare this Agent Skill to an equivalent MCP server:

AspectAgent SkillMCP Server
Setup complexityCreate SKILL.md + scriptsImplement SDK, handle JSON-RPC
Execution modelScripts run in client's contextSeparate process with stdio transport
SecurityShares client's environmentProcess isolation
DistributionCopy directorynpm package or server binary
DiscoverabilityFilesystem scanningRegistry + config file
AI modificationCan write/edit skillsCannot modify server code

When to use Agent Skills:

  • Rapid prototyping
  • Personal workflows
  • Trusted scripts you control
  • Dynamic capability expansion

When to use MCP:

  • Production deployments
  • Third-party integrations
  • Credential isolation needed
  • Organizational policies require sandboxing

Advanced: Adding Reference Documentation

Skills support progressive disclosure - loading detailed docs only when needed:

Create ~/.claude/skills/weather-forecast/references/NWS_API.md:

references/NWS_API.md
# National Weather Service API Reference

## Endpoints

### Points API
`GET /points/{lat},{lon}`

Returns metadata about a location, including:
- Grid coordinates for forecast URLs
- Time zone
- Radar station
- Forecast office

### Forecast API
`GET /gridpoints/{office}/{gridX},{gridY}/forecast`

Returns 7-day forecast with:
- Day/night periods
- Temperature and wind
- Detailed text forecast

### Alerts API
`GET /alerts/active?point={lat},{lon}`

Returns active weather alerts:
- Warnings (severe conditions)
- Watches (conditions possible)
- Advisories (less urgent)

## Rate Limits

- No API key required
- Please use a descriptive User-Agent
- Recommended: 1 request per second

Now Claude can reference this document when it needs detailed API information, without loading it into every conversation.

Best Practices

1. Clear Activation Criteria

Make the description field specific about when to use the skill:

# ❌ Too vague
description: Weather information

# ✅ Clear activation trigger
description: Fetch real-time weather forecasts for US locations using the National Weather Service API. Use when the user asks about weather, temperature, or conditions for a specific US city or coordinates.

2. Handle Errors Gracefully

Your scripts should return structured errors:

# Good error handling
try:
result = api_call()
except APIError:
return {"error": "API temporarily unavailable. Please try again."}

3. Use Progressive Disclosure

Don't put everything in SKILL.md. Use references for:

  • API documentation
  • Domain-specific guides
  • Large examples or templates

4. Keep Scripts Self-Contained

Make scripts runnable independently for testing:

if __name__ == "__main__":
# CLI interface for testing
args = parse_arguments()
result = main_function(args)
print(json.dumps(result))

5. Document Dependencies

Include a requirements.txt or note dependencies in SKILL.md:

## Setup

This skill requires:
- Python 3.8+
- `requests` library: `pip install requests`

Next Steps

Now that you've built a weather forecast skill, try:

  1. Add caching: Store recent forecasts to reduce API calls
  2. Support international locations: Integrate additional weather APIs
  3. Create visualizations: Generate weather charts using matplotlib
  4. Add notifications: Alert script that runs on a schedule

For more skill examples, check out:

Conclusion

Agent Skills provide a lightweight, flexible way to extend AI agents without the complexity of full protocol implementations. By packaging instructions and scripts in a simple directory structure, you can rapidly build specialized capabilities that work across multiple AI platforms.

The weather forecast skill we built demonstrates the core concepts: clear activation criteria, well-structured scripts, and progressive disclosure of documentation. This pattern scales from simple utilities to complex domain-specific workflows.

For production deployments with third-party code or strict security requirements, MCP's process isolation model is more appropriate. But for trusted, personal workflows and rapid iteration, Agent Skills offer an compelling alternative that puts AI-assisted development front and center.

Ready to explore both approaches? Check out my new post about comparison of Agent Skills vs MCP security architectures to understand which model fits your use case.