@mrfentmen/steam-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # steam-mcp
2
+
3
+ Steam store search, prices and discounts, app details, and game news.
4
+ Uses Steam's keyless store + news endpoints.
5
+
6
+ ## Tools
7
+
8
+ - `search_games` — store search with current prices / discount percentages
9
+ - `get_game_details` — price, release date, developers, genres, Metacritic, blurb
10
+ - `get_game_news` — recent Steam news posts for a game
11
+
12
+ ## Run
13
+
14
+ ```bash
15
+ npm install && npm run build && node dist/index.js
16
+ ```
17
+
18
+ ## Example
19
+
20
+ > "What's Helldivers 2 cost and is it on sale?"
21
+ > `search_games("helldivers")` → `get_game_details(553850)`
22
+
23
+ > "What's new for Cyberpunk?"
24
+ > `get_game_news(1091500)`
package/dist/api.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Steam client — keyless store endpoints.
3
+ * - Store search: /api/storesearch
4
+ * - App details: /api/appdetails (filtered to keep payloads small)
5
+ * - News: ISteamNews/GetNewsForApp (Steamworks Web API, no key
6
+ * required for this endpoint)
7
+ */
8
+ export declare class SteamError extends Error {
9
+ }
10
+ export interface StoreItem {
11
+ type: string;
12
+ appid: number;
13
+ name: string;
14
+ price?: {
15
+ currency: string;
16
+ initial: number;
17
+ final: number;
18
+ discount_percent: number;
19
+ };
20
+ tinyImage?: string;
21
+ }
22
+ export interface AppDetail {
23
+ appid: number;
24
+ name: string;
25
+ type?: string;
26
+ isFree?: boolean;
27
+ price: {
28
+ currency: string;
29
+ initial: number;
30
+ final: number;
31
+ discountPercent: number;
32
+ } | null;
33
+ releaseDate?: string;
34
+ developers: string[];
35
+ genres: string[];
36
+ metacritic?: number;
37
+ description?: string;
38
+ headerImage?: string;
39
+ url: string;
40
+ }
41
+ export interface NewsItem {
42
+ title: string;
43
+ url: string;
44
+ author?: string;
45
+ date?: string;
46
+ contents?: string;
47
+ }
48
+ export declare function searchStore(query: string): Promise<StoreItem[]>;
49
+ export declare function getAppDetail(appid: number): Promise<AppDetail | null>;
50
+ export declare function getNews(appid: number, count: number): Promise<NewsItem[]>;
51
+ export declare function fmtPrice(cents: number, currency?: string): string;
52
+ export declare function formatStoreItem(i: StoreItem): string;
53
+ export declare function formatAppDetail(a: AppDetail): string;
package/dist/api.js ADDED
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Steam client — keyless store endpoints.
3
+ * - Store search: /api/storesearch
4
+ * - App details: /api/appdetails (filtered to keep payloads small)
5
+ * - News: ISteamNews/GetNewsForApp (Steamworks Web API, no key
6
+ * required for this endpoint)
7
+ */
8
+ const STORE = "https://store.steampowered.com/api";
9
+ const NEWS = "https://api.steampowered.com/ISteamNews/GetNewsForApp/v2";
10
+ export class SteamError extends Error {
11
+ }
12
+ async function getJson(url) {
13
+ const res = await fetch(url, {
14
+ headers: { Accept: "application/json", "User-Agent": "steam-mcp/1.0" },
15
+ });
16
+ if (!res.ok)
17
+ throw new SteamError(`Steam API error ${res.status}: ${res.statusText}`);
18
+ return (await res.json());
19
+ }
20
+ // ---------------------------------------------------------------------------
21
+ // Endpoints
22
+ // ---------------------------------------------------------------------------
23
+ export async function searchStore(query) {
24
+ const data = await getJson(`${STORE}/storesearch/?term=${encodeURIComponent(query)}&cc=US&l=en`);
25
+ return (data.items ?? [])
26
+ .filter((i) => i.type === "app")
27
+ .slice(0, 12)
28
+ .map((i) => ({ ...i, appid: i.id }));
29
+ }
30
+ export async function getAppDetail(appid) {
31
+ const filters = [
32
+ "basic",
33
+ "price_overview",
34
+ "release_date",
35
+ "developers",
36
+ "genres",
37
+ "metacritic",
38
+ "short_description",
39
+ "header_image",
40
+ ].join(",");
41
+ const data = await getJson(`${STORE}/appdetails?appids=${appid}&cc=US&l=en&filters=${filters}`);
42
+ const entry = data[String(appid)];
43
+ if (!entry?.success || !entry.data)
44
+ return null;
45
+ const d = entry.data;
46
+ const price = d.price_overview;
47
+ return {
48
+ appid,
49
+ name: d.name ?? "?",
50
+ type: d.type,
51
+ isFree: d.is_free,
52
+ price: price
53
+ ? {
54
+ currency: price.currency ?? "USD",
55
+ initial: price.initial ?? 0,
56
+ final: price.final ?? 0,
57
+ discountPercent: price.discount_percent ?? 0,
58
+ }
59
+ : null,
60
+ releaseDate: d.release_date?.date,
61
+ developers: d.developers ?? [],
62
+ genres: (d.genres ?? []).map((g) => g.description),
63
+ metacritic: d.metacritic?.score,
64
+ description: d.short_description,
65
+ headerImage: d.header_image,
66
+ url: `https://store.steampowered.com/app/${appid}`,
67
+ };
68
+ }
69
+ export async function getNews(appid, count) {
70
+ const data = await getJson(`${NEWS}/?appid=${appid}&count=${count}&maxlength=600`);
71
+ return (data.appnews?.newsitems ?? []).map((n) => ({
72
+ title: n.title ?? "?",
73
+ url: n.url ?? "",
74
+ author: n.author,
75
+ date: n.date ? new Date(n.date * 1000).toISOString().slice(0, 10) : undefined,
76
+ contents: n.contents ? stripHtml(n.contents).slice(0, 300) : undefined,
77
+ }));
78
+ }
79
+ // ---------------------------------------------------------------------------
80
+ // Formatting
81
+ // ---------------------------------------------------------------------------
82
+ export function fmtPrice(cents, currency = "USD") {
83
+ const symbol = currency === "USD" ? "$" : `${currency} `;
84
+ return `${symbol}${(cents / 100).toFixed(2)}`;
85
+ }
86
+ export function formatStoreItem(i) {
87
+ const price = i.price
88
+ ? i.price.discount_percent > 0
89
+ ? `~~${fmtPrice(i.price.initial, i.price.currency)}~~ → **${fmtPrice(i.price.final, i.price.currency)}** (-${i.price.discount_percent}%)`
90
+ : fmtPrice(i.price.final, i.price.currency)
91
+ : "no price data";
92
+ return `[${i.appid}] ${i.name}\n ${price}\n https://store.steampowered.com/app/${i.appid}`;
93
+ }
94
+ export function formatAppDetail(a) {
95
+ const lines = [
96
+ `[${a.appid}] ${a.name}${a.type ? ` (${a.type})` : ""}`,
97
+ a.price
98
+ ? a.price.discountPercent > 0
99
+ ? `Price: ~~${fmtPrice(a.price.initial)}~~ → ${fmtPrice(a.price.final)} (-${a.price.discountPercent}%)`
100
+ : `Price: ${fmtPrice(a.price.final)}`
101
+ : a.isFree
102
+ ? "Free to play"
103
+ : "Price unavailable",
104
+ a.releaseDate ? `Released: ${a.releaseDate}` : "",
105
+ a.developers.length ? `Developers: ${a.developers.join(", ")}` : "",
106
+ a.genres.length ? `Genres: ${a.genres.join(", ")}` : "",
107
+ a.metacritic ? `Metacritic: ${a.metacritic}` : "",
108
+ ].filter(Boolean);
109
+ if (a.description)
110
+ lines.push(`\n${a.description}`);
111
+ lines.push(`\n${a.url}`);
112
+ return lines.join("\n");
113
+ }
114
+ function stripHtml(s) {
115
+ return s
116
+ .replace(/<[^>]+>/g, " ")
117
+ .replace(/\[\[([^\]]*)\]\]/g, "")
118
+ .replace(/\s+/g, " ")
119
+ .trim();
120
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
+ import { createServer } from "./server.js";
3
+ async function main() {
4
+ const server = createServer();
5
+ const transport = new StdioServerTransport();
6
+ await server.connect(transport);
7
+ console.error("Steam MCP server running on stdio");
8
+ }
9
+ main().catch((err) => {
10
+ console.error("Fatal error:", err);
11
+ process.exit(1);
12
+ });
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function createServer(): McpServer;
package/dist/server.js ADDED
@@ -0,0 +1,63 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { SteamError, formatAppDetail, formatStoreItem, getAppDetail, getNews, searchStore, } from "./api.js";
4
+ const text = (t) => ({ content: [{ type: "text", text: t }] });
5
+ export function createServer() {
6
+ const server = new McpServer({
7
+ name: "steam-mcp",
8
+ version: "1.0.0",
9
+ });
10
+ server.tool("search_games", "Search the Steam store by title. Returns appids with current prices " +
11
+ "and discounts.", { query: z.string().describe("Game title, e.g. 'Helldivers' or 'Baldur'") }, async ({ query }) => {
12
+ try {
13
+ const items = await searchStore(query);
14
+ if (items.length === 0)
15
+ return text(`Nothing on Steam matches "${query}".`);
16
+ return text(`Steam results for "${query}":\n` +
17
+ items.map((i, n) => `${n + 1}. ${formatStoreItem(i)}`).join("\n"));
18
+ }
19
+ catch (e) {
20
+ return text(errorMessage(e));
21
+ }
22
+ });
23
+ server.tool("get_game_details", "Get details for a Steam app: price/discount, release date, " +
24
+ "developers, genres, Metacritic, description.", { appid: z.number().int().describe("Steam app id from search_games") }, async ({ appid }) => {
25
+ try {
26
+ const detail = await getAppDetail(appid);
27
+ if (!detail)
28
+ return text(`No Steam app with id ${appid}.`);
29
+ return text(formatAppDetail(detail));
30
+ }
31
+ catch (e) {
32
+ return text(errorMessage(e));
33
+ }
34
+ });
35
+ server.tool("get_game_news", "Fetch recent Steam news posts for a game.", {
36
+ appid: z.number().int().describe("Steam app id"),
37
+ count: z.number().int().min(1).max(20).default(5).describe("Number of posts"),
38
+ }, async ({ appid, count }) => {
39
+ try {
40
+ const news = await getNews(appid, count);
41
+ if (news.length === 0)
42
+ return text(`No news for app ${appid}.`);
43
+ return text(`Latest news for app ${appid}:\n` +
44
+ news
45
+ .map((n, i) => `${i + 1}. ${n.title}${n.date ? ` (${n.date})` : ""}` +
46
+ (n.author ? ` by ${n.author}` : "") +
47
+ `\n ${n.url}` +
48
+ (n.contents ? `\n ${n.contents}` : ""))
49
+ .join("\n\n"));
50
+ }
51
+ catch (e) {
52
+ return text(errorMessage(e));
53
+ }
54
+ });
55
+ return server;
56
+ }
57
+ function errorMessage(e) {
58
+ if (e instanceof SteamError)
59
+ return `Error: ${e.message}`;
60
+ if (e instanceof Error)
61
+ return `Error: ${e.message}`;
62
+ return `Error: ${String(e)}`;
63
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@mrfentmen/steam-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for Steam — store search, prices and discounts, app details, and game news.",
5
+ "type": "module",
6
+ "mcpName": "io.github.mrfentmen/steam-mcp",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/mrfentmen/steam-mcp.git"
10
+ },
11
+ "bin": {
12
+ "steam-mcp": "./dist/index.js"
13
+ },
14
+ "main": "./dist/index.js",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json",
20
+ "start": "node dist/index.js",
21
+ "dev": "rm -rf dist && tsc -p tsconfig.json && node dist/index.js",
22
+ "inspect": "npx @modelcontextprotocol/inspector node dist/index.js"
23
+ },
24
+ "keywords": [
25
+ "mcp",
26
+ "steam",
27
+ "games",
28
+ "prices",
29
+ "store",
30
+ "deals"
31
+ ],
32
+ "license": "MIT",
33
+ "dependencies": {
34
+ "@modelcontextprotocol/sdk": "^1.0.4",
35
+ "zod": "^3.23.8"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^22.0.0",
39
+ "typescript": "^5.6.0"
40
+ }
41
+ }