gpra-chord-charts-mcp
This server lets MCP-capable AI clients look up and share guitar chord charts from GPRA as text diagrams and image URLs.
get_chord_chart_by_name: pass a plain chord name (e.g.
G,Am7,D/F#) and get the single matching chart, exactly as the GPRA website shows it.get_chord_chart_by_id: re-render a previously returned chart by its numeric id.
get_chord_of_the_day: fetch today's Chord of the Day, the same one posted to Bluesky/Facebook.
Each result includes a direct PNG URL, a fenced text chord grid, and attribution; optional inline PNG bytes via
format: "image"or"both".Read-only, no database or auth needed by users; hosted remotely at
https://mcp.guitarpracticeroutine.com/mcpor run locally (stdio/HTTP).Library: 12,708 standard-tuning voicings, one per chord name; no fuzzy matching, so send exact chord names.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@gpra-chord-charts-mcpShow me the chord chart for C major"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Guitar chord charts — MCP server
A small, read-only Model Context Protocol server that lets MCP-capable AI clients look up guitar chord charts from Guitar Practice Routine App (GPRA) and show them to people as text chord diagrams.
It serves exactly what 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)
EADGBEFrets 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 |
| One chord name ( |
| The same chart by its numeric id, for re-rendering something already returned. |
| 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 + | Everywhere. This is the floor. |
MCP Apps widget ( | Hosts that render MCP Apps — Claude Code, Cowork, ChatGPT, PostHog Desktop. |
Inline PNG bytes ( | 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). 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.
Related MCP server: mcp-google-sheets
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/mcpFor clients configured with a JSON config file, use the streamable-HTTP transport:
{
"mcpServers": {
"gpra-chord-charts": {
"type": "streamable-http",
"url": "https://mcp.guitarpracticeroutine.com/mcp"
}
}
}For a client that only speaks stdio, bridge with
mcp-remote:
{
"mcpServers": {
"gpra-chord-charts": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://mcp.guitarpracticeroutine.com/mcp"]
}
}
}Run locally
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:
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:
psql "$DATABASE_URL" -Atf scripts/dump-chords.sql > data/common-chords.raw.json
npm run build:index
npm run build && npm testbuild: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: 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 by @TormodKv — the chord database this is built from, itself based on chord-collection by @T-vK.
SVGuitar by @omnibrain — the chord-diagram renderer whose data format this uses, and which draws the charts on 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.
Available Tools
3 toolsget_chord_chart_by_idGet a chord chart by idARead-onlyInspect
Fetch one specific chord voicing by its numeric id, as returned by get_chord_chart_by_name. Use this to re-render a chart the user already saw without looking it up again.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Numeric chord id. | |
| format | No | Which representations to return. Defaults to "text", which includes a direct URL to a PNG of the chart — embed that where your surface renders images. Use "image" or "both" only if you need the PNG bytes inline; they cost image tokens and many clients bury them. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint annotation already covers the safety profile, so the description only needs to add selective context. It adds the id provenance and reuse intent, but discloses no additional behavioral traits such as error handling, caching, or response details.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences, with the core action and id source front-loaded and the use case following immediately. No filler or redundant restatement of the title.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only lookup with fully described parameters and no nested objects, the description provides enough context. The format parameter schema covers return-representation behavior, though the main description does not describe the response shape or error case.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description adds value beyond the schema by explaining that the id comes from a prior get_chord_chart_by_name call, which tells the agent where the id originates and reinforces the re-render scenario.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (fetch), a specific resource (one chord chart/voicing by numeric id), and ties it to the sibling get_chord_chart_by_name. The re-render use case further clarifies its distinct role.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description clearly says to use this when re-rendering a chart the user already saw, avoiding a fresh lookup. It references get_chord_chart_by_name as the source of the id, but does not explicitly contrast with get_chord_of_the_day.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_chord_chart_by_nameGet a chord chart by nameARead-onlyInspect
Look up one guitar chord chart by name and return it as a text chord diagram ready to show the user. Returns the same single voicing that https://guitarpracticeroutine.com/find-a-chord-chart shows for that name. The library holds 12,708 standard-tuning (EADGBE) chord names, exactly one voicing each. Pass a plain chord name as it would be written on a chart — "G", "Am7", "Cmaj7", "D/F#", "F#m7b5" — not a sentence. Convert spoken forms yourself first: "G major" is "G", "A minor" is "Am", and use "#" and "b" rather than the unicode sharp and flat signs. Charts are drawn on a five-fret grid starting at the nut, the same as the website; any notes above the fifth fret are named in words underneath the chart. Prefer this over recalling a fingering from memory — these are curated chart data, and a remembered fingering is often wrong. Each result leads with a direct PNG URL for the chart — a permanently cacheable image of the same diagram, which you can show or link however your surface handles images. The chord name is on the first line; keep it next to any image you show, since a chart on its own can arrive unlabelled.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | A single chord name as written on a chart, e.g. "Am7" or "D/F#". | |
| format | No | Which representations to return. Defaults to "text", which includes a direct URL to a PNG of the chart — embed that where your surface renders images. Use "image" or "both" only if you need the PNG bytes inline; they cost image tokens and many clients bury them. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover read-only safety, and the description adds substantial behavioral context beyond that: exact source of truth (the website's voicing), rendering model (five-fret grid from the nut, note names under the chart above fret 5), output layout ('chord name is on the first line'), and the permanently cacheable PNG URL. No contradiction with annotations; openWorldHint=false is reinforced by the closed 12,708-name library.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Long but every sentence earns its place: no filler, and the core purpose is front-loaded before the supporting input, rendering, and image details. It is dense rather than bloated, though it could arguably compress the image-URL guidance.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the return-value burden and does so thoroughly: it specifies the text diagram, the PNG URL, the note-labeling convention, and the first-line chord name. The only meaningful gap is behavior for a name not in the library, which is not addressed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema covers both parameters at 100%, but the description adds real value over the schema: conversion rules ('G major' → 'G'), the instruction to use '#'/'b' rather than unicode signs, and the behavior of the format enum — 'text' includes a direct PNG URL while 'image'/'both' cost tokens. That exceeds the high-coverage baseline of 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource — 'Look up one guitar chord chart by name and return it as a text chord diagram' — and pins down the exact scope: 12,708 standard-tuning names, exactly one voicing each. It differentiates from the sibling tool by_name vs by_id vs of_the_day through the lookup key and the single-voicing specificity.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit when-to-use advice — 'Prefer this over recalling a fingering from memory — these are curated chart data, and a remembered fingering is often wrong' — and prescribes input form ('not a sentence', convert spoken forms yourself). It does not explicitly route to or away from the sibling tools, so it loses the top score.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_chord_of_the_dayChord of the dayARead-onlyInspect
Return today's Chord of the Day from Guitar Practice Routine App — the same chord posted to the app's Bluesky and Facebook feeds that day. Useful as a practice prompt or a daily nudge for someone learning chords.
| Name | Required | Description | Default |
|---|---|---|---|
| format | No | Which representations to return. Defaults to "text", which includes a direct URL to a PNG of the chart — embed that where your surface renders images. Use "image" or "both" only if you need the PNG bytes inline; they cost image tokens and many clients bury them. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and openWorldHint, so the safety and openness profile is covered. The description adds provenance (same chord as the social feeds) but no further behavioral details such as caching, availability, or edge cases; given the annotations, a 3 is appropriate.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences with no filler: the first states the core action and source, the second adds a legitimate use case. The description is front-loaded and every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only tool with one optional parameter, the description explains the daily nature, the source, and the practical purpose, while the schema covers format options. It could explicitly describe the returned representation, but nothing essential is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool description itself does not explain the format parameter, but the input schema provides 100% coverage with a detailed description of the default, use cases, and token-cost warning. Since the schema carries the parameter meaning, the baseline score of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: return today's Chord of the Day from the Guitar Practice Routine App, and clarifies it matches the chord posted to the app's social feeds that day. This makes it clearly distinguishable from siblings that fetch by id or name.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives a practical use case—practice prompt or daily nudge—but does not explicitly state when to prefer this tool over get_chord_chart_by_id or get_chord_chart_by_name. Usage guidance is implied rather than made explicit with exclusions or alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
- Changed
get_chord_chart_by_id3 fields changed- changed
Input schema / properties / format / descriptionPrevious value: -"Which representations to return. Defaults to \"both\". Use \"text\" when the user does not want images or the client cannot display them, and \"image\" when only the diagram is wanted."New value: +"Which representations to return. Defaults to \"text\", which includes a direct URL to a PNG of the chart — embed that where your surface renders images. Use \"image\" or \"both\" only if you need the PNG bytes inline; they cost image tokens and many clients bury them." - removed
Input schema / properties / id / exclusiveMinimumRemoved value: -0 - added
Input schema / properties / id / minimumAdded value: +-9007199254740991
- Changed
get_chord_chart_by_name2 fields changed- changed
Input schema / properties / format / descriptionPrevious value: -"Which representations to return. Defaults to \"both\". Use \"text\" when the user does not want images or the client cannot display them, and \"image\" when only the diagram is wanted."New value: +"Which representations to return. Defaults to \"text\", which includes a direct URL to a PNG of the chart — embed that where your surface renders images. Use \"image\" or \"both\" only if you need the PNG bytes inline; they cost image tokens and many clients bury them." - removed
Input schema / properties / name / minLengthRemoved value: -1
- Changed
get_chord_of_the_day1 field changed- changed
Input schema / properties / format / descriptionPrevious value: -"Which representations to return. Defaults to \"both\". Use \"text\" when the user does not want images or the client cannot display them, and \"image\" when only the diagram is wanted."New value: +"Which representations to return. Defaults to \"text\", which includes a direct URL to a PNG of the chart — embed that where your surface renders images. Use \"image\" or \"both\" only if you need the PNG bytes inline; they cost image tokens and many clients bury them."
3 tool updates
v0.1.0- First observed
get_chord_chart_by_id - First observed
get_chord_chart_by_name - First observed
get_chord_of_the_day
TDQS
Scored across 3 tools
Each tool has a distinct retrieval route: by numeric ID, by chord name, and by daily feature. The by_id tool explicitly states its ID comes from get_chord_chart_by_name, so an agent will not confuse it with a direct name lookup.
All tools use snake_case with a get_ prefix and follow a get_<object>_<qualifier> shape. The first two share the exact get_chord_chart_by_ pattern, while get_chord_of_the_day is a slight structural variation but remains predictable.
Three tools is well-scoped for a focused, read-only chord chart server. Each tool serves a clear purpose without redundancy, so the count is neither too thin nor bloated.
The core workflows are covered: lookup by name, re-fetch by ID, and the daily chord. Minor gaps exist such as no search/browse tool for the 12,708 chord names, but they are not fatal to the server's stated purpose.
Maintenance
Related MCP Connectors
MCP connector that lets ChatGPT list, search, and run your Apple Shortcuts via a local Mac agent
Generate AI music via the Lacuna Music API from MCP clients like Claude Desktop & Code.
Automate 1,000+ services from any MCP-compatible AI agent: build Applets, run actions and queries.
- UnifAPIOAuthcom.unifapi
Hosted MCP server for live public-data APIs and Skills for AI agents.
Related MCP Servers
- AlicenseBqualityBmaintenanceProvides access to Chess.com player data, game records, and public information through standardized MCP interfaces, allowing AI assistants to search and analyze chess information.1088MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to read, write, and manage Google Sheets spreadsheets through natural language commands, providing a bridge between MCP clients and the Google Sheets API.298 npm1MIT
- FlicenseNot gradedqualityDmaintenanceEnables AI to create, edit, and manage Mermaid diagrams via MCP, with real-time preview in a browser-based editor.3-
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to interact with a local Mermaid diagram editor via MCP, allowing them to get and set diagrams programmatically.15,151,633 npm13Apache 2.0