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.
- package/README.md +121 -0
- package/dist/index.js +188 -0
- 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
|
+
}
|