ActivityPub library in OCaml
OCaml 97%
2%
Dune <1%
Shell <1%
Makefile <1%
Standard ML <1%

README.md

ActivityPub OCaml #

A complete ActivityPub implementation in OCaml with effects-based I/O abstraction.

Overview #

This project provides:

  • JSON-LD library (jsonld) - Full JSON-LD 1.1 processing (expansion, compaction, context handling)
  • Activity Streams (activity_streams) - Core Activity Streams 2.0 types
  • Activity Vocabulary (activity_vocab) - Complete Activity Vocabulary types (Object, Actor, Activity, Collection)
  • ActivityPub (activitypub) - ActivityPub protocol implementation with effects
  • ActivityPub EIO (activitypub_eio) - Effect handlers using Eio for async I/O
  • HTTP Signatures (http_signatures) - HTTP Signatures for request authentication

Building #

# Build everything
make build
# or
dune build

# Run unit tests
make test
# or
dune runtest

Components #

Test Server (ap-server) #

A minimal ActivityPub server for testing and conformance validation:

# Start the server (default port 9002)
make server

# Custom port
AP_PORT=3000 make server
# or
AP_PORT=3000 dune exec ap-server

Endpoints:

  • GET /actors/:name - Actor profile (Person)
  • GET /actors/:name/inbox - Actor inbox (OrderedCollection)
  • GET /actors/:name/outbox - Actor outbox (OrderedCollection)
  • POST /actors/:name/outbox - Post activity/object to outbox
  • GET /actors/:name/followers - Followers collection
  • GET /actors/:name/following - Following collection
  • GET /actors/:name/liked - Liked collection
  • GET /actors/:name/objects/:id - Get object by ID
  • GET /actors/:name/outbox/activities/:id - Get activity by ID
  • GET /.well-known/webfinger?resource=acct:name@host - WebFinger discovery

CLI Client (ap-cli) #

Command-line client for interacting with ActivityPub servers:

# Fetch an actor profile
dune exec ap-cli -- fetch https://mastodon.social/@user

# Get an actor's posts
dune exec ap-cli -- posts https://mastodon.social/@user

# Fetch any ActivityPub resource
dune exec ap-cli -- get https://example.com/activity/123

# WebFinger lookup
dune exec ap-cli -- webfinger user@mastodon.social

# Create a Note (posts to outbox)
dune exec ap-cli -- post https://example.com/actors/me "Hello world!"

# Follow an actor
dune exec ap-cli -- follow https://example.com/actors/me https://other.com/actors/them

# Like an object
dune exec ap-cli -- like https://example.com/actors/me https://other.com/posts/123

Conformance Testing #

This implementation passes the activitypub-testing conformance suite.

Running Conformance Tests #

# Install test dependencies (first time only)
npm install

# Run conformance tests (starts server automatically)
make conformance

# With custom port or actor
make conformance PORT=9003 ACTOR=alice

Manual Testing #

# Terminal 1: Start the server
AP_PORT=9002 dune exec ap-server

# Terminal 2: Run conformance tests
npx activitypub-testing test actor http://127.0.0.1:9002/actors/test

Conformance Results #

The test server passes core ActivityPub requirements:

  • Actor endpoints with proper JSON-LD context
  • Inbox/Outbox as OrderedCollections
  • Collection pagination support
  • Outbox POST with Create wrapping
  • Activity/Object ID assignment
  • Update activity handling

Library Usage #

Using the ActivityPub Library #

open Activitypub
open Activity_vocabulary

(* Define effect handlers *)
let run_with_handlers f =
  Activitypub_eio.run f

(* Fetch an actor *)
let fetch_actor url =
  run_with_handlers (fun () ->
    match Discovery.finger url with
    | Ok actor -> Printf.printf "Found: %s\n" (Actor.name actor)
    | Error e -> Printf.printf "Error: %s\n" e
  )

(* Create an activity *)
let create_note actor_url content =
  let note = Object.note ~content () in
  let create = Activity.create ~actor:actor_url ~obj:note () in
  run_with_handlers (fun () ->
    Outbox.post actor_url create
  )

Effects System #

The library uses OCaml 5 effects for I/O abstraction:

(* Effect signatures in Activitypub.Effects *)
type _ Effect.t +=
  | Http_get : string -> (string, string) result Effect.t
  | Http_post : string * string * string -> (string, string) result Effect.t
  | Store_get : string -> Jsont.json option Effect.t
  | Store_put : string * Jsont.json -> unit Effect.t
  | Time_now : float Effect.t
  | Log : string -> unit Effect.t
  (* ... *)

(* Provide your own handlers *)
let my_handler : type a. a Effect.t -> a = function
  | Http_get url -> (* your HTTP implementation *)
  | Http_post (url, content_type, body) -> (* ... *)
  | _ -> Effect.perform (* delegate to default *)

Project Structure #

lib/
  jsonld/           # JSON-LD 1.1 processing
  activity_streams/ # Activity Streams 2.0 core
  activity_vocab/   # Activity Vocabulary types
  activitypub/      # ActivityPub protocol
  activitypub_eio/  # Eio effect handlers
  http_signatures/  # HTTP Signatures

bin/
  ap_server/        # Test server
  ap_cli/           # CLI client

test/
  jsonld/           # JSON-LD tests
  activity_vocab/   # Vocabulary tests
  activitypub/      # Protocol tests

Development #

# Build
make build

# Test
make test

# Format code
make fmt

# Check formatting
make fmt-check

# Clean
make clean

Dependencies #

OCaml libraries (via opam):

  • eio, eio_main - Effects-based async I/O
  • cohttp-eio - HTTP client/server
  • dream - Web framework (test server)
  • jsont - JSON encoding/decoding
  • uri - URI handling
  • alcotest - Testing framework

License #

See LICENSE file.