chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,60 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyAddPlaylistCoverParams {
accessToken: string
playlistId: string
imageBase64: string
}
interface SpotifyAddPlaylistCoverResponse extends ToolResponse {
output: { success: boolean }
}
export const spotifyAddPlaylistCoverTool: ToolConfig<
SpotifyAddPlaylistCoverParams,
SpotifyAddPlaylistCoverResponse
> = {
id: 'spotify_add_playlist_cover',
name: 'Spotify Add Playlist Cover',
description: 'Upload a custom cover image for a playlist. Image must be JPEG and under 256KB.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['playlist-modify-public', 'playlist-modify-private', 'ugc-image-upload'],
},
params: {
playlistId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify playlist ID',
},
imageBase64: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Base64-encoded JPEG image data (max 256KB)',
},
},
request: {
url: (params) => `https://api.spotify.com/v1/playlists/${params.playlistId}/images`,
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'image/jpeg',
}),
body: (params) => params.imageBase64,
},
transformResponse: async (): Promise<SpotifyAddPlaylistCoverResponse> => {
return { success: true, output: { success: true } }
},
outputs: {
success: { type: 'boolean', description: 'Whether upload succeeded' },
},
}
+59
View File
@@ -0,0 +1,59 @@
import type { SpotifyAddToQueueParams, SpotifyAddToQueueResponse } from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyAddToQueueTool: ToolConfig<SpotifyAddToQueueParams, SpotifyAddToQueueResponse> =
{
id: 'spotify_add_to_queue',
name: 'Spotify Add to Queue',
description: "Add a track to the user's playback queue.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-modify-playback-state'],
},
params: {
uri: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Spotify URI of the track to add (e.g., "spotify:track:xxx")',
},
device_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Device ID. If not provided, uses active device.',
},
},
request: {
url: (params) => {
let url = `https://api.spotify.com/v1/me/player/queue?uri=${encodeURIComponent(params.uri)}`
if (params.device_id) {
url += `&device_id=${params.device_id}`
}
return url
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (): Promise<SpotifyAddToQueueResponse> => {
return {
success: true,
output: {
success: true,
},
}
},
outputs: {
success: { type: 'boolean', description: 'Whether track was added to queue' },
},
}
@@ -0,0 +1,74 @@
import type {
SpotifyAddTracksToPlaylistParams,
SpotifyAddTracksToPlaylistResponse,
} from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyAddTracksToPlaylistTool: ToolConfig<
SpotifyAddTracksToPlaylistParams,
SpotifyAddTracksToPlaylistResponse
> = {
id: 'spotify_add_tracks_to_playlist',
name: 'Spotify Add Tracks to Playlist',
description: 'Add one or more tracks to a Spotify playlist.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['playlist-modify-public', 'playlist-modify-private'],
},
params: {
playlistId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify ID of the playlist',
},
uris: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated Spotify URIs (e.g., "spotify:track:xxx,spotify:track:yyy")',
},
position: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Position to insert tracks (0-based index). If omitted, tracks are appended.',
},
},
request: {
url: (params) => `https://api.spotify.com/v1/playlists/${params.playlistId}/tracks`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const uris = params.uris.split(',').map((uri) => uri.trim())
const body: any = { uris }
if (params.position !== undefined) {
body.position = params.position
}
return body
},
},
transformResponse: async (response): Promise<SpotifyAddTracksToPlaylistResponse> => {
const data = await response.json()
return {
success: true,
output: {
snapshot_id: data.snapshot_id,
},
}
},
outputs: {
snapshot_id: { type: 'string', description: 'New playlist snapshot ID after modification' },
},
}
+66
View File
@@ -0,0 +1,66 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyCheckFollowingParams {
accessToken: string
type: string
ids: string
}
interface SpotifyCheckFollowingResponse extends ToolResponse {
output: { results: boolean[] }
}
export const spotifyCheckFollowingTool: ToolConfig<
SpotifyCheckFollowingParams,
SpotifyCheckFollowingResponse
> = {
id: 'spotify_check_following',
name: 'Spotify Check Following',
description: 'Check if the user follows artists or users.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-follow-read'],
},
params: {
type: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Type to check: "artist" or "user"',
},
ids: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated artist or user IDs (max 50)',
},
},
request: {
url: (params) => {
const ids = params.ids
.split(',')
.map((id) => id.trim())
.slice(0, 50)
.join(',')
return `https://api.spotify.com/v1/me/following/contains?type=${params.type}&ids=${ids}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyCheckFollowingResponse> => {
const results = await response.json()
return { success: true, output: { results } }
},
outputs: {
results: { type: 'json', description: 'Array of booleans for each ID' },
},
}
@@ -0,0 +1,66 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyCheckPlaylistFollowersParams {
accessToken: string
playlistId: string
userIds: string
}
interface SpotifyCheckPlaylistFollowersResponse extends ToolResponse {
output: { results: boolean[] }
}
export const spotifyCheckPlaylistFollowersTool: ToolConfig<
SpotifyCheckPlaylistFollowersParams,
SpotifyCheckPlaylistFollowersResponse
> = {
id: 'spotify_check_playlist_followers',
name: 'Spotify Check Playlist Followers',
description: 'Check if users follow a playlist.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['playlist-read-private'],
},
params: {
playlistId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify playlist ID',
},
userIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated user IDs to check (max 5)',
},
},
request: {
url: (params) => {
const ids = params.userIds
.split(',')
.map((id) => id.trim())
.slice(0, 5)
.join(',')
return `https://api.spotify.com/v1/playlists/${params.playlistId}/followers/contains?ids=${ids}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyCheckPlaylistFollowersResponse> => {
const results = await response.json()
return { success: true, output: { results } }
},
outputs: {
results: { type: 'json', description: 'Array of booleans for each user' },
},
}
@@ -0,0 +1,59 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyCheckSavedAlbumsParams {
accessToken: string
albumIds: string
}
interface SpotifyCheckSavedAlbumsResponse extends ToolResponse {
output: { results: boolean[] }
}
export const spotifyCheckSavedAlbumsTool: ToolConfig<
SpotifyCheckSavedAlbumsParams,
SpotifyCheckSavedAlbumsResponse
> = {
id: 'spotify_check_saved_albums',
name: 'Spotify Check Saved Albums',
description: 'Check if albums are saved in library.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-read'],
},
params: {
albumIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated album IDs (max 20)',
},
},
request: {
url: (params) => {
const ids = params.albumIds
.split(',')
.map((id) => id.trim())
.slice(0, 20)
.join(',')
return `https://api.spotify.com/v1/me/albums/contains?ids=${ids}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyCheckSavedAlbumsResponse> => {
const results = await response.json()
return { success: true, output: { results } }
},
outputs: {
results: { type: 'json', description: 'Array of booleans for each album' },
},
}
@@ -0,0 +1,59 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyCheckSavedAudiobooksParams {
accessToken: string
audiobookIds: string
}
interface SpotifyCheckSavedAudiobooksResponse extends ToolResponse {
output: { results: boolean[] }
}
export const spotifyCheckSavedAudiobooksTool: ToolConfig<
SpotifyCheckSavedAudiobooksParams,
SpotifyCheckSavedAudiobooksResponse
> = {
id: 'spotify_check_saved_audiobooks',
name: 'Spotify Check Saved Audiobooks',
description: 'Check if audiobooks are saved in library.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-read'],
},
params: {
audiobookIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated audiobook IDs (max 50)',
},
},
request: {
url: (params) => {
const ids = params.audiobookIds
.split(',')
.map((id) => id.trim())
.slice(0, 50)
.join(',')
return `https://api.spotify.com/v1/me/audiobooks/contains?ids=${ids}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyCheckSavedAudiobooksResponse> => {
const results = await response.json()
return { success: true, output: { results } }
},
outputs: {
results: { type: 'json', description: 'Array of booleans for each audiobook' },
},
}
@@ -0,0 +1,59 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyCheckSavedEpisodesParams {
accessToken: string
episodeIds: string
}
interface SpotifyCheckSavedEpisodesResponse extends ToolResponse {
output: { results: boolean[] }
}
export const spotifyCheckSavedEpisodesTool: ToolConfig<
SpotifyCheckSavedEpisodesParams,
SpotifyCheckSavedEpisodesResponse
> = {
id: 'spotify_check_saved_episodes',
name: 'Spotify Check Saved Episodes',
description: 'Check if episodes are saved in library.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-read'],
},
params: {
episodeIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated episode IDs (max 50)',
},
},
request: {
url: (params) => {
const ids = params.episodeIds
.split(',')
.map((id) => id.trim())
.slice(0, 50)
.join(',')
return `https://api.spotify.com/v1/me/episodes/contains?ids=${ids}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyCheckSavedEpisodesResponse> => {
const results = await response.json()
return { success: true, output: { results } }
},
outputs: {
results: { type: 'json', description: 'Array of booleans for each episode' },
},
}
@@ -0,0 +1,59 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyCheckSavedShowsParams {
accessToken: string
showIds: string
}
interface SpotifyCheckSavedShowsResponse extends ToolResponse {
output: { results: boolean[] }
}
export const spotifyCheckSavedShowsTool: ToolConfig<
SpotifyCheckSavedShowsParams,
SpotifyCheckSavedShowsResponse
> = {
id: 'spotify_check_saved_shows',
name: 'Spotify Check Saved Shows',
description: 'Check if shows are saved in library.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-read'],
},
params: {
showIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated show IDs (max 50)',
},
},
request: {
url: (params) => {
const ids = params.showIds
.split(',')
.map((id) => id.trim())
.slice(0, 50)
.join(',')
return `https://api.spotify.com/v1/me/shows/contains?ids=${ids}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyCheckSavedShowsResponse> => {
const results = await response.json()
return { success: true, output: { results } }
},
outputs: {
results: { type: 'json', description: 'Array of booleans for each show' },
},
}
@@ -0,0 +1,74 @@
import type {
SpotifyCheckSavedTracksParams,
SpotifyCheckSavedTracksResponse,
} from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyCheckSavedTracksTool: ToolConfig<
SpotifyCheckSavedTracksParams,
SpotifyCheckSavedTracksResponse
> = {
id: 'spotify_check_saved_tracks',
name: 'Spotify Check Saved Tracks',
description: "Check if one or more tracks are saved in the user's library.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-read'],
},
params: {
trackIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated track IDs to check (max 50)',
},
},
request: {
url: (params) => {
const ids = params.trackIds
.split(',')
.map((id) => id.trim())
.slice(0, 50)
.join(',')
return `https://api.spotify.com/v1/me/tracks/contains?ids=${ids}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response, params): Promise<SpotifyCheckSavedTracksResponse> => {
const data = await response.json()
const ids = (params?.trackIds || '')
.split(',')
.map((id) => id.trim())
.slice(0, 50)
const results = ids.map((id, index) => ({
id,
saved: data[index] || false,
}))
return {
success: true,
output: {
results,
all_saved: data.every((saved: boolean) => saved),
none_saved: data.every((saved: boolean) => !saved),
},
}
},
outputs: {
results: { type: 'json', description: 'Array of track IDs with saved status' },
all_saved: { type: 'boolean', description: 'Whether all tracks are saved' },
none_saved: { type: 'boolean', description: 'Whether no tracks are saved' },
},
}
+92
View File
@@ -0,0 +1,92 @@
import type {
SpotifyCreatePlaylistParams,
SpotifyCreatePlaylistResponse,
} from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyCreatePlaylistTool: ToolConfig<
SpotifyCreatePlaylistParams,
SpotifyCreatePlaylistResponse
> = {
id: 'spotify_create_playlist',
name: 'Spotify Create Playlist',
description: 'Create a new playlist for the current user on Spotify.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['playlist-modify-public', 'playlist-modify-private'],
},
params: {
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name for the new playlist',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Description for the playlist',
},
public: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
default: true,
description: 'Whether the playlist should be public',
},
collaborative: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
default: false,
description: 'Whether the playlist should be collaborative (requires public to be false)',
},
},
request: {
url: () => 'https://api.spotify.com/v1/me/playlists',
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => ({
name: params.name,
description: params.description || '',
public: params.public !== false,
collaborative: params.collaborative === true,
}),
},
transformResponse: async (response): Promise<SpotifyCreatePlaylistResponse> => {
const playlist = await response.json()
return {
success: true,
output: {
id: playlist.id,
name: playlist.name,
description: playlist.description,
public: playlist.public,
collaborative: playlist.collaborative,
snapshot_id: playlist.snapshot_id,
external_url: playlist.external_urls?.spotify || '',
},
}
},
outputs: {
id: { type: 'string', description: 'Spotify playlist ID' },
name: { type: 'string', description: 'Playlist name' },
description: { type: 'string', description: 'Playlist description', optional: true },
public: { type: 'boolean', description: 'Whether the playlist is public' },
collaborative: { type: 'boolean', description: 'Whether collaborative' },
snapshot_id: { type: 'string', description: 'Playlist snapshot ID' },
external_url: { type: 'string', description: 'Spotify URL' },
},
}
+64
View File
@@ -0,0 +1,64 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyFollowArtistsParams {
accessToken: string
artistIds: string
}
interface SpotifyFollowArtistsResponse extends ToolResponse {
output: {
success: boolean
}
}
export const spotifyFollowArtistsTool: ToolConfig<
SpotifyFollowArtistsParams,
SpotifyFollowArtistsResponse
> = {
id: 'spotify_follow_artists',
name: 'Spotify Follow Artists',
description: 'Follow one or more artists.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-follow-modify'],
},
params: {
artistIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated artist IDs to follow (max 50)',
},
},
request: {
url: (params) => {
const ids = params.artistIds
.split(',')
.map((id) => id.trim())
.slice(0, 50)
.join(',')
return `https://api.spotify.com/v1/me/following?type=artist&ids=${ids}`
},
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (): Promise<SpotifyFollowArtistsResponse> => {
return {
success: true,
output: { success: true },
}
},
outputs: {
success: { type: 'boolean', description: 'Whether artists were followed successfully' },
},
}
+63
View File
@@ -0,0 +1,63 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyFollowPlaylistParams {
accessToken: string
playlistId: string
public?: boolean
}
interface SpotifyFollowPlaylistResponse extends ToolResponse {
output: { success: boolean }
}
export const spotifyFollowPlaylistTool: ToolConfig<
SpotifyFollowPlaylistParams,
SpotifyFollowPlaylistResponse
> = {
id: 'spotify_follow_playlist',
name: 'Spotify Follow Playlist',
description: 'Follow (save) a playlist.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['playlist-modify-public', 'playlist-modify-private'],
},
params: {
playlistId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify playlist ID',
},
public: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
default: true,
description: 'Whether the playlist will appear in public playlists',
},
},
request: {
url: (params) => `https://api.spotify.com/v1/playlists/${params.playlistId}/followers`,
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => ({
public: params.public ?? true,
}),
},
transformResponse: async (): Promise<SpotifyFollowPlaylistResponse> => {
return { success: true, output: { success: true } }
},
outputs: {
success: { type: 'boolean', description: 'Whether follow succeeded' },
},
}
+99
View File
@@ -0,0 +1,99 @@
import type { SpotifyGetAlbumParams, SpotifyGetAlbumResponse } from '@/tools/spotify/types'
import {
SIMPLIFIED_ALBUM_TRACK_OUTPUT_PROPERTIES,
SIMPLIFIED_ARTIST_OUTPUT_PROPERTIES,
} from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyGetAlbumTool: ToolConfig<SpotifyGetAlbumParams, SpotifyGetAlbumResponse> = {
id: 'spotify_get_album',
name: 'Spotify Get Album',
description:
'Get detailed information about an album on Spotify by its ID, including track listing.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
},
params: {
albumId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify ID of the album',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code for track availability (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
let url = `https://api.spotify.com/v1/albums/${params.albumId}`
if (params.market) {
url += `?market=${params.market}`
}
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetAlbumResponse> => {
const album = await response.json()
return {
success: true,
output: {
id: album.id,
name: album.name,
artists: album.artists?.map((a: any) => ({ id: a.id, name: a.name })) || [],
album_type: album.album_type,
total_tracks: album.total_tracks,
release_date: album.release_date,
label: album.label || '',
popularity: album.popularity,
genres: album.genres || [],
image_url: album.images?.[0]?.url || null,
tracks: (album.tracks?.items || []).map((t: any) => ({
id: t.id,
name: t.name,
duration_ms: t.duration_ms,
track_number: t.track_number,
})),
external_url: album.external_urls?.spotify || '',
},
}
},
outputs: {
id: { type: 'string', description: 'Spotify album ID' },
name: { type: 'string', description: 'Album name' },
artists: {
type: 'array',
description: 'List of artists',
items: { type: 'object', properties: SIMPLIFIED_ARTIST_OUTPUT_PROPERTIES },
},
album_type: { type: 'string', description: 'Type of album (album, single, compilation)' },
total_tracks: { type: 'number', description: 'Total number of tracks' },
release_date: { type: 'string', description: 'Release date' },
label: { type: 'string', description: 'Record label' },
popularity: { type: 'number', description: 'Popularity score (0-100)' },
genres: { type: 'array', description: 'List of genres' },
image_url: { type: 'string', description: 'Album cover image URL', optional: true },
tracks: {
type: 'array',
description: 'List of tracks on the album',
items: { type: 'object', properties: SIMPLIFIED_ALBUM_TRACK_OUTPUT_PROPERTIES },
},
external_url: { type: 'string', description: 'Spotify URL' },
},
}
+117
View File
@@ -0,0 +1,117 @@
import { ALBUM_TRACK_OUTPUT_PROPERTIES } from '@/tools/spotify/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetAlbumTracksParams {
accessToken: string
albumId: string
limit?: number
offset?: number
market?: string
}
interface SpotifyGetAlbumTracksResponse extends ToolResponse {
output: {
tracks: Array<{
id: string
name: string
artists: Array<{ id: string; name: string }>
duration_ms: number
track_number: number
disc_number: number
explicit: boolean
preview_url: string | null
}>
total: number
next: string | null
}
}
export const spotifyGetAlbumTracksTool: ToolConfig<
SpotifyGetAlbumTracksParams,
SpotifyGetAlbumTracksResponse
> = {
id: 'spotify_get_album_tracks',
name: 'Spotify Get Album Tracks',
description: 'Get the tracks from an album.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-private'],
},
params: {
albumId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify album ID',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 20,
description: 'Number of tracks to return (1-50)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 0,
description: 'Index of first track to return for pagination',
},
},
request: {
url: (params) => {
const limit = Math.min(Math.max(params.limit || 20, 1), 50)
const offset = params.offset || 0
let url = `https://api.spotify.com/v1/albums/${params.albumId}/tracks?limit=${limit}&offset=${offset}`
if (params.market) {
url += `&market=${params.market}`
}
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetAlbumTracksResponse> => {
const data = await response.json()
const tracks = (data.items || []).map((track: any) => ({
id: track.id,
name: track.name,
artists: track.artists?.map((a: any) => ({ id: a.id, name: a.name })) || [],
duration_ms: track.duration_ms,
track_number: track.track_number,
disc_number: track.disc_number,
explicit: track.explicit || false,
preview_url: track.preview_url || null,
}))
return {
success: true,
output: {
tracks,
total: data.total || 0,
next: data.next || null,
},
}
},
outputs: {
tracks: {
type: 'array',
description: 'List of tracks',
items: { type: 'object', properties: ALBUM_TRACK_OUTPUT_PROPERTIES },
},
total: { type: 'number', description: 'Total number of tracks' },
next: { type: 'string', description: 'URL for next page', optional: true },
},
}
+115
View File
@@ -0,0 +1,115 @@
import { SIMPLIFIED_ARTIST_OUTPUT_PROPERTIES } from '@/tools/spotify/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetAlbumsParams {
accessToken: string
albumIds: string
market?: string
}
interface SpotifyGetAlbumsResponse extends ToolResponse {
output: {
albums: Array<{
id: string
name: string
artists: Array<{ id: string; name: string }>
album_type: string
total_tracks: number
release_date: string
image_url: string | null
external_url: string
}>
}
}
export const spotifyGetAlbumsTool: ToolConfig<SpotifyGetAlbumsParams, SpotifyGetAlbumsResponse> = {
id: 'spotify_get_albums',
name: 'Spotify Get Multiple Albums',
description: 'Get details for multiple albums by their IDs.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-private'],
},
params: {
albumIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated album IDs (max 20)',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
const ids = params.albumIds
.split(',')
.map((id) => id.trim())
.slice(0, 20)
.join(',')
let url = `https://api.spotify.com/v1/albums?ids=${ids}`
if (params.market) {
url += `&market=${params.market}`
}
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetAlbumsResponse> => {
const data = await response.json()
const albums = (data.albums || []).map((album: any) => ({
id: album.id,
name: album.name,
artists: album.artists?.map((a: any) => ({ id: a.id, name: a.name })) || [],
album_type: album.album_type,
total_tracks: album.total_tracks,
release_date: album.release_date,
image_url: album.images?.[0]?.url || null,
external_url: album.external_urls?.spotify || '',
}))
return {
success: true,
output: { albums },
}
},
outputs: {
albums: {
type: 'array',
description: 'List of albums',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Spotify album ID' },
name: { type: 'string', description: 'Album name' },
artists: {
type: 'array',
description: 'List of artists',
items: { type: 'object', properties: SIMPLIFIED_ARTIST_OUTPUT_PROPERTIES },
},
album_type: { type: 'string', description: 'Type of album (album, single, compilation)' },
total_tracks: { type: 'number', description: 'Total number of tracks' },
release_date: { type: 'string', description: 'Release date' },
image_url: { type: 'string', description: 'Album cover image URL', optional: true },
external_url: { type: 'string', description: 'Spotify URL' },
},
},
},
},
}
+59
View File
@@ -0,0 +1,59 @@
import type { SpotifyGetArtistParams, SpotifyGetArtistResponse } from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyGetArtistTool: ToolConfig<SpotifyGetArtistParams, SpotifyGetArtistResponse> = {
id: 'spotify_get_artist',
name: 'Spotify Get Artist',
description: 'Get detailed information about an artist on Spotify by their ID.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
},
params: {
artistId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify ID of the artist',
},
},
request: {
url: (params) => `https://api.spotify.com/v1/artists/${params.artistId}`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetArtistResponse> => {
const artist = await response.json()
return {
success: true,
output: {
id: artist.id,
name: artist.name,
genres: artist.genres || [],
popularity: artist.popularity,
followers: artist.followers?.total || 0,
image_url: artist.images?.[0]?.url || null,
external_url: artist.external_urls?.spotify || '',
},
}
},
outputs: {
id: { type: 'string', description: 'Spotify artist ID' },
name: { type: 'string', description: 'Artist name' },
genres: { type: 'array', description: 'List of genres associated with the artist' },
popularity: { type: 'number', description: 'Popularity score (0-100)' },
followers: { type: 'number', description: 'Number of followers' },
image_url: { type: 'string', description: 'Artist image URL', optional: true },
external_url: { type: 'string', description: 'Spotify URL' },
},
}
+119
View File
@@ -0,0 +1,119 @@
import type {
SpotifyGetArtistAlbumsParams,
SpotifyGetArtistAlbumsResponse,
} from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyGetArtistAlbumsTool: ToolConfig<
SpotifyGetArtistAlbumsParams,
SpotifyGetArtistAlbumsResponse
> = {
id: 'spotify_get_artist_albums',
name: 'Spotify Get Artist Albums',
description: 'Get albums by an artist on Spotify. Can filter by album type.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
},
params: {
artistId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify ID of the artist',
},
include_groups: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by album type: album, single, appears_on, compilation (comma-separated)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 20,
description: 'Maximum number of albums to return (1-50)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 0,
description: 'Index of the first album to return for pagination',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
const limit = Math.min(Math.max(params.limit || 20, 1), 50)
const offset = params.offset || 0
let url = `https://api.spotify.com/v1/artists/${params.artistId}/albums?limit=${limit}&offset=${offset}`
if (params.include_groups) {
url += `&include_groups=${encodeURIComponent(params.include_groups)}`
}
if (params.market) {
url += `&market=${params.market}`
}
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetArtistAlbumsResponse> => {
const data = await response.json()
const albums = (data.items || []).map((album: any) => ({
id: album.id,
name: album.name,
album_type: album.album_type,
total_tracks: album.total_tracks,
release_date: album.release_date,
image_url: album.images?.[0]?.url || null,
external_url: album.external_urls?.spotify || '',
}))
return {
success: true,
output: {
albums,
total: data.total || albums.length,
next: data.next || null,
},
}
},
outputs: {
albums: {
type: 'array',
description: "Artist's albums",
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Spotify album ID' },
name: { type: 'string', description: 'Album name' },
album_type: { type: 'string', description: 'Type (album, single, compilation)' },
total_tracks: { type: 'number', description: 'Number of tracks' },
release_date: { type: 'string', description: 'Release date' },
image_url: { type: 'string', description: 'Album cover URL' },
external_url: { type: 'string', description: 'Spotify URL' },
},
},
},
total: { type: 'number', description: 'Total number of albums available' },
next: { type: 'string', description: 'URL for next page of results', optional: true },
},
}
@@ -0,0 +1,83 @@
import type {
SpotifyGetArtistTopTracksParams,
SpotifyGetArtistTopTracksResponse,
} from '@/tools/spotify/types'
import { ARTIST_TOP_TRACK_OUTPUT_PROPERTIES } from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyGetArtistTopTracksTool: ToolConfig<
SpotifyGetArtistTopTracksParams,
SpotifyGetArtistTopTracksResponse
> = {
id: 'spotify_get_artist_top_tracks',
name: 'Spotify Get Artist Top Tracks',
description: 'Get the top 10 most popular tracks by an artist on Spotify.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
},
params: {
artistId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify ID of the artist',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
default: 'US',
description:
'ISO 3166-1 alpha-2 country code (e.g., "US", "GB") - required for this endpoint',
},
},
request: {
url: (params) => {
const market = params.market || 'US'
return `https://api.spotify.com/v1/artists/${params.artistId}/top-tracks?market=${market}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetArtistTopTracksResponse> => {
const data = await response.json()
const tracks = (data.tracks || []).map((track: any) => ({
id: track.id,
name: track.name,
album: {
id: track.album?.id || '',
name: track.album?.name || '',
image_url: track.album?.images?.[0]?.url || null,
},
duration_ms: track.duration_ms,
popularity: track.popularity,
preview_url: track.preview_url,
external_url: track.external_urls?.spotify || '',
}))
return {
success: true,
output: {
tracks,
},
}
},
outputs: {
tracks: {
type: 'array',
description: "Artist's top tracks",
items: { type: 'object', properties: ARTIST_TOP_TRACK_OUTPUT_PROPERTIES },
},
},
}
+87
View File
@@ -0,0 +1,87 @@
import { ARTIST_OUTPUT_PROPERTIES } from '@/tools/spotify/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetArtistsParams {
accessToken: string
artistIds: string
}
interface SpotifyGetArtistsResponse extends ToolResponse {
output: {
artists: Array<{
id: string
name: string
genres: string[]
popularity: number
followers: number
image_url: string | null
external_url: string
}>
}
}
export const spotifyGetArtistsTool: ToolConfig<SpotifyGetArtistsParams, SpotifyGetArtistsResponse> =
{
id: 'spotify_get_artists',
name: 'Spotify Get Multiple Artists',
description: 'Get details for multiple artists by their IDs.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-private'],
},
params: {
artistIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated artist IDs (max 50)',
},
},
request: {
url: (params) => {
const ids = params.artistIds
.split(',')
.map((id) => id.trim())
.slice(0, 50)
.join(',')
return `https://api.spotify.com/v1/artists?ids=${ids}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetArtistsResponse> => {
const data = await response.json()
const artists = (data.artists || []).map((artist: any) => ({
id: artist.id,
name: artist.name,
genres: artist.genres || [],
popularity: artist.popularity || 0,
followers: artist.followers?.total || 0,
image_url: artist.images?.[0]?.url || null,
external_url: artist.external_urls?.spotify || '',
}))
return {
success: true,
output: { artists },
}
},
outputs: {
artists: {
type: 'array',
description: 'List of artists',
items: { type: 'object', properties: ARTIST_OUTPUT_PROPERTIES },
},
},
}
+97
View File
@@ -0,0 +1,97 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetAudiobookParams {
accessToken: string
audiobookId: string
market?: string
}
interface SpotifyGetAudiobookResponse extends ToolResponse {
output: {
id: string
name: string
authors: Array<{ name: string }>
narrators: Array<{ name: string }>
publisher: string
description: string
total_chapters: number
languages: string[]
image_url: string | null
external_url: string
}
}
export const spotifyGetAudiobookTool: ToolConfig<
SpotifyGetAudiobookParams,
SpotifyGetAudiobookResponse
> = {
id: 'spotify_get_audiobook',
name: 'Spotify Get Audiobook',
description: 'Get details for an audiobook.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-playback-position'],
},
params: {
audiobookId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify audiobook ID',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
let url = `https://api.spotify.com/v1/audiobooks/${params.audiobookId}`
if (params.market) url += `?market=${params.market}`
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyGetAudiobookResponse> => {
const book = await response.json()
return {
success: true,
output: {
id: book.id,
name: book.name,
authors: book.authors || [],
narrators: book.narrators || [],
publisher: book.publisher || '',
description: book.description || '',
total_chapters: book.total_chapters || 0,
languages: book.languages || [],
image_url: book.images?.[0]?.url || null,
external_url: book.external_urls?.spotify || '',
},
}
},
outputs: {
id: { type: 'string', description: 'Audiobook ID' },
name: { type: 'string', description: 'Audiobook name' },
authors: { type: 'json', description: 'Authors' },
narrators: { type: 'json', description: 'Narrators' },
publisher: { type: 'string', description: 'Publisher' },
description: { type: 'string', description: 'Description' },
total_chapters: { type: 'number', description: 'Total chapters' },
languages: { type: 'json', description: 'Languages' },
image_url: { type: 'string', description: 'Cover image URL' },
external_url: { type: 'string', description: 'Spotify URL' },
},
}
@@ -0,0 +1,108 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetAudiobookChaptersParams {
accessToken: string
audiobookId: string
limit?: number
offset?: number
market?: string
}
interface SpotifyGetAudiobookChaptersResponse extends ToolResponse {
output: {
chapters: Array<{
id: string
name: string
chapter_number: number
duration_ms: number
image_url: string | null
external_url: string
}>
total: number
next: string | null
}
}
export const spotifyGetAudiobookChaptersTool: ToolConfig<
SpotifyGetAudiobookChaptersParams,
SpotifyGetAudiobookChaptersResponse
> = {
id: 'spotify_get_audiobook_chapters',
name: 'Spotify Get Audiobook Chapters',
description: 'Get chapters from an audiobook.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-playback-position'],
},
params: {
audiobookId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify audiobook ID',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 20,
description: 'Number of chapters to return (1-50)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 0,
description: 'Index of first chapter to return for pagination',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
const limit = Math.min(Math.max(params.limit || 20, 1), 50)
const offset = params.offset || 0
let url = `https://api.spotify.com/v1/audiobooks/${params.audiobookId}/chapters?limit=${limit}&offset=${offset}`
if (params.market) url += `&market=${params.market}`
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyGetAudiobookChaptersResponse> => {
const data = await response.json()
return {
success: true,
output: {
chapters: (data.items || []).map((ch: any) => ({
id: ch.id,
name: ch.name,
chapter_number: ch.chapter_number || 0,
duration_ms: ch.duration_ms || 0,
image_url: ch.images?.[0]?.url || null,
external_url: ch.external_urls?.spotify || '',
})),
total: data.total || 0,
next: data.next || null,
},
}
},
outputs: {
chapters: { type: 'json', description: 'List of chapters' },
total: { type: 'number', description: 'Total chapters' },
next: { type: 'string', description: 'URL for next page', optional: true },
},
}
+89
View File
@@ -0,0 +1,89 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetAudiobooksParams {
accessToken: string
audiobookIds: string
market?: string
}
interface SpotifyGetAudiobooksResponse extends ToolResponse {
output: {
audiobooks: Array<{
id: string
name: string
authors: Array<{ name: string }>
total_chapters: number
image_url: string | null
external_url: string
}>
}
}
export const spotifyGetAudiobooksTool: ToolConfig<
SpotifyGetAudiobooksParams,
SpotifyGetAudiobooksResponse
> = {
id: 'spotify_get_audiobooks',
name: 'Spotify Get Multiple Audiobooks',
description: 'Get details for multiple audiobooks.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-playback-position'],
},
params: {
audiobookIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated audiobook IDs (max 50)',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
const ids = params.audiobookIds
.split(',')
.map((id) => id.trim())
.slice(0, 50)
.join(',')
let url = `https://api.spotify.com/v1/audiobooks?ids=${ids}`
if (params.market) url += `&market=${params.market}`
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyGetAudiobooksResponse> => {
const data = await response.json()
return {
success: true,
output: {
audiobooks: (data.audiobooks || []).map((book: any) => ({
id: book.id,
name: book.name,
authors: book.authors || [],
total_chapters: book.total_chapters || 0,
image_url: book.images?.[0]?.url || null,
external_url: book.external_urls?.spotify || '',
})),
},
}
},
outputs: {
audiobooks: { type: 'json', description: 'List of audiobooks' },
},
}
+85
View File
@@ -0,0 +1,85 @@
import type {
SpotifyGetCategoriesParams,
SpotifyGetCategoriesResponse,
} from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyGetCategoriesTool: ToolConfig<
SpotifyGetCategoriesParams,
SpotifyGetCategoriesResponse
> = {
id: 'spotify_get_categories',
name: 'Spotify Get Categories',
description: 'Get a list of browse categories used to tag items in Spotify.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-private'],
},
params: {
country: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code (e.g., "US", "GB")',
},
locale: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Locale code (e.g., "en_US", "es_MX")',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 20,
description: 'Number of categories to return (1-50)',
},
},
request: {
url: (params) => {
const limit = Math.min(Math.max(params.limit || 20, 1), 50)
let url = `https://api.spotify.com/v1/browse/categories?limit=${limit}`
if (params.country) {
url += `&country=${params.country}`
}
if (params.locale) {
url += `&locale=${params.locale}`
}
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetCategoriesResponse> => {
const data = await response.json()
const categories = (data.categories?.items || []).map((category: any) => ({
id: category.id,
name: category.name,
icon_url: category.icons?.[0]?.url || null,
}))
return {
success: true,
output: {
categories,
total: data.categories?.total || 0,
},
}
},
outputs: {
categories: { type: 'json', description: 'List of browse categories' },
total: { type: 'number', description: 'Total number of categories' },
},
}
@@ -0,0 +1,61 @@
import type {
SpotifyGetCurrentUserParams,
SpotifyGetCurrentUserResponse,
} from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyGetCurrentUserTool: ToolConfig<
SpotifyGetCurrentUserParams,
SpotifyGetCurrentUserResponse
> = {
id: 'spotify_get_current_user',
name: 'Spotify Get Current User',
description: "Get the current user's Spotify profile information.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-private', 'user-read-email'],
},
params: {},
request: {
url: () => 'https://api.spotify.com/v1/me',
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetCurrentUserResponse> => {
const user = await response.json()
return {
success: true,
output: {
id: user.id,
display_name: user.display_name || '',
email: user.email || null,
country: user.country || null,
product: user.product || null,
followers: user.followers?.total || 0,
image_url: user.images?.[0]?.url || null,
external_url: user.external_urls?.spotify || '',
},
}
},
outputs: {
id: { type: 'string', description: 'Spotify user ID' },
display_name: { type: 'string', description: 'Display name' },
email: { type: 'string', description: 'Email address', optional: true },
country: { type: 'string', description: 'Country code', optional: true },
product: { type: 'string', description: 'Subscription level (free, premium)', optional: true },
followers: { type: 'number', description: 'Number of followers' },
image_url: { type: 'string', description: 'Profile image URL', optional: true },
external_url: { type: 'string', description: 'Spotify profile URL' },
},
}
@@ -0,0 +1,114 @@
import { CURRENTLY_PLAYING_TRACK_OUTPUT_PROPERTIES } from '@/tools/spotify/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetCurrentlyPlayingParams {
accessToken: string
market?: string
}
interface SpotifyGetCurrentlyPlayingResponse extends ToolResponse {
output: {
is_playing: boolean
progress_ms: number | null
track: {
id: string
name: string
artists: Array<{ id: string; name: string }>
album: {
id: string
name: string
image_url: string | null
}
duration_ms: number
external_url: string
} | null
}
}
export const spotifyGetCurrentlyPlayingTool: ToolConfig<
SpotifyGetCurrentlyPlayingParams,
SpotifyGetCurrentlyPlayingResponse
> = {
id: 'spotify_get_currently_playing',
name: 'Spotify Get Currently Playing',
description: "Get the user's currently playing track.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-currently-playing'],
},
params: {
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
let url = 'https://api.spotify.com/v1/me/player/currently-playing'
if (params.market) {
url += `?market=${params.market}`
}
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetCurrentlyPlayingResponse> => {
if (response.status === 204) {
return {
success: true,
output: {
is_playing: false,
progress_ms: null,
track: null,
},
}
}
const data = await response.json()
return {
success: true,
output: {
is_playing: data.is_playing || false,
progress_ms: data.progress_ms || null,
track: data.item
? {
id: data.item.id,
name: data.item.name,
artists: data.item.artists?.map((a: any) => ({ id: a.id, name: a.name })) || [],
album: {
id: data.item.album?.id || '',
name: data.item.album?.name || '',
image_url: data.item.album?.images?.[0]?.url || null,
},
duration_ms: data.item.duration_ms,
external_url: data.item.external_urls?.spotify || '',
}
: null,
},
}
},
outputs: {
is_playing: { type: 'boolean', description: 'Whether playback is active' },
progress_ms: { type: 'number', description: 'Current position in track (ms)', optional: true },
track: {
type: 'object',
description: 'Currently playing track',
optional: true,
properties: CURRENTLY_PLAYING_TRACK_OUTPUT_PROPERTIES,
},
},
}
+67
View File
@@ -0,0 +1,67 @@
import type { SpotifyGetDevicesParams, SpotifyGetDevicesResponse } from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyGetDevicesTool: ToolConfig<SpotifyGetDevicesParams, SpotifyGetDevicesResponse> =
{
id: 'spotify_get_devices',
name: 'Spotify Get Devices',
description: "Get the user's available Spotify playback devices.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-playback-state'],
},
params: {},
request: {
url: () => 'https://api.spotify.com/v1/me/player/devices',
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetDevicesResponse> => {
const data = await response.json()
const devices = (data.devices || []).map((device: any) => ({
id: device.id,
is_active: device.is_active,
is_private_session: device.is_private_session,
is_restricted: device.is_restricted,
name: device.name,
type: device.type,
volume_percent: device.volume_percent,
}))
return {
success: true,
output: {
devices,
},
}
},
outputs: {
devices: {
type: 'array',
description: 'Available playback devices',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Device ID' },
is_active: { type: 'boolean', description: 'Whether device is active' },
is_private_session: { type: 'boolean', description: 'Whether in private session' },
is_restricted: { type: 'boolean', description: 'Whether device is restricted' },
name: { type: 'string', description: 'Device name' },
type: { type: 'string', description: 'Device type (Computer, Smartphone, etc.)' },
volume_percent: { type: 'number', description: 'Current volume (0-100)' },
},
},
},
},
}
+96
View File
@@ -0,0 +1,96 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetEpisodeParams {
accessToken: string
episodeId: string
market?: string
}
interface SpotifyGetEpisodeResponse extends ToolResponse {
output: {
id: string
name: string
description: string
duration_ms: number
release_date: string
explicit: boolean
show: { id: string; name: string; publisher: string }
image_url: string | null
external_url: string
}
}
export const spotifyGetEpisodeTool: ToolConfig<SpotifyGetEpisodeParams, SpotifyGetEpisodeResponse> =
{
id: 'spotify_get_episode',
name: 'Spotify Get Episode',
description: 'Get details for a podcast episode.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-playback-position'],
},
params: {
episodeId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify episode ID',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
let url = `https://api.spotify.com/v1/episodes/${params.episodeId}`
if (params.market) url += `?market=${params.market}`
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyGetEpisodeResponse> => {
const ep = await response.json()
return {
success: true,
output: {
id: ep.id,
name: ep.name,
description: ep.description || '',
duration_ms: ep.duration_ms || 0,
release_date: ep.release_date || '',
explicit: ep.explicit || false,
show: {
id: ep.show?.id || '',
name: ep.show?.name || '',
publisher: ep.show?.publisher || '',
},
image_url: ep.images?.[0]?.url || null,
external_url: ep.external_urls?.spotify || '',
},
}
},
outputs: {
id: { type: 'string', description: 'Episode ID' },
name: { type: 'string', description: 'Episode name' },
description: { type: 'string', description: 'Episode description' },
duration_ms: { type: 'number', description: 'Duration in ms' },
release_date: { type: 'string', description: 'Release date' },
explicit: { type: 'boolean', description: 'Contains explicit content' },
show: { type: 'json', description: 'Parent show info' },
image_url: { type: 'string', description: 'Cover image URL' },
external_url: { type: 'string', description: 'Spotify URL' },
},
}
+93
View File
@@ -0,0 +1,93 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetEpisodesParams {
accessToken: string
episodeIds: string
market?: string
}
interface SpotifyGetEpisodesResponse extends ToolResponse {
output: {
episodes: Array<{
id: string
name: string
description: string
duration_ms: number
release_date: string
show: { id: string; name: string }
image_url: string | null
external_url: string
}>
}
}
export const spotifyGetEpisodesTool: ToolConfig<
SpotifyGetEpisodesParams,
SpotifyGetEpisodesResponse
> = {
id: 'spotify_get_episodes',
name: 'Spotify Get Multiple Episodes',
description: 'Get details for multiple podcast episodes.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-playback-position'],
},
params: {
episodeIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated episode IDs (max 50)',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
const ids = params.episodeIds
.split(',')
.map((id) => id.trim())
.slice(0, 50)
.join(',')
let url = `https://api.spotify.com/v1/episodes?ids=${ids}`
if (params.market) url += `&market=${params.market}`
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyGetEpisodesResponse> => {
const data = await response.json()
return {
success: true,
output: {
episodes: (data.episodes || []).map((ep: any) => ({
id: ep.id,
name: ep.name,
description: ep.description || '',
duration_ms: ep.duration_ms || 0,
release_date: ep.release_date || '',
show: { id: ep.show?.id || '', name: ep.show?.name || '' },
image_url: ep.images?.[0]?.url || null,
external_url: ep.external_urls?.spotify || '',
})),
},
}
},
outputs: {
episodes: { type: 'json', description: 'List of episodes' },
},
}
@@ -0,0 +1,105 @@
import { ARTIST_OUTPUT_PROPERTIES } from '@/tools/spotify/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetFollowedArtistsParams {
accessToken: string
limit?: number
after?: string
}
interface SpotifyGetFollowedArtistsResponse extends ToolResponse {
output: {
artists: Array<{
id: string
name: string
genres: string[]
popularity: number
followers: number
image_url: string | null
external_url: string
}>
total: number
next: string | null
}
}
export const spotifyGetFollowedArtistsTool: ToolConfig<
SpotifyGetFollowedArtistsParams,
SpotifyGetFollowedArtistsResponse
> = {
id: 'spotify_get_followed_artists',
name: 'Spotify Get Followed Artists',
description: "Get the user's followed artists.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-follow-read'],
},
params: {
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 20,
description: 'Number of artists to return (1-50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination (last artist ID from previous request)',
},
},
request: {
url: (params) => {
const limit = Math.min(Math.max(params.limit || 20, 1), 50)
let url = `https://api.spotify.com/v1/me/following?type=artist&limit=${limit}`
if (params.after) {
url += `&after=${params.after}`
}
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetFollowedArtistsResponse> => {
const data = await response.json()
const artists = (data.artists?.items || []).map((artist: any) => ({
id: artist.id,
name: artist.name,
genres: artist.genres || [],
popularity: artist.popularity || 0,
followers: artist.followers?.total || 0,
image_url: artist.images?.[0]?.url || null,
external_url: artist.external_urls?.spotify || '',
}))
return {
success: true,
output: {
artists,
total: data.artists?.total || 0,
next: data.artists?.cursors?.after || null,
},
}
},
outputs: {
artists: {
type: 'array',
description: 'List of followed artists',
items: { type: 'object', properties: ARTIST_OUTPUT_PROPERTIES },
},
total: { type: 'number', description: 'Total number of followed artists' },
next: { type: 'string', description: 'Cursor for next page', optional: true },
},
}
+47
View File
@@ -0,0 +1,47 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetMarketsParams {
accessToken: string
}
interface SpotifyGetMarketsResponse extends ToolResponse {
output: {
markets: string[]
}
}
export const spotifyGetMarketsTool: ToolConfig<SpotifyGetMarketsParams, SpotifyGetMarketsResponse> =
{
id: 'spotify_get_markets',
name: 'Spotify Get Available Markets',
description: 'Get the list of markets where Spotify is available.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-private'],
},
params: {},
request: {
url: () => 'https://api.spotify.com/v1/markets',
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyGetMarketsResponse> => {
const data = await response.json()
return {
success: true,
output: { markets: data.markets || [] },
}
},
outputs: {
markets: { type: 'json', description: 'List of ISO country codes' },
},
}
@@ -0,0 +1,96 @@
import type {
SpotifyGetNewReleasesParams,
SpotifyGetNewReleasesResponse,
} from '@/tools/spotify/types'
import { ALBUM_WITH_ARTISTS_OUTPUT_PROPERTIES } from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyGetNewReleasesTool: ToolConfig<
SpotifyGetNewReleasesParams,
SpotifyGetNewReleasesResponse
> = {
id: 'spotify_get_new_releases',
name: 'Spotify Get New Releases',
description: 'Get a list of new album releases featured in Spotify.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-private'],
},
params: {
country: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code (e.g., "US", "GB")',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 20,
description: 'Number of releases to return (1-50)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 0,
description: 'Index of first release to return for pagination',
},
},
request: {
url: (params) => {
const limit = Math.min(Math.max(params.limit || 20, 1), 50)
const offset = params.offset || 0
let url = `https://api.spotify.com/v1/browse/new-releases?limit=${limit}&offset=${offset}`
if (params.country) {
url += `&country=${params.country}`
}
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetNewReleasesResponse> => {
const data = await response.json()
const albums = (data.albums?.items || []).map((album: any) => ({
id: album.id,
name: album.name,
artists: album.artists?.map((a: any) => ({ id: a.id, name: a.name })) || [],
release_date: album.release_date,
total_tracks: album.total_tracks,
album_type: album.album_type,
image_url: album.images?.[0]?.url || null,
external_url: album.external_urls?.spotify || '',
}))
return {
success: true,
output: {
albums,
total: data.albums?.total || 0,
next: data.albums?.next || null,
},
}
},
outputs: {
albums: {
type: 'array',
description: 'List of new releases',
items: { type: 'object', properties: ALBUM_WITH_ARTISTS_OUTPUT_PROPERTIES },
},
total: { type: 'number', description: 'Total number of new releases' },
next: { type: 'string', description: 'URL for next page', optional: true },
},
}
@@ -0,0 +1,120 @@
import type {
SpotifyGetPlaybackStateParams,
SpotifyGetPlaybackStateResponse,
} from '@/tools/spotify/types'
import {
PLAYBACK_TRACK_OUTPUT_PROPERTIES,
SIMPLIFIED_DEVICE_OUTPUT_PROPERTIES,
} from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyGetPlaybackStateTool: ToolConfig<
SpotifyGetPlaybackStateParams,
SpotifyGetPlaybackStateResponse
> = {
id: 'spotify_get_playback_state',
name: 'Spotify Get Playback State',
description: 'Get the current playback state including device, track, and progress.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-playback-state'],
},
params: {
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
let url = 'https://api.spotify.com/v1/me/player'
if (params.market) {
url += `?market=${params.market}`
}
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetPlaybackStateResponse> => {
if (response.status === 204) {
return {
success: true,
output: {
is_playing: false,
device: null,
progress_ms: null,
currently_playing_type: 'unknown',
shuffle_state: false,
repeat_state: 'off',
track: null,
},
}
}
const data = await response.json()
return {
success: true,
output: {
is_playing: data.is_playing || false,
device: data.device
? {
id: data.device.id,
name: data.device.name,
type: data.device.type,
volume_percent: data.device.volume_percent,
}
: null,
progress_ms: data.progress_ms,
currently_playing_type: data.currently_playing_type || 'unknown',
shuffle_state: data.shuffle_state || false,
repeat_state: data.repeat_state || 'off',
track: data.item
? {
id: data.item.id,
name: data.item.name,
artists: data.item.artists?.map((a: any) => ({ id: a.id, name: a.name })) || [],
album: {
id: data.item.album?.id || '',
name: data.item.album?.name || '',
image_url: data.item.album?.images?.[0]?.url || null,
},
duration_ms: data.item.duration_ms,
}
: null,
},
}
},
outputs: {
is_playing: { type: 'boolean', description: 'Whether playback is active' },
device: {
type: 'object',
description: 'Active device information',
optional: true,
properties: SIMPLIFIED_DEVICE_OUTPUT_PROPERTIES,
},
progress_ms: { type: 'number', description: 'Progress in milliseconds', optional: true },
currently_playing_type: { type: 'string', description: 'Type of content playing' },
shuffle_state: { type: 'boolean', description: 'Whether shuffle is enabled' },
repeat_state: { type: 'string', description: 'Repeat mode (off, track, context)' },
track: {
type: 'object',
description: 'Currently playing track',
optional: true,
properties: PLAYBACK_TRACK_OUTPUT_PROPERTIES,
},
},
}
+88
View File
@@ -0,0 +1,88 @@
import type { SpotifyGetPlaylistParams, SpotifyGetPlaylistResponse } from '@/tools/spotify/types'
import { PLAYLIST_OWNER_OUTPUT_PROPERTIES } from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyGetPlaylistTool: ToolConfig<
SpotifyGetPlaylistParams,
SpotifyGetPlaylistResponse
> = {
id: 'spotify_get_playlist',
name: 'Spotify Get Playlist',
description: 'Get detailed information about a playlist on Spotify by its ID.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
},
params: {
playlistId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify ID of the playlist',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code for track availability (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
let url = `https://api.spotify.com/v1/playlists/${params.playlistId}`
if (params.market) {
url += `?market=${params.market}`
}
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetPlaylistResponse> => {
const playlist = await response.json()
return {
success: true,
output: {
id: playlist.id,
name: playlist.name,
description: playlist.description,
public: playlist.public,
collaborative: playlist.collaborative,
owner: {
id: playlist.owner?.id || '',
display_name: playlist.owner?.display_name || '',
},
image_url: playlist.images?.[0]?.url || null,
total_tracks: playlist.tracks?.total || 0,
snapshot_id: playlist.snapshot_id,
external_url: playlist.external_urls?.spotify || '',
},
}
},
outputs: {
id: { type: 'string', description: 'Spotify playlist ID' },
name: { type: 'string', description: 'Playlist name' },
description: { type: 'string', description: 'Playlist description', optional: true },
public: { type: 'boolean', description: 'Whether the playlist is public' },
collaborative: { type: 'boolean', description: 'Whether the playlist is collaborative' },
owner: {
type: 'object',
description: 'Playlist owner information',
properties: PLAYLIST_OWNER_OUTPUT_PROPERTIES,
},
image_url: { type: 'string', description: 'Playlist cover image URL', optional: true },
total_tracks: { type: 'number', description: 'Total number of tracks' },
snapshot_id: { type: 'string', description: 'Playlist snapshot ID for versioning' },
external_url: { type: 'string', description: 'Spotify URL' },
},
}
@@ -0,0 +1,67 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetPlaylistCoverParams {
accessToken: string
playlistId: string
}
interface SpotifyGetPlaylistCoverResponse extends ToolResponse {
output: {
images: Array<{
url: string
width: number | null
height: number | null
}>
}
}
export const spotifyGetPlaylistCoverTool: ToolConfig<
SpotifyGetPlaylistCoverParams,
SpotifyGetPlaylistCoverResponse
> = {
id: 'spotify_get_playlist_cover',
name: 'Spotify Get Playlist Cover',
description: "Get a playlist's cover image.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['playlist-read-private'],
},
params: {
playlistId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify playlist ID',
},
},
request: {
url: (params) => `https://api.spotify.com/v1/playlists/${params.playlistId}/images`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyGetPlaylistCoverResponse> => {
const images = await response.json()
return {
success: true,
output: {
images: (images || []).map((img: any) => ({
url: img.url,
width: img.width || null,
height: img.height || null,
})),
},
}
},
outputs: {
images: { type: 'json', description: 'List of cover images' },
},
}
@@ -0,0 +1,121 @@
import type {
SpotifyGetPlaylistTracksParams,
SpotifyGetPlaylistTracksResponse,
} from '@/tools/spotify/types'
import { TRACK_LIST_OUTPUT_PROPERTIES } from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyGetPlaylistTracksTool: ToolConfig<
SpotifyGetPlaylistTracksParams,
SpotifyGetPlaylistTracksResponse
> = {
id: 'spotify_get_playlist_tracks',
name: 'Spotify Get Playlist Tracks',
description: 'Get the tracks in a Spotify playlist.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
},
params: {
playlistId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify ID of the playlist',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 50,
description: 'Maximum number of tracks to return (1-100)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 0,
description: 'Index of the first track to return for pagination',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code for track availability (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
const limit = Math.min(Math.max(params.limit || 50, 1), 100)
const offset = params.offset || 0
let url = `https://api.spotify.com/v1/playlists/${params.playlistId}/tracks?limit=${limit}&offset=${offset}`
if (params.market) {
url += `&market=${params.market}`
}
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetPlaylistTracksResponse> => {
const data = await response.json()
const tracks = (data.items || [])
.filter((item: any) => item.track !== null)
.map((item: any) => ({
added_at: item.added_at,
added_by: item.added_by?.id || '',
track: {
id: item.track.id,
name: item.track.name,
artists: item.track.artists?.map((a: any) => ({ id: a.id, name: a.name })) || [],
album: {
id: item.track.album?.id || '',
name: item.track.album?.name || '',
image_url: item.track.album?.images?.[0]?.url || null,
},
duration_ms: item.track.duration_ms,
popularity: item.track.popularity,
external_url: item.track.external_urls?.spotify || '',
},
}))
return {
success: true,
output: {
tracks,
total: data.total || tracks.length,
next: data.next || null,
},
}
},
outputs: {
tracks: {
type: 'array',
description: 'List of tracks in the playlist',
items: {
type: 'object',
properties: {
added_at: { type: 'string', description: 'When the track was added' },
added_by: { type: 'string', description: 'User ID who added the track' },
track: {
type: 'object',
description: 'Track information',
properties: TRACK_LIST_OUTPUT_PROPERTIES,
},
},
},
},
total: { type: 'number', description: 'Total number of tracks in playlist' },
next: { type: 'string', description: 'URL for next page of results', optional: true },
},
}
+95
View File
@@ -0,0 +1,95 @@
import { PLAYBACK_TRACK_OUTPUT_PROPERTIES } from '@/tools/spotify/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetQueueParams {
accessToken: string
}
interface SpotifyGetQueueResponse extends ToolResponse {
output: {
currently_playing: {
id: string
name: string
artists: Array<{ id: string; name: string }>
album: {
id: string
name: string
image_url: string | null
}
duration_ms: number
} | null
queue: Array<{
id: string
name: string
artists: Array<{ id: string; name: string }>
album: {
id: string
name: string
image_url: string | null
}
duration_ms: number
}>
}
}
export const spotifyGetQueueTool: ToolConfig<SpotifyGetQueueParams, SpotifyGetQueueResponse> = {
id: 'spotify_get_queue',
name: 'Spotify Get Queue',
description: "Get the user's playback queue.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-playback-state'],
},
params: {},
request: {
url: () => 'https://api.spotify.com/v1/me/player/queue',
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetQueueResponse> => {
const data = await response.json()
const formatTrack = (track: any) => ({
id: track.id,
name: track.name,
artists: track.artists?.map((a: any) => ({ id: a.id, name: a.name })) || [],
album: {
id: track.album?.id || '',
name: track.album?.name || '',
image_url: track.album?.images?.[0]?.url || null,
},
duration_ms: track.duration_ms,
})
return {
success: true,
output: {
currently_playing: data.currently_playing ? formatTrack(data.currently_playing) : null,
queue: (data.queue || []).map(formatTrack),
},
}
},
outputs: {
currently_playing: {
type: 'object',
description: 'Currently playing track',
optional: true,
properties: PLAYBACK_TRACK_OUTPUT_PROPERTIES,
},
queue: {
type: 'array',
description: 'Upcoming tracks in queue',
items: { type: 'object', properties: PLAYBACK_TRACK_OUTPUT_PROPERTIES },
},
},
}
@@ -0,0 +1,110 @@
import type {
SpotifyGetRecentlyPlayedParams,
SpotifyGetRecentlyPlayedResponse,
} from '@/tools/spotify/types'
import { CURRENTLY_PLAYING_TRACK_OUTPUT_PROPERTIES } from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyGetRecentlyPlayedTool: ToolConfig<
SpotifyGetRecentlyPlayedParams,
SpotifyGetRecentlyPlayedResponse
> = {
id: 'spotify_get_recently_played',
name: 'Spotify Get Recently Played',
description: "Get the user's recently played tracks.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-recently-played'],
},
params: {
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 20,
description: 'Number of tracks to return (1-50)',
},
after: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Unix timestamp in milliseconds. Returns items after this cursor.',
},
before: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Unix timestamp in milliseconds. Returns items before this cursor.',
},
},
request: {
url: (params) => {
const limit = Math.min(Math.max(params.limit || 20, 1), 50)
let url = `https://api.spotify.com/v1/me/player/recently-played?limit=${limit}`
if (params.after) {
url += `&after=${params.after}`
}
if (params.before) {
url += `&before=${params.before}`
}
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetRecentlyPlayedResponse> => {
const data = await response.json()
const items = (data.items || []).map((item: any) => ({
played_at: item.played_at,
track: {
id: item.track.id,
name: item.track.name,
artists: item.track.artists?.map((a: any) => ({ id: a.id, name: a.name })) || [],
album: {
id: item.track.album?.id || '',
name: item.track.album?.name || '',
image_url: item.track.album?.images?.[0]?.url || null,
},
duration_ms: item.track.duration_ms,
external_url: item.track.external_urls?.spotify || '',
},
}))
return {
success: true,
output: {
items,
next: data.next || null,
},
}
},
outputs: {
items: {
type: 'array',
description: 'Recently played tracks',
items: {
type: 'object',
properties: {
played_at: { type: 'string', description: 'When the track was played' },
track: {
type: 'object',
description: 'Track information',
properties: CURRENTLY_PLAYING_TRACK_OUTPUT_PROPERTIES,
},
},
},
},
next: { type: 'string', description: 'URL for next page', optional: true },
},
}
+136
View File
@@ -0,0 +1,136 @@
import { SIMPLIFIED_ARTIST_OUTPUT_PROPERTIES } from '@/tools/spotify/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetSavedAlbumsParams {
accessToken: string
limit?: number
offset?: number
market?: string
}
interface SpotifyGetSavedAlbumsResponse extends ToolResponse {
output: {
albums: Array<{
added_at: string
album: {
id: string
name: string
artists: Array<{ id: string; name: string }>
total_tracks: number
release_date: string
image_url: string | null
external_url: string
}
}>
total: number
next: string | null
}
}
export const spotifyGetSavedAlbumsTool: ToolConfig<
SpotifyGetSavedAlbumsParams,
SpotifyGetSavedAlbumsResponse
> = {
id: 'spotify_get_saved_albums',
name: 'Spotify Get Saved Albums',
description: "Get the user's saved albums.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-read'],
},
params: {
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 20,
description: 'Number of albums to return (1-50)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 0,
description: 'Index of first album to return for pagination',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
const limit = Math.min(Math.max(params.limit || 20, 1), 50)
const offset = params.offset || 0
let url = `https://api.spotify.com/v1/me/albums?limit=${limit}&offset=${offset}`
if (params.market) url += `&market=${params.market}`
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyGetSavedAlbumsResponse> => {
const data = await response.json()
return {
success: true,
output: {
albums: (data.items || []).map((item: any) => ({
added_at: item.added_at,
album: {
id: item.album.id,
name: item.album.name,
artists: item.album.artists?.map((a: any) => ({ id: a.id, name: a.name })) || [],
total_tracks: item.album.total_tracks,
release_date: item.album.release_date,
image_url: item.album.images?.[0]?.url || null,
external_url: item.album.external_urls?.spotify || '',
},
})),
total: data.total || 0,
next: data.next || null,
},
}
},
outputs: {
albums: {
type: 'array',
description: 'List of saved albums',
items: {
type: 'object',
properties: {
added_at: { type: 'string', description: 'When the album was saved' },
album: {
type: 'object',
description: 'Album information',
properties: {
id: { type: 'string', description: 'Spotify album ID' },
name: { type: 'string', description: 'Album name' },
artists: {
type: 'array',
description: 'List of artists',
items: { type: 'object', properties: SIMPLIFIED_ARTIST_OUTPUT_PROPERTIES },
},
total_tracks: { type: 'number', description: 'Total number of tracks' },
release_date: { type: 'string', description: 'Release date' },
image_url: { type: 'string', description: 'Album cover image URL', optional: true },
external_url: { type: 'string', description: 'Spotify URL' },
},
},
},
},
},
total: { type: 'number', description: 'Total saved albums' },
next: { type: 'string', description: 'URL for next page', optional: true },
},
}
@@ -0,0 +1,99 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetSavedAudiobooksParams {
accessToken: string
limit?: number
offset?: number
}
interface SpotifyGetSavedAudiobooksResponse extends ToolResponse {
output: {
audiobooks: Array<{
added_at: string
audiobook: {
id: string
name: string
authors: Array<{ name: string }>
total_chapters: number
image_url: string | null
external_url: string
}
}>
total: number
next: string | null
}
}
export const spotifyGetSavedAudiobooksTool: ToolConfig<
SpotifyGetSavedAudiobooksParams,
SpotifyGetSavedAudiobooksResponse
> = {
id: 'spotify_get_saved_audiobooks',
name: 'Spotify Get Saved Audiobooks',
description: "Get the user's saved audiobooks.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-read'],
},
params: {
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 20,
description: 'Number of audiobooks to return (1-50)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 0,
description: 'Index of first audiobook to return for pagination',
},
},
request: {
url: (params) => {
const limit = Math.min(Math.max(params.limit || 20, 1), 50)
const offset = params.offset || 0
return `https://api.spotify.com/v1/me/audiobooks?limit=${limit}&offset=${offset}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyGetSavedAudiobooksResponse> => {
const data = await response.json()
return {
success: true,
output: {
audiobooks: (data.items || []).map((item: any) => ({
added_at: item.added_at,
audiobook: {
id: item.audiobook?.id || item.id,
name: item.audiobook?.name || item.name,
authors: item.audiobook?.authors || item.authors || [],
total_chapters: item.audiobook?.total_chapters || item.total_chapters || 0,
image_url: item.audiobook?.images?.[0]?.url || item.images?.[0]?.url || null,
external_url:
item.audiobook?.external_urls?.spotify || item.external_urls?.spotify || '',
},
})),
total: data.total || 0,
next: data.next || null,
},
}
},
outputs: {
audiobooks: { type: 'json', description: 'List of saved audiobooks' },
total: { type: 'number', description: 'Total saved audiobooks' },
next: { type: 'string', description: 'URL for next page', optional: true },
},
}
@@ -0,0 +1,109 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetSavedEpisodesParams {
accessToken: string
limit?: number
offset?: number
market?: string
}
interface SpotifyGetSavedEpisodesResponse extends ToolResponse {
output: {
episodes: Array<{
added_at: string
episode: {
id: string
name: string
duration_ms: number
release_date: string
show: { id: string; name: string }
image_url: string | null
external_url: string
}
}>
total: number
next: string | null
}
}
export const spotifyGetSavedEpisodesTool: ToolConfig<
SpotifyGetSavedEpisodesParams,
SpotifyGetSavedEpisodesResponse
> = {
id: 'spotify_get_saved_episodes',
name: 'Spotify Get Saved Episodes',
description: "Get the user's saved podcast episodes.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-read', 'user-read-playback-position'],
},
params: {
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 20,
description: 'Number of episodes to return (1-50)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 0,
description: 'Index of first episode to return for pagination',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
const limit = Math.min(Math.max(params.limit || 20, 1), 50)
const offset = params.offset || 0
let url = `https://api.spotify.com/v1/me/episodes?limit=${limit}&offset=${offset}`
if (params.market) url += `&market=${params.market}`
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyGetSavedEpisodesResponse> => {
const data = await response.json()
return {
success: true,
output: {
episodes: (data.items || []).map((item: any) => ({
added_at: item.added_at,
episode: {
id: item.episode.id,
name: item.episode.name,
duration_ms: item.episode.duration_ms || 0,
release_date: item.episode.release_date || '',
show: { id: item.episode.show?.id || '', name: item.episode.show?.name || '' },
image_url: item.episode.images?.[0]?.url || null,
external_url: item.episode.external_urls?.spotify || '',
},
})),
total: data.total || 0,
next: data.next || null,
},
}
},
outputs: {
episodes: { type: 'json', description: 'List of saved episodes' },
total: { type: 'number', description: 'Total saved episodes' },
next: { type: 'string', description: 'URL for next page', optional: true },
},
}
+98
View File
@@ -0,0 +1,98 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetSavedShowsParams {
accessToken: string
limit?: number
offset?: number
}
interface SpotifyGetSavedShowsResponse extends ToolResponse {
output: {
shows: Array<{
added_at: string
show: {
id: string
name: string
publisher: string
total_episodes: number
image_url: string | null
external_url: string
}
}>
total: number
next: string | null
}
}
export const spotifyGetSavedShowsTool: ToolConfig<
SpotifyGetSavedShowsParams,
SpotifyGetSavedShowsResponse
> = {
id: 'spotify_get_saved_shows',
name: 'Spotify Get Saved Shows',
description: "Get the user's saved podcast shows.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-read'],
},
params: {
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 20,
description: 'Number of shows to return (1-50)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 0,
description: 'Index of first show to return for pagination',
},
},
request: {
url: (params) => {
const limit = Math.min(Math.max(params.limit || 20, 1), 50)
const offset = params.offset || 0
return `https://api.spotify.com/v1/me/shows?limit=${limit}&offset=${offset}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyGetSavedShowsResponse> => {
const data = await response.json()
return {
success: true,
output: {
shows: (data.items || []).map((item: any) => ({
added_at: item.added_at,
show: {
id: item.show.id,
name: item.show.name,
publisher: item.show.publisher || '',
total_episodes: item.show.total_episodes || 0,
image_url: item.show.images?.[0]?.url || null,
external_url: item.show.external_urls?.spotify || '',
},
})),
total: data.total || 0,
next: data.next || null,
},
}
},
outputs: {
shows: { type: 'json', description: 'List of saved shows' },
total: { type: 'number', description: 'Total saved shows' },
next: { type: 'string', description: 'URL for next page', optional: true },
},
}
+112
View File
@@ -0,0 +1,112 @@
import type {
SpotifyGetSavedTracksParams,
SpotifyGetSavedTracksResponse,
} from '@/tools/spotify/types'
import { TRACK_LIST_OUTPUT_PROPERTIES } from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyGetSavedTracksTool: ToolConfig<
SpotifyGetSavedTracksParams,
SpotifyGetSavedTracksResponse
> = {
id: 'spotify_get_saved_tracks',
name: 'Spotify Get Saved Tracks',
description: "Get the current user's saved/liked tracks from their library.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-read'],
},
params: {
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 20,
description: 'Number of tracks to return (1-50)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 0,
description: 'Index of the first track to return for pagination',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
const limit = Math.min(Math.max(params.limit || 20, 1), 50)
const offset = params.offset || 0
let url = `https://api.spotify.com/v1/me/tracks?limit=${limit}&offset=${offset}`
if (params.market) {
url += `&market=${params.market}`
}
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetSavedTracksResponse> => {
const data = await response.json()
const tracks = (data.items || []).map((item: any) => ({
added_at: item.added_at,
track: {
id: item.track.id,
name: item.track.name,
artists: item.track.artists?.map((a: any) => ({ id: a.id, name: a.name })) || [],
album: {
id: item.track.album?.id || '',
name: item.track.album?.name || '',
image_url: item.track.album?.images?.[0]?.url || null,
},
duration_ms: item.track.duration_ms,
popularity: item.track.popularity,
external_url: item.track.external_urls?.spotify || '',
},
}))
return {
success: true,
output: {
tracks,
total: data.total || tracks.length,
next: data.next || null,
},
}
},
outputs: {
tracks: {
type: 'array',
description: "User's saved tracks",
items: {
type: 'object',
properties: {
added_at: { type: 'string', description: 'When the track was saved' },
track: {
type: 'object',
description: 'Track information',
properties: TRACK_LIST_OUTPUT_PROPERTIES,
},
},
},
},
total: { type: 'number', description: 'Total number of saved tracks' },
next: { type: 'string', description: 'URL for next page', optional: true },
},
}
+91
View File
@@ -0,0 +1,91 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetShowParams {
accessToken: string
showId: string
market?: string
}
interface SpotifyGetShowResponse extends ToolResponse {
output: {
id: string
name: string
description: string
publisher: string
total_episodes: number
explicit: boolean
languages: string[]
image_url: string | null
external_url: string
}
}
export const spotifyGetShowTool: ToolConfig<SpotifyGetShowParams, SpotifyGetShowResponse> = {
id: 'spotify_get_show',
name: 'Spotify Get Show',
description: 'Get details for a podcast show.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-playback-position'],
},
params: {
showId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify show ID',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
let url = `https://api.spotify.com/v1/shows/${params.showId}`
if (params.market) url += `?market=${params.market}`
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyGetShowResponse> => {
const show = await response.json()
return {
success: true,
output: {
id: show.id,
name: show.name,
description: show.description || '',
publisher: show.publisher || '',
total_episodes: show.total_episodes || 0,
explicit: show.explicit || false,
languages: show.languages || [],
image_url: show.images?.[0]?.url || null,
external_url: show.external_urls?.spotify || '',
},
}
},
outputs: {
id: { type: 'string', description: 'Show ID' },
name: { type: 'string', description: 'Show name' },
description: { type: 'string', description: 'Show description' },
publisher: { type: 'string', description: 'Publisher name' },
total_episodes: { type: 'number', description: 'Total episodes' },
explicit: { type: 'boolean', description: 'Contains explicit content' },
languages: { type: 'json', description: 'Languages' },
image_url: { type: 'string', description: 'Cover image URL' },
external_url: { type: 'string', description: 'Spotify URL' },
},
}
+110
View File
@@ -0,0 +1,110 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetShowEpisodesParams {
accessToken: string
showId: string
limit?: number
offset?: number
market?: string
}
interface SpotifyGetShowEpisodesResponse extends ToolResponse {
output: {
episodes: Array<{
id: string
name: string
description: string
duration_ms: number
release_date: string
image_url: string | null
external_url: string
}>
total: number
next: string | null
}
}
export const spotifyGetShowEpisodesTool: ToolConfig<
SpotifyGetShowEpisodesParams,
SpotifyGetShowEpisodesResponse
> = {
id: 'spotify_get_show_episodes',
name: 'Spotify Get Show Episodes',
description: 'Get episodes from a podcast show.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-playback-position'],
},
params: {
showId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify show ID',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 20,
description: 'Number of episodes to return (1-50)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 0,
description: 'Index of first episode to return for pagination',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
const limit = Math.min(Math.max(params.limit || 20, 1), 50)
const offset = params.offset || 0
let url = `https://api.spotify.com/v1/shows/${params.showId}/episodes?limit=${limit}&offset=${offset}`
if (params.market) url += `&market=${params.market}`
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyGetShowEpisodesResponse> => {
const data = await response.json()
return {
success: true,
output: {
episodes: (data.items || []).map((ep: any) => ({
id: ep.id,
name: ep.name,
description: ep.description || '',
duration_ms: ep.duration_ms || 0,
release_date: ep.release_date || '',
image_url: ep.images?.[0]?.url || null,
external_url: ep.external_urls?.spotify || '',
})),
total: data.total || 0,
next: data.next || null,
},
}
},
outputs: {
episodes: { type: 'json', description: 'List of episodes' },
total: { type: 'number', description: 'Total episodes' },
next: { type: 'string', description: 'URL for next page', optional: true },
},
}
+86
View File
@@ -0,0 +1,86 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetShowsParams {
accessToken: string
showIds: string
market?: string
}
interface SpotifyGetShowsResponse extends ToolResponse {
output: {
shows: Array<{
id: string
name: string
publisher: string
total_episodes: number
image_url: string | null
external_url: string
}>
}
}
export const spotifyGetShowsTool: ToolConfig<SpotifyGetShowsParams, SpotifyGetShowsResponse> = {
id: 'spotify_get_shows',
name: 'Spotify Get Multiple Shows',
description: 'Get details for multiple podcast shows.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-playback-position'],
},
params: {
showIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated show IDs (max 50)',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
const ids = params.showIds
.split(',')
.map((id) => id.trim())
.slice(0, 50)
.join(',')
let url = `https://api.spotify.com/v1/shows?ids=${ids}`
if (params.market) url += `&market=${params.market}`
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyGetShowsResponse> => {
const data = await response.json()
return {
success: true,
output: {
shows: (data.shows || []).map((show: any) => ({
id: show.id,
name: show.name,
publisher: show.publisher || '',
total_episodes: show.total_episodes || 0,
image_url: show.images?.[0]?.url || null,
external_url: show.external_urls?.spotify || '',
})),
},
}
},
outputs: {
shows: { type: 'json', description: 'List of shows' },
},
}
+100
View File
@@ -0,0 +1,100 @@
import type { SpotifyGetTopArtistsResponse, SpotifyGetTopItemsParams } from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyGetTopArtistsTool: ToolConfig<
SpotifyGetTopItemsParams,
SpotifyGetTopArtistsResponse
> = {
id: 'spotify_get_top_artists',
name: 'Spotify Get Top Artists',
description: "Get the current user's top artists based on listening history.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-top-read'],
},
params: {
time_range: {
type: 'string',
required: false,
visibility: 'user-or-llm',
default: 'medium_term',
description: 'Time range: short_term (~4 weeks), medium_term (~6 months), long_term (years)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 20,
description: 'Number of artists to return (1-50)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 0,
description: 'Index of the first artist to return for pagination',
},
},
request: {
url: (params) => {
const timeRange = params.time_range || 'medium_term'
const limit = Math.min(Math.max(params.limit || 20, 1), 50)
const offset = params.offset || 0
return `https://api.spotify.com/v1/me/top/artists?time_range=${timeRange}&limit=${limit}&offset=${offset}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetTopArtistsResponse> => {
const data = await response.json()
const artists = (data.items || []).map((artist: any) => ({
id: artist.id,
name: artist.name,
genres: artist.genres || [],
popularity: artist.popularity,
followers: artist.followers?.total || 0,
image_url: artist.images?.[0]?.url || null,
external_url: artist.external_urls?.spotify || '',
}))
return {
success: true,
output: {
artists,
total: data.total || artists.length,
next: data.next || null,
},
}
},
outputs: {
artists: {
type: 'array',
description: "User's top artists",
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Spotify artist ID' },
name: { type: 'string', description: 'Artist name' },
genres: { type: 'array', description: 'List of genres' },
popularity: { type: 'number', description: 'Popularity score' },
followers: { type: 'number', description: 'Number of followers' },
image_url: { type: 'string', description: 'Artist image URL' },
external_url: { type: 'string', description: 'Spotify URL' },
},
},
},
total: { type: 'number', description: 'Total number of top artists' },
next: { type: 'string', description: 'URL for next page', optional: true },
},
}
+116
View File
@@ -0,0 +1,116 @@
import type { SpotifyGetTopItemsParams, SpotifyGetTopTracksResponse } from '@/tools/spotify/types'
import {
SIMPLIFIED_ALBUM_OUTPUT_PROPERTIES,
SIMPLIFIED_ARTIST_OUTPUT_PROPERTIES,
} from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyGetTopTracksTool: ToolConfig<
SpotifyGetTopItemsParams,
SpotifyGetTopTracksResponse
> = {
id: 'spotify_get_top_tracks',
name: 'Spotify Get Top Tracks',
description: "Get the current user's top tracks based on listening history.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-top-read'],
},
params: {
time_range: {
type: 'string',
required: false,
visibility: 'user-or-llm',
default: 'medium_term',
description: 'Time range: short_term (~4 weeks), medium_term (~6 months), long_term (years)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 20,
description: 'Number of tracks to return (1-50)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 0,
description: 'Index of the first track to return for pagination',
},
},
request: {
url: (params) => {
const timeRange = params.time_range || 'medium_term'
const limit = Math.min(Math.max(params.limit || 20, 1), 50)
const offset = params.offset || 0
return `https://api.spotify.com/v1/me/top/tracks?time_range=${timeRange}&limit=${limit}&offset=${offset}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetTopTracksResponse> => {
const data = await response.json()
const tracks = (data.items || []).map((track: any) => ({
id: track.id,
name: track.name,
artists: track.artists?.map((a: any) => ({ id: a.id, name: a.name })) || [],
album: {
id: track.album?.id || '',
name: track.album?.name || '',
image_url: track.album?.images?.[0]?.url || null,
},
duration_ms: track.duration_ms,
popularity: track.popularity,
external_url: track.external_urls?.spotify || '',
}))
return {
success: true,
output: {
tracks,
total: data.total || tracks.length,
next: data.next || null,
},
}
},
outputs: {
tracks: {
type: 'array',
description: "User's top tracks",
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Spotify track ID' },
name: { type: 'string', description: 'Track name' },
artists: {
type: 'array',
description: 'List of artists',
items: { type: 'object', properties: SIMPLIFIED_ARTIST_OUTPUT_PROPERTIES },
},
album: {
type: 'object',
description: 'Album information',
properties: SIMPLIFIED_ALBUM_OUTPUT_PROPERTIES,
},
duration_ms: { type: 'number', description: 'Duration in milliseconds' },
popularity: { type: 'number', description: 'Popularity score' },
external_url: { type: 'string', description: 'Spotify URL' },
},
},
},
total: { type: 'number', description: 'Total number of top tracks' },
next: { type: 'string', description: 'URL for next page', optional: true },
},
}
+93
View File
@@ -0,0 +1,93 @@
import type { SpotifyGetTrackParams, SpotifyGetTrackResponse } from '@/tools/spotify/types'
import {
SIMPLIFIED_ALBUM_OUTPUT_PROPERTIES,
SIMPLIFIED_ARTIST_OUTPUT_PROPERTIES,
} from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyGetTrackTool: ToolConfig<SpotifyGetTrackParams, SpotifyGetTrackResponse> = {
id: 'spotify_get_track',
name: 'Spotify Get Track',
description: 'Get detailed information about a specific track on Spotify by its ID.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
},
params: {
trackId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify ID of the track',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code for track availability (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
let url = `https://api.spotify.com/v1/tracks/${params.trackId}`
if (params.market) {
url += `?market=${params.market}`
}
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetTrackResponse> => {
const track = await response.json()
return {
success: true,
output: {
id: track.id,
name: track.name,
artists: track.artists?.map((a: any) => ({ id: a.id, name: a.name })) || [],
album: {
id: track.album?.id || '',
name: track.album?.name || '',
image_url: track.album?.images?.[0]?.url || null,
},
duration_ms: track.duration_ms,
explicit: track.explicit,
popularity: track.popularity,
preview_url: track.preview_url,
external_url: track.external_urls?.spotify || '',
uri: track.uri,
},
}
},
outputs: {
id: { type: 'string', description: 'Spotify track ID' },
name: { type: 'string', description: 'Track name' },
artists: {
type: 'array',
description: 'List of artists',
items: { type: 'object', properties: SIMPLIFIED_ARTIST_OUTPUT_PROPERTIES },
},
album: {
type: 'object',
description: 'Album information',
properties: SIMPLIFIED_ALBUM_OUTPUT_PROPERTIES,
},
duration_ms: { type: 'number', description: 'Track duration in milliseconds' },
explicit: { type: 'boolean', description: 'Whether the track has explicit content' },
popularity: { type: 'number', description: 'Popularity score (0-100)' },
preview_url: { type: 'string', description: 'URL to 30-second preview', optional: true },
external_url: { type: 'string', description: 'Spotify URL' },
uri: { type: 'string', description: 'Spotify URI for the track' },
},
}
+106
View File
@@ -0,0 +1,106 @@
import type { SpotifyGetTracksParams, SpotifyGetTracksResponse } from '@/tools/spotify/types'
import {
SIMPLIFIED_ALBUM_OUTPUT_PROPERTIES,
SIMPLIFIED_ARTIST_OUTPUT_PROPERTIES,
} from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyGetTracksTool: ToolConfig<SpotifyGetTracksParams, SpotifyGetTracksResponse> = {
id: 'spotify_get_tracks',
name: 'Spotify Get Multiple Tracks',
description: 'Get detailed information about multiple tracks on Spotify by their IDs (up to 50).',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
},
params: {
trackIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated list of Spotify track IDs (max 50)',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code for track availability (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
let url = `https://api.spotify.com/v1/tracks?ids=${encodeURIComponent(params.trackIds)}`
if (params.market) {
url += `&market=${params.market}`
}
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetTracksResponse> => {
const data = await response.json()
const tracks = (data.tracks || [])
.filter((t: any) => t !== null)
.map((track: any) => ({
id: track.id,
name: track.name,
artists: track.artists?.map((a: any) => ({ id: a.id, name: a.name })) || [],
album: {
id: track.album?.id || '',
name: track.album?.name || '',
image_url: track.album?.images?.[0]?.url || null,
},
duration_ms: track.duration_ms,
explicit: track.explicit,
popularity: track.popularity,
preview_url: track.preview_url,
external_url: track.external_urls?.spotify || '',
}))
return {
success: true,
output: {
tracks,
},
}
},
outputs: {
tracks: {
type: 'array',
description: 'List of tracks',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Spotify track ID' },
name: { type: 'string', description: 'Track name' },
artists: {
type: 'array',
description: 'List of artists',
items: { type: 'object', properties: SIMPLIFIED_ARTIST_OUTPUT_PROPERTIES },
},
album: {
type: 'object',
description: 'Album information',
properties: SIMPLIFIED_ALBUM_OUTPUT_PROPERTIES,
},
duration_ms: { type: 'number', description: 'Track duration in milliseconds' },
explicit: { type: 'boolean', description: 'Whether the track has explicit content' },
popularity: { type: 'number', description: 'Popularity score (0-100)' },
preview_url: { type: 'string', description: 'URL to 30-second preview', optional: true },
external_url: { type: 'string', description: 'Spotify URL' },
},
},
},
},
}
@@ -0,0 +1,99 @@
import type {
SpotifyGetUserPlaylistsParams,
SpotifyGetUserPlaylistsResponse,
} from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyGetUserPlaylistsTool: ToolConfig<
SpotifyGetUserPlaylistsParams,
SpotifyGetUserPlaylistsResponse
> = {
id: 'spotify_get_user_playlists',
name: 'Spotify Get User Playlists',
description: "Get the current user's playlists on Spotify.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['playlist-read-private', 'playlist-read-collaborative'],
},
params: {
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 20,
description: 'Maximum number of playlists to return (1-50)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 0,
description: 'Index of the first playlist to return for pagination',
},
},
request: {
url: (params) => {
const limit = Math.min(Math.max(params.limit || 20, 1), 50)
const offset = params.offset || 0
return `https://api.spotify.com/v1/me/playlists?limit=${limit}&offset=${offset}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifyGetUserPlaylistsResponse> => {
const data = await response.json()
const playlists = (data.items || []).map((playlist: any) => ({
id: playlist.id,
name: playlist.name,
description: playlist.description,
public: playlist.public,
collaborative: playlist.collaborative,
owner: playlist.owner?.display_name || '',
total_tracks: playlist.tracks?.total || 0,
image_url: playlist.images?.[0]?.url || null,
external_url: playlist.external_urls?.spotify || '',
}))
return {
success: true,
output: {
playlists,
total: data.total || playlists.length,
next: data.next || null,
},
}
},
outputs: {
playlists: {
type: 'array',
description: "User's playlists",
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Spotify playlist ID' },
name: { type: 'string', description: 'Playlist name' },
description: { type: 'string', description: 'Playlist description' },
public: { type: 'boolean', description: 'Whether public' },
collaborative: { type: 'boolean', description: 'Whether collaborative' },
owner: { type: 'string', description: 'Owner display name' },
total_tracks: { type: 'number', description: 'Number of tracks' },
image_url: { type: 'string', description: 'Cover image URL' },
external_url: { type: 'string', description: 'Spotify URL' },
},
},
},
total: { type: 'number', description: 'Total number of playlists' },
next: { type: 'string', description: 'URL for next page', optional: true },
},
}
@@ -0,0 +1,71 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyGetUserProfileParams {
accessToken: string
userId: string
}
interface SpotifyGetUserProfileResponse extends ToolResponse {
output: {
id: string
display_name: string | null
followers: number
image_url: string | null
external_url: string
}
}
export const spotifyGetUserProfileTool: ToolConfig<
SpotifyGetUserProfileParams,
SpotifyGetUserProfileResponse
> = {
id: 'spotify_get_user_profile',
name: 'Spotify Get User Profile',
description: "Get a user's public profile.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-read-private'],
},
params: {
userId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify user ID',
},
},
request: {
url: (params) => `https://api.spotify.com/v1/users/${params.userId}`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response): Promise<SpotifyGetUserProfileResponse> => {
const user = await response.json()
return {
success: true,
output: {
id: user.id,
display_name: user.display_name || null,
followers: user.followers?.total || 0,
image_url: user.images?.[0]?.url || null,
external_url: user.external_urls?.spotify || '',
},
}
},
outputs: {
id: { type: 'string', description: 'User ID' },
display_name: { type: 'string', description: 'Display name' },
followers: { type: 'number', description: 'Number of followers' },
image_url: { type: 'string', description: 'Profile image URL' },
external_url: { type: 'string', description: 'Spotify URL' },
},
}
+92
View File
@@ -0,0 +1,92 @@
// Search & Discovery
export { spotifyAddPlaylistCoverTool } from './add_playlist_cover'
// Player Controls
export { spotifyAddToQueueTool } from './add_to_queue'
export { spotifyAddTracksToPlaylistTool } from './add_tracks_to_playlist'
export { spotifyCheckFollowingTool } from './check_following'
export { spotifyCheckPlaylistFollowersTool } from './check_playlist_followers'
export { spotifyCheckSavedAlbumsTool } from './check_saved_albums'
export { spotifyCheckSavedAudiobooksTool } from './check_saved_audiobooks'
export { spotifyCheckSavedEpisodesTool } from './check_saved_episodes'
export { spotifyCheckSavedShowsTool } from './check_saved_shows'
export { spotifyCheckSavedTracksTool } from './check_saved_tracks'
export { spotifyCreatePlaylistTool } from './create_playlist'
export { spotifyFollowArtistsTool } from './follow_artists'
export { spotifyFollowPlaylistTool } from './follow_playlist'
// Albums
export { spotifyGetAlbumTool } from './get_album'
export { spotifyGetAlbumTracksTool } from './get_album_tracks'
export { spotifyGetAlbumsTool } from './get_albums'
// Artists
export { spotifyGetArtistTool } from './get_artist'
export { spotifyGetArtistAlbumsTool } from './get_artist_albums'
export { spotifyGetArtistTopTracksTool } from './get_artist_top_tracks'
export { spotifyGetArtistsTool } from './get_artists'
// Audiobooks
export { spotifyGetAudiobookTool } from './get_audiobook'
export { spotifyGetAudiobookChaptersTool } from './get_audiobook_chapters'
export { spotifyGetAudiobooksTool } from './get_audiobooks'
export { spotifyGetCategoriesTool } from './get_categories'
// User Profile & Library
export { spotifyGetCurrentUserTool } from './get_current_user'
export { spotifyGetCurrentlyPlayingTool } from './get_currently_playing'
export { spotifyGetDevicesTool } from './get_devices'
// Episodes
export { spotifyGetEpisodeTool } from './get_episode'
export { spotifyGetEpisodesTool } from './get_episodes'
export { spotifyGetFollowedArtistsTool } from './get_followed_artists'
export { spotifyGetMarketsTool } from './get_markets'
// Browse
export { spotifyGetNewReleasesTool } from './get_new_releases'
// Player Controls
export { spotifyGetPlaybackStateTool } from './get_playback_state'
// Playlists
export { spotifyGetPlaylistTool } from './get_playlist'
export { spotifyGetPlaylistCoverTool } from './get_playlist_cover'
export { spotifyGetPlaylistTracksTool } from './get_playlist_tracks'
export { spotifyGetQueueTool } from './get_queue'
export { spotifyGetRecentlyPlayedTool } from './get_recently_played'
export { spotifyGetSavedAlbumsTool } from './get_saved_albums'
export { spotifyGetSavedAudiobooksTool } from './get_saved_audiobooks'
export { spotifyGetSavedEpisodesTool } from './get_saved_episodes'
export { spotifyGetSavedShowsTool } from './get_saved_shows'
export { spotifyGetSavedTracksTool } from './get_saved_tracks'
// Shows (Podcasts)
export { spotifyGetShowTool } from './get_show'
export { spotifyGetShowEpisodesTool } from './get_show_episodes'
export { spotifyGetShowsTool } from './get_shows'
export { spotifyGetTopArtistsTool } from './get_top_artists'
export { spotifyGetTopTracksTool } from './get_top_tracks'
// Tracks
export { spotifyGetTrackTool } from './get_track'
export { spotifyGetTracksTool } from './get_tracks'
export { spotifyGetUserPlaylistsTool } from './get_user_playlists'
export { spotifyGetUserProfileTool } from './get_user_profile'
export { spotifyPauseTool } from './pause'
export { spotifyPlayTool } from './play'
// Library Management
export { spotifyRemoveSavedAlbumsTool } from './remove_saved_albums'
export { spotifyRemoveSavedAudiobooksTool } from './remove_saved_audiobooks'
export { spotifyRemoveSavedEpisodesTool } from './remove_saved_episodes'
export { spotifyRemoveSavedShowsTool } from './remove_saved_shows'
export { spotifyRemoveSavedTracksTool } from './remove_saved_tracks'
export { spotifyRemoveTracksFromPlaylistTool } from './remove_tracks_from_playlist'
export { spotifyReorderPlaylistItemsTool } from './reorder_playlist_items'
export { spotifyReplacePlaylistItemsTool } from './replace_playlist_items'
export { spotifySaveAlbumsTool } from './save_albums'
export { spotifySaveAudiobooksTool } from './save_audiobooks'
export { spotifySaveEpisodesTool } from './save_episodes'
export { spotifySaveShowsTool } from './save_shows'
export { spotifySaveTracksTool } from './save_tracks'
export { spotifySearchTool } from './search'
export { spotifySeekTool } from './seek'
export { spotifySetRepeatTool } from './set_repeat'
export { spotifySetShuffleTool } from './set_shuffle'
export { spotifySetVolumeTool } from './set_volume'
export { spotifySkipNextTool } from './skip_next'
export { spotifySkipPreviousTool } from './skip_previous'
export { spotifyTransferPlaybackTool } from './transfer_playback'
export { spotifyUnfollowArtistsTool } from './unfollow_artists'
export { spotifyUnfollowPlaylistTool } from './unfollow_playlist'
export { spotifyUpdatePlaylistTool } from './update_playlist'
+52
View File
@@ -0,0 +1,52 @@
import type { SpotifyPauseParams, SpotifyPauseResponse } from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyPauseTool: ToolConfig<SpotifyPauseParams, SpotifyPauseResponse> = {
id: 'spotify_pause',
name: 'Spotify Pause',
description: 'Pause playback on Spotify.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-modify-playback-state'],
},
params: {
device_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Device ID to pause. If not provided, pauses active device.',
},
},
request: {
url: (params) => {
let url = 'https://api.spotify.com/v1/me/player/pause'
if (params.device_id) {
url += `?device_id=${params.device_id}`
}
return url
},
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (): Promise<SpotifyPauseResponse> => {
return {
success: true,
output: {
success: true,
},
}
},
outputs: {
success: { type: 'boolean', description: 'Whether playback was paused' },
},
}
+94
View File
@@ -0,0 +1,94 @@
import type { SpotifyPlayParams, SpotifyPlayResponse } from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyPlayTool: ToolConfig<SpotifyPlayParams, SpotifyPlayResponse> = {
id: 'spotify_play',
name: 'Spotify Play',
description:
'Start or resume playback on Spotify. Can play specific tracks, albums, or playlists.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-modify-playback-state'],
},
params: {
device_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Device ID to play on. If not provided, plays on active device.',
},
context_uri: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Spotify URI of album, artist, or playlist to play (e.g., "spotify:album:xxx")',
},
uris: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Comma-separated track URIs to play (e.g., "spotify:track:xxx,spotify:track:yyy")',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Position in context to start playing (0-based index)',
},
position_ms: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Position in track to start from (in milliseconds)',
},
},
request: {
url: (params) => {
let url = 'https://api.spotify.com/v1/me/player/play'
if (params.device_id) {
url += `?device_id=${params.device_id}`
}
return url
},
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: any = {}
if (params.context_uri) {
body.context_uri = params.context_uri
}
if (params.uris) {
body.uris = params.uris.split(',').map((uri) => uri.trim())
}
if (params.offset !== undefined) {
body.offset = { position: params.offset }
}
if (params.position_ms !== undefined) {
body.position_ms = params.position_ms
}
return Object.keys(body).length > 0 ? body : undefined
},
},
transformResponse: async (): Promise<SpotifyPlayResponse> => {
return {
success: true,
output: {
success: true,
},
}
},
outputs: {
success: { type: 'boolean', description: 'Whether playback started successfully' },
},
}
@@ -0,0 +1,58 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyRemoveSavedAlbumsParams {
accessToken: string
albumIds: string
}
interface SpotifyRemoveSavedAlbumsResponse extends ToolResponse {
output: { success: boolean }
}
export const spotifyRemoveSavedAlbumsTool: ToolConfig<
SpotifyRemoveSavedAlbumsParams,
SpotifyRemoveSavedAlbumsResponse
> = {
id: 'spotify_remove_saved_albums',
name: 'Spotify Remove Saved Albums',
description: "Remove albums from the user's library.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-modify'],
},
params: {
albumIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated album IDs (max 20)',
},
},
request: {
url: () => 'https://api.spotify.com/v1/me/albums',
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => ({
ids: params.albumIds
.split(',')
.map((id) => id.trim())
.slice(0, 20),
}),
},
transformResponse: async (): Promise<SpotifyRemoveSavedAlbumsResponse> => {
return { success: true, output: { success: true } }
},
outputs: {
success: { type: 'boolean', description: 'Whether albums were removed' },
},
}
@@ -0,0 +1,58 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyRemoveSavedAudiobooksParams {
accessToken: string
audiobookIds: string
}
interface SpotifyRemoveSavedAudiobooksResponse extends ToolResponse {
output: { success: boolean }
}
export const spotifyRemoveSavedAudiobooksTool: ToolConfig<
SpotifyRemoveSavedAudiobooksParams,
SpotifyRemoveSavedAudiobooksResponse
> = {
id: 'spotify_remove_saved_audiobooks',
name: 'Spotify Remove Saved Audiobooks',
description: "Remove audiobooks from the user's library.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-modify'],
},
params: {
audiobookIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated audiobook IDs (max 50)',
},
},
request: {
url: (params) => {
const ids = params.audiobookIds
.split(',')
.map((id) => id.trim())
.slice(0, 50)
.join(',')
return `https://api.spotify.com/v1/me/audiobooks?ids=${ids}`
},
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (): Promise<SpotifyRemoveSavedAudiobooksResponse> => {
return { success: true, output: { success: true } }
},
outputs: {
success: { type: 'boolean', description: 'Whether audiobooks were removed' },
},
}
@@ -0,0 +1,58 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyRemoveSavedEpisodesParams {
accessToken: string
episodeIds: string
}
interface SpotifyRemoveSavedEpisodesResponse extends ToolResponse {
output: { success: boolean }
}
export const spotifyRemoveSavedEpisodesTool: ToolConfig<
SpotifyRemoveSavedEpisodesParams,
SpotifyRemoveSavedEpisodesResponse
> = {
id: 'spotify_remove_saved_episodes',
name: 'Spotify Remove Saved Episodes',
description: "Remove podcast episodes from the user's library.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-modify'],
},
params: {
episodeIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated episode IDs (max 50)',
},
},
request: {
url: (params) => {
const ids = params.episodeIds
.split(',')
.map((id) => id.trim())
.slice(0, 50)
.join(',')
return `https://api.spotify.com/v1/me/episodes?ids=${ids}`
},
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (): Promise<SpotifyRemoveSavedEpisodesResponse> => {
return { success: true, output: { success: true } }
},
outputs: {
success: { type: 'boolean', description: 'Whether episodes were removed' },
},
}
@@ -0,0 +1,58 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyRemoveSavedShowsParams {
accessToken: string
showIds: string
}
interface SpotifyRemoveSavedShowsResponse extends ToolResponse {
output: { success: boolean }
}
export const spotifyRemoveSavedShowsTool: ToolConfig<
SpotifyRemoveSavedShowsParams,
SpotifyRemoveSavedShowsResponse
> = {
id: 'spotify_remove_saved_shows',
name: 'Spotify Remove Saved Shows',
description: "Remove podcast shows from the user's library.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-modify'],
},
params: {
showIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated show IDs (max 50)',
},
},
request: {
url: (params) => {
const ids = params.showIds
.split(',')
.map((id) => id.trim())
.slice(0, 50)
.join(',')
return `https://api.spotify.com/v1/me/shows?ids=${ids}`
},
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (): Promise<SpotifyRemoveSavedShowsResponse> => {
return { success: true, output: { success: true } }
},
outputs: {
success: { type: 'boolean', description: 'Whether shows were removed' },
},
}
@@ -0,0 +1,63 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyRemoveSavedTracksParams {
accessToken: string
trackIds: string
}
interface SpotifyRemoveSavedTracksResponse extends ToolResponse {
output: {
success: boolean
}
}
export const spotifyRemoveSavedTracksTool: ToolConfig<
SpotifyRemoveSavedTracksParams,
SpotifyRemoveSavedTracksResponse
> = {
id: 'spotify_remove_saved_tracks',
name: 'Spotify Remove Saved Tracks',
description: "Remove tracks from the user's library.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-modify'],
},
params: {
trackIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated track IDs to remove (max 50)',
},
},
request: {
url: () => 'https://api.spotify.com/v1/me/tracks',
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => ({
ids: params.trackIds
.split(',')
.map((id) => id.trim())
.slice(0, 50),
}),
},
transformResponse: async (): Promise<SpotifyRemoveSavedTracksResponse> => {
return {
success: true,
output: { success: true },
}
},
outputs: {
success: { type: 'boolean', description: 'Whether tracks were removed successfully' },
},
}
@@ -0,0 +1,65 @@
import type {
SpotifyRemoveTracksFromPlaylistParams,
SpotifyRemoveTracksFromPlaylistResponse,
} from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifyRemoveTracksFromPlaylistTool: ToolConfig<
SpotifyRemoveTracksFromPlaylistParams,
SpotifyRemoveTracksFromPlaylistResponse
> = {
id: 'spotify_remove_tracks_from_playlist',
name: 'Spotify Remove Tracks from Playlist',
description: 'Remove one or more tracks from a Spotify playlist.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['playlist-modify-public', 'playlist-modify-private'],
},
params: {
playlistId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify ID of the playlist',
},
uris: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Comma-separated Spotify URIs to remove (e.g., "spotify:track:xxx,spotify:track:yyy")',
},
},
request: {
url: (params) => `https://api.spotify.com/v1/playlists/${params.playlistId}/tracks`,
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const uris = params.uris.split(',').map((uri) => ({ uri: uri.trim() }))
return { tracks: uris }
},
},
transformResponse: async (response): Promise<SpotifyRemoveTracksFromPlaylistResponse> => {
const data = await response.json()
return {
success: true,
output: {
snapshot_id: data.snapshot_id,
},
}
},
outputs: {
snapshot_id: { type: 'string', description: 'New playlist snapshot ID after modification' },
},
}
@@ -0,0 +1,96 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyReorderPlaylistItemsParams {
accessToken: string
playlistId: string
range_start: number
insert_before: number
range_length?: number
snapshot_id?: string
}
interface SpotifyReorderPlaylistItemsResponse extends ToolResponse {
output: {
snapshot_id: string
}
}
export const spotifyReorderPlaylistItemsTool: ToolConfig<
SpotifyReorderPlaylistItemsParams,
SpotifyReorderPlaylistItemsResponse
> = {
id: 'spotify_reorder_playlist_items',
name: 'Spotify Reorder Playlist Items',
description: 'Move tracks to a different position in a playlist.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['playlist-modify-public', 'playlist-modify-private'],
},
params: {
playlistId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify playlist ID',
},
range_start: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Start index of items to reorder',
},
insert_before: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Index to insert items before',
},
range_length: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 1,
description: 'Number of items to reorder',
},
snapshot_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Playlist snapshot ID for concurrency control (22-character base62 string)',
},
},
request: {
url: (params) => `https://api.spotify.com/v1/playlists/${params.playlistId}/tracks`,
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, any> = {
range_start: params.range_start,
insert_before: params.insert_before,
range_length: params.range_length || 1,
}
if (params.snapshot_id) body.snapshot_id = params.snapshot_id
return body
},
},
transformResponse: async (response): Promise<SpotifyReorderPlaylistItemsResponse> => {
const data = await response.json()
return {
success: true,
output: { snapshot_id: data.snapshot_id || '' },
}
},
outputs: {
snapshot_id: { type: 'string', description: 'New playlist snapshot ID' },
},
}
@@ -0,0 +1,71 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyReplacePlaylistItemsParams {
accessToken: string
playlistId: string
uris: string
}
interface SpotifyReplacePlaylistItemsResponse extends ToolResponse {
output: {
snapshot_id: string
}
}
export const spotifyReplacePlaylistItemsTool: ToolConfig<
SpotifyReplacePlaylistItemsParams,
SpotifyReplacePlaylistItemsResponse
> = {
id: 'spotify_replace_playlist_items',
name: 'Spotify Replace Playlist Items',
description: 'Replace all items in a playlist with new tracks.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['playlist-modify-public', 'playlist-modify-private'],
},
params: {
playlistId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify playlist ID',
},
uris: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated Spotify URIs (max 100)',
},
},
request: {
url: (params) => `https://api.spotify.com/v1/playlists/${params.playlistId}/tracks`,
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => ({
uris: params.uris
.split(',')
.map((uri) => uri.trim())
.slice(0, 100),
}),
},
transformResponse: async (response): Promise<SpotifyReplacePlaylistItemsResponse> => {
const data = await response.json()
return {
success: true,
output: { snapshot_id: data.snapshot_id || '' },
}
},
outputs: {
snapshot_id: { type: 'string', description: 'New playlist snapshot ID' },
},
}
+56
View File
@@ -0,0 +1,56 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifySaveAlbumsParams {
accessToken: string
albumIds: string
}
interface SpotifySaveAlbumsResponse extends ToolResponse {
output: { success: boolean }
}
export const spotifySaveAlbumsTool: ToolConfig<SpotifySaveAlbumsParams, SpotifySaveAlbumsResponse> =
{
id: 'spotify_save_albums',
name: 'Spotify Save Albums',
description: "Save albums to the user's library.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-modify'],
},
params: {
albumIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated album IDs (max 20)',
},
},
request: {
url: () => 'https://api.spotify.com/v1/me/albums',
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => ({
ids: params.albumIds
.split(',')
.map((id) => id.trim())
.slice(0, 20),
}),
},
transformResponse: async (): Promise<SpotifySaveAlbumsResponse> => {
return { success: true, output: { success: true } }
},
outputs: {
success: { type: 'boolean', description: 'Whether albums were saved' },
},
}
+58
View File
@@ -0,0 +1,58 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifySaveAudiobooksParams {
accessToken: string
audiobookIds: string
}
interface SpotifySaveAudiobooksResponse extends ToolResponse {
output: { success: boolean }
}
export const spotifySaveAudiobooksTool: ToolConfig<
SpotifySaveAudiobooksParams,
SpotifySaveAudiobooksResponse
> = {
id: 'spotify_save_audiobooks',
name: 'Spotify Save Audiobooks',
description: "Save audiobooks to the user's library.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-modify'],
},
params: {
audiobookIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated audiobook IDs (max 50)',
},
},
request: {
url: (params) => {
const ids = params.audiobookIds
.split(',')
.map((id) => id.trim())
.slice(0, 50)
.join(',')
return `https://api.spotify.com/v1/me/audiobooks?ids=${ids}`
},
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (): Promise<SpotifySaveAudiobooksResponse> => {
return { success: true, output: { success: true } }
},
outputs: {
success: { type: 'boolean', description: 'Whether audiobooks were saved' },
},
}
+58
View File
@@ -0,0 +1,58 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifySaveEpisodesParams {
accessToken: string
episodeIds: string
}
interface SpotifySaveEpisodesResponse extends ToolResponse {
output: { success: boolean }
}
export const spotifySaveEpisodesTool: ToolConfig<
SpotifySaveEpisodesParams,
SpotifySaveEpisodesResponse
> = {
id: 'spotify_save_episodes',
name: 'Spotify Save Episodes',
description: "Save podcast episodes to the user's library.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-modify'],
},
params: {
episodeIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated episode IDs (max 50)',
},
},
request: {
url: (params) => {
const ids = params.episodeIds
.split(',')
.map((id) => id.trim())
.slice(0, 50)
.join(',')
return `https://api.spotify.com/v1/me/episodes?ids=${ids}`
},
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (): Promise<SpotifySaveEpisodesResponse> => {
return { success: true, output: { success: true } }
},
outputs: {
success: { type: 'boolean', description: 'Whether episodes were saved' },
},
}
+55
View File
@@ -0,0 +1,55 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifySaveShowsParams {
accessToken: string
showIds: string
}
interface SpotifySaveShowsResponse extends ToolResponse {
output: { success: boolean }
}
export const spotifySaveShowsTool: ToolConfig<SpotifySaveShowsParams, SpotifySaveShowsResponse> = {
id: 'spotify_save_shows',
name: 'Spotify Save Shows',
description: "Save podcast shows to the user's library.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-modify'],
},
params: {
showIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated show IDs (max 50)',
},
},
request: {
url: (params) => {
const ids = params.showIds
.split(',')
.map((id) => id.trim())
.slice(0, 50)
.join(',')
return `https://api.spotify.com/v1/me/shows?ids=${ids}`
},
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (): Promise<SpotifySaveShowsResponse> => {
return { success: true, output: { success: true } }
},
outputs: {
success: { type: 'boolean', description: 'Whether shows were saved' },
},
}
+48
View File
@@ -0,0 +1,48 @@
import type { SpotifySaveTracksParams, SpotifySaveTracksResponse } from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifySaveTracksTool: ToolConfig<SpotifySaveTracksParams, SpotifySaveTracksResponse> =
{
id: 'spotify_save_tracks',
name: 'Spotify Save Tracks',
description: "Save tracks to the current user's library (like tracks).",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-library-modify'],
},
params: {
trackIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated Spotify track IDs to save (max 50)',
},
},
request: {
url: (params) =>
`https://api.spotify.com/v1/me/tracks?ids=${encodeURIComponent(params.trackIds)}`,
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (): Promise<SpotifySaveTracksResponse> => {
return {
success: true,
output: {
success: true,
},
}
},
outputs: {
success: { type: 'boolean', description: 'Whether the tracks were saved successfully' },
},
}
+154
View File
@@ -0,0 +1,154 @@
import type { SpotifySearchParams, SpotifySearchResponse } from '@/tools/spotify/types'
import {
SEARCH_ALBUM_OUTPUT_PROPERTIES,
SEARCH_ARTIST_OUTPUT_PROPERTIES,
SEARCH_PLAYLIST_OUTPUT_PROPERTIES,
SEARCH_TRACK_OUTPUT_PROPERTIES,
} from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifySearchTool: ToolConfig<SpotifySearchParams, SpotifySearchResponse> = {
id: 'spotify_search',
name: 'Spotify Search',
description:
'Search for tracks, albums, artists, or playlists on Spotify. Returns matching results based on the query.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
},
params: {
query: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Search query (e.g., "Bohemian Rhapsody", "artist:Queen", "genre:rock")',
},
type: {
type: 'string',
required: false,
visibility: 'user-or-llm',
default: 'track',
description:
'Type of results: track, album, artist, playlist, or comma-separated (e.g., "track,artist")',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 20,
description: 'Maximum number of results to return (1-50)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
default: 0,
description: 'Index of the first result to return for pagination',
},
market: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code to filter results (e.g., "US", "GB")',
},
},
request: {
url: (params) => {
const type = params.type || 'track'
const limit = Math.min(Math.max(params.limit || 20, 1), 50)
const offset = params.offset || 0
let url = `https://api.spotify.com/v1/search?q=${encodeURIComponent(params.query)}&type=${encodeURIComponent(type)}&limit=${limit}&offset=${offset}`
if (params.market) {
url += `&market=${params.market}`
}
return url
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response): Promise<SpotifySearchResponse> => {
const data = await response.json()
const tracks = (data.tracks?.items || []).map((track: any) => ({
id: track.id,
name: track.name,
artists: track.artists?.map((a: any) => a.name) || [],
album: track.album?.name || '',
duration_ms: track.duration_ms,
popularity: track.popularity,
preview_url: track.preview_url,
external_url: track.external_urls?.spotify || '',
}))
const artists = (data.artists?.items || []).map((artist: any) => ({
id: artist.id,
name: artist.name,
genres: artist.genres || [],
popularity: artist.popularity,
followers: artist.followers?.total || 0,
image_url: artist.images?.[0]?.url || null,
external_url: artist.external_urls?.spotify || '',
}))
const albums = (data.albums?.items || []).map((album: any) => ({
id: album.id,
name: album.name,
artists: album.artists?.map((a: any) => a.name) || [],
total_tracks: album.total_tracks,
release_date: album.release_date,
image_url: album.images?.[0]?.url || null,
external_url: album.external_urls?.spotify || '',
}))
const playlists = (data.playlists?.items || []).map((playlist: any) => ({
id: playlist.id,
name: playlist.name,
description: playlist.description,
owner: playlist.owner?.display_name || '',
total_tracks: playlist.tracks?.total || 0,
image_url: playlist.images?.[0]?.url || null,
external_url: playlist.external_urls?.spotify || '',
}))
return {
success: true,
output: {
tracks,
artists,
albums,
playlists,
},
}
},
outputs: {
tracks: {
type: 'array',
description: 'List of matching tracks',
items: { type: 'object', properties: SEARCH_TRACK_OUTPUT_PROPERTIES },
},
artists: {
type: 'array',
description: 'List of matching artists',
items: { type: 'object', properties: SEARCH_ARTIST_OUTPUT_PROPERTIES },
},
albums: {
type: 'array',
description: 'List of matching albums',
items: { type: 'object', properties: SEARCH_ALBUM_OUTPUT_PROPERTIES },
},
playlists: {
type: 'array',
description: 'List of matching playlists',
items: { type: 'object', properties: SEARCH_PLAYLIST_OUTPUT_PROPERTIES },
},
},
}
+67
View File
@@ -0,0 +1,67 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifySeekParams {
accessToken: string
position_ms: number
device_id?: string
}
interface SpotifySeekResponse extends ToolResponse {
output: {
success: boolean
}
}
export const spotifySeekTool: ToolConfig<SpotifySeekParams, SpotifySeekResponse> = {
id: 'spotify_seek',
name: 'Spotify Seek',
description: 'Seek to a position in the currently playing track.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-modify-playback-state'],
},
params: {
position_ms: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Position in milliseconds to seek to',
},
device_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Spotify device ID to target for playback',
},
},
request: {
url: (params) => {
let url = `https://api.spotify.com/v1/me/player/seek?position_ms=${params.position_ms}`
if (params.device_id) {
url += `&device_id=${params.device_id}`
}
return url
},
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (): Promise<SpotifySeekResponse> => {
return {
success: true,
output: { success: true },
}
},
outputs: {
success: { type: 'boolean', description: 'Whether seek was successful' },
},
}
+67
View File
@@ -0,0 +1,67 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifySetRepeatParams {
accessToken: string
state: string
device_id?: string
}
interface SpotifySetRepeatResponse extends ToolResponse {
output: {
success: boolean
}
}
export const spotifySetRepeatTool: ToolConfig<SpotifySetRepeatParams, SpotifySetRepeatResponse> = {
id: 'spotify_set_repeat',
name: 'Spotify Set Repeat',
description: 'Set the repeat mode for playback.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-modify-playback-state'],
},
params: {
state: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repeat mode: "off", "track", or "context"',
},
device_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Spotify device ID to target for playback',
},
},
request: {
url: (params) => {
let url = `https://api.spotify.com/v1/me/player/repeat?state=${params.state}`
if (params.device_id) {
url += `&device_id=${params.device_id}`
}
return url
},
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (): Promise<SpotifySetRepeatResponse> => {
return {
success: true,
output: { success: true },
}
},
outputs: {
success: { type: 'boolean', description: 'Whether repeat mode was set successfully' },
},
}
+68
View File
@@ -0,0 +1,68 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifySetShuffleParams {
accessToken: string
state: boolean
device_id?: string
}
interface SpotifySetShuffleResponse extends ToolResponse {
output: {
success: boolean
}
}
export const spotifySetShuffleTool: ToolConfig<SpotifySetShuffleParams, SpotifySetShuffleResponse> =
{
id: 'spotify_set_shuffle',
name: 'Spotify Set Shuffle',
description: 'Turn shuffle on or off.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-modify-playback-state'],
},
params: {
state: {
type: 'boolean',
required: true,
visibility: 'user-or-llm',
description: 'true for shuffle on, false for off',
},
device_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Spotify device ID to target for playback',
},
},
request: {
url: (params) => {
let url = `https://api.spotify.com/v1/me/player/shuffle?state=${params.state}`
if (params.device_id) {
url += `&device_id=${params.device_id}`
}
return url
},
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (): Promise<SpotifySetShuffleResponse> => {
return {
success: true,
output: { success: true },
}
},
outputs: {
success: { type: 'boolean', description: 'Whether shuffle was set successfully' },
},
}
+59
View File
@@ -0,0 +1,59 @@
import type { SpotifySetVolumeParams, SpotifySetVolumeResponse } from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifySetVolumeTool: ToolConfig<SpotifySetVolumeParams, SpotifySetVolumeResponse> = {
id: 'spotify_set_volume',
name: 'Spotify Set Volume',
description: 'Set the playback volume on Spotify.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-modify-playback-state'],
},
params: {
volume_percent: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Volume level (0 to 100)',
},
device_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Device ID. If not provided, uses active device.',
},
},
request: {
url: (params) => {
const volume = Math.min(Math.max(params.volume_percent, 0), 100)
let url = `https://api.spotify.com/v1/me/player/volume?volume_percent=${volume}`
if (params.device_id) {
url += `&device_id=${params.device_id}`
}
return url
},
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (): Promise<SpotifySetVolumeResponse> => {
return {
success: true,
output: {
success: true,
},
}
},
outputs: {
success: { type: 'boolean', description: 'Whether volume was set' },
},
}
+52
View File
@@ -0,0 +1,52 @@
import type { SpotifySkipNextParams, SpotifySkipNextResponse } from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifySkipNextTool: ToolConfig<SpotifySkipNextParams, SpotifySkipNextResponse> = {
id: 'spotify_skip_next',
name: 'Spotify Skip to Next',
description: 'Skip to the next track on Spotify.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-modify-playback-state'],
},
params: {
device_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Device ID. If not provided, uses active device.',
},
},
request: {
url: (params) => {
let url = 'https://api.spotify.com/v1/me/player/next'
if (params.device_id) {
url += `?device_id=${params.device_id}`
}
return url
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (): Promise<SpotifySkipNextResponse> => {
return {
success: true,
output: {
success: true,
},
}
},
outputs: {
success: { type: 'boolean', description: 'Whether skip was successful' },
},
}
+55
View File
@@ -0,0 +1,55 @@
import type { SpotifySkipPreviousParams, SpotifySkipPreviousResponse } from '@/tools/spotify/types'
import type { ToolConfig } from '@/tools/types'
export const spotifySkipPreviousTool: ToolConfig<
SpotifySkipPreviousParams,
SpotifySkipPreviousResponse
> = {
id: 'spotify_skip_previous',
name: 'Spotify Skip to Previous',
description: 'Skip to the previous track on Spotify.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-modify-playback-state'],
},
params: {
device_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Device ID. If not provided, uses active device.',
},
},
request: {
url: (params) => {
let url = 'https://api.spotify.com/v1/me/player/previous'
if (params.device_id) {
url += `?device_id=${params.device_id}`
}
return url
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (): Promise<SpotifySkipPreviousResponse> => {
return {
success: true,
output: {
success: true,
},
}
},
outputs: {
success: { type: 'boolean', description: 'Whether skip was successful' },
},
}
@@ -0,0 +1,69 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyTransferPlaybackParams {
accessToken: string
device_id: string
play?: boolean
}
interface SpotifyTransferPlaybackResponse extends ToolResponse {
output: {
success: boolean
}
}
export const spotifyTransferPlaybackTool: ToolConfig<
SpotifyTransferPlaybackParams,
SpotifyTransferPlaybackResponse
> = {
id: 'spotify_transfer_playback',
name: 'Spotify Transfer Playback',
description: 'Transfer playback to a different device.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-modify-playback-state'],
},
params: {
device_id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Device ID to transfer playback to',
},
play: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
default: true,
description: 'Whether to start playing on the new device',
},
},
request: {
url: () => 'https://api.spotify.com/v1/me/player',
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => ({
device_ids: [params.device_id],
play: params.play ?? true,
}),
},
transformResponse: async (): Promise<SpotifyTransferPlaybackResponse> => {
return {
success: true,
output: { success: true },
}
},
outputs: {
success: { type: 'boolean', description: 'Whether transfer was successful' },
},
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,64 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyUnfollowArtistsParams {
accessToken: string
artistIds: string
}
interface SpotifyUnfollowArtistsResponse extends ToolResponse {
output: {
success: boolean
}
}
export const spotifyUnfollowArtistsTool: ToolConfig<
SpotifyUnfollowArtistsParams,
SpotifyUnfollowArtistsResponse
> = {
id: 'spotify_unfollow_artists',
name: 'Spotify Unfollow Artists',
description: 'Unfollow one or more artists.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['user-follow-modify'],
},
params: {
artistIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated artist IDs to unfollow (max 50)',
},
},
request: {
url: (params) => {
const ids = params.artistIds
.split(',')
.map((id) => id.trim())
.slice(0, 50)
.join(',')
return `https://api.spotify.com/v1/me/following?type=artist&ids=${ids}`
},
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (): Promise<SpotifyUnfollowArtistsResponse> => {
return {
success: true,
output: { success: true },
}
},
outputs: {
success: { type: 'boolean', description: 'Whether artists were unfollowed successfully' },
},
}
@@ -0,0 +1,51 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyUnfollowPlaylistParams {
accessToken: string
playlistId: string
}
interface SpotifyUnfollowPlaylistResponse extends ToolResponse {
output: { success: boolean }
}
export const spotifyUnfollowPlaylistTool: ToolConfig<
SpotifyUnfollowPlaylistParams,
SpotifyUnfollowPlaylistResponse
> = {
id: 'spotify_unfollow_playlist',
name: 'Spotify Unfollow Playlist',
description: 'Unfollow (unsave) a playlist.',
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['playlist-modify-public', 'playlist-modify-private'],
},
params: {
playlistId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify playlist ID',
},
},
request: {
url: (params) => `https://api.spotify.com/v1/playlists/${params.playlistId}/followers`,
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (): Promise<SpotifyUnfollowPlaylistResponse> => {
return { success: true, output: { success: true } }
},
outputs: {
success: { type: 'boolean', description: 'Whether unfollow succeeded' },
},
}
+80
View File
@@ -0,0 +1,80 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface SpotifyUpdatePlaylistParams {
accessToken: string
playlistId: string
name?: string
description?: string
public?: boolean
}
interface SpotifyUpdatePlaylistResponse extends ToolResponse {
output: { success: boolean }
}
export const spotifyUpdatePlaylistTool: ToolConfig<
SpotifyUpdatePlaylistParams,
SpotifyUpdatePlaylistResponse
> = {
id: 'spotify_update_playlist',
name: 'Spotify Update Playlist',
description: "Update a playlist's name, description, or visibility.",
version: '1.0.0',
oauth: {
required: true,
provider: 'spotify',
requiredScopes: ['playlist-modify-public', 'playlist-modify-private'],
},
params: {
playlistId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The Spotify playlist ID',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New name for the playlist',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New description for the playlist',
},
public: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the playlist should be public',
},
},
request: {
url: (params) => `https://api.spotify.com/v1/playlists/${params.playlistId}`,
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, any> = {}
if (params.name !== undefined) body.name = params.name
if (params.description !== undefined) body.description = params.description
if (params.public !== undefined) body.public = params.public
return body
},
},
transformResponse: async (): Promise<SpotifyUpdatePlaylistResponse> => {
return { success: true, output: { success: true } }
},
outputs: {
success: { type: 'boolean', description: 'Whether update succeeded' },
},
}