Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
818c965
feat: initialize PCM MCP server with save file management tools
mpicciolli Jun 23, 2026
a6995a6
feat: enhance list saves tool with structured output schema
mpicciolli Jun 23, 2026
d348752
feat: update response handling in list saves and select save tools
mpicciolli Jun 23, 2026
afbda33
feat: update response handling and improve documentation for save fil…
mpicciolli Jun 23, 2026
0cbd246
feat: add get_save_info tool for retrieving PCM save file information
mpicciolli Jun 23, 2026
443b464
feat: add get_table_info tool for inspecting PCM table structure in .…
mpicciolli Jun 23, 2026
3424064
feat: add query_save tool for executing read-only SQL queries on PCM …
mpicciolli Jun 23, 2026
c8e1e20
feat: add get_player_info tool for retrieving active player and team …
mpicciolli Jun 24, 2026
9d261a4
feat: update cdb-converter dependency to version 0.1.2
mpicciolli Jun 24, 2026
5ab5b31
feat: ensure database connections are properly closed in player, save…
mpicciolli Jun 24, 2026
0d03c4e
feat: update README.md with detailed server description, tools, insta…
mpicciolli Jun 24, 2026
a9fa0a1
feat: refactor tools to use withSaveDb for improved save handling and…
mpicciolli Jun 24, 2026
56229ee
feat: update project structure to use dist directory for builds and a…
mpicciolli Jun 24, 2026
9e3da55
feat: add start:dev script for development mode with automatic restarts
mpicciolli Jun 24, 2026
f8e684e
feat: add biome configuration file and update inspector script for pr…
mpicciolli Jun 24, 2026
2e49cc3
fix: revert version number to 0.0.0 in package.json and package-lock.…
mpicciolli Jun 24, 2026
b75cf3f
feat: add unit tests for response handling and save database function…
mpicciolli Jun 24, 2026
6385e0c
feat: update schema tools and documentation for PCM save files
mpicciolli Jun 25, 2026
50c4771
feat: refactor query validation and error handling in query-save tool
mpicciolli Jun 25, 2026
d8a19f8
feat: add description to get_save_schema tool for clarity
mpicciolli Jun 25, 2026
ea48bca
Apply suggestions from code review
mpicciolli Jun 25, 2026
8ea80cf
feat: enhance query preparation with error handling and limit enforce…
mpicciolli Jun 25, 2026
76bae83
fix: correct description wording in select_save tool
mpicciolli Jun 25, 2026
02b5fcd
Potential fix for pull request finding
mpicciolli Jun 25, 2026
8791075
Potential fix for pull request finding
mpicciolli Jun 25, 2026
cc30a2d
feat: update pcm_get_player_info tool to return resolved names for di…
mpicciolli Jun 25, 2026
cee5441
fix: streamline output schema descriptions in get-player-info tool
mpicciolli Jun 25, 2026
5084e55
fix: improve error handling to return error messages in tools
mpicciolli Jun 25, 2026
64866df
feat: add tests for assertReadOnlyQuery function and improve error ha…
mpicciolli Jun 25, 2026
65d3eb3
refactor: simplify error handling in assertReadOnlyQuery tests
mpicciolli Jun 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [22.x, 24.x]

steps:
- uses: actions/checkout@v4

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "npm"

- name: Install dependencies
run: npm ci

- name: Setup Biome
uses: biomejs/setup-biome@v2

- name: Run Biome
run: biome ci .

- name: Build
run: npm run build

- name: Test
run: npm test

- name: Check dist artifacts
run: ls -la dist/
147 changes: 9 additions & 138 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,143 +1,14 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz
# Dependencies
node_modules

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.*
!.env.example

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
# Build artifacts
dist
.output

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp directory
.temp

# Sveltekit cache directory
.svelte-kit/

# vitepress build output
**/.vitepress/dist

# vitepress cache directory
**/.vitepress/cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# Firebase cache directory
.firebase/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# pnpm
.pnpm-store
# Test output
coverage

# yarn v3
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Logs
*.log

# Vite files
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
.vite/
# OS files
.DS_Store
112 changes: 112 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# AGENTS.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this is

A **read-only** Model Context Protocol (MCP) server that exposes Pro Cycling Manager
(PCM) game saves to an LLM client over stdio. PCM stores careers as binary `.cdb`
files; this server discovers and inspects those saves but **never writes to or
modifies them**. Each call re-reads the `.cdb` from disk and loads it into an
in-memory sql.js (SQLite) database (via `cdb-converter`), so the on-disk save is
the single source of truth and is never mutated. Any new tool must keep this
read-only guarantee.

## Stack

- **Runtime/lang:** Node.js (ESM, `bundler` module resolution), TypeScript (strict).
- **MCP:** `@modelcontextprotocol/sdk` — `McpServer` + `StdioServerTransport`.
- **Save parsing:** `cdb-converter` (`cdbToSql`) + `sql.js` (in-memory SQLite).
- **Schemas:** `zod` for tool input/output schemas.
- **Build:** `tsup` → `dist/` (ESM output; `.d.ts` currently disabled). **Test:** `vitest`.
**Lint/format:** `biome`.

## Layout

```
src/
index.ts # entrypoint: builds McpServer, registers tools, connects stdio
saves.ts # save discovery + validation (listSaves, validateSave, getPcmRoot)
save-db.ts # withSaveDb(): open .cdb in-memory, run fn, always close db
helpers.ts # validResponse / errorResponse → CallToolResult
tools/
index.ts # registerTools() — wires every tool onto the server
list-saves.ts # pcm_list_saves
select-save.ts # pcm_select_save
get-save-schema.ts # pcm_get_save_schema
get-table-schema.ts # pcm_get_table_schema
get-player-info.ts # pcm_get_player_info
query-save.ts # pcm_query_save
test/ # vitest specs (test/**/*.test.ts)
```

## Tools

All tools are prefixed with `pcm_` and carry `readOnlyHint: true` / `destructiveHint: false` annotations so clients can auto-approve them.

| Tool | Purpose |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `pcm_list_saves` | Discover `.cdb` careers by scanning `Pro Cycling Manager <year>/Cloud` under `%APPDATA%` (**Windows only**). |
| `pcm_select_save` | Validate a `.cdb` path and return metadata. Stateless — the path must be kept in conversation context for later tools. |
| `pcm_get_save_schema` | List all tables (id + name) in a save via `DB_STRUCTURE`. |
| `pcm_get_table_schema` | Inspect one table: columns (name, type, NOT NULL, PK) + row count. |
| `pcm_get_player_info` | Active human player + team (joins `GAM_user` `game_i_active = 1` with `DYN_team`). |
| `pcm_query_save` | Run a single read-only `SELECT`/`WITH … SELECT`. Write/DDL rejected; results capped (default 100, max 1000). |

## Conventions

- **State is in the conversation, not the server.** Tools are stateless; every
save-reading tool takes an absolute `savePath` and re-validates it via
`validateSave`. There is no "current save".
- **Use `withSaveDb` for new save-reading tools.** It centralises validate →
read → convert → run → always-close. Some existing tools (`get_save_schema`,
`get_player_info`, `query_save`) still inline this boilerplate; prefer
`withSaveDb` and consider migrating them when touched.
- **Read-only is enforced defensively** even though the DB is in-memory — see
`assertReadOnlyQuery` in `query-save.ts` (single statement, SELECT/WITH only,
forbidden-keyword guard).
- **Guard against SQL injection** when interpolating identifiers: validate table
names against `DB_STRUCTURE` before building queries (see `get_table_schema`).
- **Tool responses** go through `validResponse` / `errorResponse`; declare both
`inputSchema` and `outputSchema` with zod.
- **Tool annotations** — every tool must include `readOnlyHint`, `destructiveHint`,
`idempotentHint`, and `openWorldHint`. All current tools are read-only
(`readOnlyHint: true`, `destructiveHint: false`).
- **Tool naming** — all tools are prefixed with `pcm_` (e.g. `pcm_list_saves`) to
avoid conflicts when used alongside other MCP servers.
- **Platform:** auto-discovery is Windows-only. On macOS/Linux (Wine/Proton),
`pcm_list_saves`/`getPcmRoot` throw — pass an absolute `.cdb` path to `pcm_select_save`.
- **Logging** must go to `stderr` (`console.error`); stdout is the MCP transport.

## README maintenance

After any change that affects the public interface of this server, update `README.md`
before considering the task done. This includes:

- Adding, removing, or renaming a tool
- Changing a tool's inputs, outputs, or description
- Changing platform support or installation requirements
- Changing the MCP transport or Claude Desktop configuration

The tool table in `README.md` must always match the exact tool names registered in the
source (currently prefixed with `pcm_`).

## Collaboration And Release Conventions

- Respect standard JavaScript library conventions for commits, pull requests, tags, and releases.
- Prefer Conventional Commit style when proposing commit messages or PR titles, especially for changes that affect release notes or semantic versioning.
- Keep pull requests focused, with a clear scope, user-visible impact, and explicit note when a change is breaking.
- Treat versioning and release artifacts as semver-driven. Breaking API or packaging changes must be clearly identified so they can drive a major release.
- Prefer annotated version tags that match the package release version format, such as `v0.1.0`, unless the repository documents another convention.
- When preparing release-related changes, make sure changelog, package metadata, exports, and release notes stay coherent with the actual API and runtime compatibility.

## Commands

```bash
npm run build # bundle src/ -> dist/ with tsup (ESM output; `.d.ts` currently disabled)
npm test # run the vitest suite once
npm run test:watch # vitest in watch mode
npm run coverage # vitest with v8 coverage (text + html + lcov)
npm run lint # biome lint --write . (autofixes)
npm run format # biome format --write .
```
85 changes: 83 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,83 @@
# pcm-mcp
MCP server for querying Pro Cycling Manager game databases
# Pro Cycling Manager MCP Server

A Model Context Protocol (MCP) server for querying Pro Cycling Manager (PCM) game databases. This server provides tools to discover and inspect PCM career save files via the MCP protocol, allowing AI assistants to explore your saves in a structured way.

## Read-Only Access

This server is strictly **read-only**. PCM stores careers as binary `.cdb` files; this server discovers and inspects those saves but **never writes to or modifies them**. Save files are loaded into an in-memory SQLite database for querying, and changes are never written back to disk.

## Available Tools

All tools are read-only and carry `readOnlyHint: true`, so clients like Claude Desktop can approve them automatically without a confirmation prompt.

| Tool | Description |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **pcm_list_saves** | Discover PCM `.cdb` career save files on this machine by scanning the `Pro Cycling Manager <year>/Cloud` folders under `%APPDATA%` (Windows only). Returns each save's absolute path, file name, last modified date and size (newest first). |
| **pcm_select_save** | Validate that an absolute path points to an existing `.cdb` save file and return its metadata. Stateless — keep the returned path in conversation context to pass to later tools. |
| **pcm_get_save_schema** | List every table inside a `.cdb` save file, with its ID and name, plus the total table count. |
| **pcm_get_table_schema** | Inspect a single table by name. Returns its columns (name, SQL type, NOT NULL and primary key flags) and its row count. Use `pcm_get_save_schema` first to discover available table names. |
| **pcm_get_player_info** | Get the active human player and their team from a save file. Returns the player login plus team details (name, resolved division name, resolved country name, evaluation and manager). |
| **pcm_query_save** | Run a read-only SQL query (`SELECT` / `WITH … SELECT` only) against any table in a save file. Write/DDL statements are rejected. Results are capped (default 100, max 1000 rows). |

## Installation

```bash
npm install
```

## Available Scripts

### Build

```bash
npm run build
```

Bundles `src/` to `dist/` with tsup (ESM output; `.d.ts` generation is currently disabled).

### Test

```bash
npm test
```

### Lint & Format

```bash
npm run lint
```

Runs Biome lint with autofixes (`biome lint --write .`).

```bash
npm run format
```

Formats the codebase with Biome (`biome format --write .`).

## Platform Support

PCM only ships on Windows, where careers live under:

```
%APPDATA%/Pro Cycling Manager <year>/Cloud/<profile>/
```

Auto-discovery via `list_saves` is therefore **Windows only**. On macOS/Linux the saves live inside a Wine/Proton prefix that can't be reliably located — pass an absolute `.cdb` path directly to `select_save` instead.

## Usage with Claude Desktop

Add the following configuration to your `claude_desktop_config.json`:

```json
{
"mcpServers": {
"pcm-mcp": {
"command": "node",
"args": ["/absolute/path/to/pcm-mcp/dist/index.js"]
}
}
}
```

The server communicates over stdio.
Loading
Loading