Contentful

Lightweight, dependency-free, in-memory fake of the Contentful Content Delivery API (CDA) and Content Management API (CMA) for testing code that uses the real contentful / contentful-management SDKs.

Default port: 4841

Quick start

import { ContentfulServer } from "./services/contentful/src/server.js";

const server = new ContentfulServer(4841);
await server.start();
// ... run your app/tests ...
await server.stop();

Point the real client at it via host: "127.0.0.1:4841" (and insecure: true), or drive the REST API directly:

await fetch("http://127.0.0.1:4841/spaces/parlel/environments/master/entries", {
  headers: { Authorization: "Bearer parlel" },
});

Access via MCP / preview URL

When run inside a parlel pool, reachable at its mapped preview URL (e.g. http://127.0.0.1:4841). MCP clients drive entries and content types using CONTENTFUL_ACCESS_TOKEN / CONTENTFUL_MANAGEMENT_TOKEN, CONTENTFUL_SPACE_ID, and CONTENTFUL_ENVIRONMENT. Any non-empty Bearer token is accepted.

Implemented operations

State is in-memory and ephemeral. All routes require Authorization: Bearer <token>. Routes live under /spaces/:spaceId/environments/:env/... (the /spaces/:spaceId/... shorthand is also accepted for entries/content_types).

Entries

Content types

Service & inspection (parlel extensions)

Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

FeatureStatus
Entry list (with content_type/skip/limit) / get / create / update / delete✅ Supported
Content type list / get (seeded blogPost)✅ Supported
sys/fields envelopes + Array collections✅ Supported
Bearer token validity / scopes✓ By design — Any non-empty credential is accepted — no real secrets needed
Rich GROQ-like query operators, include link resolution, ordering⟳ Roadmap
Assets, locales, tags, publishing workflow (/published)⟳ Roadmap
Sync API, GraphQL endpoint⟳ Roadmap
Real persistence✓ By design — In-memory by design — fast, isolated, resets cleanly between tests

Error shapes

Contentful uses { sys:{type:"Error", id}, message, requestId }:

StatusWhen
401missing/invalid Bearer (AccessTokenInvalid)
404unknown entry/content type (NotFound)

Manifest

See services/contentful/manifest.json — name contentful, port 4841, protocol http, healthcheck /health, env CONTENTFUL_ACCESS_TOKEN, CONTENTFUL_MANAGEMENT_TOKEN, CONTENTFUL_SPACE_ID, CONTENTFUL_ENVIRONMENT, CONTENTFUL_BASE_URL.

<!-- parlel:testenv:start -->

Configuration — test.env

Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.

CONTENTFUL_ACCESS_TOKEN=parlel
CONTENTFUL_MANAGEMENT_TOKEN=parlel
CONTENTFUL_SPACE_ID=parlel
CONTENTFUL_ENVIRONMENT=master
CONTENTFUL_BASE_URL=http://parlel-bridge:4841
<!-- parlel:testenv:end -->