@unified-product-graph/mcp-server 0.8.13 → 0.8.15

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/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 99 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): 6 tools
11
+ - [Workspace & Portfolios](#workspace-portfolios): 12 tools
12
12
  - [Schema](#schema): 1 tool
13
13
  - [Spec Introspection](#spec-introspection): 45 tools
14
14
  - [Cloud Sync](#cloud-sync): 3 tools
@@ -946,12 +946,39 @@ 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)
950
951
  - [`get_area_context`](#get-area-context)
951
952
  - [`get_area_graph`](#get-area-graph)
952
953
  - [`get_changes`](#get-changes)
953
954
  - [`list_product_areas`](#list-product-areas)
954
955
 
956
+ ### `assign_product_to_area`
957
+
958
+ 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.
959
+
960
+ **Atomicity:** `atomic (single portfolio.upg flush).`
961
+
962
+ **Arguments:**
963
+
964
+ | Name | Type | Required | Description |
965
+ | ---- | ---- | -------- | ----------- |
966
+ | `area_id` | string | ✓ | Product area id (from list_product_areas) |
967
+ | `product_id` | string | ✓ | Product id (from create_product / list_local_products) |
968
+
969
+ **Returns:**
970
+
971
+ JSON: `{ product_id, container_id, container_kind: "product_area",
972
+ container_title?, already_member, registered }`.
973
+
974
+ **Throws:**
975
+
976
+ - textError on a missing workspace, an unknown product, or an unknown
977
+ area id (the message points at list_product_areas / list_local_products).
978
+
979
+ **See also:** `attach_product_to_portfolio`, `create_product`
980
+
981
+
955
982
  ### `create_area`
956
983
 
957
984
  Create a product area entity in the portfolio document (`.upg/portfolio.upg`). Product areas represent the organisational axis (who owns what). Supports nesting via `parent_area_id`. The portfolio document is created on demand.
@@ -966,7 +993,7 @@ flushed in one pass.`
966
993
  | `description` | string | | What this area covers |
967
994
  | `owner` | string | | Person or team that owns this area |
968
995
  | `parent_area_id` | string | | Parent area ID for creating a sub-area |
969
- | `strategic_priority` | `critical` \| `high` \| `medium` \| `low` | | Strategic priority of this area |
996
+ | `strategic_priority` | `urgent` \| `high` \| `medium` \| `low` \| `none` | | Strategic priority of this area (canonical Priority scale) |
970
997
  | `title` | string | ✓ | Area name (e.g. "Search", "Payments") |
971
998
 
972
999
  **Returns:**
@@ -1070,6 +1097,7 @@ parent_area_id?, products? }>, total }`.
1070
1097
 
1071
1098
  _Multi-product discovery, switching, init, cross-product edges._
1072
1099
 
1100
+ - [`attach_product_to_portfolio`](#attach-product-to-portfolio)
1073
1101
  - [`create_cross_product_edge`](#create-cross-product-edge)
1074
1102
  - [`create_product`](#create-product)
1075
1103
  - [`get_organization`](#get-organization)
@@ -1080,6 +1108,33 @@ _Multi-product discovery, switching, init, cross-product edges._
1080
1108
  - [`list_portfolios`](#list-portfolios)
1081
1109
  - [`migrate_cross_edges`](#migrate-cross-edges)
1082
1110
  - [`switch_product`](#switch-product)
1111
+ - [`update_product`](#update-product)
1112
+
1113
+ ### `attach_product_to_portfolio`
1114
+
1115
+ 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.
1116
+
1117
+ **Atomicity:** `atomic (single portfolio.upg flush).`
1118
+
1119
+ **Arguments:**
1120
+
1121
+ | Name | Type | Required | Description |
1122
+ | ---- | ---- | -------- | ----------- |
1123
+ | `portfolio_id` | string | ✓ | Portfolio id (from list_portfolios) |
1124
+ | `product_id` | string | ✓ | Product id (from create_product / list_local_products) |
1125
+
1126
+ **Returns:**
1127
+
1128
+ JSON: `{ product_id, container_id, container_kind: "portfolio",
1129
+ container_title?, already_member, registered }`.
1130
+
1131
+ **Throws:**
1132
+
1133
+ - textError on a missing workspace, an unknown product, or an unknown
1134
+ portfolio id (the message points at list_portfolios / list_local_products).
1135
+
1136
+ **See also:** `assign_product_to_area`, `create_product`
1137
+
1083
1138
 
1084
1139
  ### `create_cross_product_edge`
1085
1140
 
@@ -1121,9 +1176,10 @@ portfolio edge are separate mutations.`
1121
1176
 
1122
1177
  | Name | Type | Required | Description |
1123
1178
  | ---- | ---- | -------- | ----------- |
1179
+ | `area_id` | string | | Optional product_area id (resolved against portfolio.upg) to place the new product under. |
1124
1180
  | `description` | string | | Optional product description |
1125
1181
  | `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. |
1182
+ | `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
1183
  | `slug` | string | | Optional slug for the .upg filename. Defaults to a slug derived from `name`. Collisions append `-2`, `-3`, … |
1128
1184
  | `stage` | string | | Product lifecycle stage. See UPGProductStage in @unified-product-graph/core. |
1129
1185
 
@@ -1318,6 +1374,34 @@ before any read/mutation to confirm the active product.
1318
1374
  **See also:** `get_workspace_info`, `list_local_products`, `init_workspace`
1319
1375
 
1320
1376
 
1377
+ ### `update_product`
1378
+
1379
+ 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.
1380
+
1381
+ **Atomicity:** `atomic (single flush).`
1382
+
1383
+ **Arguments:**
1384
+
1385
+ | Name | Type | Required | Description |
1386
+ | ---- | ---- | -------- | ----------- |
1387
+ | `description` | string | | Product description. |
1388
+ | `health_status` | string | | Product health (free-form, e.g. on_track / at_risk). |
1389
+ | `stage` | string | | Product lifecycle stage (canonical UPGProductStage). |
1390
+ | `title` | string | | Product display title. |
1391
+ | `url` | string | | Product URL. |
1392
+
1393
+ **Returns:**
1394
+
1395
+ JSON: `{ product, updated: string[] }` (the fields changed).
1396
+
1397
+ **Throws:**
1398
+
1399
+ - textError when no field is supplied, when there is no product header,
1400
+ or when `stage` is non-canonical (same strict validation as create_product).
1401
+
1402
+ **See also:** `create_product`
1403
+
1404
+
1321
1405
  ## Schema
1322
1406
 
1323
1407
  _Entity schema introspection. Same constraints the LSP enforces._
package/dist/index.js CHANGED
@@ -10131,7 +10131,8 @@ var UPG_PROPERTY_SCHEMA = {
10131
10131
  // ProductAreaProperties: ProductArea entity.
10132
10132
  product_area: {
10133
10133
  strategic_priority: { type: "string", enum: ["urgent", "high", "medium", "low", "none"], description: "Strategic priority assigned to this area" },
10134
- description: { type: "string", description: "Narrative description of what this area covers" }
10134
+ description: { type: "string", description: "Narrative description of what this area covers" },
10135
+ owner: { type: "string", description: "Person or team that owns this area" }
10135
10136
  },
10136
10137
  // ProgramProperties: Program.
10137
10138
  program: {
@@ -24371,7 +24372,7 @@ function serializePortfolioWithHeader(doc, opts) {
24371
24372
  header.integrity = { algorithm: INTEGRITY_ALGORITHM, body: computeBodyChecksum(doc) };
24372
24373
  return JSON.stringify({ $upg: header, ...body }, null, 2) + "\n";
24373
24374
  }
24374
- var UPG_VERSION = "0.8.13";
24375
+ var UPG_VERSION = "0.8.15";
24375
24376
  var MARKDOWN_FORMAT_VERSION = "0.1";
24376
24377
  var UPG_TYPES = getTypes();
24377
24378
  var UPG_TYPES_SET = new Set(UPG_TYPES);
@@ -26255,6 +26256,7 @@ import * as path2 from "path";
26255
26256
  import {
26256
26257
  writePortfolioScopedNode as writePortfolioScopedNode2,
26257
26258
  openPortfolioStoreIfExists,
26259
+ assignProductToArea,
26258
26260
  PortfolioRoutingError as PortfolioRoutingError2
26259
26261
  } from "@unified-product-graph/sdk";
26260
26262
  var listProductAreas = async (_args, _ctx) => {
@@ -26269,11 +26271,24 @@ var listProductAreas = async (_args, _ctx) => {
26269
26271
  if (area.description) row.description = area.description;
26270
26272
  if (area.parent_area_id !== void 0) row.parent_area_id = area.parent_area_id;
26271
26273
  if (area.strategic_priority) row.strategic_priority = area.strategic_priority;
26274
+ if (area.owner) row.owner = area.owner;
26272
26275
  if (area.products) row.products = area.products;
26273
26276
  return row;
26274
26277
  });
26275
26278
  return text(JSON.stringify({ areas: result, total: result.length }, null, 2));
26276
26279
  };
26280
+ var assignProductToAreaTool = async (args, _ctx) => {
26281
+ const productId = args.product_id;
26282
+ const areaId = args.area_id;
26283
+ if (!productId) return textError("Missing required parameter: product_id");
26284
+ if (!areaId) return textError("Missing required parameter: area_id");
26285
+ try {
26286
+ const result = await assignProductToArea(process.cwd(), { product_id: productId, area_id: areaId });
26287
+ return text(JSON.stringify(result, null, 2));
26288
+ } catch (err) {
26289
+ return textError(err.message);
26290
+ }
26291
+ };
26277
26292
  var getAreaGraph = (args, ctx) => {
26278
26293
  const { store } = ctx;
26279
26294
  const areaId = args.area_id;
@@ -26428,6 +26443,7 @@ var createArea = async (args, _ctx) => {
26428
26443
  const properties = {};
26429
26444
  if (args.strategic_priority) properties.strategic_priority = args.strategic_priority;
26430
26445
  if (args.parent_area_id) properties.parent_area_id = args.parent_area_id;
26446
+ if (args.owner) properties.owner = args.owner;
26431
26447
  try {
26432
26448
  const result = await writePortfolioScopedNode2(process.cwd(), {
26433
26449
  type: "product_area",
@@ -26474,10 +26490,12 @@ import {
26474
26490
  resolvePortfolioPath,
26475
26491
  openPortfolioStoreIfExists as openPortfolioStoreIfExists2,
26476
26492
  registerProductOnPortfolio,
26477
- findProductFileById
26493
+ findProductFileById,
26494
+ attachProductToPortfolio
26478
26495
  } from "@unified-product-graph/sdk";
26479
26496
  import {
26480
26497
  createProduct,
26498
+ updateProduct,
26481
26499
  initWorkspace,
26482
26500
  InvalidProductNameError,
26483
26501
  InvalidProductStageError,
@@ -26645,7 +26663,8 @@ var createProductTool = async (args, ctx) => {
26645
26663
  slug: args.slug,
26646
26664
  description: args.description,
26647
26665
  stage: args.stage,
26648
- portfolio_id: args.portfolio_id
26666
+ portfolio_id: args.portfolio_id,
26667
+ area_id: args.area_id
26649
26668
  });
26650
26669
  return text(
26651
26670
  JSON.stringify({ message: `Created product: ${result.title}`, ...result }, null, 2)
@@ -26658,6 +26677,31 @@ var createProductTool = async (args, ctx) => {
26658
26677
  return textError(`create_product failed: ${err.message}`);
26659
26678
  }
26660
26679
  };
26680
+ var updateProductTool = async (args, ctx) => {
26681
+ const { store } = ctx;
26682
+ try {
26683
+ const result = updateProduct({
26684
+ store,
26685
+ stage: args.stage,
26686
+ title: args.title,
26687
+ description: args.description,
26688
+ health_status: args.health_status,
26689
+ url: args.url
26690
+ });
26691
+ if (result.updated.length === 0) {
26692
+ return textError(
26693
+ "Nothing to update: pass at least one of: stage, title, description, health_status, url."
26694
+ );
26695
+ }
26696
+ await store.flush();
26697
+ return text(
26698
+ JSON.stringify({ message: `Updated product (${result.updated.join(", ")})`, ...result }, null, 2)
26699
+ );
26700
+ } catch (err) {
26701
+ if (err instanceof InvalidProductStageError) return textError(err.message);
26702
+ return textError(`update_product failed: ${err.message}`);
26703
+ }
26704
+ };
26661
26705
  var listPortfolios = async (_args, _ctx) => {
26662
26706
  const portfolioStore = await openPortfolioStoreIfExists2(process.cwd());
26663
26707
  if (!portfolioStore) {
@@ -26867,6 +26911,21 @@ var migrateCrossEdges = async (args, ctx) => {
26867
26911
  )
26868
26912
  );
26869
26913
  };
26914
+ var attachProductToPortfolioTool = async (args, _ctx) => {
26915
+ const productId = args.product_id;
26916
+ const portfolioId = args.portfolio_id;
26917
+ if (!productId) return textError("Missing required parameter: product_id");
26918
+ if (!portfolioId) return textError("Missing required parameter: portfolio_id");
26919
+ try {
26920
+ const result = await attachProductToPortfolio(process.cwd(), {
26921
+ product_id: productId,
26922
+ portfolio_id: portfolioId
26923
+ });
26924
+ return text(JSON.stringify(result, null, 2));
26925
+ } catch (err) {
26926
+ return textError(err.message);
26927
+ }
26928
+ };
26870
26929
 
26871
26930
  // src/tools/schema.ts
26872
26931
  var getEntitySchema = (args, _ctx) => {
@@ -29138,12 +29197,30 @@ var TOOL_DEFINITIONS = [
29138
29197
  },
29139
29198
  portfolio_id: {
29140
29199
  type: "string",
29141
- description: "Optional portfolio node id in the current store. When provided, a `portfolio_contains_product` edge is created in the current graph."
29200
+ description: "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)."
29201
+ },
29202
+ area_id: {
29203
+ type: "string",
29204
+ description: "Optional product_area id (resolved against portfolio.upg) to place the new product under."
29142
29205
  }
29143
29206
  },
29144
29207
  required: ["name"]
29145
29208
  }
29146
29209
  },
29210
+ {
29211
+ name: "update_product",
29212
+ description: "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.",
29213
+ inputSchema: {
29214
+ type: "object",
29215
+ properties: {
29216
+ stage: { type: "string", description: "Product lifecycle stage (canonical UPGProductStage)." },
29217
+ title: { type: "string", description: "Product display title." },
29218
+ description: { type: "string", description: "Product description." },
29219
+ health_status: { type: "string", description: "Product health (free-form, e.g. on_track / at_risk)." },
29220
+ url: { type: "string", description: "Product URL." }
29221
+ }
29222
+ }
29223
+ },
29147
29224
  {
29148
29225
  name: "migrate_type",
29149
29226
  description: "Migrate every entity of one type to another, applying defaults from `UPG_MIGRATIONS`. Three passes commit as one write: (1) node rename, (2) edges through `UPG_EDGE_MIGRATIONS` (catalog-aware renames, direction flips, drops; endpoint guards check post-migration types; uncatalogued edges surface as `unmapped_legacy_edges`), (3) every node through `UPG_PROPERTY_MIGRATIONS` (top-level renames, lifts, drops, self-referential cleanup). Type-specific property rules see the post-rename type.",
@@ -29840,14 +29917,38 @@ var TOOL_DEFINITIONS = [
29840
29917
  },
29841
29918
  strategic_priority: {
29842
29919
  type: "string",
29843
- enum: ["critical", "high", "medium", "low"],
29844
- description: "Strategic priority of this area"
29920
+ enum: ["urgent", "high", "medium", "low", "none"],
29921
+ description: "Strategic priority of this area (canonical Priority scale)"
29845
29922
  },
29846
29923
  owner: { type: "string", description: "Person or team that owns this area" }
29847
29924
  },
29848
29925
  required: ["title"]
29849
29926
  }
29850
29927
  },
29928
+ {
29929
+ name: "assign_product_to_area",
29930
+ description: "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.",
29931
+ inputSchema: {
29932
+ type: "object",
29933
+ properties: {
29934
+ product_id: { type: "string", description: "Product id (from create_product / list_local_products)" },
29935
+ area_id: { type: "string", description: "Product area id (from list_product_areas)" }
29936
+ },
29937
+ required: ["product_id", "area_id"]
29938
+ }
29939
+ },
29940
+ {
29941
+ name: "attach_product_to_portfolio",
29942
+ description: "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.",
29943
+ inputSchema: {
29944
+ type: "object",
29945
+ properties: {
29946
+ product_id: { type: "string", description: "Product id (from create_product / list_local_products)" },
29947
+ portfolio_id: { type: "string", description: "Portfolio id (from list_portfolios)" }
29948
+ },
29949
+ required: ["product_id", "portfolio_id"]
29950
+ }
29951
+ },
29851
29952
  {
29852
29953
  name: "list_portfolios",
29853
29954
  description: "List portfolios from the portfolio document (`.upg/portfolio.upg`). Portfolios represent the strategic axis (where we invest). Returns an empty list when no portfolio document exists yet.",
@@ -29999,6 +30100,7 @@ var HANDLERS = {
29999
30100
  get_workspace_info: getWorkspaceInfo,
30000
30101
  init_workspace: initWorkspaceTool,
30001
30102
  create_product: createProductTool,
30103
+ update_product: updateProductTool,
30002
30104
  migrate_type: migrateType,
30003
30105
  migrate_properties: migrateProperties,
30004
30106
  migrate_status: migrateStatus,
@@ -30054,9 +30156,11 @@ var HANDLERS = {
30054
30156
  skill_audit: skillAudit,
30055
30157
  get_area_context: getAreaContext,
30056
30158
  create_area: createArea,
30159
+ assign_product_to_area: assignProductToAreaTool,
30057
30160
  list_portfolios: listPortfolios,
30058
30161
  get_organization: getOrganization,
30059
30162
  create_cross_product_edge: createCrossProductEdge,
30163
+ attach_product_to_portfolio: attachProductToPortfolioTool,
30060
30164
  list_portfolio_cross_edges: listPortfolioCrossEdges,
30061
30165
  migrate_cross_edges: migrateCrossEdges,
30062
30166
  get_sync_state: getSyncState,