Skip to main content
Glama

liara-docs-mcp

CI Python 3.10+ MIT

An MCP server over the official Liara cloud documentation. Four read-only tools — search, page read, deployment config and build-log diagnosis — over a corpus that ships inside the package: 3,502 chunks across 1,143 pages, mostly Persian, some English.

No ingest step, no database, no vector store, and no API key required. A fresh install answers immediately.

you  ▸ my database gets wiped after every deploy on Liara. why?
      ⚙ search_docs("liara disk persistence deploy")
      ⚙ read_page("https://docs.liara.ir/paas/disks/getting-started/")

The retrieval stack and the prompt-injection defenses are lifted from lia-helper, a Persian RAG assistant over the same corpus that won Best Solution for the Liara challenge at the StartCoach hackathon in August 2026. This repository is the part of it that is useful to any assistant, not just that one.


Install

The server needs no configuration. Point your host at it and restart.

Claude Code

claude mcp add liara-docs -- uvx --from git+https://github.com/SalehB1/Lira-mcp liara-docs-mcp

Claude Desktop~/Library/Application Support/Claude/claude_desktop_config.json on macOS, %APPDATA%\Claude\claude_desktop_config.json on Windows. Quit Claude fully and reopen it after editing; the config is read at launch.

{
  "mcpServers": {
    "liara-docs": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/SalehB1/Lira-mcp", "liara-docs-mcp"]
    }
  }
}

Cursor.cursor/mcp.json

{
  "mcpServers": {
    "liara-docs": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/SalehB1/Lira-mcp", "liara-docs-mcp"]
    }
  }
}

VS Code.vscode/mcp.json. Note the different wrapper key and the type field.

{
  "servers": {
    "liara-docs": {
      "type": "stdio",
      "command": "uvx",
      "args": ["--from", "git+https://github.com/SalehB1/Lira-mcp", "liara-docs-mcp"]
    }
  }
}

Then ask your assistant something it could not answer before:

دیتابیسم بعد از هر دیپلوی پاک میشه، چیکار کنم؟


Related MCP server: docs-scraper

The tools

Tool

Returns

Bounds

search_docs

Numbered snippets with their source URLs. The tool to reach for on any factual claim.

query ≤ 300 chars, k clamped to 1–8, 600 chars per snippet

read_page

One whole documentation page. No network fetch exists on this path — the URL must already be in the loaded corpus.

body ≤ 8,000 chars

platform_docs

Every canonical deployment page for one of 15 platforms, plus the complete liara.json reference, plus one search per stated need. Enough to write a correct liara.json and cite every key.

platform through an enum, ≤ 10 needs of ≤ 80 chars

diagnose_log

The error signature extracted from a failing build or runtime log, and the pages that explain that class of failure.

log ≤ 20,000 chars, last 300 lines, signature ≤ 200 chars

There is also one resource, liara://corpus, reporting which documentation snapshot answered: upstream commit, ingest date, chunk and page counts, and whether dense search is active.

Every tool is annotated read_only_hint. Nothing here writes anything.


How it searches

Three ranked lists, fused by reciprocal rank at equal weight. Word-level BM25 and dense cosine over 50 candidates each, character-4-gram BM25 over 20, combined as 1/(60 + rank + 1) with ties broken on raw cosine. No reranker, no cross-encoder: either one puts a model call on every query.

The n-gram list is deliberately the short one. Sub-word overlap is the weakest of the three signals, and a long tail of it outvotes the word index on literal questions. Measured on a 100-question set: at 18–22 candidates literal recall@5 holds its 51/55 baseline while paraphrase recall goes 14 → 18; at the full 50, paraphrase reaches 20 but literal drops to 49.

One Persian normalizer, hand-written, no NLP dependency. A 49-key str.translate table applied after NFC: Arabic→Persian letter unifications, both Arabic-Indic digit ranges folded to ASCII, and 19 deletions covering tatweel, diacritics and invisible bidi marks. No target is itself a key, which makes normalize idempotent by construction. ZWNJ is kept and ZWJ is deleted, because ZWNJ is load-bearing orthography: پایگاه‌داده must stay one token while Node.js splits into two. Stemming is four suffixes, additive — tokenize emits both the surface form and the stem, so a wrong cut can only ever add a term the query side produces identically.

Character n-grams buy morphology and typos, not scripts. داکر and docker share no n-gram at all. Bridging that is what the 73 bidirectional synonym groups are for, and synonym expansion is BM25-only: the dense leg embeds the untouched query.

The dense list is optional and degrades silently by design. Set AVALAI_API_KEY and each query is embedded through an OpenAI-compatible /embeddings endpoint, cached in process. Without it — or on a timeout, a bad response, or a provenance mismatch between embeddings.npz and the corpus it was built from — the query runs on the two lexical lists and nothing errors. Seven independent conditions drop the dense leg, and none of them fails a request.


Grounding

The corpus is public third-party markdown and demonstrably contains instruction-shaped text: one page in it is a leftover content brief telling the reader to rewrite links. Four layers assume it is hostile, and all four are enforced in code rather than asked for in a prompt.

Layer

Where

Every result carrying documents is wrapped in <docs source="untrusted">…</docs>, so a model is told the contents are data.

tools.py

_neutralize escapes any <docs> tag in corpus text so a page cannot close the envelope or forge a trusted one, and breaks a line-leading [n] so a page author cannot mint a citation header. Applied separately to URL, title, heading and body.

tools.py

A URL allowlist at load: prefix https://docs.liara.ir/ and a slug-shaped path regex, because everything after the prefix is author-chosen and a URL carrying </docs> would close the envelope from inside.

retrieval.py

Citation numbers are minted server-side by the registry, keyed by chunk id, at the moment a chunk is formatted.

citations.py

read_page reads only URLs already in the loaded index. There is no HTTP client on the tool path, so it cannot be pointed at an internal host.

Two honest limits. A number can only be created here, but nothing resolves the markers back out of a finished answer, because over MCP the answer is written by the client's model and this server never sees it — the upstream project could check that and this one cannot. And the instruction that the corpus is data rather than instructions travels in the server's instructions field, which a client is free to drop; the four rows above are the part that does not depend on being read.

Runnable check: uv run pytest tests/test_untrusted_corpus.py -v drives a javascript: URL, a plain-http URL and a URL carrying </docs> through the real loader, then feeds _neutralize a forged envelope and two forged citation headers.


Run it as a shared service

--http serves Streamable HTTP instead of stdio, for one instance several people point at.

uvx --from git+https://github.com/SalehB1/Lira-mcp liara-docs-mcp --http --port 8000
# clients connect to http://127.0.0.1:8000/mcp
# GET /healthz answers {"status":"ok","chunks":3502,...}

The Host allowlist is the one thing to get right. The MCP SDK defaults to DNS-rebinding protection that accepts only localhost Host headers, which is correct on a laptop and returns 421 Misdirected Request to every request behind a real hostname. So:

  • Set MCP_ALLOWED_HOSTS=mcp.example.com,mcp.example.com:* and the server allowlists exactly those. Entries are matched literally, so list the bare host and the :* port form both. There is no wildcard, and MCP_ALLOWED_ORIGINS on its own is refused at startup rather than left to 421 every request.

  • Bind to loopback with nothing set, and the SDK default stands.

  • Bind to a public interface with nothing set, and the server assumes a reverse proxy already controls Host, switches the check off, and says so in the log rather than silently refusing every caller.

Deploying to Liara, whose CLI reads the committed liara.json:

liara app:create --platform docker --name liara-docs-mcp
liara deploy

Do not set AVALAI_API_KEY on a public instance unless you accept paying for one embedding call per distinct query from anyone who can reach it. There is no authentication on the HTTP transport; this serves public documentation and nothing else.


Environment

Every key is optional and every default works.

Key

Default

What it does

AVALAI_API_KEY

(unset)

Turns on the dense retrieval list. The only outbound call this server makes.

AVALAI_BASE_URL

https://api.avalai.ir/v1

Any OpenAI-compatible /embeddings endpoint.

EMBED_MODEL

text-embedding-3-small

Must match what embeddings.npz was built with, or the matrix is refused at load.

EMBED_DIM

1536

Same. A different-width model is caught here; a same-width one is caught by the provenance hash.

DATA_DIR

packaged data/

Where chunks.jsonl, embeddings.npz and corpus_meta.json live.

LOG_LEVEL

INFO

Logs go to stderr; stdout is the stdio wire.

NO_PROXY_HOSTS

api.avalai.ir

Hosts to keep away from a local proxy. api.avalai.ir answers in 0.2 s directly and hangs for 30 s through one.

MCP_ALLOWED_HOSTS

(unset)

--http only. See above.

MCP_ALLOWED_ORIGINS

(unset)

--http only. Browsers, and nothing else, send Origin. Requires MCP_ALLOWED_HOSTS.


Development

git clone https://github.com/SalehB1/Lira-mcp && cd Lira-mcp
uv sync
uv run pytest -q            # 73 checks, no key and no network
uv run ruff check .
uv run mcp dev src/liara_docs_mcp/server.py    # the MCP Inspector

The test suite runs the real corpus through the real tools and drives the server through an in-process MCP client, so what it asserts is what a host actually sees. It forces the keyless configuration, so a real key in your shell cannot turn a test run into a spend.


The corpus

A frozen snapshot of public/llms/**/*.md from liara-cloud/docs, committed to this repository rather than fetched at startup — so there is no crawler in the boot path and a cold start does no work.

Upstream commit

dbb7430b1abc5bf92ccca3538f45c54bdc632fa8

Ingested

2026-08-21

Contents

3,502 chunks over 1,143 pages — 3,270 Persian, 232 English

Embeddings

3,502 × 1,536, text-embedding-3-small

corpus_meta.json carries that provenance, and embeddings.npz is stamped with the corpus hash it was built from: a matrix that disagrees with the chunks actually on disk is refused rather than multiplied. Ask the server itself by reading the liara://corpus resource.

Refreshing it means running lia-helper's ingest/ against a newer docs commit and copying the three files back into src/liara_docs_mcp/data/.


Limitations

  • The corpus is frozen at one upstream commit. Nothing here schedules a refresh, so pages added to the documentation since August 2026 are not searchable.

  • Dense retrieval has one provider and no fallback. Without a key it is simply absent, which is a quality difference a caller cannot see — /healthz reports dense_search for exactly this reason.

  • The whole corpus is scored on every query. A matrix @ query dot product over 3,502 chunks with no approximate-nearest-neighbour index is right at this size and does not carry to a million.

  • Persian-first. Queries work in both languages and 174 of the 375 synonym keys are Latin, but the documentation itself is overwhelmingly Persian and the answers a model composes from it will be too.

  • No authentication on the HTTP transport. It serves public documentation; see the spend note above before exposing an instance with a key set.

  • Retrieval quality is unmeasured against what actually ships. The numbers quoted above come from the original project's harness against raw user wording, not against queries an MCP client's model composes.


MIT — see LICENSE.

Available Tools

4 tools
diagnose_logA
Read-only

Extract the error from a failing build or runtime log and fetch the docs for it.

Reduces the log to one error signature — the most specific failure line, not the last one — and returns the documentation pages explaining that class of failure.

Args: log: The raw log text. Only the last 300 lines are read.

ParametersJSON Schema
NameRequiredDescriptionDefault
logYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already mark the tool as readOnlyHint=true and openWorldHint=false. The description adds meaningful behavioral context beyond that: it reduces the log to one error signature, chooses the most specific failure line rather than the last one, and returns documentation pages. This is useful, non-contradictory transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and well-structured: a front-loaded purpose statement, a short behavioral clarification, and a one-line parameter specification. Every sentence adds value, and the most important constraint (300-line limit) is stated explicitly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter tool with an output schema and read-only annotations, the description covers the key operational details an agent needs: input format, log truncation, and the error-reduction behavior. It does not specify what happens when no error signature is found, but the existing output schema reduces that burden.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must carry the parameter meaning. It does: 'log: The raw log text. Only the last 300 lines are read.' This clarifies both the content and a critical truncation behavior for the single parameter.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Extract the error from a failing build or runtime log and fetch the docs for it.' It clearly conveys a diagnostic workflow distinct from a generic docs search, though it does not explicitly name sibling tools or state what makes it different from search_docs.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies when to use the tool—when you have a failing build or runtime log—and includes the input constraint that only the last 300 lines are read. However, it does not explicitly contrast with alternatives like search_docs or read_page, nor does it state when those should be preferred.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

platform_docsA
Read-only

Collect the deployment documentation for one platform, ready to write a liara.json.

Returns the platform's quick start, deployment and environment-variable pages plus the complete liara.json reference, so every key you write can be cited.

Args: platform: The target platform, for example "django" or "nextjs". needs: Extra requirements to look up as well, such as "disk", "cron" or "websocket". At most 10 short items.

ParametersJSON Schema
NameRequiredDescriptionDefault
needsNo
platformYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With readOnlyHint=true and openWorldHint=false already covering the read-only and closed-world safety profile, the description adds the aggregation behavior: it collects multiple pages for one platform and includes the full liara.json reference. No side effects or failure modes are described, but none are required given the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is about 60 words: a one-sentence purpose, a one-sentence output description, and formatted Args. No sentence is wasted, and the key action is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has only two parameters, an output schema exists (so return values need not be described), and annotations cover the safety model. The description covers purpose, output contents, and both parameters, leaving no essential invocation detail missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has 0% coverage, and the 'Args' section compensates by explaining that 'platform' is the target and 'needs' is an optional list of extra lookups, with examples ('disk', 'cron', 'websocket') and a maximum of 10 items. This is enough for an agent to construct valid calls.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence names a specific verb ('collect'), a specific resource ('deployment documentation for one platform'), and the intended end-goal ('ready to write a liara.json'). It also enumerates the return contents (quick start, deployment, env-var pages, liara.json reference), which separates it from the generic search_docs/read_page siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase 'ready to write a liara.json' gives an explicit scenario for choosing this tool. It does not name sibling alternatives or exclusion conditions, but the context is unambiguous enough to route selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

read_pageA
Read-only

Read one whole documentation page from the local corpus.

The URL must be one a previous search returned. No other URL can be fetched: this server has no HTTP client on the tool path and reads only what is already indexed.

Args: url: A canonical docs.liara.ir URL taken from a search result.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and openWorldHint=false, so the read-only nature is covered. The description adds meaningful behavioral context: the tool cannot fetch arbitrary URLs and reads only already-indexed pages, which is valuable beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Every sentence earns its place: the purpose is front-loaded, the critical limitation is stated next, and the parameter explanation is brief and direct. No filler or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter read tool with annotations and an output schema, the description is complete. It covers the prerequisite (previous search), the constraint (no arbitrary URLs), and the exact parameter expected. Nothing needed to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must carry the burden for parameter meaning. It does so clearly by stating that 'url' must be a canonical docs.liara.ir URL taken from a search result, which is essential information the schema alone does not provide.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific action ('Read one whole documentation page') and a specific resource ('the local corpus'), which clearly distinguishes it from the sibling tools. The addition that only pages returned by a prior search can be fetched makes its scope unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit when-to-use guidance: the URL must come from a previous search result, and no other URL can be fetched. It also explains the underlying reason (no HTTP client, only indexed content), leaving no ambiguity about when this tool applies.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_docsA
Read-only

Search the Liara documentation and return numbered snippets with their source URLs.

Use this for every factual claim about Liara. Write the query in the user's own words plus the product name; Persian and English both work.

Args: query: What to search for. Longer than 300 characters is truncated. k: How many snippets to return, 1 to 8. Values outside that range are clamped.

ParametersJSON Schema
NameRequiredDescriptionDefault
kNo
queryYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations already declare readOnlyHint=true, so no contradiction. The description adds useful behavioral detail by specifying that the result includes numbered snippets with source URLs, and the parameter descriptions disclose truncation and clamping behavior, going beyond the minimal annotation info.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and well-structured: a purpose sentence, a usage directive, a language tip, and then concise parameter explanations. No redundant or filler content; every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (2 params, output schema present), the description covers all necessary aspects: what it does, how to use it, parameter semantics, and output format. The presence of an output schema means detailed return structure is not needed in the description, so completeness is high.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has no descriptions for the parameters (0% coverage), but the description fully compensates by explaining query as 'What to search for' with a 300-character truncation limit, and k as 'How many snippets to return' with a 1–8 range and clamping. This adds complete meaning to both parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's function: searching the Li documentation and returning numbered snippets with source URLs. It also explicitly says to use it for every factual claim about Liara, which contrasts with the sibling tools (read_page, platform_docs, diagnose_log) that serve different purposes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit when-to-use guidance: 'Use this for every factual claim about Liara.' It also gives concrete query formulation instructions (use the user's own words plus the product name, both Persian and English work), making the usage context unmistakable.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 4 tool updatesv0.1.0
    • First observeddiagnose_log
    • First observedplatform_docs
    • First observedread_page
    • First observedsearch_docs

TDQS

A4.4/5.0
Disambiguation5/5

Each tool has a clearly distinct purpose: search, read a specific page, aggregate platform-specific docs, and diagnose logs. No overlap or ambiguity.

Naming Consistency4/5

All tools use snake_case and mostly follow verb_noun (search_docs, read_page, diagnose_log), but platform_docs is a noun phrase rather than a verb, which is a minor deviation.

Tool Count5/5

Four tools are well-scoped for a documentation server, covering search, retrieval, and specialized workflows without unnecessary bloat.

Completeness4/5

The core documentation lifecycle (search, read, platform-specific aggregation, and error diagnosis) is covered. Minor gaps like browsing an index or listing available platforms exist, but they are not critical.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

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/SalehB1/Lira-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server