Make @imqueue first-class inside AI coding agents. When a developer asks their assistant to "build an @imqueue service" or "how do I expose a method", the agent should reach for authoritative docs and correct scaffolding rather than hallucinating an API. This is the GEO (Generative Engine Optimization) counterpart to SEO: instead of ranking in a search page, we rank at code-time, inside the tools developers already use.
Three capabilities, fourteen tools locally:
- Docs access —
search_docs,get_doc,list_packages,package_status - Offline scaffolding —
scaffold_service,scaffold_client(templates, no deps) - CLI bridge —
cli_status,cli_install,cli_help,create_service,generate_client,fleet(imq ctl),config(imq config),logs(imq log) (drive the installedimqbinary — install it, create projects, generate clients, manage the local fleet and CLI configuration)
The first two capabilities are read-only and run anywhere; the CLI bridge acts on the
machine the server runs on, so the hosted endpoint carries the first two plus a
local_install_guide and nothing else — seven read-only tools.
AI agent (Claude Code / Cursor / …)
│ MCP (JSON-RPC over stdio)
▼
@imqueue/mcp ── fetch ──▶ imqueue.org
├─ docs.ts (/llms.txt, /<page>/index.md)
├─ packages.ts (static catalog)
└─ scaffold.ts (code templates)
- Transport: stdio (the universal local-MCP transport; works with every host
today). The hosted Streamable HTTP variant since planned in §7 now exists at
mcp.imqueue.org/mcp; it shares this code and serves the read-only subset (docs + scaffolding + a local-install guide), because the CLI-backed tools act on the caller's own machine. Seeworker/README.md. - Runtime: Node ≥ 18, TypeScript,
@modelcontextprotocol/sdkhigh-levelMcpServer,zodinput schemas. Ships as an npm bin (npx -y @imqueue/mcp). - Docs source: fetched live from the sites' existing machine-readable feeds and
cached in-process (1 h TTL), with a 5 s per-request timeout and a fall back to the
stale copy rather than an error. No docs are bundled, so the server can never go
stale against a release. Only
imqueue.organdimqueue.comare ever fetched (host-checked).
imqueue.org already emits, for GEO:
/llms.txt— curated index (## Section+- [Title](url): description)/api/search-index.json— every exported symbol of the current majors/<page-url>index.mdand/<page-url>.md— a plain-markdown mirror of every page/blog/search-index.json— structured post index
imqueue.com emits its own /llms.txt and mirrors, covering licensing, pricing and
support — the questions the framework docs deliberately do not answer, and therefore
the ones this server could not answer while it read one site.
The MCP server is a thin, agent-facing adapter over those — one source of truth.
Parse both /llms.txt feeds (imqueue.org for the framework, imqueue.com for
licensing/pricing/support) plus /api/search-index.json into
{title, url, description, section, symbol?} entries, then rank them.
Ranking is IDF-weighted, not flat overlap: every term's contribution is scaled by
how rare it is across the corpus, because every page here is about @imqueue services
and imqueue/service/queue say nothing about which page answers the question.
On top of that:
- Titles match on identifier segments —
MigrateDownOptionsyieldsmigrate/down/options— so a term cannot match the middle of a longer word. An exact segment scores as a whole token, a prefix of one scores less, and fragments under four characters score nothing. - Inflections (
-s/-es/-ies,-ing,-ed,-tion/-sion) are expanded on both the query and the corpus and matched form-to-form, and discounted relative to the word as written. No synonym map. - A symbol page can match on its summary alone, weakly; a symbol's package
(which lives in its section) is scored, so
package-style queries work and the optionalpackagefilter scopes a search to one of a mutually exclusive pair. - Blog posts are demoted by a multiplier, not a sort key, so a decisively better article can still lead while a doc page wins a near-tie.
- Per-parent AND per-member-name caps stop one class, or one method name repeated across six classes, filling the answer.
- A miss returns the corpus's section names and the indexed terms nearest the query, because zero results with no guidance is positive evidence to a model that the topic is not covered.
→ "how do I expose a method" → the RPC guide + API pages.
Resolve a page URL to its markdown mirror (…/index.md, or <page>.md) and return
the raw markdown for reading/quoting. A URL that already names a file
(/llms-full.txt, /blog/feed.xml) is fetched as-is. Host-restricted to
imqueue.org and imqueue.com; a commercial page with no mirror returns a pointer
rather than an error. Bodies over 200 kB are truncated, which the result reports.
Catalog of every documented package with one-liners, install commands and, where
two packages cover the same ground, an explicit pick rule — so the agent picks the
right one first instead of choosing on wording. @imqueue/js and @imqueue/travis
are published but deliberately undocumented and are not listed; @imqueue/mcp is
this server, which is not a dependency a service takes, so it is absent here and
present in package_status.
Each entry also carries the package's current version, license, node floor and
release date, read from imqueue.org/status.json at call time. When that feed is
unreachable the catalogue is still returned — it is compiled in — with
factsUnavailable: true, stated rather than silently omitted: an agent handed a list
with no version field and no explanation reads it as "this package has no version".
The current version, licence, engines.node floor and last release date of one
published package or of all of them, plus the framework-wide licence, Node and Redis
requirements. Every published package is covered, including @imqueue/cli and
@imqueue/mcp.
It exists because an agent with this server connected for an entire conversation
still went to a search engine to ask what version and licence @imqueue was — and got
1.x-era answers, because npmjs.com refuses automated fetches. The tool list is what
an agent reads before deciding what to ask, and nothing in it said this server knew.
Unlike list_packages there is no compiled-in fallback: if the feed is unreachable
it says so, because a confidently stated stale version is the exact failure it exists
to prevent.
Emit an IMQService subclass with @expose()d, JSDoc-typed methods (JSDoc is
@imqueue's type source) + a bootstrap that start()s it, and — for any non-primitive
parameter or return type — a types.ts with the @classType()/@property()
declarations that type needs. Omitting methods yields a starter template. Points to
imq service create for a fully provider-wired project.
@imqueue generates the real typed client from a running service, so types
never drift. The tool returns that command plus an illustrative usage snippet (it
does not fabricate a client that could go stale). Every identifier it emits is pinned
by test/scaffold.test.ts against IMQClient's generator and the CLI's writer: the
file is named after the service, the CLI argument is the service's class name
because that is its queue name, and the module's only export is a namespace
holding the client class.
The server runs locally, so when @imqueue/cli is on PATH it can drive the real
imq (see src/cli.ts). Safety posture:
- Every call runs
imqwith stdin closed and a timeout, so an interactive prompt (a missing flag) fails fast with guidance rather than hanging the server. create_servicerunsimq service create … --dry-runby default (writes nothing); a real run requires an explicitapply: true— an agent must never create repos / push to remotes silently.cli_helpsurfaces the exact flags to pass so the run is non-interactive.generate_clientrunsimq client generate(the service must be running).cli_installrunsnpm install -g @imqueue/clito bootstrap the CLI when absent.fleetwrapsimq ctl <start|stop|restart|status>(status is read-only; the others change running processes).configwrapsimq config <check|get|set|init>(get/check read-only; set writes a single value; init is interactive so automation should prefer set).- If
imqis absent, the tools return an install hint and the offlinescaffold_*tools remain available.
All four hints the spec defines are set explicitly on all fourteen tools — never
omitted, never null. test/annotations.test.ts asserts that through a real
tools/list call and pins every value in the table below, so a change of judgement
has to be made deliberately and shows up in a diff as a claim about behaviour.
Two things worth stating because they are easy to read as contradictions:
destructiveandidempotentare not opposites.cli_installreplaces whateverimqwas installed, which is destructive; installing the same version twice lands the same binary, so all the destruction happens in the first call and the second adds none.openWorldandidempotentanswer different questions.get_docmay return different bytes on two calls because the page changed — that is whatopenWorldHint: truesays. It still changes nothing by being called twice.
| Tool | read-only | destructive | idempotent | open-world | Justification |
|---|---|---|---|---|---|
search_docs |
✅ | ❌ | ✅ | ✅ | Fetches public pages from imqueue.org/imqueue.com and ranks them. Reads only; the sites change between calls, so open-world. |
get_doc |
✅ | ❌ | ✅ | ✅ | Fetches one page's markdown. Host-locked to the two @imqueue domains; refuses anything else. |
list_packages |
✅ | ❌ | ✅ | ✅ | The catalogue is compiled in, but each entry's version and licence are read from imqueue.org/status.json at call time — a release between two identical calls changes the answer. Closed-world until 3.6.0, when the facts were added. |
package_status |
✅ | ❌ | ✅ | ✅ | Reads imqueue.org/status.json and nothing else. |
scaffold_service |
✅ | ❌ | ✅ | ❌ | Returns source code as text. Writes no file, creates no project, runs no command — the caller decides whether anything is ever written. create_service is the tool that writes. |
scaffold_client |
✅ | ❌ | ✅ | ❌ | Returns text, including the imq client generate command as a string. It does not run it. generate_client is the tool that does. |
local_install_guide |
✅ | ❌ | ✅ | ❌ | Returns static setup instructions. Installs nothing — which is why it is not called install_locally. |
cli_status |
✅ | ❌ | ✅ | ❌ | Detects the local imq binary and reports its version. |
cli_help |
✅ | ❌ | ✅ | ❌ | Runs imq <command> --help, which prints and exits. |
create_service |
❌ | ❌ | ❌ | ✅ | Dry-run by default; with apply: true it writes a project and may init git, wire CI and push to a remote — hence open-world. Not destructive: it creates rather than damages. Not idempotent: a second apply meets a populated directory and can create a second repository. |
generate_client |
❌ | ❌ | ✅ | ✅ | Introspects a running service over its queue (open-world) and overwrites two files at a fixed path. A repeat leaves the same state, so idempotent. |
cli_install |
❌ | ✅ | ✅ | ✅ | npm install -g @imqueue/cli — downloads from the npm registry and replaces any existing imq, so destructive. Idempotent: see the note above. |
fleet |
❌ | ✅ | ❌ | ❌ | imq ctl over local service repos. status is read-only but stop/restart kill running processes, and a hint describes the tool's worst case. Not idempotent: restart twice restarts twice, dropping in-flight work each time. |
config |
❌ | ✅ | ❌ | ❌ | imq config set overwrites a value and init rewrites the file. Not idempotent because init is interactive and a repeat is not guaranteed to be a no-op. |
logs |
❌ | ✅ | ❌ | ❌ | dump reads; clean deletes the collected logs. Not idempotent: a running fleet writes logs continuously, so a second clean deletes different data — a client must not auto-retry it. |
The hosted surface is the first nine rows minus the two cli_* entries — seven tools,
every one readOnlyHint: true and idempotentHint: true. Nothing that changes state
is registered there at all, because a server on Cloudflare's edge cannot reach the
caller's machine; see §2 and worker/README.md.
On the two scaffold_* names. "Scaffold" means "write files" in most tooling, so
a reader who stops at the name would conclude readOnlyHint: true is wrong. It is
not — they return strings — but appearance is what a reviewer judges, so both
descriptions now open with READ-ONLY: returns … and writes nothing to disk, and
each points at the CLI-backed tool that does the real thing.
Input:
search_docs:{ query: string (1..200), limit?: 1..20, package?: string }get_doc:{ url: string }list_packages:{}package_status:{ package?: string }— with or without the@imqueue/scope; omit for every packagescaffold_service/scaffold_client:{ name|service: string, methods?: Method[] }whereMethod = { name, description?, params?: {name,type,description?,optional?}[], returns? }.
Output: every tool returns { content: [{ type: "text", text }] }, and errors return
the same shape with isError: true (so the agent sees a message, not a transport
failure). The six shared tools additionally declare an outputSchema and return
structuredContent:
| Tool | structuredContent |
|---|---|
search_docs |
{ query, count, results[{title, section, description, url, symbol?}] } |
get_doc |
{ url, markdown, mimeType, bytes, truncated, section?{heading, ancestors[], index, total}, fragmentMiss?{anchor, available[]} } — the body is in both markdown and content, deliberately: the spec frames content as the backwards-compatible mirror of the structured result, so a client that renders structuredContent when present is entitled to ignore content, and a metadata-only schema handed it a citable URL with no page behind it. section and fragmentMiss mirror the header lines for the same reason — they are what stops a slice being mistaken for the whole page |
list_packages |
{ packages[{name, install, summary, version?, license?, node?, released?, deprecated?, pick?}], framework?{license, node, redis, commercial}, factsUnavailable? } — the optional fields are absent together, and only when imqueue.org/status.json could not be read, which factsUnavailable states outright |
package_status |
{ framework{license, licenseNote, commercial, node, redis}, packages[{name, scoped, version, license, node, released, firstRelease, releases, majors[], deprecated, install, docs, npm, repo, summary}], generated, source } |
scaffold_service |
{ service, install, files[{path, language, content}], types[], cliAlternative } |
scaffold_client |
{ service, client, namespace, generateCommand, output, example{language, content} } |
For the scaffolders and the catalogue the markdown is rendered from that structure,
so prose and data cannot disagree. The CLI-bridge tools declare no outputSchema:
they return imq stdout, which has no shape worth promising.
Publish @imqueue/mcp to npm, then list it everywhere agents discover servers:
A channel with no status is a channel nobody can tell is done, so every row carries one. Re-verify before quoting any of them: a listing can be auto-indexed and then go stale, which is how Context7 came to serve a superseded safety model in the project's own voice.
| Channel | Artifact / action | Status (verified 2026-08-03) |
|---|---|---|
| Official MCP registry | server.json (this repo) → publish via mcp-publisher. Namespace org.imqueue/mcp (DNS auth on imqueue.org). |
done, automated in postpublish |
| Smithery | smithery.yaml (this repo) → connect the GitHub repo. |
listed, but advertises CLI tools the hosted server does not serve — convert to a declared remote |
| Glama / LobeHub | Auto-index from npm + GitHub. | indexed, stale — needs a re-scrape |
| Context7 | Submit imqueue.org; context7.json is the rules block it serves to agents. |
indexed at an old revision; refresh needed, and a refresh ping belongs in postpublish |
| mcp.so / PulseMCP / Docker MCP Catalog / mcp-get | Submit the listing. | absent — verified 404/zero-result on all four |
| awesome-mcp-servers (appcypher, wong2) | PR the repo into the list. | absent |
| Cline marketplace | llms-install.md (this repo, current). |
absent; optional, since it already works from a repo URL |
| Cursor / VS Code directories | The GitHub/VS Code gallery pulls from the official registry. | expected to arrive on its own |
| Anthropic Connectors Directory | No auth, seven read-only tools with hints, privacy + terms + docs pages all 200. | technically ready; blocked on a non-technical prerequisite — the portal needs a Team/Enterprise org with directory-management access |
| OpenAI plugin directory (the App directory, renamed July 2026 — one directory for ChatGPT and Codex) | Domain-verification token live at /.well-known/openai-apps-challenge; the listing wires the hosted endpoint. |
done (verified 2026-08-21) — accepted and live at chatgpt.com/plugins/plugin_asdk_app_6a6f945292888191a7d77db4893f8520. It installs the hosted seven, so a Codex user who wants the CLI bridge still needs the local server; both are documented on /mcp/installation/#chatgpt-codex |
| imqueue.org | "MCP server" section on /using-ai-assistants/ with the install snippet. |
done |
Install snippet promoted everywhere:
{ "mcpServers": { "imqueue": { "command": "npx", "args": ["-y", "@imqueue/mcp"] } } }Three layers, because each catches what the others cannot.
npm test runs the unit tests (node:test under tsx, type-checked by
tsconfig.test.json). This is where anything whose correctness does not depend on the
network lives: the ranker on a fixed corpus, the scaffolders' emitted identifiers, URL
resolution, telemetry, and the Worker's HTTP surface — worker.ts is a plain
Web-Standard fetch handler, so it is called with a Request and asserted on the
Response, with no wrangler and no deploy.
npm run smoke spawns the built stdio server and drives the JSON-RPC handshake:
initialize (asserting instructions and a title) → tools/list (the exact local
tool list, a title and all three behaviour hints on every tool, and which tools do and
do not declare an outputSchema) → tools/call for the offline tools, plus live
ranking checks that only a real 1,500-entry corpus exercises.
npm run smoke:remote [url] does the same for the hosted endpoint, where the
contract is stricter: the exact seven-tool list, readOnlyHint: true on all of them,
the search → get_doc chain on structured data alone, and the method handling
(GET/DELETE → 405, HEAD / → 200) that a hang would otherwise reach production
with.
npm run verify runs all of it plus both type-checks.
Streamable HTTP deployment (a hosted endpoint) for zero-install use and for hosts that prefer remote servers.— shipped:mcp.imqueue.org/mcp.Richer search — light stemming to improve recall.— shipped: inflection expansion, segment-aware title matching, summary and package scoring, and apackagefilter. Folding in/blog/search-index.jsontopics is still open.generate_clientfor real — spin up against a reachable running service and return the actual generated client.- Resources — expose docs pages as MCP resources (not just tool results) so
hosts can surface them in their UI. Deferred rather than planned: these are the
attach/@-mention picker, a user-initiated surface, not a citation path, and call
volume does not yet justify it. The wiring is nearly free —
loadIndex()already yields title/description/section/url andgetDocis the read callback. - Prompts — ship an "author an @imqueue service" prompt template. Same reasoning.
- Protocol revision — the current revision is 2026-07-28; the SDK's
LATEST_PROTOCOL_VERSIONis behind that and this server cannot speak it. Do not hand-rollserver/discoveror-32022: the spec's own compatibility matrix says a non-modern 400 body is what makes a dual-era client fall back toinitializeand connect, so emitting-32022would advertise modern support and suppress the recovery path. Track the SDK release that moves the constant, raise the^1.12.0floor then, and assert the negotiated version in a scheduled smoke run.
GPL-3.0, matching the framework; commercial licensing via imqueue.com.