MCP server for cellxgene and bio-data APIs with Code Mode execution, GraphQL/SPARQL support, and auto-staging of large responses
Server defines 4 tools with detailed descriptions and complex parameter schemas. However, tool naming is inconsistent (3 tools share the same name '{prefix}_execute' with different modes), descriptions are verbose (500+ chars, exceeding best practice), and schema organization lacks clarity. Parameter descriptions are present but dense. No explicit error handling guidance or output schema documentation visible. Schemas use Zod but lack formal JSON Schema export validation in source.
Execute JavaScript code against the API with sandboxed V8 isolate. Code runs with: searchSpec(query) / searchPaths(query) — search the API catalog or OpenAPI spec; listCategories() / listTags() — inspect tags/categories; getEndpoint(path) / getOperation(idOrPath) — get endpoint docs; describeEndpoint(path, method?) / describeOperation(idOrPath) — format endpoint docs; spec — full frozen OpenAPI spec object; api.get(path, params) for GET, api.post(path, body, params) for POST; Path params like /lookup/{id} are auto-interpolated from params; Remaining params become query string; Large responses (>100KB) are auto-staged: check result.__staged, return the staging info, use {prefix}_query_data to explore; Use limit/pagination params to control response size. Large datasets auto-stage for SQL queries.
Execute JavaScript code against the SPARQL endpoint with sandboxed V8 isolate. Code runs with: sparql.query(queryString, opts?) — run SELECT/CONSTRUCT/DESCRIBE; returns array of plain { variable: value } rows; sparql.ask(queryString) — run ASK; returns boolean; sparql.raw(queryString, opts?) — return unparsed { head, results } envelope; prefixes — object of common ontology prefixes (rdfs, owl, xsd, skos, dcterms, void, obo, uberon, go, ncbigene, efo, obi, sio, up, ensembl, bgee); prefixHeader — pre-built `PREFIX …` block to prepend to your queries; console logging (log, warn, error, info) — captured output; fs.readFile / fs.writeFile / fs.glob ... — persistent virtual filesystem (when fsDoNamespace provided). The last expression or return value is the result. STAGING: Large SPARQL responses (>30KB) are auto-staged into SQLite. When this happens, sparql.query returns {__staged: true, data_access_id, schema, tables_created, total_rows, message}. Use api.query(data_access_id, sql) in-band, or return the staging object so the caller can use {prefix}_query_data.
Execute JavaScript code against the GraphQL API with sandboxed V8 isolate. Code runs with: gql.query(queryString, variables?) — GraphQL execution through host; schema.types(), schema.type(), schema.search() etc. — introspection helpers; db.stage(), db.queryStaged(), api.query() — staging helpers; console.log() capture. Optionally also a SECOND, REST upstream (api.get/api.post) routed through the same host proxy + DO-SQLite staging (hybrid GraphQL+REST). API keys never enter the isolate — all HTTP goes through the host's gqlFetch.
Three tools share identical name '{prefix}_execute' with different modes (REST, GraphQL, SPARQL). LLMs cannot distinguish between them; naming violates verb_noun clarity principle. Should be '{prefix}_execute_rest', '{prefix}_execute_graphql', '{prefix}_execute_sparql'.
Tool descriptions exceed 500 characters (baseline 194 chars, p90=392). Verbose descriptions waste tokens and bury key details. '{prefix}_execute (SPARQL)' description is 600+ chars with embedded usage examples that should be in parameter descriptions instead.
No output schema documentation visible. Tools return complex staged data structures (__staged, data_access_id, schema, tables_created) but LLMs have no formal schema to understand response structure. Must document return types for all tools.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | F | 37 | 2026-07-28+ | v2 |
Search the API catalog (catalog mode) or OpenAPI spec (OpenAPI mode). Returns matching endpoints with full parameter docs. Use this to discover API capabilities before calling {prefix}_execute. Categories are listed. USAGE IN {prefix}_execute: api.get(path, params) for GET, api.post(path, body, params) for POST. Path params like /lookup/{id} are auto-interpolated from params. Remaining params become query string. Large responses (>100KB) are auto-staged: check result.__staged, return the staging info, use {prefix}_query_data to explore. Use limit/pagination params to control response size.
No error handling guidance. Tools execute arbitrary JavaScript in sandboxed V8 but provide no documentation of failure modes, recovery steps, or error classification (retryable vs fatal). LLMs cannot self-correct on failures.
Parameter 'code' in execute tools lacks format/length constraints. No guidance on code size limits, timeout behavior, or what happens if code runs >30s. Unbounded code execution invites resource exhaustion.