MCP Tools
The Model Context Protocol (MCP) lets AI assistants connect directly to your knowledge graph. Graphory exposes 49 tools grouped into read/search, write (idempotent, non-idempotent, and destructive), and review. Your AI can search, traverse relationships, manage connections, ingest data, and write insights back, all through natural conversation.
What is MCP?
MCP is an open standard that lets AI assistants connect to external data sources and tools. Graphory provides an MCP server that gives your AI read and write access to your knowledge graph, plus tools for managing your entire Graphory account.
Instead of copy-pasting data into chat, your AI can directly search your graph, connect new data sources, check billing, and save findings back for future reference.
Agent-First Philosophy
Setup
See the Getting Started guide for platform-specific configuration instructions covering Claude Desktop, Claude Code, ChatGPT, Gemini CLI, Cursor, Windsurf, VS Code, Codex CLI, and direct API access.
MCP Config Snippet
For any MCP-compatible client, point to the Graphory MCP endpoint with your API key:
{
"mcpServers": {
"graphory": {
"url": "https://api.graphory.io/mcp",
"headers": {
"Authorization": "Bearer gs_ak_your_api_key"
}
}
}
}
Tool Reference
Graphory exposes 49 tools across three categories. Read tools are safe to call freely; write tools create or modify graph data and are subject to confidence gating (destructive variants delete or revoke and require explicit user intent); review tools accept or reject pending suggestions surfaced by the nightly pipeline.
- Read and search (24 tools)
- Write (21 tools)
- Review (4 tools)
Tool List
The reference below is auto-generated from mcp_server/server.py. 49 tools total: 24 read, 21 write (which includes idempotent, non-idempotent, and destructive writes), 4 review. Argument schemas, defaults, and descriptions come straight from the server source, so they always match the live tool surface.
mcp_server/server.py. 24 read · 21 write · 4 review - 49 total.Read tools (24)
browse_nodes read
Browse nodes in the graph by type and/or entity. Use to list contacts, activities, assets, etc.
Annotation: _READ_ONLY · Returns: str · Source: server.py:4427
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
node_type | str | optional | '' | Filter by node type. Call describe_schema for the live vocabulary. |
entity | str | optional | '' | Filter by entity/company slug. Optional. |
limit | int | optional | 50 | Max nodes to return (default 50, max 200) |
connection_health read
Check health and sync status of data source connections.
Shows last sync time, error count, and status for each connection.
Optionally filter to a single app.
Annotation: _READ_ONLY · Returns: str · Source: server.py:2384
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
app | str | optional | '' | Filter to a specific app (optional, e.g. "gmail", "github") |
describe_schema read
Return the full graph schema: node labels, relationship types,
common properties, and trust tiers.
Use this before write_to_graph when you need to see every valid node
label, every allowed relationship type, and recommended property names.
No arguments needed.
When an org is in scope, the response is augmented with the actual node
labels and edge types currently present in that org's graph (queried
live from /stats), so callers can distinguish "vocabulary the API
accepts" from "what is already in my graph".
Annotation: _READ_ONLY · Returns: str · Source: server.py:967
No arguments.
dossier read
Get a comprehensive cross-source profile of an entity in one call.
Composes search to resolve the entity -> 1-hop neighborhood with
edge-type counts -> recent timeline -> latent (community-based)
connections. Returns a single structured summary so an agent doesn't
have to chain search + get_entity + traverse + timeline +
get_latent_connections separately. Lower latency, lower token spend,
same coverage. Recommended single-call alternative for "tell me about X"
style requests.
Annotation: _READ_ONLY · Returns: str · Source: server.py:658
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
entity | str | required | - | name, id, email, or fuzzy reference to a node |
depth | int | optional | 2 | traversal depth (1-3, default 2; depth>=2 adds an indirect neighbor count when the start node has < ~150 hop-1 edges) |
include_timeline | bool | optional | True | include recent activity feed (default True) |
include_latent | bool | optional | True | include latent_connections (default True) |
timeline_days | int | optional | 90 | lookback window for timeline (default 90). Pass 0 for unlimited (return all events). |
max_neighbors | int | optional | 30 | cap on neighbors returned (default 30, hard cap 100) |
graph_id | str | optional | 'primary' | which graph to query, default "primary" |
get_entity read
Get full details for a node and its relationships.
Annotation: _READ_ONLY · Returns: str · Source: server.py:485
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
node_id | str | required | - | The node ID to look up |
get_extraction_queue read
View the current extraction queue for your user.
Wave 2B replaced the old global file lock with a per-user queue: every
write tool that runs with sync=False (or that can't finish inline within
the 60s deadline of sync=True) lands here, and the per-user worker
drains it. This tool is how you see what's pending, what's processing,
what failed, and roughly how long the rest will take.
Returns a formatted summary plus a list of recent queue rows. The
summary includes:
- your_pending / your_processing counts
- global_pending / global_processing counts (so you know how many
items are ahead of yours)
- eta_seconds: estimated wait for the next pending row to complete,
based on the average extraction time of items finished in the last
hour (or "unknown" if there's no recent history).
Annotation: _READ_ONLY · Returns: str · Source: server.py:5245
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
status | str | optional | 'all' | 'all' (default), 'pending', 'processing', 'done', 'failed', 'abandoned'. |
limit | int | optional | 50 | Maximum rows to return (default 50, max 200). |
get_file read
Get the source .md file content for a node. Returns the original collected data behind any graph node.
Annotation: _READ_ONLY · Returns: str · Source: server.py:4484
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
node_id | str | required | - | The node ID to retrieve the source file for |
get_job_status read newly documented
Check the progress of an async job (sync, bulk import, collection trigger).
Pass a job_id returned from: sync_graph, configure_collection, import_from_api.
Returns job status, progress, and completion details.
Annotation: _READ_ONLY · Returns: str · Source: server.py:5332
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
job_id | str | required | - |
get_latent_connections read
Find entities in the same graph community as entity_id that are NOT
directly connected. These are potential warm introductions or hidden
relationships the user may not have noticed.
Annotation: _READ_ONLY · Returns: str · Source: server.py:1097
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
entity_id | str | required | - | The node ID to find latent connections for (e.g. contact:sarah@acme.com) |
limit | int | optional | 10 | Max suggestions to return (default 10, max 50) |
get_stale_entities read
Find entities that have gone quiet - no activity in the last N days.
Useful for surfacing forgotten follow-ups or dormant relationships.
Annotation: _READ_ONLY · Returns: str · Source: server.py:1144
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
days | int | optional | 14 | Days of inactivity to consider "stale" (default 14) |
node_type | str | optional | 'Contact' | Node type to filter. Call describe_schema for the live vocabulary. |
limit | int | optional | 20 | Max to return (default 20) |
get_weekly_digest read
Get the most recent weekly digest - a prebuilt summary of the week's
graph activity, new entities, merged duplicates, stale relationships,
and community changes. Generated automatically every Monday.
Annotation: _READ_ONLY · Returns: str · Source: server.py:1185
No arguments.
graph_stats read
Get overview statistics for the knowledge graph (node/edge counts by type).
Annotation: _READ_ONLY · Returns: str · Source: server.py:840
No arguments.
list_connections read
List active data source connections for this org.
Annotation: _READ_ONLY · Returns: str · Source: server.py:1018
No arguments.
list_custom_sources read
List all custom API connections for an entity or the whole org.
Shows system names, connection status, endpoint count, and last collection time.
Annotation: _READ_ONLY · Returns: str · Source: server.py:4072
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
entity | str | optional | '' | Filter to a specific entity (optional, lists all if empty) |
list_knowledge_bases read
List all graphs in your org (operational + standalone).
Shows display name, template, description, node count, edge count,
and the published flag for each. The `primary` graph is your
operational graph (Gmail / QB / Slack data); other graphs are
standalone KBs created via create_knowledge_base.
Annotation: _READ_ONLY · Returns: str · Source: server.py:4838
No arguments.
list_public_tokens read
List public tokens scoped to a given graph. Owner-only.
Returns label, prefix, rate limit, created_at, last_used_at, and
usage_count for each token. Does NOT return raw keys - the full
key is only shown once at creation.
Annotation: _READ_ONLY · Returns: str · Source: server.py:5056
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
graph_id | str | required | - | Slug of the standalone graph to list tokens for. |
list_rules read
List correction rules that have been applied to this org's graph.
Annotation: _READ_ONLY · Returns: str · Source: server.py:1049
No arguments.
propose_ontology read
Propose a custom ontology for a domain. Owner-only.
Calls Claude (via subprocess) to design a Pydantic schema describing
the entity types and relationship types an LLM extractor should pull
out of prose for this domain. Returns a JSON proposal -- nothing is
persisted. Iterate via the `refinement` argument; commit via
save_ontology_template.
First call: pass domain_description (e.g. "directory of US-based SBA
lenders with loan products, asset classes, geographies, contacts").
To iterate: pass refinement (e.g. "add a CreditScoreRange field to
LoanProduct"). Optionally fork an existing template via base_template.
Annotation: _READ_ONLY · Returns: str · Source: server.py:4905
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
domain_description | str | required | - | Plain-English description of the domain. |
refinement | str | optional | '' | Optional adjustment instruction for an iterative round. |
base_template | str | optional | '' | Optional existing template name (e.g. "general") to fork as a starting point. |
search_graph read
Search the knowledge graph for nodes matching a query.
Searches node names, titles, and full document body text via the
full-text index. For broader matching, include synonyms or related terms
in expanded_terms - your AI knows the user's intent best.
Annotation: _READ_ONLY · Returns: str · Source: server.py:424
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
query | str | required | - | Search text (names, keywords, topics) |
node_type | str | optional | '' | Filter by node type. Call describe_schema for the live vocabulary. |
entity | str | optional | '' | Filter by entity/company within the org (optional) |
limit | int | optional | 20 | Max results (default 20, max 100) |
expanded_terms | str | optional | '' | Comma-separated synonyms or related terms for broader matching (e.g. "pricing,payment,subscription,invoice"). Your AI should generate these based on user intent. |
setup_status read
Combined Graphory account dashboard and setup checklist.
Works even without a valid API key - returns onboarding instructions.
If authenticated, shows account info, graph stats, plan/usage,
connections, last sync, and a setup checklist with the next
recommended action. Useful as a first call to understand the current
state of everything.
Annotation: _READ_ONLY · Returns: str · Source: server.py:2503
No arguments.
sync_status read
Check the status of the most recent sync pipeline run.
Shows progress of each step: Data Extraction, Deduplication & Linking,
Quality Enrichment.
Annotation: _READ_ONLY · Returns: str · Source: server.py:3115
No arguments.
test_custom_source read
Test connectivity for an existing custom API connection.
Re-tests that the stored credentials can reach the API.
Annotation: _READ_ONLY · Returns: str · Source: server.py:4187
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
system_name | str | required | - | The system slug (e.g. "cloze") |
entity | str | required | - | Entity/company slug |
test_endpoint | str | optional | '' | Optional endpoint path to test (e.g. "/me"). If empty, tests the first configured endpoint. |
timeline read
Get recent activity feed sorted by date.
Annotation: _READ_ONLY · Returns: str · Source: server.py:797
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
entity | str | optional | '' | Filter by entity/company (optional) |
days | int | optional | 30 | Look back N days (default 30). Pass 0 for unlimited (return all events regardless of date). |
limit | int | optional | 50 | Max events (default 50, max 200) |
traverse read
Multi-hop traversal from a starting node, with cursor pagination.
Response includes has_more (bool) and next_cursor (string or null).
For dense hub nodes (thousands of edges), iterate by passing next_cursor back in.
Annotation: _READ_ONLY · Returns: str · Source: server.py:616
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
start_id | str | required | - | Starting node ID |
depth | int | optional | 2 | How many hops (1-4, default 2) |
edge_types | str | list[str] | optional | '' | Edge type filter. Accepts either a comma-separated string ("works_for,knows") or a list of strings (["works_for", "knows"]). Call describe_schema for the live vocabulary. |
limit | int | optional | 200 | Max edges per page (default 200, max 1000). |
cursor | str | optional | '' | Pass next_cursor from a previous response to fetch the next page. |
Write tools (21)
configure_collection write
Configure what data to collect from a connected source.
Built-in collectors run their full action set automatically once a source
is connected via `connect_source`. There is no per-action catalog to
configure for built-in connectors. For arbitrary REST/GraphQL sources, use
`configure_custom_collector` to declare endpoints explicitly.
This tool returns guidance on which path to use. See args below.
Annotation: _NON_IDEMPOTENT_WRITE · Returns: str · Source: server.py:2191
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
app | str | required | - | Which connected app (gmail, github, slack, quickbooks, etc.) |
intent | str | optional | '' | Natural language description of what to collect (legacy, ignored) |
actions | str | optional | '' | Comma-separated explicit action names (legacy, ignored) |
trigger_now | bool | optional | True | Whether to start collection immediately (legacy, ignored) |
configure_custom_collector write
Configure which API endpoints to collect data from for a custom source.
Call this after connect_custom_source to define what data to pull.
Supports BOTH REST and GraphQL endpoints, mixed freely in the same
config (pick mode per-endpoint via `api_type`).
REST endpoint (default, `api_type` omitted or "rest"):
{
"name": "contacts",
"path": "/contacts",
"method": "GET",
"node_type": "<see describe_schema>",
"item_type": "contact",
"domain": "operations",
"params": {"sort": "-updated_at"},
"body": null,
"items_path": "data.contacts",
"id_field": "id",
"title_field": "name",
"date_field": "updated_at",
"url_template": "https://app.example.com/contacts/{id}",
"pagination": {"type": "offset", "param": "offset",
"size": 100, "size_param": "limit"}
}
GraphQL endpoint (`api_type: "graphql"`):
{
"name": "repositories",
"api_type": "graphql",
"graphql_query": "query($cursor: String) { viewer { repositories(first: 50, after: $cursor) { nodes { id name description url updatedAt } pageInfo { hasNextPage endCursor } } } }",
"graphql_variables": {},
"node_type": "<see describe_schema>",
"item_type": "repository",
"domain": "content",
"items_path": "data.viewer.repositories.nodes",
"id_field": "id",
"title_field": "name",
"date_field": "updatedAt",
"pagination": {
"type": "graphql_cursor",
"has_more_path": "data.viewer.repositories.pageInfo.hasNextPage",
"cursor_path": "data.viewer.repositories.pageInfo.endCursor",
"variable_name": "cursor"
}
}
For GraphQL:
- The collector POSTs {query, variables} to the base_url
you set in connect_custom_source. Include `/graphql`
in base_url if the API needs it (e.g.
`https://api.github.com/graphql`).
- `path` and `method` are ignored for GraphQL endpoints.
- `graphql_query` is REQUIRED.
- A 200 response with non-empty `errors[]` is treated as
a failure (auth-looking errors map to exit code 2).
REST pagination types: "offset", "page_number", "cursor",
"link_header", "has_more", or null.
GraphQL pagination types: "graphql_cursor" or null.
rate_limit_seconds: Seconds between API requests (default: 0.5)
schedule: Optional - update the polling schedule for this source at
the same time as configuring endpoints. Valid:
live, standard, nightly, manual, paused.
Empty string keeps the existing value. See AGENT_GUIDE.
Annotation: _NON_IDEMPOTENT_WRITE · Returns: str · Source: server.py:3650
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
system_name | str | required | - | The system slug (must match an existing custom connection) |
entity | str | required | - | Entity/company slug |
endpoints | str | required | - | JSON string containing a list of endpoint configs. |
rate_limit_seconds | float | optional | 0.5 | |
schedule | str | optional | '' |
connect_custom_source write
Connect a custom REST API that is not in the standard connector list.
For systems like Cloze CRM, niche ERPs, or internal tools where the user
has API credentials. Stores credentials in encrypted credential storage
and optionally tests connectivity.
Annotation: _NON_IDEMPOTENT_WRITE · Returns: str · Source: server.py:3180
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
system_name | str | required | - | Short slug for the system (e.g. "cloze", "propertyware", "buildium") |
base_url | str | required | - | API base URL (e.g. "https://api.cloze.com/v1") |
auth_type | str | required | - | How to authenticate - "bearer", "api_key_header", "api_key_query", or "basic_auth" |
credentials | str | required | - | JSON string with credential fields. Varies by auth_type: - bearer: {"token": "..."} - api_key_header: {"api_key": "..."} - api_key_query: {"api_key": "..."} - basic_auth: {"username": "...", "password": "..."} |
entity | str | required | - | Entity/company slug this connection belongs to |
test_endpoint | str | optional | '' | Optional endpoint path to verify connectivity (e.g. "/me", "/ping") |
auth_config | str | optional | '' | Optional JSON string with extra auth settings: - api_key_header: {"header_name": "X-Custom-Header"} - api_key_query: {"param_name": "api_key", "extra_params": {"user": "me@co.com"}} |
extra_headers | str | optional | '' | Optional JSON dict of additional headers to send alongside the primary auth. Use this for APIs that require multiple auth headers, like Supabase which needs both 'apikey' AND 'Authorization: Bearer'. Example: '{"apikey": "eyJ..."}' |
schedule | str | optional | 'standard' | How often Graphory should poll this source. You MUST pick this based on how often the data changes (see AGENT_GUIDE). |
connect_source write
Route a user to the right way to connect a data source.
Graphory is BYOC (bring your own credentials). There is no managed OAuth
broker. This tool tells the agent which of two paths to use for a given
source:
1. Browser OAuth sources (Gmail, Google Calendar, Google Drive, Slack,
HubSpot, Salesforce, QuickBooks, Shopify, Stripe, etc.) - the user
finishes the OAuth flow in the web app at app.graphory.io/connections.
2. Personal access token / API key sources (GitHub, Notion, Supabase,
Airtable, Linear, Asana, Cloze, any custom REST API) - the agent
should call connect_custom_source with the user's token instead.
Annotation: _NON_IDEMPOTENT_WRITE · Returns: str · Source: server.py:2701
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
app | str | optional | '' | App slug (e.g. gmail, github, slack, notion). Leave empty to see both paths and how to choose. |
entity | str | optional | '' | Unused here. Entity is selected inside app.graphory.io or passed to connect_custom_source. |
create_knowledge_base write
Create a new standalone knowledge graph in your org. Owner-only.
Provisions an empty FalkorDB graph and registers it under a slug
derived from `name` (you can pass `graph_id` via the dashboard for
full control). The template determines the ontology used by the
LLM-driven KB extractor when you ingest content into this graph.
Available templates:
- general (safe default - people, orgs, places, dates, concepts)
- coach (business / self-improvement content)
- research (academic papers, scientific writeups)
- ai_chat (transcripts of AI <-> human conversations)
- documents (mixed-format business documents)
- custom_<name> (any custom template you saved with
save_ontology_template)
Returns the new graph_id you can pass to other tools (save_message,
write_to_graph, search_graph) via their `graph_id` arg to target
this graph instead of the org's primary operational graph.
Annotation: _NON_IDEMPOTENT_WRITE · Returns: str · Source: server.py:4780
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
name | str | required | - | Human-readable display name (e.g. "Lender Directory"). |
template | str | optional | 'general' | Ontology template. Defaults to "general". |
description | str | optional | '' | Optional short description. |
create_public_token write
Create a public read-only token scoped to one of your graphs.
The returned token can be embedded in client-side code or shared
publicly - it only allows read access to the named graph and is
rate-limited per token. Treat it like a Stripe publishable key.
Returns the full token ONCE on creation. Save it; you can't
retrieve it later (only the prefix and metadata are visible
via list_public_tokens).
Refused for the operational `primary` graph - public tokens are
for standalone graphs created via create_knowledge_base.
Annotation: _NON_IDEMPOTENT_WRITE · Returns: str · Source: server.py:4994
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
graph_id | str | required | - | Slug of the standalone graph this token can read. Get from list_knowledge_bases. Cannot be 'primary'. |
label | str | required | - | Human-readable label, e.g. 'lender-directory-public-2026'. |
rate_limit_per_min | int | optional | 100 | Per-token rate limit. Default 100/min. Burst capacity is a fixed 20. Cap 10000. |
delete_knowledge_base write
Delete a standalone graph. Owner-only.
Refuses to delete the `primary` (operational) graph -- use the
org-level delete endpoint for that. Refuses to delete any graph
with `published=true`; unpublish it first.
WARNING: this drops the FalkorDB graph and removes the metadata
entry. The .md files on disk are unchanged but the graph index
over them is gone. Pass `confirm=true` to actually delete; without
confirm the call returns a preview of what would be deleted.
Annotation: _DESTRUCTIVE · Returns: str · Source: server.py:4872
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
graph_id | str | required | - | Slug of the graph to delete (from list_knowledge_bases). |
confirm | bool | optional | False | Pass true to actually proceed with the delete. |
import_from_api write
Push data from any external API into Graphory.
Your AI agent pulls data from a REST API (CRM, ERP, custom system) using
the user's credentials, then passes the raw response items here. Graphory
transforms them into the standard ingest format and writes them to the graph.
Annotation: _IDEMPOTENT_WRITE · Returns: str · Source: server.py:1749
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
source_name | str | required | - | Name of the source system (e.g. "cloze", "pipedrive", "custom_crm") |
items | str | required | - | JSON string containing a list of objects from the API response |
item_type | str | required | - | What kind of data (e.g. "contact", "deal", "invoice", "message", "task") |
domain | str | optional | 'operations' | Data domain - "operations" (default), "capital", or "content" |
entity | str | optional | '' | Entity/company slug this data belongs to (ask the user if unclear) |
id_field | str | optional | 'id' | Which field in each item is the unique ID (default "id") |
sync | bool | optional | True | If True (default), block until every record is queryable via both graph and full-text search, and return node/edge counts. Pass sync=False to enqueue for batch processing later (records are durable on B2 immediately; the per-user extraction worker picks them up - call get_extraction_queue to track progress). |
ingest write
Canonical push-to-graph entry point. Use this for any operation that
pushes data to the user's graph: messages, notes, files, conversations,
structured records, batch operations, multi-source snapshots, etc.
This is the DEFAULT for any AI memory operation. `save_message` is just
a thin convenience wrapper around this endpoint for single notes;
anything else (batches, structured records, file payloads with metadata)
should call `ingest` directly.
Data is written as .md files (one per item) with YAML frontmatter, then
extracted into the graph inline when sync=True (default) or enqueued
for the per-user extraction worker when sync=False.
Annotation: _IDEMPOTENT_WRITE · Returns: str · Source: server.py:1662
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
payload | str | required | - | JSON string. Single item shape: {"entity": "<slug>", "source": "<system>", "type": "<doctype>", "title": "<title>", "body": "<text>", "date": "YYYY-MM-DD", "url": "<url>", "metadata": {...}} |
sync | bool | optional | True | If True (default), run extraction inline and return node/edge counts. Pass sync=False to enqueue for batch processing later (data is durable on B2 immediately; the per-user extraction worker picks it up - call get_extraction_queue to track). |
manage_billing write
Check your plan, usage, or manage your subscription.
Annotation: _DESTRUCTIVE · Returns: str · Source: server.py:2835
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
action | str | optional | 'status' | What to do: - "status" - See your current plan and usage - "upgrade" - Get a link to upgrade your plan - "portal" - Get a link to manage your subscription (invoices, payment method, cancel) |
publish_graph write
Publish a standalone graph to the marketplace. Owner-only.
Marks the graph as a sellable knowledge graph and starts a recurring
Stripe subscription item billed at the marketplace publish fee
(default $50/mo). The fee is independent of your org plan.
Refused for the operational `primary` graph and for graphs that are
already published.
Pass `confirm=True` to authorize the recurring charge. Without
confirm the call returns 400 with a description of what would be
charged so you can show a confirmation prompt.
Use list_public_tokens / create_public_token to issue embed-safe
tokens for downstream consumers; publishing and public token
issuance are independent. Existing public tokens for this graph
remain valid through publish / unpublish cycles.
Annotation: _NON_IDEMPOTENT_WRITE · Returns: str · Source: server.py:5134
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
graph_id | str | required | - | Slug of the standalone graph to publish (from list_knowledge_bases). Cannot be 'primary'. |
license_fee_usd | float | optional | 50.0 | Per-month licensing fee in USD. Default 50.0. |
confirm | bool | optional | False | Pass True to authorize the recurring charge. |
register_business write
Register a Business (a company the user owns) in this org. Owner-only.
Creates a :Business node on the org's primary graph and appends the
derived slug to the org's `entities` list. After this returns, save_message,
send_file, ingest, and write_to_graph calls targeting that slug will pass
the entity-access gate. Until a Business is registered for a slug, those
writes are rejected with "Entity '<slug>' is not registered."
Idempotent: if `slug` is already registered, the existing record is
returned with status="exists" and no graph mutation happens.
Returns the slug to use as the `entity` arg in subsequent calls.
Annotation: _NON_IDEMPOTENT_WRITE · Returns: str · Source: server.py:4682
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
name | str | required | - | Display name (e.g. "Groundstone Group"). |
biz_type | str | required | - | One of: operating, holding, project, fund, vehicle, other. - operating - active business (consulting, products, services) - holding - parent / portfolio entity - project - one-off venture or build - fund - investment vehicle - vehicle - SPV, LLC for a single asset - other - anything else |
slug | str | optional | '' | Optional entity slug. Auto-derived from name if absent (e.g. "Groundstone Group" -> "groundstone-group"). |
revoke_public_token write
Revoke a public token immediately. Owner-only.
The token stops working as soon as the cache refreshes (within
seconds). Issue a new token first if you need uninterrupted
embed access.
Annotation: _DESTRUCTIVE · Returns: str · Source: server.py:5095
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
graph_id | str | required | - | Slug of the graph the token belongs to. |
token_id | str | required | - | Token id (pt_*) from list_public_tokens. |
save_conversation write
Save a conversation to the knowledge graph. Two-step process.
Step 1: Call with action="get_schema" to get the required format and instructions.
Step 2: Call with action="save" and data=<YAML frontmatter + body>.
The schema tells you exactly how to structure the data. Fill in every field
based on what you know from the conversation, then call again with action="save".
The YAML frontmatter supports both `participants:` (backward-compat, list
of names/emails that become participant edges) and `linked_entities:`
(preferred, list of {name, context, email?} dicts where context is one of
participant/subject/mentioned/author/recipient). Both forms coexist; the
extractor reads both.
Annotation: _IDEMPOTENT_WRITE · Returns: str · Source: server.py:1949
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
action | str | optional | 'get_schema' | "get_schema" (returns template + instructions) or "save" (submits data) |
data | str | optional | '' | For action="save", the complete .md content with YAML frontmatter |
sync | bool | optional | True | If True (default), block until the conversation is queryable via both graph and full-text search, and return node/edge counts. Pass sync=False to enqueue for batch processing later (data is durable on B2 immediately; the per-user extraction worker picks it up - call get_extraction_queue to track progress). |
save_message write
Convenience wrapper around `ingest` for the common case of saving a
SINGLE message / conversation memory.
For anything other than a single note (batch records, structured
payloads, file content, multi-source snapshots, etc.) prefer `ingest`
directly. `ingest` is the canonical push-to-graph entry point.
Use save_message when:
- Saving one observation, decision, TODO, summary, or conversation
memory the user just produced.
- You don't have a structured record - just title + body text.
The note is saved as a graph node that auto-links to any people,
companies, accounts, or other entities mentioned in the body text.
Name-mining runs automatically on the content, so plain prose gets
connected without you having to wire entities yourself. Provide
linked_entities explicitly when you want role-typed edges (who
participated, who the note is about, etc.).
Your next session can query these notes like any other node. Notes
are connected to the rest of the graph - every customer, invoice,
meeting, or thread they reference - so later questions like "what
did I decide about Acme last quarter" return the right node with
its full context.
Annotation: _IDEMPOTENT_WRITE · Returns: str · Source: server.py:1397
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
title | str | required | - | Short name for this note |
content | str | required | - | Body of the note - plain text, markdown OK |
entity | str | optional | '' | Which company/entity this note belongs to |
type | str | optional | 'note' | Content type (note, conversation, summary, research, memo) |
domain | str | optional | 'operations' | content | operations | capital |
participants | str | optional | '' | Backward-compat alias. JSON array of names or emails, each auto-converted to a linked_entities entry with context="participant". Prefer linked_entities for new code. |
linked_entities | str | optional | '' | PREFERRED. JSON array of entities this note is about. |
sync | bool | optional | True | If True (default), block until the note is queryable via both graph and full-text search, and return node/edge counts. Pass sync=False to enqueue for batch processing later (data lands on durable storage immediately; the per-user extraction worker picks it up - call get_extraction_queue to track progress). |
save_ontology_template write
Persist a proposed ontology to a custom KB template file. Owner-only.
The saved template can immediately be used to back a new graph:
create_knowledge_base(name="Lender Directory",
template=f"custom_{name}")
Annotation: _NON_IDEMPOTENT_WRITE · Returns: str · Source: server.py:4949
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
name | str | required | - | Slug for the template. Lowercase letters, numbers, underscores. Stored on disk as `custom_<name>.py`. |
proposal_json | str | required | - | JSON string returned from propose_ontology (or a hand-edited equivalent with the same shape). |
send_file write
Write a file (report, summary, analysis) to the data collection.
Same as save_message but you control the filename.
Annotation: _NON_IDEMPOTENT_WRITE · Returns: str · Source: server.py:1563
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
filename | str | required | - | Output filename (e.g. q1-report.md) |
content | str | required | - | File content (will get YAML frontmatter prepended) |
entity | str | optional | '' | Which company/entity this belongs to |
type | str | optional | 'document' | document | report | summary | analysis |
domain | str | optional | 'content' | content | operations | capital |
sync | bool | optional | True | If True (default), block until the record is queryable via both graph and full-text search, and return node/edge counts. Pass sync=False to enqueue for batch processing later (file is durable on B2 immediately; the per-user extraction worker picks it up - call get_extraction_queue to track progress). |
sync_graph write
Trigger processing of collected data into the graph.
Runs the full pipeline:
Data Extraction -> Deduplication & Linking -> Quality Enrichment.
This is the same pipeline that runs nightly, but triggered on demand.
Use this after collecting new data (emails, Slack messages, etc.) to make
it immediately available in the graph without waiting for the nightly run.
Returns status and job_id for tracking progress.
Annotation: _NON_IDEMPOTENT_WRITE · Returns: str · Source: server.py:2969
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
wait | bool | optional | False | If true, poll until the sync completes (up to 10 minutes). Default: false. |
entity | str | optional | '' | Optional - scope a collection run to a single entity (company slug). Required when `system` is provided. |
system | str | optional | '' | Optional - scope a collection run to a single custom source (e.g. "supabase", "github", "firecrawl"). Use this to fire collection on a manual-schedule source that does NOT run on the cron. When provided, the orchestrator fetches ONLY that source and then the full extraction sync runs as normal. |
unpublish_graph write
Unpublish a graph and cancel the marketplace licensing fee. Owner-only.
Detaches the recurring Stripe subscription item (Stripe handles
proration per its defaults) and clears the publish metadata.
Existing public tokens for this graph remain valid through the
unpublish; revoke them separately via revoke_public_token if you
also want to cut off embed access.
Annotation: _DESTRUCTIVE · Returns: str · Source: server.py:5199
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
graph_id | str | required | - | Slug of the graph to unpublish. |
update_source_schedule write
Change the polling schedule for an existing custom API connection.
Use this to pause a source ("paused"), move it to on-demand only
("manual"), or change the cron cadence after the fact.
This updates the stored configuration for the source and takes effect on
the next cron run.
Annotation: _IDEMPOTENT_WRITE · Returns: str · Source: server.py:3965
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
system_name | str | required | - | The custom source slug (e.g. "cloze", "supabase", "firecrawl") |
entity | str | required | - | Entity/company slug the source belongs to |
schedule | str | required | - | New schedule. MUST be one of: - "live" - polled every 6h (live email, tickets, chat) - "standard" - polled twice daily (most CRMs/ERPs; default) - "nightly" - polled once a day at 4am (reference data) - "manual" - NOT polled; you trigger via sync_graph(system=...) - "paused" - parked, not active (expired auth, user paused) |
write_to_graph write
Write data to the graph or correct existing data.
Actions:
add - Add new information. Provide node_id, label, and properties.
connect - Link two existing items. Provide from_id, to_id, relationship.
correct - Fix something that is wrong. Provide node_id and the corrected properties.
merge - Consolidate two nodes you believe are duplicates. Provide
loser_id and winner_id. Rewrites every edge from loser onto
winner, copies non-conflicting properties (winner-wins),
and marks the loser :Merged (preserves audit trail). The
loser node is NOT deleted - it gains a :Merged label
and merged_into/merged_at/status='merged' properties.
For nodes you believe are duplicates, use action='merge' to consolidate.
Use action='connect' relationship='same_as' only to record a soft
assertion without consolidation (the nodes stay separate; no edges move).
Annotation: _DESTRUCTIVE · Returns: str · Source: server.py:1220
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
action | str | required | - | add | connect | correct | merge |
node_id | str | optional | '' | ID of the item to add or correct |
label | str | optional | 'Activity' | Node type/label. Call describe_schema for the live vocabulary. Defaults to the generic activity type. |
properties | str | optional | '{}' | JSON string of data (name, email, description, etc.) |
from_id | str | optional | '' | Source item ID when connecting two items |
to_id | str | optional | '' | Target item ID when connecting two items |
relationship | str | optional | '' | One of the allowed relationship types. Call describe_schema for the live vocabulary. |
confidence | float | optional | 0.9 | How sure you are (0.0-1.0). Ignored for action='merge' (forced to 1.0, user_correction authority). |
evidence | str | optional | '' | Why this change is being made |
loser_id | str | optional | '' | For action='merge'. Node to mark as :Merged - its edges are rewritten onto the winner. |
winner_id | str | optional | '' | For action='merge'. Node to keep - receives all edges and any missing properties from the loser. |
Review tools (4)
batch_merge_suggestions review
Confirm many merge-category suggestions in one call.
Accepts both merge_candidate and link_review suggestion ids -- both
perform graph merges when confirmed. Use after walking the user through
a batch of obvious merges (e.g. 12 pairs that share an email and have
near-identical names). Suggestions in other categories are skipped,
not errored.
WARNING: destructive. Confirm the full list with the user first.
Annotation: _DESTRUCTIVE · Returns: str · Source: server.py:4648
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
suggestion_ids | list[str] | required | - | List of sug_* ids to confirm as merges. |
confirm_suggestion review
Confirm a Graphory suggestion and apply its write-back action.
For merge_candidate and link_review suggestions this merges the two
nodes in the graph (edges redirected, loser deleted). For other
categories it marks the suggestion accepted without a graph write -
follow up with write_node / write_edge if a graph change is needed.
WARNING: merge confirmations (merge_candidate, link_review) are
destructive. Always confirm the merge with the user before calling
this tool.
Annotation: _DESTRUCTIVE · Returns: str · Source: server.py:4602
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
suggestion_id | str | required | - | The sug_* id from get_suggestions. |
reason | str | optional | '' | Optional note captured in the audit log. |
get_suggestions review
List pending improvement suggestions Graphory has found for this org.
Graphory's nightly pipeline flags candidates it is not confident enough
to auto-apply: likely merges, link-review pairs (multi-signal matches
below auto-merge threshold), uncertain relationships, incomplete contacts,
unconnected entities. Use this to surface them to the user and walk them
through confirm / reject.
Annotation: _READ_ONLY · Returns: str · Source: server.py:4548
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
category | str | optional | '' | Optional filter. One of: merge_candidate, link_review, uncertain_relationship, incomplete_contact, unconnected_entity. |
status | str | optional | 'pending' | pending (default), confirmed, rejected, or superseded. |
limit | int | optional | 50 | Max suggestions to return (default 50, max 500). |
reject_suggestion review
Reject a Graphory suggestion. Does not touch the graph.
The rejection reason is persisted inside the suggestion payload so the
pipeline can learn from it (e.g., reduce priority on similar candidates).
Annotation: _IDEMPOTENT_WRITE · Returns: str · Source: server.py:4626
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
suggestion_id | str | required | - | The sug_* id from get_suggestions. |
reason | str | optional | '' | Why the user rejected this suggestion. |
get_job_status- Check the progress of an async job (sync, bulk import, collection trigger).
Example Conversations
Searching with query expansion
What do we have on pricing at Acme Corp?
search_graph(query="pricing", entity="acme-corp", expanded_terms="payment,subscription,invoice,billing,plan,quote"). The expanded terms broaden the full-text match to cover related concepts without any vector search.
Walking a duplicate-merge queue
Clean up my contact duplicates.
get_suggestions(category="merge_candidate"), presents each pair for confirmation, then uses batch_merge_suggestions for obvious merges and reject_suggestion for false positives.
Connecting a new source via AI
I want to connect my Slack workspace to Graphory.
connect_source with app "slack", which routes it to the web app for browser OAuth. After you authorize, it calls list_connections to verify and then configure_collection to set what to pull.
Checking account health
Is everything running smoothly? Any issues with my connections?
connection_health to check all connections, setup_status for the full account dashboard, and reports any errors, stale syncs, or approaching plan limits.
Ingesting then forcing a sync
Push these 40 meeting notes into the graph and make them queryable right now.
ingest with the batch, then sync_graph(wait=true) to run extraction immediately instead of waiting for the nightly pipeline.
Writing back insights
Based on those emails, Jane Smith just moved from Acme to Beta Inc. Update the graph.
write_to_graph with action="correct" to update Jane's Organization association. Because the correction came from the user, it carries user-level authority and outranks any conflicting machine-generated edge.
Tips for Best Results
- Expand queries yourself. Pass
expanded_termstosearch_graphwith synonyms and related terms. Your AI has the context an embedding cannot. - Name the business entity when your org has multiple. "What's happening at Acme Corp?" scopes the search to that entity.
- Ask your AI to explore. "Who else is connected to this deal?" triggers traversals that reveal hidden relationships.
- Let it write back. When you discover something new in conversation, ask to save it. User corrections outrank machine extractions.
- Start with stats. Ask "What does my graph look like?" to orient before diving into specific queries.
- Use the suggestion queue.
get_suggestionssurfaces the work the pipeline was not confident enough to apply on its own. - Chain operations. "Connect Gmail, set it to hourly, and pull the last 90 days" works as one request.
Troubleshooting
Tools not appearing
- Fully restart your AI client (not just close the window).
- Check that the config JSON is valid; a missing comma or bracket will break it.
- Verify your API key is correct and has not been revoked.
Authentication errors
- Confirm the key starts with
gs_ak_. - Make sure there are no extra spaces in the Authorization header value.
- Try generating a new key from the Settings page.
Slow responses
- Large graphs may take a few seconds for deep traversals.
- Use filters (
node_type,entity) to narrow search scope. - Reduce
depthon traverse operations if results are too broad.