@qe-mcp/server-ena 0.1.5 → 0.1.7

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
@@ -30,7 +30,24 @@ Gives Claude a set of ENA tools that run **locally on your machine**. The WASM m
30
30
 
31
31
  ## Installation
32
32
 
33
- The package is published to the public npm registry no registry configuration needed.
33
+ ### Recommendedone-click desktop extension (`.mcpb`)
34
+
35
+ The easiest install, no terminal or config files. Download `server-ena.mcpb`
36
+ from the [releases](https://qe-libs.org/mcp/qe-mcp-server-ena/), then in
37
+ Claude Desktop go to **Settings → Extensions** and drag the file in (or
38
+ double-click it). Click Install and restart. Node.js ships inside Claude
39
+ Desktop, so nothing else is required.
40
+
41
+ The bundle is pure JS/WASM and cross-platform. It produces SVG (`svg_plot`)
42
+ and interactive HTML (`interactive_plot`) plots; inline PNG rendering is
43
+ omitted from the bundle (it requires a native module) — open the SVG or HTML
44
+ to view the network.
45
+
46
+ To build the bundle yourself: `bash scripts/build-mcpb.sh` → `dist/server-ena.mcpb`.
47
+
48
+ ---
49
+
50
+ The package is also published to the public npm registry — no registry configuration needed.
34
51
 
35
52
  ### Option A — npx (no install needed)
36
53
 
package/manifest.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "manifest_version": "0.2",
3
+ "name": "server-ena",
4
+ "display_name": "ENA — Epistemic Network Analysis",
5
+ "version": "0.1.6",
6
+ "description": "Epistemic Network Analysis on tabular coded data. Runs entirely on your machine — data never leaves your computer.",
7
+ "long_description": "Model patterns of co-occurrence between coded events (discourse moves, behaviors, features) within conversations. The full ENA pipeline — accumulate, normalize, rotate, project, node positions — runs locally in WebAssembly; only results and plots are returned. Provides ena_profile (privacy-safe column metadata), ena_inspect (triage), ena_fit, ena_compare_groups (mean-rotation group comparison), ena_plot (rendered network views), and ena_accumulate. Prefer a file path over pasted data so raw rows stay on your machine.",
8
+ "author": {
9
+ "name": "Cody L Marquart",
10
+ "email": "clmarquart@wisc.edu"
11
+ },
12
+ "homepage": "https://qe-libs.org/mcp/qe-mcp-server-ena/",
13
+ "documentation": "https://qe-libs.org/mcp/qe-mcp-server-ena/",
14
+ "license": "GPL-3.0-only",
15
+ "keywords": [
16
+ "ena",
17
+ "epistemic-network-analysis",
18
+ "quantitative-ethnography",
19
+ "discourse-analysis",
20
+ "learning-analytics",
21
+ "wasm"
22
+ ],
23
+ "server": {
24
+ "type": "node",
25
+ "entry_point": "server.js",
26
+ "mcp_config": {
27
+ "command": "node",
28
+ "args": [
29
+ "${__dirname}/server.js"
30
+ ],
31
+ "env": {}
32
+ }
33
+ },
34
+ "tools": [
35
+ {
36
+ "name": "ena_profile",
37
+ "description": "Privacy-safe column metadata for a local CSV — no raw data transmitted."
38
+ },
39
+ {
40
+ "name": "ena_inspect",
41
+ "description": "Triage a dataset for ENA suitability; suggests codes, units, and conversations."
42
+ },
43
+ {
44
+ "name": "ena_fit",
45
+ "description": "Fit a full ENA model; returns a reusable model_id and a network plot."
46
+ },
47
+ {
48
+ "name": "ena_compare_groups",
49
+ "description": "Mean-rotation comparison of two groups; returns a model_id and plots."
50
+ },
51
+ {
52
+ "name": "ena_plot",
53
+ "description": "Render a network view (all, group, unit, comparison, subtraction) from a cached model_id."
54
+ },
55
+ {
56
+ "name": "ena_accumulate",
57
+ "description": "Accumulation step only — raw per-unit co-occurrence networks."
58
+ }
59
+ ]
60
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@qe-mcp/server-ena",
3
- "version": "0.1.5",
4
- "description": "MCP server for Epistemic Network Analysis WASM backend, zero Python dependency",
3
+ "version": "0.1.7",
4
+ "description": "MCP server for Epistemic Network Analysis \u2014 WASM backend, zero Python dependency",
5
5
  "type": "module",
6
6
  "main": "server.js",
7
7
  "bin": {
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env bash
2
+ # build-mcpb.sh — Produce a cross-platform .mcpb desktop-extension bundle.
3
+ #
4
+ # Stages only the runtime files plus a pure JS/WASM node_modules (optional deps
5
+ # omitted, so the native @resvg/resvg-js is excluded and the bundle works on any
6
+ # platform). PNG output is disabled in the bundle; SVG + interactive HTML remain.
7
+ #
8
+ # Usage: bash scripts/build-mcpb.sh
9
+ # Output: dist/server-ena.mcpb
10
+
11
+ set -euo pipefail
12
+
13
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
14
+ STAGE="$(mktemp -d)/server-ena"
15
+ OUT="${ROOT}/dist"
16
+
17
+ echo "Staging runtime files → ${STAGE}"
18
+ mkdir -p "${STAGE}" "${OUT}"
19
+
20
+ # Runtime files only (server.js resolves qeviz from node_modules, so the
21
+ # repo-root qeviz-*.js/html dev artifacts and tests are not needed).
22
+ cp "${ROOT}/server.js" "${STAGE}/"
23
+ cp "${ROOT}/manifest.json" "${STAGE}/"
24
+ cp "${ROOT}/package.json" "${STAGE}/"
25
+
26
+ # Keep the bundle's manifest version in lockstep with package.json.
27
+ VERSION="$(node -e "process.stdout.write(require('${ROOT}/package.json').version)")"
28
+ node -e "
29
+ const fs = require('fs');
30
+ const p = '${STAGE}/manifest.json';
31
+ const m = JSON.parse(fs.readFileSync(p, 'utf8'));
32
+ m.version = '${VERSION}';
33
+ fs.writeFileSync(p, JSON.stringify(m, null, 2) + '\n');
34
+ "
35
+ echo "Manifest version synced to ${VERSION}"
36
+ cp "${ROOT}/package-lock.json" "${STAGE}/" 2>/dev/null || true
37
+ [ -f "${ROOT}/README.md" ] && cp "${ROOT}/README.md" "${STAGE}/"
38
+ [ -f "${ROOT}/.npmrc" ] && cp "${ROOT}/.npmrc" "${STAGE}/"
39
+
40
+ echo "Installing production deps without optional (pure JS/WASM)…"
41
+ ( cd "${STAGE}" && npm install --omit=optional --omit=dev --no-audit --no-fund --silent )
42
+
43
+ # Drop the .npmrc so the registry token/config isn't shipped in the bundle.
44
+ rm -f "${STAGE}/.npmrc"
45
+
46
+ # Some npm versions (e.g. 9.x) still materialize root-level optionalDependencies
47
+ # despite --omit=optional. Remove the native rasterizer explicitly so the bundle
48
+ # stays pure JS/WASM and cross-platform. The server degrades gracefully without it.
49
+ rm -rf "${STAGE}"/node_modules/@resvg
50
+
51
+ # Sanity: the native rasterizer must NOT be present in the bundle.
52
+ if [ -d "${STAGE}/node_modules/@resvg" ]; then
53
+ echo "ERROR: @resvg present in bundle — not cross-platform. Aborting." >&2
54
+ exit 1
55
+ fi
56
+
57
+ echo "Packing bundle…"
58
+ mcpb pack "${STAGE}" "${OUT}/server-ena.mcpb"
59
+
60
+ echo ""
61
+ echo "Built: ${OUT}/server-ena.mcpb"
62
+ mcpb info "${OUT}/server-ena.mcpb" 2>/dev/null | head -20 || true
package/server.js CHANGED
@@ -506,10 +506,35 @@ function toolInspect({ data_path, data_csv }) {
506
506
  recommended = true;
507
507
  confidence = 'low';
508
508
  reason = `Found ${nCodes} binary code columns but no clear unit/conversation columns by name.`;
509
+ } else if (nCodes === 1) {
510
+ recommended = false;
511
+ confidence = 'low';
512
+ reason = `Only 1 binary code column was detected. ENA models co-occurrence ` +
513
+ `*between* codes, so it needs at least 2 (ideally 4+) binary presence/absence columns.`;
509
514
  } else {
510
515
  recommended = false;
511
516
  confidence = 'low';
512
- reason = `Found only ${nCodes} binary column(s). ENA typically requires 4+ binary code columns.`;
517
+ reason = `No binary (0/1) code columns were detected in this dataset. ENA models the ` +
518
+ `co-occurrence of *codes* — columns that mark, for each row, whether a concept, ` +
519
+ `behavior, or feature is present (1) or absent (0). This dataset has none, so ENA ` +
520
+ `cannot be run on it as-is.`;
521
+ }
522
+
523
+ // Actionable guidance for the not-applicable cases, so Claude can explain
524
+ // to the user *why* ENA doesn't fit and what would be needed.
525
+ let guidance = null;
526
+ if (!recommended) {
527
+ const needs =
528
+ 'For a dataset to be analyzable with ENA it needs:\n' +
529
+ ' • 2+ binary (0/1) CODE columns — each marking presence/absence of a concept or behavior per row\n' +
530
+ ' • a UNIT column identifying who or what each row belongs to\n' +
531
+ ' • a CONVERSATION column grouping rows into episodes';
532
+ guidance = nCodes === 0
533
+ ? `${needs}\n\nThis dataset appears to contain no coded columns. If it is uncoded text or ` +
534
+ `raw events, each concept of interest must first be turned into a 0/1 column (a ` +
535
+ `qualitative-coding step) before ENA can be applied. If you expected coded columns, ` +
536
+ `check that they use 0/1 values rather than text labels.`
537
+ : `${needs}\n\nAdd more binary code columns, or use a standard statistical analysis instead.`;
513
538
  }
514
539
 
515
540
  let nextStep = {};
@@ -554,6 +579,8 @@ function toolInspect({ data_path, data_csv }) {
554
579
  suggestedConvos.slice(0,3).map(c => `${c.column} (n=${c.n_unique}, score=${c.score})`).join(', '));
555
580
  for (const [col, vals] of Object.entries(suggestedGroups))
556
581
  summaryLines.push(`Likely group column: '${col}' → ${JSON.stringify(vals)}`);
582
+ if (guidance)
583
+ summaryLines.push('', guidance);
557
584
 
558
585
  const privacyNote = isPrivacySafe
559
586
  ? 'PRIVACY SAFE — data was read from a local file path. No raw row data was sent to Claude; only this analysis result was transmitted.'
@@ -564,6 +591,7 @@ function toolInspect({ data_path, data_csv }) {
564
591
  ena_recommended: recommended,
565
592
  confidence,
566
593
  reason,
594
+ guidance,
567
595
  n_rows: nRows,
568
596
  n_cols: headers.length,
569
597
  suggested_codes: codeCols,
@@ -1078,6 +1106,9 @@ const TOOLS = [
1078
1106
  'Inspect a dataset and recommend whether ENA is an appropriate analysis. ' +
1079
1107
  'Call this FIRST whenever a user provides tabular data and asks for analysis. ' +
1080
1108
  'Returns ena_recommended, confidence, suggested codes/units/conversations, and a ready-to-use next_step call. ' +
1109
+ 'When ena_recommended is false, the result includes a "guidance" field explaining why the data is not ENA-shaped ' +
1110
+ '(e.g. no binary code columns) and what would be needed — relay this to the user rather than silently failing or ' +
1111
+ 'claiming the connector is unavailable. ' +
1081
1112
  'PRIVACY: prefer data_path over data_csv so raw data stays local. ' +
1082
1113
  'If the user has not yet indicated whether their data is sensitive, ask before using data_csv. ' +
1083
1114
  'If they indicate sensitivity, use ena_profile instead (no raw data transmitted).',