doc-platform
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., "@doc-platformsearch docs for audience gating setup"
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.
@ingadhoc/docs-platform
Adhoc's documentation platform: one search engine, one MCP core, one access gate, and one leak guard, consumed pinned by the content repos (oba-docs, odumbo-docs, adhoc-docs).
Before this, the four pieces lived forked across the three repos: the same file with three dialects, and each fix propagated by hand — or not propagated. The measurement is in docs/unificacion/: lib/mcp/indice.mjs had 41 differences among the three copies, and 17 were fixes that one repo had and the other two didn't. The most expensive case: the leak guard was byte-identical in two repos and didn't exist in the third.
ADR 0006 from
knowledge-management— one repo per content body, and the platform as a separate package: content and engine have different lifecycles and different owners.ADR 0007 from
knowledge-management— the gate and the leak guard belong to the platform, not to each site: a protection that each repo reimplements is a protection that some repo doesn't have.Stage A of the
arquitectura-plataforma-docsspec: this package, with the two versioned contracts and the drift-check that makes the pin lag visible.
How it's consumed
npm i --ignore-scripts github:ingadhoc/doc-platform#v0.1.0Exact pin, always by tag. No ^, no main, no branches: the pin is what prevents a platform fix from breaking three sites at once, and it's what allows a one-line rollback. A range makes docs-drift-check fail on purpose — a pin that doesn't pin isn't a pin.
--ignore-scripts recommended. This package has no install script and won't have one; the flag is for the whole tree, because this runs in the buildCommand of public sites. Same reason the package has a single dependency (minisearch, which the search engine needs) and zero devDependencies: minimal surface in the build.
What the consumer already has and this package doesn't declare: mcp-handler and zod, which lib/mcp/mcp-handler.mjs imports. They're repo dependencies on purpose: the repo decides which version of the MCP framework it deploys with, and the package doesn't impose one. All three repos have them today.
After npm i, the consumer repo is left with three lines of glue:
// api/mcp.mjs
import { crearMcp } from '@ingadhoc/docs-platform/mcp-handler';
import { crearFeedback } from '@ingadhoc/docs-platform/feedback';
import * as indice from '@ingadhoc/docs-platform/indice';
import { config } from '../docs.mcp.config.mjs';
export const { handler, default: fetchHandler } = crearMcp({
config,
indice,
crearIssue: crearFeedback(config.feedback),
});// middleware.js — en la RAÍZ del repo (Vercel lo exige ahí)
import { next } from '@vercel/functions';
import { decidir } from '@ingadhoc/docs-platform/gate';
const AUDIENCIAS = ['publico', 'interno']; // adhoc-docs: ['interno']
export default function middleware(request) {
return decidir(request, process.env, { audiencias: AUDIENCIAS }) ?? next();
}// package.json del consumidor — el guard, dentro del buildCommand
"build:publico": "node tools/build.mjs --audience=publico && npm --prefix site run build && npx docs-guard-fuga --salida=dist/publico"The && isn't cosmetic: it's what aborts the deploy when the guard exits with 1. Don't change it to ;.
What it exports
Import | What it is |
| search engine: |
|
|
|
|
| constant-time token comparison (uses |
| the |
|
|
|
|
|
|
| the reference |
bin | the leak guard, for the |
bin | the drift-check, for the consumer's CI |
The two contracts
Both carry schemaVersion, and both readers throw if the emitter declares a newer version than they know how to read — or if it doesn't declare one. No silent degradation: a wrong index that answers badly is worse than one that doesn't answer.
config ↔ platform:
docs.config.json, with schema published atschema/docs.config.schema.jsonand validator inlib/config.mjs(own, no dependencies:ajvdoesn't enter a public site's build). The design of each field, with measured evidence, is indocs/unificacion/diseno-eje.md; the three current configs translated, inmapeo-configs.md.index ↔ engine: emitted by each repo's
tools/build.mjsand read bylib/mcp/indice.mjs. It's specified indocs/unificacion/contrato-indice.md.
The axis, in a table
The corpus declares one axis as an object: { tipo, default?, valores[] }.
| corpus | param in the tools |
| wildcard (articles outside the axis) |
| oba-docs |
| picks the | yes ( |
| adhoc-docs |
| structured ambiguity (doesn't declare | no |
| odumbo-docs | (not exposed) | — | — |
The leer() rule is one and has no if per axis type: it only chooses when the config declared who to choose. What changes behavior is the presence of eje.default, not the type — and there's a test that proves it by giving a default to a corpus with a project axis.
Running the tests
npm install && npm test # 227 casosbloques needs a content repo (it actually runs its tools/build.mjs on the incident fixtures) and is skipped with a reason if there isn't one:
DOCS_REPO=~/repositorios/oba-docs node --test tests/bloques.test.mjsThe HTTP handler slice of mcp.test.mjs (16 cases) is also skipped with a reason if the checkout doesn't have mcp-handler/zod, which are consumer dependencies, not this package's. With both installed, mcp gives 57. A case without its capability is explicitly skipped; it isn't run degraded.
For jjs — open decisions
What this assembly doesn't solve on its own. The first three are from diseno-eje.md §7 and commit the contract; the rest came out of the four analyses and are still alive after unifying.
1. One axis per corpus: is the ceiling accepted?
schemaVersion: 1 supports one axis per config, and today that's enough for the three repos. The day a corpus needs project × version at once, the schema doesn't express it and the way out is a schemaVersion: 2 with ejes: [...] (plural). Design recommendation: accept the ceiling explicitly and let real need reopen it with evidence (same criterion as the Stage B bump alarm). It's your call because it commits the major.
2. metadata.types: per-corpus vocabulary or Adhoc-wide?
Today only adhoc-docs has types, and its 6 values look a lot like a knowledge-management standard (concepto, referencia, procedimiento, troubleshooting, guia, indice). If the vocabulary is Adhoc's, it doesn't go in each repo's config: it goes in the package, and the config only says whether it requires it. It's a content governance decision, not a schema one; until it's settled, the schema leaves it as a per-corpus list (compatible with both outcomes).
3. The leak guard opt-out in adhoc-docs: do you sign it?
The schema requires declaring deploy.guardDeFuga, so silent omission is no longer possible. Two ways out remain, both defensible: {"activo": false, "motivo": "…"} (that repo has no public build: its gate is unconditional, and the guard protects against leakage to the public build), or the guard goes in anyway, as a belt. The motivo currently in mapeo-configs.md literally says "PENDIENTE DE FIRMA (jjs)".
And there's a technical part that isn't fixed by copying the file (QUESTION 1 from analisis-04-seguridad.md): adhoc-docs has no :::interno blocks, doesn't emit site/generated.json with audience, and has no deploy.proyectos map. With the guard active as-is, its build fails from the start because "site/generated.json doesn't exist". The strict one is for it to emit those two things.
4. The audience list is still duplicated, and the drift-check still doesn't compare it
docs.config.json → audiences and middleware.js → AUDIENCIAS have to match, and there's no way to avoid the duplication: the edge doesn't read from the filesystem. It's exactly the kind of silent drift where the fork started. A CI case that compares them is missing (today's docs-drift-check measures the pin, not that coherence).
5. Three things to check in the repos before tagging
DOCS_AUDIENCEin the three environments of each Vercel project (Production, Preview, and Development) before the merge that adopts the package. With fail-closed, a project without the variable returns 503. It's the safe direction, but it's not free.--esperadain the currentbuildCommands: now the guard rejects it running on Vercel. If any buildCommand passes it today, that deploy starts failing. It couldn't be verified from the snapshots.The MCP GET returns 503 if the deployment doesn't declare a servable audience. It's an observable change for the consumer: Claude Code's preflight receives 503 instead of the banner when the deployment is misconfigured.
6. Measured debt this package can't close
The preprocessor's fail-closed emits and then fails. With a misspelled directive (
::: interno),build.mjswritessite/docs/**with the internal line inside and then exits with 1. Today it doesn't leak because thebuildCommandchains with&&: the protection is in the operator, not in the program. It's listed as atodointests/bloques.test.mjs, and the unification ofbuild.mjsfixes it — which was not included in this stage.tests/bloques.test.mjswrites to<repo>/site/because in oba and odumbo the build output is hardcoded. After running the suite you have to regenerate withnpm run gen.Limits of the guard's lexical approach: numbers and strings shorter than 5 characters never get a probe (a
4821key, an acronym), images are not scanned, and a leak insideapplyBlocksdoesn't generate a probe. It's in the guard's header; I'm repeating it here because it's the part that can be confused with coverage.serverInfo.versionis still hardcoded to'1.0.0'in the handler. It should come from the pinned package'spackage.json, so an MCP client can report which platform version it talked to. It wasn't changed: that would be inventing behavior.The wildcard belongs to the axis
tipo, not the corpus. A corpus with aprojectaxis cannot have a cross-cutting document (eje: nullstays invisible to any filter). If it's ever needed, the strict output is that the index contract forbids it while the wildcard is off, so the contradiction fails at build time and not at runtime.The spec says "vitest" as the Stage A test convention, and none of the three repos use vitest: the real convention — and this package's — is native
node:test. That line is worth fixing before someone installs vitest to comply 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.
Related MCP Connectors
Query any docs site via MCP. Submit a URL, ask questions, get cited answers.
A paid remote MCP for Context7 MCP docs, built to return verdicts, receipts, usage logs, and audit-r
Knowledge coverage map and health score. Ingest docs into a governed knowledge graph via MCP.
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/ingadhoc/doc-platform'
If you have feedback or need assistance with the MCP directory API, please join our Discord server