@unified-product-graph/cloud-server 0.7.1 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -1
- package/SELF-HOSTING.md +19 -19
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/tools-manifest.json +2 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,7 @@ This package co-versions with `@unified-product-graph/core` and `@unified-produc
|
|
|
6
6
|
|
|
7
7
|
## 0.6.0 · 2026-05-22 · Launch train alignment
|
|
8
8
|
|
|
9
|
-
Aligned with `@unified-product-graph/core@0.6.0`. Bumped `@unified-product-graph/core` dep to `^0.6.0`. No cloud-server surface changes
|
|
9
|
+
Aligned with `@unified-product-graph/core@0.6.0`. Bumped `@unified-product-graph/core` dep to `^0.6.0`. No cloud-server surface changes; co-versioned for the launch train.
|
|
10
10
|
|
|
11
11
|
## 0.5.0 · 2026-05-19 · Inaugural public release
|
|
12
12
|
|
package/SELF-HOSTING.md
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
The cloud server is a **Postgres-backed MCP server**. Unlike the local server
|
|
4
4
|
(`@unified-product-graph/mcp-server`), which reads and writes a single `.upg`
|
|
5
|
-
file, the cloud server stores the graph in Postgres
|
|
5
|
+
file, the cloud server stores the graph in Postgres, so a team can share one
|
|
6
6
|
source of truth with concurrent, transactional writes, multiple products, an
|
|
7
7
|
audit log, comments, and webhooks.
|
|
8
8
|
|
|
9
9
|
> **Mental model:** the local server is to Git as the cloud server is to a
|
|
10
|
-
> shared database. Same tools, same UPG semantics
|
|
10
|
+
> shared database. Same tools, same UPG semantics; different substrate.
|
|
11
11
|
|
|
12
12
|
This guide covers three deployment tiers, from "ready today" to "real SaaS."
|
|
13
13
|
|
|
14
14
|
| Tier | What you host | Auth model | Effort | Use when |
|
|
15
15
|
|------|---------------|-----------|--------|----------|
|
|
16
|
-
| **1
|
|
17
|
-
| **2
|
|
18
|
-
| **3
|
|
16
|
+
| **Tier 1: Shared DB** | Just Postgres; each client runs the server locally | Connection string = full access | Minutes | A small, trusting team |
|
|
17
|
+
| **Tier 2: Central endpoint** | The server behind a stdio→HTTP bridge + auth proxy | One shared identity, gated by a proxy | A day of ops | One shared endpoint, still one trust domain |
|
|
18
|
+
| **Tier 3: Multi-tenant SaaS** | The server with auth + per-user identity + RLS | Per-user, per-product RBAC | A real build | External / untrusted users |
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
@@ -23,9 +23,9 @@ This guide covers three deployment tiers, from "ready today" to "real SaaS."
|
|
|
23
23
|
|
|
24
24
|
- **Transport is stdio.** The server speaks MCP over stdin/stdout to a single
|
|
25
25
|
trusted client (Claude Code, Cursor, etc.). There is **no built-in network
|
|
26
|
-
endpoint**
|
|
26
|
+
endpoint** (Tiers 2 and 3 add one).
|
|
27
27
|
- **One config knob:** `UPG_DATABASE_URL` (or `--database-url`). The server
|
|
28
|
-
opens a default `pg.Pool` against it and runs `SELECT 1` at startup
|
|
28
|
+
opens a default `pg.Pool` against it and runs `SELECT 1` at startup; **it
|
|
29
29
|
exits immediately if the database is unreachable.**
|
|
30
30
|
- **Postgres 13+** is required (the schema uses `gen_random_uuid()`).
|
|
31
31
|
- **RBAC is recorded, not yet enforced.** The schema has an `access` table
|
|
@@ -36,14 +36,14 @@ This guide covers three deployment tiers, from "ready today" to "real SaaS."
|
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
39
|
-
## Tier 1
|
|
39
|
+
## Tier 1: Shared managed Postgres (recommended starting point)
|
|
40
40
|
|
|
41
41
|
Host the **database**; every team member runs the server **locally** against it.
|
|
42
42
|
Production-ready today for a team inside one trust boundary.
|
|
43
43
|
|
|
44
44
|
### 1. Provision Postgres
|
|
45
45
|
|
|
46
|
-
Any Postgres 13+ works
|
|
46
|
+
Any Postgres 13+ works: [Neon](https://neon.tech), [Supabase](https://supabase.com),
|
|
47
47
|
[Railway](https://railway.app), RDS, or a VM. Grab the connection string:
|
|
48
48
|
|
|
49
49
|
```
|
|
@@ -66,7 +66,7 @@ The four migrations create the `upg` schema: `products`, `nodes`, `edges`,
|
|
|
66
66
|
### 3. Point each member's client at the shared DB
|
|
67
67
|
|
|
68
68
|
Add an MCP server entry to each member's client config. Keep the connection
|
|
69
|
-
string in **personal / local** config
|
|
69
|
+
string in **personal / local** config, never a committed file.
|
|
70
70
|
|
|
71
71
|
```jsonc
|
|
72
72
|
// If installed from npm (the package ships a `upg-cloud-server` bin):
|
|
@@ -97,7 +97,7 @@ The cloud server is **multi-product**, so every tool is scoped by `product_id`:
|
|
|
97
97
|
2. Pass that `product_id` to `create_node`, `create_edge`, `query`, etc.
|
|
98
98
|
|
|
99
99
|
Concurrent writes from different members land as atomic Postgres transactions
|
|
100
|
-
with foreign-key integrity
|
|
100
|
+
with foreign-key integrity; no `.upg` merge conflicts.
|
|
101
101
|
|
|
102
102
|
### Local Postgres for development
|
|
103
103
|
|
|
@@ -112,12 +112,12 @@ docker compose up -d postgres
|
|
|
112
112
|
|
|
113
113
|
---
|
|
114
114
|
|
|
115
|
-
## Tier 2
|
|
115
|
+
## Tier 2: A single shared endpoint (stdio → HTTP bridge)
|
|
116
116
|
|
|
117
117
|
Centralize the **process** so clients don't run anything locally. Wrap the
|
|
118
118
|
stdio server in an MCP HTTP bridge (e.g. [`supergateway`](https://github.com/supercorp-ai/supergateway)
|
|
119
119
|
or `mcp-proxy`, or swap in the SDK's StreamableHTTP transport) and put an
|
|
120
|
-
**auth proxy in front**
|
|
120
|
+
**auth proxy in front** (the server itself has no authentication).
|
|
121
121
|
|
|
122
122
|
```
|
|
123
123
|
[client] --HTTPS--> [auth proxy] --> [bridge + upg-cloud-server (stdio)] --> [managed Postgres]
|
|
@@ -144,9 +144,9 @@ clients must be isolated from one another.
|
|
|
144
144
|
|
|
145
145
|
---
|
|
146
146
|
|
|
147
|
-
## Tier 3
|
|
147
|
+
## Tier 3: Multi-tenant SaaS (the enforcement tier)
|
|
148
148
|
|
|
149
|
-
The data model is already built for this
|
|
149
|
+
The data model is already built for this: `products`, `access` roles,
|
|
150
150
|
`audit_log`, `webhooks`, `cross_product_edges`. What's missing is the
|
|
151
151
|
**enforcement layer**. Reaching genuine per-user multi-tenancy requires:
|
|
152
152
|
|
|
@@ -155,8 +155,8 @@ The data model is already built for this — `products`, `access` roles,
|
|
|
155
155
|
2. **Identity propagation** into the store, so every query carries the calling
|
|
156
156
|
user (e.g. `SET LOCAL app.user_id = …` per transaction).
|
|
157
157
|
3. **RLS policies** on `upg.*` keyed off that identity and the `access` table,
|
|
158
|
-
so reads/writes are gated at the database
|
|
159
|
-
4. **Operational scale
|
|
158
|
+
so reads/writes are gated at the database, not by convention.
|
|
159
|
+
4. **Operational scale:** stateless server replicas behind a load balancer,
|
|
160
160
|
connection pooling (e.g. PgBouncer; the server currently opens an untuned
|
|
161
161
|
default `pg.Pool`), and the existing webhook/audit machinery wired to fire
|
|
162
162
|
on mutations.
|
|
@@ -164,7 +164,7 @@ The data model is already built for this — `products`, `access` roles,
|
|
|
164
164
|
Until those land, treat the `access` roles as **advisory metadata**, not a
|
|
165
165
|
security boundary.
|
|
166
166
|
|
|
167
|
-
> Tracked as a roadmap item
|
|
167
|
+
> Tracked as a roadmap item; see the UPG issue tracker for "Tier-3 enforcement:
|
|
168
168
|
> HTTP transport + per-user identity + RLS."
|
|
169
169
|
|
|
170
170
|
---
|
|
@@ -181,5 +181,5 @@ security boundary.
|
|
|
181
181
|
|----------|----------|-------------|
|
|
182
182
|
| `UPG_DATABASE_URL` | Yes | Postgres connection string. Also accepted as `--database-url`. |
|
|
183
183
|
|
|
184
|
-
The server is intentionally minimal in configuration
|
|
184
|
+
The server is intentionally minimal in configuration; everything else lives
|
|
185
185
|
in the database.
|
package/dist/index.js
CHANGED
|
@@ -1469,7 +1469,7 @@ var createNode = async (args, { store }) => {
|
|
|
1469
1469
|
});
|
|
1470
1470
|
const warnings = [...lengthWarnings];
|
|
1471
1471
|
if (resolvedType.alias) {
|
|
1472
|
-
warnings.push(`Type "${resolvedType.alias.from}" is deprecated
|
|
1472
|
+
warnings.push(`Type "${resolvedType.alias.from}" is deprecated; using canonical "${resolvedType.alias.to}".`);
|
|
1473
1473
|
}
|
|
1474
1474
|
if (args.status) {
|
|
1475
1475
|
newNode.status = args.status;
|
|
@@ -1495,7 +1495,7 @@ var createNode = async (args, { store }) => {
|
|
|
1495
1495
|
const inference = inferEdgeTypeWithTier(parent.type, nodeType);
|
|
1496
1496
|
if (!inference.ok) {
|
|
1497
1497
|
const suggestion = inference.suggestions.length > 0 ? ` Suggestions: ${inference.suggestions.map((s) => `${s.source_type} \u2192 ${s.target_type} (${s.edge_type})`).join("; ")}.` : "";
|
|
1498
|
-
warnings.push(`Parent edge not created
|
|
1498
|
+
warnings.push(`Parent edge not created; no canonical edge for ${parent.type} \u2192 ${nodeType}.${suggestion}`);
|
|
1499
1499
|
} else {
|
|
1500
1500
|
edge = { id: edgeId(), source: parentId, target: nId, type: inference.edgeType };
|
|
1501
1501
|
await store.addEdge(productId, edge);
|
|
@@ -2764,7 +2764,7 @@ var batchCreateNodes = async (args, { store }) => {
|
|
|
2764
2764
|
const parentType = parentRows[0].type;
|
|
2765
2765
|
const inference = inferEdgeTypeWithTier3(parentType, nodeType);
|
|
2766
2766
|
if (!inference.ok) {
|
|
2767
|
-
warnings.push(`Node "${newId}": parent edge not created
|
|
2767
|
+
warnings.push(`Node "${newId}": parent edge not created; no canonical edge for ${parentType} \u2192 ${nodeType}.`);
|
|
2768
2768
|
} else {
|
|
2769
2769
|
const eid = edgeId();
|
|
2770
2770
|
await client.query(
|