@unified-product-graph/mcp-server 0.8.2 → 0.8.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 CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  All notable changes to `@unified-product-graph/mcp-server` are documented in this file. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
4
4
 
5
+ ## [0.8.4] - 2026-06-02
6
+
7
+ Framework exercises, with the 0.8.3 launch fix folded in.
8
+
9
+ ### Added
10
+ - `apply_framework` and `score_entity` tools: create a `framework_exercise` and record per-entity results on the includes edge (94 to 96 tools).
11
+ - `create_edge` accepts gated `properties` (only edge types that opt in). `prioritise` accepts an optional `exercise_id` that sources scoring inputs from the includes edges and scores across any entity type.
12
+
13
+ ### Fixed
14
+ - The server no longer crashes with `ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL` when launched via `upg mcp run` — `parseArgs` now tolerates stray positionals. The standalone `upg-mcp-server` bin is unaffected.
15
+
5
16
  ## [0.8.2] - 2026-06-02
6
17
 
7
18
  Co-version with the @unified-product-graph/* 0.8.2 release train.
package/TOOLS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # UPG MCP Server: Tool Reference
2
2
 
3
- Reference for the 94 tools exposed by `@unified-product-graph/mcp-server`. Generated from JSDoc on `src/tools/*.ts` (do not edit by hand).
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).
4
4
 
5
5
  ## Contents
6
6
 
@@ -10,7 +10,7 @@ Reference for the 94 tools exposed by `@unified-product-graph/mcp-server`. Gener
10
10
  - [Areas & Change Log](#areas-change-log): 5 tools
11
11
  - [Workspace & Portfolios](#workspace-portfolios): 10 tools
12
12
  - [Schema](#schema): 1 tool
13
- - [Spec Introspection](#spec-introspection): 43 tools
13
+ - [Spec Introspection](#spec-introspection): 45 tools
14
14
  - [Cloud Sync](#cloud-sync): 3 tools
15
15
  - [Validation](#validation): 3 tools
16
16
 
@@ -767,6 +767,7 @@ Create one edge between two nodes. Edge type auto-infers when omitted. Target ac
767
767
 
768
768
  | Name | Type | Required | Description |
769
769
  | ---- | ---- | -------- | ----------- |
770
+ | `properties` | object | | Edge-scoped properties. Only permitted on edge types that opt in (currently framework_exercise_includes_node); rejected on plain semantic edges. |
770
771
  | `source_id` | string | ✓ | Source node ID |
771
772
  | `target_id` | string | | Target node ID |
772
773
  | `target_title` | string | | Target node title (alternative to target_id; requires target_type). |
@@ -1349,6 +1350,7 @@ edges_in, phases?, initial_phase?, terminal_phases?, domain_guide? }`.
1349
1350
 
1350
1351
  _Canonical playbooks, approaches, domain guides, frameworks, edge catalogue, regions, lenses, type labels, hierarchy, version, cross-edges, entity meta, anti-patterns, benchmarks, bare-verb approach handlers, migrations, lifecycles, scales, framework categories/patterns, and domain rings. All from `@unified-product-graph/core`._
1351
1352
 
1353
+ - [`apply_framework`](#apply-framework)
1352
1354
  - [`get_anti_pattern`](#get-anti-pattern)
1353
1355
  - [`get_approach`](#get-approach)
1354
1356
  - [`get_domain_guide`](#get-domain-guide)
@@ -1391,8 +1393,35 @@ _Canonical playbooks, approaches, domain guides, frameworks, edge catalogue, reg
1391
1393
  - [`prioritise`](#prioritise)
1392
1394
  - [`reflect`](#reflect)
1393
1395
  - [`resolve_edge_for_pair`](#resolve-edge-for-pair)
1396
+ - [`score_entity`](#score-entity)
1394
1397
  - [`trace`](#trace)
1395
1398
 
1399
+ ### `apply_framework`
1400
+
1401
+ Apply a framework (MoSCoW, RICE, Kano, ...) to a set of entities: creates a framework_exercise node and an `includes` edge to each entity. The per-entity result is recorded on the edge via score_entity, never on the entity node, so the same entity can sit in many exercises and any entity type can be scored. Returns { exercise_id, exercise, included, warnings }.
1402
+
1403
+ **Atomicity:** `atomic.`
1404
+
1405
+ **Arguments:**
1406
+
1407
+ | Name | Type | Required | Description |
1408
+ | ---- | ---- | -------- | ----------- |
1409
+ | `entity_ids` | array | | Entities to pull into the exercise (any type). |
1410
+ | `framework_id` | string | ✓ | Required. UPGFramework.id (e.g. "moscow", "rice-scoring"). |
1411
+ | `status` | string | | Lifecycle phase: draft \| active \| archived (default draft). |
1412
+ | `title` | string | | Human label for the exercise (default "<Framework> exercise"). |
1413
+
1414
+ **Returns:**
1415
+
1416
+ JSON: `{ exercise_id, exercise, included: [{ edge_id, entity_id }], warnings }`.
1417
+
1418
+ **Throws:**
1419
+
1420
+ - textError on a missing/unknown framework_id.
1421
+
1422
+ **See also:** `score_entity`
1423
+
1424
+
1396
1425
  ### `get_anti_pattern`
1397
1426
 
1398
1427
  Return one curated anti-pattern by id (kebab-case slug, e.g. "features-without-hypotheses", "personas-without-jobs"). Includes structured condition, why-it-matters, remediation, applicable stages, severity, optional source citation. IDs are stable URL fragments.
@@ -2235,7 +2264,8 @@ execution_mode: "execution_v0_4_0" }`.
2235
2264
 
2236
2265
  | Name | Type | Required | Description |
2237
2266
  | ---- | ---- | -------- | ----------- |
2238
- | `candidates` | array || Required. entity_id[] to rank. |
2267
+ | `candidates` | array | | entity_id[] to rank. Optional when exercise_id is given (the exercise supplies them). |
2268
+ | `exercise_id` | string | | Optional (0.8.4). A framework_exercise id: reads each candidate's scoring inputs from its includes-edge properties instead of node.properties, and bypasses the target-type guard so any entity type can be scored. |
2239
2269
  | `framework_id` | string | ✓ | Required. UPGFramework.id of the scoring lens (e.g. "rice-scoring", "ice-scoring", "kano-model", "cost-of-delay", "wsjf"). |
2240
2270
 
2241
2271
  **Returns:**
@@ -2310,6 +2340,33 @@ not synthesise a non-canonical key.
2310
2340
  **See also:** `list_edge_types`, `get_edge_type`, `create_edge`, `trace`
2311
2341
 
2312
2342
 
2343
+ ### `score_entity`
2344
+
2345
+ Record a framework's result for one entity on the exercise's includes edge (a MoSCoW bucket, a RICE score, a canvas slot). Auto-includes the entity if not already in scope. Merges into existing edge properties unless replace is set. Returns { edge, warnings }.
2346
+
2347
+ **Atomicity:** `atomic.`
2348
+
2349
+ **Arguments:**
2350
+
2351
+ | Name | Type | Required | Description |
2352
+ | ---- | ---- | -------- | ----------- |
2353
+ | `entity_id` | string | ✓ | Required. The entity being scored. |
2354
+ | `exercise_id` | string | ✓ | Required. The framework_exercise id. |
2355
+ | `replace` | boolean | | Replace the edge properties instead of merging (default false). |
2356
+ | `values` | object | ✓ | Required. The result as { input: value }, e.g. { "moscow": "must" } or { "reach": 800, "impact": 3 }. |
2357
+
2358
+ **Returns:**
2359
+
2360
+ JSON: `{ edge, warnings }`.
2361
+
2362
+ **Throws:**
2363
+
2364
+ - textError when the exercise/entity is missing or the node is not a
2365
+ framework_exercise.
2366
+
2367
+ **See also:** `apply_framework`
2368
+
2369
+
2313
2370
  ### `trace`
2314
2371
 
2315
2372
  [LLM-mediated] This tool returns a routing envelope, not computed results. For user-facing tracing, invoke the /upg-trace skill instead of calling this tool directly. Trace approach: path of arrival to "walk a meaningful path through existing graph". Returns the Trace record + invocation params in the family-resemblance envelope. The LLM uses `anchor` + `path` to compose `query()` calls and emits `{ trail: [{ depth, entity_id, edge_type_in }], reached: entity_id[] }`. `path` is type-shorthand: `["persona","job","feature"]` walks persona→job→feature using the canonical edge per pair (via `resolve_edge_for_pair`). Optional `edges_override` selects non-canonical edges per hop; `null` per element means "use canonical".
package/dist/index.js CHANGED
@@ -511,8 +511,8 @@ var UPG_DOMAINS = [
511
511
  {
512
512
  id: "workspace",
513
513
  label: "Workspace",
514
- description: "Spatial thinking spaces for arranging entities, debating decisions, and committing to the graph. Workspaces are transient canvases that sit alongside all other domains, letting you compose and explore relationships before they become permanent graph structure.",
515
- types: ["workspace"]
514
+ description: "Spatial thinking spaces for arranging entities, debating decisions, and committing to the graph. Workspaces are transient canvases that sit alongside all other domains, letting you compose and explore relationships before they become permanent graph structure. A framework exercise is a structured workspace: one run of a framework (MoSCoW, RICE, Kano, \u2026) applied to a chosen set of entities, with each entity's result recorded on the exercise-to-entity edge rather than the entity itself.",
515
+ types: ["workspace", "framework_exercise"]
516
516
  }
517
517
  ];
518
518
  var UPG_ENTITY_TO_DOMAIN = Object.freeze(
@@ -933,7 +933,8 @@ var UPG_ENTITY_META = [
933
933
  { name: "integration_partner", type_id: "ent_311", maturity: "stable", since: "0.1.0" },
934
934
  { name: "partner_revenue_share", type_id: "ent_312", maturity: "stable", since: "0.1.0" },
935
935
  // ── Workspace ──
936
- { name: "workspace", type_id: "ent_328", maturity: "proposed", since: "0.2.0" }
936
+ { name: "workspace", type_id: "ent_328", maturity: "proposed", since: "0.2.0" },
937
+ { name: "framework_exercise", type_id: "ent_350", maturity: "proposed", since: "0.8.4" }
937
938
  ];
938
939
  var UPG_ENTITY_META_BY_NAME = new Map(
939
940
  UPG_ENTITY_META.map((m) => [m.name, m])
@@ -2073,6 +2074,18 @@ var UPG_EDGE_CATALOG = {
2073
2074
  // universal across the DDD type family. Verbs follow DDD literature:
2074
2075
  // aggregate belongs_to context, context contains aggregates.
2075
2076
  node_belongs_to_bounded_context: { forward_verb: "belongs_to", reverse_verb: "contains", classification: "cross-domain", source_type: "node", target_type: "bounded_context" },
2077
+ // ── Cross-domain: Framework exercises ────────────────────────────────────────
2078
+ // A framework_exercise is one run of a framework (MoSCoW, RICE, Kano, …) over a
2079
+ // set of entities. It `includes` each entity it touches; the framework's
2080
+ // per-entity result — a MoSCoW bucket, a RICE score, a canvas slot, a funnel
2081
+ // stage — rides on this edge's `properties`, NOT on the entity node. A value
2082
+ // that exists only within a specific exercise is a fact about the relationship,
2083
+ // not the entity, so it lives on the edge (same principle as owner-as-edge).
2084
+ // Polymorphic (`target_type: 'node'`): an exercise can include any entity type,
2085
+ // which closes the feature-only limitation structurally. `carries_properties`
2086
+ // opts this edge into the gated edge-property model. See ADR
2087
+ // 2026-06-02-framework-exercises.
2088
+ framework_exercise_includes_node: { forward_verb: "includes", reverse_verb: "included_in", classification: "cross-domain", source_type: "framework_exercise", target_type: "node", carries_properties: true },
2076
2089
  // ── New edges replacing deleted string properties ────────────────────
2077
2090
  // Marketing
2078
2091
  marketing_strategy_pursues_outcome: { forward_verb: "pursues", reverse_verb: "pursued_by", classification: "cross-domain", source_type: "marketing_strategy", target_type: "outcome" },
@@ -2702,7 +2715,9 @@ var UPG_POLYMORPHIC_EDGE_KEYS = [
2702
2715
  "node_owned_by_department",
2703
2716
  "node_owned_by_person",
2704
2717
  // Universal architecture references
2705
- "node_belongs_to_bounded_context"
2718
+ "node_belongs_to_bounded_context",
2719
+ // Framework exercises (an exercise can include any entity type)
2720
+ "framework_exercise_includes_node"
2706
2721
  ];
2707
2722
  var _POLY_KEY_SET = new Set(UPG_POLYMORPHIC_EDGE_KEYS);
2708
2723
  var LEGACY_PRODUCT_STAGES = Object.freeze({
@@ -5625,6 +5640,31 @@ var TEMPLATE_LIFECYCLES = [
5625
5640
  // ── Phase B: SALES_DEAL (qualified → proposal → negotiation → won/lost) ─────
5626
5641
  fromTemplate("deal", SALES_DEAL_TEMPLATE)
5627
5642
  ];
5643
+ var FRAMEWORK_EXERCISE_LIFECYCLE = {
5644
+ entity_type: "framework_exercise",
5645
+ initial_phase: "draft",
5646
+ terminal_phases: ["archived"],
5647
+ phases: [
5648
+ {
5649
+ id: "draft",
5650
+ label: "Draft",
5651
+ description: "The exercise has been created and entities pulled into scope, but the framework's inputs have not all been filled in yet.",
5652
+ transitions_to: ["active", "archived"]
5653
+ },
5654
+ {
5655
+ id: "active",
5656
+ label: "Active",
5657
+ description: "The current, authoritative run of its framework. Its include edges carry the live results consumers read, rank, and render by.",
5658
+ transitions_to: ["archived"]
5659
+ },
5660
+ {
5661
+ id: "archived",
5662
+ label: "Archived",
5663
+ description: "A past run, retained for provenance. Still queryable but superseded by a newer exercise and hidden from default views. Can be revived to active.",
5664
+ transitions_to: ["active"]
5665
+ }
5666
+ ]
5667
+ };
5628
5668
  var UPG_LIFECYCLES = [
5629
5669
  // Product (root)
5630
5670
  PRODUCT_LIFECYCLE,
@@ -5726,6 +5766,8 @@ var UPG_LIFECYCLES = [
5726
5766
  // Product & Growth
5727
5767
  VARIANT_LIFECYCLE,
5728
5768
  GROWTH_CAMPAIGN_LIFECYCLE,
5769
+ // Workspace
5770
+ FRAMEWORK_EXERCISE_LIFECYCLE,
5729
5771
  // ── Template-generated lifecycles ─────────────────────────────────
5730
5772
  ...TEMPLATE_LIFECYCLES
5731
5773
  ];
@@ -9203,6 +9245,11 @@ var UPG_PROPERTY_SCHEMA = {
9203
9245
  },
9204
9246
  methodology: { type: "string", description: "Forecasting methodology used" }
9205
9247
  },
9248
+ // FrameworkExerciseProperties: Framework exercise: one run of a framework over a set of entities.
9249
+ framework_exercise: {
9250
+ framework_id: { type: "string", description: "Which framework this exercise runs: a framework id (e.g. 'moscow', 'rice-scoring', 'kano-model'). Resolves against the framework catalog." },
9251
+ inputs_snapshot: { type: "object", description: "Optional frozen copy of the framework's input spec at apply time, so a historical exercise still renders correctly if the framework definition later evolves (inputs added, removed, or rescaled)." }
9252
+ },
9206
9253
  // FunnelProperties: Funnel entity.
9207
9254
  funnel: {
9208
9255
  funnel_type: { type: "string", enum: ["acquisition", "activation", "retention", "revenue", "referral", "custom"], description: "Which stage of the customer lifecycle this funnel measures" },
@@ -18764,7 +18811,7 @@ var PORTFOLIO_GUIDE = {
18764
18811
  var WORKSPACE_GUIDE = {
18765
18812
  domain_id: "workspace",
18766
18813
  anchor_entity: "workspace",
18767
- creation_sequence: ["workspace"],
18814
+ creation_sequence: ["workspace", "framework_exercise"],
18768
18815
  patterns: [],
18769
18816
  required_bridges: [],
18770
18817
  anti_patterns: [
@@ -23564,7 +23611,7 @@ var NODE_KEY_ORDER = [
23564
23611
  "sort_order",
23565
23612
  "properties"
23566
23613
  ];
23567
- var EDGE_KEY_ORDER = ["id", "source", "target", "type", "mapping_confidence"];
23614
+ var EDGE_KEY_ORDER = ["id", "source", "target", "type", "mapping_confidence", "properties"];
23568
23615
  var CROSS_EDGE_KEY_ORDER = ["id", "source", "target", "type", "source_product_id", "target_product_id", "mapping_confidence"];
23569
23616
  var PRODUCT_KEY_ORDER = ["id", "title", "description", "stage", "properties"];
23570
23617
  function canonicalNode(node) {
@@ -23576,7 +23623,8 @@ function canonicalNode(node) {
23576
23623
  }
23577
23624
  function canonicalEdge(edge) {
23578
23625
  return orderedObject(edge, EDGE_KEY_ORDER, {
23579
- forceKeys: ["id", "source", "target", "type"]
23626
+ forceKeys: ["id", "source", "target", "type"],
23627
+ openKeys: ["properties"]
23580
23628
  });
23581
23629
  }
23582
23630
  function canonicalCrossEdge(edge) {
@@ -23680,7 +23728,7 @@ function serializePortfolioWithHeader(doc, opts) {
23680
23728
  header.integrity = { algorithm: INTEGRITY_ALGORITHM, body: computeBodyChecksum(doc) };
23681
23729
  return JSON.stringify({ $upg: header, ...body }, null, 2) + "\n";
23682
23730
  }
23683
- var UPG_VERSION = "0.8.2";
23731
+ var UPG_VERSION = "0.8.4";
23684
23732
  var MARKDOWN_FORMAT_VERSION = "0.1";
23685
23733
  var UPG_TYPES = getTypes();
23686
23734
  var UPG_TYPES_SET = new Set(UPG_TYPES);
@@ -25286,7 +25334,8 @@ var createEdge = (args, ctx) => {
25286
25334
  target_id: args.target_id,
25287
25335
  target_title: args.target_title,
25288
25336
  target_type: args.target_type,
25289
- type: args.type
25337
+ type: args.type,
25338
+ properties: args.properties
25290
25339
  });
25291
25340
  if ("error" in result) {
25292
25341
  if (result.no_canonical_edge_for) {
@@ -26185,6 +26234,62 @@ var getEntitySchema = (args, _ctx) => {
26185
26234
  }
26186
26235
  };
26187
26236
 
26237
+ // src/tools/frameworks.ts
26238
+ import {
26239
+ applyFramework as applyFrameworkLib,
26240
+ scoreEntity as scoreEntityLib
26241
+ } from "@unified-product-graph/sdk";
26242
+ var applyFramework = (args, ctx) => {
26243
+ const { store } = ctx;
26244
+ const frameworkId = args.framework_id;
26245
+ if (!frameworkId) {
26246
+ return textError('Missing required parameter: framework_id (e.g. "moscow", "rice-scoring")');
26247
+ }
26248
+ try {
26249
+ const result = applyFrameworkLib(store, {
26250
+ framework_id: frameworkId,
26251
+ title: args.title,
26252
+ entity_ids: args.entity_ids ?? [],
26253
+ status: args.status
26254
+ });
26255
+ return text(
26256
+ JSON.stringify(
26257
+ {
26258
+ exercise_id: result.exercise.id,
26259
+ exercise: result.exercise,
26260
+ included: result.edges.map((e) => ({ edge_id: e.id, entity_id: e.target })),
26261
+ warnings: result.warnings
26262
+ },
26263
+ null,
26264
+ 2
26265
+ )
26266
+ );
26267
+ } catch (err) {
26268
+ return textError(err.message);
26269
+ }
26270
+ };
26271
+ var scoreEntity = (args, ctx) => {
26272
+ const { store } = ctx;
26273
+ const exerciseId = args.exercise_id;
26274
+ const entityId = args.entity_id;
26275
+ const values = args.values;
26276
+ if (!exerciseId) return textError("Missing required parameter: exercise_id");
26277
+ if (!entityId) return textError("Missing required parameter: entity_id");
26278
+ if (!values || typeof values !== "object" || Array.isArray(values)) {
26279
+ return textError(
26280
+ 'Missing required parameter: values (object of input \u2192 value, e.g. { "moscow": "must" })'
26281
+ );
26282
+ }
26283
+ const result = scoreEntityLib(store, {
26284
+ exercise_id: exerciseId,
26285
+ entity_id: entityId,
26286
+ values,
26287
+ replace: args.replace
26288
+ });
26289
+ if ("error" in result) return textError(result.error);
26290
+ return text(JSON.stringify({ edge: result.edge, warnings: result.warnings }, null, 2));
26291
+ };
26292
+
26188
26293
  // src/tools/spec.ts
26189
26294
  import { buildResolverHints as buildResolverHints2 } from "@unified-product-graph/sdk";
26190
26295
  import {
@@ -26779,34 +26884,38 @@ var inspect = async (args, ctx) => {
26779
26884
  });
26780
26885
  };
26781
26886
  var prioritise = (args, ctx) => {
26782
- const candidates = args.candidates;
26887
+ const candidates = args.candidates ?? [];
26783
26888
  const frameworkId = args.framework_id;
26784
- if (!candidates || !Array.isArray(candidates) || candidates.length === 0) {
26785
- return textError("Missing required parameter: candidates (entity_id[])");
26786
- }
26889
+ const exerciseId = args.exercise_id;
26787
26890
  if (!frameworkId) {
26788
26891
  return textError(
26789
26892
  'Missing required parameter: framework_id (e.g. "rice-scoring", "ice-scoring", "wsjf")'
26790
26893
  );
26791
26894
  }
26895
+ if (!exerciseId && (!Array.isArray(candidates) || candidates.length === 0)) {
26896
+ return textError(
26897
+ "Provide candidates (entity_id[]), or an exercise_id whose includes edges supply the candidates and their scoring inputs."
26898
+ );
26899
+ }
26792
26900
  const framework = UPG_FRAMEWORKS_BY_ID[frameworkId];
26793
26901
  if (!framework) {
26794
26902
  return textError(
26795
26903
  `Unknown framework_id: "${frameworkId}". See list_frameworks for valid ids.`
26796
26904
  );
26797
26905
  }
26798
- const execResult = executePrioritise(framework, candidates, ctx.store);
26906
+ const execResult = executePrioritise(framework, candidates, ctx.store, { exerciseId });
26907
+ const params = { candidates, framework_id: frameworkId, ...exerciseId ? { exercise_id: exerciseId } : {} };
26799
26908
  if (execResult.kind === "execution") {
26800
26909
  return approachEnvelope("prioritise", candidates, {
26801
- params: { candidates, framework_id: frameworkId },
26910
+ params,
26802
26911
  framework_resolved: execResult.framework_used,
26803
26912
  ranked: execResult.ranked,
26804
26913
  required_properties: execResult.required_properties,
26805
- execution_mode: "execution_v0_4_0"
26914
+ execution_mode: exerciseId ? "exercise_v0_8_4" : "execution_v0_4_0"
26806
26915
  });
26807
26916
  }
26808
26917
  return approachEnvelope("prioritise", candidates, {
26809
- params: { candidates, framework_id: frameworkId },
26918
+ params,
26810
26919
  framework_resolved: execResult.framework_used,
26811
26920
  hint: execResult.hint,
26812
26921
  execution_mode: "definition_lookup_v0_4_0"
@@ -28181,6 +28290,10 @@ var TOOL_DEFINITIONS = [
28181
28290
  type: {
28182
28291
  type: "string",
28183
28292
  description: "Edge type. Auto-inferred if omitted."
28293
+ },
28294
+ properties: {
28295
+ type: "object",
28296
+ description: "Edge-scoped properties. Only permitted on edge types that opt in (currently framework_exercise_includes_node); rejected on plain semantic edges."
28184
28297
  }
28185
28298
  },
28186
28299
  required: ["source_id"]
@@ -28482,10 +28595,39 @@ var TOOL_DEFINITIONS = [
28482
28595
  inputSchema: {
28483
28596
  type: "object",
28484
28597
  properties: {
28485
- candidates: { type: "array", items: { type: "string" }, description: "Required. entity_id[] to rank." },
28486
- framework_id: { type: "string", description: 'Required. UPGFramework.id of the scoring lens (e.g. "rice-scoring", "ice-scoring", "kano-model", "cost-of-delay", "wsjf").' }
28598
+ candidates: { type: "array", items: { type: "string" }, description: "entity_id[] to rank. Optional when exercise_id is given (the exercise supplies them)." },
28599
+ framework_id: { type: "string", description: 'Required. UPGFramework.id of the scoring lens (e.g. "rice-scoring", "ice-scoring", "kano-model", "cost-of-delay", "wsjf").' },
28600
+ exercise_id: { type: "string", description: "Optional (0.8.4). A framework_exercise id: reads each candidate's scoring inputs from its includes-edge properties instead of node.properties, and bypasses the target-type guard so any entity type can be scored." }
28601
+ },
28602
+ required: ["framework_id"]
28603
+ }
28604
+ },
28605
+ {
28606
+ name: "apply_framework",
28607
+ description: "Apply a framework (MoSCoW, RICE, Kano, ...) to a set of entities: creates a framework_exercise node and an `includes` edge to each entity. The per-entity result is recorded on the edge via score_entity, never on the entity node, so the same entity can sit in many exercises and any entity type can be scored. Returns { exercise_id, exercise, included, warnings }.",
28608
+ inputSchema: {
28609
+ type: "object",
28610
+ properties: {
28611
+ framework_id: { type: "string", description: 'Required. UPGFramework.id (e.g. "moscow", "rice-scoring").' },
28612
+ title: { type: "string", description: 'Human label for the exercise (default "<Framework> exercise").' },
28613
+ entity_ids: { type: "array", items: { type: "string" }, description: "Entities to pull into the exercise (any type)." },
28614
+ status: { type: "string", description: "Lifecycle phase: draft | active | archived (default draft)." }
28487
28615
  },
28488
- required: ["candidates", "framework_id"]
28616
+ required: ["framework_id"]
28617
+ }
28618
+ },
28619
+ {
28620
+ name: "score_entity",
28621
+ description: "Record a framework's result for one entity on the exercise's includes edge (a MoSCoW bucket, a RICE score, a canvas slot). Auto-includes the entity if not already in scope. Merges into existing edge properties unless replace is set. Returns { edge, warnings }.",
28622
+ inputSchema: {
28623
+ type: "object",
28624
+ properties: {
28625
+ exercise_id: { type: "string", description: "Required. The framework_exercise id." },
28626
+ entity_id: { type: "string", description: "Required. The entity being scored." },
28627
+ values: { type: "object", description: 'Required. The result as { input: value }, e.g. { "moscow": "must" } or { "reach": 800, "impact": 3 }.' },
28628
+ replace: { type: "boolean", description: "Replace the edge properties instead of merging (default false)." }
28629
+ },
28630
+ required: ["exercise_id", "entity_id", "values"]
28489
28631
  }
28490
28632
  },
28491
28633
  {
@@ -29141,6 +29283,8 @@ var HANDLERS = {
29141
29283
  batch_delete_nodes: batchDeleteNodes,
29142
29284
  batch_create_edges: batchCreateEdges,
29143
29285
  batch_delete_edges: batchDeleteEdges,
29286
+ apply_framework: applyFramework,
29287
+ score_entity: scoreEntity,
29144
29288
  repair_dangling_edges: repairDanglingEdges,
29145
29289
  export_edges: exportEdges,
29146
29290
  rename_edge_type: renameEdgeType,
@@ -29409,7 +29553,12 @@ async function runMcpServer() {
29409
29553
  options: {
29410
29554
  file: { type: "string", short: "f" },
29411
29555
  title: { type: "string", short: "t" }
29412
- }
29556
+ },
29557
+ // Tolerate stray positionals. When launched via `upg mcp run`, argv carries
29558
+ // the `mcp run` subcommand tokens; without this, parseArgs throws
29559
+ // ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL and the server never starts. The
29560
+ // standalone bin (`upg-mcp-server`) passes none, so this is harmless there.
29561
+ allowPositionals: true
29413
29562
  });
29414
29563
  let resolvedPath = await discoverUPGFile(values.file);
29415
29564
  if (!resolvedPath) {