@pipeworx/mcp-rick-and-morty 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +55 -0
- package/package.json +20 -0
- package/server.json +18 -0
- package/src/index.ts +289 -0
- package/tsconfig.json +14 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pipeworx
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# mcp-rick-and-morty
|
|
2
|
+
|
|
3
|
+
Rick and Morty MCP — wraps the Rick and Morty API (free, no auth)
|
|
4
|
+
|
|
5
|
+
Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 965+ live data sources.
|
|
6
|
+
|
|
7
|
+
## Tools
|
|
8
|
+
|
|
9
|
+
| Tool | Description |
|
|
10
|
+
|------|-------------|
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
Add to your MCP client (Claude Desktop, Cursor, Windsurf, etc.):
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mcpServers": {
|
|
19
|
+
"rick-and-morty": {
|
|
20
|
+
"url": "https://gateway.pipeworx.io/rick-and-morty/mcp"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or connect to the full Pipeworx gateway for access to all 965+ data sources:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"pipeworx": {
|
|
32
|
+
"url": "https://gateway.pipeworx.io/mcp"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Using with ask_pipeworx
|
|
39
|
+
|
|
40
|
+
Instead of calling tools directly, you can ask questions in plain English:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
ask_pipeworx({ question: "your question about Rick And Morty data" })
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The gateway picks the right tool and fills the arguments automatically.
|
|
47
|
+
|
|
48
|
+
## More
|
|
49
|
+
|
|
50
|
+
- [All tools and guides](https://github.com/pipeworx-io/examples)
|
|
51
|
+
- [pipeworx.io](https://pipeworx.io)
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pipeworx/mcp-rick-and-morty",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Rick and Morty MCP — wraps the Rick and Morty API (free, no auth)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"keywords": ["mcp", "mcp-server", "model-context-protocol", "pipeworx", "rick-and-morty"],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/pipeworx-io/mcp-rick-and-morty"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"typescript": "^5.7.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.pipeworx-io/rick-and-morty",
|
|
4
|
+
"title": "Rick And Morty",
|
|
5
|
+
"description": "Rick and Morty MCP — wraps the Rick and Morty API (free, no auth)",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"websiteUrl": "https://pipeworx.io/packs/rick-and-morty",
|
|
8
|
+
"repository": {
|
|
9
|
+
"url": "https://github.com/pipeworx-io/mcp-rick-and-morty",
|
|
10
|
+
"source": "github"
|
|
11
|
+
},
|
|
12
|
+
"remotes": [
|
|
13
|
+
{
|
|
14
|
+
"type": "streamable-http",
|
|
15
|
+
"url": "https://gateway.pipeworx.io/rick-and-morty/mcp"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
interface McpToolDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: 'object';
|
|
6
|
+
properties: Record<string, unknown>;
|
|
7
|
+
required?: string[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface McpToolExport {
|
|
12
|
+
tools: McpToolDefinition[];
|
|
13
|
+
callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
14
|
+
meter?: { credits: number };
|
|
15
|
+
cost?: Record<string, unknown>;
|
|
16
|
+
provider?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Rick and Morty MCP — wraps the Rick and Morty API (free, no auth)
|
|
21
|
+
*
|
|
22
|
+
* API: https://rickandmortyapi.com
|
|
23
|
+
*
|
|
24
|
+
* Tools:
|
|
25
|
+
* - search_characters: by name + filters (status, species, gender)
|
|
26
|
+
* - get_character: full character record by ID
|
|
27
|
+
* - get_episode: single episode by ID
|
|
28
|
+
* - search_episodes: by name or episode code (e.g. "S01E03")
|
|
29
|
+
* - get_location: single location by ID
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
const BASE_URL = 'https://rickandmortyapi.com/api';
|
|
34
|
+
|
|
35
|
+
// ── Raw API types ────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
interface RawCharacter {
|
|
38
|
+
id: number;
|
|
39
|
+
name: string;
|
|
40
|
+
status: string;
|
|
41
|
+
species: string;
|
|
42
|
+
type: string;
|
|
43
|
+
gender: string;
|
|
44
|
+
origin: { name: string; url: string };
|
|
45
|
+
location: { name: string; url: string };
|
|
46
|
+
image: string;
|
|
47
|
+
episode: string[];
|
|
48
|
+
url: string;
|
|
49
|
+
created: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface RawEpisode {
|
|
53
|
+
id: number;
|
|
54
|
+
name: string;
|
|
55
|
+
air_date: string;
|
|
56
|
+
episode: string;
|
|
57
|
+
characters: string[];
|
|
58
|
+
url: string;
|
|
59
|
+
created: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface RawLocation {
|
|
63
|
+
id: number;
|
|
64
|
+
name: string;
|
|
65
|
+
type: string;
|
|
66
|
+
dimension: string;
|
|
67
|
+
residents: string[];
|
|
68
|
+
url: string;
|
|
69
|
+
created: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface Paginated<T> {
|
|
73
|
+
info?: { count: number; pages: number; next: string | null; prev: string | null };
|
|
74
|
+
results?: T[];
|
|
75
|
+
error?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
function reqNum(args: Record<string, unknown>, key: string, example: string): number {
|
|
81
|
+
const v = args[key];
|
|
82
|
+
const n = typeof v === 'number' ? v : typeof v === 'string' ? Number(v) : NaN;
|
|
83
|
+
if (!Number.isFinite(n)) {
|
|
84
|
+
throw new Error(`Required argument "${key}" is missing or invalid. Pass a number like ${example}.`);
|
|
85
|
+
}
|
|
86
|
+
return n;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function rmFetch<T>(path: string): Promise<T | { error: string }> {
|
|
90
|
+
const res = await fetch(`${BASE_URL}${path}`);
|
|
91
|
+
if (res.status === 404) return { error: 'not_found' };
|
|
92
|
+
if (!res.ok) throw new Error(`Rick and Morty API error: ${res.status}`);
|
|
93
|
+
return res.json() as Promise<T>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function idFromUrl(url: string): number | null {
|
|
97
|
+
const m = url.match(/\/(\d+)$/);
|
|
98
|
+
return m ? Number(m[1]) : null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function formatCharacter(c: RawCharacter) {
|
|
102
|
+
return {
|
|
103
|
+
id: c.id,
|
|
104
|
+
name: c.name,
|
|
105
|
+
status: c.status,
|
|
106
|
+
species: c.species,
|
|
107
|
+
type: c.type || null,
|
|
108
|
+
gender: c.gender,
|
|
109
|
+
origin: c.origin.name,
|
|
110
|
+
location: c.location.name,
|
|
111
|
+
image: c.image,
|
|
112
|
+
episode_count: c.episode.length,
|
|
113
|
+
episode_ids: c.episode.map(idFromUrl).filter((n): n is number => n !== null),
|
|
114
|
+
url: c.url,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function formatEpisode(e: RawEpisode) {
|
|
119
|
+
return {
|
|
120
|
+
id: e.id,
|
|
121
|
+
name: e.name,
|
|
122
|
+
code: e.episode,
|
|
123
|
+
air_date: e.air_date,
|
|
124
|
+
character_count: e.characters.length,
|
|
125
|
+
character_ids: e.characters.map(idFromUrl).filter((n): n is number => n !== null),
|
|
126
|
+
url: e.url,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function formatLocation(l: RawLocation) {
|
|
131
|
+
return {
|
|
132
|
+
id: l.id,
|
|
133
|
+
name: l.name,
|
|
134
|
+
type: l.type,
|
|
135
|
+
dimension: l.dimension,
|
|
136
|
+
resident_count: l.residents.length,
|
|
137
|
+
resident_ids: l.residents.map(idFromUrl).filter((n): n is number => n !== null),
|
|
138
|
+
url: l.url,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── Tool definitions ─────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
const tools: McpToolExport['tools'] = [
|
|
145
|
+
{
|
|
146
|
+
name: 'search_characters',
|
|
147
|
+
description:
|
|
148
|
+
'Search Rick and Morty characters by name with optional filters for status (alive/dead/unknown), species (Human/Alien/etc.), and gender. Returns up to 20 matching characters per page with status, species, origin, current location, and episode appearances.',
|
|
149
|
+
inputSchema: {
|
|
150
|
+
type: 'object',
|
|
151
|
+
properties: {
|
|
152
|
+
name: { type: 'string', description: 'Character name to search for (partial match, e.g., "rick", "beth")' },
|
|
153
|
+
status: { type: 'string', description: 'Filter by status: "alive", "dead", or "unknown".' },
|
|
154
|
+
species: { type: 'string', description: 'Filter by species (e.g., "Human", "Alien", "Mythological Creature").' },
|
|
155
|
+
gender: { type: 'string', description: 'Filter by gender: "female", "male", "genderless", or "unknown".' },
|
|
156
|
+
page: { type: 'number', description: 'Page number (1-based, default 1). API returns 20 per page.' },
|
|
157
|
+
},
|
|
158
|
+
required: [],
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: 'get_character',
|
|
163
|
+
description:
|
|
164
|
+
'Get a single Rick and Morty character by numeric ID. Returns name, status, species, origin, current location, episode appearances, and image URL.',
|
|
165
|
+
inputSchema: {
|
|
166
|
+
type: 'object',
|
|
167
|
+
properties: {
|
|
168
|
+
id: { type: 'number', description: 'Character ID (1-826+). Example: 1 = Rick Sanchez.' },
|
|
169
|
+
},
|
|
170
|
+
required: ['id'],
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: 'search_episodes',
|
|
175
|
+
description:
|
|
176
|
+
'Search Rick and Morty episodes by name or episode code (S01E03 style). Returns episode metadata including air date, character list, and ID.',
|
|
177
|
+
inputSchema: {
|
|
178
|
+
type: 'object',
|
|
179
|
+
properties: {
|
|
180
|
+
name: { type: 'string', description: 'Episode name to search for (partial match).' },
|
|
181
|
+
episode: { type: 'string', description: 'Episode code (e.g., "S01E03").' },
|
|
182
|
+
page: { type: 'number', description: 'Page number (1-based, default 1).' },
|
|
183
|
+
},
|
|
184
|
+
required: [],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: 'get_episode',
|
|
189
|
+
description:
|
|
190
|
+
'Get a single Rick and Morty episode by numeric ID. Returns title, code (e.g. "S01E03"), air date, and the list of characters that appear.',
|
|
191
|
+
inputSchema: {
|
|
192
|
+
type: 'object',
|
|
193
|
+
properties: {
|
|
194
|
+
id: { type: 'number', description: 'Episode ID. Example: 1 = Pilot.' },
|
|
195
|
+
},
|
|
196
|
+
required: ['id'],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: 'get_location',
|
|
201
|
+
description:
|
|
202
|
+
'Get a single Rick and Morty location/planet/dimension by ID. Returns name, type, dimension, and residents.',
|
|
203
|
+
inputSchema: {
|
|
204
|
+
type: 'object',
|
|
205
|
+
properties: {
|
|
206
|
+
id: { type: 'number', description: 'Location ID. Example: 1 = Earth (C-137).' },
|
|
207
|
+
},
|
|
208
|
+
required: ['id'],
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
// ── Tool implementations ─────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
async function searchCharacters(args: Record<string, unknown>) {
|
|
216
|
+
const params = new URLSearchParams();
|
|
217
|
+
if (typeof args.name === 'string') params.set('name', args.name);
|
|
218
|
+
if (typeof args.status === 'string') params.set('status', args.status);
|
|
219
|
+
if (typeof args.species === 'string') params.set('species', args.species);
|
|
220
|
+
if (typeof args.gender === 'string') params.set('gender', args.gender);
|
|
221
|
+
if (typeof args.page === 'number') params.set('page', String(args.page));
|
|
222
|
+
|
|
223
|
+
const data = await rmFetch<Paginated<RawCharacter>>(`/character${params.toString() ? '?' + params : ''}`);
|
|
224
|
+
if ('error' in data) {
|
|
225
|
+
return { found: false, count: 0, characters: [], hint: 'No characters matched. Try a broader name search.' };
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
total: data.info?.count ?? 0,
|
|
229
|
+
pages: data.info?.pages ?? 0,
|
|
230
|
+
page: typeof args.page === 'number' ? args.page : 1,
|
|
231
|
+
has_more: !!data.info?.next,
|
|
232
|
+
characters: (data.results ?? []).map(formatCharacter),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function getCharacter(args: Record<string, unknown>) {
|
|
237
|
+
const id = reqNum(args, 'id', '1 (Rick Sanchez)');
|
|
238
|
+
const data = await rmFetch<RawCharacter>(`/character/${id}`);
|
|
239
|
+
if ('error' in data) return { found: false, id, hint: 'Character not found. IDs are 1–826+.' };
|
|
240
|
+
return { found: true, ...formatCharacter(data) };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function searchEpisodes(args: Record<string, unknown>) {
|
|
244
|
+
const params = new URLSearchParams();
|
|
245
|
+
if (typeof args.name === 'string') params.set('name', args.name);
|
|
246
|
+
if (typeof args.episode === 'string') params.set('episode', args.episode);
|
|
247
|
+
if (typeof args.page === 'number') params.set('page', String(args.page));
|
|
248
|
+
|
|
249
|
+
const data = await rmFetch<Paginated<RawEpisode>>(`/episode${params.toString() ? '?' + params : ''}`);
|
|
250
|
+
if ('error' in data) {
|
|
251
|
+
return { found: false, count: 0, episodes: [], hint: 'No episodes matched.' };
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
total: data.info?.count ?? 0,
|
|
255
|
+
pages: data.info?.pages ?? 0,
|
|
256
|
+
page: typeof args.page === 'number' ? args.page : 1,
|
|
257
|
+
has_more: !!data.info?.next,
|
|
258
|
+
episodes: (data.results ?? []).map(formatEpisode),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function getEpisode(args: Record<string, unknown>) {
|
|
263
|
+
const id = reqNum(args, 'id', '1 (Pilot)');
|
|
264
|
+
const data = await rmFetch<RawEpisode>(`/episode/${id}`);
|
|
265
|
+
if ('error' in data) return { found: false, id, hint: 'Episode not found.' };
|
|
266
|
+
return { found: true, ...formatEpisode(data) };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function getLocation(args: Record<string, unknown>) {
|
|
270
|
+
const id = reqNum(args, 'id', '1 (Earth C-137)');
|
|
271
|
+
const data = await rmFetch<RawLocation>(`/location/${id}`);
|
|
272
|
+
if ('error' in data) return { found: false, id, hint: 'Location not found.' };
|
|
273
|
+
return { found: true, ...formatLocation(data) };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ── callTool router ──────────────────────────────────────────────────
|
|
277
|
+
|
|
278
|
+
async function callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
|
|
279
|
+
switch (name) {
|
|
280
|
+
case 'search_characters': return searchCharacters(args);
|
|
281
|
+
case 'get_character': return getCharacter(args);
|
|
282
|
+
case 'search_episodes': return searchEpisodes(args);
|
|
283
|
+
case 'get_episode': return getEpisode(args);
|
|
284
|
+
case 'get_location': return getLocation(args);
|
|
285
|
+
default: throw new Error(`Unknown tool: ${name}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export default { tools, callTool, meter: { credits: 1 } } satisfies McpToolExport;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"declaration": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|