setlist-mcp 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +9 -0
- package/SKILL.md +17 -3
- package/dist/attribution.js +6 -0
- package/dist/bundle.js +94 -23
- package/dist/client.js +9 -2
- package/dist/tools/artists.js +6 -3
- package/dist/tools/geo.js +6 -3
- package/dist/tools/setlists.js +24 -7
- package/dist/tools/users.js +7 -3
- package/dist/tools/venues.js +6 -3
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "MCP server for setlist.fm — concert setlists, artists, venues, and tours via the setlist.fm API",
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.4.0"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "setlist.fm",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "MCP server for setlist.fm — search concert setlists, artists, venues, and tours via natural language",
|
|
18
|
-
"version": "0.
|
|
18
|
+
"version": "0.4.0",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
package/README.md
CHANGED
|
@@ -42,6 +42,15 @@ Optional: `SETLIST_ACCEPT_LANGUAGE` (one of `en, es, fr, de, pt, tr, it, pl`) lo
|
|
|
42
42
|
|
|
43
43
|
See [SKILL.md](SKILL.md) for from-source setup, the full tool reference, and example flows.
|
|
44
44
|
|
|
45
|
+
## Attribution & terms
|
|
46
|
+
|
|
47
|
+
Use is governed by the [setlist.fm API terms](https://www.setlist.fm/help/api-terms). In short:
|
|
48
|
+
|
|
49
|
+
- **Attribute setlist.fm.** Every result carries a `url`; surface it as a *followable* source link (no `nofollow`) wherever the data is shown. The tool descriptions instruct the model to do this, and results pass the `url` through verbatim.
|
|
50
|
+
- **Non-commercial only** under a free key — commercial use requires setlist.fm's permission.
|
|
51
|
+
- **No persistent caching** — this server makes a live API call per tool invocation and keeps no datastore. Please don't add one.
|
|
52
|
+
- **Keep your API key private** — it lives in `SETLIST_API_KEY` (`.env` is gitignored) and never appears in tool output.
|
|
53
|
+
|
|
45
54
|
## Development
|
|
46
55
|
|
|
47
56
|
```bash
|
package/SKILL.md
CHANGED
|
@@ -72,7 +72,7 @@ All tools are read-only and prefixed `setlist_`.
|
|
|
72
72
|
- **`setlist_get_artist_setlists`** — an artist's setlists (most recent first), by `mbid`, paginated via `p`.
|
|
73
73
|
|
|
74
74
|
### Setlists
|
|
75
|
-
- **`setlist_search_setlists`** — search by any mix of artist, venue, city, country, tour, `date` (
|
|
75
|
+
- **`setlist_search_setlists`** — search by any mix of artist, venue, city, country, tour, `date` (ISO yyyy-MM-dd), or `year`.
|
|
76
76
|
- **`setlist_get_setlist`** — a setlist (with full song list) by `setlistId`.
|
|
77
77
|
- **`setlist_get_setlist_version`** — a specific historical version by `versionId`.
|
|
78
78
|
|
|
@@ -98,11 +98,25 @@ All tools are read-only and prefixed `setlist_`.
|
|
|
98
98
|
|
|
99
99
|
- **"What did Radiohead play at their last show?"** → `setlist_search_artists` (Radiohead → mbid) → `setlist_get_artist_setlists` (latest) → `setlist_get_setlist` for the song list.
|
|
100
100
|
- **"Setlists at Red Rocks in 2023"** → `setlist_search_venues` (Red Rocks → venueId) → `setlist_search_setlists` with `venueId` + `year: 2023`.
|
|
101
|
-
- **"Phish on 2023-08-07"** → `setlist_search_setlists` with `artistName: "Phish"`, `date: "
|
|
101
|
+
- **"Phish on 2023-08-07"** → `setlist_search_setlists` with `artistName: "Phish"`, `date: "2023-08-07"`.
|
|
102
|
+
|
|
103
|
+
## Attribution & API terms
|
|
104
|
+
|
|
105
|
+
setlist.fm's [API terms](https://www.setlist.fm/help/api-terms) bind anyone using this data. When you present setlist.fm results to a user:
|
|
106
|
+
|
|
107
|
+
- **Always cite the source.** Each setlist/artist/venue result includes a `url` — show it as a real, clickable link to setlist.fm (e.g. "Source: [The Beatles setlist on setlist.fm](https://www.setlist.fm/...)"). The terms require a *followable* link — never a `nofollow`. If a particular result has no `url`, link to https://www.setlist.fm instead.
|
|
108
|
+
- **Non-commercial use only.** A free API key covers non-commercial use; commercial use needs setlist.fm's permission.
|
|
109
|
+
- **Live data, not a datastore.** The terms forbid persistent caching — this server fetches fresh on every call and you should treat results as point-in-time, not build a local copy.
|
|
110
|
+
- **Don't expose or share the API key.** It's read from `SETLIST_API_KEY` and never appears in results.
|
|
111
|
+
|
|
112
|
+
## Interpreting setlist data
|
|
113
|
+
|
|
114
|
+
- Songs live in `sets.set[]`; each set may have an `encore` number (1 = first encore) and a `name` (e.g. an acoustic set or a full album).
|
|
115
|
+
- Each `song` may carry: `tape: true` (a pre-recorded intro/outro — not actually performed live), `cover` (the original artist when it's a cover), `with` (a guest performer), and `info` (a note like "acoustic" or "first time live"). Surface these when relevant rather than dropping them.
|
|
102
116
|
|
|
103
117
|
## Notes
|
|
104
118
|
|
|
105
119
|
- IDs chain: `search_*` tools return the `mbid` / `setlistId` / `venueId` / `geoId` you feed into the `get_*` tools.
|
|
106
|
-
-
|
|
120
|
+
- **All dates are ISO `yyyy-MM-dd`** — both the `date`/`lastUpdated` inputs and every `eventDate` in the output. (The server translates to/from setlist.fm's native `dd-MM-yyyy` internally.)
|
|
107
121
|
- Results are paginated; pass `p` (1-based) to page through large result sets.
|
|
108
122
|
- setlist.fm rate-limits the standard tier (~2 req/sec); a 429 is retried once.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// setlist.fm API terms require a *followable* attribution link wherever their
|
|
2
|
+
// data appears (no nofollow): https://www.setlist.fm/help/api-terms. Every
|
|
3
|
+
// setlist/artist/venue object carries a `url` (passed through verbatim by
|
|
4
|
+
// textResult), so this note — appended to each data tool's description, but not
|
|
5
|
+
// to setlist_healthcheck — tells the model to surface that `url` as a source link.
|
|
6
|
+
export const ATTRIBUTION_NOTE = ' Results include a setlist.fm `url`; when you present this data, cite it as a clickable source link to setlist.fm (their API terms require followable attribution — no nofollow). If a result has no `url`, link to https://www.setlist.fm instead.';
|
package/dist/bundle.js
CHANGED
|
@@ -31001,6 +31001,22 @@ function textResult(data) {
|
|
|
31001
31001
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
31002
31002
|
};
|
|
31003
31003
|
}
|
|
31004
|
+
function deepMapStringField(value, field, map2) {
|
|
31005
|
+
if (Array.isArray(value)) {
|
|
31006
|
+
for (const item of value)
|
|
31007
|
+
deepMapStringField(item, field, map2);
|
|
31008
|
+
} else if (value !== null && typeof value === "object") {
|
|
31009
|
+
const obj = value;
|
|
31010
|
+
for (const key of Object.keys(obj)) {
|
|
31011
|
+
const v = obj[key];
|
|
31012
|
+
if (key === field && typeof v === "string")
|
|
31013
|
+
obj[key] = map2(v);
|
|
31014
|
+
else
|
|
31015
|
+
deepMapStringField(v, field, map2);
|
|
31016
|
+
}
|
|
31017
|
+
}
|
|
31018
|
+
return value;
|
|
31019
|
+
}
|
|
31004
31020
|
|
|
31005
31021
|
// node_modules/@chrischall/mcp-utils/dist/errors/index.js
|
|
31006
31022
|
var DEFAULT_ERROR_MESSAGE_MAX = 500;
|
|
@@ -31071,6 +31087,15 @@ var RateLimitedError = class extends Error {
|
|
|
31071
31087
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
31072
31088
|
}
|
|
31073
31089
|
};
|
|
31090
|
+
var RequestTimeoutError = class extends Error {
|
|
31091
|
+
timeoutMs;
|
|
31092
|
+
constructor(service, timeoutMs) {
|
|
31093
|
+
super(`Request to ${service} timed out after ${timeoutMs}ms.`);
|
|
31094
|
+
this.name = "RequestTimeoutError";
|
|
31095
|
+
this.timeoutMs = timeoutMs;
|
|
31096
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
31097
|
+
}
|
|
31098
|
+
};
|
|
31074
31099
|
function hostOf(baseUrl) {
|
|
31075
31100
|
try {
|
|
31076
31101
|
return new URL(baseUrl).host;
|
|
@@ -31086,6 +31111,18 @@ function createApiClient(opts) {
|
|
|
31086
31111
|
const sleep = opts.sleep ?? defaultSleep;
|
|
31087
31112
|
const unauthorized = () => opts.onUnauthorized ? opts.onUnauthorized() : new UnauthorizedError(service);
|
|
31088
31113
|
const rateLimited = () => opts.onRateLimited ? opts.onRateLimited() : new RateLimitedError(service);
|
|
31114
|
+
const timeoutMs = opts.timeout;
|
|
31115
|
+
function withTimeout(run) {
|
|
31116
|
+
if (timeoutMs == null || timeoutMs <= 0)
|
|
31117
|
+
return run(void 0);
|
|
31118
|
+
const controller = new AbortController();
|
|
31119
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
31120
|
+
return run(controller.signal).catch((err) => {
|
|
31121
|
+
if (err instanceof Error && err.name === "AbortError")
|
|
31122
|
+
throw new RequestTimeoutError(service, timeoutMs);
|
|
31123
|
+
throw err;
|
|
31124
|
+
}).finally(() => clearTimeout(timer));
|
|
31125
|
+
}
|
|
31089
31126
|
async function send(method, path, opt) {
|
|
31090
31127
|
const isMultipart = opt.formData !== void 0;
|
|
31091
31128
|
const hasJsonBody = !isMultipart && opt.body !== void 0;
|
|
@@ -31093,7 +31130,7 @@ function createApiClient(opts) {
|
|
|
31093
31130
|
const query = opt.query ? buildQueryString(opt.query) : "";
|
|
31094
31131
|
const url2 = `${base}${path}${query}`;
|
|
31095
31132
|
const bodyInit = reqBody !== void 0 ? { body: reqBody } : {};
|
|
31096
|
-
const fetchWith = (token) => doFetch(url2, {
|
|
31133
|
+
const fetchWith = (token, signal) => doFetch(url2, {
|
|
31097
31134
|
method,
|
|
31098
31135
|
headers: {
|
|
31099
31136
|
Accept: "application/json",
|
|
@@ -31102,9 +31139,10 @@ function createApiClient(opts) {
|
|
|
31102
31139
|
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
31103
31140
|
...opt.headers
|
|
31104
31141
|
},
|
|
31142
|
+
...signal ? { signal } : {},
|
|
31105
31143
|
...bodyInit
|
|
31106
31144
|
});
|
|
31107
|
-
const once =
|
|
31145
|
+
const once = () => withTimeout((signal) => opts.tokenManager ? opts.tokenManager.withAuth((token) => fetchWith(token, signal)) : (async () => fetchWith(await opts.getToken?.() || void 0, signal))());
|
|
31108
31146
|
let attempt = 0;
|
|
31109
31147
|
for (; ; ) {
|
|
31110
31148
|
const res = await once();
|
|
@@ -31171,6 +31209,29 @@ function formatApiError(status, method, path, errorText, opts = {}) {
|
|
|
31171
31209
|
return safe.length > 0 ? `${head}: ${safe}` : head;
|
|
31172
31210
|
}
|
|
31173
31211
|
|
|
31212
|
+
// node_modules/@chrischall/mcp-utils/dist/dates/index.js
|
|
31213
|
+
var ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
31214
|
+
var DMY_DATE = /^(\d{2})-(\d{2})-(\d{4})$/;
|
|
31215
|
+
var ISO_DATETIME = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2}))?$/;
|
|
31216
|
+
function isoToDmy(date5) {
|
|
31217
|
+
const m = ISO_DATE.exec(date5.trim());
|
|
31218
|
+
return m ? `${m[3]}-${m[2]}-${m[1]}` : date5.trim();
|
|
31219
|
+
}
|
|
31220
|
+
function dmyToIso(date5) {
|
|
31221
|
+
const m = DMY_DATE.exec(date5.trim());
|
|
31222
|
+
return m ? `${m[3]}-${m[2]}-${m[1]}` : date5.trim();
|
|
31223
|
+
}
|
|
31224
|
+
function isoToCompactTimestamp(value) {
|
|
31225
|
+
const v = value.trim();
|
|
31226
|
+
const d = ISO_DATE.exec(v);
|
|
31227
|
+
if (d)
|
|
31228
|
+
return `${d[1]}${d[2]}${d[3]}000000`;
|
|
31229
|
+
const dt = ISO_DATETIME.exec(v);
|
|
31230
|
+
if (dt)
|
|
31231
|
+
return `${dt[1]}${dt[2]}${dt[3]}${dt[4]}${dt[5]}${dt[6] ?? "00"}`;
|
|
31232
|
+
return v;
|
|
31233
|
+
}
|
|
31234
|
+
|
|
31174
31235
|
// node_modules/@chrischall/mcp-utils/dist/zod/index.js
|
|
31175
31236
|
var PositiveInt = external_exports.number().int().positive();
|
|
31176
31237
|
var NonNegInt = external_exports.number().int().nonnegative();
|
|
@@ -31197,7 +31258,7 @@ function toolAnnotations(opts = {}) {
|
|
|
31197
31258
|
}
|
|
31198
31259
|
|
|
31199
31260
|
// src/version.ts
|
|
31200
|
-
var VERSION = "0.
|
|
31261
|
+
var VERSION = "0.4.0";
|
|
31201
31262
|
|
|
31202
31263
|
// src/client.ts
|
|
31203
31264
|
import { dirname, join } from "path";
|
|
@@ -31206,6 +31267,7 @@ var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
31206
31267
|
await loadDotenvSafely({ path: join(__dirname, "..", ".env"), override: false });
|
|
31207
31268
|
var BASE_URL = "https://api.setlist.fm/rest";
|
|
31208
31269
|
var SERVICE_NAME = "setlist.fm";
|
|
31270
|
+
var REQUEST_TIMEOUT_MS = 15e3;
|
|
31209
31271
|
var SetlistClient = class {
|
|
31210
31272
|
apiKey;
|
|
31211
31273
|
configError;
|
|
@@ -31229,7 +31291,8 @@ var SetlistClient = class {
|
|
|
31229
31291
|
baseUrl: BASE_URL,
|
|
31230
31292
|
serviceName: SERVICE_NAME,
|
|
31231
31293
|
retry: { count: 1, delayMs: 2e3 },
|
|
31232
|
-
baseHeaders: lang ? { "Accept-Language": lang } : void 0
|
|
31294
|
+
baseHeaders: lang ? { "Accept-Language": lang } : void 0,
|
|
31295
|
+
timeout: REQUEST_TIMEOUT_MS
|
|
31233
31296
|
});
|
|
31234
31297
|
}
|
|
31235
31298
|
requireKey() {
|
|
@@ -31243,22 +31306,26 @@ var SetlistClient = class {
|
|
|
31243
31306
|
*/
|
|
31244
31307
|
async request(method, path, opts = {}) {
|
|
31245
31308
|
const apiKey = this.requireKey();
|
|
31246
|
-
|
|
31309
|
+
const data = await this.api.fetchJson(method, path, {
|
|
31247
31310
|
headers: { "x-api-key": apiKey },
|
|
31248
31311
|
...opts.query !== void 0 ? { query: opts.query } : {},
|
|
31249
31312
|
...opts.body !== void 0 ? { body: opts.body } : {}
|
|
31250
31313
|
});
|
|
31314
|
+
return deepMapStringField(data, "eventDate", dmyToIso);
|
|
31251
31315
|
}
|
|
31252
31316
|
};
|
|
31253
31317
|
var client = new SetlistClient();
|
|
31254
31318
|
|
|
31319
|
+
// src/attribution.ts
|
|
31320
|
+
var ATTRIBUTION_NOTE = " Results include a setlist.fm `url`; when you present this data, cite it as a clickable source link to setlist.fm (their API terms require followable attribution \u2014 no nofollow). If a result has no `url`, link to https://www.setlist.fm instead.";
|
|
31321
|
+
|
|
31255
31322
|
// src/tools/artists.ts
|
|
31256
31323
|
var page = external_exports.number().int().positive().optional().describe("Result page number (defaults to 1)");
|
|
31257
31324
|
function registerArtistTools(server) {
|
|
31258
31325
|
server.registerTool(
|
|
31259
31326
|
"setlist_search_artists",
|
|
31260
31327
|
{
|
|
31261
|
-
description: "Search setlist.fm for artists by name or MusicBrainz ID. Returns matching artists with their MusicBrainz ID (mbid) \u2014 use that mbid with setlist_get_artist or setlist_get_artist_setlists.",
|
|
31328
|
+
description: "Search setlist.fm for artists by name or MusicBrainz ID. Returns matching artists with their MusicBrainz ID (mbid) \u2014 use that mbid with setlist_get_artist or setlist_get_artist_setlists." + ATTRIBUTION_NOTE,
|
|
31262
31329
|
annotations: { readOnlyHint: true },
|
|
31263
31330
|
inputSchema: {
|
|
31264
31331
|
artistName: external_exports.string().optional().describe("Artist name to search for"),
|
|
@@ -31277,7 +31344,7 @@ function registerArtistTools(server) {
|
|
|
31277
31344
|
server.registerTool(
|
|
31278
31345
|
"setlist_get_artist",
|
|
31279
31346
|
{
|
|
31280
|
-
description: "Get a setlist.fm artist by their MusicBrainz ID (mbid).",
|
|
31347
|
+
description: "Get a setlist.fm artist by their MusicBrainz ID (mbid)." + ATTRIBUTION_NOTE,
|
|
31281
31348
|
annotations: { readOnlyHint: true },
|
|
31282
31349
|
inputSchema: {
|
|
31283
31350
|
mbid: external_exports.string().describe("Artist's MusicBrainz ID (mbid)")
|
|
@@ -31291,7 +31358,7 @@ function registerArtistTools(server) {
|
|
|
31291
31358
|
server.registerTool(
|
|
31292
31359
|
"setlist_get_artist_setlists",
|
|
31293
31360
|
{
|
|
31294
|
-
description: "Get an artist's setlists (most recent first) by their MusicBrainz ID (mbid). Paginated via `p`.",
|
|
31361
|
+
description: "Get an artist's setlists (most recent first) by their MusicBrainz ID (mbid). Paginated via `p`." + ATTRIBUTION_NOTE,
|
|
31295
31362
|
annotations: { readOnlyHint: true },
|
|
31296
31363
|
inputSchema: {
|
|
31297
31364
|
mbid: external_exports.string().describe("Artist's MusicBrainz ID (mbid)"),
|
|
@@ -31310,12 +31377,13 @@ function registerArtistTools(server) {
|
|
|
31310
31377
|
}
|
|
31311
31378
|
|
|
31312
31379
|
// src/tools/setlists.ts
|
|
31380
|
+
var SETLIST_SHAPE_NOTE = ' A setlist\'s songs live in `sets.set[]`; each set may have an `encore` number (1 = first encore) and a `name` (e.g. an acoustic set or a full album). Each `song` may carry: `tape: true` (pre-recorded intro/outro/interlude \u2014 not actually performed), `cover` (the original artist when it is a cover), `with` (a guest performer), and `info` (a note like "acoustic" or "first time live").';
|
|
31313
31381
|
var page2 = external_exports.number().int().positive().optional().describe("Result page number (defaults to 1)");
|
|
31314
31382
|
function registerSetlistTools(server) {
|
|
31315
31383
|
server.registerTool(
|
|
31316
31384
|
"setlist_search_setlists",
|
|
31317
31385
|
{
|
|
31318
|
-
description: "Search setlist.fm for concert setlists. Filter by any combination of artist, venue, city, country, tour, date, or year. Returns setlists with their songs and event details. Provide at least one filter.",
|
|
31386
|
+
description: "Search setlist.fm for concert setlists. Filter by any combination of artist, venue, city, country, tour, date, or year. Returns setlists with their songs and event details. Provide at least one filter." + ATTRIBUTION_NOTE,
|
|
31319
31387
|
annotations: { readOnlyHint: true },
|
|
31320
31388
|
inputSchema: {
|
|
31321
31389
|
artistName: external_exports.string().optional().describe("Artist name"),
|
|
@@ -31328,21 +31396,24 @@ function registerSetlistTools(server) {
|
|
|
31328
31396
|
stateCode: external_exports.string().optional().describe("State code"),
|
|
31329
31397
|
countryCode: external_exports.string().optional().describe("Country code (ISO 3166-1 alpha-2)"),
|
|
31330
31398
|
tourName: external_exports.string().optional().describe("Tour name"),
|
|
31331
|
-
date: external_exports.string().optional().describe("Event date
|
|
31399
|
+
date: external_exports.string().optional().describe("Event date, ISO yyyy-MM-dd (e.g. 2025-08-28)"),
|
|
31332
31400
|
year: external_exports.number().int().optional().describe("Event year"),
|
|
31333
|
-
lastUpdated: external_exports.string().optional().describe("Only setlists updated after this UTC time
|
|
31401
|
+
lastUpdated: external_exports.string().optional().describe("Only setlists updated on/after this UTC time, ISO yyyy-MM-dd or yyyy-MM-ddTHH:mm:ss"),
|
|
31334
31402
|
p: page2
|
|
31335
31403
|
}
|
|
31336
31404
|
},
|
|
31337
31405
|
async (args) => {
|
|
31338
|
-
const
|
|
31406
|
+
const query = { ...args };
|
|
31407
|
+
if (args.date) query.date = isoToDmy(args.date);
|
|
31408
|
+
if (args.lastUpdated) query.lastUpdated = isoToCompactTimestamp(args.lastUpdated);
|
|
31409
|
+
const data = await client.request("GET", "/1.0/search/setlists", { query });
|
|
31339
31410
|
return textResult(data);
|
|
31340
31411
|
}
|
|
31341
31412
|
);
|
|
31342
31413
|
server.registerTool(
|
|
31343
31414
|
"setlist_get_setlist",
|
|
31344
31415
|
{
|
|
31345
|
-
description: "Get a setlist.fm setlist by its ID, including the full song list and event details.",
|
|
31416
|
+
description: "Get a setlist.fm setlist by its ID, including the full song list and event details." + SETLIST_SHAPE_NOTE + ATTRIBUTION_NOTE,
|
|
31346
31417
|
annotations: { readOnlyHint: true },
|
|
31347
31418
|
inputSchema: {
|
|
31348
31419
|
setlistId: external_exports.string().describe("Setlist ID (e.g. 63de4613)")
|
|
@@ -31356,7 +31427,7 @@ function registerSetlistTools(server) {
|
|
|
31356
31427
|
server.registerTool(
|
|
31357
31428
|
"setlist_get_setlist_version",
|
|
31358
31429
|
{
|
|
31359
|
-
description: "Get a specific historical version of a setlist by its version ID. Setlists are wiki-edited; each edit has a version ID returned in a setlist's `versionId` field.",
|
|
31430
|
+
description: "Get a specific historical version of a setlist by its version ID. Setlists are wiki-edited; each edit has a version ID returned in a setlist's `versionId` field." + SETLIST_SHAPE_NOTE + ATTRIBUTION_NOTE,
|
|
31360
31431
|
annotations: { readOnlyHint: true },
|
|
31361
31432
|
inputSchema: {
|
|
31362
31433
|
versionId: external_exports.string().describe("Setlist version ID")
|
|
@@ -31378,7 +31449,7 @@ function registerVenueTools(server) {
|
|
|
31378
31449
|
server.registerTool(
|
|
31379
31450
|
"setlist_search_venues",
|
|
31380
31451
|
{
|
|
31381
|
-
description: "Search setlist.fm for venues by name and/or location. Returns matching venues with their venue ID \u2014 use it with setlist_get_venue or setlist_get_venue_setlists.",
|
|
31452
|
+
description: "Search setlist.fm for venues by name and/or location. Returns matching venues with their venue ID \u2014 use it with setlist_get_venue or setlist_get_venue_setlists." + ATTRIBUTION_NOTE,
|
|
31382
31453
|
annotations: { readOnlyHint: true },
|
|
31383
31454
|
inputSchema: {
|
|
31384
31455
|
name: external_exports.string().optional().describe("Venue name"),
|
|
@@ -31398,7 +31469,7 @@ function registerVenueTools(server) {
|
|
|
31398
31469
|
server.registerTool(
|
|
31399
31470
|
"setlist_get_venue",
|
|
31400
31471
|
{
|
|
31401
|
-
description: "Get a setlist.fm venue by its ID.",
|
|
31472
|
+
description: "Get a setlist.fm venue by its ID." + ATTRIBUTION_NOTE,
|
|
31402
31473
|
annotations: { readOnlyHint: true },
|
|
31403
31474
|
inputSchema: {
|
|
31404
31475
|
venueId: external_exports.string().describe("Venue ID")
|
|
@@ -31412,7 +31483,7 @@ function registerVenueTools(server) {
|
|
|
31412
31483
|
server.registerTool(
|
|
31413
31484
|
"setlist_get_venue_setlists",
|
|
31414
31485
|
{
|
|
31415
|
-
description: "Get setlists performed at a venue, by venue ID (most recent first). Paginated via `p`.",
|
|
31486
|
+
description: "Get setlists performed at a venue, by venue ID (most recent first). Paginated via `p`." + ATTRIBUTION_NOTE,
|
|
31416
31487
|
annotations: { readOnlyHint: true },
|
|
31417
31488
|
inputSchema: {
|
|
31418
31489
|
venueId: external_exports.string().describe("Venue ID"),
|
|
@@ -31436,7 +31507,7 @@ function registerGeoTools(server) {
|
|
|
31436
31507
|
server.registerTool(
|
|
31437
31508
|
"setlist_search_cities",
|
|
31438
31509
|
{
|
|
31439
|
-
description: "Search setlist.fm for cities by name and/or location. Returns cities with their geoId \u2014 use it as cityId in setlist_search_setlists / setlist_search_venues, or with setlist_get_city.",
|
|
31510
|
+
description: "Search setlist.fm for cities by name and/or location. Returns cities with their geoId \u2014 use it as cityId in setlist_search_setlists / setlist_search_venues, or with setlist_get_city." + ATTRIBUTION_NOTE,
|
|
31440
31511
|
annotations: { readOnlyHint: true },
|
|
31441
31512
|
inputSchema: {
|
|
31442
31513
|
name: external_exports.string().optional().describe("City name"),
|
|
@@ -31454,7 +31525,7 @@ function registerGeoTools(server) {
|
|
|
31454
31525
|
server.registerTool(
|
|
31455
31526
|
"setlist_get_city",
|
|
31456
31527
|
{
|
|
31457
|
-
description: "Get a city by its geoId.",
|
|
31528
|
+
description: "Get a city by its geoId." + ATTRIBUTION_NOTE,
|
|
31458
31529
|
annotations: { readOnlyHint: true },
|
|
31459
31530
|
inputSchema: {
|
|
31460
31531
|
geoId: external_exports.string().describe("City's geoId")
|
|
@@ -31468,7 +31539,7 @@ function registerGeoTools(server) {
|
|
|
31468
31539
|
server.registerTool(
|
|
31469
31540
|
"setlist_search_countries",
|
|
31470
31541
|
{
|
|
31471
|
-
description: "List all countries supported by setlist.fm, with their ISO country codes. Use a code as countryCode in setlist_search_setlists.",
|
|
31542
|
+
description: "List all countries supported by setlist.fm, with their ISO country codes. Use a code as countryCode in setlist_search_setlists." + ATTRIBUTION_NOTE,
|
|
31472
31543
|
annotations: { readOnlyHint: true }
|
|
31473
31544
|
},
|
|
31474
31545
|
async () => {
|
|
@@ -31484,7 +31555,7 @@ function registerUserTools(server) {
|
|
|
31484
31555
|
server.registerTool(
|
|
31485
31556
|
"setlist_get_user",
|
|
31486
31557
|
{
|
|
31487
|
-
description: "Get a setlist.fm user's public profile by their userId (their setlist.fm username).",
|
|
31558
|
+
description: "Get a setlist.fm user's public profile by their userId (their setlist.fm username)." + ATTRIBUTION_NOTE,
|
|
31488
31559
|
annotations: { readOnlyHint: true },
|
|
31489
31560
|
inputSchema: {
|
|
31490
31561
|
userId: external_exports.string().describe("setlist.fm userId (username)")
|
|
@@ -31498,7 +31569,7 @@ function registerUserTools(server) {
|
|
|
31498
31569
|
server.registerTool(
|
|
31499
31570
|
"setlist_get_user_attended",
|
|
31500
31571
|
{
|
|
31501
|
-
description: "Get the concerts a setlist.fm user has marked as attended. Paginated via `p`.",
|
|
31572
|
+
description: "Get the concerts a setlist.fm user has marked as attended. Paginated via `p`." + ATTRIBUTION_NOTE,
|
|
31502
31573
|
annotations: { readOnlyHint: true },
|
|
31503
31574
|
inputSchema: {
|
|
31504
31575
|
userId: external_exports.string().describe("setlist.fm userId (username)"),
|
|
@@ -31517,7 +31588,7 @@ function registerUserTools(server) {
|
|
|
31517
31588
|
server.registerTool(
|
|
31518
31589
|
"setlist_get_user_edited",
|
|
31519
31590
|
{
|
|
31520
|
-
description: "Get the setlists a setlist.fm user has created or edited. Paginated via `p`.",
|
|
31591
|
+
description: "Get the setlists a setlist.fm user has created or edited. Paginated via `p`." + ATTRIBUTION_NOTE,
|
|
31521
31592
|
annotations: { readOnlyHint: true },
|
|
31522
31593
|
inputSchema: {
|
|
31523
31594
|
userId: external_exports.string().describe("setlist.fm userId (username)"),
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { dirname, join } from 'path';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
|
-
import { loadDotenvSafely, readEnvVar, createApiClient, } from '@chrischall/mcp-utils';
|
|
3
|
+
import { loadDotenvSafely, readEnvVar, createApiClient, deepMapStringField, dmyToIso, } from '@chrischall/mcp-utils';
|
|
4
4
|
// Load .env for local dev; silently skip if dotenv is unavailable (e.g. the
|
|
5
5
|
// mcpb bundle). `loadDotenvSafely` swallows a missing dotenv module and never
|
|
6
6
|
// lets .env override a host-provided value.
|
|
@@ -8,6 +8,10 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
8
8
|
await loadDotenvSafely({ path: join(__dirname, '..', '.env'), override: false });
|
|
9
9
|
const BASE_URL = 'https://api.setlist.fm/rest';
|
|
10
10
|
const SERVICE_NAME = 'setlist.fm';
|
|
11
|
+
// Bound every request so a slow/hung upstream fails fast (createApiClient throws
|
|
12
|
+
// RequestTimeoutError) instead of hanging the tool call. setlist.fm normally
|
|
13
|
+
// answers in well under a second.
|
|
14
|
+
const REQUEST_TIMEOUT_MS = 15_000;
|
|
11
15
|
export class SetlistClient {
|
|
12
16
|
apiKey;
|
|
13
17
|
configError;
|
|
@@ -37,6 +41,7 @@ export class SetlistClient {
|
|
|
37
41
|
serviceName: SERVICE_NAME,
|
|
38
42
|
retry: { count: 1, delayMs: 2000 },
|
|
39
43
|
baseHeaders: lang ? { 'Accept-Language': lang } : undefined,
|
|
44
|
+
timeout: REQUEST_TIMEOUT_MS,
|
|
40
45
|
});
|
|
41
46
|
}
|
|
42
47
|
requireKey() {
|
|
@@ -51,11 +56,13 @@ export class SetlistClient {
|
|
|
51
56
|
*/
|
|
52
57
|
async request(method, path, opts = {}) {
|
|
53
58
|
const apiKey = this.requireKey();
|
|
54
|
-
|
|
59
|
+
const data = await this.api.fetchJson(method, path, {
|
|
55
60
|
headers: { 'x-api-key': apiKey },
|
|
56
61
|
...(opts.query !== undefined ? { query: opts.query } : {}),
|
|
57
62
|
...(opts.body !== undefined ? { body: opts.body } : {}),
|
|
58
63
|
});
|
|
64
|
+
// Surface every date as ISO yyyy-MM-dd (the API returns eventDate as dd-MM-yyyy).
|
|
65
|
+
return deepMapStringField(data, 'eventDate', dmyToIso);
|
|
59
66
|
}
|
|
60
67
|
}
|
|
61
68
|
/**
|
package/dist/tools/artists.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { textResult } from '@chrischall/mcp-utils';
|
|
3
3
|
import { client } from '../client.js';
|
|
4
|
+
import { ATTRIBUTION_NOTE } from '../attribution.js';
|
|
4
5
|
const page = z
|
|
5
6
|
.number()
|
|
6
7
|
.int()
|
|
@@ -9,7 +10,8 @@ const page = z
|
|
|
9
10
|
.describe('Result page number (defaults to 1)');
|
|
10
11
|
export function registerArtistTools(server) {
|
|
11
12
|
server.registerTool('setlist_search_artists', {
|
|
12
|
-
description: "Search setlist.fm for artists by name or MusicBrainz ID. Returns matching artists with their MusicBrainz ID (mbid) — use that mbid with setlist_get_artist or setlist_get_artist_setlists."
|
|
13
|
+
description: "Search setlist.fm for artists by name or MusicBrainz ID. Returns matching artists with their MusicBrainz ID (mbid) — use that mbid with setlist_get_artist or setlist_get_artist_setlists." +
|
|
14
|
+
ATTRIBUTION_NOTE,
|
|
13
15
|
annotations: { readOnlyHint: true },
|
|
14
16
|
inputSchema: {
|
|
15
17
|
artistName: z.string().optional().describe('Artist name to search for'),
|
|
@@ -27,7 +29,7 @@ export function registerArtistTools(server) {
|
|
|
27
29
|
return textResult(data);
|
|
28
30
|
});
|
|
29
31
|
server.registerTool('setlist_get_artist', {
|
|
30
|
-
description: "Get a setlist.fm artist by their MusicBrainz ID (mbid).",
|
|
32
|
+
description: "Get a setlist.fm artist by their MusicBrainz ID (mbid)." + ATTRIBUTION_NOTE,
|
|
31
33
|
annotations: { readOnlyHint: true },
|
|
32
34
|
inputSchema: {
|
|
33
35
|
mbid: z.string().describe("Artist's MusicBrainz ID (mbid)"),
|
|
@@ -37,7 +39,8 @@ export function registerArtistTools(server) {
|
|
|
37
39
|
return textResult(data);
|
|
38
40
|
});
|
|
39
41
|
server.registerTool('setlist_get_artist_setlists', {
|
|
40
|
-
description: "Get an artist's setlists (most recent first) by their MusicBrainz ID (mbid). Paginated via `p`."
|
|
42
|
+
description: "Get an artist's setlists (most recent first) by their MusicBrainz ID (mbid). Paginated via `p`." +
|
|
43
|
+
ATTRIBUTION_NOTE,
|
|
41
44
|
annotations: { readOnlyHint: true },
|
|
42
45
|
inputSchema: {
|
|
43
46
|
mbid: z.string().describe("Artist's MusicBrainz ID (mbid)"),
|
package/dist/tools/geo.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { textResult } from '@chrischall/mcp-utils';
|
|
3
3
|
import { client } from '../client.js';
|
|
4
|
+
import { ATTRIBUTION_NOTE } from '../attribution.js';
|
|
4
5
|
const page = z
|
|
5
6
|
.number()
|
|
6
7
|
.int()
|
|
@@ -9,7 +10,8 @@ const page = z
|
|
|
9
10
|
.describe('Result page number (defaults to 1)');
|
|
10
11
|
export function registerGeoTools(server) {
|
|
11
12
|
server.registerTool('setlist_search_cities', {
|
|
12
|
-
description: "Search setlist.fm for cities by name and/or location. Returns cities with their geoId — use it as cityId in setlist_search_setlists / setlist_search_venues, or with setlist_get_city."
|
|
13
|
+
description: "Search setlist.fm for cities by name and/or location. Returns cities with their geoId — use it as cityId in setlist_search_setlists / setlist_search_venues, or with setlist_get_city." +
|
|
14
|
+
ATTRIBUTION_NOTE,
|
|
13
15
|
annotations: { readOnlyHint: true },
|
|
14
16
|
inputSchema: {
|
|
15
17
|
name: z.string().optional().describe('City name'),
|
|
@@ -23,7 +25,7 @@ export function registerGeoTools(server) {
|
|
|
23
25
|
return textResult(data);
|
|
24
26
|
});
|
|
25
27
|
server.registerTool('setlist_get_city', {
|
|
26
|
-
description: "Get a city by its geoId.",
|
|
28
|
+
description: "Get a city by its geoId." + ATTRIBUTION_NOTE,
|
|
27
29
|
annotations: { readOnlyHint: true },
|
|
28
30
|
inputSchema: {
|
|
29
31
|
geoId: z.string().describe("City's geoId"),
|
|
@@ -33,7 +35,8 @@ export function registerGeoTools(server) {
|
|
|
33
35
|
return textResult(data);
|
|
34
36
|
});
|
|
35
37
|
server.registerTool('setlist_search_countries', {
|
|
36
|
-
description: "List all countries supported by setlist.fm, with their ISO country codes. Use a code as countryCode in setlist_search_setlists."
|
|
38
|
+
description: "List all countries supported by setlist.fm, with their ISO country codes. Use a code as countryCode in setlist_search_setlists." +
|
|
39
|
+
ATTRIBUTION_NOTE,
|
|
37
40
|
annotations: { readOnlyHint: true },
|
|
38
41
|
}, async () => {
|
|
39
42
|
const data = await client.request('GET', '/1.0/search/countries');
|
package/dist/tools/setlists.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { textResult } from '@chrischall/mcp-utils';
|
|
2
|
+
import { textResult, isoToDmy, isoToCompactTimestamp } from '@chrischall/mcp-utils';
|
|
3
3
|
import { client } from '../client.js';
|
|
4
|
+
import { ATTRIBUTION_NOTE } from '../attribution.js';
|
|
5
|
+
// How to read a setlist's song data (per setlist.fm's guidelines), so the model
|
|
6
|
+
// renders shows correctly. Appended to the get_setlist descriptions.
|
|
7
|
+
const SETLIST_SHAPE_NOTE = ' A setlist\'s songs live in `sets.set[]`; each set may have an `encore` number (1 = first encore) and a `name` (e.g. an acoustic set or a full album). Each `song` may carry: `tape: true` (pre-recorded intro/outro/interlude — not actually performed), `cover` (the original artist when it is a cover), `with` (a guest performer), and `info` (a note like "acoustic" or "first time live").';
|
|
4
8
|
const page = z
|
|
5
9
|
.number()
|
|
6
10
|
.int()
|
|
@@ -9,7 +13,8 @@ const page = z
|
|
|
9
13
|
.describe('Result page number (defaults to 1)');
|
|
10
14
|
export function registerSetlistTools(server) {
|
|
11
15
|
server.registerTool('setlist_search_setlists', {
|
|
12
|
-
description: "Search setlist.fm for concert setlists. Filter by any combination of artist, venue, city, country, tour, date, or year. Returns setlists with their songs and event details. Provide at least one filter."
|
|
16
|
+
description: "Search setlist.fm for concert setlists. Filter by any combination of artist, venue, city, country, tour, date, or year. Returns setlists with their songs and event details. Provide at least one filter." +
|
|
17
|
+
ATTRIBUTION_NOTE,
|
|
13
18
|
annotations: { readOnlyHint: true },
|
|
14
19
|
inputSchema: {
|
|
15
20
|
artistName: z.string().optional().describe('Artist name'),
|
|
@@ -22,20 +27,30 @@ export function registerSetlistTools(server) {
|
|
|
22
27
|
stateCode: z.string().optional().describe('State code'),
|
|
23
28
|
countryCode: z.string().optional().describe('Country code (ISO 3166-1 alpha-2)'),
|
|
24
29
|
tourName: z.string().optional().describe('Tour name'),
|
|
25
|
-
date: z
|
|
30
|
+
date: z
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('Event date, ISO yyyy-MM-dd (e.g. 2025-08-28)'),
|
|
26
34
|
year: z.number().int().optional().describe('Event year'),
|
|
27
35
|
lastUpdated: z
|
|
28
36
|
.string()
|
|
29
37
|
.optional()
|
|
30
|
-
.describe('Only setlists updated after this UTC time
|
|
38
|
+
.describe('Only setlists updated on/after this UTC time, ISO yyyy-MM-dd or yyyy-MM-ddTHH:mm:ss'),
|
|
31
39
|
p: page,
|
|
32
40
|
},
|
|
33
41
|
}, async (args) => {
|
|
34
|
-
const
|
|
42
|
+
const query = { ...args };
|
|
43
|
+
if (args.date)
|
|
44
|
+
query.date = isoToDmy(args.date);
|
|
45
|
+
if (args.lastUpdated)
|
|
46
|
+
query.lastUpdated = isoToCompactTimestamp(args.lastUpdated);
|
|
47
|
+
const data = await client.request('GET', '/1.0/search/setlists', { query });
|
|
35
48
|
return textResult(data);
|
|
36
49
|
});
|
|
37
50
|
server.registerTool('setlist_get_setlist', {
|
|
38
|
-
description: "Get a setlist.fm setlist by its ID, including the full song list and event details."
|
|
51
|
+
description: "Get a setlist.fm setlist by its ID, including the full song list and event details." +
|
|
52
|
+
SETLIST_SHAPE_NOTE +
|
|
53
|
+
ATTRIBUTION_NOTE,
|
|
39
54
|
annotations: { readOnlyHint: true },
|
|
40
55
|
inputSchema: {
|
|
41
56
|
setlistId: z.string().describe('Setlist ID (e.g. 63de4613)'),
|
|
@@ -45,7 +60,9 @@ export function registerSetlistTools(server) {
|
|
|
45
60
|
return textResult(data);
|
|
46
61
|
});
|
|
47
62
|
server.registerTool('setlist_get_setlist_version', {
|
|
48
|
-
description: "Get a specific historical version of a setlist by its version ID. Setlists are wiki-edited; each edit has a version ID returned in a setlist's `versionId` field."
|
|
63
|
+
description: "Get a specific historical version of a setlist by its version ID. Setlists are wiki-edited; each edit has a version ID returned in a setlist's `versionId` field." +
|
|
64
|
+
SETLIST_SHAPE_NOTE +
|
|
65
|
+
ATTRIBUTION_NOTE,
|
|
49
66
|
annotations: { readOnlyHint: true },
|
|
50
67
|
inputSchema: {
|
|
51
68
|
versionId: z.string().describe('Setlist version ID'),
|
package/dist/tools/users.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { textResult } from '@chrischall/mcp-utils';
|
|
3
3
|
import { client } from '../client.js';
|
|
4
|
+
import { ATTRIBUTION_NOTE } from '../attribution.js';
|
|
4
5
|
const page = z
|
|
5
6
|
.number()
|
|
6
7
|
.int()
|
|
@@ -9,7 +10,8 @@ const page = z
|
|
|
9
10
|
.describe('Result page number (defaults to 1)');
|
|
10
11
|
export function registerUserTools(server) {
|
|
11
12
|
server.registerTool('setlist_get_user', {
|
|
12
|
-
description: "Get a setlist.fm user's public profile by their userId (their setlist.fm username)."
|
|
13
|
+
description: "Get a setlist.fm user's public profile by their userId (their setlist.fm username)." +
|
|
14
|
+
ATTRIBUTION_NOTE,
|
|
13
15
|
annotations: { readOnlyHint: true },
|
|
14
16
|
inputSchema: {
|
|
15
17
|
userId: z.string().describe('setlist.fm userId (username)'),
|
|
@@ -19,7 +21,8 @@ export function registerUserTools(server) {
|
|
|
19
21
|
return textResult(data);
|
|
20
22
|
});
|
|
21
23
|
server.registerTool('setlist_get_user_attended', {
|
|
22
|
-
description: "Get the concerts a setlist.fm user has marked as attended. Paginated via `p`."
|
|
24
|
+
description: "Get the concerts a setlist.fm user has marked as attended. Paginated via `p`." +
|
|
25
|
+
ATTRIBUTION_NOTE,
|
|
23
26
|
annotations: { readOnlyHint: true },
|
|
24
27
|
inputSchema: {
|
|
25
28
|
userId: z.string().describe('setlist.fm userId (username)'),
|
|
@@ -30,7 +33,8 @@ export function registerUserTools(server) {
|
|
|
30
33
|
return textResult(data);
|
|
31
34
|
});
|
|
32
35
|
server.registerTool('setlist_get_user_edited', {
|
|
33
|
-
description: "Get the setlists a setlist.fm user has created or edited. Paginated via `p`."
|
|
36
|
+
description: "Get the setlists a setlist.fm user has created or edited. Paginated via `p`." +
|
|
37
|
+
ATTRIBUTION_NOTE,
|
|
34
38
|
annotations: { readOnlyHint: true },
|
|
35
39
|
inputSchema: {
|
|
36
40
|
userId: z.string().describe('setlist.fm userId (username)'),
|
package/dist/tools/venues.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { textResult } from '@chrischall/mcp-utils';
|
|
3
3
|
import { client } from '../client.js';
|
|
4
|
+
import { ATTRIBUTION_NOTE } from '../attribution.js';
|
|
4
5
|
const page = z
|
|
5
6
|
.number()
|
|
6
7
|
.int()
|
|
@@ -9,7 +10,8 @@ const page = z
|
|
|
9
10
|
.describe('Result page number (defaults to 1)');
|
|
10
11
|
export function registerVenueTools(server) {
|
|
11
12
|
server.registerTool('setlist_search_venues', {
|
|
12
|
-
description: "Search setlist.fm for venues by name and/or location. Returns matching venues with their venue ID — use it with setlist_get_venue or setlist_get_venue_setlists."
|
|
13
|
+
description: "Search setlist.fm for venues by name and/or location. Returns matching venues with their venue ID — use it with setlist_get_venue or setlist_get_venue_setlists." +
|
|
14
|
+
ATTRIBUTION_NOTE,
|
|
13
15
|
annotations: { readOnlyHint: true },
|
|
14
16
|
inputSchema: {
|
|
15
17
|
name: z.string().optional().describe('Venue name'),
|
|
@@ -25,7 +27,7 @@ export function registerVenueTools(server) {
|
|
|
25
27
|
return textResult(data);
|
|
26
28
|
});
|
|
27
29
|
server.registerTool('setlist_get_venue', {
|
|
28
|
-
description: "Get a setlist.fm venue by its ID.",
|
|
30
|
+
description: "Get a setlist.fm venue by its ID." + ATTRIBUTION_NOTE,
|
|
29
31
|
annotations: { readOnlyHint: true },
|
|
30
32
|
inputSchema: {
|
|
31
33
|
venueId: z.string().describe('Venue ID'),
|
|
@@ -35,7 +37,8 @@ export function registerVenueTools(server) {
|
|
|
35
37
|
return textResult(data);
|
|
36
38
|
});
|
|
37
39
|
server.registerTool('setlist_get_venue_setlists', {
|
|
38
|
-
description: "Get setlists performed at a venue, by venue ID (most recent first). Paginated via `p`."
|
|
40
|
+
description: "Get setlists performed at a venue, by venue ID (most recent first). Paginated via `p`." +
|
|
41
|
+
ATTRIBUTION_NOTE,
|
|
39
42
|
annotations: { readOnlyHint: true },
|
|
40
43
|
inputSchema: {
|
|
41
44
|
venueId: z.string().describe('Venue ID'),
|
package/dist/version.js
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
// release-please-config.json's `extra-files`), and `versionSyncTest` guards
|
|
4
4
|
// that it stays equal to package.json. Import VERSION wherever the version is
|
|
5
5
|
// needed rather than re-declaring it.
|
|
6
|
-
export const VERSION = '0.
|
|
6
|
+
export const VERSION = '0.4.0'; // x-release-please-version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "setlist-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"mcpName": "io.github.chrischall/setlist-mcp",
|
|
5
5
|
"description": "setlist.fm MCP server for Claude — developed and maintained by AI (Claude Code)",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"test:coverage": "vitest run --coverage"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@chrischall/mcp-utils": "^0.
|
|
45
|
+
"@chrischall/mcp-utils": "^0.6.0",
|
|
46
46
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
47
47
|
"dotenv": "^17.4.0",
|
|
48
48
|
"zod": "^4.4.2"
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/chrischall/setlist-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.4.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "setlist-mcp",
|
|
14
|
-
"version": "0.
|
|
14
|
+
"version": "0.4.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|