@darrenjaws/spotify-mcp 0.1.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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +606 -0
  3. package/build/bin.d.ts +7 -0
  4. package/build/bin.d.ts.map +1 -0
  5. package/build/bin.js +22 -0
  6. package/build/bin.js.map +1 -0
  7. package/build/handlers/call-tool.d.ts +6 -0
  8. package/build/handlers/call-tool.d.ts.map +1 -0
  9. package/build/handlers/call-tool.js +46 -0
  10. package/build/handlers/call-tool.js.map +1 -0
  11. package/build/handlers/tools.d.ts +296 -0
  12. package/build/handlers/tools.d.ts.map +1 -0
  13. package/build/handlers/tools.js +246 -0
  14. package/build/handlers/tools.js.map +1 -0
  15. package/build/index.d.ts +2 -0
  16. package/build/index.d.ts.map +1 -0
  17. package/build/index.js +147 -0
  18. package/build/index.js.map +1 -0
  19. package/build/setup.d.ts +7 -0
  20. package/build/setup.d.ts.map +1 -0
  21. package/build/setup.js +290 -0
  22. package/build/setup.js.map +1 -0
  23. package/build/spotify/auth.d.ts +43 -0
  24. package/build/spotify/auth.d.ts.map +1 -0
  25. package/build/spotify/auth.js +263 -0
  26. package/build/spotify/auth.js.map +1 -0
  27. package/build/spotify/client.d.ts +14 -0
  28. package/build/spotify/client.d.ts.map +1 -0
  29. package/build/spotify/client.js +40 -0
  30. package/build/spotify/client.js.map +1 -0
  31. package/build/tools/playback.d.ts +17 -0
  32. package/build/tools/playback.d.ts.map +1 -0
  33. package/build/tools/playback.js +143 -0
  34. package/build/tools/playback.js.map +1 -0
  35. package/build/tools/playlists.d.ts +11 -0
  36. package/build/tools/playlists.d.ts.map +1 -0
  37. package/build/tools/playlists.js +93 -0
  38. package/build/tools/playlists.js.map +1 -0
  39. package/build/tools/search.d.ts +6 -0
  40. package/build/tools/search.d.ts.map +1 -0
  41. package/build/tools/search.js +62 -0
  42. package/build/tools/search.js.map +1 -0
  43. package/build/tools/user.d.ts +8 -0
  44. package/build/tools/user.d.ts.map +1 -0
  45. package/build/tools/user.js +103 -0
  46. package/build/tools/user.js.map +1 -0
  47. package/build/types.d.ts +56 -0
  48. package/build/types.d.ts.map +1 -0
  49. package/build/types.js +5 -0
  50. package/build/types.js.map +1 -0
  51. package/build/utils/logger.d.ts +12 -0
  52. package/build/utils/logger.d.ts.map +1 -0
  53. package/build/utils/logger.js +86 -0
  54. package/build/utils/logger.js.map +1 -0
  55. package/package.json +70 -0
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Search tools
3
+ */
4
+ import { getAuthenticatedClient } from "../spotify/client.js";
5
+ export async function search(args) {
6
+ const client = await getAuthenticatedClient();
7
+ const limit = args.limit || 10;
8
+ const result = await client.search(args.query, [args.type], { limit });
9
+ let items = [];
10
+ let itemType = "";
11
+ switch (args.type) {
12
+ case "track":
13
+ items = result.body.tracks?.items || [];
14
+ itemType = "tracks";
15
+ break;
16
+ case "album":
17
+ items = result.body.albums?.items || [];
18
+ itemType = "albums";
19
+ break;
20
+ case "artist":
21
+ items = result.body.artists?.items || [];
22
+ itemType = "artists";
23
+ break;
24
+ case "playlist":
25
+ items = result.body.playlists?.items || [];
26
+ itemType = "playlists";
27
+ break;
28
+ }
29
+ if (items.length === 0) {
30
+ return {
31
+ content: [
32
+ {
33
+ type: "text",
34
+ text: `No ${itemType} found for query: "${args.query}"`,
35
+ },
36
+ ],
37
+ };
38
+ }
39
+ const formatted = items.map((item, index) => {
40
+ switch (args.type) {
41
+ case "track":
42
+ return `${index + 1}. ${item.name} - ${item.artists.map((a) => a.name).join(", ")} (${item.uri})`;
43
+ case "album":
44
+ return `${index + 1}. ${item.name} - ${item.artists.map((a) => a.name).join(", ")} (${item.uri})`;
45
+ case "artist":
46
+ return `${index + 1}. ${item.name} (${item.uri})`;
47
+ case "playlist":
48
+ return `${index + 1}. ${item.name} - ${item.owner.display_name} (${item.tracks.total} tracks) (${item.uri})`;
49
+ default:
50
+ return `${index + 1}. ${item.name}`;
51
+ }
52
+ });
53
+ return {
54
+ content: [
55
+ {
56
+ type: "text",
57
+ text: `Found ${items.length} ${itemType} for "${args.query}":\n\n${formatted.join("\n")}`,
58
+ },
59
+ ],
60
+ };
61
+ }
62
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * User data tools
3
+ */
4
+ import type { ToolResponse, TopItemsArgs, RecentlyPlayedArgs } from "../types.js";
5
+ export declare function getUserProfile(): Promise<ToolResponse>;
6
+ export declare function getTopItems(args: TopItemsArgs): Promise<ToolResponse>;
7
+ export declare function getRecentlyPlayed(args: RecentlyPlayedArgs): Promise<ToolResponse>;
8
+ //# sourceMappingURL=user.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/tools/user.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAElF,wBAAsB,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC,CAsB5D;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAoD3E;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAgCvF"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * User data tools
3
+ */
4
+ import { getAuthenticatedClient } from "../spotify/client.js";
5
+ export async function getUserProfile() {
6
+ const client = await getAuthenticatedClient();
7
+ const result = await client.getMe();
8
+ const user = result.body;
9
+ const text = `User Profile:
10
+ Name: ${user.display_name || "N/A"}
11
+ Email: ${user.email || "N/A"}
12
+ Country: ${user.country || "N/A"}
13
+ Product: ${user.product || "N/A"}
14
+ Followers: ${user.followers?.total || 0}
15
+ User ID: ${user.id}`;
16
+ return {
17
+ content: [
18
+ {
19
+ type: "text",
20
+ text,
21
+ },
22
+ ],
23
+ };
24
+ }
25
+ export async function getTopItems(args) {
26
+ const client = await getAuthenticatedClient();
27
+ const limit = args.limit || 20;
28
+ const timeRange = args.time_range || "medium_term";
29
+ let result;
30
+ let items = [];
31
+ let itemType = "";
32
+ if (args.type === "artists") {
33
+ result = await client.getMyTopArtists({ limit, time_range: timeRange });
34
+ items = result.body.items;
35
+ itemType = "artists";
36
+ }
37
+ else {
38
+ result = await client.getMyTopTracks({ limit, time_range: timeRange });
39
+ items = result.body.items;
40
+ itemType = "tracks";
41
+ }
42
+ if (items.length === 0) {
43
+ return {
44
+ content: [
45
+ {
46
+ type: "text",
47
+ text: `No top ${itemType} found`,
48
+ },
49
+ ],
50
+ };
51
+ }
52
+ const timeRangeText = {
53
+ short_term: "last 4 weeks",
54
+ medium_term: "last 6 months",
55
+ long_term: "all time",
56
+ }[timeRange];
57
+ const formatted = items.map((item, index) => {
58
+ if (args.type === "artists") {
59
+ return `${index + 1}. ${item.name} (${item.genres.slice(0, 2).join(", ")})`;
60
+ }
61
+ else {
62
+ return `${index + 1}. ${item.name} - ${item.artists.map((a) => a.name).join(", ")}`;
63
+ }
64
+ });
65
+ return {
66
+ content: [
67
+ {
68
+ type: "text",
69
+ text: `Your top ${itemType} (${timeRangeText}):\n\n${formatted.join("\n")}`,
70
+ },
71
+ ],
72
+ };
73
+ }
74
+ export async function getRecentlyPlayed(args) {
75
+ const client = await getAuthenticatedClient();
76
+ const limit = args.limit || 20;
77
+ const result = await client.getMyRecentlyPlayedTracks({ limit });
78
+ const items = result.body.items;
79
+ if (items.length === 0) {
80
+ return {
81
+ content: [
82
+ {
83
+ type: "text",
84
+ text: "No recently played tracks found",
85
+ },
86
+ ],
87
+ };
88
+ }
89
+ const formatted = items.map((item, index) => {
90
+ const track = item.track;
91
+ const playedAt = new Date(item.played_at).toLocaleString();
92
+ return `${index + 1}. ${track.name} - ${track.artists.map((a) => a.name).join(", ")} (${playedAt})`;
93
+ });
94
+ return {
95
+ content: [
96
+ {
97
+ type: "text",
98
+ text: `Recently played tracks:\n\n${formatted.join("\n")}`,
99
+ },
100
+ ],
101
+ };
102
+ }
103
+ //# sourceMappingURL=user.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user.js","sourceRoot":"","sources":["../../src/tools/user.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAG9D,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,MAAM,GAAG,MAAM,sBAAsB,EAAE,CAAC;IAE9C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACpC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IAEzB,MAAM,IAAI,GAAG;QACP,IAAI,CAAC,YAAY,IAAI,KAAK;SACzB,IAAI,CAAC,KAAK,IAAI,KAAK;WACjB,IAAI,CAAC,OAAO,IAAI,KAAK;WACrB,IAAI,CAAC,OAAO,IAAI,KAAK;aACnB,IAAI,CAAC,SAAS,EAAE,KAAK,IAAI,CAAC;WAC5B,IAAI,CAAC,EAAE,EAAE,CAAC;IAEnB,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI;aACL;SACF;KACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAkB;IAClD,MAAM,MAAM,GAAG,MAAM,sBAAsB,EAAE,CAAC;IAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;IAC/B,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,IAAI,aAAa,CAAC;IAEnD,IAAI,MAAM,CAAC;IACX,IAAI,KAAK,GAAU,EAAE,CAAC;IACtB,IAAI,QAAQ,GAAG,EAAE,CAAC;IAElB,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC;QACxE,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;QAC1B,QAAQ,GAAG,SAAS,CAAC;IACvB,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC;QACvE,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;QAC1B,QAAQ,GAAG,QAAQ,CAAC;IACtB,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,UAAU,QAAQ,QAAQ;iBACjC;aACF;SACF,CAAC;IACJ,CAAC;IAED,MAAM,aAAa,GAAG;QACpB,UAAU,EAAE,cAAc;QAC1B,WAAW,EAAE,eAAe;QAC5B,SAAS,EAAE,UAAU;KACtB,CAAC,SAAS,CAAC,CAAC;IAEb,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC1C,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC5B,OAAO,GAAG,KAAK,GAAG,CAAC,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAC9E,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,KAAK,GAAG,CAAC,KAAK,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3F,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,YAAY,QAAQ,KAAK,aAAa,SAAS,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;aAC5E;SACF;KACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAwB;IAC9D,MAAM,MAAM,GAAG,MAAM,sBAAsB,EAAE,CAAC;IAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;IAE/B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,yBAAyB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACjE,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;IAEhC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,iCAAiC;iBACxC;aACF;SACF,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,cAAc,EAAE,CAAC;QAC3D,OAAO,GAAG,KAAK,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,QAAQ,GAAG,CAAC;IACtG,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,8BAA8B,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;aAC3D;SACF;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Type definitions for Spotify MCP server
3
+ */
4
+ export interface SpotifyCredentials {
5
+ clientId: string;
6
+ clientSecret: string;
7
+ redirectUri: string;
8
+ }
9
+ export interface StoredTokens {
10
+ accessToken: string;
11
+ refreshToken: string;
12
+ expiresAt: number;
13
+ }
14
+ export interface ToolResponse {
15
+ content: Array<{
16
+ type: "text";
17
+ text: string;
18
+ }>;
19
+ isError?: boolean;
20
+ }
21
+ export interface PlaybackArgs {
22
+ uri?: string;
23
+ uris?: string[];
24
+ device_id?: string;
25
+ }
26
+ export interface SearchArgs {
27
+ query: string;
28
+ type: "track" | "album" | "artist" | "playlist";
29
+ limit?: number;
30
+ }
31
+ export interface CreatePlaylistArgs {
32
+ name: string;
33
+ description?: string;
34
+ public?: boolean;
35
+ }
36
+ export interface AddToPlaylistArgs {
37
+ playlist_id: string;
38
+ uris: string[];
39
+ position?: number;
40
+ }
41
+ export interface GetPlaylistArgs {
42
+ playlist_id: string;
43
+ }
44
+ export interface VolumeArgs {
45
+ volume_percent: number;
46
+ device_id?: string;
47
+ }
48
+ export interface TopItemsArgs {
49
+ type: "artists" | "tracks";
50
+ time_range?: "short_term" | "medium_term" | "long_term";
51
+ limit?: number;
52
+ }
53
+ export interface RecentlyPlayedArgs {
54
+ limit?: number;
55
+ }
56
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC3B,UAAU,CAAC,EAAE,YAAY,GAAG,aAAa,GAAG,WAAW,CAAC;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
package/build/types.js ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Type definitions for Spotify MCP server
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Simple logging utility
3
+ * Logs to stderr to avoid interfering with stdio transport
4
+ * Automatically redacts sensitive data (tokens, secrets, passwords)
5
+ */
6
+ export declare const logger: {
7
+ debug(message: string, ...args: any[]): void;
8
+ info(message: string, ...args: any[]): void;
9
+ warn(message: string, ...args: any[]): void;
10
+ error(message: string, ...args: any[]): void;
11
+ };
12
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAuEH,eAAO,MAAM,MAAM;mBACF,MAAM,WAAW,GAAG,EAAE;kBAMvB,MAAM,WAAW,GAAG,EAAE;kBAMtB,MAAM,WAAW,GAAG,EAAE;mBAMrB,MAAM,WAAW,GAAG,EAAE;CAKtC,CAAC"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Simple logging utility
3
+ * Logs to stderr to avoid interfering with stdio transport
4
+ * Automatically redacts sensitive data (tokens, secrets, passwords)
5
+ */
6
+ const LOG_LEVEL = process.env.LOG_LEVEL || "info";
7
+ const levels = {
8
+ debug: 0,
9
+ info: 1,
10
+ warn: 2,
11
+ error: 3,
12
+ };
13
+ // Sensitive field names that should be redacted
14
+ const SENSITIVE_FIELDS = [
15
+ 'token', 'accessToken', 'refreshToken', 'access_token', 'refresh_token',
16
+ 'secret', 'clientSecret', 'client_secret', 'password', 'apiKey', 'api_key',
17
+ 'authorization', 'auth', 'bearer'
18
+ ];
19
+ /**
20
+ * Recursively redact sensitive fields from objects
21
+ */
22
+ function redactSensitiveData(obj) {
23
+ if (obj === null || obj === undefined) {
24
+ return obj;
25
+ }
26
+ if (typeof obj === 'string') {
27
+ // Redact if string looks like a token (long alphanumeric strings)
28
+ if (obj.length > 20 && /^[A-Za-z0-9_-]+$/.test(obj)) {
29
+ return '[REDACTED]';
30
+ }
31
+ return obj;
32
+ }
33
+ if (Array.isArray(obj)) {
34
+ return obj.map(item => redactSensitiveData(item));
35
+ }
36
+ if (typeof obj === 'object') {
37
+ const redacted = {};
38
+ for (const [key, value] of Object.entries(obj)) {
39
+ if (SENSITIVE_FIELDS.some(field => key.toLowerCase().includes(field.toLowerCase()))) {
40
+ redacted[key] = '[REDACTED]';
41
+ }
42
+ else {
43
+ redacted[key] = redactSensitiveData(value);
44
+ }
45
+ }
46
+ return redacted;
47
+ }
48
+ return obj;
49
+ }
50
+ function shouldLog(level) {
51
+ return levels[level] >= levels[LOG_LEVEL];
52
+ }
53
+ function formatMessage(level, message, ...args) {
54
+ const timestamp = new Date().toISOString();
55
+ const formattedArgs = args.length > 0 ? " " + args.map(arg => {
56
+ if (typeof arg === "object") {
57
+ const redacted = redactSensitiveData(arg);
58
+ return JSON.stringify(redacted);
59
+ }
60
+ return String(arg);
61
+ }).join(" ") : "";
62
+ return `[${timestamp}] [${level.toUpperCase()}] ${message}${formattedArgs}`;
63
+ }
64
+ export const logger = {
65
+ debug(message, ...args) {
66
+ if (shouldLog("debug")) {
67
+ console.error(formatMessage("debug", message, ...args));
68
+ }
69
+ },
70
+ info(message, ...args) {
71
+ if (shouldLog("info")) {
72
+ console.error(formatMessage("info", message, ...args));
73
+ }
74
+ },
75
+ warn(message, ...args) {
76
+ if (shouldLog("warn")) {
77
+ console.error(formatMessage("warn", message, ...args));
78
+ }
79
+ },
80
+ error(message, ...args) {
81
+ if (shouldLog("error")) {
82
+ console.error(formatMessage("error", message, ...args));
83
+ }
84
+ },
85
+ };
86
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@darrenjaws/spotify-mcp",
3
+ "version": "0.1.0",
4
+ "description": "Model Context Protocol server for Spotify integration",
5
+ "type": "module",
6
+ "bin": {
7
+ "spotify-mcp": "build/bin.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc && chmod +x build/bin.js",
11
+ "dev": "tsc --watch",
12
+ "clean": "rm -rf build",
13
+ "prepare": "npm run build",
14
+ "prepublishOnly": "npm run build && npm test",
15
+ "test": "vitest run",
16
+ "test:watch": "vitest",
17
+ "test:coverage": "vitest run --coverage",
18
+ "lint": "eslint src --ext .ts",
19
+ "lint:fix": "eslint src --ext .ts --fix"
20
+ },
21
+ "keywords": [
22
+ "mcp",
23
+ "model-context-protocol",
24
+ "spotify",
25
+ "music",
26
+ "ai",
27
+ "claude"
28
+ ],
29
+ "author": "Darren Jaworski <darrenjaworski@gmail.com>",
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/darrenjaworski/spotify-mcp.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/darrenjaworski/spotify-mcp/issues"
37
+ },
38
+ "homepage": "https://github.com/darrenjaworski/spotify-mcp#readme",
39
+ "files": [
40
+ "build/**/*.js",
41
+ "build/**/*.d.ts",
42
+ "build/**/*.js.map",
43
+ "build/**/*.d.ts.map",
44
+ "!build/**/*.test.js",
45
+ "!build/**/*.test.d.ts",
46
+ "!build/**/*.test.js.map",
47
+ "!build/**/*.test.d.ts.map",
48
+ "README.md",
49
+ "LICENSE"
50
+ ],
51
+ "dependencies": {
52
+ "@modelcontextprotocol/sdk": "^1.26.0",
53
+ "dotenv": "^16.4.7",
54
+ "spotify-web-api-node": "^5.0.2",
55
+ "zod": "^3.24.1"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "^22.10.5",
59
+ "@types/spotify-web-api-node": "^5.0.11",
60
+ "@typescript-eslint/eslint-plugin": "^8.19.1",
61
+ "@typescript-eslint/parser": "^8.19.1",
62
+ "@vitest/coverage-v8": "^4.0.18",
63
+ "eslint": "^9.17.0",
64
+ "typescript": "^5.7.2",
65
+ "vitest": "^4.0.18"
66
+ },
67
+ "engines": {
68
+ "node": ">=18.0.0"
69
+ }
70
+ }