rewind-mcp-server 0.1.1 → 0.3.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 (53) hide show
  1. package/README.md +84 -11
  2. package/dist/__tests__/helpers.test.d.ts +1 -0
  3. package/dist/__tests__/helpers.test.js +131 -0
  4. package/dist/__tests__/helpers.test.js.map +1 -0
  5. package/dist/__tests__/image-block.live.d.ts +1 -0
  6. package/dist/__tests__/image-block.live.js +41 -0
  7. package/dist/__tests__/image-block.live.js.map +1 -0
  8. package/dist/__tests__/server.test.js +710 -16
  9. package/dist/__tests__/server.test.js.map +1 -1
  10. package/dist/__tests__/stdio-e2e.live.d.ts +1 -0
  11. package/dist/__tests__/stdio-e2e.live.js +60 -0
  12. package/dist/__tests__/stdio-e2e.live.js.map +1 -0
  13. package/dist/__tests__/ui-resource.live.d.ts +1 -0
  14. package/dist/__tests__/ui-resource.live.js +71 -0
  15. package/dist/__tests__/ui-resource.live.js.map +1 -0
  16. package/dist/client.d.ts +9 -0
  17. package/dist/client.js +15 -0
  18. package/dist/client.js.map +1 -1
  19. package/dist/index.js +0 -0
  20. package/dist/prompts.js +101 -0
  21. package/dist/prompts.js.map +1 -1
  22. package/dist/resources/ui.d.ts +33 -0
  23. package/dist/resources/ui.js +14 -0
  24. package/dist/resources/ui.js.map +1 -0
  25. package/dist/resources.js +128 -0
  26. package/dist/resources.js.map +1 -1
  27. package/dist/server.js +55 -1
  28. package/dist/server.js.map +1 -1
  29. package/dist/tools/collecting.js +78 -37
  30. package/dist/tools/collecting.js.map +1 -1
  31. package/dist/tools/cross-domain.js +74 -17
  32. package/dist/tools/cross-domain.js.map +1 -1
  33. package/dist/tools/debug.d.ts +6 -0
  34. package/dist/tools/debug.js +26 -0
  35. package/dist/tools/debug.js.map +1 -0
  36. package/dist/tools/helpers.d.ts +81 -5
  37. package/dist/tools/helpers.js +138 -4
  38. package/dist/tools/helpers.js.map +1 -1
  39. package/dist/tools/listening.js +223 -52
  40. package/dist/tools/listening.js.map +1 -1
  41. package/dist/tools/reading.js +74 -22
  42. package/dist/tools/reading.js.map +1 -1
  43. package/dist/tools/running.js +87 -43
  44. package/dist/tools/running.js.map +1 -1
  45. package/dist/tools/watching.js +199 -32
  46. package/dist/tools/watching.js.map +1 -1
  47. package/dist/ui-bundles.d.ts +3 -0
  48. package/dist/ui-bundles.js +8 -0
  49. package/dist/ui-bundles.js.map +1 -0
  50. package/dist/worker.d.ts +2 -1
  51. package/dist/worker.js +55 -30
  52. package/dist/worker.js.map +1 -1
  53. package/package.json +13 -2
package/README.md CHANGED
@@ -5,11 +5,13 @@
5
5
  [![docs](https://img.shields.io/badge/docs-docs.rewind.rest-blue)](https://docs.rewind.rest/mcp-server)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow?logo=opensourceinitiative&logoColor=white)](https://opensource.org/licenses/MIT)
7
7
 
8
- MCP server for the [Rewind](https://rewind.rest) personal data API. Gives Claude access to your listening, running, watching, collecting, and reading data.
8
+ MCP server for the [Rewind](https://rewind.rest) personal data API. Gives AI assistants access to your listening, running, watching, collecting, and reading data.
9
9
 
10
10
  ## Setup
11
11
 
12
- Add to your [Claude Desktop config](https://modelcontextprotocol.io/quickstart/user):
12
+ ### Desktop Apps
13
+
14
+ Add to your MCP client config (Claude Desktop, ChatGPT, Gemini, etc.):
13
15
 
14
16
  ```json
15
17
  {
@@ -25,24 +27,65 @@ Add to your [Claude Desktop config](https://modelcontextprotocol.io/quickstart/u
25
27
  }
26
28
  ```
27
29
 
28
- Or with Claude Code:
30
+ ### Mobile & Web
31
+
32
+ Add as a remote integration in your client's settings:
33
+
34
+ - **URL**: `https://mcp.rewind.rest/mcp`
35
+ - **Authorization**: `Bearer rw_live_your_key_here`
36
+
37
+ <details>
38
+ <summary>Claude Code</summary>
29
39
 
30
40
  ```bash
31
41
  claude mcp add rewind -- npx -y rewind-mcp-server
32
42
  ```
33
43
 
44
+ </details>
45
+
34
46
  Requires a [Rewind API key](https://docs.rewind.rest/authentication). `REWIND_API_URL` defaults to `https://api.rewind.rest`.
35
47
 
36
48
  ## Tools
37
49
 
38
- | Domain | Source | Tools |
39
- | ---------------- | ---------------- | -------------------------------------------------------------------------------------------------- |
40
- | **Listening** | Last.fm | Now playing, recent scrobbles, stats, top artists/albums/tracks, streaks, artist and album details |
41
- | **Running** | Strava | Stats, recent runs, personal records, streaks, activity details, per-mile splits |
42
- | **Watching** | Plex, Letterboxd | Recent watches, movie details, browse by genre/decade/director, stats |
43
- | **Collecting** | Discogs, Trakt | Vinyl collection, physical media (Blu-ray/4K/HD DVD), collection stats |
44
- | **Reading** | Instapaper | Recent articles, highlights, random highlight, stats |
45
- | **Cross-domain** | All | Full-text search, unified feed, on-this-day, health check |
50
+ | Domain | Source | Tools |
51
+ | ---------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
52
+ | **Listening** | Last.fm | Now playing, recent scrobbles, stats, top artists/albums/tracks, streaks, artist and album details, genre breakdown over time |
53
+ | **Running** | Strava | Stats, recent runs, personal records, streaks, activity details, per-mile splits, per-year summaries |
54
+ | **Watching** | Plex, Letterboxd | Recent watches, movie details, browse by genre/decade/director, stats, genre/decade/director breakdowns |
55
+ | **Collecting** | Discogs, Trakt | Vinyl collection, physical media (Blu-ray/4K UHD/HD DVD), collection and media stats |
56
+ | **Reading** | Instapaper | Recent articles, highlights, random highlight, stats |
57
+ | **Cross-domain** | All | Full-text search, unified feed, on-this-day |
58
+
59
+ ## Rich responses
60
+
61
+ Tool responses follow the MCP 2025-06-18 content model and include more than plain text:
62
+
63
+ - **Images.** Detail tools (`get_movie_details`, `get_album_details`, `get_artist_details`) and list tools (`get_recent_watches`, `get_recent_listens`, `get_top_albums`, `get_vinyl_collection`, etc.) return cover art / posters / artist imagery as `image` content blocks (top-N on lists, default N=5). Pass `include_images: false` to skip them when keeping responses compact matters.
64
+ - **Resource links.** External platform URLs come back as `resource_link` content blocks rather than being baked into prose -- Letterboxd reviews, Strava activities, Discogs releases, Apple Music pages, original article URLs, Last.fm pages.
65
+ - **Structured content.** Every tool also returns `structuredContent` with a JSON shape that mirrors the underlying API response, so the model can reason over exact numbers without re-parsing prose.
66
+
67
+ ## Entity resources
68
+
69
+ The server exposes `@`-mentionable resources for fetching full detail on any entity:
70
+
71
+ | Entity | URI | Source endpoint |
72
+ | -------------- | -------------------------------- | --------------------------------- |
73
+ | Movie | `rewind://movie/{id}` | `/v1/watching/movies/{id}` |
74
+ | Show | `rewind://show/{id}` | `/v1/watching/shows/{id}` |
75
+ | Album | `rewind://album/{id}` | `/v1/listening/albums/{id}` |
76
+ | Artist | `rewind://artist/{id}` | `/v1/listening/artists/{id}` |
77
+ | Vinyl | `rewind://vinyl/{id}` | `/v1/collecting/vinyl/{id}` |
78
+ | Physical media | `rewind://physical-media/{id}` | `/v1/collecting/media/{id}` |
79
+ | Article | `rewind://article/{id}` | `/v1/reading/articles/{id}` |
80
+ | Activity | `rewind://activity/{id}` | `/v1/running/activities/{id}` |
81
+ | Sync status | `rewind://sync/status` | `/v1/health/sync` |
82
+ | Year in review | `rewind://{domain}/year/{year}` | `/v1/{domain}/year/{year}` |
83
+
84
+ `search` returns `resource_link`s pointing at these URIs so clients can drill from a match straight into the full record.
85
+
86
+ ## Interactive UI (MCP Apps)
87
+
88
+ `get_recent_watches` advertises a [MCP Apps](https://blog.modelcontextprotocol.io/posts/2026-01-26-mcp-apps/) UI resource. On clients that render MCP Apps (Claude Desktop, Claude web, VS Code GitHub Copilot, Goose) the tool returns an interactive poster grid inline in the conversation: thumbhash placeholders, theme-aware cards, clickable Letterboxd reviews, hover states. On clients that do not render MCP Apps the existing text + image + resource_link + structuredContent response is unchanged, so no caller regresses.
46
89
 
47
90
  ## Example Queries
48
91
 
@@ -52,3 +95,33 @@ Requires a [Rewind API key](https://docs.rewind.rest/authentication). `REWIND_AP
52
95
  - "What Beastie Boys records are missing from my vinyl collection?"
53
96
  - "How many articles did I read last year and stack-rank the top 10 sources"
54
97
  - "Can you give me a quick summary of everything I did last week?"
98
+
99
+ ## Prompts
100
+
101
+ The server exposes slash-command prompts that orchestrate multiple tools for common asks:
102
+
103
+ - `weekly-summary` -- rolls up activity across all domains for the past 7 days
104
+ - `year-in-review` -- comprehensive yearly recap
105
+ - `compare-periods` -- parametric comparison between two time windows in a single domain
106
+ - `letterboxd-review-draft` -- drafts a Letterboxd-style review for your most recent unrated film
107
+ - `training-report` -- coach-style running report for the last 7-14 days
108
+ - `film-diet` -- portrait of your film-watching taste (genre mix, decades, directors)
109
+
110
+ ## Authentication
111
+
112
+ **Desktop apps** use the `REWIND_API_KEY` environment variable passed directly to the server process. The server authenticates with the Rewind API using this key as a Bearer token.
113
+
114
+ **Mobile and web clients** connect to the remote server at `mcp.rewind.rest`, which uses OAuth 2.1 with GitHub as the identity provider. The flow:
115
+
116
+ 1. Client discovers endpoints via `/.well-known/oauth-authorization-server`
117
+ 2. Client dynamically registers via `/register`
118
+ 3. User authenticates with GitHub and approves access
119
+ 4. Client receives scoped tokens (1h access, 90d refresh, PKCE S256)
120
+
121
+ The server has **read-only access** to your data. No write or admin operations are exposed.
122
+
123
+ ## Privacy & Support
124
+
125
+ - [Privacy Policy](https://rewind.rest/privacy)
126
+ - [Documentation](https://docs.rewind.rest/mcp-server)
127
+ - [Report an issue](https://github.com/pdugan20/rewind/issues)
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,131 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { text, resourceLink, imageBlock, withRichResponse, withErrorHandling, } from '../tools/helpers.js';
3
+ import { RewindClient } from '../client.js';
4
+ describe('helpers', () => {
5
+ describe('text()', () => {
6
+ it('wraps a string in a text content block', () => {
7
+ expect(text('hello')).toEqual({ type: 'text', text: 'hello' });
8
+ });
9
+ it('accepts an empty string', () => {
10
+ expect(text('')).toEqual({ type: 'text', text: '' });
11
+ });
12
+ });
13
+ describe('resourceLink()', () => {
14
+ it('builds a resource_link block for a valid URI', () => {
15
+ const block = resourceLink('https://letterboxd.com/u/film/', 'Letterboxd review');
16
+ expect(block).toEqual({
17
+ type: 'resource_link',
18
+ uri: 'https://letterboxd.com/u/film/',
19
+ name: 'Letterboxd review',
20
+ });
21
+ });
22
+ it('includes optional mimeType and description', () => {
23
+ const block = resourceLink('https://example.com', 'Example', {
24
+ mimeType: 'text/html',
25
+ description: 'An example site',
26
+ });
27
+ expect(block).toMatchObject({
28
+ type: 'resource_link',
29
+ uri: 'https://example.com',
30
+ name: 'Example',
31
+ mimeType: 'text/html',
32
+ description: 'An example site',
33
+ });
34
+ });
35
+ it('returns null when uri is null/undefined/empty', () => {
36
+ expect(resourceLink(null, 'x')).toBeNull();
37
+ expect(resourceLink(undefined, 'x')).toBeNull();
38
+ expect(resourceLink('', 'x')).toBeNull();
39
+ });
40
+ });
41
+ describe('imageBlock()', () => {
42
+ const bytes = new Uint8Array([0xff, 0xd8, 0xff, 0xe0]); // JPEG magic
43
+ it('base64-encodes bytes fetched from image.cdn_url', async () => {
44
+ const client = new RewindClient('https://api.test', 'rw_test');
45
+ const spy = vi.spyOn(client, 'getBinaryFromUrl').mockResolvedValue({
46
+ bytes,
47
+ mimeType: 'image/jpeg',
48
+ });
49
+ const block = await imageBlock(client, {
50
+ cdn_url: 'https://cdn.rewind.rest/watching/movies/1/original.jpg',
51
+ });
52
+ expect(block).not.toBeNull();
53
+ expect(block?.type).toBe('image');
54
+ expect(block?.mimeType).toBe('image/jpeg');
55
+ expect(block?.data).toBe('/9j/4A==');
56
+ expect(spy).toHaveBeenCalledWith('https://cdn.rewind.rest/watching/movies/1/original.jpg');
57
+ });
58
+ it('falls back to image.url when cdn_url is absent', async () => {
59
+ const client = new RewindClient('https://api.test', 'rw_test');
60
+ const spy = vi.spyOn(client, 'getBinaryFromUrl').mockResolvedValue({
61
+ bytes,
62
+ mimeType: 'image/jpeg',
63
+ });
64
+ await imageBlock(client, { url: 'https://cdn.example/x.jpg' });
65
+ expect(spy).toHaveBeenCalledWith('https://cdn.example/x.jpg');
66
+ });
67
+ it('returns null when image is null or undefined', async () => {
68
+ const client = new RewindClient('https://api.test', 'rw_test');
69
+ expect(await imageBlock(client, null)).toBeNull();
70
+ expect(await imageBlock(client, undefined)).toBeNull();
71
+ });
72
+ it('returns null when image has no cdn_url or url', async () => {
73
+ const client = new RewindClient('https://api.test', 'rw_test');
74
+ expect(await imageBlock(client, { thumbhash: 'x' })).toBeNull();
75
+ });
76
+ it('returns null on fetch failure instead of throwing', async () => {
77
+ const client = new RewindClient('https://api.test', 'rw_test');
78
+ vi.spyOn(client, 'getBinaryFromUrl').mockRejectedValue(new Error('network down'));
79
+ const block = await imageBlock(client, {
80
+ cdn_url: 'https://cdn.example/x.jpg',
81
+ });
82
+ expect(block).toBeNull();
83
+ });
84
+ });
85
+ describe('withRichResponse()', () => {
86
+ it('returns the handler result on success', async () => {
87
+ const result = await withRichResponse(async () => ({
88
+ content: [text('ok')],
89
+ structuredContent: { value: 42 },
90
+ }));
91
+ expect(result).toEqual({
92
+ content: [{ type: 'text', text: 'ok' }],
93
+ structuredContent: { value: 42 },
94
+ });
95
+ });
96
+ it('catches exceptions and returns isError=true', async () => {
97
+ const result = await withRichResponse(async () => {
98
+ throw new Error('boom');
99
+ });
100
+ expect(result.isError).toBe(true);
101
+ expect(result.content).toHaveLength(1);
102
+ expect(result.content[0]).toEqual({
103
+ type: 'text',
104
+ text: 'Error: boom',
105
+ });
106
+ });
107
+ it('handles non-Error throwables', async () => {
108
+ const result = await withRichResponse(async () => {
109
+ throw 'string error';
110
+ });
111
+ expect(result.isError).toBe(true);
112
+ expect(result.content[0].text).toBe('Error: string error');
113
+ });
114
+ });
115
+ describe('withErrorHandling() (legacy text-only wrapper)', () => {
116
+ it('still works for text-only tool handlers', async () => {
117
+ const result = await withErrorHandling(async () => 'plain text');
118
+ expect(result).toEqual({
119
+ content: [{ type: 'text', text: 'plain text' }],
120
+ });
121
+ });
122
+ it('catches and reports errors', async () => {
123
+ const result = await withErrorHandling(async () => {
124
+ throw new Error('nope');
125
+ });
126
+ expect(result.isError).toBe(true);
127
+ expect(result.content[0].text).toBe('Error: nope');
128
+ });
129
+ });
130
+ });
131
+ //# sourceMappingURL=helpers.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.test.js","sourceRoot":"","sources":["../../src/__tests__/helpers.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Live diagnostic: does imageBlock() actually produce a valid image
3
+ * content block when run against the real Rewind CDN? No mocks.
4
+ *
5
+ * Run: npx tsx src/__tests__/image-block.live.ts
6
+ */
7
+ import { RewindClient } from '../client.js';
8
+ import { imageBlock } from '../tools/helpers.js';
9
+ const FIXTURE = {
10
+ movieId: 710,
11
+ title: "I'm Chevy Chase and You're Not",
12
+ cdn_url: 'https://cdn.rewind.rest/watching/movies/710/original.jpg?width=300&height=300&fit=cover&format=auto&quality=85&v=1',
13
+ };
14
+ async function main() {
15
+ // Base URL + api key are irrelevant here -- imageBlock goes through
16
+ // getBinaryFromUrl which hits the full CDN URL without auth.
17
+ const client = new RewindClient('https://api.rewind.rest', 'rw_unused');
18
+ console.log(`Testing imageBlock against live CDN...`);
19
+ console.log(`Fixture: ${FIXTURE.title} (movie ${FIXTURE.movieId})`);
20
+ console.log(`URL: ${FIXTURE.cdn_url}\n`);
21
+ const start = Date.now();
22
+ const block = await imageBlock(client, { cdn_url: FIXTURE.cdn_url });
23
+ const ms = Date.now() - start;
24
+ if (!block) {
25
+ console.error(`FAIL: imageBlock returned null (fetched in ${ms}ms)`);
26
+ process.exit(1);
27
+ }
28
+ console.log(`SUCCESS (${ms}ms)`);
29
+ console.log(`- type: ${block.type}`);
30
+ console.log(`- mimeType: ${block.mimeType}`);
31
+ console.log(`- data size: ${block.data.length} base64 chars`);
32
+ console.log(`- sniff: ${block.data.slice(0, 24)}...`);
33
+ // JPEG base64 starts with /9j/
34
+ const looksLikeJpeg = block.data.startsWith('/9j/');
35
+ console.log(`- JPEG? ${looksLikeJpeg ? 'yes (starts with /9j/)' : 'NO -- unexpected'}`);
36
+ }
37
+ main().catch((err) => {
38
+ console.error('Unexpected error:', err);
39
+ process.exit(1);
40
+ });
41
+ //# sourceMappingURL=image-block.live.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-block.live.js","sourceRoot":"","sources":["../../src/__tests__/image-block.live.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD,MAAM,OAAO,GAAG;IACd,OAAO,EAAE,GAAG;IACZ,KAAK,EAAE,gCAAgC;IACvC,OAAO,EACL,oHAAoH;CACvH,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,oEAAoE;IACpE,6DAA6D;IAC7D,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,yBAAyB,EAAE,WAAW,CAAC,CAAC;IAExE,OAAO,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC;IACtD,OAAO,CAAC,GAAG,CAAC,YAAY,OAAO,CAAC,KAAK,WAAW,OAAO,CAAC,OAAO,GAAG,CAAC,CAAC;IACpE,OAAO,CAAC,GAAG,CAAC,QAAQ,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC;IAEzC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACrE,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;IAE9B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,8CAA8C,EAAE,KAAK,CAAC,CAAC;QACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IACjC,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1C,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC9C,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,CAAC,IAAI,CAAC,MAAM,eAAe,CAAC,CAAC;IAC9D,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;IAC1D,+BAA+B;IAC/B,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CACT,gBAAgB,aAAa,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,kBAAkB,EAAE,CAChF,CAAC;AACJ,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC;IACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}