Your data should belong to you.
Today your photos belong to a photo app, your messages to a messenger, your notes to a note app, your AI conversations to whoever runs the assistant. You are the tenant; the app is the landlord.
A pod inverts that. It is a data space you host, addressed over HTTP, holding structured linked data. Apps and agents come to your data instead of keeping copies of it, and you decide who may read or write what — and can change your mind without losing anything.
This repository provides Kotlin/JVM building blocks and the reference implementation of sempods-spec. It combines HTTP, RDF, SPARQL, OAuth/OIDC and MCP with context-based permissions. The specification defines the protocol; this repository provides the services and clients.
-
Resources are HTTP URIs.
https://example.org/alice/events/summer-partyis both the identifier and the address. Dereference it and you get RDF — JSON-LD by default, other serializations by content negotiation. -
Every statement lives in exactly one context. A context is a named graph, and it is the permission boundary. Not the resource, not the property — the context. One concept carries the whole access-control model.
-
Permissions are grants on contexts:
<context-iri>#read,#write,#manage. A grant is durable server-side policy; it never travels inside a token. Apps acting for a person use Authorization Code + PKCE; services acting as themselves use Client Credentials. The auth overview explains both flows and their client identities. -
Read-only SPARQL, with the sandbox enforced by the server. Queries see only contexts the caller may read. SPARQL Update and
SERVICEare rejected; writes use the HTTP CRUD routes with an explicit context. Client-supplied dataset clauses are not trusted. -
/_system/*is the control plane — contexts, grants, media, retrieval, the OAuth surface. It is not reachable through ordinary RDF writes.
One resource can hold public and private properties in different contexts at the same URI. An anonymous reader sees the public ones — automatic Linked Open Data — an authorized reader sees more. Same identifier, different depth, no duplication.
Contexts are core; their management API is optional. The specification's context-management module adds client-facing creation and deletion. A deployment can provision fixed contexts without that module. This reference implementation provides the management routes; the specification's core and module index defines the boundary.
0.x: APIs can change. Pod hosting, public Linked Open Data, apps using several pods,
and both per-pod and hosted MCP are in use. A separate implementation also uses the specification.
A conformance suite and one-command distribution are not available yet.
The specification's governance
owns protocol requirements, deviations and identifier stability. gradle.properties names the
implemented specification version; checkDocLinks checks it against the vendored requirement index.
Compatibility. 0.x allows API changes; it still requires care with deployed data and
identifiers. The naming contract identifies stable deployed names,
including Mongo database and collection names. The Kotlin package namespace remains org.sempods.*.
Vocabulary terms follow the
specification's deprecation policy.
Changes to stored formats and token contracts need explicit compatibility handling and documentation.
Maven coordinates froze with 0.1.0.
Deployment and upgrades. The supplied composition has no supported upgrade path; a concrete deployment owns its upgrades (deployment responsibilities).
The project is maintained by one person with substantial AI assistance. Independent review of the SPARQL sandbox, grant resolution and OAuth flows is especially welcome.
You need Java 25 and Docker Compose. The commands use the v2 docker compose spelling;
standalone docker-compose or podman-compose can be substituted.
# 1. the only infrastructure a pod server needs
docker compose -f deployments/local/compose.yaml up -d
# 2. local configuration — one active line, which arms the development admin credential
cp deployments/local/env/local.example.env deployments/local/env/local.env
# 3. the pod server → http://localhost:8090
./gradlew :deployments:sempods:image:runCreate a pod using the local host-admin API. Copying local.example.env enables the published
development credential below. A deployment supplies its own SEMPODS_ADMIN_CLIENTS; without
configured authority, admin routes return 503. The owner email is stored as a derived WebID.
curl -X PUT http://localhost:8090/_system/admin/pods/demo \
-H "Authorization: Bearer sc_development-admin-secret" \
-H "Content-Type: application/json" \
-d '{"ownerEmail":"alice@example.org"}'
# → 200 {"pod":"demo","exists":true}
curl http://localhost:8090/_system/admin/pods/demo \
-H "Authorization: Bearer sc_development-admin-secret"
# the pod's OAuth metadata — no authentication needed (RFC 9728)
curl http://localhost:8090/demo/.well-known/oauth-protected-resourceContinue with the auth overview to choose a flow, or the JVM client quick start. The specification's CRUD chapter covers HTTP resource operations.
Configuration is documented where it is used; the variables that matter for a first run are
SEMPODS_HTTP_PORT, SEMPODS_PUBLIC_BASE_URL (the address the server is known by — pod IRIs
are minted from it), and MONGODB_URL. The natural-language layer is disabled by default.
Set AI_PROVIDER=ollama or AI_PROVIDER=openai to enable it; disabled, unset or blank leaves
its routes unregistered. See AI providers for runtime settings.
Start with the client guide for the 0.2 API, module selection and examples. Migration from 0.1 covers the breaking changes.
The libraries are on Maven Central; the current release is 0.2.0. One version covers the whole
repository, so pin the platform and let the modules carry no version of their own:
dependencies {
implementation(platform("org.sempods:sempods-bom:0.2.0"))
implementation("org.sempods:sempods-client")
}sempods-client is the whole client at 0.1.0 and the HTTP core from 0.2 on, with the RDF4J
values and the media routes in artifacts of their own — docs/migration/0.2.md
is what a 0.1.0 consumer reads before raising the platform.
platform(...) supplies version constraints; enforcedPlatform(...) forces those versions even
when another dependency requests a newer one.
Published bytecode targets Java 21, and building this repository needs 25. A module that brings RDF4J needs Java 25 to run, because RDF4J 6 is built for it. The client guide lists the runtime of each client module.
Gradle consumers can use the published testFixtures(...) capabilities. Maven consumers need
the test-fixtures classifier and must supply its test dependencies themselves; these dependencies
are intentionally absent from the ordinary POM.
Releasing explains development snapshots and publication.
The services communicate over HTTP and can run independently. They share libraries for OAuth and MCP behavior.
| Service | What it does | Needed when |
|---|---|---|
pod server (sempods-server) |
The pod itself: CRUD, SPARQL, contexts, grants, media, per-pod MCP | always |
identity (sempods-auth) |
WebID registry and OIDC bridge — gives people an identity a pod can grant to | you want person identities rather than only app credentials |
hosted MCP (sempods-mcp) |
One MCP connection fronting many pods, including pods run by others | you want an AI client to reach several pods at once |
Container images are ghcr.io/haed/sempods, ghcr.io/haed/sempods-auth and
ghcr.io/haed/sempods-mcp. latest moves; clean builds also carry a short commit tag.
Read the source revision of a running container with:
docker inspect --format '{{index .Config.Labels "org.opencontainers.image.revision"}}' <container><sha>-dirty and unknown identify uncommitted or unavailable source state and receive no commit
tag. A commit tag identifies source, not exact bytes: base images can change on rebuild. Pin a digest
for exact content. Images are pushed by hand with each service's jib task, so the label is the
only record of which commit reached the registry.
sempods-commons/ sempods-commons-json/ sempods-commons-mongo/
sempods-commons-okhttp/ sempods-commons-jaxrs/ sempods-commons-ktor/
framework-free shared base; take only what you need
sempods-model/ the contract as code — service interfaces, URI builder, ontologies
sempods-media/ the media contract a pod and its clients share
sempods-server/ the pod server: RDF4J store, contexts, OAuth, SPARQL, AI layer, MCP
sempods-media-s3/ the S3 binding of the media seam
sempods-auth/ identity service (Ktor)
sempods-auth-core/ the OAuth machinery all three services share — framework-free
sempods-mcp/ hosted MCP service (Ktor)
sempods-mcp-core/ the tool catalog and execution both MCP surfaces share
sempods-client/ sempods-client-rdf4j/ sempods-client-media/
HTTP client implementing the contract against a remote pod
sempods-control-plane-client/
HTTP client for the host-level admin surface (pod hosting)
deployments/ the server as a process, and the local stack
docs/ shared concepts and a navigation index; local guides live with their modules
Modularity explains which components a deployment can replace and where the current composition still fixes an implementation.
Choose a starting point:
| You want to… | Read |
|---|---|
| Build an app or backend against a pod | JVM client guide |
| Understand login and permissions | Auth overview |
| Connect a backend without a user at runtime | Service access |
| Let a user approve an app | delegated access |
| Run or embed the identity service | sempods-auth |
| Reuse OAuth/OIDC components in a service | sempods-auth-core |
| Understand the architecture or find other topics | Documentation index |
| Implement the protocol in another stack | sempods-spec |
Module READMEs introduce their libraries or services. Their docs/ directories hold local
details; root docs/ connects subjects spanning modules. Maintained documentation describes
current code. Issues own public plans.
The documentation strategy defines placement and example checks.
Small changes are welcome. Follow the
issue-planning rules for the work record and
completion evidence, including automated updates and private security fixes. Agree larger changes
before implementation.
Contributions run under the Developer Certificate of Origin — git commit -s — and there is
deliberately no CLA: everyone, maintainer included, works under the same licence. See
CONTRIBUTING.md, which also lists the handful of properties that will not
change.
Response times vary. This is not yet anyone's full-time job.
Vulnerability reports go to hello@sempods.org, never into a public issue. See
SECURITY.md for scope, expectations, and the design decisions that look like
vulnerabilities but are not.
Code is licensed Apache 2.0 (LICENSE). Documentation, the specification and
the vocabulary are CC BY 4.0.
The Apache licence grants no rights to the name (§6), so what you may call your own work is set
out separately in TRADEMARKS.md — deliberately permissive: build it, run it
commercially, embed it in a closed product, fork it. The name is regulated only where it would
suggest that this project produced or endorsed something it did not.
Vocabulary terms and their stability guarantees: sempods-spec vocabulary/.
Questions, ideas, or interest in building on this: hello@sempods.org