rewind-mcp-server 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/dist/__tests__/helpers/build-client.d.ts +2 -0
  2. package/dist/__tests__/helpers/build-client.js +24 -0
  3. package/dist/__tests__/helpers/build-client.js.map +1 -0
  4. package/dist/__tests__/helpers.test.js +11 -0
  5. package/dist/__tests__/helpers.test.js.map +1 -1
  6. package/dist/__tests__/image-block.live.js +1 -1
  7. package/dist/__tests__/image-block.live.js.map +1 -1
  8. package/dist/__tests__/manifest-snapshot.test.js +1 -0
  9. package/dist/__tests__/manifest-snapshot.test.js.map +1 -1
  10. package/dist/__tests__/output-schema-attending.test.d.ts +1 -0
  11. package/dist/__tests__/output-schema-attending.test.js +353 -0
  12. package/dist/__tests__/output-schema-attending.test.js.map +1 -0
  13. package/dist/__tests__/output-schema-collecting.test.d.ts +1 -0
  14. package/dist/__tests__/output-schema-collecting.test.js +155 -0
  15. package/dist/__tests__/output-schema-collecting.test.js.map +1 -0
  16. package/dist/__tests__/output-schema-cross-domain.test.d.ts +1 -0
  17. package/dist/__tests__/output-schema-cross-domain.test.js +142 -0
  18. package/dist/__tests__/output-schema-cross-domain.test.js.map +1 -0
  19. package/dist/__tests__/output-schema-listening.test.d.ts +1 -0
  20. package/dist/__tests__/output-schema-listening.test.js +235 -0
  21. package/dist/__tests__/output-schema-listening.test.js.map +1 -0
  22. package/dist/__tests__/output-schema-reading.test.d.ts +1 -0
  23. package/dist/__tests__/output-schema-reading.test.js +181 -0
  24. package/dist/__tests__/output-schema-reading.test.js.map +1 -0
  25. package/dist/__tests__/output-schema-running.test.d.ts +1 -0
  26. package/dist/__tests__/output-schema-running.test.js +185 -0
  27. package/dist/__tests__/output-schema-running.test.js.map +1 -0
  28. package/dist/__tests__/output-schema-watching.test.d.ts +1 -0
  29. package/dist/__tests__/output-schema-watching.test.js +190 -0
  30. package/dist/__tests__/output-schema-watching.test.js.map +1 -0
  31. package/dist/__tests__/output-schema.live.d.ts +1 -0
  32. package/dist/__tests__/output-schema.live.js +180 -0
  33. package/dist/__tests__/output-schema.live.js.map +1 -0
  34. package/dist/__tests__/output-schema.test.d.ts +1 -0
  35. package/dist/__tests__/output-schema.test.js +38 -0
  36. package/dist/__tests__/output-schema.test.js.map +1 -0
  37. package/dist/__tests__/server.test.js +1 -1
  38. package/dist/server.js +10 -12
  39. package/dist/server.js.map +1 -1
  40. package/dist/tools/attending.js +9 -0
  41. package/dist/tools/attending.js.map +1 -1
  42. package/dist/tools/collecting.js +5 -0
  43. package/dist/tools/collecting.js.map +1 -1
  44. package/dist/tools/cross-domain.js +12 -1
  45. package/dist/tools/cross-domain.js.map +1 -1
  46. package/dist/tools/helpers.js +14 -9
  47. package/dist/tools/helpers.js.map +1 -1
  48. package/dist/tools/listening.js +12 -1
  49. package/dist/tools/listening.js.map +1 -1
  50. package/dist/tools/reading.js +8 -1
  51. package/dist/tools/reading.js.map +1 -1
  52. package/dist/tools/running.js +8 -0
  53. package/dist/tools/running.js.map +1 -1
  54. package/dist/tools/schemas/attending.d.ts +8225 -0
  55. package/dist/tools/schemas/attending.js +404 -0
  56. package/dist/tools/schemas/attending.js.map +1 -0
  57. package/dist/tools/schemas/collecting.d.ts +1011 -0
  58. package/dist/tools/schemas/collecting.js +108 -0
  59. package/dist/tools/schemas/collecting.js.map +1 -0
  60. package/dist/tools/schemas/cross-domain.d.ts +1001 -0
  61. package/dist/tools/schemas/cross-domain.js +107 -0
  62. package/dist/tools/schemas/cross-domain.js.map +1 -0
  63. package/dist/tools/schemas/listening.d.ts +4298 -0
  64. package/dist/tools/schemas/listening.js +255 -0
  65. package/dist/tools/schemas/listening.js.map +1 -0
  66. package/dist/tools/schemas/reading.d.ts +1421 -0
  67. package/dist/tools/schemas/reading.js +151 -0
  68. package/dist/tools/schemas/reading.js.map +1 -0
  69. package/dist/tools/schemas/running.d.ts +680 -0
  70. package/dist/tools/schemas/running.js +154 -0
  71. package/dist/tools/schemas/running.js.map +1 -0
  72. package/dist/tools/schemas/shared.d.ts +98 -0
  73. package/dist/tools/schemas/shared.js +74 -0
  74. package/dist/tools/schemas/shared.js.map +1 -0
  75. package/dist/tools/schemas/system.d.ts +57 -0
  76. package/dist/tools/schemas/system.js +22 -0
  77. package/dist/tools/schemas/system.js.map +1 -0
  78. package/dist/tools/schemas/watching.d.ts +1821 -0
  79. package/dist/tools/schemas/watching.js +167 -0
  80. package/dist/tools/schemas/watching.js.map +1 -0
  81. package/dist/tools/watching.js +8 -0
  82. package/dist/tools/watching.js.map +1 -1
  83. package/dist/ui-bundles.d.ts +10 -11
  84. package/dist/ui-bundles.js +20 -22
  85. package/dist/ui-bundles.js.map +1 -1
  86. package/package.json +1 -1
  87. package/dist/tools/debug.d.ts +0 -6
  88. package/dist/tools/debug.js +0 -26
  89. package/dist/tools/debug.js.map +0 -1
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Output-schema conformance — collecting domain (issue #105).
3
+ *
4
+ * For every collecting tool: run it end-to-end through the SDK against a
5
+ * fixture and assert it resolves. The SDK's validateToolOutput throws if
6
+ * `structuredContent` does not match the declared `outputSchema`, so a
7
+ * resolved call IS the conformance proof. Also asserts each tool
8
+ * advertises a clean JSON Schema (top-level object, no $ref, no
9
+ * `additionalProperties: false`).
10
+ *
11
+ * Structure mirrors output-schema-listening.test.ts.
12
+ */
13
+ import { describe, it, expect, vi } from 'vitest';
14
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
15
+ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
16
+ import { createServer } from '../server.js';
17
+ import { RewindClient } from '../client.js';
18
+ // --- Fixtures (minimal valid API responses) -------------------------------
19
+ const paginationFixture = {
20
+ page: 1,
21
+ limit: 10,
22
+ total: 1,
23
+ total_pages: 1,
24
+ };
25
+ const vinylItemFixture = {
26
+ id: 1,
27
+ discogs_id: 12345,
28
+ title: 'Test Album',
29
+ artists: ['Test Artist'],
30
+ year: 1999,
31
+ format: 'Vinyl',
32
+ format_detail: 'LP, Album',
33
+ label: 'Test Label',
34
+ genres: ['Rock'],
35
+ styles: ['Indie Rock'],
36
+ image: null,
37
+ date_added: '2026-05-01T00:00:00Z',
38
+ rating: 5,
39
+ discogs_url: 'https://discogs.com/release/12345',
40
+ };
41
+ const mediaItemFixture = {
42
+ id: 1,
43
+ title: 'Test Movie',
44
+ year: 2001,
45
+ tmdb_id: 678,
46
+ imdb_id: 'tt0000001',
47
+ image: null,
48
+ runtime: 120,
49
+ tmdb_rating: 8.1,
50
+ media_type: 'bluray',
51
+ resolution: '1080p',
52
+ hdr: null,
53
+ audio: 'DTS-HD MA',
54
+ audio_channels: '5.1',
55
+ collected_at: '2026-05-01T00:00:00Z',
56
+ };
57
+ const ROUTES = {
58
+ '/collecting/vinyl': {
59
+ data: [vinylItemFixture],
60
+ pagination: paginationFixture,
61
+ },
62
+ '/collecting/stats': {
63
+ data: {
64
+ total_items: 100,
65
+ by_format: { Vinyl: 80, CD: 20 },
66
+ wantlist_count: 5,
67
+ unique_artists: 40,
68
+ estimated_value: 1500,
69
+ top_genre: 'Rock',
70
+ oldest_release_year: 1965,
71
+ newest_release_year: 2026,
72
+ most_collected_artist: { name: 'Test Artist', count: 8 },
73
+ added_this_year: 12,
74
+ },
75
+ },
76
+ '/collecting/media': {
77
+ data: [mediaItemFixture],
78
+ pagination: paginationFixture,
79
+ },
80
+ '/collecting/media/formats': {
81
+ data: [
82
+ { name: 'bluray', count: 30 },
83
+ { name: 'uhd_bluray', count: 12 },
84
+ ],
85
+ },
86
+ };
87
+ function resolveRoute(path) {
88
+ return ROUTES[path] ?? {};
89
+ }
90
+ async function buildClient() {
91
+ const rewindClient = new RewindClient('https://api.test', 'rw_test');
92
+ vi.spyOn(rewindClient, 'get').mockImplementation(async (path) => resolveRoute(path));
93
+ const server = createServer(rewindClient);
94
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
95
+ const client = new Client({ name: 'output-schema-test', version: '1.0.0' });
96
+ await server.connect(serverTransport);
97
+ await client.connect(clientTransport);
98
+ return client;
99
+ }
100
+ const CASES = [
101
+ { name: 'get_vinyl_collection', args: { include_images: false } },
102
+ { name: 'get_collecting_stats', args: {} },
103
+ { name: 'get_physical_media', args: { include_images: false } },
104
+ { name: 'get_physical_media_stats', args: {} },
105
+ ];
106
+ describe('output-schema conformance — collecting', () => {
107
+ for (const c of CASES) {
108
+ it(`${c.name}: structuredContent conforms to outputSchema`, async () => {
109
+ const client = await buildClient();
110
+ // A schema mismatch makes the SDK's validateToolOutput throw and this
111
+ // call reject -- resolving without error IS the conformance check.
112
+ const res = await client.callTool({ name: c.name, arguments: c.args });
113
+ expect(res.isError).toBeFalsy();
114
+ expect(res.structuredContent).toBeDefined();
115
+ });
116
+ }
117
+ it('empty-state branches still conform', async () => {
118
+ const rewindClient = new RewindClient('https://api.test', 'rw_test');
119
+ vi.spyOn(rewindClient, 'get').mockImplementation(async (path) => {
120
+ if (path === '/collecting/vinyl')
121
+ return { data: [], pagination: paginationFixture };
122
+ if (path === '/collecting/media')
123
+ return { data: [], pagination: paginationFixture };
124
+ return {};
125
+ });
126
+ const server = createServer(rewindClient);
127
+ const [ct, st] = InMemoryTransport.createLinkedPair();
128
+ const client = new Client({ name: 'empty-test', version: '1.0.0' });
129
+ await server.connect(st);
130
+ await client.connect(ct);
131
+ for (const [name, args] of [
132
+ ['get_vinyl_collection', { include_images: false }],
133
+ ['get_physical_media', { include_images: false }],
134
+ ]) {
135
+ const res = await client.callTool({ name, arguments: args });
136
+ expect(res.isError, name).toBeFalsy();
137
+ }
138
+ });
139
+ it('every collecting tool advertises a clean outputSchema', async () => {
140
+ const client = await buildClient();
141
+ const { tools } = await client.listTools();
142
+ const names = new Set(CASES.map((c) => c.name));
143
+ const collecting = tools.filter((t) => names.has(t.name));
144
+ expect(collecting).toHaveLength(CASES.length);
145
+ for (const t of collecting) {
146
+ expect(t.outputSchema, t.name).toMatchObject({ type: 'object' });
147
+ const json = JSON.stringify(t.outputSchema);
148
+ // No $ref/$defs: older Claude Desktop builds failed to compile them.
149
+ expect(json, `${t.name} $ref`).not.toContain('$ref');
150
+ // .passthrough() keeps the advertised schema forward-compatible.
151
+ expect(json, `${t.name} additionalProperties`).not.toContain('"additionalProperties":false');
152
+ }
153
+ });
154
+ });
155
+ //# sourceMappingURL=output-schema-collecting.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-schema-collecting.test.js","sourceRoot":"","sources":["../../src/__tests__/output-schema-collecting.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Output-schema conformance — cross-domain (issue #105).
3
+ *
4
+ * For every cross-domain tool: run it end-to-end through the SDK against
5
+ * a fixture and assert it resolves. The SDK's validateToolOutput throws if
6
+ * `structuredContent` does not match the declared `outputSchema`, so a
7
+ * resolved call IS the conformance proof. Also asserts each tool
8
+ * advertises a clean JSON Schema (top-level object, no $ref, no
9
+ * `additionalProperties: false`).
10
+ *
11
+ * Mirrors the structure of output-schema-listening.test.ts.
12
+ */
13
+ import { describe, it, expect, vi } from 'vitest';
14
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
15
+ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
16
+ import { createServer } from '../server.js';
17
+ import { RewindClient } from '../client.js';
18
+ // --- Fixtures (minimal valid API responses) -------------------------------
19
+ const paginationFixture = {
20
+ page: 1,
21
+ limit: 10,
22
+ total: 1,
23
+ total_pages: 1,
24
+ };
25
+ const searchResultFixture = {
26
+ domain: 'reading',
27
+ entity_type: 'article',
28
+ entity_id: '42',
29
+ title: 'Test Article',
30
+ subtitle: 'A subtitle',
31
+ image: null,
32
+ url: 'https://example.com/article',
33
+ instapaper_url: 'https://instapaper.com/read/42',
34
+ instapaper_app_url: 'instapaper://read/42',
35
+ author: 'Test Author',
36
+ score: 0.87,
37
+ };
38
+ const feedItemFixture = {
39
+ domain: 'listening',
40
+ event_type: 'scrobble',
41
+ occurred_at: '2026-05-15T12:00:00Z',
42
+ title: 'Test Song',
43
+ subtitle: 'Test Artist',
44
+ };
45
+ const onThisDayFixture = {
46
+ month: 5,
47
+ day: 15,
48
+ years: [
49
+ {
50
+ year: 2024,
51
+ items: [
52
+ {
53
+ domain: 'watching',
54
+ event_type: 'watch',
55
+ title: 'Test Movie',
56
+ subtitle: 'A film',
57
+ },
58
+ ],
59
+ },
60
+ ],
61
+ };
62
+ const ROUTES = {
63
+ '/search': { data: [searchResultFixture], pagination: paginationFixture },
64
+ '/feed': { data: [feedItemFixture], pagination: { has_more: false } },
65
+ '/feed/on-this-day': onThisDayFixture,
66
+ };
67
+ function resolveRoute(path) {
68
+ if (path.startsWith('/feed/domain/')) {
69
+ return { data: [feedItemFixture], pagination: { has_more: false } };
70
+ }
71
+ return ROUTES[path] ?? {};
72
+ }
73
+ async function buildClient() {
74
+ const rewindClient = new RewindClient('https://api.test', 'rw_test');
75
+ vi.spyOn(rewindClient, 'get').mockImplementation(async (path) => resolveRoute(path));
76
+ const server = createServer(rewindClient);
77
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
78
+ const client = new Client({ name: 'output-schema-test', version: '1.0.0' });
79
+ await server.connect(serverTransport);
80
+ await client.connect(clientTransport);
81
+ return client;
82
+ }
83
+ const CASES = [
84
+ { name: 'search', args: { query: 'test' } },
85
+ { name: 'semantic_search', args: { query: 'test' } },
86
+ { name: 'get_feed', args: {} },
87
+ { name: 'get_on_this_day', args: {} },
88
+ ];
89
+ describe('output-schema conformance — cross-domain', () => {
90
+ for (const c of CASES) {
91
+ it(`${c.name}: structuredContent conforms to outputSchema`, async () => {
92
+ const client = await buildClient();
93
+ // A schema mismatch makes the SDK's validateToolOutput throw and this
94
+ // call reject -- resolving without error IS the conformance check.
95
+ const res = await client.callTool({ name: c.name, arguments: c.args });
96
+ expect(res.isError).toBeFalsy();
97
+ expect(res.structuredContent).toBeDefined();
98
+ });
99
+ }
100
+ it('empty-state branches still conform', async () => {
101
+ const rewindClient = new RewindClient('https://api.test', 'rw_test');
102
+ vi.spyOn(rewindClient, 'get').mockImplementation(async (path) => {
103
+ if (path === '/search')
104
+ return { data: [], pagination: paginationFixture };
105
+ if (path === '/feed')
106
+ return { data: [], pagination: { has_more: false } };
107
+ if (path === '/feed/on-this-day')
108
+ return { month: 5, day: 15, years: [] };
109
+ return {};
110
+ });
111
+ const server = createServer(rewindClient);
112
+ const [ct, st] = InMemoryTransport.createLinkedPair();
113
+ const client = new Client({ name: 'empty-test', version: '1.0.0' });
114
+ await server.connect(st);
115
+ await client.connect(ct);
116
+ for (const [name, args] of [
117
+ ['search', { query: 'nothing' }],
118
+ ['semantic_search', { query: 'nothing' }],
119
+ ['get_feed', {}],
120
+ ['get_on_this_day', {}],
121
+ ]) {
122
+ const res = await client.callTool({ name, arguments: args });
123
+ expect(res.isError, name).toBeFalsy();
124
+ }
125
+ });
126
+ it('every cross-domain tool advertises a clean outputSchema', async () => {
127
+ const client = await buildClient();
128
+ const { tools } = await client.listTools();
129
+ const names = new Set(CASES.map((c) => c.name));
130
+ const crossDomain = tools.filter((t) => names.has(t.name));
131
+ expect(crossDomain).toHaveLength(CASES.length);
132
+ for (const t of crossDomain) {
133
+ expect(t.outputSchema, t.name).toMatchObject({ type: 'object' });
134
+ const json = JSON.stringify(t.outputSchema);
135
+ // No $ref/$defs: older Claude Desktop builds failed to compile them.
136
+ expect(json, `${t.name} $ref`).not.toContain('$ref');
137
+ // .passthrough() keeps the advertised schema forward-compatible.
138
+ expect(json, `${t.name} additionalProperties`).not.toContain('"additionalProperties":false');
139
+ }
140
+ });
141
+ });
142
+ //# sourceMappingURL=output-schema-cross-domain.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-schema-cross-domain.test.js","sourceRoot":"","sources":["../../src/__tests__/output-schema-cross-domain.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Output-schema conformance — listening domain (issue #105, Phase 1).
3
+ *
4
+ * For every listening tool: run it end-to-end through the SDK against a
5
+ * fixture and assert it resolves. The SDK's validateToolOutput throws if
6
+ * `structuredContent` does not match the declared `outputSchema`, so a
7
+ * resolved call IS the conformance proof. Also asserts each tool
8
+ * advertises a clean JSON Schema (top-level object, no $ref, no
9
+ * `additionalProperties: false`).
10
+ *
11
+ * This file is the template the other domains' conformance tests copy.
12
+ */
13
+ import { describe, it, expect, vi } from 'vitest';
14
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
15
+ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
16
+ import { createServer } from '../server.js';
17
+ import { RewindClient } from '../client.js';
18
+ // --- Fixtures (minimal valid API responses) -------------------------------
19
+ const scrobbleFixture = {
20
+ track: {
21
+ id: 1,
22
+ name: 'Test Song',
23
+ url: null,
24
+ apple_music_url: null,
25
+ preview_url: null,
26
+ },
27
+ artist: { id: 2, name: 'Test Artist' },
28
+ album: { id: 3, name: 'Test Album', image: null },
29
+ scrobbled_at: '2026-05-15T12:00:00Z',
30
+ };
31
+ const topItemFixture = {
32
+ rank: 1,
33
+ id: 1,
34
+ name: 'Test Item',
35
+ detail: 'Test detail',
36
+ playcount: 10,
37
+ image: null,
38
+ url: 'https://last.fm/x',
39
+ apple_music_url: null,
40
+ };
41
+ const artistDetailFixture = {
42
+ id: 1,
43
+ name: 'Test Artist',
44
+ mbid: null,
45
+ url: null,
46
+ apple_music_url: null,
47
+ playcount: 100,
48
+ scrobble_count: 100,
49
+ first_scrobbled_at: '2020-01-01',
50
+ last_played_at: '2026-05-15',
51
+ all_time_rank: 5,
52
+ distinct_tracks: 20,
53
+ distinct_albums: 4,
54
+ genre: 'rock',
55
+ tags: [{ name: 'rock', count: 50 }],
56
+ bio_summary: 'A band.',
57
+ bio_content: 'A longer bio.',
58
+ bio_synced_at: null,
59
+ image: null,
60
+ sparkline: { granularity: 'month', points: [{ at: '2026-01', count: 10 }] },
61
+ top_albums: [
62
+ { id: 2, name: 'Album', playcount: 30, apple_music_url: null, image: null },
63
+ ],
64
+ top_tracks: [
65
+ {
66
+ id: 3,
67
+ name: 'Track',
68
+ album_id: 2,
69
+ album_name: 'Album',
70
+ scrobble_count: 15,
71
+ apple_music_url: null,
72
+ preview_url: null,
73
+ image: null,
74
+ },
75
+ ],
76
+ similar_artists: [
77
+ {
78
+ id: 4,
79
+ name: 'Similar',
80
+ your_scrobble_count: 8,
81
+ similarity_score: 0.9,
82
+ image: null,
83
+ },
84
+ ],
85
+ };
86
+ const albumDetailFixture = {
87
+ id: 1,
88
+ name: 'Album',
89
+ mbid: null,
90
+ url: null,
91
+ apple_music_url: null,
92
+ playcount: 50,
93
+ image: null,
94
+ artist: { id: 2, name: 'Artist' },
95
+ tracks: [
96
+ {
97
+ id: 3,
98
+ name: 'Track',
99
+ scrobble_count: 10,
100
+ apple_music_url: null,
101
+ preview_url: null,
102
+ },
103
+ ],
104
+ };
105
+ const ROUTES = {
106
+ '/listening/now-playing': {
107
+ is_playing: true,
108
+ track: {
109
+ name: 'Test Song',
110
+ artist: { id: 1, name: 'Test Artist', apple_music_url: null },
111
+ album: { id: 2, name: 'Test Album', image: null },
112
+ url: null,
113
+ apple_music_url: null,
114
+ preview_url: null,
115
+ },
116
+ scrobbled_at: '2026-05-15T12:00:00Z',
117
+ },
118
+ '/listening/recent': { data: [scrobbleFixture] },
119
+ '/listening/stats': {
120
+ total_scrobbles: 100,
121
+ unique_artists: 10,
122
+ unique_albums: 20,
123
+ unique_tracks: 50,
124
+ registered_date: '2010-01-01',
125
+ years_tracking: 16,
126
+ scrobbles_per_day: 1.5,
127
+ },
128
+ '/listening/top/artists': { period: '1month', data: [topItemFixture] },
129
+ '/listening/top/albums': { period: '1month', data: [topItemFixture] },
130
+ '/listening/top/tracks': {
131
+ period: '1month',
132
+ artist_id: null,
133
+ data: [topItemFixture],
134
+ },
135
+ '/listening/streaks': {
136
+ current: { days: 5, start_date: '2026-05-10', total_scrobbles: 50 },
137
+ longest: {
138
+ days: 30,
139
+ start_date: '2025-01-01',
140
+ end_date: '2025-01-31',
141
+ total_scrobbles: 300,
142
+ },
143
+ },
144
+ '/listening/genres': {
145
+ data: [{ period: '2026-05', genres: { rock: 10 }, total: 10 }],
146
+ },
147
+ };
148
+ function resolveRoute(path) {
149
+ if (path.startsWith('/listening/artists/'))
150
+ return artistDetailFixture;
151
+ if (path.startsWith('/listening/albums/'))
152
+ return albumDetailFixture;
153
+ return ROUTES[path] ?? {};
154
+ }
155
+ async function buildClient() {
156
+ const rewindClient = new RewindClient('https://api.test', 'rw_test');
157
+ vi.spyOn(rewindClient, 'get').mockImplementation(async (path) => resolveRoute(path));
158
+ const server = createServer(rewindClient);
159
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
160
+ const client = new Client({ name: 'output-schema-test', version: '1.0.0' });
161
+ await server.connect(serverTransport);
162
+ await client.connect(clientTransport);
163
+ return client;
164
+ }
165
+ const CASES = [
166
+ { name: 'get_now_playing', args: { include_images: false } },
167
+ { name: 'get_recent_listens', args: { include_images: false } },
168
+ { name: 'get_listening_stats', args: {} },
169
+ {
170
+ name: 'get_top_artists',
171
+ args: { include_images: false, include_sparklines: false },
172
+ },
173
+ { name: 'get_top_albums', args: { include_images: false } },
174
+ { name: 'get_top_tracks', args: {} },
175
+ { name: 'get_listening_streaks', args: {} },
176
+ { name: 'get_artist_details', args: { id: 1, include_images: false } },
177
+ { name: 'get_album_details', args: { id: 1, include_images: false } },
178
+ { name: 'get_listening_genres', args: {} },
179
+ ];
180
+ describe('output-schema conformance — listening', () => {
181
+ for (const c of CASES) {
182
+ it(`${c.name}: structuredContent conforms to outputSchema`, async () => {
183
+ const client = await buildClient();
184
+ // A schema mismatch makes the SDK's validateToolOutput throw and this
185
+ // call reject -- resolving without error IS the conformance check.
186
+ const res = await client.callTool({ name: c.name, arguments: c.args });
187
+ expect(res.isError).toBeFalsy();
188
+ expect(res.structuredContent).toBeDefined();
189
+ });
190
+ }
191
+ it('empty-state branches still conform', async () => {
192
+ const rewindClient = new RewindClient('https://api.test', 'rw_test');
193
+ vi.spyOn(rewindClient, 'get').mockImplementation(async (path) => {
194
+ if (path === '/listening/now-playing')
195
+ return { is_playing: false, track: null, scrobbled_at: null };
196
+ if (path === '/listening/recent')
197
+ return { data: [] };
198
+ if (path === '/listening/top/artists')
199
+ return { period: '1month', data: [] };
200
+ if (path === '/listening/genres')
201
+ return { data: [] };
202
+ return {};
203
+ });
204
+ const server = createServer(rewindClient);
205
+ const [ct, st] = InMemoryTransport.createLinkedPair();
206
+ const client = new Client({ name: 'empty-test', version: '1.0.0' });
207
+ await server.connect(st);
208
+ await client.connect(ct);
209
+ for (const [name, args] of [
210
+ ['get_now_playing', { include_images: false }],
211
+ ['get_recent_listens', { include_images: false }],
212
+ ['get_top_artists', { include_images: false, include_sparklines: false }],
213
+ ['get_listening_genres', {}],
214
+ ]) {
215
+ const res = await client.callTool({ name, arguments: args });
216
+ expect(res.isError, name).toBeFalsy();
217
+ }
218
+ });
219
+ it('every listening tool advertises a clean outputSchema', async () => {
220
+ const client = await buildClient();
221
+ const { tools } = await client.listTools();
222
+ const names = new Set(CASES.map((c) => c.name));
223
+ const listening = tools.filter((t) => names.has(t.name));
224
+ expect(listening).toHaveLength(CASES.length);
225
+ for (const t of listening) {
226
+ expect(t.outputSchema, t.name).toMatchObject({ type: 'object' });
227
+ const json = JSON.stringify(t.outputSchema);
228
+ // No $ref/$defs: older Claude Desktop builds failed to compile them.
229
+ expect(json, `${t.name} $ref`).not.toContain('$ref');
230
+ // .passthrough() keeps the advertised schema forward-compatible.
231
+ expect(json, `${t.name} additionalProperties`).not.toContain('"additionalProperties":false');
232
+ }
233
+ });
234
+ });
235
+ //# sourceMappingURL=output-schema-listening.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-schema-listening.test.js","sourceRoot":"","sources":["../../src/__tests__/output-schema-listening.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};