About IPSC Scoreboard
IPSC Scoreboard is an MCP server in the Search category: analyze IPSC competition data and results from Shoot-n-Score-It to track shooter performance. Compare competitors stage-by-stage using detailed metrics like hit factors, time-vs-accuracy breakdowns, and efficiency percentages. Search for upcoming matches or retrieve popular event details to stay updated on competitive practical shooting. It has been installed 0 times through Conduid.
Install
git clone https://github.com/mandakan/ssi-scoreboardThis server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.
Ask AI
Ask AI about IPSC Scoreboard
Powered by Claude · Grounded in docs
Security checks
- ·README presentNot checked yet.
- ·License declaredNot checked yet.
- ·Tests presentNot checked yet.
- ·Dependencies pinnedNot checked yet.
- ·No dynamic code executionNot checked yet.
- ·Scoped permissionsNot checked yet.
README
SSI Scoreboard
Live stage-by-stage comparison tool for IPSC competitions on shootnscoreit.com.
The official site only lets you view one competitor at a time. This app lets you select any group of competitors and compare their results across every stage side-by-side -- including during an active match before official results are published.
Live instance: scoreboard.urdr.dev -- not affiliated with or endorsed by ShootNScoreIt.
Prerequisites
- Node.js 20+
- pnpm 10+ (
corepack enable && corepack prepare pnpm@10.30.3 --activate) - A ShootNScoreIt API key (account settings on shootnscoreit.com)
Local Setup
pnpm install
cp .env.local.example .env.local # then fill in SSI_API_KEY
pnpm dev # http://localhost:3000
Deployment
The app supports two production targets, selected at build time via DEPLOY_TARGET. Both
require a Redis instance for hot caching, and a relational store (SQLite or D1) for
persistent shooter / match metadata that survives Redis flushes.
Docker / Docker Compose
cp .env.local.example .env.local # fill in SSI_API_KEY, CACHE_PURGE_SECRET
pnpm docker:build # builds image
pnpm docker:up # starts redis + app on port 3000
REDIS_URL is set automatically via the compose service name. Two named volumes persist
state across container restarts:
redis_data-- Redis hot cacheshooter_data->/app/data-- SQLite store (shooter profiles, match indices, achievements, historical match cache)
Without Docker Compose (bare server, Kubernetes, Fly.io): run a Redis instance and
set REDIS_URL to its connection string (rediss:// for TLS). Mount a writable volume
at ./data/ (or set SHOOTER_DB_PATH) so the SQLite database persists. The app uses
lazyConnect: true, so a missing Redis at startup is non-fatal -- requests fall back to
direct GraphQL fetches.
Cloudflare Pages
The Cloudflare build swaps in HTTP-based adapters at build time:
@upstash/redisinstead ofioredis(Workers cannot open TCP)- Cloudflare D1 instead of
better-sqlite3(Workers have no persistent disk)
You will need an Upstash Redis database and a D1 database.
pnpm cf:build # DEPLOY_TARGET=cloudflare + @opennextjs/cloudflare build
pnpm cf:deploy # build + wrangler deploy
Full step-by-step walkthrough -- wrangler login, secrets, custom subdomain, verification, troubleshooting -- in docs/deploy-cloudflare.md.
Note on serverless platforms without persistent disk (Vercel, Netlify default): the app's SQLite AppDatabase needs writable disk for shooter/match metadata. These platforms only work if you switch the persistence layer to D1 or a managed SQL store -- they are not currently supported targets.
Environment Variables
The most common ones an operator needs:
| Variable | Target | Description |
|---|---|---|
SSI_API_KEY |
Both | ShootNScoreIt API key -- server-side only, never exposed to browser |
CACHE_PURGE_SECRET |
Both | Secret for the admin cache-purge endpoint -- any strong random string |
NEXT_PUBLIC_BUILD_ID |
Both | Git SHA baked into the bundle for version detection (auto-injected by pnpm docker:build) |
REDIS_URL |
Docker | Redis connection string (redis://localhost:6379, or rediss://... for TLS) |
SHOOTER_DB_PATH |
Docker | Optional. Path to SQLite database file. Defaults to ./data/shooter-index.db |
UPSTASH_REDIS_REST_URL |
Cloudflare | REST URL from the Upstash console |
UPSTASH_REDIS_REST_TOKEN |
Cloudflare | REST token from the Upstash console |
MCP_SECRET |
Both | Optional. If set, POST /api/mcp requires Authorization: Bearer <MCP_SECRET> |
NEXT_PUBLIC_APP_URL |
Both | Base URL for MCP tool internal API calls. Required on Cloudflare Pages |
AI_PROVIDER / AI_MODEL / AI_API_KEY / AI_API_URL |
Both | AI coaching tips. Set to "openai" or "cloudflare"; omit AI_PROVIDER to disable |
Full reference -- including TTL tunables, telemetry sampling, probe kill switches, and all internal flags -- in docs/env-vars.md.
Usage
- Browse competitions month by month using the month navigator on the landing page,
or paste a match URL (
https://shootnscoreit.com/event/22/26547/) to jump straight to a match. The collapsible Filters panel narrows results by discipline, country, and level. - Select competitors individually by name, number, or club -- or tap Add squad to load an entire IPSC squad at once (up to 12 competitors total).
- Multiple analysis views update immediately (stage table, hit-factor and HF% charts, speed/accuracy scatter, balance radar, coaching panels, style fingerprint, AI tip).
- Use the Share button to copy the link with selections encoded -- recipients open the same match with the same competitors pre-selected.
Features
Comparison & rankings
- Side-by-side stage table with hit factor, time, hit zones, rank, percentile, shooting order, stage difficulty, run classification
- Group / Division / Overall ranking contexts
- Delta heatmap view (each cell relative to the group leader)
- Incomplete scorecard flag (IPSC rule 9.7.6.2)
- DQ banner and clean-match indicator
Charts
- Hit-factor bar chart with optional benchmark overlay
- HF% vs stage-winner line chart
- Speed-vs-accuracy scatter with iso-HF reference lines
- Stage-balance polar radar
- Style fingerprint (alpha ratio vs PPS) with archetype labels
- Style radar (composure, consistency, multi-axis profile)
- Info popovers on every chart explaining axes and reading tips
Coaching analysis
- Consistency score (CV of HF% across fired stages), efficiency (points per shot)
- Penalty rate, match-percentage impact, points left on the table
- "One stage away" what-if analysis
- AI coaching tips for completed matches (sparkle icon; requires AI provider configuration)
Cross-competition (shooter dashboard)
- Per-shooter career view with match history, aggregate stats
- Tiered achievements that survive the 200-match prune window
- Manual match URL submission, dashboard backfill from cached data
Search & navigation
- Month-by-month browser; typing a query switches to full-history search mode
- Filters: firearms, country (ISO 3166-1 alpha-3, defaults to SWE), level (L2+, L3+, L4+, all)
- Recent matches list (localStorage)
- Shareable URLs encode the full competitor selection
Platform
- Server-side cache with smart TTL tiers (future / pre-match / active / complete)
- Persistent SQLite/D1 store for shooter profiles, match indices, achievements, historical match cache
- Dynamic Open Graph images per match (Satori /
next/og) - MCP server (HTTP + stdio transports) -- 7 tools for AI assistant integration
- New-version banner; PWA installable; mobile-first (390px); WCAG 2.1 AA; dark mode
Development Commands
pnpm dev # start dev server on port 3000
pnpm build # production build
pnpm lint # ESLint -- zero warnings required
pnpm typecheck # TypeScript type check -- zero errors required
pnpm test # Vitest unit + component tests
pnpm test:watch # Vitest in watch mode
pnpm test:e2e # Playwright E2E tests (mocked API, no live key needed)
pnpm test:e2e:ui # Playwright with interactive UI
Quality bar: pnpm lint, pnpm typecheck, and pnpm test must all pass with zero
errors and zero warnings before merging. CI enforces this.
Intentionally pinned major versions
Held back from latest -- don't upgrade without a migration plan:
| Package | Pinned at | Why |
|---|---|---|
zod |
3.x | Zod 4 has breaking API changes. Needs a dedicated migration across lib/ and app/api/. |
eslint |
^9 | ESLint 10 is brand-new; wait until eslint-config-next officially supports it as a peer dep. |
MCP Server
SSI Scoreboard exposes an MCP server so Claude and other
AI assistants can query competition data directly. Seven tools: search_events, get_match,
compare_competitors, get_stage_times, get_popular_matches, get_shooter_dashboard,
find_shooter.
HTTP transport (public, no local setup needed):
POST https://scoreboard.urdr.dev/api/mcp
stdio transport (local subprocess) -- .mcp.json at repo root pre-registers two stdio
servers:
ssi-scoreboard-- calls the live production instance (no local server needed)ssi-scoreboard-local-- callslocalhost:3000(requirespnpm devrunning)
Claude Code picks up .mcp.json automatically when you open the repo.
For client-specific setup (Claude Desktop, generic HTTP clients), example prompts, and troubleshooting, see docs/mcp.md.
Architecture
Browser -> Next.js Route Handlers -> shootnscoreit.com/graphql/
^
POST /api/mcp (MCP clients / Claude)
The SSI_API_KEY lives server-side only and is never sent to the browser. Route Handlers
are the only place that touches the upstream GraphQL API.
Two persistence layers:
- Redis (ioredis on Docker, @upstash/redis on Cloudflare) -- hot cache for active matches and per-request data.
- AppDatabase (SQLite on Docker, D1 on Cloudflare) -- durable store for shooter profiles, match indices, achievements, and historical match data offloaded from Redis.
Key entry-points:
app/api/match/[ct]/[id]/-- match metadata (stages, competitors, scoring progress)app/api/compare/-- fans out scorecard queries, merges ranking dataapp/api/events/-- event search with date-range, firearms, country, level filtersapp/api/shooter/[shooterId]/-- cross-competition shooter dashboardapp/api/og/match/[ct]/[id]/-- dynamic Open Graph imagesapp/api/mcp/-- MCP HTTP endpoint (JSON-RPC, single-shot transport)app/api/coaching/-- AI coaching tipsapp/api/admin/cache/-- authenticated cache purge / health / force-refresh
For the full module layout and contributor conventions, see CLAUDE.md.
Design System
This project uses Tailwind v4 with CSS custom property design tokens (OKLCH color space)
defined in app/globals.css. Always use semantic token classes (bg-background,
text-muted-foreground, etc.) rather than raw palette classes (bg-gray-100).
Dark mode is supported via next-themes and the .dark class -- all tokens have light
and dark values. A theme toggle in the toolbar lets users switch at runtime.
shadcn/ui is the primary component library. Never modify files in components/ui/
directly; use pnpm dlx shadcn@latest add to update them.
Accessibility
This project targets WCAG 2.1 AA compliance:
- All interactive elements are keyboard-navigable with a visible focus ring
- Minimum touch target size 44x44px (enforced globally in
globals.css) - Error states use
role="alert"for screen reader announcements - Color is never the only means of conveying information -- pairs with shape, icon, or pattern
- Semantic HTML throughout (
<button>,<table>,<th scope>, etc.)
Support
If this tool saves you time at the range, consider buying me a coffee:
README mirrored from the source repository 4 months ago. The original is authoritative.
