hunchful-mcp 0.1.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.
Files changed (3) hide show
  1. package/README.md +78 -0
  2. package/index.mjs +136 -0
  3. package/package.json +23 -0
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # Hunchful MCP server
2
+
3
+ Connect your AI assistant directly to a [Hunchful](https://hunchful.io) model, so
4
+ it can **read** your collaboration model and **refine** it as it learns — instead
5
+ of you copy-pasting links or payloads.
6
+
7
+ It's a thin wrapper over Hunchful's public HTTP endpoints; no data is stored by
8
+ the server, and it honours the same provenance-pointer contract (no transcripts,
9
+ quotes, or third-party names).
10
+
11
+ ## Tools
12
+
13
+ | Tool | What it does | Auth |
14
+ | --- | --- | --- |
15
+ | `read_model` | Read the model: hunches, confidence, refinable axis ids | none (public) |
16
+ | `propose_refinement` | Propose changes → land in the owner's confirm queue | contribution token |
17
+ | `apply_refinement` | Apply changes directly as one logged revision | edit token |
18
+
19
+ `propose_refinement` is the safe default: the owner confirms each change. Use
20
+ `apply_refinement` only when the owner has handed you their edit key.
21
+
22
+ ## Configure
23
+
24
+ Set these environment variables (in your MCP client config):
25
+
26
+ - `HUNCHFUL_MODEL_ID` — your model id (from `hunchful.io/m/<id>`), so tools can omit it
27
+ - `HUNCHFUL_CONTRIBUTION_TOKEN` — scoped, propose-only key. Generate it on your model's
28
+ Edit page → **"let an agent contribute"**. It can only *propose*.
29
+ - `HUNCHFUL_EDIT_TOKEN` — the model's edit key. Enables **direct** apply. Optional and
30
+ powerful — only set it if you want the AI to write without a confirm step. You can
31
+ regenerate the key anytime to revoke access.
32
+ - `HUNCHFUL_BASE` — override the site origin (default `https://hunchful.io`).
33
+
34
+ ### Claude Desktop / Claude Code (`claude_desktop_config.json` or `.mcp.json`)
35
+
36
+ ```json
37
+ {
38
+ "mcpServers": {
39
+ "hunchful": {
40
+ "command": "npx",
41
+ "args": ["-y", "hunchful-mcp"],
42
+ "env": {
43
+ "HUNCHFUL_MODEL_ID": "your-model-id",
44
+ "HUNCHFUL_CONTRIBUTION_TOKEN": "ctb_…"
45
+ }
46
+ }
47
+ }
48
+ }
49
+ ```
50
+
51
+ To run it from a local checkout instead of npx:
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "hunchful": {
57
+ "command": "node",
58
+ "args": ["/absolute/path/to/hunchful/mcp-server/index.mjs"],
59
+ "env": { "HUNCHFUL_MODEL_ID": "your-model-id", "HUNCHFUL_CONTRIBUTION_TOKEN": "ctb_…" }
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ ## Install (local)
66
+
67
+ ```bash
68
+ cd mcp-server
69
+ npm install
70
+ ```
71
+
72
+ ## Safety
73
+
74
+ - The owner stays in control: `propose_refinement` never changes anything until the
75
+ owner confirms; a shared edit key can be regenerated to revoke access instantly.
76
+ - Provenance pointers only. The server rejects anything but `{patternId, direction,
77
+ observedAt, source}` — no free text.
78
+ - Every applied change is recorded in the model's revision history.
package/index.mjs ADDED
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Hunchful MCP server.
4
+ *
5
+ * Lets an AI assistant connect directly to a Hunchful model — read it, and
6
+ * refine it — instead of the person copy-pasting links or payloads. It's a thin,
7
+ * dependency-light wrapper over Hunchful's public HTTP endpoints:
8
+ *
9
+ * read_model GET /m/{id}/cmp.json (open)
10
+ * propose_refinement POST /api/contribute (contribution token → owner confirms)
11
+ * apply_refinement POST /api/apply (edit token → direct, one logged revision)
12
+ *
13
+ * Configure with environment variables (in your MCP client config):
14
+ * HUNCHFUL_MODEL_ID default model id, so tools can omit it
15
+ * HUNCHFUL_CONTRIBUTION_TOKEN scoped, propose-only key (Edit page → "let an agent contribute")
16
+ * HUNCHFUL_EDIT_TOKEN the model's edit key — enables direct apply (powerful; optional)
17
+ * HUNCHFUL_BASE override the site origin (default https://hunchful.io)
18
+ *
19
+ * Provenance-pointer contract only: no transcripts, quotes, or third-party names.
20
+ */
21
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
23
+ import { z } from "zod";
24
+
25
+ const BASE = (process.env.HUNCHFUL_BASE || "https://hunchful.io").replace(/\/+$/, "");
26
+ const DEFAULT_MODEL = process.env.HUNCHFUL_MODEL_ID || "";
27
+ const EDIT_TOKEN = process.env.HUNCHFUL_EDIT_TOKEN || "";
28
+ const CONTRIB_TOKEN = process.env.HUNCHFUL_CONTRIBUTION_TOKEN || "";
29
+
30
+ function modelIdOr(given) {
31
+ const id = (given || DEFAULT_MODEL || "").trim();
32
+ if (!id) throw new Error("No modelId given and HUNCHFUL_MODEL_ID is not set.");
33
+ return id;
34
+ }
35
+ function ok(obj) {
36
+ return { content: [{ type: "text", text: typeof obj === "string" ? obj : JSON.stringify(obj, null, 2) }] };
37
+ }
38
+ function fail(msg) {
39
+ return { content: [{ type: "text", text: String(msg) }], isError: true };
40
+ }
41
+
42
+ async function fetchCmp(modelId) {
43
+ const r = await fetch(`${BASE}/m/${encodeURIComponent(modelId)}/cmp.json`, { headers: { accept: "application/json" } });
44
+ if (!r.ok) throw new Error(`Couldn't read model ${modelId} (HTTP ${r.status}).`);
45
+ return r.json();
46
+ }
47
+ function normalizeItems(items) {
48
+ const now = new Date().toISOString();
49
+ return items.map((it) => ({
50
+ patternId: it.patternId,
51
+ direction: it.direction,
52
+ observedAt: it.observedAt || now,
53
+ source: it.source || "agent:mcp",
54
+ }));
55
+ }
56
+
57
+ const itemShape = {
58
+ patternId: z.string().describe("One of the model's refinable axis ids (see read_model → refinablePatternIds)."),
59
+ direction: z.enum(["strengthens", "weakens"]).describe("Does the evidence strengthen or weaken this hunch?"),
60
+ observedAt: z.string().optional().describe("ISO date the evidence was observed (defaults to now)."),
61
+ source: z.string().optional().describe('Provenance pointer only, e.g. "agent:claude". No free text, no names.'),
62
+ };
63
+
64
+ const server = new McpServer({ name: "hunchful", version: "1.0.0" });
65
+
66
+ server.tool(
67
+ "read_model",
68
+ "Read a Hunchful collaboration model: its hunches (axis, pole, confidence), the axis ids you can refine, and how to write back. Start here.",
69
+ { modelId: z.string().optional().describe("Model id. Defaults to HUNCHFUL_MODEL_ID.") },
70
+ async ({ modelId }) => {
71
+ try {
72
+ const id = modelIdOr(modelId);
73
+ const doc = await fetchCmp(id);
74
+ return ok({
75
+ modelId: id,
76
+ libraryVersion: doc.libraryVersion,
77
+ hunches: (doc.hypotheses || []).map((h) => ({ patternId: h.patternId, pole: h.pole, statement: h.statement, confidence: h.confidence })),
78
+ refinablePatternIds: doc.contribution?.patternIds ?? (doc.hypotheses || []).map((h) => h.patternId),
79
+ constraints: doc.contribution?.constraints,
80
+ canApplyDirectly: !!EDIT_TOKEN,
81
+ canPropose: !!CONTRIB_TOKEN,
82
+ });
83
+ } catch (e) {
84
+ return fail(e.message || e);
85
+ }
86
+ }
87
+ );
88
+
89
+ server.tool(
90
+ "propose_refinement",
91
+ "Propose a refinement (provenance pointers only). It lands in the owner's queue for them to confirm — nothing changes until they do. Needs HUNCHFUL_CONTRIBUTION_TOKEN.",
92
+ {
93
+ modelId: z.string().optional(),
94
+ items: z.array(z.object(itemShape)).min(1).describe("Which axes to strengthen/weaken, based on what you learned."),
95
+ },
96
+ async ({ modelId, items }) => {
97
+ try {
98
+ if (!CONTRIB_TOKEN) return fail("No HUNCHFUL_CONTRIBUTION_TOKEN set. Ask the owner to generate one (their model's Edit page → \"let an agent contribute\"), or use apply_refinement with an edit token.");
99
+ const id = modelIdOr(modelId);
100
+ const doc = await fetchCmp(id).catch(() => ({}));
101
+ const body = { type: "hunchful.refinement", modelId: id, libraryVersion: doc.libraryVersion || "v1", contributionToken: CONTRIB_TOKEN, items: normalizeItems(items) };
102
+ const r = await fetch(`${BASE}/api/contribute`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
103
+ const j = await r.json().catch(() => ({}));
104
+ if (!r.ok) return fail(j.debugMessage || j.error || `HTTP ${r.status}`);
105
+ return ok({ status: "pending", ...j, note: "The owner confirms this in their refine page before it applies." });
106
+ } catch (e) {
107
+ return fail(e.message || e);
108
+ }
109
+ }
110
+ );
111
+
112
+ server.tool(
113
+ "apply_refinement",
114
+ "Apply a refinement directly as one logged revision (append evidence, nudge confidence). Needs HUNCHFUL_EDIT_TOKEN — the owner's key. Prefer propose_refinement unless the owner asked you to write directly.",
115
+ {
116
+ modelId: z.string().optional(),
117
+ items: z.array(z.object(itemShape)).min(1),
118
+ },
119
+ async ({ modelId, items }) => {
120
+ try {
121
+ if (!EDIT_TOKEN) return fail("No HUNCHFUL_EDIT_TOKEN set. Direct apply needs the owner's edit key. Use propose_refinement instead (with a contribution token).");
122
+ const id = modelIdOr(modelId);
123
+ const doc = await fetchCmp(id).catch(() => ({}));
124
+ const body = { type: "hunchful.refinement", modelId: id, libraryVersion: doc.libraryVersion || "v1", items: normalizeItems(items), editToken: EDIT_TOKEN };
125
+ const r = await fetch(`${BASE}/api/apply`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
126
+ const j = await r.json().catch(() => ({}));
127
+ if (!r.ok) return fail(j.debugMessage || j.error || `HTTP ${r.status}`);
128
+ return ok(j);
129
+ } catch (e) {
130
+ return fail(e.message || e);
131
+ }
132
+ }
133
+ );
134
+
135
+ const transport = new StdioServerTransport();
136
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "hunchful-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Hunchful — let an AI read and refine a Hunchful collaboration model directly, instead of copy-pasting links.",
5
+ "type": "module",
6
+ "bin": {
7
+ "hunchful-mcp": "index.mjs"
8
+ },
9
+ "files": [
10
+ "index.mjs",
11
+ "README.md"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
16
+ "dependencies": {
17
+ "@modelcontextprotocol/sdk": "^1.17.0",
18
+ "zod": "^3.23.8"
19
+ },
20
+ "license": "MIT",
21
+ "author": "Filip Nilsson",
22
+ "repository": "github:filinils/cognitive-model-protocol"
23
+ }