@unified-product-graph/cloud-server 0.6.0

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.
@@ -0,0 +1,14 @@
1
+ FROM node:22-slim AS builder
2
+ WORKDIR /app
3
+ COPY package.json tsconfig.json ./
4
+ RUN npm install
5
+ COPY src/ ./src/
6
+ RUN npx tsup
7
+
8
+ FROM node:22-slim
9
+ WORKDIR /app
10
+ COPY --from=builder /app/dist ./dist
11
+ COPY --from=builder /app/node_modules ./node_modules
12
+ COPY package.json ./
13
+ COPY migrations/ ./migrations/
14
+ ENTRYPOINT ["node", "dist/index.js"]
@@ -0,0 +1,33 @@
1
+ version: '3.8'
2
+
3
+ services:
4
+ postgres:
5
+ image: postgres:16-alpine
6
+ environment:
7
+ POSTGRES_DB: upg
8
+ POSTGRES_USER: upg
9
+ POSTGRES_PASSWORD: upg
10
+ ports:
11
+ - "5433:5432"
12
+ volumes:
13
+ - pgdata:/var/lib/postgresql/data
14
+ - ../migrations:/docker-entrypoint-initdb.d
15
+ healthcheck:
16
+ test: ["CMD-SHELL", "pg_isready -U upg"]
17
+ interval: 5s
18
+ timeout: 5s
19
+ retries: 5
20
+
21
+ upg-cloud:
22
+ build:
23
+ context: ..
24
+ dockerfile: docker/Dockerfile
25
+ depends_on:
26
+ postgres:
27
+ condition: service_healthy
28
+ environment:
29
+ UPG_DATABASE_URL: postgres://upg:upg@postgres:5432/upg
30
+ stdin_open: true
31
+
32
+ volumes:
33
+ pgdata:
@@ -0,0 +1,70 @@
1
+ -- UPG Cloud Server: Initial Schema
2
+ -- Run this against your Postgres database before starting the server.
3
+
4
+ CREATE SCHEMA IF NOT EXISTS upg;
5
+
6
+ -- Products
7
+ CREATE TABLE upg.products (
8
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
9
+ title TEXT NOT NULL,
10
+ description TEXT,
11
+ stage TEXT DEFAULT 'idea' CHECK (stage IN ('idea', 'mvp', 'growth', 'scale')),
12
+ metadata JSONB DEFAULT '{}',
13
+ created_at TIMESTAMPTZ DEFAULT now(),
14
+ updated_at TIMESTAMPTZ DEFAULT now()
15
+ );
16
+
17
+ -- Nodes (entities)
18
+ CREATE TABLE upg.nodes (
19
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
20
+ product_id UUID NOT NULL REFERENCES upg.products(id) ON DELETE CASCADE,
21
+ type TEXT NOT NULL,
22
+ title TEXT NOT NULL,
23
+ description TEXT,
24
+ status TEXT DEFAULT 'active',
25
+ tags TEXT[] DEFAULT '{}',
26
+ data JSONB DEFAULT '{}',
27
+ created_at TIMESTAMPTZ DEFAULT now(),
28
+ updated_at TIMESTAMPTZ DEFAULT now()
29
+ );
30
+
31
+ -- Edges (relationships)
32
+ CREATE TABLE upg.edges (
33
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
34
+ product_id UUID NOT NULL REFERENCES upg.products(id) ON DELETE CASCADE,
35
+ source UUID NOT NULL REFERENCES upg.nodes(id) ON DELETE CASCADE,
36
+ target UUID NOT NULL REFERENCES upg.nodes(id) ON DELETE CASCADE,
37
+ type TEXT NOT NULL,
38
+ metadata JSONB DEFAULT '{}',
39
+ created_at TIMESTAMPTZ DEFAULT now()
40
+ );
41
+
42
+ -- Full-text search on nodes
43
+ CREATE INDEX idx_nodes_fts ON upg.nodes USING gin(
44
+ to_tsvector('english', coalesce(title, '') || ' ' || coalesce(description, ''))
45
+ );
46
+
47
+ -- Performance indexes
48
+ CREATE INDEX idx_nodes_product ON upg.nodes(product_id);
49
+ CREATE INDEX idx_nodes_type ON upg.nodes(product_id, type);
50
+ CREATE INDEX idx_edges_product ON upg.edges(product_id);
51
+ CREATE INDEX idx_edges_source ON upg.edges(source);
52
+ CREATE INDEX idx_edges_target ON upg.edges(target);
53
+ CREATE INDEX idx_edges_type ON upg.edges(product_id, type);
54
+
55
+ -- Updated_at trigger
56
+ CREATE OR REPLACE FUNCTION upg.update_updated_at()
57
+ RETURNS TRIGGER AS $$
58
+ BEGIN
59
+ NEW.updated_at = now();
60
+ RETURN NEW;
61
+ END;
62
+ $$ LANGUAGE plpgsql;
63
+
64
+ CREATE TRIGGER trg_products_updated_at
65
+ BEFORE UPDATE ON upg.products
66
+ FOR EACH ROW EXECUTE FUNCTION upg.update_updated_at();
67
+
68
+ CREATE TRIGGER trg_nodes_updated_at
69
+ BEFORE UPDATE ON upg.nodes
70
+ FOR EACH ROW EXECUTE FUNCTION upg.update_updated_at();
@@ -0,0 +1,41 @@
1
+ -- Collaboration primitives: audit log, comments, access control
2
+
3
+ -- Audit log: who changed what, when
4
+ CREATE TABLE upg.audit_log (
5
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
6
+ product_id UUID NOT NULL REFERENCES upg.products(id) ON DELETE CASCADE,
7
+ user_id TEXT,
8
+ action TEXT NOT NULL CHECK (action IN ('create', 'update', 'delete')),
9
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('node', 'edge', 'product')),
10
+ entity_id UUID NOT NULL,
11
+ changes JSONB,
12
+ created_at TIMESTAMPTZ DEFAULT now()
13
+ );
14
+
15
+ CREATE INDEX idx_audit_log_product ON upg.audit_log(product_id, created_at DESC);
16
+ CREATE INDEX idx_audit_log_entity ON upg.audit_log(entity_id);
17
+
18
+ -- Comments on entities
19
+ CREATE TABLE upg.comments (
20
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
21
+ product_id UUID NOT NULL REFERENCES upg.products(id) ON DELETE CASCADE,
22
+ node_id UUID REFERENCES upg.nodes(id) ON DELETE CASCADE,
23
+ user_id TEXT NOT NULL,
24
+ body TEXT NOT NULL,
25
+ created_at TIMESTAMPTZ DEFAULT now()
26
+ );
27
+
28
+ CREATE INDEX idx_comments_node ON upg.comments(node_id, created_at DESC);
29
+
30
+ -- Access control
31
+ CREATE TABLE upg.access (
32
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
33
+ product_id UUID NOT NULL REFERENCES upg.products(id) ON DELETE CASCADE,
34
+ user_id TEXT NOT NULL,
35
+ role TEXT NOT NULL CHECK (role IN ('owner', 'editor', 'viewer')),
36
+ created_at TIMESTAMPTZ DEFAULT now(),
37
+ UNIQUE(product_id, user_id)
38
+ );
39
+
40
+ CREATE INDEX idx_access_product ON upg.access(product_id);
41
+ CREATE INDEX idx_access_user ON upg.access(user_id);
@@ -0,0 +1,14 @@
1
+ -- Webhook event system
2
+
3
+ CREATE TABLE upg.webhooks (
4
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
5
+ product_id UUID NOT NULL REFERENCES upg.products(id) ON DELETE CASCADE,
6
+ event TEXT NOT NULL,
7
+ url TEXT NOT NULL,
8
+ secret TEXT,
9
+ active BOOLEAN DEFAULT true,
10
+ created_at TIMESTAMPTZ DEFAULT now()
11
+ );
12
+
13
+ CREATE INDEX idx_webhooks_product ON upg.webhooks(product_id);
14
+ CREATE INDEX idx_webhooks_event ON upg.webhooks(product_id, event);
@@ -0,0 +1,22 @@
1
+ -- Cross-product edges: portfolio-level relationships between products.
2
+ -- Supports the portfolio family of tools and `repair_dangling_edges`.
3
+ --
4
+ -- In local .upg files, cross-product edges live in portfolio.cross_edges[].
5
+ -- Cloud needs a dedicated table because products are rows, not files.
6
+ -- IDs are TEXT (nanoid-prefixed, matching the cloud server's id minting pattern)
7
+ -- rather than UUID, matching how the MCP server generates IDs at the tool layer.
8
+
9
+ CREATE TABLE IF NOT EXISTS upg.cross_product_edges (
10
+ id TEXT PRIMARY KEY,
11
+ source TEXT NOT NULL, -- qualified: "{product_id}/{node_id}"
12
+ target TEXT NOT NULL, -- qualified: "{product_id}/{node_id}"
13
+ type TEXT NOT NULL, -- must be in UPG_CROSS_EDGE_TYPES
14
+ created_by_product_id TEXT NOT NULL, -- which product "owns" this cross-edge
15
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
16
+ );
17
+
18
+ CREATE INDEX IF NOT EXISTS idx_cross_product_edges_owner
19
+ ON upg.cross_product_edges(created_by_product_id);
20
+
21
+ CREATE INDEX IF NOT EXISTS idx_cross_product_edges_type
22
+ ON upg.cross_product_edges(type);
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@unified-product-graph/cloud-server",
3
+ "version": "0.6.0",
4
+ "description": "Postgres-backed MCP server for the Unified Product Graph \u2014 self-hostable, open source",
5
+ "license": "MIT",
6
+ "author": "The Product Creator <hello@theproductcreator.com>",
7
+ "homepage": "https://unifiedproductgraph.org",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/unified-product-graph/cloud-server.git"
11
+ },
12
+ "type": "module",
13
+ "bin": {
14
+ "upg-cloud-server": "dist/index.js"
15
+ },
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "files": [
19
+ "dist",
20
+ "migrations",
21
+ "docker",
22
+ "CHANGELOG.md",
23
+ "SELF-HOSTING.md"
24
+ ],
25
+ "scripts": {
26
+ "prebuild": "npm run build --workspace=@unified-product-graph/mcp-tooling 2>/dev/null || true",
27
+ "pregenerate-tools": "npm run build --workspace=@unified-product-graph/mcp-tooling 2>/dev/null || true",
28
+ "build": "tsup",
29
+ "postbuild": "npm run generate-tools",
30
+ "dev": "tsx src/index.ts",
31
+ "test": "vitest run",
32
+ "test:watch": "vitest",
33
+ "test:integration": "vitest run --config vitest.integration.config.ts",
34
+ "generate-tools": "tsx scripts/generate-tool-reference.ts",
35
+ "generate-tools:check": "tsx scripts/generate-tool-reference.ts --check",
36
+ "type-check": "tsc --noEmit",
37
+ "lint": "eslint src/",
38
+ "prepublishOnly": "npm run build"
39
+ },
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.27.0",
42
+ "@unified-product-graph/core": "*",
43
+ "@unified-product-graph/sdk": "*",
44
+ "nanoid": "^5.1.0",
45
+ "pg": "^8.13.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/pg": "^8.11.0",
49
+ "tsup": "^8.0.0",
50
+ "tsx": "^4.0.0",
51
+ "typescript": "^5.8.0",
52
+ "typescript-eslint": "^8.0.0",
53
+ "eslint": "^9.0.0",
54
+ "@unified-product-graph/mcp-tooling": "*"
55
+ },
56
+ "engines": {
57
+ "node": ">=20"
58
+ },
59
+ "publishConfig": {
60
+ "access": "public"
61
+ },
62
+ "bugs": {
63
+ "url": "https://github.com/unified-product-graph/cloud-server/issues"
64
+ },
65
+ "keywords": [
66
+ "mcp",
67
+ "upg",
68
+ "unified-product-graph",
69
+ "product-management",
70
+ "model-context-protocol",
71
+ "knowledge-graph",
72
+ "postgres",
73
+ "self-hosted"
74
+ ]
75
+ }