humor-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@humor-mcpsearch for jokes about technology"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
humor-mcp
An MCP server over a local humor corpus. Every line it returns says who wrote it.
Point a model at a body of jokes and it will happily launder them: you get the material back with no idea whose it was, whether you may publish it, or whether you may sell it. This server treats that as the primary problem. Every result carries the credit for the line it came from, material that may not be redistributed is withheld unless you ask for it by name, and the exporter refuses to hand out anything whose licence does not permit it.
No database server, no network, no required dependencies — Python stdlib and one SQLite file. It runs the same whether or not the machine that built the corpus is awake.
(That holds for the server and every text path. Audio ingest is the one
exception: it needs numpy, scipy and soundfile, and is only imported when
you actually use it.)
packs/<id>/pack.json who made this material, under what licence
packs/<id>/lines.jsonl the lines
packs/<id>/pairs.jsonl chosen/rejected preference pairs (optional)
humor_mcp/cli.py the one entry point; subcommands below
server.py the MCP server (stdio, read-only)
build.py packs -> humor.db (SQLite + FTS5)
import_audio.py audio + timed transcript (measured reactions)
import_transcript.py transcript (text reaction markers)
import_corpus.py a jokes file
audio_reactions.py the laughter/applause detector
transcripts.py shared .srt/.vtt/.json parsing
paths.py where the bundled and user corpora live
server.py shim, so a checkout runs with nothing installedInstall
Python 3.9+, no required dependencies. One line:
claude mcp add humor -- uvx humor-mcpThat is the whole install. A corpus ships inside the package — 1,248 lines with 975 human ratings and the best-of-N picks — so there is nothing to download, no build step, and nothing to wire together. The first launch compiles the database itself; your client just starts working. No account, no checkout.
To run the unreleased tip instead:
uvx --from git+https://github.com/zoombulous/humor-mcp humor-mcpCloned — adds the third-party packs (CUP, ExPUNations, r/Jokes, New Yorker), which are too large and too non-commercial to bundle:
git clone https://github.com/zoombulous/humor-mcp
cd humor-mcp
python -m humor_mcp.cli build
claude mcp add humor -- python "$(pwd)/server.py"On Windows use the full path in place of $(pwd). Any MCP client works; this
speaks plain stdio JSON-RPC over stdin/stdout, and running the command with no
arguments is what starts the server.
Audio ingest is the one optional extra:
pip install "humor-mcp[audio]" # or: pip install numpy scipy soundfileCommands
humor-mcp serve on stdio <- what an MCP client runs
humor-mcp where which corpus directories are in use
humor-mcp build compile packs -> humor.db
humor-mcp build --export DIR copy out only what may be redistributed
humor-mcp import-corpus a .txt/.csv/.jsonl of jokes
humor-mcp import-transcript a transcript
humor-mcp import-audio audio + a timed transcript
humor-mcp reactions FILE what the detector hears in a recordingFrom a clone with nothing installed, python -m humor_mcp.cli ... is the same
thing.
Check it:
python test_server.py # 61 checks over real stdio
python test_import.py # 52 checks over the importers and the pack model
python test_audio.py # 27 checks against synthesized audio (needs numpy)The suites build their own temporary corpus, so they pass on a fresh clone regardless of which packs you have.
Related MCP server: mcp-copilotcli-history
Your corpus lives in your own folder
There are two pack directories, and the second one is yours:
<checkout>/packs the packs that ship with this repo (clone only)
~/.humor-mcp/packs YOUR corpus <- imports go here by default
~/.humor-mcp/humor.db the built databaseIf you pip-installed rather than cloned, the first of those does not exist and your corpus is the only one — the wheel deliberately carries no packs, since they are ~9 MB of other people's material and two of those packs are non-commercial.
Both are read and merged at build time, so your material is searchable next to
the bundled packs without ever being mixed into the checkout. It survives
git pull, nothing in this repo touches it, and you will never resolve a merge
conflict over a joke file.
humor-mcp import-corpus --id my-sets --input jokes.txt \
--title "My tight five" --authors "Your Name" --license CC-BY-4.0
# -> ~/.humor-mcp/packs/my-sets
humor-mcp buildIf a pack of yours claims the same id as a bundled one, yours wins and the
build says so — that is how you replace a shipped pack rather than working
around it.
Every importer takes --packs-dir if you want a pack somewhere specific.
Environment
variable | what it does |
| move your corpus somewhere other than |
| read packs from exactly these directories instead ( |
| build or serve a database somewhere other than |
Together these let one checkout serve several corpora, or keep everything outside the repo entirely.
Tools
tool | what it gives you |
| full-text search, filtered by pack / kind / minimum human score |
| the lines a human actually scored highly |
| liked vs disliked with the score distribution — calibration, not just examples |
| the structural analysis of a joke: mechanism, setup/turn, technique |
| what won head-to-head and what lost |
| a paste-ready brief: exemplars + calibration + the attribution block |
| every pack: authors, licence, whether it may be shared or sold, how to cite |
| what is loaded |
Every result carries a credit object. Packs whose licence bars redistribution
are hidden unless a call passes include_restricted: true. limit is clamped to
200 — tool results land straight in the calling model's context, and a response
should not be able to flood it.
Bring your own corpus
From audio + a transcript (best)
humor-mcp reactions set.mp3 # what does it hear? look first
humor-mcp import-audio --id tight-five --audio set.mp3 --transcript set.srt \
--performer "Your Name" --title "Comedy Cellar, March" --i-own-this
humor-mcp buildThe audio supplies the reactions, the transcript supplies the words, and each
reaction is anchored to the line that earned it. Needs a timestamped transcript
(.srt, .vtt, Whisper .json); a plain .txt carries no timings and cannot
be aligned.
⚠️ The bundled detector does not work on real audience audio
Measured against 14 minutes of real stand-up with per-line ground truth from an AudioSet classifier: F1 0.24 — precision 0.15, recall 0.54. It fired 93 times for 26 real laughs.
The reason is not a bad threshold, it is that the features carry no signal here. On that recording, median spectral flatness is 0.0582 for laughter and 0.0549 for speech; 4–8 Hz modulation is 0.3255 against 0.3246. The best single threshold on any of the three features scores exactly the majority-class baseline — no threshold beats always guessing "speech".
It scored well on synthetic signals only because those were built with the property being tested: pure harmonic speech at flatness 0.004 against noise-based laughter at 0.47. Real mic'd, compressed, reverberant room audio collapses that two-order-of-magnitude gap to nothing.
Use a trained classifier instead.
MIT/ast-finetuned-audioset-10-10-0.4593has laughter and applause classes and is what produced the ground truth above. Feed its output in with--reactions, below.
It does not transcribe. Nothing downloads a model or calls a service — bring
a transcript from whatever you already use. Audio needs numpy, scipy and
soundfile; nothing else in this repo has dependencies. wav/flac/ogg/mp3 read
directly, m4a and aac need converting with ffmpeg first.
How the detector works, and where it breaks. Two features do the work: spectral flatness separates the room from the voice (a voice has pitch so its spectrum is peaky; audience noise is broadband — measured on synthetic signals, speech 0.004 against laughter 0.47 and applause 0.56), then 4–8 Hz modulation separates laughter from applause, since laughter has a syllable rhythm and applause is a wash. Loudness is used only to rule out silence: the performer is mic'd and the room is not, so real applause runs about 3 dB above speech and any volume threshold finds either everything or nothing.
It is a heuristic detector, not a trained model. Music, a noisy room, or a comic
laughing into their own mic will fool it. Run humor-mcp reactions on your file
and read the flatness values before trusting it; --flat-speech-max is the knob.
From a transcript
The usual case — you have a recording of a set, not a tidy file of jokes:
humor-mcp import-transcript --id tight-five --input set.srt \
--performer "Your Name" --title "Comedy Cellar, March" --i-own-this
humor-mcp build.srt, .vtt, Whisper .json and plain .txt all work, with or without
SPEAKER: labels. Timestamps, cue numbers, music cues and stage directions are
stripped. Pass several files at once, or --append to add another set later;
--performer is recorded per line, so one pack can hold several people.
How it finds the jokes. If the transcript keeps its reaction cues —
[laughter], (laughs), [applause] — they are used the way an audio pipeline
uses the waveform: the sentence before the cue is the line, what came before it
is the context, and the cue's strength becomes a laugh weight (a chuckle
ranks below laughter, which ranks below laughter-and-applause). That weight is
ordering, not measurement.
If there are no cues, the transcript is segmented into sentences with a rolling context window and nothing is marked as a punchline, because nothing in the text says which parts landed. You get material, not judgements, and the importer tells you so rather than quietly presenting utterances as jokes.
Transcripts of other people's performances default to not redistributable —
that is what a transcript of a copyrighted set is. --i-own-this flips it to
CC-BY-4.0, or set --license explicitly.
From a structured file
If you already have jokes in a file:
humor-mcp import-corpus --id my-sets --input jokes.txt \
--title "My tight five" --authors "Your Name" --license CC-BY-4.0
humor-mcp build.txt (blank-line separated), .csv (a text/line/joke column, optionally
context, score, note) and .jsonl all work. --authors and --license
are required. If you don't know the licence, pass --license UNKNOWN: the pack
still loads and is still searchable locally, but it is flagged everywhere it
surfaces and refused by the exporter.
Or skip the importer and write packs/<id>/pack.json plus a lines.jsonl by
hand — that is the whole contract:
{"text": "...", "context": "...", "score": 3, "note": "...", "attribution": "..."}The pack model
packs/*/pack.json is a declarative model and humor-mcp build is its compiler, so the
model is checked before anything is built — every field has a declared type with
a value predicate, and a bad manifest fails at author time with all its
problems listed at once, not one per rebuild.
$ humor-mcp build
6 problem(s) in the corpus model — nothing was built:
- packs/zz/pack.json: unknown field 'redistributible' — did you mean 'redistributable'?
- packs/zz/pack.json: 'redistributable' must be a real boolean — true/false, not "true"/"false", got 'false'
- packs/zz/pack.json: licence 'CC-BY-NC-4.O' is not a recognised identifier. Fix the
typo, or set "custom_license": true to state that you meant it.
- packs/zz/pack.json: default_hidden is true but hidden_reason is empty — record why,
or the reason lives only in someone's memoryThis matters more than ordinary input validation, because the fields are the
licence policy. "redistributable": "false" is a non-empty string, so it used to
coerce to True and the exporter would ship material whose manifest said three
separate times not to. Typed fields make that unrepresentable.
Cross-field rules are checked too: ALL-RIGHTS-RESERVED cannot be
redistributable, an -NC- licence cannot allow commercial_use, a placeholder
licence cannot be license_verified, and default_hidden requires a
hidden_reason.
The tool schemas the MCP advertises are derived from the Python function signatures rather than maintained alongside them, so a parameter cannot exist in one and not the other; an undocumented parameter raises at import.
Sharing a corpus
humor-mcp build --export ./shareCopies out only the packs whose licence actually permits it, and writes a
CREDITS.md covering exactly what went. It refuses anything non-redistributable,
anything with an unverified licence, and anything whose licence field is still
UNSET — so an unlabelled pack can't leak by accident.
What ships with this repo
Cloning gets you a working corpus, not an empty shell — but read the licence column before you build anything on top of it. Two of these packs are non-commercial, and the credit attached to every result will tell you so.
pack | lines / pairs | licence | |
| 1,248 + 463 | CC-BY-4.0 | © James Barker — engine output plus his own ratings, h2h picks and eval verdicts. Ships inside the wheel too, so |
| 2,753 | CC-BY-NC-4.0 | Context-Situated Pun dataset, Sun et al., EMNLP 2022 |
| 1,899 | CC-BY-NC-4.0 | ExPUNations, Sun et al., EMNLP 2022 |
| 4,000 pairs | CC-BY-4.0 | Reddit r/Jokes via SocialGrep. Off-rubric — hidden by default. |
| 1,500 pairs | CC-BY-4.0 | New Yorker Caption Contest, Hessel et al., ACL 2023. Text only, no cartoons. Off-rubric — hidden by default. |
The author's own local corpus also holds a standup pack of transcribed
crowd work, which is not in this repo: its licence does not permit
redistribution, so .gitignore keeps it out and humor-mcp build --export refuses it.
That asymmetry is the point of the tool — the same rules that hide it from you
hide your restricted material from everyone else.
Two gates, deliberately separate
A pack can be withheld from default results for two unrelated reasons, and collapsing them into one switch loses information:
redistributable: false— a licence question. You may not pass this material on.standupis the only such pack. Reachable withinclude_restricted: true, never exported.default_hidden: true— a relevance question. The licence is fine and the exporter will happily ship it; the corpus owner judged it unrepresentative.rjokesandnyccare 92% of all pairs and were excluded from DPO v6 for pulling the model off Mallard's voice, so they are held back rather than drowning the 463 on-rubric pairs 12:1. Reachable withinclude_hidden: true, or by naming the pack — asking for it by name is answer enough.
preference_pairs reports what it withheld in a withheld field rather than
quietly returning less than you asked for.
A note on de-duplication
The ingest merges duplicate lines within a pack, because some source tables overlap — ExPUN arrives once carrying its structural breakdown and again carrying its explanation, so every pun would otherwise appear twice with half its fields each.
The merge key is (text, attribution), never text alone. Two people can deliver
the same line, and collapsing those would credit one of them for the other's
material; 25 texts in the standup pack already appear under more than one
credit and are kept separate. Identical lines in different packs are never
merged at all, since they carry different credit by definition.
Licences
Two, as is normal for a repo that ships both software and data:
The code — the
humor_mcppackage and the test suites — is MIT, © 2026 James Barker. See LICENSE.The corpora are licensed individually, per pack, in
packs/<id>/pack.json. They are not covered by the MIT licence, two of them forbid commercial use, and some may not be redistributed at all. See NOTICE for the summary; thesourcestool prints the live state of all of them.
Owning something and licensing it are different: the authors field in each
pack records who owns the material, and the license field records what
everyone else is permitted to do with it.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/zoombulous/humor-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server