Guitar Practice Routine App — Chord Charts
Look up a guitar chord by name and get a text chord diagram. 12,708 standard-tuning names.
Open source Repository Open in the app JSON README (API)
About
Look up a guitar chord by name and get a text chord diagram. 12,708 standard-tuning names.
Details
- Kind
- MCP servers
- Topic
- Media, design & games
- Publisher
- slshults
- Origin
- official
- Category
- ferramentas
- Transport
- http
- Version
- 0.1.0
- Last push
- 2026-08-31T20:17:33Z
- Repository state
- ativo
- Language
- TypeScript
- License
- MIT
- Added
- 2026-08-30 21:01:02
- Updated
- 2026-08-30 21:01:02
- Origin id
io.github.slshults/gpra-chord-charts
README
# Guitar chord charts — MCP server
A small, read-only [Model Context Protocol](https://modelcontextprotocol.io)
server that lets MCP-capable AI clients look up guitar chord charts from
[Guitar Practice Routine App (GPRA)](https://guitarpracticeroutine.com) and show
them to people as text chord diagrams.
It serves exactly what
[guitarpracticeroutine.com/find-a-chord-chart](https://guitarpracticeroutine.com/find-a-chord-chart)
serves: one chord name in, one chart out, from a bundled snapshot of the same
chord library. No database, no authentication, no secrets.
```
Am
E A D G B E
x o o
==================
1 | | | | 1 |
-+--+--+--+--+--+-
2 | | 3 2 | |
-+--+--+--+--+--+-
3 | | | | | |
-+--+--+--+--+--+-
4 | | | | | |
-+--+--+--+--+--+-
5 | | | | | |
-+--+--+--+--+--+-
x = muted o = open digits in grid = fingers (1 index, 2 middle, 3 ring, 4 pinky)
EADGBE
```
Frets run top to bottom from the nut, and string 1 — the highest-pitched
string — is the rightmost column, matching standard chord-box convention and
the charts on the site.
## Tools
| Tool | What it does |
| --- | --- |
| `get_chord_chart_by_name` | One chord name (`G`, `Am7`, `D/F#`) → the one chart the website shows for it. |
| `get_chord_chart_by_id` | The same chart by its numeric id, for re-rendering something already returned. |
| `get_chord_of_the_day` | Today's Chord of the Day — the same chord GPRA posts to Bluesky and Facebook. |
Each result **leads with a direct PNG URL** for the chart, then the same chart as
a fenced text grid. The URL is the picture in the form that survives every
surface: it renders where markdown images work, stays clickable where they
don't, and can be dropped into an artifact, an HTML page, or a saved file. The
text grid is the fallback for anywhere neither is true.
Inline PNG bytes are **opt-in** via `format: "image"` or `format: "both"`. They
cost image tokens and several clients bury them in a collapsed tool drawer, so
they're not worth sending by default now that a URL does the job. `format`
defaults to `"text"`, which still includes the image URL. Attribution is kept in
every mode.
Every tool also takes a `context` argument describing why it's being called.
That's injected by PostHog's MCP SDK and populates agent intent in analytics;
nothing in the response depends on it.
### How a chart reaches a person
There is no capability negotiation for "can you show an image", so the result
carries several layers and lets the host use whichever it understands:
| Layer | Where it lands |
| --- | --- |
| Text grid + `Chart image:` URL | Everywhere. This is the floor. |
| MCP Apps widget (`_meta.ui.resourceUri`) | Hosts that render MCP Apps — Claude Code, Cowork, ChatGPT, PostHog Desktop. |
| Inline PNG bytes (`format: "image"`) | Hosts that surface image content blocks, on request. |
The widget is a `ui://gpra-chord-charts/chart/{id}` resource resolving to a
self-contained HTML page with the chord already baked in — no JavaScript, no
postMessage bridge, no client bundle. A host that ignores it simply shows no
widget, and the text answer is untouched.
Measured on claude.ai, for anyone tempted to try these again: bare markdown
images render as a click-through placeholder that opens a browser tab rather
than loading inline; images wrapped in links collapse to the link; raw HTML is
escaped to literal text; and the MCP Apps iframe is fetched but never mounted
([ext-apps#671](https://github.com/modelcontextprotocol/ext-apps/issues/671)).
On that surface the URL is the whole story, which is why it leads the text.
Results deliberately carry **no `structuredContent`**. A client that understands
it may render it *instead of* the content blocks — one measurably did, reducing
the answer to three JSON fields and dropping the chart, the attribution and the
call to action. Nothing goes in a result that can displace the text.
## What's in the library
12,708 standard-tuning (EADGBE) voicings, **exactly one per chord name**. This
server does no fuzzy matching and no query cleanup — it passes the name through
the same way the website does, so the tool description asks the calling
assistant to send a plain chord name ("G", not "how do I play G major").
Charts are drawn on a five-fret grid starting at the nut, matching the site —
which means a voicing with notes above the fifth fret has them fall outside the
grid. The **image** drops them, exactly as the website does; the **text** names
them underneath ("Also fretted, past this five-fret grid: string 5 (A) fret 6").
So the pair is honest even where the picture alone isn't.
Images use black ink on white rather than the site's white-on-transparent: the
site can assume its own dark UI, and a chat client's background is unknown.
## Connecting a client
Remote MCP means users add a **URL**, no install:
```
https://mcp.guitarpracticeroutine.com/mcp
```
For clients configured with a JSON config file, use the streamable-HTTP
transport:
```json
{
"mcpServers": {
"gpra-chord-charts": {
"type": "streamable-http",
"url": "https://mcp.guitarpracticeroutine.com/mcp"
}
}
}
```
For a client that only speaks stdio, bridge with
[`mcp-remote`](https://www.npmjs.com/package/mcp-remote):
```json
{
"mcpServers": {
"gpra-chord-charts": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://mcp.guitarpracticeroutine.com/mcp"]
}
}
}
```
## Run locally
```bash
npm install
npm run build
npm test
npm start # stdio transport
npm run start:http # HTTP, listens on 127.0.0.1:2112, POST /mcp (set PORT / HOST)
```
Health check: `GET /health` → status, version, chord count.
Preview charts from the terminal without a client:
```bash
npx tsx scripts/preview.ts G Am C F Bm D/F#
```
No CORS headers are set. Claude and connector directories fetch server-side, so
they don't need them — a browser-based MCP client would.
### Rebuilding the chord snapshot
Requires access to a GPRA database. The source table changes rarely, so this is
a manual step:
```bash
psql "$DATABASE_URL" -Atf scripts/dump-chords.sql > data/common-chords.raw.json
npm run build:index
npm run build && npm test
```
`build:index` prints the library's fret range to the terminal and reports data
anomalies (voicings with no fretted notes, missing finger numbers, notes dropped
as unplayable) rather than quietly normalizing them away.
## Privacy
The hosted server records anonymous usage analytics through
[`@posthog/mcp`](https://www.npmjs.com/package/@posthog/mcp): which tool was
called, how long it took, whether it errored, the calling client's identity
(name and version on connect, user-agent on each call), and the `context` string
the agent supplied describing why it called. No personal identifier is stored and
no person profile is created.
Analytics are off entirely unless `POSTHOG_API_KEY` is set, so running this
yourself — including over stdio — sends nothing anywhere.
## Deployment
`deploy/` contains a systemd unit and an nginx reverse-proxy config, if you want
to host your own instance.
## Credits
The chord library exists because of other people's work:
- **[SVGuitar-ChordCollection](https://github.com/TormodKv/SVGuitar-ChordCollection)**
by [@TormodKv](https://github.com/TormodKv) — the chord database this is built
from, itself based on
[chord-collection](https://github.com/T-vK/chord-collection) by
[@T-vK](https://github.com/T-vK).
- **[SVGuitar](https://github.com/omnibrain/svguitar)** by
[@omnibrain](https://github.com/omnibrain) — the chord-diagram renderer whose
data format this uses, and which draws the charts on
[guitarpracticeroutine.com](https://guitarpracticeroutine.com). (This server
renders text, not SVG.)
## A note on `npm audit`
`npm audit` reports three high-severity advisories against `image-size`, pulled
in transitively by `svguitar` → `svgdom`. They are denial-of-service infinite
loops in the ICNS, JXL and HEIF **file** parsers, reached only through
`svgdom`'s `HTMLImageElement`, which calls `imageSizeFromFile` when an `<img>`
is given a source.
Nothing here constructs one. Chart rendering draws vector primitives from a
chord name and an integer id; the only inputs that cross the boundary are a
string of at most 64 characters and a positive integer, and no code path
accepts, fetches or decodes an image file. There is no upstream fix, so the
advisories will keep showing until `svgdom` moves off `image-size`.
## License
- **Code:** MIT — see `LICENSE`.
- **Provenance of the chord data:** see `NOTICE`.
- **Chord data:** the voicings originate from SVGuitar-ChordCollection, which
carries no stated licence, so no licence is asserted over them here. The
fingerings themselves are factual descriptions of where fingers go on a
fretboard. Please credit the projects above if you reuse the data.
---
[](https://glama.ai/mcp/servers/slshults/gpra-chord-charts-mcp)