intentos-mcp 1.0.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 +121 -0
  2. package/dist/index.js +188 -0
  3. package/package.json +29 -0
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # intentos-mcp
2
+
3
+ > MCP server for IntentOS — gives any MCP-compatible AI client read/write access to a user's personal preference ledger.
4
+
5
+ ## What it does
6
+
7
+ [IntentOS](https://github.com/L0nE-F0x/intentos) is a personal preference ledger — a sovereign source of truth for what someone likes, who they trust, and what's worked or failed for them. This MCP server exposes that ledger to any agent harness that speaks the [Model Context Protocol](https://modelcontextprotocol.io): Claude Desktop, Cursor, Cline, Continue, OpenWebUI, and most modern agentic clients.
8
+
9
+ When connected, your agent can:
10
+
11
+ - **`query_preferences`** — semantic search across the ledger ("what kind of coffee does the user love?")
12
+ - **`get_category`** — read everything in a category ("show me their full headphones config")
13
+ - **`check_vendor_trust`** — check if a brand is trusted/distrusted before recommending it
14
+ - **`add_preferences`** — append new preferences or record purchase outcomes (write scope)
15
+ - **`record_audit_event`** — agents self-report what they did with the data
16
+
17
+ ## Quick start
18
+
19
+ ### 1. Issue an API key in IntentOS
20
+
21
+ Go to your IntentOS dashboard → **Agents → Connect agent** → name it ("Claude Desktop", "Cursor", whatever) → pick scopes → **Issue API key**. Copy the `sk_intent_…` key. (Shown once — store it safely.)
22
+
23
+ Recommended scopes for read-only agents:
24
+ - `preferences:read`
25
+ - `trust:read`
26
+ - `query:semantic`
27
+
28
+ Add `preferences:write` and `purchases:write` if you want the agent to record outcomes.
29
+
30
+ ### 2. Build the server
31
+
32
+ From the IntentOS repo:
33
+
34
+ ```bash
35
+ cd mcp
36
+ npm install
37
+ npm run build
38
+ ```
39
+
40
+ This compiles to `mcp/dist/index.js`. Note the absolute path — you'll need it.
41
+
42
+ ### 3. Configure your MCP client
43
+
44
+ #### Claude Desktop
45
+
46
+ Edit `claude_desktop_config.json`:
47
+
48
+ - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
49
+ - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
50
+ - **Linux**: `~/.config/Claude/claude_desktop_config.json`
51
+
52
+ ```json
53
+ {
54
+ "mcpServers": {
55
+ "intentos": {
56
+ "command": "node",
57
+ "args": ["/absolute/path/to/intentos/mcp/dist/index.js"],
58
+ "env": {
59
+ "INTENTOS_API_KEY": "sk_intent_...",
60
+ "INTENTOS_BASE_URL": "https://useintentos.com"
61
+ }
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ Restart Claude Desktop. You should see the **🔌 intentos** indicator in the bottom-right of any chat. Try: *"What kind of headphones do I love?"*
68
+
69
+ #### Cursor
70
+
71
+ Open Cursor settings → **MCP Servers** → add:
72
+
73
+ ```json
74
+ {
75
+ "intentos": {
76
+ "command": "node",
77
+ "args": ["/absolute/path/to/intentos/mcp/dist/index.js"],
78
+ "env": {
79
+ "INTENTOS_API_KEY": "sk_intent_...",
80
+ "INTENTOS_BASE_URL": "https://useintentos.com"
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ #### Cline / Continue / other MCP clients
87
+
88
+ The config format is the same — `command`, `args`, `env`. Consult your client's docs for where the config file lives.
89
+
90
+ ### 4. Verify
91
+
92
+ In a chat: *"Use IntentOS to find what kind of coffee I like and suggest a similar bean."*
93
+
94
+ The agent should call `query_preferences`, then `check_vendor_trust` on any vendor it suggests, and you'll see those calls in your **/dashboard/audit** log on IntentOS.
95
+
96
+ ## Environment variables
97
+
98
+ | Var | Required | Default | Purpose |
99
+ |---|---|---|---|
100
+ | `INTENTOS_API_KEY` | yes | — | The `sk_intent_…` key from `/dashboard/agents` |
101
+ | `INTENTOS_BASE_URL` | no | `https://useintentos.com` | Override if you self-host IntentOS at a different URL |
102
+
103
+ ## Security
104
+
105
+ The API key is stored in your MCP client's config file (typically `~/.config/...`) and passed to the server as an env var. It never leaves your machine except to talk to your IntentOS deployment. The IntentOS server stores only a sha256 hash of the key — the plaintext is your responsibility.
106
+
107
+ If a key is compromised: log into IntentOS, **/dashboard/agents** → trash icon to revoke, then issue a new key and update your MCP config.
108
+
109
+ ## Troubleshooting
110
+
111
+ **Agent can't see the tools / "intentos" not listed**: Make sure your config JSON is valid (mcpServers is the top-level key). Restart the MCP client fully — Claude Desktop in particular caches mcp configs.
112
+
113
+ **"INTENTOS_API_KEY is not set"**: The env var isn't reaching the subprocess. Double-check the `env` block in your config; on Windows make sure paths are forward-slashed or properly escaped.
114
+
115
+ **`401 invalid_api_key`**: Key was revoked or has a typo. Check `/dashboard/agents` and reissue if needed.
116
+
117
+ **`403 scope_required`**: The agent's API key doesn't have the scope it needs. Reissue with broader scopes (e.g. add `query:semantic` if `query_preferences` returns 403).
118
+
119
+ ## License
120
+
121
+ MIT.
package/dist/index.js ADDED
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+ //
3
+ // IntentOS MCP Server
4
+ //
5
+ // Exposes the IntentOS REST API to any MCP-compatible AI client (Claude
6
+ // Desktop, Cursor, Cline, Continue, etc.) so the agent can read the user's
7
+ // personal preference ledger before answering taste-sensitive questions.
8
+ //
9
+ // Configure via env vars:
10
+ // INTENTOS_API_KEY — required, sk_intent_… from /dashboard/agents
11
+ // INTENTOS_BASE_URL — optional, defaults to https://useintentos.com
12
+ //
13
+ // Usage with Claude Desktop, claude_desktop_config.json:
14
+ // {
15
+ // "mcpServers": {
16
+ // "intentos": {
17
+ // "command": "node",
18
+ // "args": ["/absolute/path/to/intentos/mcp/dist/index.js"],
19
+ // "env": {
20
+ // "INTENTOS_API_KEY": "sk_intent_..."
21
+ // }
22
+ // }
23
+ // }
24
+ // }
25
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
26
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
27
+ import { z } from "zod";
28
+ const API_KEY = process.env.INTENTOS_API_KEY?.trim();
29
+ const BASE_URL = (process.env.INTENTOS_BASE_URL ?? "https://useintentos.com").replace(/\/+$/, "");
30
+ if (!API_KEY) {
31
+ console.error("intentos-mcp: INTENTOS_API_KEY is not set. Issue a key at " +
32
+ `${BASE_URL}/dashboard/agents and pass it via the MCP client's env config.`);
33
+ process.exit(1);
34
+ }
35
+ // ── HTTP helpers ──────────────────────────────────────────────────────────
36
+ async function intentosRequest(path, init = {}) {
37
+ const res = await fetch(`${BASE_URL}${path}`, {
38
+ ...init,
39
+ headers: {
40
+ Authorization: `Bearer ${API_KEY}`,
41
+ "Content-Type": "application/json",
42
+ ...(init.headers ?? {}),
43
+ },
44
+ });
45
+ if (!res.ok) {
46
+ const body = await res.text().catch(() => "");
47
+ throw new Error(`IntentOS ${res.status}: ${body || res.statusText}`);
48
+ }
49
+ return (await res.json());
50
+ }
51
+ function asTextResult(payload) {
52
+ return {
53
+ content: [
54
+ {
55
+ type: "text",
56
+ text: typeof payload === "string" ? payload : JSON.stringify(payload, null, 2),
57
+ },
58
+ ],
59
+ };
60
+ }
61
+ // ── Server ────────────────────────────────────────────────────────────────
62
+ const server = new McpServer({
63
+ name: "intentos",
64
+ version: "1.0.0",
65
+ });
66
+ // 1) Semantic search across the ledger
67
+ server.tool("query_preferences", "Semantic search across the user's IntentOS ledger. Use this whenever the user is making a decision in a domain where their stored preferences might apply (shopping, travel, recommendations, food, gear, etc.). Returns matching preferences with similarity scores; filter by sentiment to separate things they love (positive) from things they avoid (negative). Always cite their preferences when explaining recommendations.", {
68
+ query: z
69
+ .string()
70
+ .min(1)
71
+ .describe("Natural-language question to match against the user's preferences. e.g. 'what kind of coffee do I drink', 'restaurants I avoid', 'travel mistakes'."),
72
+ k: z
73
+ .number()
74
+ .int()
75
+ .min(1)
76
+ .max(40)
77
+ .optional()
78
+ .describe("Maximum matches to return. Default 8."),
79
+ min_similarity: z
80
+ .number()
81
+ .min(0)
82
+ .max(1)
83
+ .optional()
84
+ .describe("Cosine-similarity threshold between 0 and 1. Default 0.5. Lower for more recall, higher for precision."),
85
+ category: z
86
+ .string()
87
+ .optional()
88
+ .describe("Optional category slug to scope the query (e.g. 'coffee', 'headphones')."),
89
+ }, async ({ query, k, min_similarity, category }) => {
90
+ const data = await intentosRequest("/api/v1/query", {
91
+ method: "POST",
92
+ body: JSON.stringify({ query, k, min_similarity, category }),
93
+ });
94
+ return asTextResult(data);
95
+ });
96
+ // 2) Read a full category
97
+ server.tool("get_category", "Read everything the user has captured in a specific preference category — structured fields, positive examples, negative examples. Use when the user has named a domain (e.g. 'help me pick coffee' → category: 'coffee'). Prefer this over query_preferences when you already know the category, since it's authoritative rather than similarity-based.", {
98
+ slug: z
99
+ .string()
100
+ .min(1)
101
+ .describe("The category slug as stored in IntentOS (lowercase, hyphenated). e.g. 'coffee', 'home-cooking', 'travel'."),
102
+ }, async ({ slug }) => {
103
+ const data = await intentosRequest(`/api/v1/preferences/${encodeURIComponent(slug)}`);
104
+ return asTextResult(data);
105
+ });
106
+ // 3) Trust lookup on a vendor
107
+ server.tool("check_vendor_trust", "Look up whether the user has flagged a specific vendor as trusted or distrusted, with their stated reason and a numeric trust score (0-100). ALWAYS call this before recommending a specific brand or merchant — distrusted vendors should be avoided, trusted ones can be cited as a reason.", {
108
+ vendor: z
109
+ .string()
110
+ .min(1)
111
+ .describe("Brand or merchant name. e.g. 'Sennheiser', 'Starbucks Reserve'."),
112
+ }, async ({ vendor }) => {
113
+ const data = await intentosRequest(`/api/v1/trust/${encodeURIComponent(vendor)}`);
114
+ return asTextResult(data);
115
+ });
116
+ // 4) Add new preferences (write — requires *:write scope on the API key)
117
+ server.tool("add_preferences", "Append new preferences to a category — useful when the user explicitly says 'remember that I…' or after they've confirmed a recommendation worked or failed. Requires the agent's API key to have the appropriate write scope (preferences:write or {category}:write). Use sparingly and only when the user clearly wants something persisted.", {
118
+ category: z
119
+ .string()
120
+ .min(1)
121
+ .describe("Category slug to append to. Must already exist in the ledger."),
122
+ positive_examples: z
123
+ .array(z.object({
124
+ label: z
125
+ .string()
126
+ .min(1)
127
+ .describe("Short label (e.g. 'Sennheiser HD600s')."),
128
+ detail: z.string().optional(),
129
+ tags: z.array(z.string()).optional(),
130
+ }))
131
+ .optional()
132
+ .describe("Things the user likes."),
133
+ negative_examples: z
134
+ .array(z.object({
135
+ label: z.string().min(1),
136
+ detail: z.string().optional(),
137
+ tags: z.array(z.string()).optional(),
138
+ }))
139
+ .optional()
140
+ .describe("Things the user dislikes or avoids."),
141
+ outcome: z
142
+ .object({
143
+ vendor: z.string().min(1),
144
+ product: z.string().min(1),
145
+ result: z.enum(["success", "partial", "failure"]),
146
+ rating: z.number().int().min(1).max(5).optional(),
147
+ price_cents: z.number().int().min(0).optional(),
148
+ notes: z.string().optional(),
149
+ })
150
+ .optional()
151
+ .describe("Optional purchase outcome — record that a specific product from a vendor worked, partially worked, or failed."),
152
+ }, async ({ category, ...body }) => {
153
+ const data = await intentosRequest(`/api/v1/preferences/${encodeURIComponent(category)}/update`, {
154
+ method: "POST",
155
+ body: JSON.stringify(body),
156
+ });
157
+ return asTextResult(data);
158
+ });
159
+ // 5) Self-report what the agent did with the data
160
+ server.tool("record_audit_event", "Optionally record what you did with the user's preferences — useful when an agent acts on retrieved data (e.g. 'used trust ledger to reject vendor X', 'recommended product Y based on category Z'). Helps the user see the full chain in their /dashboard/audit log.", {
161
+ endpoint: z
162
+ .string()
163
+ .min(1)
164
+ .describe("Logical endpoint or action name. e.g. 'recommendation/coffee', 'reject/vendor'."),
165
+ scope: z.string().optional(),
166
+ query: z.string().optional().describe("Original user prompt that triggered this."),
167
+ result_summary: z
168
+ .string()
169
+ .optional()
170
+ .describe("One-line summary of what you did."),
171
+ metadata: z.record(z.string(), z.unknown()).optional(),
172
+ }, async (body) => {
173
+ const data = await intentosRequest("/api/v1/audit", {
174
+ method: "POST",
175
+ body: JSON.stringify(body),
176
+ });
177
+ return asTextResult(data);
178
+ });
179
+ // ── Boot ──────────────────────────────────────────────────────────────────
180
+ async function main() {
181
+ const transport = new StdioServerTransport();
182
+ await server.connect(transport);
183
+ console.error(`intentos-mcp connected to ${BASE_URL} — ready to serve preference reads.`);
184
+ }
185
+ main().catch((err) => {
186
+ console.error("intentos-mcp fatal error:", err);
187
+ process.exit(1);
188
+ });
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "intentos-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for IntentOS — exposes a personal preference ledger to any MCP-compatible AI client (Claude Desktop, Cursor, Cline, Continue, etc.)",
5
+ "type": "module",
6
+ "bin": {
7
+ "intentos-mcp": "dist/index.js"
8
+ },
9
+ "main": "dist/index.js",
10
+ "files": [
11
+ "dist",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "start": "node dist/index.js",
17
+ "dev": "tsc --watch"
18
+ },
19
+ "keywords": ["mcp", "intentos", "agents", "preferences"],
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "@modelcontextprotocol/sdk": "^1.0.4",
23
+ "zod": "^3.24.1"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^22.10.2",
27
+ "typescript": "^5.7.2"
28
+ }
29
+ }