@unified-product-graph/mcp-server 0.8.14 → 0.8.16

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/README.md CHANGED
@@ -21,7 +21,7 @@ For Claude Code, add to `.mcp.json`:
21
21
  ```json
22
22
  {
23
23
  "mcpServers": {
24
- "upg": {
24
+ "unified-product-graph": {
25
25
  "command": "npx",
26
26
  "args": ["upg-mcp-server"]
27
27
  }
@@ -34,7 +34,7 @@ The server auto-discovers `.upg` files in the current directory. To point at a s
34
34
  ```json
35
35
  {
36
36
  "mcpServers": {
37
- "upg": {
37
+ "unified-product-graph": {
38
38
  "command": "npx",
39
39
  "args": ["upg-mcp-server", "--file", "path/to/product.upg"]
40
40
  }
package/TOOLS.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # UPG MCP Server: Tool Reference
2
2
 
3
- Reference for the 96 tools exposed by `@unified-product-graph/mcp-server`. Generated from JSDoc on `src/tools/*.ts` (do not edit by hand).
3
+ Reference for the 106 tools exposed by `@unified-product-graph/mcp-server`. Generated from JSDoc on `src/tools/*.ts` (do not edit by hand).
4
4
 
5
5
  ## Contents
6
6
 
7
7
  - [Context & Session](#context-session): 5 tools
8
8
  - [Nodes](#nodes): 15 tools
9
9
  - [Edges](#edges): 9 tools
10
- - [Areas & Change Log](#areas-change-log): 5 tools
11
- - [Workspace & Portfolios](#workspace-portfolios): 10 tools
10
+ - [Areas & Change Log](#areas-change-log): 10 tools
11
+ - [Workspace & Portfolios](#workspace-portfolios): 15 tools
12
12
  - [Schema](#schema): 1 tool
13
13
  - [Spec Introspection](#spec-introspection): 45 tools
14
14
  - [Cloud Sync](#cloud-sync): 3 tools
@@ -946,11 +946,42 @@ re-computed on next save; a subsequent reload won't bring them back.
946
946
 
947
947
  _Product areas, the `.upg-area.json` cwd scoper, and the session change log._
948
948
 
949
+ - [`assign_product_to_area`](#assign-product-to-area)
949
950
  - [`create_area`](#create-area)
951
+ - [`delete_area`](#delete-area)
950
952
  - [`get_area_context`](#get-area-context)
951
953
  - [`get_area_graph`](#get-area-graph)
952
954
  - [`get_changes`](#get-changes)
953
955
  - [`list_product_areas`](#list-product-areas)
956
+ - [`move_product_to_area`](#move-product-to-area)
957
+ - [`remove_product_from_area`](#remove-product-from-area)
958
+ - [`update_area`](#update-area)
959
+
960
+ ### `assign_product_to_area`
961
+
962
+ Place an existing product under a product area (adds it to the area's `products[]` in `.upg/portfolio.upg`). Resolves the area against the portfolio document and auto-registers the product on the portfolio registry. Use after `create_product`, or pass `area_id` to `create_product` directly.
963
+
964
+ **Atomicity:** `atomic (single portfolio.upg flush).`
965
+
966
+ **Arguments:**
967
+
968
+ | Name | Type | Required | Description |
969
+ | ---- | ---- | -------- | ----------- |
970
+ | `area_id` | string | ✓ | Product area id (from list_product_areas) |
971
+ | `product_id` | string | ✓ | Product id (from create_product / list_local_products) |
972
+
973
+ **Returns:**
974
+
975
+ JSON: `{ product_id, container_id, container_kind: "product_area",
976
+ container_title?, already_member, registered }`.
977
+
978
+ **Throws:**
979
+
980
+ - textError on a missing workspace, an unknown product, or an unknown
981
+ area id (the message points at list_product_areas / list_local_products).
982
+
983
+ **See also:** `attach_product_to_portfolio`, `create_product`
984
+
954
985
 
955
986
  ### `create_area`
956
987
 
@@ -966,7 +997,7 @@ flushed in one pass.`
966
997
  | `description` | string | | What this area covers |
967
998
  | `owner` | string | | Person or team that owns this area |
968
999
  | `parent_area_id` | string | | Parent area ID for creating a sub-area |
969
- | `strategic_priority` | `critical` \| `high` \| `medium` \| `low` | | Strategic priority of this area |
1000
+ | `strategic_priority` | `urgent` \| `high` \| `medium` \| `low` \| `none` | | Strategic priority of this area (canonical Priority scale) |
970
1001
  | `title` | string | ✓ | Area name (e.g. "Search", "Payments") |
971
1002
 
972
1003
  **Returns:**
@@ -982,6 +1013,31 @@ fails.
982
1013
  **See also:** `list_product_areas`
983
1014
 
984
1015
 
1016
+ ### `delete_area`
1017
+
1018
+ Delete a product area from `.upg/portfolio.upg`. Guarded: refuses while the area still has products unless `force: true`. Child areas are un-nested (their parent link is cleared) so no parent reference dangles.
1019
+
1020
+ **Atomicity:** `atomic (single portfolio.upg flush).`
1021
+
1022
+ **Arguments:**
1023
+
1024
+ | Name | Type | Required | Description |
1025
+ | ---- | ---- | -------- | ----------- |
1026
+ | `area_id` | string | ✓ | Product area id to delete (from list_product_areas) |
1027
+ | `force` | boolean | | Delete even if the area still has products (default false) |
1028
+
1029
+ **Returns:**
1030
+
1031
+ JSON: `{ message, area_id, deleted, unnested_children: string[] }`.
1032
+
1033
+ **Throws:**
1034
+
1035
+ - textError on a missing workspace, unknown area, or a non-empty area without
1036
+ `force`.
1037
+
1038
+ **See also:** `create_area`, `remove_product_from_area`
1039
+
1040
+
985
1041
  ### `get_area_context`
986
1042
 
987
1043
  Check whether the current working directory has a `.upg-area.json` that scopes work to a specific product area.
@@ -1066,12 +1122,96 @@ parent_area_id?, products? }>, total }`.
1066
1122
  **See also:** `create_area`, `get_area_graph`
1067
1123
 
1068
1124
 
1125
+ ### `move_product_to_area`
1126
+
1127
+ Move a product to a different product area: remove it from `from_area_id` (or, when omitted, from every area it currently sits in) and add it to `to_area_id`. Convenience over remove_product_from_area + assign_product_to_area.
1128
+
1129
+ **Atomicity:** `atomic (single portfolio.upg flush).`
1130
+
1131
+ **Arguments:**
1132
+
1133
+ | Name | Type | Required | Description |
1134
+ | ---- | ---- | -------- | ----------- |
1135
+ | `from_area_id` | string | | Source area id to remove from; omit to remove from all areas |
1136
+ | `product_id` | string | ✓ | Product id (from list_local_products) |
1137
+ | `to_area_id` | string | ✓ | Destination product area id (from list_product_areas) |
1138
+
1139
+ **Returns:**
1140
+
1141
+ JSON: `{ product_id, to_area_id, to_area_title?, removed_from: string[], added }`.
1142
+
1143
+ **Throws:**
1144
+
1145
+ - textError on a missing workspace, unknown product, or unknown target area.
1146
+
1147
+ **See also:** `assign_product_to_area`, `remove_product_from_area`
1148
+
1149
+
1150
+ ### `remove_product_from_area`
1151
+
1152
+ Remove a product from a product area's `products[]` in `.upg/portfolio.upg` (the product stays registered on the portfolio and in any other container). The inverse of `assign_product_to_area`.
1153
+
1154
+ **Atomicity:** `atomic (single portfolio.upg flush).`
1155
+
1156
+ **Arguments:**
1157
+
1158
+ | Name | Type | Required | Description |
1159
+ | ---- | ---- | -------- | ----------- |
1160
+ | `area_id` | string | ✓ | Product area id (from list_product_areas) |
1161
+ | `product_id` | string | ✓ | Product id (from list_local_products) |
1162
+
1163
+ **Returns:**
1164
+
1165
+ JSON: `{ product_id, container_id, container_kind: "product_area",
1166
+ container_title?, removed }`. `removed: false` (not an error) when the product
1167
+ was not a member, so retries are idempotent.
1168
+
1169
+ **Throws:**
1170
+
1171
+ - textError on a missing workspace or an unknown area id.
1172
+
1173
+ **See also:** `assign_product_to_area`, `move_product_to_area`
1174
+
1175
+
1176
+ ### `update_area`
1177
+
1178
+ Edit a product area in `.upg/portfolio.upg` (title, description, strategic_priority, owner) and/or re-parent it via `parent_area_id`. The mirror of `update_product` for the organisational axis. `parent_area_id` is tri-state: omit to leave unchanged, pass null to un-nest (top-level), or pass an area id to re-parent (rejected if it would create a cycle).
1179
+
1180
+ **Atomicity:** `atomic (single portfolio.upg flush).`
1181
+
1182
+ **Arguments:**
1183
+
1184
+ | Name | Type | Required | Description |
1185
+ | ---- | ---- | -------- | ----------- |
1186
+ | `area_id` | string | ✓ | Product area id to edit (from list_product_areas) |
1187
+ | `description` | string | | New area description |
1188
+ | `owner` | string | | Person or team that owns this area |
1189
+ | `parent_area_id` | string,null | | Re-parent under this area id; null un-nests (top-level); omit to leave unchanged |
1190
+ | `strategic_priority` | `urgent` \| `high` \| `medium` \| `low` \| `none` | | Strategic priority (canonical Priority scale) |
1191
+ | `title` | string | | New area title |
1192
+
1193
+ **Returns:**
1194
+
1195
+ JSON: `{ message, area, updated: string[] }`.
1196
+
1197
+ **Throws:**
1198
+
1199
+ - textError on a missing workspace, unknown area/parent, a re-parent cycle, or
1200
+ when no editable field is supplied.
1201
+
1202
+ **See also:** `create_area`, `list_product_areas`
1203
+
1204
+
1069
1205
  ## Workspace & Portfolios
1070
1206
 
1071
1207
  _Multi-product discovery, switching, init, cross-product edges._
1072
1208
 
1209
+ - [`attach_product_to_portfolio`](#attach-product-to-portfolio)
1210
+ - [`batch_create_cross_product_edges`](#batch-create-cross-product-edges)
1073
1211
  - [`create_cross_product_edge`](#create-cross-product-edge)
1074
1212
  - [`create_product`](#create-product)
1213
+ - [`delete_cross_product_edge`](#delete-cross-product-edge)
1214
+ - [`detach_product_from_portfolio`](#detach-product-from-portfolio)
1075
1215
  - [`get_organization`](#get-organization)
1076
1216
  - [`get_workspace_info`](#get-workspace-info)
1077
1217
  - [`init_workspace`](#init-workspace)
@@ -1080,10 +1220,63 @@ _Multi-product discovery, switching, init, cross-product edges._
1080
1220
  - [`list_portfolios`](#list-portfolios)
1081
1221
  - [`migrate_cross_edges`](#migrate-cross-edges)
1082
1222
  - [`switch_product`](#switch-product)
1223
+ - [`update_product`](#update-product)
1224
+
1225
+ ### `attach_product_to_portfolio`
1226
+
1227
+ Place an existing product under a portfolio (adds it to the portfolio's `products[]` in `.upg/portfolio.upg`). Resolves the portfolio against the portfolio document and auto-registers the product on the portfolio registry. Use after `create_product`, or pass `portfolio_id` to `create_product` directly.
1228
+
1229
+ **Atomicity:** `atomic (single portfolio.upg flush).`
1230
+
1231
+ **Arguments:**
1232
+
1233
+ | Name | Type | Required | Description |
1234
+ | ---- | ---- | -------- | ----------- |
1235
+ | `portfolio_id` | string | ✓ | Portfolio id (from list_portfolios) |
1236
+ | `product_id` | string | ✓ | Product id (from create_product / list_local_products) |
1237
+
1238
+ **Returns:**
1239
+
1240
+ JSON: `{ product_id, container_id, container_kind: "portfolio",
1241
+ container_title?, already_member, registered }`.
1242
+
1243
+ **Throws:**
1244
+
1245
+ - textError on a missing workspace, an unknown product, or an unknown
1246
+ portfolio id (the message points at list_portfolios / list_local_products).
1247
+
1248
+ **See also:** `assign_product_to_area`, `create_product`
1249
+
1250
+
1251
+ ### `batch_create_cross_product_edges`
1252
+
1253
+ Create up to 50 cross-product edges in one atomic write (the portfolio-tier mirror of batch_create_edges). Every edge is validated and qualified before anything is written; if any is invalid the whole batch is rejected. Referenced products are auto-registered.
1254
+
1255
+ **Atomicity:** `atomic. All edges validated first, then a single portfolio.upg flush.`
1256
+
1257
+ **Arguments:**
1258
+
1259
+ | Name | Type | Required | Description |
1260
+ | ---- | ---- | -------- | ----------- |
1261
+ | `auto_create_portfolio` | boolean | | Create an empty portfolio document if none exists (default false) |
1262
+ | `edges` | array | ✓ | Cross-product edges to create (max 50). Each: { source_id, target_id, type, source_product_id?, target_product_id? }. |
1263
+
1264
+ **Returns:**
1265
+
1266
+ JSON: `{ message, created: UPGCrossEdge[], count, portfolio_file,
1267
+ registered_products? }`.
1268
+
1269
+ **Throws:**
1270
+
1271
+ - textError when `edges` is missing/empty/oversized, when any edge is invalid,
1272
+ or when no portfolio document exists (pass `auto_create_portfolio: true` to mint one).
1273
+
1274
+ **See also:** `create_cross_product_edge`, `list_cross_edge_types`
1275
+
1083
1276
 
1084
1277
  ### `create_cross_product_edge`
1085
1278
 
1086
- Create a cross-product relationship between two entities in different products within a portfolio graph. Types: `shares_persona`, `shares_competitor`, `shares_metric`, `depends_on_product`, `cannibalises`, `succeeds`.
1279
+ Create a cross-product relationship between two entities in different products within a portfolio graph. Types: `shares_persona`, `shares_competitor`, `shares_metric`, `depends_on_product`, `cannibalises`, `succeeds`, `hosts` (host product runs the hosted product inside itself, directed host to hosted).
1087
1280
 
1088
1281
  **Atomicity:** `non-atomic. Portfolio file create (if new) + edge append are
1089
1282
  separate filesystem operations.`
@@ -1096,7 +1289,7 @@ separate filesystem operations.`
1096
1289
  | `source_product_id` | string | | Product ID of the source node |
1097
1290
  | `target_id` | string | ✓ | Target node ID |
1098
1291
  | `target_product_id` | string | | Product ID of the target node |
1099
- | `type` | `shares_persona` \| `shares_competitor` \| `shares_metric` \| `depends_on_product` \| `cannibalises` \| `succeeds` | ✓ | Cross-product relationship type |
1292
+ | `type` | `shares_persona` \| `shares_competitor` \| `shares_metric` \| `depends_on_product` \| `cannibalises` \| `succeeds` \| `hosts` | ✓ | Cross-product relationship type |
1100
1293
 
1101
1294
  **Returns:**
1102
1295
 
@@ -1121,9 +1314,10 @@ portfolio edge are separate mutations.`
1121
1314
 
1122
1315
  | Name | Type | Required | Description |
1123
1316
  | ---- | ---- | -------- | ----------- |
1317
+ | `area_id` | string | | Optional product_area id (resolved against portfolio.upg) to place the new product under. |
1124
1318
  | `description` | string | | Optional product description |
1125
1319
  | `name` | string | ✓ | Product display title (required, non-empty). |
1126
- | `portfolio_id` | string | | Optional portfolio node id in the current store. When provided, a `portfolio_contains_product` edge is created in the current graph. |
1320
+ | `portfolio_id` | string | | Optional portfolio id (resolved against portfolio.upg) to place the new product under. A portfolio id that resolves only in the active graph still attaches via an in-graph edge (DEPRECATED; prefer attach_product_to_portfolio). |
1127
1321
  | `slug` | string | | Optional slug for the .upg filename. Defaults to a slug derived from `name`. Collisions append `-2`, `-3`, … |
1128
1322
  | `stage` | string | | Product lifecycle stage. See UPGProductStage in @unified-product-graph/core. |
1129
1323
 
@@ -1141,6 +1335,56 @@ JSON: `{ message, ...result }`. `result` carries `id`, `title`,
1141
1335
  **See also:** `init_workspace`
1142
1336
 
1143
1337
 
1338
+ ### `delete_cross_product_edge`
1339
+
1340
+ Delete a cross-product edge from `.upg/portfolio.upg` by id. The inverse of `create_cross_product_edge`. Returns `deleted: false` (not an error) when no edge with that id exists.
1341
+
1342
+ **Atomicity:** `atomic (single portfolio.upg flush).`
1343
+
1344
+ **Arguments:**
1345
+
1346
+ | Name | Type | Required | Description |
1347
+ | ---- | ---- | -------- | ----------- |
1348
+ | `edge_id` | string | ✓ | Cross-product edge id (from list_portfolio_cross_edges) |
1349
+
1350
+ **Returns:**
1351
+
1352
+ JSON: `{ edge_id, deleted, edge? }`. `deleted: false` (not an error) when
1353
+ no edge with that id exists, so retries are idempotent.
1354
+
1355
+ **Throws:**
1356
+
1357
+ - textError on a missing workspace.
1358
+
1359
+ **See also:** `create_cross_product_edge`, `list_portfolio_cross_edges`
1360
+
1361
+
1362
+ ### `detach_product_from_portfolio`
1363
+
1364
+ Remove a product from a portfolio's `products[]` in `.upg/portfolio.upg` (the product stays registered and in any other container). The inverse of `attach_product_to_portfolio`.
1365
+
1366
+ **Atomicity:** `atomic (single portfolio.upg flush).`
1367
+
1368
+ **Arguments:**
1369
+
1370
+ | Name | Type | Required | Description |
1371
+ | ---- | ---- | -------- | ----------- |
1372
+ | `portfolio_id` | string | ✓ | Portfolio id (from list_portfolios) |
1373
+ | `product_id` | string | ✓ | Product id (from list_local_products) |
1374
+
1375
+ **Returns:**
1376
+
1377
+ JSON: `{ product_id, container_id, container_kind: "portfolio",
1378
+ container_title?, removed }`. `removed: false` (not an error) when the product was
1379
+ not a member, so retries are idempotent.
1380
+
1381
+ **Throws:**
1382
+
1383
+ - textError on a missing workspace or an unknown portfolio id.
1384
+
1385
+ **See also:** `attach_product_to_portfolio`
1386
+
1387
+
1144
1388
  ### `get_organization`
1145
1389
 
1146
1390
  Get the organisation that owns the current workspace's portfolio. Reads the singleton `portfolio.upg.organization`. Returns `{ organization: null }` when no portfolio document exists yet.
@@ -1318,6 +1562,34 @@ before any read/mutation to confirm the active product.
1318
1562
  **See also:** `get_workspace_info`, `list_local_products`, `init_workspace`
1319
1563
 
1320
1564
 
1565
+ ### `update_product`
1566
+
1567
+ Update the product header (`$upg.product`): stage, title, description, health_status, url. The supported way to advance a product's lifecycle stage; it writes the value get_graph_digest reads, without hand-editing the .upg file.
1568
+
1569
+ **Atomicity:** `atomic (single flush).`
1570
+
1571
+ **Arguments:**
1572
+
1573
+ | Name | Type | Required | Description |
1574
+ | ---- | ---- | -------- | ----------- |
1575
+ | `description` | string | | Product description. |
1576
+ | `health_status` | string | | Product health (free-form, e.g. on_track / at_risk). |
1577
+ | `stage` | string | | Product lifecycle stage (canonical UPGProductStage). |
1578
+ | `title` | string | | Product display title. |
1579
+ | `url` | string | | Product URL. |
1580
+
1581
+ **Returns:**
1582
+
1583
+ JSON: `{ product, updated: string[] }` (the fields changed).
1584
+
1585
+ **Throws:**
1586
+
1587
+ - textError when no field is supplied, when there is no product header,
1588
+ or when `stage` is non-canonical (same strict validation as create_product).
1589
+
1590
+ **See also:** `create_product`
1591
+
1592
+
1321
1593
  ## Schema
1322
1594
 
1323
1595
  _Entity schema introspection. Same constraints the LSP enforces._
@@ -1887,7 +2159,7 @@ JSON: `{ kind, total, count, benchmarks: ... }`
1887
2159
 
1888
2160
  ### `list_cross_edge_types`
1889
2161
 
1890
- List the canonical cross-product edge types from `UPG_CROSS_EDGE_TYPES`: `shares_persona`, `shares_competitor`, `shares_metric`, `depends_on_product`, `cannibalises`, `succeeds`. Portfolio-level relationships across products. Distinct from the within-product `UPG_EDGE_CATALOG`.
2162
+ List the canonical cross-product edge types from `UPG_CROSS_EDGE_TYPES`: `shares_persona`, `shares_competitor`, `shares_metric`, `depends_on_product`, `cannibalises`, `succeeds`, `hosts`. Portfolio-level relationships across products. Distinct from the within-product `UPG_EDGE_CATALOG`.
1891
2163
 
1892
2164
  **Atomicity:** `atomic (read-only)`
1893
2165