Reference · §05

JSON-RPC server

parc-server is a JSON-RPC 2.0 server that wraps the parc-core library, enabling any non-Rust application to interact with parc programmatically. Twenty methods cover the full core API: fragment CRUD, search, links, attachments, vault inspection, schemas, tags, and history.

# Transport

# stdio (default)

Newline-delimited JSON over stdin/stdout. Launch as a child process:

# Standalone binary
parc-server --vault /path/to/.parc

# Via CLI
parc server --vault /path/to/.parc

Send one JSON-RPC request per line. Read one JSON-RPC response per line. Flush after each write.

# Unix domain socket

Persistent server accepting multiple connections:

parc-server --vault /path/to/.parc --socket
parc-server --vault /path/to/.parc --socket-path /tmp/parc.sock

Default socket path: <vault>/server.sock. Same newline-delimited protocol per connection.

# Server config

The vault's config.yml can set server defaults:

server:
  transport: stdio    # "stdio" | "socket"
  socket_path: null   # override default socket location

CLI flags override config values.

# Protocol

All requests and responses follow JSON-RPC 2.0.

// Request
{"jsonrpc": "2.0", "id": 1, "method": "vault.info", "params": {}}

// Success response
{"jsonrpc": "2.0", "id": 1, "result": { "...": "..." }}

// Error response
{"jsonrpc": "2.0", "id": 1, "error": {"code": -32601, "message": "Method not found: foo"}}

Batch requests are supported — send a JSON array of requests, receive a JSON array of responses.

# Error codes

Code Name Description
-32700 Parse error Invalid JSON
-32600 Invalid Request Missing jsonrpc: "2.0" or malformed structure
-32601 Method not found Unknown method name
-32602 Invalid params Missing or wrong parameter types, unknown schema type
-32603 Internal error Core library error (fragment not found, index error, etc.)

Internal errors include data with the error message string.

# Methods

# fragment.create

Create a new fragment.

Field Type Required Description
type string yes Fragment type name or alias
title string no Fragment title
tags string[] no Tags
body string no Markdown body
links string[] no IDs to link to
status string no Status value
priority string no Priority level
due string no Due date (ISO 8601 or relative: today, tomorrow, etc.)
assignee string no Assignee

Returns the full fragment object.

{"jsonrpc":"2.0","id":1,"method":"fragment.create","params":{"type":"todo","title":"Review PRD","tags":["project"],"priority":"high","due":"2026-03-01"}}

# fragment.get

Retrieve a fragment by ID. Prefix matching supported.

Params: { "id": "<id-or-prefix>" }

Returns the full fragment object with body, tags, links, attachments, and any extra fields.

# fragment.update

Update an existing fragment. Only provided fields are changed.

Field Type Required Description
id string yes Fragment ID or prefix
title string no New title
tags string[] no Replace tags
body string no Replace body
links string[] no Replace links
status string no New status
priority string no New priority
due string no New due date
assignee string no New assignee

Returns the updated fragment object.

# fragment.delete

Soft-delete a fragment (moves to trash).

Params: { "id": "<id-or-prefix>" }

Result: { "id": "<full-id>", "deleted": true }

# fragment.list

List fragments with optional filters.

Field Type Required Description
type string no Filter by type
status string no Filter by status
tag string no Filter by tag
limit number no Max results
sort string no Sort order

Returns an array of fragment summary objects.

# fragment.search

Search using the full DSL query language.

Field Type Required Description
query string yes DSL query (e.g. type:todo status:open #backend)
limit number no Max results
sort string no Sort order

Returns an array of search result objects with id, type, title, status, tags, updated_at, snippet.

Create a bidirectional link between two fragments.

Params: { "id_a": "<id>", "id_b": "<id>" }

Result: { "linked": ["<id_a>", "<id_b>"] }

Remove a bidirectional link.

Params: { "id_a": "<id>", "id_b": "<id>" }

Result: { "unlinked": ["<id_a>", "<id_b>"] }

List all fragments linking to a given fragment.

Params: { "id": "<id-or-prefix>" }

Result: Array of { "id", "type", "title" }.

# fragment.attach

Attach a file to a fragment (copies the file).

Params: { "id": "<id-or-prefix>", "path": "/absolute/path/to/file" }

Result: { "id": "<full-id>", "filename": "<name>", "size": <bytes> }

# fragment.detach

Remove an attachment from a fragment.

Params: { "id": "<id-or-prefix>", "filename": "<name>" }

Result: { "id": "<full-id>", "filename": "<name>", "detached": true }

# fragment.attachments

List attachments for a fragment.

Params: { "id": "<id-or-prefix>" }

Result: Array of { "filename", "size" }.

# vault.info

Get vault metadata.

Params: {}

Result: { "path": "<vault-path>", "scope": "local"|"global", "fragment_count": <n> }

# vault.reindex

Rebuild the search index from fragment files.

Params: {}

Result: { "indexed": <count> }

# vault.doctor

Run vault health diagnostics.

Params: {}

Result: { "fragments_checked": <n>, "healthy": true|false, "findings": [...] }

Each finding has a type field: broken_link, orphan, schema_violation, attachment_mismatch, or vault_size_warning.

# schema.list

List all registered fragment types.

Params: {}

Result: Array of { "name", "alias", "fields": [...] }.

# schema.get

Get a specific schema definition.

Params: { "type": "<type-name-or-alias>" }

Result: { "name", "alias", "editor_skip", "fields": [...] }

# tags.list

List all tags with usage counts.

Params: {}

Result: Array of { "tag": "<name>", "count": <n> }.

# history.list

List version history for a fragment.

Params: { "id": "<id-or-prefix>" }

Result: Array of { "timestamp": "<iso-8601>", "size": <bytes> }.

# history.get

Retrieve a specific historical version.

Params: { "id": "<id-or-prefix>", "timestamp": "<iso-8601>" }

Result: Fragment content at that version.

# history.restore

Restore a previous version. Creates a new snapshot of the current version first.

Params: { "id": "<id-or-prefix>", "timestamp": "<iso-8601>" }

Result: { "id", "type", "title", "restored_from": "<timestamp>" }

# Integration examples

# Node.js / TypeScript

import { spawn } from 'child_process';
import * as readline from 'readline';

const server = spawn('parc-server', ['--vault', '/path/to/.parc']);
const rl = readline.createInterface({ input: server.stdout });

let nextId = 1;
const pending = new Map<number, { resolve: Function; reject: Function }>();

rl.on('line', (line) => {
  const resp = JSON.parse(line);
  const p = pending.get(resp.id);
  if (p) {
    pending.delete(resp.id);
    if (resp.error) p.reject(resp.error);
    else p.resolve(resp.result);
  }
});

function call(method: string, params: any): Promise<any> {
  return new Promise((resolve, reject) => {
    const id = nextId++;
    pending.set(id, { resolve, reject });
    server.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n');
  });
}

// Usage
const note = await call('fragment.create', { type: 'note', title: 'Hello', body: 'World' });
const results = await call('fragment.search', { query: 'type:note' });

# Python

import subprocess, json

proc = subprocess.Popen(
    ['parc-server', '--vault', '/path/to/.parc'],
    stdin=subprocess.PIPE, stdout=subprocess.PIPE,
    text=True, bufsize=1,
)

def call(method, params, id=1):
    req = json.dumps({"jsonrpc": "2.0", "id": id, "method": method, "params": params})
    proc.stdin.write(req + "\n")
    proc.stdin.flush()
    line = proc.stdout.readline()
    return json.loads(line)

# Usage
resp = call("fragment.create", {"type": "todo", "title": "Test", "priority": "high"})
print(resp["result"]["id"])

resp = call("fragment.search", {"query": "type:todo status:open"}, id=2)
print(f"Found {len(resp['result'])} todos")