discogs-mcp-server
Version:
MCP server for Discogs
1,623 lines (1,609 loc) • 106 kB
JavaScript
#!/usr/bin/env node
import { FastMCP, imageContent, UserError } from 'fastmcp';
import dotenv from 'dotenv';
import { z } from 'zod';
// src/version.ts
var VERSION = "0.4.2";
// src/config.ts
dotenv.config();
var config = {
discogs: {
apiUrl: process.env.DISCOGS_API_URL || "https://api.discogs.com",
/* Some MCP clients can't handle large amounts of data.
* The client may explicitly request more at their own peril. */
defaultPerPage: 5,
mediaType: process.env.DISCOGS_MEDIA_TYPE || "application/vnd.discogs.v2.discogs+json",
personalAccessToken: process.env.DISCOGS_PERSONAL_ACCESS_TOKEN,
userAgent: process.env.DISCOGS_USER_AGENT || `DiscogsMCPServer/${VERSION}`
},
server: {
name: process.env.SERVER_NAME || "Discogs MCP Server",
port: process.env.PORT ? parseInt(process.env.PORT, 10) : 3001
}
};
function validateConfig() {
const missingVars = [];
if (!process.env.DISCOGS_PERSONAL_ACCESS_TOKEN) {
missingVars.push("DISCOGS_PERSONAL_ACCESS_TOKEN");
}
if (missingVars.length > 0) {
throw new Error(`Missing required environment variables: ${missingVars.join(", ")}`);
}
}
var DiscogsError = class extends Error {
constructor(message, status, response) {
super(message);
this.status = status;
this.response = response;
this.name = new.target.name;
}
};
var DiscogsAuthenticationError = class extends DiscogsError {
constructor(message = "Authentication failed") {
super(message, 401, { message });
this.name = new.target.name;
}
};
var DiscogsMethodNotAllowedError = class extends DiscogsError {
constructor(message = "Method not allowed") {
super(message, 405, { message });
this.name = new.target.name;
}
};
var DiscogsPermissionError = class extends DiscogsError {
constructor(message = "Insufficient permissions") {
super(message, 403, { message });
this.name = new.target.name;
}
};
var DiscogsRateLimitError = class extends DiscogsError {
constructor(message = "Rate limit exceeded", resetAt) {
super(message, 429, { message, reset_at: resetAt.toISOString() });
this.resetAt = resetAt;
this.name = new.target.name;
}
};
var DiscogsResourceNotFoundError = class extends DiscogsError {
constructor(message = "Resource not found") {
super(message, 404, { message });
this.name = new.target.name;
}
};
var DiscogsValidationFailedError = class extends DiscogsError {
constructor(response) {
let message = "Validation failed";
if (response && typeof response === "object" && response !== null) {
const detail = response.detail;
if (Array.isArray(detail) && detail.length > 0 && detail[0].msg) {
message = detail[0].msg;
}
}
super(message, 422, { message });
this.name = new.target.name;
}
};
function createDiscogsError(status, response) {
switch (status) {
case 401:
return new DiscogsAuthenticationError(response?.message);
case 403:
return new DiscogsPermissionError(response?.message);
case 404:
return new DiscogsResourceNotFoundError(response?.message || "Resource");
case 405:
return new DiscogsMethodNotAllowedError(response?.message);
case 422:
return new DiscogsValidationFailedError(response);
case 429:
return new DiscogsRateLimitError(
response?.message,
new Date(response?.reset_at || Date.now() + 6e4)
);
default:
return new DiscogsError(response?.message || "Discogs API error", status, response);
}
}
function formatDiscogsError(error) {
let message;
if (error instanceof Error) {
message = error.message;
} else {
message = String(error);
}
return new UserError(message);
}
function isDiscogsError(error) {
return error instanceof DiscogsError;
}
var log = {
_log: (level, ...args) => {
const msg = `[${level} ${(/* @__PURE__ */ new Date()).toISOString()}] ${args.join(" ")}
`;
process.stderr.write(msg);
},
info: (...args) => {
log._log("INFO", ...args);
},
debug: (...args) => {
log._log("DEBUG", ...args);
},
warn: (...args) => {
log._log("WARN", ...args);
},
error: (...args) => {
log._log("ERROR", ...args);
}
};
var urlOrEmptySchema = () => {
return z.string().refine((val) => val === "" || /^https?:\/\/.+/.test(val), {
message: "Must be a valid URL or empty string"
});
};
var CurrencyCodeSchema = z.enum([
"USD",
// US Dollar
"GBP",
// British Pound
"EUR",
// Euro
"CAD",
// Canadian Dollar
"AUD",
// Australian Dollar
"JPY",
// Japanese Yen
"CHF",
// Swiss Franc
"MXN",
// Mexican Peso
"BRL",
// Brazilian Real
"NZD",
// New Zealand Dollar
"SEK",
// Swedish Krona
"ZAR"
// South African Rand
]);
var ImageSchema = z.object({
width: z.number().int().optional(),
height: z.number().int().optional(),
resource_url: urlOrEmptySchema(),
type: z.string().optional(),
uri: urlOrEmptySchema(),
uri150: urlOrEmptySchema().optional()
});
var FilteredResponseSchema = z.object({
filters: z.object({
applied: z.record(z.string(), z.array(z.any())).default({}),
available: z.record(z.string(), z.record(z.string(), z.number().int())).default({})
}),
filter_facets: z.array(
z.object({
title: z.string(),
id: z.string(),
values: z.array(
z.object({
title: z.string(),
value: z.string(),
count: z.number().int()
})
),
allows_multiple_values: z.boolean()
})
)
});
var PaginationSchema = z.object({
page: z.number().int().min(0).optional(),
per_page: z.number().int().min(0).optional(),
pages: z.number().int().min(0),
items: z.number().int().min(0),
urls: z.object({
first: z.string().url().optional(),
prev: z.string().url().optional(),
next: z.string().url().optional(),
last: z.string().url().optional()
}).optional()
});
var PaginatedResponseSchema = (itemSchema, resultsFieldName) => z.object({
pagination: PaginationSchema,
[resultsFieldName]: z.array(itemSchema)
});
var PaginatedResponseWithObjectSchema = (itemSchema, resultsFieldName) => z.object({
pagination: PaginationSchema,
[resultsFieldName]: itemSchema
});
var QueryParamsSchema = (validSortKeys = []) => z.object({
// Pagination
page: z.number().int().min(1).optional(),
per_page: z.number().int().min(1).max(100).optional(),
// Sorting
sort: z.enum(validSortKeys).optional(),
sort_order: z.enum(["asc", "desc"]).optional()
});
var StatusSchema = z.enum(["Accepted", "Draft", "Deleted", "Rejected"]);
var UsernameInputSchema = z.object({
username: z.string().min(1, "username is required")
});
// src/types/artist.ts
var ArtistIdParamSchema = z.object({
artist_id: z.number()
});
var ArtistBasicSchema = z.object({
id: z.number(),
anv: z.string(),
join: z.string(),
name: z.string(),
resource_url: urlOrEmptySchema(),
role: z.string(),
tracks: z.string()
});
var ArtistSchema = z.object({
id: z.number(),
aliases: z.array(
z.object({
id: z.number(),
name: z.string(),
resource_url: urlOrEmptySchema(),
thumbnail_url: urlOrEmptySchema().optional()
})
).optional(),
data_quality: z.string().optional(),
images: z.array(ImageSchema).optional(),
members: z.array(
z.object({
id: z.number(),
active: z.boolean().optional(),
name: z.string(),
resource_url: urlOrEmptySchema(),
thumbnail_url: urlOrEmptySchema().optional()
})
).optional(),
name: z.string(),
namevariations: z.array(z.string()).optional(),
profile: z.string().optional(),
realname: z.string().optional(),
releases_url: urlOrEmptySchema().optional(),
resource_url: urlOrEmptySchema(),
uri: urlOrEmptySchema().optional(),
urls: z.array(z.string()).optional()
});
var ArtistReleaseSchema = z.object({
id: z.number(),
artist: z.string(),
catno: z.string().optional(),
format: z.string().optional(),
label: z.string().optional(),
main_release: z.number().optional(),
resource_url: urlOrEmptySchema(),
role: z.string().optional(),
status: z.string().optional(),
stats: z.object({
community: z.object({
in_collection: z.number(),
in_wantlist: z.number()
}),
user: z.object({
in_collection: z.number(),
in_wantlist: z.number()
})
}).optional(),
thumb: urlOrEmptySchema().optional(),
title: z.string(),
trackinfo: z.string().optional(),
type: z.string().optional(),
year: z.number().optional()
});
var ArtistReleasesParamsSchema = ArtistIdParamSchema.merge(
QueryParamsSchema(["year", "title", "format"])
);
var ArtistReleasesSchema = PaginatedResponseSchema(ArtistReleaseSchema, "releases");
// src/services/index.ts
var DiscogsService = class {
constructor(servicePath) {
this.servicePath = servicePath;
if (!config.discogs.personalAccessToken || !config.discogs.userAgent) {
throw new Error("Discogs API configuration is incomplete");
}
this.baseUrl = `${config.discogs.apiUrl}${servicePath}`;
this.headers = {
Accept: config.discogs.mediaType,
Authorization: `Discogs token=${config.discogs.personalAccessToken}`,
"Content-Type": "application/json",
"User-Agent": config.discogs.userAgent
};
}
baseUrl;
headers;
async request(path, options) {
const url = new URL(`${this.baseUrl}${path}`);
if (options?.params) {
Object.entries(options.params).forEach(([key, value]) => {
if (value !== void 0) {
url.searchParams.append(key, String(value));
}
});
}
if (!url.searchParams.has("per_page")) {
url.searchParams.append("per_page", String(config.discogs.defaultPerPage));
}
const response = await fetch(url.toString(), {
method: options?.method || "GET",
headers: this.headers,
body: options?.body ? JSON.stringify(options.body) : void 0
});
const contentType = response.headers.get("content-type");
const isJson = contentType && contentType.includes("application/json");
let responseBody;
try {
responseBody = isJson ? await response.json() : await response.text();
} catch {
responseBody = { message: "Failed to parse response" };
}
if (!response.ok) {
throw createDiscogsError(response.status, responseBody);
}
return responseBody;
}
};
var BaseUserService = class extends DiscogsService {
constructor() {
super("/users");
}
};
// src/services/artist.ts
var ArtistService = class extends DiscogsService {
constructor() {
super("/artists");
}
/**
* Get an artist
*
* @param params - Parameters containing the artist ID
* @returns {Artist} The artist information
* @throws {DiscogsResourceNotFoundError} If the artist cannot be found
* @throws {Error} If there's an unexpected error
*/
async get({ artist_id }) {
try {
const response = await this.request(`/${artist_id}`);
const validatedResponse = ArtistSchema.parse(response);
return validatedResponse;
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to get artist: ${String(error)}`);
}
}
/**
* Get an artist's releases
*
* @param params - Parameters containing the artist ID and pagination options
* @returns {ArtistReleases} The artist releases
* @throws {DiscogsResourceNotFoundError} If the artist cannot be found
* @throws {Error} If there's an unexpected error
*/
async getReleases({ artist_id, ...options }) {
try {
const response = await this.request(`/${artist_id}/releases`, {
params: options
});
const validatedResponse = ArtistReleasesSchema.parse(response);
return validatedResponse;
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to get artist releases: ${String(error)}`);
}
}
};
var SearchParamsSchema = z.object({
q: z.string().optional(),
type: z.enum(["artist", "label", "master", "release"]).optional(),
title: z.string().optional(),
release_title: z.string().optional(),
credit: z.string().optional(),
artist: z.string().optional(),
anv: z.string().optional(),
label: z.string().optional(),
genre: z.string().optional(),
style: z.string().optional(),
country: z.string().optional(),
year: z.string().optional(),
format: z.string().optional(),
catno: z.string().optional(),
barcode: z.string().optional(),
track: z.string().optional(),
submitter: z.string().optional(),
contributor: z.string().optional()
}).merge(QueryParamsSchema(["title", "artist", "year"]));
var SearchResultSchema = z.object({
id: z.number(),
barcode: z.array(z.string()).optional(),
catno: z.string().optional(),
community: z.object({
have: z.number(),
want: z.number()
}).optional(),
country: z.string().optional(),
cover_image: urlOrEmptySchema().optional(),
format: z.array(z.string()).optional(),
format_quantity: z.number().optional(),
formats: z.array(
z.object({
descriptions: z.array(z.string()).optional(),
name: z.string(),
qty: z.string(),
text: z.string().optional()
})
).optional(),
genre: z.array(z.string()).optional(),
label: z.array(z.string()).optional(),
master_id: z.number().nullable().optional(),
master_url: urlOrEmptySchema().nullable().optional(),
resource_url: urlOrEmptySchema(),
style: z.array(z.string()).optional(),
thumb: urlOrEmptySchema().optional(),
title: z.string(),
type: z.enum(["artist", "label", "master", "release"]),
uri: z.string(),
user_data: z.object({
in_collection: z.boolean(),
in_wantlist: z.boolean()
}).optional(),
year: z.string().optional()
});
var SearchResultsSchema = PaginatedResponseSchema(SearchResultSchema, "results");
// src/services/database.ts
var DatabaseService = class extends DiscogsService {
constructor() {
super("/database");
}
/**
* Issue a search query to the Discogs database
*
* @param params - Search parameters
* @throws {DiscogsAuthenticationError} If authentication fails
* @throws {Error} If the search times out or an unexpected error occurs
* @returns {SearchResults} Search results
*/
async search(params) {
try {
const response = await this.request("/search", { params });
const validatedResponse = SearchResultsSchema.parse(response);
return validatedResponse;
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to search database: ${String(error)}`);
}
}
};
var LabelIdParamSchema = z.object({
label_id: z.number()
});
var LabelBasicSchema = z.object({
id: z.number(),
catno: z.string(),
entity_type: z.string().optional(),
entity_type_name: z.string().optional(),
name: z.string(),
resource_url: urlOrEmptySchema()
});
var LabelSchema = z.object({
id: z.number(),
contact_info: z.string().optional(),
data_quality: z.string().optional(),
images: z.array(ImageSchema).optional(),
name: z.string(),
parent_label: z.object({
id: z.number(),
name: z.string(),
resource_url: urlOrEmptySchema()
}).optional(),
profile: z.string().optional(),
releases_url: urlOrEmptySchema().optional(),
resource_url: urlOrEmptySchema(),
sublabels: z.array(
z.object({
id: z.number(),
name: z.string(),
resource_url: urlOrEmptySchema()
})
).optional(),
uri: urlOrEmptySchema().optional(),
urls: z.array(z.string()).optional()
});
var LabelReleasesParamsSchema = LabelIdParamSchema.merge(QueryParamsSchema());
var LabelReleasesSchema = PaginatedResponseSchema(ArtistReleaseSchema, "releases");
// src/services/label.ts
var LabelService = class extends DiscogsService {
constructor() {
super("/labels");
}
/**
* Get a label
*
* @param params - Parameters containing the label ID
* @returns {Label} The label information
* @throws {DiscogsResourceNotFoundError} If the label cannot be found
* @throws {Error} If there's an unexpected error
*/
async get({ label_id }) {
try {
const response = await this.request(`/${label_id}`);
const validatedResponse = LabelSchema.parse(response);
return validatedResponse;
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to get label: ${String(error)}`);
}
}
/**
* Returns a list of Releases associated with the label
*
* @param params - Parameters containing the label ID and pagination options
* @returns {LabelReleases} The label releases
* @throws {DiscogsResourceNotFoundError} If the label cannot be found
* @throws {Error} If there's an unexpected error
*/
async getReleases({ label_id, ...params }) {
try {
const response = await this.request(`/${label_id}/releases`, {
params
});
const validatedResponse = LabelReleasesSchema.parse(response);
return validatedResponse;
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to get label releases: ${String(error)}`);
}
}
};
var ReleaseFormatSchema = z.object({
descriptions: z.array(z.string()).optional(),
name: z.string(),
qty: z.string(),
text: z.string().optional()
});
var BasicInformationSchema = z.object({
id: z.number(),
artists: z.array(ArtistBasicSchema),
cover_image: urlOrEmptySchema(),
formats: z.array(ReleaseFormatSchema),
genres: z.array(z.string()).optional(),
master_id: z.number().optional(),
master_url: urlOrEmptySchema().nullable().optional(),
labels: z.array(LabelBasicSchema),
resource_url: urlOrEmptySchema(),
styles: z.array(z.string()).optional(),
thumb: urlOrEmptySchema(),
title: z.string(),
year: z.number()
});
var ReleaseSchema = z.object({
id: z.number().int(),
artists_sort: z.string().optional(),
artists: z.array(ArtistBasicSchema),
blocked_from_sale: z.boolean().optional(),
companies: z.array(
z.object({
id: z.number().int().optional(),
catno: z.string().optional(),
entity_type: z.string().optional(),
entity_type_name: z.string().optional(),
name: z.string().optional(),
resource_url: urlOrEmptySchema().optional(),
thumbnail_url: urlOrEmptySchema().optional()
})
).optional(),
community: z.object({
contributors: z.array(
z.object({
resource_url: urlOrEmptySchema().optional(),
username: z.string().optional()
})
).optional(),
data_quality: z.string().optional(),
have: z.number().int().optional(),
rating: z.object({
average: z.number().optional(),
count: z.number().int().optional()
}).optional(),
status: z.string().optional(),
submitter: z.object({
resource_url: urlOrEmptySchema().optional(),
username: z.string().optional()
}).optional(),
want: z.number().int().optional()
}).optional(),
country: z.string().optional(),
data_quality: z.string().optional(),
date_added: z.string().optional(),
date_changed: z.string().optional(),
estimated_weight: z.number().int().optional(),
extraartists: z.array(ArtistBasicSchema).optional(),
format_quantity: z.number().int().optional(),
formats: z.array(ReleaseFormatSchema).optional(),
genres: z.array(z.string()).optional(),
identifiers: z.array(
z.object({
type: z.string(),
value: z.string(),
description: z.string().optional()
})
).optional(),
images: z.array(ImageSchema).optional(),
labels: z.array(LabelBasicSchema).optional(),
lowest_price: z.number().nullable().optional(),
master_id: z.number().optional(),
master_url: urlOrEmptySchema().optional(),
notes: z.string().optional(),
num_for_sale: z.number().int().optional(),
released: z.string().optional(),
released_formatted: z.string().optional(),
resource_url: urlOrEmptySchema(),
series: z.array(z.unknown()).optional(),
status: z.string().optional(),
styles: z.array(z.string()).optional(),
thumb: urlOrEmptySchema().optional(),
title: z.string(),
tracklist: z.array(
z.object({
duration: z.string().optional(),
position: z.string(),
title: z.string(),
type_: z.string().optional(),
extraartists: z.array(ArtistBasicSchema).optional()
})
).optional(),
uri: urlOrEmptySchema().optional(),
videos: z.array(
z.object({
description: z.string().nullable().optional(),
duration: z.number().int().optional(),
embed: z.boolean().optional(),
title: z.string().optional(),
uri: urlOrEmptySchema().optional()
})
).optional(),
year: z.number()
});
var ReleaseIdParamSchema = z.object({
release_id: z.number().min(1, "The release_id must be non-zero")
});
var ReleaseParamsSchema = ReleaseIdParamSchema.extend({
curr_abbr: CurrencyCodeSchema.optional()
});
var ReleaseRatingSchema = UsernameInputSchema.merge(ReleaseIdParamSchema).extend({
rating: z.number()
});
var ReleaseRatingCommunitySchema = ReleaseIdParamSchema.extend({
rating: z.object({
average: z.number(),
count: z.number().int()
})
});
var ReleaseRatingParamsSchema = UsernameInputSchema.merge(ReleaseIdParamSchema);
var ReleaseRatingEditParamsSchema = ReleaseRatingParamsSchema.extend({
rating: z.number().int().min(1, "The rating must be at least 1").max(5, "The rating must be at most 5")
});
// src/types/master.ts
var MasterReleaseIdParamSchema = z.object({
master_id: z.number().int()
});
var MasterReleaseVersionsParamSchema = MasterReleaseIdParamSchema.extend({
format: z.string().optional(),
label: z.string().optional(),
released: z.string().optional(),
country: z.string().optional()
}).merge(QueryParamsSchema(["released", "title", "format", "label", "catno", "country"]));
var MasterReleaseSchema = ReleaseSchema.extend({
main_release: z.number(),
most_recent_release: z.number(),
versions_url: urlOrEmptySchema(),
main_release_url: urlOrEmptySchema(),
most_recent_release_url: urlOrEmptySchema(),
tracklist: z.array(
z.object({
position: z.string(),
type_: z.string().optional(),
title: z.string(),
duration: z.string().optional(),
extraartists: z.array(ArtistBasicSchema).optional()
})
).optional(),
artists: z.array(
ArtistBasicSchema.extend({
thumbnail_url: urlOrEmptySchema().optional()
})
)
});
var MasterReleaseVersionItemSchema = z.object({
id: z.number().int(),
label: z.string(),
country: z.string(),
title: z.string(),
major_formats: z.array(z.string()),
format: z.string(),
catno: z.string(),
released: z.string(),
status: StatusSchema,
resource_url: urlOrEmptySchema(),
thumb: urlOrEmptySchema().optional(),
stats: z.object({
community: z.object({
in_wantlist: z.number().int(),
in_collection: z.number().int()
}).optional(),
user: z.object({
in_wantlist: z.number().int(),
in_collection: z.number().int()
}).optional()
})
});
var MasterReleaseVersionsResponseSchema = z.object({
...PaginatedResponseSchema(MasterReleaseVersionItemSchema, "versions").shape,
...FilteredResponseSchema.shape
});
// src/services/master.ts
var MasterReleaseService = class extends DiscogsService {
constructor() {
super("/masters");
}
/**
* Get a master release
*
* @param params - Parameters containing the master release ID
* @returns {MasterRelease} The master release information
* @throws {DiscogsResourceNotFoundError} If the master release cannot be found
* @throws {Error} If there's an unexpected error
*/
async get({ master_id }) {
try {
const response = await this.request(`/${master_id}`);
const validatedResponse = MasterReleaseSchema.parse(response);
return validatedResponse;
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to get master release: ${String(error)}`);
}
}
/**
* Retrieves a list of all Releases that are versions of this master
*
* @param params - Parameters containing the master release ID and optional query parameters
* @returns {MasterReleaseVersionsResponse} The master release versions information
* @throws {DiscogsResourceNotFoundError} If the master release versions cannot be found
* @throws {Error} If there's an unexpected error
*/
async getVersions({
master_id,
...options
}) {
try {
const response = await this.request(`/${master_id}/versions`, {
params: options
});
const validatedResponse = MasterReleaseVersionsResponseSchema.parse(response);
return validatedResponse;
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to get master release versions: ${String(error)}`);
}
}
};
// src/services/release.ts
var ReleaseService = class extends DiscogsService {
constructor() {
super("/releases");
}
/**
* Deletes the release's rating for a given user
*
* @param params - Parameters for the request
* @throws {DiscogsAuthenticationError} If authentication fails
* @throws {DiscogsPermissionError} If trying to delete a release rating of another user
* @throws {DiscogsResourceNotFoundError} If the release or user cannot be found
* @throws {Error} If there's an unexpected error
*/
async deleteRatingByUser({ username, release_id }) {
try {
await this.request(`/${release_id}/rating/${username}`, {
method: "DELETE"
});
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to delete release rating: ${String(error)}`);
}
}
/**
* Updates the release's rating for a given user
*
* @param params - Parameters for the request
* @returns {ReleaseRating} The updated release rating
* @throws {DiscogsAuthenticationError} If authentication fails
* @throws {DiscogsPermissionError} If trying to edit a release rating of another user
* @throws {DiscogsResourceNotFoundError} If the release or user cannot be found
* @throws {Error} If there's an unexpected error
*/
async editRatingByUser({
username,
release_id,
rating
}) {
try {
const response = await this.request(`/${release_id}/rating/${username}`, {
method: "PUT",
body: { rating }
});
const validatedResponse = ReleaseRatingSchema.parse(response);
return validatedResponse;
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to edit release rating: ${String(error)}`);
}
}
/**
* Get a release
*
* @param params - Parameters for the request
* @returns {Release} The release information
* @throws {DiscogsResourceNotFoundError} If the release cannot be found
* @throws {Error} If there's an unexpected error
*/
async get({ release_id, ...options }) {
try {
const response = await this.request(`/${release_id}`, {
params: options
});
const validatedResponse = ReleaseSchema.parse(response);
return validatedResponse;
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to get release: ${String(error)}`);
}
}
/**
* Retrieves the community release rating average and count
*
* @param params - Parameters for the request
* @returns {ReleaseRatingCommunity} The release community rating
* @throws {DiscogsResourceNotFoundError} If the release cannot be found
* @throws {Error} If there's an unexpected error
*/
async getCommunityRating({ release_id }) {
try {
const response = await this.request(`/${release_id}/rating`);
const validatedResponse = ReleaseRatingCommunitySchema.parse(response);
return validatedResponse;
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to get release community rating: ${String(error)}`);
}
}
/**
* Retrieves the release's rating for a given user
*
* @param params - Parameters for the request
* @returns {ReleaseRating} The release rating
* @throws {DiscogsResourceNotFoundError} If the release or user cannot be found
* @throws {Error} If there's an unexpected error
*/
async getRatingByUser({ username, release_id }) {
try {
const response = await this.request(`/${release_id}/rating/${username}`);
const validatedResponse = ReleaseRatingSchema.parse(response);
return validatedResponse;
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to get release rating: ${String(error)}`);
}
}
};
// src/tools/database.ts
var deleteReleaseRatingTool = {
name: "delete_release_rating",
description: `Deletes the release's rating for a given user`,
parameters: ReleaseRatingParamsSchema,
execute: async (args) => {
try {
const releaseService = new ReleaseService();
await releaseService.deleteRatingByUser(args);
return "Release rating deleted successfully";
} catch (error) {
throw formatDiscogsError(error);
}
}
};
var editReleaseRatingTool = {
name: "edit_release_rating",
description: `Updates the release's rating for a given user`,
parameters: ReleaseRatingEditParamsSchema,
execute: async (args) => {
try {
const releaseService = new ReleaseService();
const releaseRating = await releaseService.editRatingByUser(args);
return JSON.stringify(releaseRating);
} catch (error) {
throw formatDiscogsError(error);
}
}
};
var getArtistTool = {
name: "get_artist",
description: "Get an artist",
parameters: ArtistIdParamSchema,
execute: async (args) => {
try {
const artistService = new ArtistService();
const artist = await artistService.get(args);
return JSON.stringify(artist);
} catch (error) {
throw formatDiscogsError(error);
}
}
};
var getArtistReleasesTool = {
name: "get_artist_releases",
description: `Get an artist's releases`,
parameters: ArtistReleasesParamsSchema,
execute: async (args) => {
try {
const artistService = new ArtistService();
const artistReleases = await artistService.getReleases(args);
return JSON.stringify(artistReleases);
} catch (error) {
throw formatDiscogsError(error);
}
}
};
var getLabelTool = {
name: "get_label",
description: "Get a label",
parameters: LabelIdParamSchema,
execute: async (args) => {
try {
const labelService = new LabelService();
const label = await labelService.get(args);
return JSON.stringify(label);
} catch (error) {
throw formatDiscogsError(error);
}
}
};
var getLabelReleasesTool = {
name: "get_label_releases",
description: "Returns a list of Releases associated with the label",
parameters: LabelReleasesParamsSchema,
execute: async (args) => {
try {
const labelService = new LabelService();
const labelReleases = await labelService.getReleases(args);
return JSON.stringify(labelReleases);
} catch (error) {
throw formatDiscogsError(error);
}
}
};
var getMasterReleaseTool = {
name: "get_master_release",
description: "Get a master release",
parameters: MasterReleaseIdParamSchema,
execute: async (args) => {
try {
const masterReleaseService = new MasterReleaseService();
const masterRelease = await masterReleaseService.get(args);
return JSON.stringify(masterRelease);
} catch (error) {
throw formatDiscogsError(error);
}
}
};
var getMasterReleaseVersionsTool = {
name: "get_master_release_versions",
description: "Retrieves a list of all Releases that are versions of this master",
parameters: MasterReleaseVersionsParamSchema,
execute: async (args) => {
try {
const masterReleaseService = new MasterReleaseService();
const masterReleaseVersions = await masterReleaseService.getVersions(args);
return JSON.stringify(masterReleaseVersions);
} catch (error) {
throw formatDiscogsError(error);
}
}
};
var getReleaseTool = {
name: "get_release",
description: "Get a release",
parameters: ReleaseParamsSchema,
execute: async (args) => {
try {
const releaseService = new ReleaseService();
const release = await releaseService.get(args);
return JSON.stringify(release);
} catch (error) {
throw formatDiscogsError(error);
}
}
};
var getReleaseCommunityRatingTool = {
name: "get_release_community_rating",
description: "Retrieves the release community rating average and count",
parameters: ReleaseIdParamSchema,
execute: async (args) => {
try {
const releaseService = new ReleaseService();
const releaseRating = await releaseService.getCommunityRating(args);
return JSON.stringify(releaseRating);
} catch (error) {
throw formatDiscogsError(error);
}
}
};
var getReleaseRatingTool = {
name: "get_release_rating_by_user",
description: `Retrieves the release's rating for a given user`,
parameters: ReleaseRatingParamsSchema,
execute: async (args) => {
try {
const releaseService = new ReleaseService();
const releaseRating = await releaseService.getRatingByUser(args);
return JSON.stringify(releaseRating);
} catch (error) {
throw formatDiscogsError(error);
}
}
};
var searchTool = {
name: "search",
description: "Issue a search query to the Discogs database",
parameters: SearchParamsSchema,
execute: async (args) => {
try {
const databaseService = new DatabaseService();
const searchResults = await databaseService.search(args);
return JSON.stringify(searchResults);
} catch (error) {
throw formatDiscogsError(error);
}
}
};
function registerDatabaseTools(server) {
server.addTool(getReleaseTool);
server.addTool(getReleaseRatingTool);
server.addTool(editReleaseRatingTool);
server.addTool(deleteReleaseRatingTool);
server.addTool(getReleaseCommunityRatingTool);
server.addTool(getMasterReleaseTool);
server.addTool(getMasterReleaseVersionsTool);
server.addTool(getArtistTool);
server.addTool(getArtistReleasesTool);
server.addTool(getLabelTool);
server.addTool(getLabelReleasesTool);
server.addTool(searchTool);
}
var InventoryIdParamSchema = z.object({
id: z.number()
});
var InventoryExportItemSchema = z.object({
status: z.string(),
created_ts: z.string().nullable(),
url: urlOrEmptySchema(),
finished_ts: z.string().nullable(),
download_url: urlOrEmptySchema(),
filename: z.string(),
id: z.number()
});
var InventoryExportsResponseSchema = PaginatedResponseSchema(
InventoryExportItemSchema,
"items"
);
// src/services/inventory.ts
var InventoryService = class extends DiscogsService {
constructor() {
super("/inventory");
}
/**
* Download an inventory export as a CSV
*
* @param {InventoryIdParam} params - The parameters for the request
* @returns {string} The inventory export as a CSV
* @throws {DiscogsAuthenticationError} If the request is not authenticated
* @throws {DiscogsResourceNotFoundError} If the inventory export does not exist
* @throws {Error} If there's an unexpected error
*/
async downloadExport({ id }) {
try {
const response = await this.request(`/export/${id}/download`);
return response;
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to download inventory export: ${String(error)}`);
}
}
/**
* Request an export of your inventory as a CSV
*
* @returns {void}
* @throws {DiscogsAuthenticationError} If the request is not authenticated
* @throws {Error} If there's an unexpected error
*/
async export() {
try {
await this.request("/export", {
method: "POST"
});
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to export inventory: ${String(error)}`);
}
}
/**
* Get details about an inventory export
*
* @param {InventoryIdParam} params - The parameters for the request
* @returns {InventoryExportItem} The inventory export item
* @throws {DiscogsAuthenticationError} If the request is not authenticated
* @throws {DiscogsResourceNotFoundError} If the inventory export does not exist
* @throws {Error} If there's an unexpected error
*/
async getExport({ id }) {
try {
const response = await this.request(`/export/${id}`);
const validatedResponse = InventoryExportItemSchema.parse(response);
return validatedResponse;
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to get inventory export: ${String(error)}`);
}
}
/**
* Get a list of all recent exports of your inventory
*
* @returns {InventoryExportsResponse} The inventory exports
* @throws {DiscogsAuthenticationError} If the request is not authenticated
* @throws {Error} If there's an unexpected error
*/
async getExports() {
try {
const response = await this.request("/export");
const validatedResponse = InventoryExportsResponseSchema.parse(response);
return validatedResponse;
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to get inventory exports: ${String(error)}`);
}
}
};
// src/tools/inventoryExport.ts
var downloadInventoryExportTool = {
name: "download_inventory_export",
description: "Download an inventory export as a CSV",
parameters: InventoryIdParamSchema,
execute: async (args) => {
try {
const inventoryService = new InventoryService();
const csv = await inventoryService.downloadExport(args);
return csv;
} catch (error) {
throw formatDiscogsError(error);
}
}
};
var getInventoryExportTool = {
name: "get_inventory_export",
description: "Get details about an inventory export",
parameters: InventoryIdParamSchema,
execute: async (args) => {
try {
const inventoryService = new InventoryService();
const exportItem = await inventoryService.getExport(args);
return JSON.stringify(exportItem);
} catch (error) {
throw formatDiscogsError(error);
}
}
};
var getInventoryExportsTool = {
name: "get_inventory_exports",
description: "Get a list of all recent exports of your inventory",
parameters: z.object({}),
execute: async () => {
try {
const inventoryService = new InventoryService();
const exports = await inventoryService.getExports();
return JSON.stringify(exports);
} catch (error) {
throw formatDiscogsError(error);
}
}
};
var inventoryExportTool = {
name: "inventory_export",
description: "Request an export of your inventory as a CSV",
parameters: z.object({}),
execute: async () => {
try {
const inventoryService = new InventoryService();
await inventoryService.export();
return "Inventory export requested";
} catch (error) {
throw formatDiscogsError(error);
}
}
};
function registerInventoryExportTool(server) {
server.addTool(inventoryExportTool);
server.addTool(getInventoryExportsTool);
server.addTool(getInventoryExportTool);
server.addTool(downloadInventoryExportTool);
}
var ConditionSchema = z.enum([
"Mint (M)",
"Near Mint (NM or M-)",
"Very Good Plus (VG+)",
"Very Good (VG)",
"Good Plus (G+)",
"Good (G)",
"Fair (F)",
"Poor (P)"
]);
var SleeveConditionSchema = z.enum([
...ConditionSchema.options,
"Generic",
"Not Graded",
"No Cover"
]);
var OrderStatusSchema = z.enum([
"New Order",
"Buyer Contacted",
"Invoice Sent",
"Payment Pending",
"Payment Received",
"Shipped",
"Refund Sent",
"Cancelled (Non-Paying Buyer)",
"Cancelled (Item Unavailable)",
`Cancelled (Per Buyer's Request)`
]);
var ListingReleaseSchema = z.object({
catalog_number: z.string().optional(),
resource_url: urlOrEmptySchema(),
year: z.number(),
id: z.number().int(),
description: z.string(),
images: z.array(ImageSchema).optional(),
artist: z.string(),
title: z.string(),
format: z.string(),
thumbnail: urlOrEmptySchema(),
stats: z.object({
community: z.object({
in_wantlist: z.number().int(),
in_collection: z.number().int()
}),
user: z.object({
in_wantlist: z.number().int(),
in_collection: z.number().int()
}).optional()
})
});
var PriceSchema = z.object({
currency: CurrencyCodeSchema.optional(),
value: z.number().optional()
});
var OriginalPriceSchema = z.object({
curr_abbr: CurrencyCodeSchema.optional(),
curr_id: z.number().optional(),
formatted: z.string().optional(),
value: z.number().optional()
});
var OrderMessageSchema = z.object({
timestamp: z.string().optional(),
message: z.string(),
type: z.string().optional(),
order: z.object({
id: z.number(),
resource_url: urlOrEmptySchema()
}),
subject: z.string().optional(),
refund: z.object({
amount: z.number(),
order: z.object({
id: z.number(),
resource_url: urlOrEmptySchema()
})
}).optional(),
from: z.object({
id: z.number().optional(),
resource_url: urlOrEmptySchema(),
username: z.string(),
avatar_url: urlOrEmptySchema().optional()
}).optional(),
status_id: z.number().optional(),
actor: z.object({
username: z.string(),
resource_url: urlOrEmptySchema()
}).optional(),
original: z.number().optional(),
new: z.number().optional()
});
var SaleStatusSchema = z.enum(["For Sale", "Expired", "Draft", "Pending"]);
var ListingSchema = z.object({
id: z.number(),
resource_url: z.string().url(),
uri: z.string().url(),
status: SaleStatusSchema,
condition: z.string(),
sleeve_condition: z.string(),
comments: z.string().optional(),
ships_from: z.string(),
posted: z.string(),
allow_offers: z.boolean(),
offer_submitted: z.boolean().optional(),
audio: z.boolean(),
price: PriceSchema,
original_price: OriginalPriceSchema,
shipping_price: PriceSchema.optional(),
original_shipping_price: OriginalPriceSchema.optional(),
seller: z.object({
id: z.number(),
username: z.string(),
resource_url: urlOrEmptySchema().optional(),
avatar_url: urlOrEmptySchema().optional(),
stats: z.object({
rating: z.string(),
stars: z.number(),
total: z.number()
}),
min_order_total: z.number(),
html_url: urlOrEmptySchema(),
uid: z.number(),
url: urlOrEmptySchema(),
payment: z.string(),
shipping: z.string()
}),
release: ListingReleaseSchema
});
var ListingIdParamSchema = z.object({
listing_id: z.number().int()
});
var ListingGetParamsSchema = ListingIdParamSchema.extend({
curr_abbr: CurrencyCodeSchema.optional()
});
var ListingNewParamsSchema = z.object({
release_id: z.number().int(),
condition: ConditionSchema,
sleeve_condition: SleeveConditionSchema.optional(),
price: z.number(),
comments: z.string().optional(),
allow_offers: z.boolean().optional(),
status: SaleStatusSchema,
external_id: z.string().optional(),
location: z.string().optional(),
weight: z.number().optional(),
format_quantity: z.number().optional()
});
var ListingNewResponseSchema = z.object({
listing_id: z.number().int(),
resource_url: z.string().url()
});
var ListingUpdateParamsSchema = ListingIdParamSchema.merge(ListingNewParamsSchema);
var OrderIdParamSchema = z.object({
order_id: z.number()
});
var OrderCreateMessageParamsSchema = OrderIdParamSchema.extend({
message: z.string().optional(),
status: OrderStatusSchema.optional()
});
var OrderEditParamsSchema = OrderIdParamSchema.extend({
status: OrderStatusSchema.optional(),
shipping: z.number().optional()
});
var OrderMessagesParamsSchema = QueryParamsSchema().merge(OrderIdParamSchema);
var OrderMessagesResponseSchema = PaginatedResponseSchema(OrderMessageSchema, "messages");
var OrdersParamsSchema = z.object({
status: OrderStatusSchema.optional(),
created_after: z.string().optional(),
created_before: z.string().optional(),
archived: z.boolean().optional()
}).merge(QueryParamsSchema(["id", "buyer", "created", "status", "last_activity"]));
var OrderResponseSchema = z.object({
id: z.number(),
resource_url: urlOrEmptySchema(),
messages_url: urlOrEmptySchema(),
uri: urlOrEmptySchema(),
status: OrderStatusSchema,
next_status: z.array(OrderStatusSchema),
fee: PriceSchema,
created: z.string(),
items: z.array(
z.object({
release: z.object({
id: z.number(),
description: z.string().optional()
}),
price: PriceSchema,
media_condition: ConditionSchema,
sleeve_condition: SleeveConditionSchema.optional(),
id: z.number()
})
),
shipping: z.object({
currency: CurrencyCodeSchema,
method: z.string(),
value: z.number()
}),
shipping_address: z.string(),
address_instructions: z.string().optional(),
archived: z.boolean().optional(),
seller: z.object({
id: z.number(),
username: z.string(),
resource_url: urlOrEmptySchema().optional()
}),
last_activity: z.string().optional(),
buyer: z.object({
id: z.number(),
username: z.string(),
resource_url: urlOrEmptySchema().optional()
}),
total: PriceSchema
});
var OrdersResponseSchema = PaginatedResponseSchema(OrderResponseSchema, "orders");
var ReleaseStatsResponseSchema = z.object({
lowest_price: PriceSchema.nullable().optional(),
num_for_sale: z.number().nullable().optional(),
blocked_from_sale: z.boolean()
});
// src/services/marketplace.ts
var MarketplaceService = class extends DiscogsService {
constructor() {
super("/marketplace");
}
/**
* Create a new marketplace listing
*
* @param params - Parameters containing the listing data
* @returns {ListingNewResponse} The listing information
* @throws {DiscogsAuthenticationError} If the user is not authenticated
* @throws {DiscogsPermissionError} If the user does not have permission to create a listing
* @throws {Error} If there's an unexpected error
*/
async createListing(params) {
try {
const response = await this.request(`/listings`, {
method: "POST",
body: params
});
const validatedResponse = ListingNewResponseSchema.parse(response);
return validatedResponse;
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to create listing: ${String(error)}`);
}
}
/**
* Adds a new message to the order's message log
*
* @param params - Parameters containing the order ID and the message data
* @returns {OrderMessageResponse} The order message information
* @throws {DiscogsAuthenticationError} If the user is not authenticated
* @throws {DiscogsPermissionError} If the user does not have permission to create a message
* @throws {DiscogsResourceNotFoundError} If the order cannot be found
* @throws {Error} If there's an unexpected error
*/
async createOrderMessage({
order_id,
...body
}) {
try {
const response = await this.request(`/orders/${order_id}/messages`, {
method: "POST",
body
});
const validatedResponse = OrderMessageSchema.parse(response);
return validatedResponse;
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to create order message: ${String(error)}`);
}
}
/**
* Delete a listing from the marketplace
*
* @param params - Parameters containing the listing ID
* @throws {DiscogsAuthenticationError} If the user is not authenticated
* @throws {DiscogsPermissionError} If the user does not have permission to delete a listing
* @throws {DiscogsResourceNotFoundError} If the listing cannot be found
* @throws {Error} If there's an unexpected error
*/
async deleteListing({ listing_id }) {
try {
await this.request(`/listings/${listing_id}`, {
method: "DELETE"
});
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to delete listing: ${String(error)}`);
}
}
/**
* The Listing resource allows you to view Marketplace listings
*
* @param params - Parameters containing the listing ID and optional currency code
* @returns {Listing} The listing information
* @throws {DiscogsResourceNotFoundError} If the listing cannot be found
* @throws {Error} If there's an unexpected error
*/
async getListing({ listing_id, ...options }) {
try {
const response = await this.request(`/listings/${listing_id}`, {
params: options
});
const validatedResponse = ListingSchema.parse(response);
return validatedResponse;
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
throw new Error(`Failed to get listing: ${String(error)}`);
}
}
/**
* Get a marketplace order
*
* @param params - Parameters containing the order ID
* @throws {DiscogsAuthenticationError} If the user is not authenticated
* @throws {DiscogsPermissionError} If the user does not have permission to view the order
* @throws {DiscogsResourceNotFoundError} If the order cannot be found
* @throws {Error} If there's an unexpected error
* @returns {OrderResponse} The order information
*/
async getOrder({ order_id }) {
try {
const response = await this.request(`/orders/${order_id}`);
const validatedResponse = OrderResponseSchema.parse(response);
return validatedResponse;
} catch (error) {
if (isDiscogsError(error)) {
throw error;
}
t