MCP Server (AI Agent Integration)
AD4M includes a built-in Model Context Protocol (MCP) (opens in a new tab) server, enabling AI agents to interact with AD4M's spanning layer natively — creating perspectives, managing data models, joining neighbourhoods, and participating in distributed collective intelligence through a standardised protocol.
What is MCP? The Model Context Protocol is an open standard that lets AI applications discover and use tools exposed by external services. Any MCP-compatible client (OpenClaw, Claude Desktop, Cursor, etc.) can connect to AD4M's MCP server.
Why MCP?
While AD4M's WebSocket RPC API provides full programmatic access, MCP is purpose-built for AI agents:
- Tool discovery — agents automatically discover available operations and their parameters
- Stable, model-agnostic tools —
describe_perspectivereturns any perspective's SHACL data model as data, and the genericinstance_*tools create, query and update instances of any class by name. The tool list stays constant no matter which social DNA is loaded, so plugin manifests and agent configs can declare it statically - Optional per-class tools — with
dynamicClassToolsenabled, SHACL subject classes additionally generate typed tools at runtime (e.g.,channel_create,message_set_body) - Natural descriptions — every tool includes LLM-optimised descriptions with examples
- Authentication built-in — signup, login, and JWT management through MCP tools themselves
Enabling the MCP Server
Option 1: AD4M Launcher — Open Settings and enable the "MCP Server" toggle. Configure the port (default: 3001). Restart the launcher for changes to take effect.
Option 2: CLI — Pass --enable-mcp true when starting the executor:
export AD4M_ADMIN_CREDENTIAL="$(cat ~/.ad4m/admin-credential)" # required
ad4m-executor run \
--app-data-path ~/.ad4m \
--port 12100 \
--enable-mcp trueThe MCP server starts on port 3001 by default, serving Streamable HTTP at /mcp.
Static vs. dynamic tool surface
By default the MCP server exposes a fixed tool list: the core perspective / neighbourhood / auth tools plus the generic instance tools (describe_perspective, instance_create, instance_query, instance_get, instance_update, instance_add_to_collection, instance_remove). Registering a model or joining a neighbourhood never adds tools — the agent reads the new classes through describe_perspective and passes the class name as an argument.
Per-class tools ({class}_create, {class}_set_{property}, …) are still generated for every SHACL class, but are only exposed over MCP when you opt in:
ad4m-executor run ... --enable-mcp true --dynamic-class-tools true(dynamicClassTools: true in the config file.) With the flag on, both surfaces are available side by side. Calling a per-class tool while the flag is off returns an error naming the instance_* equivalent. The executor's built-in interpretation engine is unaffected by the flag — it always uses the typed per-class definitions in-process.
Connecting an AI Agent
OpenClaw
Add AD4M as an MCP server in your OpenClaw configuration:
{
"mcpServers": {
"ad4m": {
"url": "http://localhost:3001/mcp"
}
}
}Once connected, your agent can call any AD4M tool directly. An OpenClaw skill (opens in a new tab) is also available with setup guides and reference material.
Other MCP Clients
Any MCP client supporting Streamable HTTP transport can connect to http://localhost:3001/mcp. Refer to your client's documentation for configuration.
Authentication
AD4M's MCP server supports two authentication modes:
Single-User Mode (Local Development)
When running a personal executor, use the admin credential:
# The same credential the executor was started with (AD4M_ADMIN_CREDENTIAL
# or --admin-credential). Send it as an Authorization header on every call:
# Authorization: Bearer <credential>For local development, this is the simplest approach — no signup/login needed.
The MCP session itself still starts with no token, and auth_status() only
reports on the session token — so it answers authenticated: false until the
first protected call arrives with a matching header. That is not a sign the
credential is wrong.
Multi-User Mode
For shared executors serving multiple users, AD4M provides a full auth flow through MCP tools:
signup— Create an account with email and passwordlogin_email— Authenticate and receive a JWTverify_email_code— Verify email (if SMTP is configured)auth_status— Check current authentication state
→ signup(email: "[email protected]", password: "secure-pass")
← { did: "did:key:z6Mk...", success: true }
→ login_email(email: "[email protected]", password: "secure-pass")
← { token: "eyJ...", success: true }After login, the JWT is stored in the MCP session and used automatically for all subsequent tool calls. Each user gets their own DID and isolated perspective space.
Unauthenticated connections can only access the bootstrap tools: get_documentation, auth_status, signup, login_email, request_login_verification, verify_email_code, request_capability and generate_jwt. Everything else requires a valid JWT or the admin credential.
Available Tools
Tools are organised by domain. Per-class dynamic tools (generated from SHACL subject classes) appear alongside these core tools only when dynamicClassTools is enabled.
Perspective & Link Tools
| Tool | Description |
|---|---|
add_perspective | Create a new perspective (personal knowledge graph) |
list_perspectives | List all perspectives with metadata |
add_model | Register a subject class in a perspective from SHACL JSON |
add_link | Add an RDF-like triple to a perspective |
query_links | Query links by source, predicate, or target |
get_documentation | The executor's own overview / architecture docs as markdown (no auth needed) |
Raw links are the escape hatch, not the normal path — there is no remove-link tool on the MCP surface. Use the instance tools below for anything a subject class models.
Instance Tools (static, model-agnostic)
These tools are the default way to work with typed data. The subject class is an argument, and the schema an agent needs comes back from describe_perspective as data:
| Tool | Signature (essentials) | Description |
|---|---|---|
describe_perspective | (perspective_id) | Every registered class with its properties (name, type, required, cardinality, interpretation hints), collections, and flows |
instance_create | (perspective_id, class_name, properties{}, base_uri?, parent?) | Create an instance; properties is a JSON object, validated against the class |
instance_query | (perspective_id, class_name, filter?, parent?, limit?, offset?) | List instances with typed property values; filter is a where clause ({"status": "open"}, {"title": {"contains": "mcp"}}, OR / AND / NOT) |
instance_get | (perspective_id, class_name, base_uri) | One instance with all properties and collections |
instance_update | (perspective_id, class_name, base_uri, properties{}) | Set single-valued properties |
instance_add_to_collection | (perspective_id, class_name, base_uri, collection, item_uri) | Add an item to a collection property |
instance_remove_from_collection | (perspective_id, class_name, base_uri, collection, item_uri) | Remove the membership link only; the item itself is untouched |
instance_remove | (perspective_id, class_name, base_uri) | Delete an instance and every link touching it |
instance_transcript | (perspective_id, class_name, parent, limit?, text_property?) | The newest N instances of one class under a parent, as a readable chronological transcript — the way to read a channel |
add_child / get_children | (perspective_id, parent, child) / (perspective_id, parent, limit?) | The raw ad4m://has_child tree, class-agnostic: neither node has to be a subject-class instance |
execute_commands | (perspective_id, expression_address, commands, parameters?) | Escape hatch: raw SDNA {action, source, predicate, target} commands on one instance. Not SHACL-validated |
Every write through the typed tools is validated against the class's SHACL shape before anything is stored (execute_commands is the documented exception). A rejected call returns all problems at once, each naming the property, the expected type and the cardinality:
{
"error": "Validation failed for class 'Channel' (2 problems): name: missing required property (expected string, exactly one value (minCount 1, maxCount 1)); isPinned: expects true or false (expected boolean, at most one value (maxCount 1)), received string \"maybe\"",
"class_name": "Channel",
"validation_errors": [
{ "property": "name", "problem": "missing required property", "expected_type": "string", "cardinality": "exactly one value (minCount 1, maxCount 1)" },
{ "property": "isPinned", "problem": "expects true or false", "expected_type": "boolean", "cardinality": "at most one value (maxCount 1)", "received": "string \"maybe\"" }
],
"hint": "Call describe_perspective for the full schema of this class."
}Dynamic SHACL Tools (opt-in)
With --dynamic-class-tools true, AD4M's MCP server additionally introspects SHACL subject class definitions in perspectives and generates typed tools at runtime. This means:
- Install or define SDNA (SHACL schemas) in a perspective
- MCP server discovers the classes and their properties/collections
- Tools appear automatically — no manual registration needed
- AI agents can call
describe_perspectiveto discover the classes behind the tools
This is useful for a fixed, known DNA where per-property tool parameters give smaller models better guidance. The trade-off is a tool list that grows with every class (roughly five tools per class plus one per property) and changes at runtime, which static integrations cannot follow.
Tool generation rules:
- Per class →
{class}_create,{class}_query,{class}_list,{class}_get,{class}_delete - Scalar properties (
sh:maxCount 1) →{class}_set_{property}— a setter only; there is no per-scalar getter (read them with{class}_get) - Collections (
collection: true, or nomax_count) →{class}_get_{collection},{class}_add_{collection},{class}_remove_{collection} {class}_createtakes every non-collection property as a parameter, required or not;min_countonly decides whether the description marks it with*, andperspective_idis the only entry in the schema'srequiredlist- Every instance-scoped tool identifies the instance by an
expression_addressparameter - SHACL Flow actions do not generate tools — flows are read and driven through the static
add_flow/get_flows/flow_state/flow_actionstools
Example: A Channel SDNA added via add_model(class_name: "Channel", shacl_json: ...):
{
"target_class": "app://Channel",
"properties": [
{
"path": "app://has_name",
"name": "name",
"datatype": "xsd:string",
"min_count": 1,
"max_count": 1,
"writable": true,
"resolve_language": "literal"
},
{
"path": "app://has_description",
"name": "description",
"datatype": "xsd:string",
"max_count": 1,
"writable": true,
"resolve_language": "literal"
},
{
"path": "app://has_member",
"name": "members",
"node_kind": "sh:IRI"
},
{
"path": "app://has_message",
"name": "messages",
"node_kind": "sh:IRI"
}
],
"constructor": [
{ "action": "addLink", "source": "this", "predicate": "rdf://type", "target": "app://Channel" },
{ "action": "setSingleTarget", "source": "this", "predicate": "app://has_name", "target": "name" }
]
}Generates these MCP tools:
channel_create(perspective_id, expression_address?, parent?, name, description)—nameis marked*in the description; onlyperspective_idis schema-required, andexpression_addressis generated when omittedchannel_set_name(perspective_id, expression_address, value)— update namechannel_set_description(perspective_id, expression_address, value)— update descriptionchannel_get_members(perspective_id, expression_address)— list memberschannel_add_members(perspective_id, expression_address, value)— add an Agent to memberschannel_remove_members(perspective_id, expression_address, value)— remove an Agentchannel_get_messages/channel_add_messages/channel_remove_messages— same three for messageschannel_get(perspective_id, expression_address)— retrieve full Channel datachannel_query/channel_list/channel_delete
All tools include LLM-optimized descriptions with examples, data types, and constraints.
Neighbourhood Tools
| Tool | Description |
|---|---|
neighbourhood_join_from_url | Join a neighbourhood by URL |
list_link_language_templates | List the P2P sync engines you can publish with |
neighbourhood_publish_from_perspective | Publish a perspective as a shared neighbourhood (pass a template address as link_language) |
Agent & Profile Tools
| Tool | Description |
|---|---|
get_my_did | The current agent's DID |
get_agent_profile / set_agent_profile | Read and write your profile (name, bio, …) |
set_agent_profile_picture | Set your profile picture |
get_agent_public_perspective | Read any agent's public profile |
set_agent_public_perspective | Update your public profile links |
Unlocking the keystore is not an MCP tool — it is the node operator's job,
over the CLI or WS-RPC (agent.unlock). Until it happens, the JWT paths fail
with a "key not found" error.
Waker / Subscription Tools
| Tool | Description |
|---|---|
generate_waker_query | Build the SPARQL subscription that wakes you when a class changes under a parent |
get_mention_waker_config | The subscription config that wakes you on mentions of your DID |
The subscription itself is registered by your client (e.g. the OpenClaw plugin's waker service), not by the executor.
Example Workflow
Here's a typical AI agent workflow — discover models, create data, query it back:
# 1. List perspectives
→ list_perspectives()
← [{ uuid: "ab23...", name: "My Space", neighbourhood: null }]
# 2. Discover the data model (schema as data)
→ describe_perspective(perspective_id: "ab23...")
← { classes: [{ name: "Task",
properties: [{ name: "title", type: "string", required: true, cardinality: { min: 1, max: 1 } },
{ name: "status", type: "string", required: false, ... },
{ name: "assignee", type: "string", ... }],
collections: [{ name: "tags", type: "string", cardinality: { min: 0, max: null } }] }],
flows: [] }
# 3. Create a task
→ instance_create(perspective_id: "ab23...", class_name: "Task",
properties: { title: "Review MCP docs", status: "open",
assignee: "did:key:z6Mk..." })
← { created: true, class_name: "Task", base_uri: "ad4m://obj/k3f9qzhtvbnmlxwerdcv" }
# 4. Add a tag
→ instance_add_to_collection(perspective_id: "ab23...", class_name: "Task",
base_uri: "ad4m://obj/k3f9qzhtvbnmlxwerdcv",
collection: "tags", item_uri: "documentation")
← { success: true }
# 5. Query all open tasks
→ instance_query(perspective_id: "ab23...", class_name: "Task",
filter: { status: "open" })
← { count: 1, total_count: 1,
instances: [{ id: "ad4m://obj/k3f9qzhtvbnmlxwerdcv", title: "Review MCP docs",
status: "open", tags: ["documentation"] }] }Joining a Neighbourhood
AI agents can join shared spaces and participate alongside humans. Neighbourhoods are shared perspectives synced P2P via Holochain — they form the collaborative layer of AD4M's spanning architecture.
# Join an existing neighbourhood
→ neighbourhood_join_from_url(url: "neighbourhood://Qm...")
← { success: true, perspective_uuid: "cd45..." }
# Discover what models (SHACL subject classes) exist in this community
→ describe_perspective(perspective_id: "cd45...")
← { classes: [{ name: "Channel", collections: [{ name: "messages", ... }], ... },
{ name: "Message", properties: [{ name: "body", type: "string", required: true, ... }], ... }] }
# Send a message into a channel — same tools, whatever the DNA
→ instance_create(perspective_id: "cd45...", class_name: "Message",
properties: { body: "Hello from an AI agent! 🖖" },
parent: "<channel uri from instance_query>")
← { created: true, base_uri: "ad4m://obj/8ac2mfrxlpwkdgtvzyqn", added_to_parent: true }What just happened?
- Your agent joined a Holochain-backed shared space
- The neighbourhood SDNA defined the data schema (Channel and Message classes)
describe_perspectivehanded that schema to the agent as data — no new tools needed- Your message was validated against the schema, signed with your DID, and synced to all members
- Other agents (human and AI) see your message in real-time
This is the spanning layer in action — your agent participates in a P2P network using SHACL-structured data, all through simple MCP tool calls.
Understanding the Spanning Layer Through MCP
When you use AD4M's MCP tools, you're interacting with multiple layers:
1. Agent Layer (Identity)
get_my_did()→ your DID (e.g.,did:key:z6Mk...)- Every action is signed with your keys
- Cryptographic provenance on all data
2. Perspective Layer (Knowledge Graph)
add_perspective(),add_link(),query_links()- Your subjective view of data — links between expressions
- Mix data from any protocol/language in one graph
3. Language Layer (Protocol Abstraction)
- Expressions have addresses like
<language_hash>://<address> - Languages wrap existing systems (HTTP, IPFS, Holochain)
- Global addressing scheme — reference any data from anywhere
4. Subject Class Layer (Structured Data)
- SHACL SDNA defines models (Channel, Message, Task, etc.)
describe_perspectiveexposes those schemas as data;instance_*tools work with any of them- Validation rules enforced across the network
5. Neighbourhood Layer (Collaboration)
- Shared perspectives synced P2P
- SDNA enforced by all members
- Real-time synchronization via Holochain
The key insight: You don't need to understand Holochain, SHACL, or RDF to use AD4M. The MCP tools provide a clean abstraction that hides complexity while preserving the power of the spanning layer architecture.
Further Reading
- Model Classes — defining SHACL subject classes
- Social DNA — understanding SDNA and SHACL schemas
- Neighbourhoods — how shared perspectives work
- Authentication — AD4M's auth system in depth
- AI in AD4M — local AI model inference (complementary to MCP)