Skip to content

Commit ec824f1

Browse files
authored
feat: add collection write tools (folders, ratings, custom fields) (#9)
* feat: add collection write tools (folders, ratings, custom fields) Add 11 new MCP tools for modifying Discogs collections: Folder management: - list_folders, create_folder, edit_folder, delete_folder Collection items: - add_to_collection, remove_from_collection, move_release Ratings: - rate_release Custom fields: - list_custom_fields, edit_custom_field All write operations invalidate the user's collection cache to ensure subsequent reads reflect the changes. Custom field edits skip cache invalidation since they don't affect collection structure. Client layer (discogs.ts): 9 new API methods wrapping Discogs v2 endpoints with OAuth 1.0a auth, throttling, and retry. Cached layer (cachedDiscogs.ts): Pass-through wrappers that call invalidateUserCache() after mutations. Tools layer (authenticated.ts): 11 new server.tool() registrations following existing patterns (auth guard, Zod validation, consistent response format). * chore: update package-lock.json * test: add tests for collection write tools and fix tool count assertion Add unit tests for new DiscogsClient write methods (folders, ratings, custom fields) and CachedDiscogsClient cache invalidation behavior. Fix hardcoded tool count in integration test (8 → 18). * feat: add create_custom_field tool Adds createCustomField() to DiscogsClient and CachedDiscogsClient (with cache invalidation), registers the create_custom_field MCP tool, and adds full test coverage — success, body shape, cache invalidation, and 429 rate limit tests. Bumps wrangler to ^4.76.0. * Revert "feat: add create_custom_field tool" This reverts commit c893031.
1 parent bda8748 commit ec824f1

7 files changed

Lines changed: 1579 additions & 7 deletions

File tree

package-lock.json

Lines changed: 17 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/clients/cachedDiscogs.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
type DiscogsCollectionStats,
1717
type DiscogsSearchResponse,
1818
type DiscogsCollectionItem,
19+
type DiscogsFolder,
20+
type DiscogsCustomField,
1921
} from './discogs'
2022
import { SmartCache, CacheKeys, createDiscogsCache } from '../utils/cache'
2123

@@ -429,6 +431,128 @@ export class CachedDiscogsClient {
429431
return stats
430432
}
431433

434+
// ──────────────────────────────────────────────
435+
// Collection write operations (pass-through with cache invalidation)
436+
// ──────────────────────────────────────────────
437+
438+
async listFolders(
439+
username: string,
440+
accessToken: string,
441+
accessTokenSecret: string,
442+
consumerKey: string,
443+
consumerSecret: string,
444+
): Promise<DiscogsFolder[]> {
445+
return this.client.listFolders(username, accessToken, accessTokenSecret, consumerKey, consumerSecret)
446+
}
447+
448+
async createFolder(
449+
username: string,
450+
name: string,
451+
accessToken: string,
452+
accessTokenSecret: string,
453+
consumerKey: string,
454+
consumerSecret: string,
455+
): Promise<DiscogsFolder> {
456+
const result = await this.client.createFolder(username, name, accessToken, accessTokenSecret, consumerKey, consumerSecret)
457+
await this.invalidateUserCache(username)
458+
return result
459+
}
460+
461+
async editFolder(
462+
username: string,
463+
folderId: number,
464+
name: string,
465+
accessToken: string,
466+
accessTokenSecret: string,
467+
consumerKey: string,
468+
consumerSecret: string,
469+
): Promise<DiscogsFolder> {
470+
const result = await this.client.editFolder(username, folderId, name, accessToken, accessTokenSecret, consumerKey, consumerSecret)
471+
await this.invalidateUserCache(username)
472+
return result
473+
}
474+
475+
async deleteFolder(
476+
username: string,
477+
folderId: number,
478+
accessToken: string,
479+
accessTokenSecret: string,
480+
consumerKey: string,
481+
consumerSecret: string,
482+
): Promise<void> {
483+
await this.client.deleteFolder(username, folderId, accessToken, accessTokenSecret, consumerKey, consumerSecret)
484+
await this.invalidateUserCache(username)
485+
}
486+
487+
async addToFolder(
488+
username: string,
489+
folderId: number,
490+
releaseId: number,
491+
accessToken: string,
492+
accessTokenSecret: string,
493+
consumerKey: string,
494+
consumerSecret: string,
495+
): Promise<{ instance_id: number; resource_url: string }> {
496+
const result = await this.client.addToFolder(username, folderId, releaseId, accessToken, accessTokenSecret, consumerKey, consumerSecret)
497+
await this.invalidateUserCache(username)
498+
return result
499+
}
500+
501+
async removeFromFolder(
502+
username: string,
503+
folderId: number,
504+
releaseId: number,
505+
instanceId: number,
506+
accessToken: string,
507+
accessTokenSecret: string,
508+
consumerKey: string,
509+
consumerSecret: string,
510+
): Promise<void> {
511+
await this.client.removeFromFolder(username, folderId, releaseId, instanceId, accessToken, accessTokenSecret, consumerKey, consumerSecret)
512+
await this.invalidateUserCache(username)
513+
}
514+
515+
async editInstance(
516+
username: string,
517+
folderId: number,
518+
releaseId: number,
519+
instanceId: number,
520+
changes: { folder_id?: number; rating?: number },
521+
accessToken: string,
522+
accessTokenSecret: string,
523+
consumerKey: string,
524+
consumerSecret: string,
525+
): Promise<void> {
526+
await this.client.editInstance(username, folderId, releaseId, instanceId, changes, accessToken, accessTokenSecret, consumerKey, consumerSecret)
527+
await this.invalidateUserCache(username)
528+
}
529+
530+
async listCustomFields(
531+
username: string,
532+
accessToken: string,
533+
accessTokenSecret: string,
534+
consumerKey: string,
535+
consumerSecret: string,
536+
): Promise<DiscogsCustomField[]> {
537+
return this.client.listCustomFields(username, accessToken, accessTokenSecret, consumerKey, consumerSecret)
538+
}
539+
540+
async editCustomFieldValue(
541+
username: string,
542+
folderId: number,
543+
releaseId: number,
544+
instanceId: number,
545+
fieldId: number,
546+
value: string,
547+
accessToken: string,
548+
accessTokenSecret: string,
549+
consumerKey: string,
550+
consumerSecret: string,
551+
): Promise<void> {
552+
await this.client.editCustomFieldValue(username, folderId, releaseId, instanceId, fieldId, value, accessToken, accessTokenSecret, consumerKey, consumerSecret)
553+
// No cache invalidation needed — custom fields don't affect collection structure
554+
}
555+
432556
/**
433557
* Cleanup old cache entries
434558
*/

0 commit comments

Comments
 (0)