Content
# Steam MCP Server
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that gives AI assistants access to Steam profile, game library, wishlist, and store data — built to power the **[Steam Gift Recommender](.github/agents/steam-gift-recommender.agent.md)** agent. Written in .NET, communicates over stdio.
Point the agent at any Steam profile and it recommends a game that person doesn't own yet, grounded in evidence: their wishlist, playtime history, and recent activity.
> **Scope note:** the tool surface is deliberately minimal — it implements exactly what the recommender agent needs, not the full Steam Web API. It still works as a general Steam MCP server (*"What has this user played recently?"*, *"Find co-op roguelikes"*), but new tools are added only when a use case demands them.
## The Steam Gift Recommender agent
[`.github/agents/steam-gift-recommender.agent.md`](.github/agents/steam-gift-recommender.agent.md) defines a custom agent for GitHub Copilot in VS Code. Given a Steam profile (steamId64, vanity name, or profile URL), it:
1. Resolves the profile to a canonical steamId64 and checks which data is public.
2. Reads owned games, recently played games, and the wishlist.
3. Infers taste from strong signals — wishlist intent, recent activity, heavily played genres, franchise affinity.
4. Searches the store by tags, validates candidates with store metadata, and excludes games already owned.
5. Returns one primary recommendation plus up to two backups, each with cited evidence, and states plainly when hidden profile data makes a safe recommendation impossible.
To use it:
1. Configure the MCP server in `.vscode/mcp.json` under the name `steamMcp` (see [Client setup](#client-setup) — the agent references its tools as `steammcp/...`).
2. Open Copilot Chat and invoke the **Steam Gift Recommender** agent.
3. Give it a profile: *"Recommend a gift for steamcommunity.com/id/gaben"*.
The agent's constraints (never invent Steam data, never recommend an owned game, always cite evidence) are what shaped this server's design — structured `blocked` responses with reason codes exist so the agent can reason honestly about private profiles instead of hallucinating.
## Tools
| Tool | Description | Needs API key |
|---|---|---|
| `steam_resolve_profile_to_steamid64` | Normalizes a vanity ID, profile URL, or steamId64 into a canonical steamId64 | Yes |
| `steam_get_player_summary` | Player summary (name, avatar, status, visibility) | Yes |
| `steam_get_owned_games` | Owned games sorted by playtime, paginated (max 100 per call) | Yes |
| `steam_get_wishlist` | Public wishlist slice, paginated (max 100 per call) | Yes |
| `steam_get_recently_played_games` | Games played in the last two weeks | Yes |
| `steam_get_profile_data_availability` | Reports which data is public/private for a profile | Yes |
| `steam_search_store_apps` | Text search over the Steam store catalog (max 50 results) | Yes |
| `steam_search_store_apps_by_tags` | Store search filtered by Steam tag IDs, optional text query | No |
| `steam_get_store_app_metadata` | Compact store metadata (price, genres, release date) for up to 25 appIds | No |
Every tool returns a consistent envelope:
```json
{
"status": "ok | blocked",
"steamId": "76561198000000000",
"toolType": "get_owned_games",
"summary": "Human-readable summary of the result.",
"data": { },
"reasonCode": "profile_private"
}
```
`blocked` responses carry a machine-readable `reasonCode` (e.g. `profile_private`, `owned_games_hidden`, `invalid_steam_id`, `rate_limited`, `insufficient_data`) instead of throwing errors, so the assistant can explain *why* data is unavailable.
## Requirements
- A **Steam Web API key** — free, get one at [steamcommunity.com/dev/apikey](https://steamcommunity.com/dev/apikey)
- **.NET 10 SDK** ([download](https://dotnet.microsoft.com/download)) — *or* **Docker** if you prefer the container route
## Configuration
All configuration is via environment variables:
| Variable | Required | Default | Description |
|---|---|---|---|
| `STEAM_API_KEY` | Yes* | — | Steam Web API key. *Without it, tools that need it return `blocked` responses instead of crashing. |
| `CACHE_ENABLED` | No | `true` | Enables in-memory response caching |
| `CACHE_TTL_MINUTES` | No | `15` | Cache entry lifetime |
| `STEAM_API_BASE_URL` | No | `https://api.steampowered.com/` | Override for testing/proxying |
## Running
### Option 1: dotnet
```sh
dotnet run --project SteamMCP.Host.Stdio -c Release
```
The server speaks MCP over stdin/stdout; logs go to stderr. You normally don't run it by hand — your MCP client launches it (see below).
### Option 2: Docker
```sh
docker build -t steam-mcp-stdio:local .
```
```sh
docker run --rm -i -e STEAM_API_KEY=your-key steam-mcp-stdio:local
```
## Client setup
### Claude Code
```sh
claude mcp add steam --env STEAM_API_KEY=your-key -- dotnet run --project /path/to/SteamMCP.Host.Stdio -c Release
```
Or with Docker:
```sh
claude mcp add steam -- docker run --rm -i -e STEAM_API_KEY=your-key steam-mcp-stdio:local
```
### Claude Desktop
Add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"steam": {
"command": "docker",
"args": ["run", "--rm", "-i", "-e", "STEAM_API_KEY=your-key", "steam-mcp-stdio:local"]
}
}
}
```
### VS Code (GitHub Copilot)
Create `.vscode/mcp.json` in your workspace — this version prompts for the key instead of storing it in the file:
```json
{
"servers": {
"steamMcp": {
"type": "stdio",
"command": "docker",
"args": [
"run", "--rm", "-i",
"-e", "STEAM_API_KEY=${input:steam-api-key}",
"steam-mcp-stdio:local"
]
}
},
"inputs": [
{
"type": "promptString",
"id": "steam-api-key",
"description": "Steam Web API key",
"password": true
}
]
}
```
Then run `MCP: List Servers` from the Command Palette and start `steamMcp`.
## Project structure
```
SteamMCP.Domain Contracts, reason codes, models, Steam ID parsing
SteamMCP.Application Request validation and orchestration
SteamMCP.Infrastructure.Steam Steam Web API + store HTTP clients, caching, throttling
SteamMCP.Host.Stdio MCP server bootstrap and tool definitions
```
See [docs/solution-structure.md](docs/solution-structure.md) for layering conventions and how to add a new tool.
## Behavior notes
- **Private profiles never error** — tools return `status: "blocked"` with a reason code describing what is hidden.
- **Store endpoints are throttled** internally to stay polite to Steam's public endpoints.
- **Responses are cached** in memory (15 min default) to reduce repeated upstream calls within a session.
## License
[MIT](LICENSE)