MCP
Model Context Protocol server: read-only catalogue tools for AI agents.
https://api.mangabaka.org/mcp Most clients only need the URL above. Paste it wherever they ask for a remote or HTTP MCP server — there is no key, token or account. Pick your client for the exact config, because the key names differ between them.
Turn an id from MyAnimeList, AniList, Anime-Planet, Kitsu or MangaUpdates into the MangaBaka series. Use it whenever a user arrives with a link or an id from one of those sites, or when you are reconciling a list exported from one.
This is the reverse of the source_ids on every series result, and the two together close the loop. Note the spelling differs: source_ids keys use underscores (my_anime_list), while provider here takes the site name as it appears in a URL (my-anime-list).
Returns identity only — id, title, type, link and content_rating. Feed the id to series_get for anything more. One source id can resolve to more than one MangaBaka series, so expect a short list rather than a single answer.
content_rating here measures sexual explicitness and nothing else. It is not a maturity rating: Vagabond comes back safe while carrying Gore, Mass Murder and a rape tag. If someone is asking whether a series suits a particular reader, that answer lives in the tags, so call series_get before giving one.
berserk) and manga-updates (njeqwry).{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"provider": {
"type": "string",
"enum": [
"anilist",
"my-anime-list",
"anime-planet",
"kitsu",
"manga-updates"
],
"description": "Which site the id is from."
},
"id": {
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
{
"type": "string",
"pattern": "^[A-Za-z0-9_-]+$"
}
],
"description": "The id on that site, not MangaBaka's. A number for anilist, my-anime-list and kitsu; a slug or code for anime-planet (`berserk`) and manga-updates (`njeqwry`)."
}
},
"required": [
"provider",
"id"
]
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "number",
"description": "MangaBaka series id. Feed it to `series_get`."
},
"title": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"type": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"canonical_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"content_rating": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Sexual explicitness only, and nothing else about the series. `safe` says nothing about violence or abuse: call `series_get` before vouching for a series to anyone."
}
},
"required": [
"id",
"title",
"type",
"canonical_url",
"content_rating"
],
"additionalProperties": false
}
},
"hint": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"results",
"hint"
],
"additionalProperties": false
}Everything known about one or more series, by MangaBaka id. This is what answers the follow-up to a search: what is it about, is it finished, who publishes it, is there a sequel.
Takes a LIST of ids, up to 10 at a time, and the usual shape is "search returned five, tell me about them" — so send them together rather than one call each.
include decides what comes back, and it is the same parameter with the same meaning on every tool here. meta: "full" is what this tool is usually for — the publication dates with their estimated flags, every alternate title and the publishers. description: true gives the whole synopsis rather than a truncated one.
What this tool does NOT return: the tag list, the related series, the links, and the titles as rows. Each is its own tool — series_get_tags, series_get_relationships, series_get_links, series_get_titles — because each is a list worth filtering and paging, and a preview of one here would be a slice a model could mistake for the whole. include: {"genres": true} is the exception and IS complete, so a genre missing from it really is missing.
Titles are the one split: meta: "full" already gives EVERY alternate title, as bare strings, so it answers "what else is it called" on its own. series_get_titles is for when the language, the traits or the primary flag of a title is the question.
Ids that match no active series come back in not_found rather than as an error.
This tool returns whatever the id names, including erotica and pornographic series. series_search hides those by default; an explicit id request does not, so read content_rating before describing what came back.
id, not ids, though it takes a list.series_get and on a recommendation rail. Everything is off by default except meta, which is base. Ask for what the question needs and nothing else — a result is several times the size with everything on, and the cost is paid per row. Tags, relationships, links and the full title list are NOT here. Each is its own tool — series_get_tags, series_get_relationships, series_get_links, series_get_titles — because each is a list that has to be filtered and paged rather than switched on.Every link for ONE series, filtered and paged. This is the "where can I read this" tool.
type is what answers the question: webplatform is where you read it online, retailer is where you buy it, publisher is the licensor's own page, and info, news, social and other are about the series rather than places to read it. Asking for ["webplatform", "retailer"] is usually the whole job.
language filters by the link's own language, not the series'. Most of a Japanese series' links are Japanese, so a reader who wants English should say so rather than be handed the lot and asked to pick. unknown is a real value: it means nobody recorded one, not that the link is untargeted.
counts.by_type and counts.by_language count every link on the series and IGNORE your type and language filters, so a value not listed is zero and one call settles what is worth asking for. counts.matched is the filtered number; those two are not.
Piracy links are never returned, so an empty result is not evidence that a series cannot be read anywhere — only that MangaBaka has no legitimate link for it.
series_get.counts.by_language.page * limit must not exceed the 12,000-row result window, so the deepest page depends on limit (120 at limit 100, 500 at limit 24, 12,000 at limit 1). Past it the request is refused with 400 rather than served.page: page * limit must not exceed the 12,000-row result window, so a larger limit reaches fewer pages. At 50 the deepest page is 240.Every related series for ONE series, filtered and paged. series_get returns the top few; this returns all of them, which is what answers "what is the reading order" and "is there a sequel" without a guess.
Edges come back as ids and kinds, never titles — series_get on those ids names them, and it takes a list, so name a whole page in one call. Read their content_rating there too before describing anything: nothing on an edge says what the target contains.
The edge reads TARGET-first. A source edge points at the work this one was adapted FROM, so a manga of a light novel has the novel as its source; adaptation points the other way. That is how you tell an original from its adaptation rather than assuming from the dates.
Every edge points at another series in this catalogue, and an anime is not one. No type returns anime, adaptation included — whether a series was animated is series_get's anime field, never a relationship.
type and not_type filter by kind, and type wins: send both and not_type is ignored. A popular title carries dozens of parody and cameo doujin edges and little else, so not_type: ["parody", "cameo"] is usually what you want, and type: ["sequel", "prequel", "side_story"] is a reading order.
chronology on an edge says narrative for true in-story order and release for publication order. It is ABSENT when nobody has recorded which, so its absence is not evidence of either — do not build a reading order on the assumption.
counts.by_type is the whole picture of what exists before you page: it lists every kind the series carries with its count, so a kind not listed is zero. Read it first and you rarely need more than one page.
series_get.["parody", "cameo"] removes the doujin noise. Ignored when type is set.page * limit must not exceed the 12,000-row result window, so the deepest page depends on limit (120 at limit 100, 500 at limit 24, 12,000 at limit 1). Past it the request is refused with 400 rather than served.page: page * limit must not exceed the 12,000-row result window, so a larger limit reaches fewer pages. At 50 the deepest page is 240.series_get on them to name them and read their content_rating.Every tag on ONE series, filtered and paged. series_get returns a ranked slice; this returns the list, which is what makes an answer about a tag the series does NOT have possible.
Reach for it when the question turns on a specific tag rather than on what the series is broadly about: "does this have X", "is there anything upsetting in it", "what are its core themes". For "what is this series", series_get already answers.
Pass tag with ids from tags_find to check membership directly. The whole list is read before the filter runs, so an empty result there is a definite no rather than a slice that happened not to include it — and the hint says so. Remember a parent tag does NOT match its subtree here: this reports what the series actually carries, so ask about the specific tag, or read the returned names.
weight keeps only certain relevance buckets. core and defining are what the series is about; incidental and unweighted are often a single scene, so a recommendation built on them is usually wrong.
spoilers decides what happens to tags flagged as giving away plot: include (the default) returns them, exclude drops them, only returns just those. Flagged tags carry is_spoiler so they can be reasoned about without being repeated to a user.
counts describes the WHOLE series, not the page: total before any filter, matched after, returned on this page, and by_weight with every bucket present so a bucket the series has none of reads 0 rather than going missing. That is how you tell "no core tags" from "not asked for".
Genres are in this list, flagged is_genre and sorted first, then everything under Themes and Settings, then the rest — the order the website reads in, and the order that puts what the series is ABOUT on the first page instead of its cast. A genre id filters like any other tag id. (series_get splits them into a separate genres field instead, because its list is capped and the split stops the cap being spent on five genres. This one is paged, so it has no cap to protect.)
series_get.tags_find. Returns only those the series carries, so an empty result is a definite no. Matches the exact tag, NOT its subtree.page * limit must not exceed the 12,000-row result window, so the deepest page depends on limit (120 at limit 100, 500 at limit 24, 12,000 at limit 1). Past it the request is refused with 400 rather than served.page: page * limit must not exceed the 12,000-row result window, so a larger limit reaches fewer pages. At 100 the deepest page is 120.Every title ONE series is known by, filtered and paged, each with the language it is in and who says so.
This is the row behind the choice, not the choice. series_get and the list tools return the title resolve_titles PICKED for a language priority — send a titles priority in include there if you just want a different language back. Come here when the question is about the titles themselves: every name a series goes by, which one is official in a language, what a moderator noted about one.
traits says where a title comes from: official is the licensed name, native is the original, alternative is a name it is also known by. is_primary marks the one a moderator picked for its language, which is what a resolution prefers first.
language filters, it does not rank — several languages come back in recording order, not in your order. counts.by_language lists every language present with its count, so a language not listed is zero and you do not need to page to find out.
show_note adds the moderator note where there is one. Off by default: most titles carry none, and the ones that do are usually about provenance rather than the work.
series_get.counts.by_language.page * limit must not exceed the 12,000-row result window, so the deepest page depends on limit (120 at limit 100, 500 at limit 24, 12,000 at limit 1). Past it the request is refused with 400 rather than served.page: page * limit must not exceed the 12,000-row result window, so a larger limit reaches fewer pages. At 100 the deepest page is 120.Series with a high external score and low popularity — good, and largely unread. This is the rail for "surprise me", "something I have not heard of" or "underrated", and it is deliberately biased AGAINST the famous: a series everyone already knows cannot appear here however good it is.
For what is popular or gaining readers right now, use series_rising, which ranks on recent library adds and is close to the opposite of this. Neither takes a subject: to browse a theme, use series_search with tag ids and sort_by.
series_get and on a recommendation rail. Everything is off by default except meta, which is base. Ask for what the question needs and nothing else — a result is several times the size with everything on, and the cost is paid per row. Tags, relationships, links and the full title list are NOT here. Each is its own tool — series_get_tags, series_get_relationships, series_get_links, series_get_titles — because each is a list that has to be filtered and paged rather than switched on.Turn a complete, correctly spelled series title into its MangaBaka id. Use it whenever you already have the title — from the user, a library export, a filename, another site — and want the record.
This is the cheap way in. series_get takes ids, and the only other way to get one is series_search, which has a far tighter rate limit. Resolving a known title here and then calling series_get leaves the search budget for questions that actually need searching.
It matches the full normalized title, or a leading phrase of it, with the words in order. So "Re:Zero" finds "Re:Zero -Starting Life in Another World-" and "Mushoku Tensei" finds the subtitled record, which is what people actually type. What it does NOT do is correct spelling: one wrong letter returns nothing rather than a near miss.
An empty result is a successful answer, not an error — it means the catalogue has nothing under that spelling. Retry with series_search and the same string, which handles typos and mid-title fragments.
A result can match on a title you cannot see. Matching runs over EVERY known title, including ones in other languages, while alternate_titles here shows only a few — so "Berserk" legitimately returns Black Summoner, whose full title list includes "The Berserker Rises to Greatness." Judge a surprising row with series_get, which returns the complete list, rather than assuming it is wrong.
Unlike series_search this does NOT filter by content rating by default. The caller named a specific title, so returning it is not the same as surfacing adult material in a browse — and Berserk, a plain lookup by any measure, is rated erotica. Every result carries its own content_rating, so check it before describing what you got.
Returns no totals and no pagination, only the best matches for the title as written.
q, not query or title.series_get and on a recommendation rail. Everything is off by default except meta, which is base. Ask for what the question needs and nothing else — a result is several times the size with everything on, and the cost is paid per row. Tags, relationships, links and the full title list are NOT here. Each is its own tool — series_get_tags, series_get_relationships, series_get_links, series_get_titles — because each is a list that has to be filtered and paged rather than switched on.What else the people who have this series in their library also have. Built from shared library activity, NOT from tags or content — so it surfaces series that resemble the source in no measurable way and are still what its readers went to next. That is the thing it can do and series_recommend structurally cannot.
Reach for this FIRST on a bare "I just finished X, what next" — an unsteered question is what co-reading answers best. Use series_recommend when the user is steering ("like these but less horror") or naming several titles at once, since this takes exactly one id and cannot blend or pull toward a theme. It can still push away from one: tag_not excludes results carrying ANY of the ids you list.
The source series never comes back. But it only ever sees the ONE id you pass, so if the user named several titles, the others can appear here as recommendations — drop them yourself.
Present the rows in the order given. They are reranked for variety, so neither shared_users nor rank runs in order down the list and re-sorting by either throws the variety away. shared_users is how many libraries hold both and is good evidence to quote, but a popular series shares readers with everything, so it is not why one row beat another. There are no shared tags to report, because there may not be any.
Results come from real reading behaviour, so a series with few readers returns little or nothing. An empty result is either that or your own filters: content_rating and tag_not are applied to the co-read set, and a seed can have fifty co-read partners with none of them inside the rating default. Either way it does not mean nothing is similar — widen the filters, or retry with series_recommend, which works off content and always has an answer.
tags_find.series_get and on a recommendation rail. Everything is off by default except meta, which is base. Ask for what the question needs and nothing else — a result is several times the size with everything on, and the cost is paid per row. Tags, relationships, links and the full title list are NOT here. Each is its own tool — series_get_tags, series_get_relationships, series_get_links, series_get_titles — because each is a list that has to be filtered and paged rather than switched on."I liked A, B and C, what next." Give it every series the user named at once — it blends their tag profiles into a single probe rather than recommending from each separately, so one call beats three lists you would have to merge yourself.
The seeds themselves never come back. A declared sequel or spin-off of one can, flagged matched_related — often the right answer to "what next", but check the flag before offering it as something new.
Steer it: tag pulls toward a theme, tag_not pushes away from one (ids from tags_find). "More like these but no love triangles" is one call. It also works with no seeds at all — tag alone is "find me something that feels like this".
Several tag ids are combined with AND while strict is on, which it is by default — the same conjunction series_search uses, so a second tag narrows the pool. Turning strict off drops the filter entirely rather than loosening it to ANY: the ids then only steer the similarity, and a result need carry none of them.
Present the rows in the order given. They are reranked for variety, so score does not run strictly downwards and re-sorting by it throws the variety away. Read its magnitude, not its position.
Every result says WHY: shared_tags with ids, and flags for a shared author or a declared relation. matched_seed_ids lists the seeds sharing at least three tags with the result, which for seeds that resemble each other is usually all of them, so it is weak evidence to quote back. The response-level dna is the combined taste profile and is worth showing to the user as "here is what I matched on".
This is CONTENT similarity: it finds series that resemble the seeds. For "what else do people who read this also read", use series_readers_also_like — that rail can surface a series sharing no tags at all, which this one structurally cannot.
tags_find.tags_find.tag as a hard filter (true) or a soft nudge (false). While true the filter wins over similarity, so an unrelated tag returns the best series carrying it rather than nothing: expect score to collapse, not an empty result. Empty happens only when nothing in the catalogue satisfies the filters at all.series_get and on a recommendation rail. Everything is off by default except meta, which is base. Ask for what the question needs and nothing else — a result is several times the size with everything on, and the cost is paid per row. Tags, relationships, links and the full title list are NOT here. Each is its own tool — series_get_tags, series_get_relationships, series_get_links, series_get_titles — because each is a list that has to be filtered and paged rather than switched on.Series gaining the most library adds over the last window_days. This is momentum, not quality and not all-time popularity: it answers "what is everyone reading at the moment" and "what is blowing up", and a long-running favourite will not appear unless it is gaining readers right now.
window_days sets the horizon. A week is the default and reads as "this week"; widen it toward 30 for a steadier picture, shorten it for something sharper.
For quality rather than momentum, use series_hidden_gems — that rail returns well-rated series that few people have found, which is close to the opposite of this one. Neither takes a subject: to browse a theme, use series_search with tag ids and sort_by.
series_get and on a recommendation rail. Everything is off by default except meta, which is base. Ask for what the question needs and nothing else — a result is several times the size with everything on, and the cost is paid per row. Tags, relationships, links and the full title list are NOT here. Each is its own tool — series_get_tags, series_get_relationships, series_get_links, series_get_titles — because each is a list that has to be filtered and paged rather than switched on.Search the MangaBaka catalog of manga, manhwa, manhua and light novels. Handles partial titles, misspellings, single keywords and autocomplete fragments: it applies typo tolerance, n-gram and prefix matching and broad token recall, then ranks by relevance.
q matches TITLES ONLY. For themes, settings, tropes and content, pass numeric tag ids from tags_find; tag names are not accepted. A tag matches its entire subtree, so a lower-level tag is a broader filter. tag ids are combined with AND by default; pass tag_mode: "or" to widen.
A tag filter means the series CARRIES that tag, not that the tag is what it is about. A tag sits on a series at a weight from defining down to unweighted, and the filter matches every weight with no way to ask for the strong ones, so Hunter x Hunter is a real hit for Isekai. A genre id is the closest thing to "the user means this KIND of story", but it is also a subtree root, so it matches series that carry none of the genre itself: tag: [44] returns Delicious in Dungeon, which has Body Horror and no Horror genre. There is no way to ask for a genre without its descendants.
So treat a tag filter as recall, not precision, and verify before you describe. The cheap check is include: ["genres"] on this call: every result comes back with its genres and their weights, so a row carrying Horror at unweighted or not carrying it at all is visible without a second request. For a non-genre tag, or to read a specific tag's weight, call series_get with that ONE id — a batched call returns a short tag list, so a tag missing there is not evidence the series lacks it.
Works with no q at all: "best romance manhwa" is tag plus sort_by, not a text search. There is nothing to rank without q, so the default relevance_desc falls through to the same vote-weighted score as score_desc. Pass the sort_by you actually want.
Results are limited to safe and suggestive titles by default. A third of the catalog is erotica or pornographic, so widen content_rating only when the user has clearly asked for adult material. excluded_count counts content_rating and nothing else: rows dropped by not_content_rating, tag_not or any other exclusion are invisible, and a shrinking total is the only sign of those. Within content_rating it is how many matches fall outside the ratings you asked for, in either direction. Under the default that is adult titles being withheld and worth saying so rather than reporting the series as missing; if you widen to adult ratings only, the same number is the safe titles you just excluded, so do not report it as matches being hidden from the user.
Two things about sort_by that the field names do not tell you. score_desc orders by a vote-weighted score, not by the rating in the response, so a series with a slightly lower rating and far more votes ranks above one with a higher rating — trust the order rather than re-sorting by rating. And popularity_desc currently returns the LEAST popular series; popularity_asc is the one that returns the most popular.
The four published_* bounds drop any series with no recorded date, which is over half of completed manga for the end date. So a date-bounded search answers "of the series with a known date" and never says how many it silently left out. Use status when the question is whether something finished, and the dates only when the question is when.
A result is slim by default: title, type, status, year, rating, length, content rating, popularity rank and the MangaBaka link. include adds groups on top and turning all of them on makes a result about four times the size, so ask for what you will actually use. Every tool here that returns a list of series takes the same include.
This tool is rate limited far more tightly than the rest, so prefer one well-filtered search over several narrow ones.
q, not query or search. Titles ONLY: themes, settings and content go in tag as ids from tags_find. Omit it entirely for a filter-driven browse.page * limit must not exceed the 12,000-row result window, so the deepest page depends on limit (120 at limit 100, 500 at limit 24, 12,000 at limit 1). Past it the request is refused with 400 rather than served.page: page * limit must not exceed the 12,000-row result window, so a larger limit reaches fewer pages. At 25 the deepest page is 480.popularity_asc is what "most popular" means and popularity_desc is the least. score_desc is a vote-weighted score, not the rating field. With no q, relevance_desc falls through to score_desc.status instead to find finished works. Accepts YYYY, YYYY-MM or YYYY-MM-DD.series_get and on a recommendation rail. Everything is off by default except meta, which is base. Ask for what the question needs and nothing else — a result is several times the size with everything on, and the cost is paid per row. Tags, relationships, links and the full title list are NOT here. Each is its own tool — series_get_tags, series_get_relationships, series_get_links, series_get_titles — because each is a list that has to be filtered and paged rather than switched on.tags_find, never guessed. A parent tag matches its whole subtree. The result echoes each id back under filters.tag with the tag it actually names — read that before describing the results, because a wrong id returns a full page of plausible rows and nothing else marks them wrong.tags_find.content_rating filter. Above zero means results exist that were not shown.Resolve a theme, trope, setting, genre or content concept to the numeric tag ids that series_search filters on. series_search accepts ids only, never names, so this is the step before any search that is about content rather than a title.
Ranks over the tag name and its ancestor path, with stemming ("assassin" finds Assassins), prefixes, substrings and single-character typo tolerance, so an approximate word is fine.
Tags form a tree and a tag id matches its ENTIRE subtree, so level is the breadth dial. Each step down narrows by roughly half: the Themes root matches most of the catalogue, its Boys Love descendant a quarter of that, and that one's Yaoi leaf less again. Pick the level that matches how specific the user was, and read name_path to see where a tag sits.
One word often names several unrelated tags in different branches: "supernatural" returns the Supernatural genre under Settings, Supernatural Elements beside it, and Supernatural Beings under Species & Creatures, none of them an ancestor of the others. name_path and is_genre are how you tell them apart, and the genre is usually the broad sense a reader means. Passing all of them is not the safe option: series_search's tag combines with AND, so three senses of one word asks for series carrying all three.
Every tag is returned with its own content_rating. That rating is about the CONCEPT, not about what a search using it returns: Omegaverse is rated erotica and still returns hundreds of safe and suggestive series. Do not widen series_search's content_rating because a tag is rated; that is a separate decision.
This searches NAMES and paths, not concepts, so a confident single hit is not coverage: "sexual violence" returns one tag while Rape and Sexual Abuse sit under other names in other branches. That matters most when you are building a tag_not, where missing a sibling means telling the user you filtered something out and handing them it anyway. Before excluding a sensitive concept, open its parent with tags_list and take the siblings too.
Use tags_list to browse the tree when you do not have a word to search for.
has_more is true.Walk the tag hierarchy when you do not have a word to search for: call it with no arguments for the top-level taxonomy, then again with a parent id to open that branch.
Rows come back in depth-first tree order and level is the indent depth, so the two together are the tree. A tag matches its whole subtree when used in series_search, so a level-1 tag is a broad sweep and a level-4 tag pins something down.
depth counts levels from parent, or from the roots when there is none. It defaults to 1, which is one level: the tree runs to a few thousand tags and is never worth fetching whole.
Pass is_genre: true for the genres alone, the 21 coarse buckets a reader names. That is the fastest way to answer "what genres are there", and it ignores depth — the genres are scattered two to four levels down, so bounding them would return a partial set.
Use tags_find when you have a word to look up, which is nearly always faster.
parent to include, or below the roots when there is no parent. Defaults to 1. Ignored when is_genre is true.