mcp-rule34 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 smeet666
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,410 @@
1
+ # mcp-rule34
2
+
3
+ [![npm](https://img.shields.io/npm/v/mcp-rule34.svg)](https://www.npmjs.com/package/mcp-rule34)
4
+ [![CI](https://github.com/smeet666/mcp-rule34/actions/workflows/ci.yml/badge.svg)](https://github.com/smeet666/mcp-rule34/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/npm/l/mcp-rule34.svg)](./LICENSE)
6
+
7
+ An [MCP](https://modelcontextprotocol.io) server for [rule34.xxx](https://rule34.xxx).
8
+ Search the site's posts by tag through its own API, and get back each post's page,
9
+ its file, its dimensions, its score, its rating and its full tag list, alongside
10
+ the number of posts the whole search matches.
11
+
12
+ **This server needs credentials.** rule34.xxx issues an API key per account, and
13
+ its terms allow automated reading through the API alone. Getting one takes a
14
+ minute and is described under [Configuration](#configuration).
15
+
16
+ _(Version française plus bas / [French version below](#mcp-rule34-français))_
17
+
18
+ ---
19
+
20
+ ## Quickstart
21
+
22
+ **Claude Code**
23
+
24
+ ```bash
25
+ claude mcp add rule34 --env RULE34_USER_ID=your-id --env RULE34_API_KEY=your-key -- npx -y mcp-rule34
26
+ ```
27
+
28
+ **Claude Desktop, Cursor, and any client using the standard config format**
29
+
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "rule34": {
34
+ "command": "npx",
35
+ "args": ["-y", "mcp-rule34"],
36
+ "env": {
37
+ "RULE34_USER_ID": "your-id",
38
+ "RULE34_API_KEY": "your-key"
39
+ }
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ Put this in your **user** configuration rather than in a project file: a
46
+ `.mcp.json` at the root of a repository travels with the repository, and the key
47
+ travels with it.
48
+
49
+ ## Configuration
50
+
51
+ | Variable | What it is | Default |
52
+ | -------------------------- | ------------------------------------------------- | --------------------------- |
53
+ | `RULE34_USER_ID` | Your numeric account id. Required. | — |
54
+ | `RULE34_API_KEY` | Your API key. Required, and personal. | — |
55
+ | `RULE34_MIN_INTERVAL_MS` | Milliseconds between two requests. | `1000`, and never lower |
56
+ | `RULE34_TIMEOUT_MS` | How long one request may take. | `20000` |
57
+ | `RULE34_MAX_RETRIES` | Attempts after the first, for transient failures. | `3` |
58
+ | `RULE34_CACHE_TTL_MS` | How long an answer stays in memory. | `300000` |
59
+ | `RULE34_CACHE_MAX_ENTRIES` | How many answers stay in memory. | `300` |
60
+ | `RULE34_USER_AGENT` | Overrides the agent this client sends. | `mcp-rule34/<version> (+…)` |
61
+ | `RULE34_LOG_LEVEL` | `silent`, `error`, `info` or `debug`, on stderr. | `error` |
62
+
63
+ **Where the credentials come from.** Sign in, open
64
+ [Account options](https://rule34.xxx/index.php?page=account&s=options), and find
65
+ the row named _API Access Credentials_. It shows `&api_key=…&user_id=…`; those
66
+ two values are what goes in the configuration above. If the key is empty, tick
67
+ _Generate New Key?_ and save.
68
+
69
+ The site issues **one key per person** and asks that applications serving its
70
+ content display no advertising and put it behind no paywall. This server ships
71
+ no key of its own, and each user brings their own.
72
+
73
+ A server started without credentials still starts, publishes its tool, and
74
+ answers every call by naming the two variables to set. It never sends a request
75
+ it knows the site will refuse.
76
+
77
+ ## Tools
78
+
79
+ | Tool | What it does | Key parameters |
80
+ | -------------- | ------------------------------------------------ | ---------------------------------------------------------------------------- |
81
+ | `search_posts` | Finds posts carrying a tag, or several at once. | `tags`, `any_of`, `exclude`, `media_type`, `rating`, `sort`, `limit`, `page` |
82
+ | `get_post` | Reads one post whole, with a kind for every tag. | `id`, `url` |
83
+
84
+ ### A search names posts, a post is then read whole
85
+
86
+ A post on rule34.xxx carries a median of about two hundred tags, and some carry
87
+ nine hundred. A page of twenty results with every tag spelled out would spend
88
+ four fifths of its answer on tag strings nobody reads, so a search row shows the
89
+ first twelve and states how many the post carries in all.
90
+
91
+ `get_post` reads one post by its id, or by a link to its page, and returns every
92
+ tag with its kind, character, copyright, artist, general or metadata, and how
93
+ many posts share it, alongside the uploader, the number of comments and the
94
+ publication date. A link is read rather than followed: this server builds its
95
+ own request from the id it finds in it.
96
+
97
+ Reading one post costs two requests, because neither format the API publishes
98
+ carries everything: the publication date comes from one, and the uploader's
99
+ name, the comment count and the tag kinds from the other.
100
+
101
+ ### Tags are single tokens
102
+
103
+ rule34.xxx stores a tag as one token, joining with underscores what a person
104
+ says as words. `asuka langley sohryu` and `asuka_langley_sohryu` are the same
105
+ tag, and this server writes the second from the first. Punctuation inside a name
106
+ belongs to it: the series tag is `ranma_1/2`, slash included.
107
+
108
+ ### Three lists, three questions
109
+
110
+ ```jsonc
111
+ {
112
+ "tags": ["asuka langley sohryu", "black hair"], // every one of these
113
+ "any_of": ["blonde hair", "red hair"], // at least one of these
114
+ "exclude": ["monochrome", "3d"], // none of these
115
+ }
116
+ ```
117
+
118
+ Filters live in arguments of their own rather than inside the tags, so an answer
119
+ can state the rating and the sort it actually used.
120
+
121
+ ### Asking for images, animations or video
122
+
123
+ `media_type` takes `image`, `animated`, `video` or `any`. rule34.xxx carries the
124
+ distinction in its own tags, so the site does the filtering and the total it
125
+ returns counts what was asked for. The classification is therefore as good as
126
+ the site's tagging.
127
+
128
+ Colour is stated by absence: the site tags what is black and white, and tags
129
+ nothing to say a post is in colour. Ask for colour with
130
+ `"exclude": ["monochrome", "greyscale", "grayscale"]`.
131
+
132
+ ### Ratings
133
+
134
+ The site holds two ratings: `questionable` and `explicit`. Asking rule34.xxx for
135
+ `safe`, `general` or `sensitive` returns zero posts and no error, which reads as
136
+ an absence rather than as a rating the site never had, so this server refuses
137
+ those three instead of passing them on.
138
+
139
+ ### When a search finds nothing
140
+
141
+ A tag that does not exist empties a search exactly the way a combination the
142
+ site holds nothing for does. When a search of several tags comes back empty,
143
+ this server asks the site which of the names it holds, and the answer says which
144
+ one it does not:
145
+
146
+ ```
147
+ No post on rule34.xxx matches neon_genesis_evangelion asuka_langley_sohry.
148
+
149
+ Note: rule34.xxx holds no tag named 'asuka_langley_sohry'. A tag that does not
150
+ exist empties a search on its own.
151
+ ```
152
+
153
+ ## What an answer states
154
+
155
+ Every answer carries the total the site counted for the whole search, the page
156
+ that was served, and the search as it was sent in the site's own language. A
157
+ post carries its rule34.xxx page, its file, its thumbnail, its dimensions, its
158
+ score, its rating, its tags, whatever the uploader credited as a source, and the
159
+ date it was published.
160
+
161
+ Two dates exist on a post and only one of them is publication: a post first
162
+ uploaded in 2016 and retagged last spring carries both, and this server reports
163
+ the first.
164
+
165
+ ## What this server does not do
166
+
167
+ It never uploads, votes, comments, or writes anything to rule34.xxx.
168
+
169
+ ## Errors
170
+
171
+ Every failure carries one of six codes, and the code opens the message:
172
+ `not_found`, `invalid_input`, `rate_limited`, `parse_failure`, `network_error`,
173
+ `timeout`.
174
+
175
+ `rate_limited` means the site is refusing this client for now, and it never
176
+ means a search found nothing. rule34.xxx limits its rate without publishing what
177
+ the rate is, so this client asks one question at a time, with a second between
178
+ two requests that no configuration can shorten, and it backs further off when the
179
+ site pushes back.
180
+
181
+ ## Using it as a library
182
+
183
+ The reading layer is published on its own, without the protocol:
184
+
185
+ ```ts
186
+ import { Rule34Client } from "mcp-rule34/client";
187
+ ```
188
+
189
+ It carries the same pacing, the same cache and the same error taxonomy.
190
+
191
+ ## Development
192
+
193
+ ```bash
194
+ npm install
195
+ npm test # unit tests, no network
196
+ npm run typecheck
197
+ npm run check # biome and prettier
198
+ npm run build
199
+ ```
200
+
201
+ A live suite reaches the site and runs only when credentials are present:
202
+
203
+ ```bash
204
+ RULE34_USER_ID=… RULE34_API_KEY=… npm run test:live
205
+ ```
206
+
207
+ ## License
208
+
209
+ MIT.
210
+
211
+ ---
212
+
213
+ # mcp-rule34 (français)
214
+
215
+ Un serveur [MCP](https://modelcontextprotocol.io) pour
216
+ [rule34.xxx](https://rule34.xxx). Cherche les posts du site par tag, à travers sa
217
+ propre API, et rend pour chacun sa page, son fichier, ses dimensions, son score,
218
+ sa classification et ses tags, avec le nombre de posts que la recherche entière
219
+ trouve.
220
+
221
+ **Ce serveur demande des identifiants.** rule34.xxx délivre une clé d'API par
222
+ compte, et ses conditions n'autorisent la lecture automatisée que par cette API.
223
+ En obtenir une prend une minute, voir [Configuration](#configuration-1).
224
+
225
+ ## Démarrage
226
+
227
+ **Claude Code**
228
+
229
+ ```bash
230
+ claude mcp add rule34 --env RULE34_USER_ID=votre-id --env RULE34_API_KEY=votre-clé -- npx -y mcp-rule34
231
+ ```
232
+
233
+ **Claude Desktop, Cursor, et tout client au format standard**
234
+
235
+ ```json
236
+ {
237
+ "mcpServers": {
238
+ "rule34": {
239
+ "command": "npx",
240
+ "args": ["-y", "mcp-rule34"],
241
+ "env": {
242
+ "RULE34_USER_ID": "votre-id",
243
+ "RULE34_API_KEY": "votre-clé"
244
+ }
245
+ }
246
+ }
247
+ }
248
+ ```
249
+
250
+ À placer dans votre configuration **utilisateur** plutôt que dans un fichier de
251
+ projet : un `.mcp.json` à la racine d'un dépôt part avec le dépôt, et la clé
252
+ part avec lui.
253
+
254
+ ## Configuration
255
+
256
+ | Variable | Ce que c'est | Défaut |
257
+ | -------------------------- | -------------------------------------------------- | --------------------------- |
258
+ | `RULE34_USER_ID` | L'identifiant numérique du compte. Obligatoire. | — |
259
+ | `RULE34_API_KEY` | La clé d'API. Obligatoire, et personnelle. | — |
260
+ | `RULE34_MIN_INTERVAL_MS` | Millisecondes entre deux requêtes. | `1000`, jamais moins |
261
+ | `RULE34_TIMEOUT_MS` | Durée maximale d'une requête. | `20000` |
262
+ | `RULE34_MAX_RETRIES` | Tentatives après la première, sur panne passagère. | `3` |
263
+ | `RULE34_CACHE_TTL_MS` | Durée de vie d'une réponse en mémoire. | `300000` |
264
+ | `RULE34_CACHE_MAX_ENTRIES` | Nombre de réponses gardées en mémoire. | `300` |
265
+ | `RULE34_USER_AGENT` | Remplace l'agent que ce client envoie. | `mcp-rule34/<version> (+…)` |
266
+ | `RULE34_LOG_LEVEL` | `silent`, `error`, `info` ou `debug`, sur stderr. | `error` |
267
+
268
+ **Où trouver les identifiants.** Connectez-vous, ouvrez
269
+ [les options du compte](https://rule34.xxx/index.php?page=account&s=options), et
270
+ cherchez la ligne _API Access Credentials_. Elle affiche `&api_key=…&user_id=…` :
271
+ ces deux valeurs vont dans la configuration ci-dessus. Si la clé est vide, cochez
272
+ _Generate New Key?_ puis enregistrez.
273
+
274
+ Le site délivre **une clé par personne** et demande qu'une application servant
275
+ son contenu n'affiche aucune publicité et ne le mette derrière aucun péage. Ce
276
+ serveur n'embarque aucune clé, et chacun apporte la sienne.
277
+
278
+ Un serveur démarré sans identifiants démarre quand même, publie son outil, et
279
+ répond à chaque appel en nommant les deux variables à régler. Il n'émet jamais
280
+ une requête dont il sait que le site la refusera.
281
+
282
+ ## Les outils
283
+
284
+ | Outil | Ce qu'il fait | Paramètres principaux |
285
+ | -------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------- |
286
+ | `search_posts` | Trouve les posts portant un tag, ou plusieurs. | `tags`, `any_of`, `exclude`, `media_type`, `rating`, `sort`, `limit`, `page` |
287
+ | `get_post` | Lit un post en entier, avec le type de chacun de ses tags. | `id`, `url` |
288
+
289
+ ### La recherche nomme les posts, la fiche les lit en entier
290
+
291
+ Un post de rule34.xxx porte une médiane d'environ deux cents tags, et certains
292
+ en portent neuf cents. Une page de vingt résultats avec tous les tags écrits
293
+ dépenserait quatre cinquièmes de sa réponse en chaînes que personne ne lit, donc
294
+ une ligne de recherche montre les douze premiers et dit combien le post en porte
295
+ au total.
296
+
297
+ `get_post` lit un post par son identifiant, ou par un lien vers sa page, et rend
298
+ chaque tag avec son type, personnage, copyright, artiste, général ou métadonnée,
299
+ et le nombre de posts qui le partagent, avec le déposant, le nombre de
300
+ commentaires et la date de publication. Un lien est lu et non suivi : ce serveur
301
+ construit sa propre requête à partir de l'identifiant qu'il y trouve.
302
+
303
+ Lire une fiche coûte deux requêtes, parce qu'aucun des deux formats publiés par
304
+ l'API ne porte tout : la date de publication vient de l'un, et le nom du
305
+ déposant, le nombre de commentaires et les types de tags viennent de l'autre.
306
+
307
+ ### Un tag est un seul mot
308
+
309
+ rule34.xxx range un tag en un seul mot, en joignant par des underscores ce qu'une
310
+ personne dit en plusieurs. `asuka langley sohryu` et `asuka_langley_sohryu` sont
311
+ le même tag, et ce serveur écrit le second à partir du premier. La ponctuation
312
+ d'un nom lui appartient : le tag de la série est `ranma_1/2`, barre oblique
313
+ comprise.
314
+
315
+ ### Trois listes, trois questions
316
+
317
+ ```jsonc
318
+ {
319
+ "tags": ["asuka langley sohryu", "black hair"], // tous ceux-là
320
+ "any_of": ["blonde hair", "red hair"], // au moins un de ceux-là
321
+ "exclude": ["monochrome", "3d"], // aucun de ceux-là
322
+ }
323
+ ```
324
+
325
+ Les filtres vivent dans leurs propres arguments plutôt que dans les tags, ce qui
326
+ permet à une réponse d'énoncer la classification et le tri qu'elle a réellement
327
+ appliqués.
328
+
329
+ ### Demander des images, des animations ou de la vidéo
330
+
331
+ `media_type` accepte `image`, `animated`, `video` ou `any`. rule34.xxx porte la
332
+ distinction dans ses propres tags, donc c'est le site qui filtre et le total
333
+ qu'il rend compte bien ce qui a été demandé. Le classement vaut donc ce que vaut
334
+ le travail de ses contributeurs.
335
+
336
+ La couleur s'énonce par une absence : le site tague le noir et blanc, et ne
337
+ tague rien pour dire qu'un post est en couleur. Demandez la couleur par
338
+ `"exclude": ["monochrome", "greyscale", "grayscale"]`.
339
+
340
+ ### Les classifications
341
+
342
+ Le site tient deux classifications : `questionable` et `explicit`. Demander
343
+ `safe`, `general` ou `sensitive` à rule34.xxx rend zéro post et aucune erreur, ce
344
+ qui se lit comme une absence plutôt que comme une classification que le site n'a
345
+ jamais eue, donc ce serveur refuse ces trois-là au lieu de les transmettre.
346
+
347
+ ### Quand une recherche ne trouve rien
348
+
349
+ Un tag qui n'existe pas vide une recherche exactement comme le fait une
350
+ combinaison dont le site n'a rien. Quand une recherche de plusieurs tags revient
351
+ vide, ce serveur demande au site lesquels de ces noms il détient, et la réponse
352
+ dit celui qu'il ne connaît pas.
353
+
354
+ ## Ce qu'une réponse énonce
355
+
356
+ Chaque réponse porte le total compté par le site pour la recherche entière, la
357
+ page servie, et la requête telle qu'elle a été envoyée dans le langage du site.
358
+ Un post porte sa page rule34.xxx, son fichier, sa vignette, ses dimensions, son
359
+ score, sa classification, ses tags, la source créditée par le déposant quand il
360
+ en a crédité une, et sa date de publication.
361
+
362
+ Un post porte deux dates et une seule est la publication : une image déposée en
363
+ 2016 et retaguée au printemps dernier porte les deux, et ce serveur rend la
364
+ première.
365
+
366
+ ## Ce que ce serveur ne fait pas
367
+
368
+ Il ne téléverse rien, ne vote pas, ne commente pas, et n'écrit rien sur
369
+ rule34.xxx.
370
+
371
+ ## Les erreurs
372
+
373
+ Toute panne porte un de six codes, et le code ouvre le message : `not_found`,
374
+ `invalid_input`, `rate_limited`, `parse_failure`, `network_error`, `timeout`.
375
+
376
+ `rate_limited` veut dire que le site refuse ce client pour l'instant, et jamais
377
+ qu'une recherche n'a rien trouvé. rule34.xxx limite son débit sans publier
378
+ lequel, donc ce client pose une question à la fois, avec une seconde entre deux
379
+ requêtes qu'aucune configuration ne peut raccourcir, et il s'écarte davantage
380
+ quand le site le repousse.
381
+
382
+ ## L'utiliser comme bibliothèque
383
+
384
+ La couche de lecture est publiée seule, sans le protocole :
385
+
386
+ ```ts
387
+ import { Rule34Client } from "mcp-rule34/client";
388
+ ```
389
+
390
+ Elle porte le même rythme, le même cache et la même taxonomie d'erreurs.
391
+
392
+ ## Le développement
393
+
394
+ ```bash
395
+ npm install
396
+ npm test # tests unitaires, sans réseau
397
+ npm run typecheck
398
+ npm run check # biome et prettier
399
+ npm run build
400
+ ```
401
+
402
+ Une suite en direct atteint le site et ne tourne qu'avec des identifiants :
403
+
404
+ ```bash
405
+ RULE34_USER_ID=… RULE34_API_KEY=… npm run test:live
406
+ ```
407
+
408
+ ## Licence
409
+
410
+ MIT.
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node