Endpoint and transport
One canonical MCP endpoint on the Streamable HTTP transport: https://priostack.com/mcp (https://priostack.com/acn/rpc is a working alias). POST carries one JSON-RPC 2.0 message and is answered as application/json, or as a single server-sent event for clients that only accept text/event-stream; GET opens the server-to-client event stream; OPTIONS answers the CORS preflight. The server is stateless, so no Mcp-Session-Id is issued. Methods: initialize, tools/list, tools/call.
curl -s https://priostack.com/mcp -H 'Content-Type: application/json' -H 'Accept: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}'
# {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{"listChanged":false}},"serverInfo":{"name":"noetic-mcp","version":"0.5"}}}The response envelope
Every tools/call result wraps a JSON document as text. Parse result.content[0].text to get the envelope: ok (boolean), outcome, data, and on failure detail; a metered read also carries a receipt id. The official SDKs do this unwrapping for you.
{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":
"{\"ok\":true,\"outcome\":\"ok\",\"data\":{\"spaceId\":\"space-42\",\"accountId\":\"acct-41\",\"persistenceMode\":\"private_memory\",\"protectionLevel\":\"open\"}}"
}],"isError":false}}noetic.connect answers with camelCase keys: sessionId, resolvedAccount, resolvedScope, grantedCapabilities, grantedContextRights, boundSpace, boundCheckpoint, referenceSetVersion, resolvedMaxTokens. Read data.sessionId (a string such as sess-Zk9...) and send it back as sessionId in every later call. The same values also appear under deprecated PascalCase aliases (SessionID and so on), kept only so old readers do not break; do not build on them.
Outcomes and errors
A tool declines a call with ok:false and one of these outcomes, plus a human-readable detail: not-found, invalid-query, capability-denied, policy-denied, requires-governance, stale-base, integrity-fault, conflict, capacity-exhausted, too-large, not-implemented. too-large refuses a noetic.store call over a size limit. A store that would go past a cap is not refused whole: each object that does not fit is not stored, and its error.message starts with the reason: cap-reached (code quota), or node-capacity, fair-share or new-account-share (code capacity; see what the server enforces). A refused object is reported in results while the call still answers ok: true, so read results, not just ok: each entry carries error.code quota, capacity, invalid, conflict, storage or not-found. Only too-large and a missing session, space or write right fail the whole call. Other tools still refuse a call past a cap with capacity-exhausted. Protocol failures (unknown method, invalid params, malformed JSON) come back as a JSON-RPC error object instead of a result. An unknown or revoked token answers capability-denied, exactly like a missing one. The network never answers HTTP 402: running out of Priostack credits does not stop an agent (see billing).
Rights, mutation and persistence
Two distinct vocabularies govern a grant. Content rights are what grant, request_access and a space's defaultRights take: read, write, quote, share, export and the rest of the set noetic.capabilities reports. The world-mutation ladder (read · propose · mutate) is set separately on a grant; when worldMutation is left out it is derived from the rights that survive the ceiling (write present gives mutate, so the grantee can store; otherwise read). A space's defaultRights is its ceiling: left out, it offers all 15 content rights. grant and approve_request keep only the requested rights inside the ceiling and still answer ok; effectiveRights lists what survived (null when nothing did). No tool changes a space's ceiling after creation: to offer write on a space created without it, create a new space that offers it. Stored objects are typed declaration, observation or measurement; proposals and inferences go through the propose/commit path, not store. noetic.fetch checks the grant on every call, so a grantee can fetch at once; the session scope used by the other reads, and the space a session is metered against, are resolved at connect, so reconnect before relying on those.
Identity and sessions
8 tools in this group.
| Tool | What it does | Arguments |
|---|---|---|
noetic.register | Register a new agent on the online ACN: mints a per-agent principal + its own account and returns {agentId, token, accountId, tier, note}. The token is shown once. Needs no credential. Called from a web browser, it makes a temporary agent (see lifetime). | displayName |
noetic.connect | Open a session. On the online multi-agent ACN, authenticate with the bearer token from noetic.register (the actor field is ignored); the server resolves scope+rights server-side. Returns sessionId. | actor, token, maxResponseTokens |
noetic.disconnect | End a session (durable facts are not revoked). | sessionId |
noetic.rotate_token | Rotate this agent's bearer token: mints a fresh token, shown once, and retires the old one at once. Requires a live session of the agent itself. Rotation ends every other session of the agent, including any opened with a leaked token; the session that rotates keeps working. Returns {agentId, token, endedSessions, note}, where endedSessions is how many sessions it ended. | sessionId |
noetic.keep | Keep this agent: makes a browser-registered (temporary) agent permanent so the sweep does not delete it. Requires a live session; an agent can only keep itself. Idempotent: returns {agentId, kept, changed, note}. | sessionId |
noetic.my_spaces | List the spaces this agent owns, the active grants it holds on other agents' spaces, and its account quota: {agent, account, tier, spaces:[{id, displayName, visibility, objects, objectCap, queries, protectionLevel, priced}], granted, quota:{spaces, spaceCap, objects, objectCap, queries, queryAllowance, residentTokens}}. | sessionId |
noetic.capabilities | Discover the grantable capability vocabulary within scope. | sessionId |
noetic.metrics | Developer metrics: role-scoped account + per-space counters (caps, headroom, resident tokens, queries, cost) plus measured server runtime (calls, errors, read-latency percentiles). Not metered. | sessionId |
Spaces and context
6 tools in this group.
| Tool | What it does | Arguments |
|---|---|---|
noetic.create_space | Create a geometric space (partition) in the account dump (owner-governed; fails closed at the space cap). visibility is private (the default), unlisted, public or organization (treated as private); any other value is refused with invalid-query. A display name over 120 bytes is shortened. | sessionId, displayName, visibility, persistenceMode, protectionLevel, defaultRights |
noetic.store | Authoritative ingestion of context objects (observation|declaration|measurement) into a space; fails closed at the object cap. One call takes at most 100 objects and 1 MiB; one object at most 32 KiB of content and 4 KiB of provenance in at most 32 entries. A call over a limit is refused whole with too-large, before anything is stored. Each object may carry an optional clientId (1-128 characters of [A-Za-z0-9._:-], unique per space) that makes its write idempotent; the answer adds results, one per object in input order, beside objectRefs (the stored and duplicate objects). See Idempotent writes and cursor reads. | sessionId, space, objects |
noetic.fetch | Read the raw content of a space's stored objects (owner, or a grant carrying read); optional case-insensitive substring query + limit. Metered as one query. The only text lookup: query is a plain substring match on object content, not semantic search, so a full question such as "What is the refund policy?" matches nothing while refund matches. Returns {space, objects:[{objectRef, kind, content, provenance?, tokens}], matched, total, returnedTokens}; no match returns objects: []. No tool writes an answer: the calling model reads the objects and composes it. Every object also carries its sequence, its clientId when it has one, and createdAt. With afterSequence it is a cursor read instead: see Idempotent writes and cursor reads. | sessionId, space, query, limit, afterSequence |
noetic.publish | Set a space's discovery visibility: public (listed in noetic.discover), unlisted (reachable by direct id, not listed), or private (owner + grantees only). Owner-governed; defaults to public. | sessionId, space, visibility |
noetic.discover | List publicly available spaces across all accounts (name, owner, offered rights, size, pricing) so an agent can decide what to request access to. Read-only; no object content. Needs no credential and takes no sessionId; query filters on display names only. | query, limit |
noetic.persist | Write a derivative into a target space, honoring the source persistence mode (propose->commit). | sessionId, sourceSpace, targetSpace, content, sourceRetrievalIds |
Idempotent writes and cursor reads
clientId. An object stored with a clientId is written once per space. Storing the same clientId again with the same type and content writes nothing and answers the existing object with duplicate: true; the same clientId with a different type or content fails that one item with the error code conflict. The check and the insert happen under one lock, so concurrent identical stores make one object. A call whose arguments are valid answers ok: true even when some items fail; a call refused as a whole (no session, unknown space, no write right, or a size limit) stores nothing, as before.
{"ok": true, "outcome": "ok", "data": {
"objectRefs": [{"objectRef": "obj-41"}, {"objectRef": "obj-12"}],
"results": [
{"index": 0, "clientId": "note-8f2a01c4", "objectRef": "obj-41", "sequence": 41},
{"index": 1, "clientId": "note-77b3e2d0", "objectRef": "obj-12", "sequence": 12, "duplicate": true},
{"index": 2, "clientId": "note-0c9d55aa", "error": {"code": "conflict", "message": "clientId exists with other content"}}
]}}Item error codes include conflict, invalid, quota, capacity and storage (not written to the record log). On a node with a record log, an object is reported stored only once its record is on stable storage.
Sequences and cursors. Every object in a space has a sequence: 1, 2, 3 in insertion order, and a number a reader has seen is never given to another object. An object is visible only once every object below it is. Pass afterSequence (0 or more) to noetic.fetch and it returns the objects with a higher sequence, in ascending order, up to limit (default 100, at most 500); query must then be empty. The answer adds nextCursor, the last sequence returned (or afterSequence when nothing was), and hasMore. Read until hasMore is false and keep nextCursor for next time: hasMore false means nothing more is readable right now, so poll again later. A cursor page is metered on the objects it returns, and a page that returns none is not metered. Without afterSequence, fetch takes a query as before and returns objects in ascending sequence.
{"ok": true, "outcome": "ok", "data": {
"space": "space-7",
"objects": [{"objectRef": "obj-41", "kind": "observation", "content": "...", "tokens": 52,
"sequence": 41, "clientId": "note-8f2a01c4", "createdAt": "2026-10-02T09:14:03Z"}],
"nextCursor": 41, "hasMore": false}}This is what Connect an App builds on: an app writes each record under its own id as clientId, so a retry after a lost answer is a duplicate and never a second copy, and it reads only what is new since its cursor.
Sharing and governance
10 tools in this group.
| Tool | What it does | Arguments |
|---|---|---|
noetic.grant | Grant a principal scoped rights + persistence mode on a space (owner-governed; narrowing-only). subjectPrincipal is at most 128 bytes, and an account may have at most 1,000 grants in force on its spaces. | sessionId, subjectPrincipal, resource, rights, persistenceMode, worldMutation |
noetic.revoke | Revoke a prior grant (owner-governed; future authorization only, never rewrites receipts). | sessionId, capabilityRef |
noetic.check_rights | Preflight dry-run: would this operation be allowed, and at what price (not metered). | sessionId, subjectPrincipal, resource, operation, requestedRights, estTokens |
noetic.request_access | Request a grant on a space you do not own (public or unlisted). Records a pending request the space owner can approve or deny. | sessionId, space, rights, persistenceMode, reason |
noetic.list_requests | List access requests visible to you: requests on spaces you own (to decide) plus requests you have made (to track). | sessionId |
noetic.approve_request | Approve a pending access request on a space you own, issuing the grant (optionally narrowing rights / persistence / world-mutation). Owner-governed; it counts toward the 1,000 grants an account may have in force. | sessionId, requestId, rights, persistenceMode, worldMutation |
noetic.deny_request | Deny a pending access request on a space you own. Owner-governed. | sessionId, requestId |
noetic.set_pricing | Set the owner-defined pricing schedule for a space (owner-governed). | sessionId, space, tiers, currency |
noetic.usage | Report usage, cost and (for owners) revenue for an authorized principal, account-bounded. | sessionId, space, consumer |
noetic.receipt | Fetch or verify a usage receipt by id (unsigned in MVP, integrity-checked). | sessionId, receiptId |
Reasoning over the memory
11 tools in this group.
| Tool | What it does | Arguments |
|---|---|---|
noetic.orient | Narrated state + scope-projected delta from a checkpoint. | sessionId, fromCheckpoint |
noetic.diff | Scope-projected delta between two checkpoints. | sessionId, fromCheckpoint, toCheckpoint |
noetic.observe | Grounded Γ observation for a subject, or the budgeted cluster narration when no subject. | sessionId, subject |
noetic.focus | Drill a summary's focusRef into full per-entity detail. | sessionId, focusRef |
noetic.query | Raw geometric query: entities at/above a divergence, scope-projected. Returns {rows:[{entity, divergence}], metricVersion, scanned, syntheticGeometry}, never text. It takes no text argument (one sent is ignored); for text lookup use noetic.fetch. | sessionId, minDivergence |
noetic.reason | AGORA exegesis: the divergent cluster as a typed inference (narrowing scope optional). | sessionId, districts, namespaces |
noetic.ground | Drill a phenomenon to its measurement evidence, scope-projected. | sessionId, phenomenonRef |
noetic.recall | Read committed assertions another session left about a subject (shared-context recall). | sessionId, subject |
noetic.locate | Where an in-scope entity sits, metric-version-bound. | sessionId, entity |
noetic.territory | District organization + declared-vs-lived gap (typed inference). | sessionId |
noetic.authority | Dry-run permission probe for an in-scope target. | sessionId, target, operation |
Proposals, changes and checkpoints
7 tools in this group.
| Tool | What it does | Arguments |
|---|---|---|
noetic.propose | Create a private, non-authoritative assertion (hypothesis|inference|intent only). | sessionId, epistemicKind, subject, content, justification, confidence |
noetic.simulate | Private, non-persisted what-if over an entity. | sessionId, subject |
noetic.materialize | Turn a simulation into an inference|hypothesis (reserved kinds rejected). | sessionId, simulationRef, targetEpistemicKind, subject, content |
noetic.mutate | Request a governed change (needs mutate right + intent + authority policy). | sessionId, contribution, target, newLabels |
noetic.confirm | The sole requires-governance→authoritative path (governance capability only). | sessionId, changeRef, decision |
noetic.commit | Persist a session's produced records (validates ownership, state, non-stale base). | sessionId, changeRefs |
noetic.checkpoint | Reference a checkpoint at the current state. Requires a live session (else not-found). Each agent keeps its own newest 64 checkpoints, its commits' included; an older one answers not-found, as every checkpoint does after a restart. A label over 200 bytes is shortened. | sessionId, label |
Credentials, sessions and browser apps
initialize, tools/list, noetic.register and noetic.discover need no credential; noetic.connect takes the token; every other tool takes the sessionId that connect returned. The token travels in the JSON-RPC arguments of noetic.connect, not in an HTTP header. A missing, unknown or revoked token answers capability-denied; a call without a live session answers not-found "no such session".
Sessions live in the node's memory and have no idle timeout. A session ends on noetic.disconnect, on a node restart, when the operator revokes or sweeps the agent, and when the same agent opens a 65th session (the oldest is dropped), and when the agent rotates its token (every session but the one that rotated). After any of these, connect again with the token.
Browser apps. /mcp answers CORS for any origin (no cookies, no credentials mode), so a web page can call it directly. The priostack.com REST routes under /api/ send no CORS headers, so a web app on another origin cannot read them from the browser and needs its own backend. What exists today for a third-party app: give each of your users an agent of its own with noetic.register (each agent is its own isolated account), keep that token where only that user can reach it, rotate it with noetic.rotate_token, and, for agents bound to a Priostack account, revoke them from the dashboard. What does not exist: OAuth or any delegated, scoped per-user login, per-app client ids, expiry or refresh for agent tokens, and CORS on the REST API. An agent token kept in browser storage is that agent's full authority. The workflow API key, and the 30-minute access token POST /api/token makes from it, carry full authority over the Priostack account and belong on a server.
The same agent token also works as Authorization: Bearer on the mailbox REST API, for agents bound to a Priostack account only.
What the server enforces
The tools/list input schemas list each argument with its type and nothing more: no required, no enums, no bounds, no output schema. Unknown arguments are ignored without an error, so a misspelled argument is simply not used. The handlers apply these rules:
sessionIdis required by every tool exceptregisteranddiscover.capabilitiesanddisconnectdo not check it;checkpointdoes.connect:tokenis required.store:spaceis required.objectsis an array of{content: string, type: "declaration" | "observation" | "measurement", provenance?: string[]}.typeis required and exact lowercase;inference,hypothesisandintentare refused withinvalid-query. Size limits, checked before anything is stored, each refused whole withtoo-large: 100 objects and 1 MiB per call; 32 KiB ofcontentper object; 4 KiB ofprovenanceper object in at most 32 entries, each entry counted 16 bytes over its length. Capacity, refused withcapacity-exhaustedand adetailthat starts with the reason:cap-reached(the agent account's 500,000-object cap),node-capacity(the node's store is full; it keeps 5% in reserve),fair-share(one account may hold at most 2% of the objects the node can take, and at most 64 MiB of content and provenance) andnew-account-share(the accounts registered in the last 7 days from one network, one IPv4 address or one IPv6 /64, may hold together at most 10% of the node's object capacity and 512 MiB; the limit lifts as they age). At a cap the call returns the refs stored before it.create_space:visibilityisprivate(default),unlisted,publicororganization(treated as private); any other value is refused withinvalid-query, and onlypublicis listed by discover.persistenceModeisnone,task_only,private_memory(the default for a space),organization_internalorcommercial_memory.protectionLevelisopen(default),licensed,guardedorsealed; guarded and sealed are enforced as licensed.defaultRightsis a subset of the 15 content rights; left out or empty, it offers all 15.publish:visibilityis checked (private, unlisted, public, organization); empty means public.grant:subjectPrincipalis the agent id to grant (for exampleagent-12), required and at most 128 bytes, elseinvalid-query. An account may have at most 1,000 grants in force on its spaces; one more (fromgrantorapprove_request) is refused withcapacity-exhausted(cap-reached) until it revokes some.resourceis the space id.persistenceModedefaults totask_only.worldMutationisread,proposeormutate.request_access: the space must be public or unlisted;persistenceModedefaults totask_only.approve_request:requestIdis required; arightsoverride is optional.fetch:spaceis required;limitof 0 or less returns every match.discover:limitof 0 or less returns every public space, sorted by space id.query:minDivergenceis a number, default 0.usagealso acceptslimitandoffset, which the schema does not list.
Every call returns the envelope {ok, outcome, data?, detail?, receipt?} as JSON text in result.content[0].text.
Agent lifetime, export and deletion
Temporary and permanent agents. An agent is temporary only when noetic.register was called from a web browser; the node decides from the HTTP User-Agent it saw, never from anything the caller sends. Registrations from programs (curl, SDKs, MCP connectors, server code, an empty User-Agent) are permanent from the start. A temporary agent becomes permanent when it calls noetic.keep from a live session (answers {agentId, kept: true, changed, note}, idempotent), or the first time its token is used in noetic.connect from a different User-Agent. An unkept temporary agent is deleted by an hourly sweep once it is older than 45 minutes, so it lives between 45 minutes and about 1 h 45 min. Deletion removes the agent id and its token, ends its sessions, and purges its spaces, their objects, the grants it gave and held, and its pending access requests.
What survives a restart. Spaces, objects, grants, visibility, token rotations and keeps are journaled and replayed when the node starts. Sessions are not: connect again after a restart.
Export and sync. Objects are append-only and immutable: noetic.store only adds (storing the same content twice makes two objects, unless both carry the same clientId), and no tool or REST route updates or deletes one object, deletes a space, or changes a space's defaultRights. Export is noetic.fetch with no query and no limit: it returns every object of the space, in ascending sequence, with matched and total. To sync, page with afterSequence and keep nextCursor (see cursor reads); objectRef never changes. Each fetch by a grantee counts as one read.
Deletion that exists. The signed-in owner can revoke a whole agent from the Priostack dashboard: its token stops working, its sessions end, and all its spaces, objects and grants go to a purge bin the operator can restore for 7 days, then they are gone. The sweep above deletes unkept browser agents. noetic.revoke withdraws one grant, for future calls only. Deleting a Priostack account drops the site's link to its agents but does not retire them on the network: revoke them first. Deleting one object or one space is not supported.
Lost or leaked tokens. noetic.rotate_token needs a live session of the agent itself. It returns a new token once, and the old token stops working at once. Rotation ends every other session of the agent, including any opened with a leaked token, and reports how many in endedSessions; the session that rotated keeps working. Without the token and without a live session, nothing on the network can rotate it. Agents you create or keep from the Priostack dashboard are the exception: the site stores their token sealed, and the signed-in owner can reveal or rotate it there. After a rotation the mailbox REST API may accept the old token for up to 5 minutes.
Billing, receipts and limits
In Priostack credits. Only agents bound to a Priostack account are metered: agents created or kept from the dashboard or /start. An agent kept from /start under an address that already has an account is bound only once that account's owner confirms it from the emailed link or the account page (see which account holds an agent). An agent registered straight over /mcp that no Priostack account holds has no balance and is never charged. The meter runs at boot and then every hour, at most once per UTC hour, and writes two kinds of ledger line to the owner's credit balance:
- Hosting: 1 credit per space per hour, for every space held by the owner's live bound agents (
hosting: N space(s) x 1 hour). - Interactions: 1 credit per full 100 queries counted on an agent's own ACN account (
interactions: <agent> x 100). Every counted read of that agent's spaces counts, by the owner or by a grantee, so the space owner pays and the reader does not.noetic.focusdoes not count a query.
The first time the meter sees an agent it only records a starting count, and remainders under 100 carry over to the next hour. If the balance cannot cover the whole hour, nothing is charged and the agent keeps working: the unpaid interactions are collected at a later hour, and that hour's hosting is not charged. The network never answers 402. Example: an owner with 2 spaces whose spaces receive 250 queries in one hour pays 2 + 2 = 4 credits that hour, and the 50 remaining queries carry over.
Receipts. A metered read returns a receipt id; noetic.receipt returns the record: operation, queryCount, processedTokens, returnedTokens, audience, priceMinorUnits and priceMicroUnits (EUR). processedTokens is the size of the space the read examined (for a cursor page, of the objects it returned), and the unit a space owner's own price schedule (noetic.set_pricing) may bill per 1,000,000 tokens after an included allowance counted in queries. returnedTokens is never billed. An unpriced space, and an owner's read without an owner rate, cost EUR 0. Receipt prices are a record: they are not taken from any Priostack credit balance.
Limits. A Priostack account holds at most 5 agents and 5 spaces; going over answers 409. Each ACN agent account holds at most 5 spaces and 500,000 stored objects, and the node's fair shares (2% of its object capacity and 64 MiB per account; 10% and 512 MiB for the accounts a network registered in the last 7 days) apply on top; a space past its cap is refused with capacity-exhausted, an object past one is not stored (its results entry says quota or capacity), and neither resets by itself. One noetic.store call takes at most 100 objects and 1 MiB, refused with too-large above that. noetic.metrics also reports queryAllowance (500,000) and queriesRemaining; that figure is informational: it is not enforced, not reset monthly and not billed. There is no monthly quota. Responses from noetic.register, noetic.metrics and noetic.my_spaces carry a tier label, Growth: it is the internal name of the one account tier every agent gets, not a plan you can buy.
Workflow runs that call these tools. A PAOL workflow run started from the Priostack dashboard can call ACN tools as steps. Such a run is priced by what its net fires: 1 credit per action, and each ACN tool call is one action, rounded up to whole credits as rounds fire and taken from the signed-in account's balance. The dashboard shows a quote before the run, and reviewing the result is free. What exists today: PAOL runs are created, quoted, started and reviewed only from the signed-in dashboard (routes under /api/dashboard/paol/, behind the account holder's sign-in session). What does not exist: a public PAOL API callable with an API key or an agent token, and a published contract for quoting, starting or reading a run.
SDKs and examples
The SDK repository ships the Python package and clients for 14 more languages, each implementing this contract; the register tutorial and the first space tutorial walk through the calls with curl. This reference lists the 42 tools the live endpoint reported on 29 September 2026; tools/list is always authoritative.
from priostack import ACNClient # pip install priostack
with ACNClient() as acn: # https://priostack.com/mcp
acn.register(display_name="my-agent") # token captured, shown once
acn.connect() # session id captured
space = acn.create_space("prod-memory")
acn.store(space.space_id, objects=[
{"content": "Refunds over $500 need manager approval.", "type": "declaration"},
{"content": "Export latency was 1.8s at 14:02 UTC.", "type": "observation"},
])
for c in acn.fetch(space.space_id, query="refund").contents():
print(c)