rewind-mcp-server 0.9.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 (94) 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/prompts.js +60 -32
  39. package/dist/prompts.js.map +1 -1
  40. package/dist/resources.js +26 -13
  41. package/dist/resources.js.map +1 -1
  42. package/dist/server.js +16 -13
  43. package/dist/server.js.map +1 -1
  44. package/dist/tools/attending.js +124 -90
  45. package/dist/tools/attending.js.map +1 -1
  46. package/dist/tools/collecting.js +85 -49
  47. package/dist/tools/collecting.js.map +1 -1
  48. package/dist/tools/cross-domain.js +103 -66
  49. package/dist/tools/cross-domain.js.map +1 -1
  50. package/dist/tools/helpers.d.ts +6 -2
  51. package/dist/tools/helpers.js +20 -11
  52. package/dist/tools/helpers.js.map +1 -1
  53. package/dist/tools/listening.js +94 -42
  54. package/dist/tools/listening.js.map +1 -1
  55. package/dist/tools/reading.js +53 -26
  56. package/dist/tools/reading.js.map +1 -1
  57. package/dist/tools/running.js +71 -28
  58. package/dist/tools/running.js.map +1 -1
  59. package/dist/tools/schemas/attending.d.ts +8225 -0
  60. package/dist/tools/schemas/attending.js +404 -0
  61. package/dist/tools/schemas/attending.js.map +1 -0
  62. package/dist/tools/schemas/collecting.d.ts +1011 -0
  63. package/dist/tools/schemas/collecting.js +108 -0
  64. package/dist/tools/schemas/collecting.js.map +1 -0
  65. package/dist/tools/schemas/cross-domain.d.ts +1001 -0
  66. package/dist/tools/schemas/cross-domain.js +107 -0
  67. package/dist/tools/schemas/cross-domain.js.map +1 -0
  68. package/dist/tools/schemas/listening.d.ts +4298 -0
  69. package/dist/tools/schemas/listening.js +255 -0
  70. package/dist/tools/schemas/listening.js.map +1 -0
  71. package/dist/tools/schemas/reading.d.ts +1421 -0
  72. package/dist/tools/schemas/reading.js +151 -0
  73. package/dist/tools/schemas/reading.js.map +1 -0
  74. package/dist/tools/schemas/running.d.ts +680 -0
  75. package/dist/tools/schemas/running.js +154 -0
  76. package/dist/tools/schemas/running.js.map +1 -0
  77. package/dist/tools/schemas/shared.d.ts +98 -0
  78. package/dist/tools/schemas/shared.js +74 -0
  79. package/dist/tools/schemas/shared.js.map +1 -0
  80. package/dist/tools/schemas/system.d.ts +57 -0
  81. package/dist/tools/schemas/system.js +22 -0
  82. package/dist/tools/schemas/system.js.map +1 -0
  83. package/dist/tools/schemas/watching.d.ts +1821 -0
  84. package/dist/tools/schemas/watching.js +167 -0
  85. package/dist/tools/schemas/watching.js.map +1 -0
  86. package/dist/tools/watching.js +87 -46
  87. package/dist/tools/watching.js.map +1 -1
  88. package/dist/ui-bundles.d.ts +10 -11
  89. package/dist/ui-bundles.js +20 -22
  90. package/dist/ui-bundles.js.map +1 -1
  91. package/package.json +1 -1
  92. package/dist/tools/debug.d.ts +0 -6
  93. package/dist/tools/debug.js +0 -26
  94. package/dist/tools/debug.js.map +0 -1
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Output-schema conformance — reading domain (issue #105).
3
+ *
4
+ * For every reading 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 articleFixture = {
20
+ id: 1,
21
+ title: 'Test Article',
22
+ author: 'Test Author',
23
+ url: 'https://example.com/article',
24
+ instapaper_url: 'https://instapaper.com/read/1',
25
+ instapaper_app_url: null,
26
+ domain: 'example.com',
27
+ description: 'A test article description.',
28
+ estimated_read_min: 5,
29
+ status: 'unread',
30
+ progress: 0,
31
+ image: null,
32
+ saved_at: '2026-05-15T12:00:00Z',
33
+ };
34
+ const highlightFixture = {
35
+ text: 'A highlighted passage.',
36
+ note: 'My note.',
37
+ created_at: '2026-05-15T12:00:00Z',
38
+ article: {
39
+ id: 1,
40
+ title: 'Test Article',
41
+ author: 'Test Author',
42
+ domain: 'example.com',
43
+ url: 'https://example.com/article',
44
+ instapaper_url: 'https://instapaper.com/read/1',
45
+ instapaper_app_url: null,
46
+ },
47
+ };
48
+ const articleDetailFixture = {
49
+ id: 1,
50
+ title: 'Test Article',
51
+ author: 'Test Author',
52
+ url: 'https://example.com/article',
53
+ instapaper_url: 'https://instapaper.com/read/1',
54
+ instapaper_app_url: null,
55
+ domain: 'example.com',
56
+ description: 'A test article description.',
57
+ content: 'The full article body text.',
58
+ excerpt: 'An excerpt.',
59
+ word_count: 1200,
60
+ estimated_read_min: 5,
61
+ status: 'unread',
62
+ progress: 0,
63
+ saved_at: '2026-05-15T12:00:00Z',
64
+ image: null,
65
+ highlights: [
66
+ {
67
+ id: 10,
68
+ text: 'A highlighted passage.',
69
+ note: 'My note.',
70
+ created_at: '2026-05-15T12:00:00Z',
71
+ },
72
+ ],
73
+ };
74
+ const relatedFixture = {
75
+ id: 2,
76
+ title: 'Related Article',
77
+ author: 'Other Author',
78
+ url: 'https://example.com/related',
79
+ instapaper_url: 'https://instapaper.com/read/2',
80
+ instapaper_app_url: null,
81
+ domain: 'example.com',
82
+ description: 'A related article.',
83
+ score: 0.87,
84
+ };
85
+ const ROUTES = {
86
+ '/reading/recent': { data: [articleFixture] },
87
+ '/reading/highlights': {
88
+ data: [highlightFixture],
89
+ pagination: { page: 1, limit: 10, total: 1, total_pages: 1 },
90
+ },
91
+ '/reading/highlights/random': highlightFixture,
92
+ '/reading/stats': {
93
+ total_articles: 100,
94
+ finished_count: 40,
95
+ currently_reading_count: 3,
96
+ total_highlights: 250,
97
+ total_word_count: 500000,
98
+ avg_estimated_read_min: 6,
99
+ },
100
+ };
101
+ function resolveRoute(path) {
102
+ if (/^\/reading\/articles\/\d+\/related$/.test(path))
103
+ return { data: [relatedFixture] };
104
+ if (/^\/reading\/articles\/\d+$/.test(path))
105
+ return articleDetailFixture;
106
+ return ROUTES[path] ?? {};
107
+ }
108
+ async function buildClient() {
109
+ const rewindClient = new RewindClient('https://api.test', 'rw_test');
110
+ vi.spyOn(rewindClient, 'get').mockImplementation(async (path) => resolveRoute(path));
111
+ const server = createServer(rewindClient);
112
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
113
+ const client = new Client({ name: 'output-schema-test', version: '1.0.0' });
114
+ await server.connect(serverTransport);
115
+ await client.connect(clientTransport);
116
+ return client;
117
+ }
118
+ const CASES = [
119
+ { name: 'get_article', args: { id: 1 } },
120
+ { name: 'get_recent_reads', args: { include_images: false } },
121
+ { name: 'get_reading_highlights', args: {} },
122
+ { name: 'get_random_highlight', args: {} },
123
+ { name: 'get_reading_stats', args: {} },
124
+ { name: 'find_similar_articles', args: { article_id: 1 } },
125
+ ];
126
+ describe('output-schema conformance — reading', () => {
127
+ for (const c of CASES) {
128
+ it(`${c.name}: structuredContent conforms to outputSchema`, async () => {
129
+ const client = await buildClient();
130
+ // A schema mismatch makes the SDK's validateToolOutput throw and this
131
+ // call reject -- resolving without error IS the conformance check.
132
+ const res = await client.callTool({ name: c.name, arguments: c.args });
133
+ expect(res.isError).toBeFalsy();
134
+ expect(res.structuredContent).toBeDefined();
135
+ });
136
+ }
137
+ it('empty-state branches still conform', async () => {
138
+ const rewindClient = new RewindClient('https://api.test', 'rw_test');
139
+ vi.spyOn(rewindClient, 'get').mockImplementation(async (path) => {
140
+ if (path === '/reading/recent')
141
+ return { data: [] };
142
+ if (path === '/reading/highlights')
143
+ return {
144
+ data: [],
145
+ pagination: { page: 1, limit: 10, total: 0, total_pages: 0 },
146
+ };
147
+ if (/^\/reading\/articles\/\d+\/related$/.test(path))
148
+ return { data: [] };
149
+ return {};
150
+ });
151
+ const server = createServer(rewindClient);
152
+ const [ct, st] = InMemoryTransport.createLinkedPair();
153
+ const client = new Client({ name: 'empty-test', version: '1.0.0' });
154
+ await server.connect(st);
155
+ await client.connect(ct);
156
+ for (const [name, args] of [
157
+ ['get_recent_reads', { include_images: false }],
158
+ ['get_reading_highlights', {}],
159
+ ['find_similar_articles', { article_id: 1 }],
160
+ ]) {
161
+ const res = await client.callTool({ name, arguments: args });
162
+ expect(res.isError, name).toBeFalsy();
163
+ }
164
+ });
165
+ it('every reading tool advertises a clean outputSchema', async () => {
166
+ const client = await buildClient();
167
+ const { tools } = await client.listTools();
168
+ const names = new Set(CASES.map((c) => c.name));
169
+ const reading = tools.filter((t) => names.has(t.name));
170
+ expect(reading).toHaveLength(CASES.length);
171
+ for (const t of reading) {
172
+ expect(t.outputSchema, t.name).toMatchObject({ type: 'object' });
173
+ const json = JSON.stringify(t.outputSchema);
174
+ // No $ref/$defs: older Claude Desktop builds failed to compile them.
175
+ expect(json, `${t.name} $ref`).not.toContain('$ref');
176
+ // .passthrough() keeps the advertised schema forward-compatible.
177
+ expect(json, `${t.name} additionalProperties`).not.toContain('"additionalProperties":false');
178
+ }
179
+ });
180
+ });
181
+ //# sourceMappingURL=output-schema-reading.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-schema-reading.test.js","sourceRoot":"","sources":["../../src/__tests__/output-schema-reading.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Output-schema conformance — running domain (issue #105).
3
+ *
4
+ * For every running 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 copied from 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 activityFixture = {
20
+ id: 1,
21
+ strava_id: 1001,
22
+ name: 'Morning Run',
23
+ date: '2026-05-15T08:00:00Z',
24
+ distance_mi: 5.2,
25
+ duration: '42:10',
26
+ pace: '8:06',
27
+ elevation_ft: 120,
28
+ city: 'Brooklyn',
29
+ state: 'NY',
30
+ is_race: false,
31
+ strava_url: 'https://strava.com/activities/1001',
32
+ };
33
+ const prFixture = {
34
+ distance_label: '5K',
35
+ time: '21:30',
36
+ pace: '6:55',
37
+ date: '2026-04-01T08:00:00Z',
38
+ activity_name: 'Spring 5K',
39
+ activity_id: 2,
40
+ };
41
+ const splitFixture = {
42
+ split: 1,
43
+ distance_mi: 1.0,
44
+ moving_time_s: 480,
45
+ elapsed_time_s: 485,
46
+ elevation_ft: 25,
47
+ pace: '8:00',
48
+ heartrate: 152,
49
+ };
50
+ const activityDetailFixture = {
51
+ id: 1,
52
+ name: 'Morning Run',
53
+ date: '2026-05-15T08:00:00Z',
54
+ distance_mi: 5.2,
55
+ duration: '42:10',
56
+ pace: '8:06',
57
+ elevation_ft: 120,
58
+ heartrate_avg: 150,
59
+ heartrate_max: 172,
60
+ cadence: 168,
61
+ calories: 540,
62
+ suffer_score: 45,
63
+ city: 'Brooklyn',
64
+ state: 'NY',
65
+ is_race: false,
66
+ workout_type: 'Run',
67
+ strava_url: 'https://strava.com/activities/1001',
68
+ };
69
+ const yearFixture = {
70
+ year: 2025,
71
+ total_runs: 180,
72
+ total_distance_mi: 920.5,
73
+ total_elevation_ft: 18000,
74
+ total_duration_s: 432000,
75
+ avg_pace: '8:12',
76
+ longest_run_mi: 26.2,
77
+ race_count: 4,
78
+ };
79
+ const ROUTES = {
80
+ '/running/stats': {
81
+ data: {
82
+ total_runs: 500,
83
+ total_distance_mi: 2500.5,
84
+ total_elevation_ft: 50000,
85
+ total_duration: '300:00:00',
86
+ avg_pace: '8:10',
87
+ years_active: 5,
88
+ first_run: '2021-01-01T08:00:00Z',
89
+ eddington_number: 30,
90
+ },
91
+ },
92
+ '/running/recent': { data: [activityFixture] },
93
+ '/running/prs': { data: [prFixture] },
94
+ '/running/streaks': {
95
+ data: {
96
+ current: { days: 5, start: '2026-05-10', end: '2026-05-15' },
97
+ longest: { days: 30, start: '2025-01-01', end: '2025-01-31' },
98
+ },
99
+ },
100
+ '/running/stats/years': { data: [yearFixture] },
101
+ };
102
+ function resolveRoute(path) {
103
+ if (/^\/running\/activities\/\d+\/splits$/.test(path)) {
104
+ return { data: [splitFixture] };
105
+ }
106
+ if (/^\/running\/activities\/\d+$/.test(path)) {
107
+ return activityDetailFixture;
108
+ }
109
+ return ROUTES[path] ?? {};
110
+ }
111
+ async function buildClient() {
112
+ const rewindClient = new RewindClient('https://api.test', 'rw_test');
113
+ vi.spyOn(rewindClient, 'get').mockImplementation(async (path) => resolveRoute(path));
114
+ const server = createServer(rewindClient);
115
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
116
+ const client = new Client({ name: 'output-schema-test', version: '1.0.0' });
117
+ await server.connect(serverTransport);
118
+ await client.connect(clientTransport);
119
+ return client;
120
+ }
121
+ const CASES = [
122
+ { name: 'get_running_stats', args: {} },
123
+ { name: 'get_recent_runs', args: {} },
124
+ { name: 'get_personal_records', args: {} },
125
+ { name: 'get_running_streaks', args: {} },
126
+ { name: 'get_activity_details', args: { id: 1 } },
127
+ { name: 'get_activity_splits', args: { id: 1 } },
128
+ { name: 'get_running_years', args: {} },
129
+ ];
130
+ describe('output-schema conformance — running', () => {
131
+ for (const c of CASES) {
132
+ it(`${c.name}: structuredContent conforms to outputSchema`, async () => {
133
+ const client = await buildClient();
134
+ // A schema mismatch makes the SDK's validateToolOutput throw and this
135
+ // call reject -- resolving without error IS the conformance check.
136
+ const res = await client.callTool({ name: c.name, arguments: c.args });
137
+ expect(res.isError).toBeFalsy();
138
+ expect(res.structuredContent).toBeDefined();
139
+ });
140
+ }
141
+ it('empty-state branches still conform', async () => {
142
+ const rewindClient = new RewindClient('https://api.test', 'rw_test');
143
+ vi.spyOn(rewindClient, 'get').mockImplementation(async (path) => {
144
+ if (path === '/running/recent')
145
+ return { data: [] };
146
+ if (path === '/running/prs')
147
+ return { data: [] };
148
+ if (path === '/running/stats/years')
149
+ return { data: [] };
150
+ if (/^\/running\/activities\/\d+\/splits$/.test(path))
151
+ return { data: [] };
152
+ return {};
153
+ });
154
+ const server = createServer(rewindClient);
155
+ const [ct, st] = InMemoryTransport.createLinkedPair();
156
+ const client = new Client({ name: 'empty-test', version: '1.0.0' });
157
+ await server.connect(st);
158
+ await client.connect(ct);
159
+ for (const [name, args] of [
160
+ ['get_recent_runs', {}],
161
+ ['get_personal_records', {}],
162
+ ['get_activity_splits', { id: 1 }],
163
+ ['get_running_years', {}],
164
+ ]) {
165
+ const res = await client.callTool({ name, arguments: args });
166
+ expect(res.isError, name).toBeFalsy();
167
+ }
168
+ });
169
+ it('every running tool advertises a clean outputSchema', async () => {
170
+ const client = await buildClient();
171
+ const { tools } = await client.listTools();
172
+ const names = new Set(CASES.map((c) => c.name));
173
+ const running = tools.filter((t) => names.has(t.name));
174
+ expect(running).toHaveLength(CASES.length);
175
+ for (const t of running) {
176
+ expect(t.outputSchema, t.name).toMatchObject({ type: 'object' });
177
+ const json = JSON.stringify(t.outputSchema);
178
+ // No $ref/$defs: older Claude Desktop builds failed to compile them.
179
+ expect(json, `${t.name} $ref`).not.toContain('$ref');
180
+ // .passthrough() keeps the advertised schema forward-compatible.
181
+ expect(json, `${t.name} additionalProperties`).not.toContain('"additionalProperties":false');
182
+ }
183
+ });
184
+ });
185
+ //# sourceMappingURL=output-schema-running.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-schema-running.test.js","sourceRoot":"","sources":["../../src/__tests__/output-schema-running.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Output-schema conformance — watching domain (issue #105, Phase 2).
3
+ *
4
+ * For every watching 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
+ * Copied from the output-schema-listening.test.ts template.
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 recentWatchFixture = {
20
+ movie: {
21
+ id: 1,
22
+ title: 'Test Movie',
23
+ year: 2024,
24
+ director: 'Test Director',
25
+ tmdb_id: 100,
26
+ summary: 'A test movie.',
27
+ tagline: 'A test tagline.',
28
+ image: null,
29
+ },
30
+ watched_at: '2026-05-15T12:00:00Z',
31
+ user_rating: 4,
32
+ rewatch: false,
33
+ source: 'plex',
34
+ review: null,
35
+ review_url: null,
36
+ };
37
+ const movieDetailFixture = {
38
+ id: 1,
39
+ title: 'Test Movie',
40
+ year: 2024,
41
+ director: 'Test Director',
42
+ directors: ['Test Director'],
43
+ genres: ['Drama'],
44
+ duration_min: 120,
45
+ rating: 'PG-13',
46
+ tmdb_id: 100,
47
+ tmdb_rating: 7.5,
48
+ tagline: 'A test tagline.',
49
+ summary: 'A test movie.',
50
+ imdb_id: 'tt1234567',
51
+ image: null,
52
+ watch_history: [
53
+ {
54
+ watched_at: '2026-05-15T12:00:00Z',
55
+ user_rating: 4,
56
+ rewatch: false,
57
+ review: null,
58
+ review_url: null,
59
+ source: 'plex',
60
+ },
61
+ ],
62
+ };
63
+ const browseMovieFixture = {
64
+ id: 1,
65
+ title: 'Test Movie',
66
+ year: 2024,
67
+ director: 'Test Director',
68
+ genres: ['Drama'],
69
+ duration_min: 120,
70
+ tmdb_id: 100,
71
+ tmdb_rating: 7.5,
72
+ image: null,
73
+ };
74
+ const paginationFixture = {
75
+ page: 1,
76
+ limit: 10,
77
+ total: 1,
78
+ total_pages: 1,
79
+ };
80
+ const ROUTES = {
81
+ '/watching/recent': { data: [recentWatchFixture] },
82
+ '/watching/stats': {
83
+ data: {
84
+ total_movies: 100,
85
+ total_watch_time_hours: 200,
86
+ movies_this_year: 12,
87
+ avg_per_month: 8.3,
88
+ top_genre: 'Drama',
89
+ top_decade: 2010,
90
+ top_director: 'Test Director',
91
+ total_shows: 5,
92
+ total_episodes_watched: 60,
93
+ episodes_this_year: 20,
94
+ },
95
+ },
96
+ '/watching/movies': {
97
+ data: [browseMovieFixture],
98
+ pagination: paginationFixture,
99
+ },
100
+ '/watching/stats/genres': {
101
+ data: [{ name: 'Drama', count: 40, percentage: 40 }],
102
+ },
103
+ '/watching/stats/decades': { data: [{ decade: 2010, count: 30 }] },
104
+ '/watching/stats/directors': {
105
+ data: [{ name: 'Test Director', count: 8 }],
106
+ },
107
+ };
108
+ function resolveRoute(path) {
109
+ if (path.startsWith('/watching/movies/'))
110
+ return movieDetailFixture;
111
+ return ROUTES[path] ?? {};
112
+ }
113
+ async function buildClient() {
114
+ const rewindClient = new RewindClient('https://api.test', 'rw_test');
115
+ vi.spyOn(rewindClient, 'get').mockImplementation(async (path) => resolveRoute(path));
116
+ const server = createServer(rewindClient);
117
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
118
+ const client = new Client({ name: 'output-schema-test', version: '1.0.0' });
119
+ await server.connect(serverTransport);
120
+ await client.connect(clientTransport);
121
+ return client;
122
+ }
123
+ const CASES = [
124
+ { name: 'get_recent_watches', args: { include_images: false } },
125
+ { name: 'get_movie_details', args: { id: 1, include_images: false } },
126
+ { name: 'get_watching_stats', args: {} },
127
+ { name: 'browse_movies', args: { include_images: false } },
128
+ { name: 'get_watching_genres', args: {} },
129
+ { name: 'get_watching_decades', args: {} },
130
+ { name: 'get_watching_directors', args: {} },
131
+ ];
132
+ describe('output-schema conformance — watching', () => {
133
+ for (const c of CASES) {
134
+ it(`${c.name}: structuredContent conforms to outputSchema`, async () => {
135
+ const client = await buildClient();
136
+ // A schema mismatch makes the SDK's validateToolOutput throw and this
137
+ // call reject -- resolving without error IS the conformance check.
138
+ const res = await client.callTool({ name: c.name, arguments: c.args });
139
+ expect(res.isError).toBeFalsy();
140
+ expect(res.structuredContent).toBeDefined();
141
+ });
142
+ }
143
+ it('empty-state branches still conform', async () => {
144
+ const rewindClient = new RewindClient('https://api.test', 'rw_test');
145
+ vi.spyOn(rewindClient, 'get').mockImplementation(async (path) => {
146
+ if (path === '/watching/recent')
147
+ return { data: [] };
148
+ if (path === '/watching/movies')
149
+ return { data: [], pagination: paginationFixture };
150
+ if (path === '/watching/stats/genres')
151
+ return { data: [] };
152
+ if (path === '/watching/stats/decades')
153
+ return { data: [] };
154
+ if (path === '/watching/stats/directors')
155
+ return { data: [] };
156
+ return {};
157
+ });
158
+ const server = createServer(rewindClient);
159
+ const [ct, st] = InMemoryTransport.createLinkedPair();
160
+ const client = new Client({ name: 'empty-test', version: '1.0.0' });
161
+ await server.connect(st);
162
+ await client.connect(ct);
163
+ for (const [name, args] of [
164
+ ['get_recent_watches', { include_images: false }],
165
+ ['browse_movies', { include_images: false }],
166
+ ['get_watching_genres', {}],
167
+ ['get_watching_decades', {}],
168
+ ['get_watching_directors', {}],
169
+ ]) {
170
+ const res = await client.callTool({ name, arguments: args });
171
+ expect(res.isError, name).toBeFalsy();
172
+ }
173
+ });
174
+ it('every watching tool advertises a clean outputSchema', async () => {
175
+ const client = await buildClient();
176
+ const { tools } = await client.listTools();
177
+ const names = new Set(CASES.map((c) => c.name));
178
+ const watching = tools.filter((t) => names.has(t.name));
179
+ expect(watching).toHaveLength(CASES.length);
180
+ for (const t of watching) {
181
+ expect(t.outputSchema, t.name).toMatchObject({ type: 'object' });
182
+ const json = JSON.stringify(t.outputSchema);
183
+ // No $ref/$defs: older Claude Desktop builds failed to compile them.
184
+ expect(json, `${t.name} $ref`).not.toContain('$ref');
185
+ // .passthrough() keeps the advertised schema forward-compatible.
186
+ expect(json, `${t.name} additionalProperties`).not.toContain('"additionalProperties":false');
187
+ }
188
+ });
189
+ });
190
+ //# sourceMappingURL=output-schema-watching.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-schema-watching.test.js","sourceRoot":"","sources":["../../src/__tests__/output-schema-watching.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,6EAA6E;AAE7E,MAAM,kBAAkB,GAAG;IACzB,KAAK,EAAE;QACL,EAAE,EAAE,CAAC;QACL,KAAK,EAAE,YAAY;QACnB,IAAI,EAAE,IAAI;QACV,QAAQ,EAAE,eAAe;QACzB,OAAO,EAAE,GAAG;QACZ,OAAO,EAAE,eAAe;QACxB,OAAO,EAAE,iBAAiB;QAC1B,KAAK,EAAE,IAAI;KACZ;IACD,UAAU,EAAE,sBAAsB;IAClC,WAAW,EAAE,CAAC;IACd,OAAO,EAAE,KAAK;IACd,MAAM,EAAE,MAAM;IACd,MAAM,EAAE,IAAI;IACZ,UAAU,EAAE,IAAI;CACjB,CAAC;AAEF,MAAM,kBAAkB,GAAG;IACzB,EAAE,EAAE,CAAC;IACL,KAAK,EAAE,YAAY;IACnB,IAAI,EAAE,IAAI;IACV,QAAQ,EAAE,eAAe;IACzB,SAAS,EAAE,CAAC,eAAe,CAAC;IAC5B,MAAM,EAAE,CAAC,OAAO,CAAC;IACjB,YAAY,EAAE,GAAG;IACjB,MAAM,EAAE,OAAO;IACf,OAAO,EAAE,GAAG;IACZ,WAAW,EAAE,GAAG;IAChB,OAAO,EAAE,iBAAiB;IAC1B,OAAO,EAAE,eAAe;IACxB,OAAO,EAAE,WAAW;IACpB,KAAK,EAAE,IAAI;IACX,aAAa,EAAE;QACb;YACE,UAAU,EAAE,sBAAsB;YAClC,WAAW,EAAE,CAAC;YACd,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,IAAI;YACZ,UAAU,EAAE,IAAI;YAChB,MAAM,EAAE,MAAM;SACf;KACF;CACF,CAAC;AAEF,MAAM,kBAAkB,GAAG;IACzB,EAAE,EAAE,CAAC;IACL,KAAK,EAAE,YAAY;IACnB,IAAI,EAAE,IAAI;IACV,QAAQ,EAAE,eAAe;IACzB,MAAM,EAAE,CAAC,OAAO,CAAC;IACjB,YAAY,EAAE,GAAG;IACjB,OAAO,EAAE,GAAG;IACZ,WAAW,EAAE,GAAG;IAChB,KAAK,EAAE,IAAI;CACZ,CAAC;AAEF,MAAM,iBAAiB,GAAG;IACxB,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,EAAE;IACT,KAAK,EAAE,CAAC;IACR,WAAW,EAAE,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,GAA4B;IACtC,kBAAkB,EAAE,EAAE,IAAI,EAAE,CAAC,kBAAkB,CAAC,EAAE;IAClD,iBAAiB,EAAE;QACjB,IAAI,EAAE;YACJ,YAAY,EAAE,GAAG;YACjB,sBAAsB,EAAE,GAAG;YAC3B,gBAAgB,EAAE,EAAE;YACpB,aAAa,EAAE,GAAG;YAClB,SAAS,EAAE,OAAO;YAClB,UAAU,EAAE,IAAI;YAChB,YAAY,EAAE,eAAe;YAC7B,WAAW,EAAE,CAAC;YACd,sBAAsB,EAAE,EAAE;YAC1B,kBAAkB,EAAE,EAAE;SACvB;KACF;IACD,kBAAkB,EAAE;QAClB,IAAI,EAAE,CAAC,kBAAkB,CAAC;QAC1B,UAAU,EAAE,iBAAiB;KAC9B;IACD,wBAAwB,EAAE;QACxB,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;KACrD;IACD,yBAAyB,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,EAAE;IAClE,2BAA2B,EAAE;QAC3B,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;KAC5C;CACF,CAAC;AAEF,SAAS,YAAY,CAAC,IAAY;IAChC,IAAI,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC;QAAE,OAAO,kBAAkB,CAAC;IACpE,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;AAC5B,CAAC;AAED,KAAK,UAAU,WAAW;IACxB,MAAM,YAAY,GAAG,IAAI,YAAY,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;IACrE,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE,CACtE,YAAY,CAAC,IAAI,CAAC,CACnB,CAAC;IACF,MAAM,MAAM,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;IAC1C,MAAM,CAAC,eAAe,EAAE,eAAe,CAAC,GACtC,iBAAiB,CAAC,gBAAgB,EAAE,CAAC;IACvC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAC5E,MAAM,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IACtC,MAAM,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IACtC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,KAAK,GAA2D;IACpE,EAAE,IAAI,EAAE,oBAAoB,EAAE,IAAI,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE;IAC/D,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE;IACrE,EAAE,IAAI,EAAE,oBAAoB,EAAE,IAAI,EAAE,EAAE,EAAE;IACxC,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE;IAC1D,EAAE,IAAI,EAAE,qBAAqB,EAAE,IAAI,EAAE,EAAE,EAAE;IACzC,EAAE,IAAI,EAAE,sBAAsB,EAAE,IAAI,EAAE,EAAE,EAAE;IAC1C,EAAE,IAAI,EAAE,wBAAwB,EAAE,IAAI,EAAE,EAAE,EAAE;CAC7C,CAAC;AAEF,QAAQ,CAAC,sCAAsC,EAAE,GAAG,EAAE;IACpD,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,8CAA8C,EAAE,KAAK,IAAI,EAAE;YACrE,MAAM,MAAM,GAAG,MAAM,WAAW,EAAE,CAAC;YACnC,sEAAsE;YACtE,mEAAmE;YACnE,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YACvE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC;YAChC,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,WAAW,EAAE,CAAC;QAC9C,CAAC,CAAC,CAAC;IACL,CAAC;IAED,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAClD,MAAM,YAAY,GAAG,IAAI,YAAY,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;QACrE,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE;YACtE,IAAI,IAAI,KAAK,kBAAkB;gBAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;YACrD,IAAI,IAAI,KAAK,kBAAkB;gBAC7B,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,UAAU,EAAE,iBAAiB,EAAE,CAAC;YACrD,IAAI,IAAI,KAAK,wBAAwB;gBAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;YAC3D,IAAI,IAAI,KAAK,yBAAyB;gBAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;YAC5D,IAAI,IAAI,KAAK,2BAA2B;gBAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;YAC9D,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;QAC1C,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,iBAAiB,CAAC,gBAAgB,EAAE,CAAC;QACtD,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QACpE,MAAM,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACzB,MAAM,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAEzB,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI;YACzB,CAAC,oBAAoB,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;YACjD,CAAC,eAAe,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;YAC5C,CAAC,qBAAqB,EAAE,EAAE,CAAC;YAC3B,CAAC,sBAAsB,EAAE,EAAE,CAAC;YAC5B,CAAC,wBAAwB,EAAE,EAAE,CAAC;SACtB,EAAE,CAAC;YACX,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC7D,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;QACxC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,MAAM,GAAG,MAAM,WAAW,EAAE,CAAC;QACnC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAChD,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACxD,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAE5C,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YACzB,MAAM,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;YACjE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;YAC5C,qEAAqE;YACrE,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YACrD,iEAAiE;YACjE,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,uBAAuB,CAAC,CAAC,GAAG,CAAC,SAAS,CAC1D,8BAA8B,CAC/B,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};