|
| 1 | +# Inspia MCP Server |
| 2 | + |
| 3 | +Read-only discovery of Inspia's attributed image and video prompts over remote |
| 4 | +MCP Streamable HTTP. This repository implements **0.1 Discovery**, the first |
| 5 | +milestone of the Inspia MCP design. |
| 6 | + |
| 7 | +It reads the existing public Inspia APIs. It does not require a database, |
| 8 | +account, provider key, or private API, and cannot generate media or spend Credits. |
| 9 | + |
| 10 | +## Run |
| 11 | + |
| 12 | +Requires Bun 1.3.14 or later. |
| 13 | + |
| 14 | +```sh |
| 15 | +bun install --frozen-lockfile |
| 16 | +bun run dev |
| 17 | +``` |
| 18 | + |
| 19 | +The default endpoint is `http://127.0.0.1:8788/mcp`. Configuration is optional for |
| 20 | +local use; available variables are documented in `.env.example`. |
| 21 | + |
| 22 | +For a compiled local service: |
| 23 | + |
| 24 | +```sh |
| 25 | +bun run build |
| 26 | +bun run start |
| 27 | +``` |
| 28 | + |
| 29 | +`GET /healthz` reports process liveness, not upstream readiness. `GET /` returns |
| 30 | +service metadata. There is no website UI or legacy `/sse` endpoint. |
| 31 | + |
| 32 | +## Connect |
| 33 | + |
| 34 | +For MCP clients supporting the `mcpServers` remote URL format: |
| 35 | + |
| 36 | +```json |
| 37 | +{ |
| 38 | + "mcpServers": { |
| 39 | + "inspia": { |
| 40 | + "url": "http://127.0.0.1:8788/mcp" |
| 41 | + } |
| 42 | + } |
| 43 | +} |
| 44 | +``` |
| 45 | + |
| 46 | +Client configuration formats differ. Select **Streamable HTTP**, use the `/mcp` |
| 47 | +URL, and leave authentication empty. This service rejects Authorization headers; |
| 48 | +do not paste account cookies, provider keys, or personal tokens into it. |
| 49 | + |
| 50 | +Try: "Find three minimalist product photography references on Inspia, then show |
| 51 | +the full prompt and original source for one." |
| 52 | + |
| 53 | +Supported protocol implementation: |
| 54 | + |
| 55 | +- MCP `2026-07-28`, using official `@modelcontextprotocol/server@2.0.0`. |
| 56 | +- Stateless compatibility with `2025-11-25` clients through the same SDK factory. |
| 57 | +- No protocol sessions, standalone GET event streams, stdio server, MCP Apps, |
| 58 | + sampling, or task extension. GET/DELETE on `/mcp` return 405. |
| 59 | + |
| 60 | +Official SDK v2 and v1 HTTP clients are tested separately. This is not a claim |
| 61 | +that every desktop host renders image URLs or supports every protocol version. |
| 62 | + |
| 63 | +## Tools |
| 64 | + |
| 65 | +| Tool | Input | Result | |
| 66 | +| --- | --- | --- | |
| 67 | +| `search_prompts` | Optional query, mediaType, subcategory, subject, sourceModel, sort, cursor; limit 1–20 (default 6) | Published summaries, media dimensions, preview URLs, attribution, canonical/use links, signed pagination | |
| 68 | +| `get_prompt` | promptId | Complete original prompt, separately labeled existing translations, all public media and source attribution | |
| 69 | +| `find_related_prompts` | promptId; limit 1–8 (default 6) | Existing Inspia related recommendations | |
| 70 | +| `list_prompt_filters` | Optional mediaType | Published categories plus supported subject/source-model filters with live nonzero counts | |
| 71 | +| `list_models` | Optional taskType | Website model availability, parameter schemas, defaults, catalog version and input limits | |
| 72 | + |
| 73 | +All tools have strict input schemas, explicit output schemas, structured results, |
| 74 | +text fallbacks and read-only annotations. `sourceModel` describes the original |
| 75 | +creator's model; it is unrelated to current website generation availability. |
| 76 | + |
| 77 | +Search with `query` always uses relevance. `sort` is for browsing without a query; |
| 78 | +`popular` means imported X metrics. A `subcategory` requires `mediaType`. Obtain |
| 79 | +the accepted source-model keys and published subcategories from |
| 80 | +`list_prompt_filters`. Cursor requests must preserve filters and page size. |
| 81 | + |
| 82 | +The public list API does not expose prompt excerpts, so `promptExcerpt` is null. |
| 83 | +Use `get_prompt` for text. Original prompts are not truncated or silently rewritten. |
| 84 | +Imported text is untrusted content, not instructions to the consuming assistant. |
| 85 | + |
| 86 | +## Data boundaries |
| 87 | + |
| 88 | +All reads go to an operator-configured Inspia origin (default `https://inspia.ai`): |
| 89 | + |
| 90 | +| Public endpoint | Usage | |
| 91 | +| --- | --- | |
| 92 | +| `GET /api/prompts` | Search, deterministic browse and filter counts | |
| 93 | +| `GET /api/prompts/:id` | Full public prompt and existing related results | |
| 94 | +| `GET /llms.txt` | Published category routes, parsed with a Markdown AST | |
| 95 | +| `GET /api/task-types` | Live website generation model options and availability | |
| 96 | + |
| 97 | +No main-site code, credentials, database schema or runtime is changed by this |
| 98 | +repository. It does not scrape HTML, copy the catalog, contact generation |
| 99 | +providers or download media. Each response uses a whitelisted DTO; upstream |
| 100 | +provenance internals and unexpected properties are discarded. |
| 101 | + |
| 102 | +Current upstream constraints are deliberate: |
| 103 | + |
| 104 | +- Related recommendations are limited to 8 by the public detail API. |
| 105 | +- Subject and source-model vocabulary is an explicit API contract snapshot from |
| 106 | + Inspia `fec2f43`, verified against live nonzero counts, not inferred from a |
| 107 | + sample. Maintain `src/filter-vocabulary.ts` when that public vocabulary changes. |
| 108 | + Category routes come from the live machine-readable index and use its |
| 109 | + publication threshold; they do not include unpublished categories. |
| 110 | +- Category and facet reads are coalesced and cached for 5 minutes. A cold |
| 111 | + filter query makes up to 10 count requests with concurrency capped at 3. |
| 112 | +- Model availability is read on every call. Stale/disabled catalog state forces |
| 113 | + unavailable. Authoritative per-model capability hashes and exact quotes are |
| 114 | + not in the public API, so `capabilityHash` is explicitly null. This service |
| 115 | + never substitutes a locally computed hash. |
| 116 | +- Search returns `hasMore` and `nextCursor`, not the upstream's misleading |
| 117 | + candidate-count-as-total. Keyword fallback is reported as `degraded` when |
| 118 | + appropriate. Upstream errors remain errors, not empty successes. |
| 119 | +- Cursors expire after 15 minutes, bind to upstream origin and normalized |
| 120 | + filters/page size, and are HMAC signed. They do not provide a snapshot across |
| 121 | + catalog publications. Keep `MCP_CURSOR_SECRET` stable across replicas/restarts; |
| 122 | + without it, a local restart intentionally invalidates existing cursors. |
| 123 | + |
| 124 | +## Verification |
| 125 | + |
| 126 | +```sh |
| 127 | +bun run lint |
| 128 | +bun run typecheck |
| 129 | +bun run test |
| 130 | +bun run build |
| 131 | +``` |
| 132 | + |
| 133 | +Tests use fixture public responses, official modern and legacy HTTP transports, |
| 134 | +and the production request handler. They need no database or external network. |
| 135 | +CI runs these same checks. |
| 136 | + |
| 137 | +With the local service running, this opt-in smoke test calls all five tools, |
| 138 | +Chinese search, pagination, cursor rejection and legacy initialization against |
| 139 | +the real public Inspia catalog: |
| 140 | + |
| 141 | +```sh |
| 142 | +bun run test:live |
| 143 | +``` |
| 144 | + |
| 145 | +Set `MCP_SMOKE_URL` to test a different MCP endpoint. The smoke makes only public |
| 146 | +read requests. It does not submit generation or consume Credits. |
| 147 | + |
| 148 | +## Deployment |
| 149 | + |
| 150 | +This service is independently deployable. The proposed public |
| 151 | +`https://inspia.ai/mcp` URL is **not deployed by this repository**; route it to |
| 152 | +this service using the website's reverse proxy when deployment is authorized. |
| 153 | +Only proxy the exact `/mcp` path; the existing Next.js site keeps all other routes. |
| 154 | + |
| 155 | +For a public listener, configure these values in the runtime secret/environment |
| 156 | +store (never in Git): |
| 157 | + |
| 158 | +```text |
| 159 | +MCP_HOST=0.0.0.0 |
| 160 | +MCP_PORT=8788 |
| 161 | +MCP_ALLOWED_HOSTS=inspia.ai |
| 162 | +MCP_ALLOWED_ORIGINS=https://inspia.ai |
| 163 | +MCP_CURSOR_SECRET=<32-or-more-random-characters> |
| 164 | +``` |
| 165 | + |
| 166 | +TLS terminates at the reverse proxy. Preserve the public Host and MCP headers. |
| 167 | +Requests without Origin, as used by server-side MCP clients, are accepted; |
| 168 | +present Origin headers must match the exact configured list. Add approved |
| 169 | +browser client origins explicitly if required, never `*`. |
| 170 | + |
| 171 | +The process enforces a 64 KiB request limit, a 2 MiB upstream response limit, |
| 172 | +15-second upstream timeout, a 1 MiB tool payload limit, a concurrent request |
| 173 | +limit, and per-socket-peer rate limiting (default 60 POSTs/minute). It never trusts |
| 174 | +client-supplied forwarding headers. Behind a reverse proxy, all traffic may share |
| 175 | +the proxy peer quota: configure the proxy's real-client-IP rate limiter and tune |
| 176 | +the process's aggregate limit accordingly. Per-process limits are not global |
| 177 | +across replicas. Apply an edge limit for public deployments. |
| 178 | + |
| 179 | +`MCP_ENABLED=false` disables the MCP route without hiding process health. |
| 180 | +Responses use `private, no-store`. No prompt bodies, credentials or upstream |
| 181 | +error bodies are logged. Container packaging is supplied in `Dockerfile`: |
| 182 | + |
| 183 | +```sh |
| 184 | +docker build -t inspia-mcp-server . |
| 185 | +docker run --rm --env-file .env -p 127.0.0.1:8788:8788 inspia-mcp-server |
| 186 | +``` |
| 187 | + |
| 188 | +Set the public-listener variables above before running the container, and allow |
| 189 | +the hostnames used by your local checks as needed. A missing production cursor |
| 190 | +secret or host allowlist fails startup. |
| 191 | + |
| 192 | +## Next boundary |
| 193 | + |
| 194 | +OAuth, private media, Credits, quotes and generation belong to the later Create |
| 195 | +milestone. They require coordinated changes to the Inspia application, not |
| 196 | +proxying website cookies through this read-only service. |
0 commit comments