#
tokens: 5447/50000 14/14 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .github
│   ├── actions
│   │   └── setup-java
│   │       └── action.yml
│   └── workflows
│       ├── default.yml
│       └── publish.yml
├── .gitignore
├── dependency-reduced-pom.xml
├── devenv.lock
├── devenv.nix
├── devenv.yaml
├── Dockerfile
├── LICENSE
├── pom.xml
├── pom.xml.versionsBackup
├── README.md
└── src
    ├── main
    │   └── java
    │       └── net
    │           └── experimentalworks
    │               ├── App.java
    │               ├── Game.java
    │               ├── SteamGames.java
    │               └── SteamGamesServer.java
    └── test
        └── java
            └── net
                └── experimentalworks
                    └── AppTest.java
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Compiled class file
*.class

# Log file
*.log

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*

# Devenv and Maven
target/

*.swp
.devenv.flake.nix
.devenv/

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# MCP Steam Server

A Model Context Protocol (MCP) server that provides Steam gaming context to AI assistants. This service integrates with the Steam API to fetch user gaming information and exposes it through the MCP protocol, allowing AI assistants to access and understand users' gaming activities and preferences.

## Installation

### Using Docker (Recommended)

The easiest way to run the MCP Steam server is using Docker:

```bash
docker run --rm -i ghcr.io/dsp/mcp-server-steam:latest
```

### Configuration

The server can be configured using environment variables:

```bash
# Required configuration
STEAM_API_KEY=your_steam_api_key
```

## Development

### Prerequisites

- OpenJDK 21
- Docker (for container builds)
- Git
- [devenv.sh](https://devenv.sh)

### Setting Up Development Environment

1. Clone the repository:
   ```bash
   git clone https://github.com/dsp/mcp-steam.git
   cd mcp-steam
   ```

2. Use the development shell:
   ```bash
   devshell shell
   ```
   This will set up the required development environment with all necessary dependencies.

3. Build the project:
   ```bash
   mvn package
   ```

### Building Docker Image Locally

```bash
docker build -t mcp-server-steam .
```

## API Documentation

The server implements the Model Context Protocol (MCP) specification. For detailed API documentation, please refer to the [MCP Documentation](https://modelcontextprotocol.io).

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## License

MIT License

```

--------------------------------------------------------------------------------
/devenv.yaml:
--------------------------------------------------------------------------------

```yaml
inputs:
  nix2container:
    url: github:nlewo/nix2container
    inputs:
      nixpkgs:
        follows: nixpkgs
  mk-shell-bin:
    url: github:rrbutani/nix-mk-shell-bin

```

--------------------------------------------------------------------------------
/src/main/java/net/experimentalworks/App.java:
--------------------------------------------------------------------------------

```java
package net.experimentalworks;

import io.modelcontextprotocol.server.transport.StdioServerTransport;

/** Hello world! */
public class App {

  public static void main(String[] args) {
    var server = new SteamGamesServer(new StdioServerTransport());
    server.run().block();
  }
}

```

--------------------------------------------------------------------------------
/.github/workflows/default.yml:
--------------------------------------------------------------------------------

```yaml
name: Build Check
run-name: Push and PR checks
on:
  - push
  - pull_request

jobs:
  checks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup-java
      - run: mvn spotless:check

  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup-java
      - run: mvn compile

```

--------------------------------------------------------------------------------
/src/test/java/net/experimentalworks/AppTest.java:
--------------------------------------------------------------------------------

```java
package net.experimentalworks;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

/** Unit test for simple App. */
public class AppTest extends TestCase {
  /**
   * Create the test case
   *
   * @param testName name of the test case
   */
  public AppTest(String testName) {
    super(testName);
  }

  /**
   * @return the suite of tests being tested
   */
  public static Test suite() {
    return new TestSuite(AppTest.class);
  }

  /** Rigourous Test :-) */
  public void testApp() {
    assertTrue(true);
  }
}

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Build stage
FROM alpine:latest AS builder

# Add build arg
ARG VERSION=1.0-SNAPSHOT

# Install devenv
RUN apk add --no-cache openjdk21 maven

# Copy project files
WORKDIR /build
COPY pom.xml .
COPY src/ src/

# Build with version
RUN mvn package -Dversion=$VERSION

# Find the jar with dependencies (most reliable approach)
RUN mv $(find target -name "mcp-steam-*.jar") target/app.jar

# Runtime stage
FROM alpine:latest AS mcp-server-steam

RUN apk add --no-cache openjdk21-jre
WORKDIR /app
COPY --from=builder /build/target/app.jar app.jar

CMD ["java", "-jar", "app.jar"]

```

--------------------------------------------------------------------------------
/.github/actions/setup-java/action.yml:
--------------------------------------------------------------------------------

```yaml
name: 'Setup Java'
description: 'Sets up Java and Maven cache'
runs:
  using: "composite"
  steps:
    - uses: actions/setup-java@v4
      with:
        java-version: '21'
        distribution: 'temurin'
        architecture: x64

    # Handle Maven cache separately with a more reliable key strategy
    - shell: bash
      id: cache-key
      run: |
        echo "timestamp=$(date -u +%Y%m%d%H%M%S)" >> $GITHUB_OUTPUT
        echo "hash=$(echo ${{ hashFiles('**/pom.xml') }})" >> $GITHUB_OUTPUT

    - uses: actions/cache@v3
      id: maven-cache
      with:
        path: ~/.m2/repository
        key: ${{ runner.os }}-maven-${{ steps.cache-key.outputs.hash }}
        restore-keys: |
          ${{ runner.os }}-maven-

```

--------------------------------------------------------------------------------
/src/main/java/net/experimentalworks/Game.java:
--------------------------------------------------------------------------------

```java
package net.experimentalworks;

import java.io.Serializable;
import java.util.Optional;

public class Game implements Serializable {

  private long appId;
  private String name;
  private float playtimeForever;
  private Optional<Float> playtime2weeks;

  public Game(long appId, String name, float playtimeForever) {
    this.appId = appId;
    this.name = name;
    this.playtimeForever = playtimeForever;
  }

  public Game(long appId, String name, float playtimeForever, float playtime2weeks) {
    this.appId = appId;
    this.name = name;
    this.playtimeForever = playtimeForever;
    this.playtime2weeks = Optional.of(playtime2weeks);
  }

  public long getAppId() {
    return appId;
  }

  public String getName() {
    return name;
  }

  public float getPlaytimeForever() {
    return playtimeForever;
  }

  public Optional<Float> getPlaytime2weeks() {
    return playtime2weeks;
  }
}

```

--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------

```yaml
name: Release Docker Image

on:
  release:
    types: [published]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: dsp/mcp-server-steam

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - uses: ./.github/actions/setup-java

      - name: Set version in pom.xml
        run: |
          VERSION=${GITHUB_REF#refs/tags/}
          mvn versions:set -DnewVersion=$VERSION

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          build-args: |
            VERSION=${GITHUB_REF#refs/tags/}
          context: .
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

```

--------------------------------------------------------------------------------
/src/main/java/net/experimentalworks/SteamGames.java:
--------------------------------------------------------------------------------

```java
package net.experimentalworks;

import java.util.List;
import java.util.stream.Collectors;

import com.lukaspradel.steamapi.core.exception.SteamApiException;
import com.lukaspradel.steamapi.data.json.ownedgames.GetOwnedGames;
import com.lukaspradel.steamapi.data.json.recentlyplayedgames.GetRecentlyPlayedGames;
import com.lukaspradel.steamapi.webapi.client.SteamWebApiClient;
import com.lukaspradel.steamapi.webapi.request.GetOwnedGamesRequest;
import com.lukaspradel.steamapi.webapi.request.GetRecentlyPlayedGamesRequest;
import com.lukaspradel.steamapi.webapi.request.builders.SteamWebApiRequestFactory;

public class SteamGames {

  private final SteamWebApiClient client;

  public SteamGames(String apiKey) {
    this.client = new SteamWebApiClient.SteamWebApiClientBuilder(apiKey).build();
  }

  public GetOwnedGames getOwnedGames(String steamId) throws SteamApiException {
    GetOwnedGamesRequest request =
        SteamWebApiRequestFactory.createGetOwnedGamesRequest(steamId, true, true, List.of());
    return client.processRequest(request);
  }

  public GetRecentlyPlayedGames getRecentlyPlayedGames(String steamId) throws SteamApiException {
    GetRecentlyPlayedGamesRequest request =
        SteamWebApiRequestFactory.createGetRecentlyPlayedGamesRequest(steamId);

    return client.processRequest(request);
  }

  public List<Game> getGames(String steamId) throws SteamApiException {
    GetOwnedGames ownedGames = getOwnedGames(steamId);
    return ownedGames.getResponse().getGames().stream()
        .map(game -> new Game(game.getAppid(), game.getName(), game.getPlaytimeForever()))
        .collect(Collectors.toList());
  }

  public List<Game> getRecentlyGames(String steamId) throws SteamApiException {
    GetRecentlyPlayedGames recentGames = getRecentlyPlayedGames(steamId);
    return recentGames.getResponse().getGames().stream()
        .map(
            game ->
                new Game(
                    game.getAppid(),
                    game.getName(),
                    game.getPlaytimeForever(),
                    game.getPlaytime2weeks()))
        .collect(Collectors.toList());
  }
}

```

--------------------------------------------------------------------------------
/dependency-reduced-pom.xml:
--------------------------------------------------------------------------------

```
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>net.experimentalworks</groupId>
  <artifactId>mcp-steam</artifactId>
  <name>mcp-steam</name>
  <version>1.0-SNAPSHOT</version>
  <url>http://maven.apache.org</url>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.13.0</version>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>versions-maven-plugin</artifactId>
        <version>2.18.0</version>
      </plugin>
      <plugin>
        <groupId>com.diffplug.spotless</groupId>
        <artifactId>spotless-maven-plugin</artifactId>
        <version>${spotless.version}</version>
        <configuration>
          <java>
            <googleJavaFormat>
              <version>1.25.2</version>
              <style>GOOGLE</style>
            </googleJavaFormat>
            <removeUnusedImports />
            <importOrder>
              <order>java,javax,org,com,</order>
            </importOrder>
            <trimTrailingWhitespace />
            <endWithNewline />
          </java>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.5.1</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <filters>
                <filter>
                  <artifact>*:*</artifact>
                  <excludes>
                    <exclude>module-info.class</exclude>
                    <exclude>META-INF/*.SF</exclude>
                    <exclude>META-INF/*.DSA</exclude>
                    <exclude>META-INF/*.RSA</exclude>
                    <exclude>META-INF/MANIFEST.MF</exclude>
                    <exclude>META-INF/LICENSE</exclude>
                    <exclude>META-INF/NOTICE</exclude>
                    <exclude>META-INF/versions/9/module-info.class</exclude>
                    <exclude>META-INF.versions.9.module-info</exclude>
                  </excludes>
                </filter>
              </filters>
              <transformers>
                <transformer>
                  <mainClass>net.experimentalworks.App</mainClass>
                </transformer>
                <transformer />
                <transformer />
                <transformer>
                  <addHeader>false</addHeader>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <properties>
    <maven.compiler.release>19</maven.compiler.release>
    <spotless.version>2.44.2</spotless.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
</project>

```

--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------

```
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>net.experimentalworks</groupId>
  <artifactId>mcp-steam</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>mcp-steam</name>
  <url>http://maven.apache.org</url>
  <properties>
    <maven.compiler.release>19</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spotless.version>2.44.2</spotless.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.modelcontextprotocol.sdk</groupId>
      <artifactId>mcp</artifactId>
      <version>0.7.0</version>
    </dependency>
    <dependency>
      <groupId>io.projectreactor</groupId>
      <artifactId>reactor-core</artifactId>
      <version>3.7.3</version>
    </dependency>
    <dependency>
      <groupId>com.lukaspradel</groupId>
      <artifactId>steam-web-api</artifactId>
      <version>1.9.1</version>
    </dependency>
    <dependency>
      <groupId>org.json</groupId>
      <artifactId>json</artifactId>
      <version>20250107</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.13.0</version>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>versions-maven-plugin</artifactId>
        <version>2.18.0</version>
      </plugin>
      <plugin>
        <groupId>com.diffplug.spotless</groupId>
        <artifactId>spotless-maven-plugin</artifactId>
        <version>${spotless.version}</version>
        <configuration>
          <java>
            <googleJavaFormat>
              <version>1.25.2</version>
              <style>GOOGLE</style>
            </googleJavaFormat>
            <removeUnusedImports/>
            <importOrder>
              <order>java,javax,org,com,</order>
            </importOrder>
            <trimTrailingWhitespace/>
            <endWithNewline/>
          </java>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.5.1</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <filters>
                <filter>
                  <artifact>*:*</artifact>
                  <excludes>
                    <exclude>module-info.class</exclude>
                    <exclude>META-INF/*.SF</exclude>
                    <exclude>META-INF/*.DSA</exclude>
                    <exclude>META-INF/*.RSA</exclude>
                    <exclude>META-INF/MANIFEST.MF</exclude>
                    <exclude>META-INF/LICENSE</exclude>
                    <exclude>META-INF/NOTICE</exclude>
                    <exclude>META-INF/versions/9/module-info.class</exclude>
                    <exclude>META-INF.versions.9.module-info</exclude>
                  </excludes>
                </filter>
              </filters>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <mainClass>net.experimentalworks.App</mainClass>
                </transformer>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ApacheLicenseResourceTransformer"/>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer">
                  <addHeader>false</addHeader>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

```

--------------------------------------------------------------------------------
/src/main/java/net/experimentalworks/SteamGamesServer.java:
--------------------------------------------------------------------------------

```java
package net.experimentalworks;

import java.util.List;
import java.util.Map;

import org.json.JSONArray;
import org.json.JSONObject;

import io.modelcontextprotocol.server.McpAsyncServer;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
import io.modelcontextprotocol.spec.McpSchema.TextContent;
import io.modelcontextprotocol.spec.McpSchema.Tool;
import io.modelcontextprotocol.spec.ServerMcpTransport;
import reactor.core.publisher.Mono;

public class SteamGamesServer {

  private static final String STEAM_API_KEY = System.getenv("STEAM_API_KEY");
  private static final String STEAM_ID = System.getenv("STEAM_ID");
  private static final String TOOL_PREFIX = SteamGamesServer.getenvOrDefault("TOOL_PREFIX", "");

  private final McpAsyncServer server;

  private static String getenvOrDefault(String key, String defaultValue) {
    String value = System.getenv(key);
    return value != null ? value : defaultValue;
  }

  public SteamGamesServer(ServerMcpTransport transport) {
    String version = getClass().getPackage().getImplementationVersion();
    if (version == null) {
      version = "1.0.0"; // Fallback version if not found
    }
    this.server =
        McpServer.async(transport)
            .serverInfo("steam-games", version)
            .capabilities(ServerCapabilities.builder().tools(true).logging().build())
            .build();
  }

  public Mono<Void> run() {
    return server
        .addTool(createGetGamesTool())
        .then(server.addTool(createGetRecentGamesTool()))
        .then(Mono.never());
  }

  private static McpServerFeatures.AsyncToolRegistration createGetGamesTool() {
    var schema =
        """
            {
              "type": "object",
              "properties": {}
            }
            """;

    var tool =
        new Tool(
            TOOL_PREFIX + "get-games",
            """
            Get a comprehensive list of all games owned by the specified Steam user, including their total playtime in minutes.
            This includes all games in their Steam library, both installed and uninstalled, free and purchased. For each game,
            returns details like the game name, AppID, total playtime, and whether they've played it recently. The data comes
            directly from Steam's official API using the provided Steam ID.
            NOTE: playtime is sent in minutes.
            """,
            schema);

    return new McpServerFeatures.AsyncToolRegistration(tool, args -> handleGetGames(args));
  }

  private static Mono<CallToolResult> handleGetGames(Map<String, Object> args) {
    return Mono.fromCallable(
        () -> {
          var steamGames = new SteamGames(STEAM_API_KEY);
          var games = steamGames.getGames(STEAM_ID);

          var json =
              new JSONObject()
                  .put("owner", STEAM_ID)
                  .put("description", "Played games by the given steam id")
                  .put("all_games", new JSONArray(games));

          return new CallToolResult(List.of(new TextContent(json.toString())), false);
        });
  }

  private static McpServerFeatures.AsyncToolRegistration createGetRecentGamesTool() {
    var schema =
        """
            {
              "type": "object",
              "properties": {}
            }
            """;

    var tool =
        new Tool(
            TOOL_PREFIX + "get-recent-games",
            """
            Retrieve a list of recently played games for the specified Steam user, including playtime
            details from the last 2 weeks. This tool fetches data directly from Steam's API using the
            provided Steam ID and returns information like game names, AppIDs, and recent playtime
            statistics in minutes. The results only include games that have been played in the recent time period,
            making it useful for tracking current gaming activity and habits.
            NOTE: playtime is sent in minutes.
            """,
            schema);

    return new McpServerFeatures.AsyncToolRegistration(tool, args -> handleGetRecentGames(args));
  }

  private static Mono<CallToolResult> handleGetRecentGames(Map<String, Object> args) {
    return Mono.fromCallable(
        () -> {
          var steamGames = new SteamGames(STEAM_API_KEY);
          var games = steamGames.getRecentlyGames(STEAM_ID);

          var json =
              new JSONObject()
                  .put("owner", STEAM_ID)
                  .put("description", "Recently played games by the given steam id")
                  .put("recent_games", new JSONArray(games));

          return new CallToolResult(List.of(new TextContent(json.toString())), false);
        });
  }
}

```