SDK · Compare
Three ways to read and write a UPG product graph. Same operations, different surfaces. Pick the right one for the job, or use all three side by side.
At a glance
@unified-product-graph/mcp-server
For
Product makers · AI agents
Surface
Natural language via Claude Code, Cursor, or any MCP client
When to reach
You think in product terms, not code. You want AI to do the work.
claude mcp add upg ...
SDK@unified-product-graph/sdk
For
Engineers · tool builders
Surface
TypeScript: UPGClient with namespaced .nodes / .edges
When to reach
You're writing code that reads or writes graphs programmatically.
npm install @unified-product-graph/sdk
CLI@unified-product-graph/cli
For
Anyone in a terminal · CI pipelines
Surface
Shell commands: upg <command> [args]
When to reach
One-off operations · shell scripts · CI · scaffolding new files.
npm install -g @unified-product-graph/cli
Same operation, three surfaces
Six operations in the order most consumers will reach for them. Each row shows the same task expressed through MCP, the SDK, and the CLI.
Create a new .upg file for a product.
You: "Start a new product graph called 'My Product'."
Claude: I'll create that for you.
→ create_product { title: "My Product" }
Created ./product.upg.underlying MCP tool: create_product
// The SDK does not auto-create the file yet.
// Use the CLI for the initial scaffold, then UPGClient takes over.
import { UPGClient } from '@unified-product-graph/sdk'
const upg = new UPGClient({ file: './product.upg' })$ upg init --title "My Product" --single
✓ Created product.upg
✓ Added workspace.json (run `upg products` to list)Add a typed entity: feature, persona, job, hypothesis, anything.
You: "Add a feature called 'Dark mode'."
Claude: Done.
→ create_node { type: "feature", title: "Dark mode" }
Created n_Kg19aubWwbwm7V5t.underlying MCP tool: create_node
const { node } = await upg.nodes.create({
type: 'feature',
title: 'Dark mode',
})
console.log(node.id)
// n_Kg19aubWwbwm7V5treturns { node, edge? }. Destructure.
$ upg create feature "Dark mode" --file ./product.upg
✓ Created n_Kg19aubWwbwm7V5t (feature)Page through every node of a given type.
You: "List all features."
Claude:
→ list_nodes { type: "feature" }
3 features:
• Dark mode
• Keyboard shortcuts
• Offline modeunderlying MCP tool: list_nodes
const { nodes, total } = await upg.nodes.list({ type: 'feature' })
console.log(`${total} features`)
for (const f of nodes) console.log(f.title)returns { nodes, total }, not a bare array
$ upg list --type feature --file ./product.upg
3 features
n_Kg19aubWwbwm7V5t Dark mode
n_8FzqJpHcXr2BvW1n Keyboard shortcuts
n_LbVm3sKdYpQwR4Tt Offline modeCreate a typed edge between two existing nodes. Edge type inferred from endpoints.
You: "Connect the persona 'Busy Parent' to the job
'Find ten minutes to work out'."
Claude:
→ create_edge {
source: "n_2Hf…", target: "n_4Mq…"
}
Created edge: persona_pursues_job.underlying MCP tool: create_edge
const edge = await upg.edges.connect(persona.id, job.id)
console.log(edge.type)
// persona_pursues_job (inferred)$ upg connect n_2HfGtKqMpLcVbXwR n_4MqRtYpKvBnZxW8j \
--file ./product.upg
✓ Connected: persona_pursues_job
n_2HfGtKqMpLcVbXwR → n_4MqRtYpKvBnZxW8jFuzzy match across every node title + description.
You: "Find anything about dark mode."
Claude:
→ search_nodes { query: "dark mode" }
Top hits:
0.92 · feature · Dark mode
0.71 · opportunity · Dark mode opportunity in mobile
0.43 · hypothesis · Users prefer dark UI at nightunderlying MCP tool: search_nodes
const hits = await upg.search('dark mode', { limit: 5 })
for (const h of hits) {
console.log(h.score.toFixed(2), h.node.type, h.node.title)
}$ upg search "dark mode" --file ./product.upg
0.92 feature Dark mode
0.71 opportunity Dark mode opportunity in mobile
0.43 hypothesis Users prefer dark UI at nightScore the graph (0–10) plus a structural digest. Wire into CI as a quality gate.
You: "How healthy is the graph?"
Claude:
→ get_graph_digest {}
Health: 8/10
142 nodes · 311 edges · 0 orphans.underlying MCP tool: get_graph_digest
const { score, digest } = await upg.health()
console.log(score, digest.orphans)
// 8 0$ upg health --file ./product.upg --min-score 7
Graph health: 8/10
{ nodes: 142, edges: 311, orphans: 0, ... }
# Exits 1 if score < 7 — wire into CI.How to choose
If youwant AI to maintain the graph for you while you discuss product
Conversational mutations. Claude picks the right tool calls; you stay in product language.
If youare building an app, adapter, or pipeline that reads/writes .upg
Typed API, async-everywhere, real return values you can branch on.
If youneed a one-off operation, a shell script, or a CI gate
Zero TypeScript boilerplate. Exits with a status code you can pipe and gate on.
If youare wiring an LLM agent (Claude Agent SDK, OpenAI Tools)
Wrap UPGClient methods as agent tools. Cookbook recipe wire-into-claude-agent-sdk shows the loop.
If youwant to import from Notion / Linear / Markdown
upg import handles the canonical adapters. The SDK is there if you need a custom adapter.
Read deeper