@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.
- package/CHANGELOG.md +40 -0
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/SELF-HOSTING.md +185 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5094 -0
- package/dist/index.js.map +1 -0
- package/dist/tools-manifest.json +3432 -0
- package/docker/Dockerfile +14 -0
- package/docker/docker-compose.yml +33 -0
- package/migrations/001_initial.sql +70 -0
- package/migrations/002_collaboration.sql +41 -0
- package/migrations/003_webhooks.sql +14 -0
- package/migrations/004_cross_product_edges.sql +22 -0
- package/package.json +75 -0
|
@@ -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
|
+
}
|