@pipeworx/mcp-quotes 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 +316 -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-quotes
|
|
2
|
+
|
|
3
|
+
Quotes MCP — citation-grade quote API for public-domain authors.
|
|
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
|
+
"quotes": {
|
|
20
|
+
"url": "https://gateway.pipeworx.io/quotes/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 Quotes 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-quotes",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Quotes MCP — citation-grade quote API for public-domain authors.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"keywords": ["mcp", "mcp-server", "model-context-protocol", "pipeworx", "quotes"],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/pipeworx-io/mcp-quotes"
|
|
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/quotes",
|
|
4
|
+
"title": "Quotes",
|
|
5
|
+
"description": "Quotes MCP — citation-grade quote API for public-domain authors.",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"websiteUrl": "https://pipeworx.io/packs/quotes",
|
|
8
|
+
"repository": {
|
|
9
|
+
"url": "https://github.com/pipeworx-io/mcp-quotes",
|
|
10
|
+
"source": "github"
|
|
11
|
+
},
|
|
12
|
+
"remotes": [
|
|
13
|
+
{
|
|
14
|
+
"type": "streamable-http",
|
|
15
|
+
"url": "https://gateway.pipeworx.io/quotes/mcp"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
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
|
+
* Quotes MCP — citation-grade quote API for public-domain authors.
|
|
21
|
+
*
|
|
22
|
+
* Tools:
|
|
23
|
+
* - random_quote: random quote with optional filters
|
|
24
|
+
* - search_quotes: substring search across the corpus
|
|
25
|
+
* - check_attribution: verify whether a quote is genuinely attributed (the differentiator)
|
|
26
|
+
* - quote_by_location: structural lookup (act/scene/chapter)
|
|
27
|
+
* - list_authors: list available authors
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
checkAttribution,
|
|
32
|
+
getAct,
|
|
33
|
+
getChapter,
|
|
34
|
+
getFullWorkByAuthorSlug,
|
|
35
|
+
getScene,
|
|
36
|
+
listAuthors,
|
|
37
|
+
listFullTextWorks,
|
|
38
|
+
quotesByLocation,
|
|
39
|
+
randomQuote,
|
|
40
|
+
searchQuotes,
|
|
41
|
+
} from './query';
|
|
42
|
+
|
|
43
|
+
const tools: McpToolExport['tools'] = [
|
|
44
|
+
{
|
|
45
|
+
name: 'random_quote',
|
|
46
|
+
description:
|
|
47
|
+
'Return a random quote from the corpus, optionally filtered by author, fame, verification status, or tag. Each result includes citation: work, year, speaker (for plays/fiction), and source URL.',
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
author_id: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'Optional author id (e.g., "oscar-wilde", "william-shakespeare", "mark-twain"). Use list_authors to discover ids.',
|
|
54
|
+
},
|
|
55
|
+
famous_only: {
|
|
56
|
+
type: 'boolean',
|
|
57
|
+
description: 'If true, only return famous/canonical quotes.',
|
|
58
|
+
},
|
|
59
|
+
verified_only: {
|
|
60
|
+
type: 'boolean',
|
|
61
|
+
description: 'If true, exclude misattributed quotes.',
|
|
62
|
+
},
|
|
63
|
+
tag: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
description: 'Filter by tag (e.g., "wit", "love", "misattributed", "commonly-misquoted").',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'search_quotes',
|
|
72
|
+
description:
|
|
73
|
+
"Search quotes by substring. Matches both canonical text and known popular paraphrases — searching for 'protest' finds Hamlet's 'The lady doth protest too much, methinks' even though the popular form 'Methinks the lady doth protest too much' is what most people remember.",
|
|
74
|
+
inputSchema: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {
|
|
77
|
+
query: {
|
|
78
|
+
type: 'string',
|
|
79
|
+
description: 'Substring to search for (case-insensitive).',
|
|
80
|
+
},
|
|
81
|
+
author_id: {
|
|
82
|
+
type: 'string',
|
|
83
|
+
description: 'Optional author filter.',
|
|
84
|
+
},
|
|
85
|
+
verified_only: {
|
|
86
|
+
type: 'boolean',
|
|
87
|
+
description: 'If true, exclude misattributed quotes.',
|
|
88
|
+
},
|
|
89
|
+
limit: {
|
|
90
|
+
type: 'number',
|
|
91
|
+
description: 'Max results to return (default 10, max 25).',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
required: ['query'],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'check_attribution',
|
|
99
|
+
description:
|
|
100
|
+
'Given a quote and (optionally) the author it is claimed to be by, return one of four verdicts: "verified" (genuine, with citation), "misattributed" (no primary source — popular but fake), "paraphrase_of_verified" (popular corruption of a real quote, returns the actual text), or "no_match" (not in corpus). Useful for journalists, researchers, and anyone tired of fake Mark Twain quotes.',
|
|
101
|
+
inputSchema: {
|
|
102
|
+
type: 'object',
|
|
103
|
+
properties: {
|
|
104
|
+
text: {
|
|
105
|
+
type: 'string',
|
|
106
|
+
description: 'The quote text to check.',
|
|
107
|
+
},
|
|
108
|
+
claimed_author: {
|
|
109
|
+
type: 'string',
|
|
110
|
+
description: 'Optional: who the quote is popularly attributed to (e.g., "Oscar Wilde", "Mark Twain"). Narrows the check.',
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
required: ['text'],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'quote_by_location',
|
|
118
|
+
description:
|
|
119
|
+
'Look up quotes by structural address within a work — act/scene for plays, chapter for novels. Example: author_id="william-shakespeare", work_title="Hamlet", act=3, scene=1 returns the "To be, or not to be" line.',
|
|
120
|
+
inputSchema: {
|
|
121
|
+
type: 'object',
|
|
122
|
+
properties: {
|
|
123
|
+
author_id: { type: 'string', description: 'Author id (e.g., "william-shakespeare").' },
|
|
124
|
+
work_title: { type: 'string', description: 'Title of the work (e.g., "Hamlet", "The Picture of Dorian Gray").' },
|
|
125
|
+
act: { type: 'number', description: 'Act number (plays only).' },
|
|
126
|
+
scene: { type: 'number', description: 'Scene number (plays only).' },
|
|
127
|
+
chapter: { type: 'number', description: 'Chapter number (novels/essays).' },
|
|
128
|
+
},
|
|
129
|
+
required: ['author_id', 'work_title'],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'list_authors',
|
|
134
|
+
description:
|
|
135
|
+
'Return all authors available in the corpus — each entry includes author id (used as input to other tools), display name, birth/death dates, and public-domain status.',
|
|
136
|
+
inputSchema: {
|
|
137
|
+
type: 'object',
|
|
138
|
+
properties: {},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'list_full_text_works',
|
|
143
|
+
description:
|
|
144
|
+
'List works for which the full text (every scene, speech, and line) is loaded — beyond just the famous-quote excerpts. Use this to discover what is available for deep structural lookup via get_scene and get_act.',
|
|
145
|
+
inputSchema: { type: 'object', properties: {} },
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: 'get_scene',
|
|
149
|
+
description:
|
|
150
|
+
'Return the full text of a specific scene from a play (every speech, every line, in order). Example: author_id="william-shakespeare", work_slug="hamlet", act=3, scene=1 returns the entire "To be, or not to be" scene including all of Hamlet\'s soliloquy and the subsequent dialogue with Ophelia. Useful for context, citation, or close reading.',
|
|
151
|
+
inputSchema: {
|
|
152
|
+
type: 'object',
|
|
153
|
+
properties: {
|
|
154
|
+
author_id: { type: 'string', description: 'Author id (e.g., "william-shakespeare").' },
|
|
155
|
+
work_slug: { type: 'string', description: 'Work slug (e.g., "hamlet").' },
|
|
156
|
+
act: { type: 'number', description: 'Act number.' },
|
|
157
|
+
scene: { type: 'number', description: 'Scene number.' },
|
|
158
|
+
},
|
|
159
|
+
required: ['author_id', 'work_slug', 'act', 'scene'],
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'get_act',
|
|
164
|
+
description:
|
|
165
|
+
'Return the full text of an entire act (all scenes within it). Use this to read a full structural unit at once. Returned object contains nested scenes with their speeches and lines.',
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: 'object',
|
|
168
|
+
properties: {
|
|
169
|
+
author_id: { type: 'string', description: 'Author id (e.g., "william-shakespeare").' },
|
|
170
|
+
work_slug: { type: 'string', description: 'Work slug (e.g., "hamlet").' },
|
|
171
|
+
act: { type: 'number', description: 'Act number.' },
|
|
172
|
+
},
|
|
173
|
+
required: ['author_id', 'work_slug', 'act'],
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: 'get_chapter',
|
|
178
|
+
description:
|
|
179
|
+
'Return the full text of a chapter from a novel/prose work (all paragraphs in order). Example: author_id="oscar-wilde", work_slug="the-picture-of-dorian-gray", chapter=2 returns the entire chapter where Lord Henry tempts Dorian. Use list_full_text_works to discover which works are chapter-based.',
|
|
180
|
+
inputSchema: {
|
|
181
|
+
type: 'object',
|
|
182
|
+
properties: {
|
|
183
|
+
author_id: { type: 'string', description: 'Author id (e.g., "mark-twain").' },
|
|
184
|
+
work_slug: { type: 'string', description: 'Work slug (e.g., "adventures-of-huckleberry-finn").' },
|
|
185
|
+
chapter: { type: 'number', description: 'Chapter number.' },
|
|
186
|
+
},
|
|
187
|
+
required: ['author_id', 'work_slug', 'chapter'],
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
async function callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
|
|
193
|
+
switch (name) {
|
|
194
|
+
case 'random_quote': {
|
|
195
|
+
const result = randomQuote({
|
|
196
|
+
authorId: args.author_id as string | undefined,
|
|
197
|
+
famousOnly: args.famous_only as boolean | undefined,
|
|
198
|
+
verifiedOnly: args.verified_only as boolean | undefined,
|
|
199
|
+
tag: args.tag as string | undefined,
|
|
200
|
+
});
|
|
201
|
+
if (!result) return { error: 'No quotes match the given filters.' };
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
case 'search_quotes': {
|
|
206
|
+
const limit = Math.max(1, Math.min(25, (args.limit as number | undefined) ?? 10));
|
|
207
|
+
const results = searchQuotes(args.query as string, {
|
|
208
|
+
authorId: args.author_id as string | undefined,
|
|
209
|
+
verifiedOnly: args.verified_only as boolean | undefined,
|
|
210
|
+
});
|
|
211
|
+
return {
|
|
212
|
+
query: args.query,
|
|
213
|
+
count: results.length,
|
|
214
|
+
quotes: results.slice(0, limit),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
case 'check_attribution': {
|
|
219
|
+
return checkAttribution(
|
|
220
|
+
args.text as string,
|
|
221
|
+
args.claimed_author as string | undefined,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
case 'quote_by_location': {
|
|
226
|
+
const results = quotesByLocation(
|
|
227
|
+
args.author_id as string,
|
|
228
|
+
args.work_title as string,
|
|
229
|
+
{
|
|
230
|
+
act: args.act as number | undefined,
|
|
231
|
+
scene: args.scene as number | undefined,
|
|
232
|
+
chapter: args.chapter as number | undefined,
|
|
233
|
+
},
|
|
234
|
+
);
|
|
235
|
+
return {
|
|
236
|
+
author_id: args.author_id,
|
|
237
|
+
work: args.work_title,
|
|
238
|
+
count: results.length,
|
|
239
|
+
quotes: results,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
case 'list_authors':
|
|
244
|
+
return { authors: listAuthors() };
|
|
245
|
+
|
|
246
|
+
case 'list_full_text_works':
|
|
247
|
+
return { works: listFullTextWorks() };
|
|
248
|
+
|
|
249
|
+
case 'get_scene': {
|
|
250
|
+
const scene = getScene(
|
|
251
|
+
args.author_id as string,
|
|
252
|
+
args.work_slug as string,
|
|
253
|
+
args.act as number,
|
|
254
|
+
args.scene as number,
|
|
255
|
+
);
|
|
256
|
+
if (!scene) {
|
|
257
|
+
return {
|
|
258
|
+
error: `Scene ${args.act}.${args.scene} of ${args.author_id}/${args.work_slug} not found. Use list_full_text_works to see what is available.`,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
author_id: args.author_id,
|
|
263
|
+
work_slug: args.work_slug,
|
|
264
|
+
act: args.act,
|
|
265
|
+
scene: scene.number,
|
|
266
|
+
setting: scene.setting,
|
|
267
|
+
speech_count: scene.speeches.length,
|
|
268
|
+
speeches: scene.speeches,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
case 'get_act': {
|
|
273
|
+
const act = getAct(args.author_id as string, args.work_slug as string, args.act as number);
|
|
274
|
+
if (!act) {
|
|
275
|
+
return {
|
|
276
|
+
error: `Act ${args.act} of ${args.author_id}/${args.work_slug} not found. Use list_full_text_works to see what is available.`,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
const work = getFullWorkByAuthorSlug(args.author_id as string, args.work_slug as string);
|
|
280
|
+
return {
|
|
281
|
+
author_id: args.author_id,
|
|
282
|
+
work_slug: args.work_slug,
|
|
283
|
+
work_title: work?.workTitle,
|
|
284
|
+
act: act.number,
|
|
285
|
+
scene_count: act.scenes.length,
|
|
286
|
+
scenes: act.scenes,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
case 'get_chapter': {
|
|
291
|
+
const chapter = getChapter(
|
|
292
|
+
args.author_id as string,
|
|
293
|
+
args.work_slug as string,
|
|
294
|
+
args.chapter as number,
|
|
295
|
+
);
|
|
296
|
+
if (!chapter) {
|
|
297
|
+
return {
|
|
298
|
+
error: `Chapter ${args.chapter} of ${args.author_id}/${args.work_slug} not found. Use list_full_text_works to see which chapter-based works are available.`,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
author_id: args.author_id,
|
|
303
|
+
work_slug: args.work_slug,
|
|
304
|
+
chapter: chapter.number,
|
|
305
|
+
title: chapter.title,
|
|
306
|
+
paragraph_count: chapter.paragraphs.length,
|
|
307
|
+
paragraphs: chapter.paragraphs,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
default:
|
|
312
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
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
|
+
}
|