@thesapientcompany/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.
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/client.js +165 -0
- package/dist/index.js +215 -0
- package/knowledge-base.txt +245 -0
- package/package.json +40 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The Sapient Company
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# @thesapientcompany/mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for the **Sapient** brain-response API. It lets any MCP client — Claude Code, Claude Desktop, Cursor, etc. — submit content to Sapient, read scan results, query your cross-scan intelligence, and read the Sapient knowledge base.
|
|
4
|
+
|
|
5
|
+
Sapient predicts how a real human brain would respond to a piece of content (video, audio, image, or text) — second by second — and distills that into a score, a grade, and seven lenses.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Add it to Claude Code with one command:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
claude mcp add sapient --env SAPIENT_API_KEY=sk_live_… -- npx -y @thesapientcompany/mcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or run the binary directly:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
SAPIENT_API_KEY=sk_live_… npx -y @thesapientcompany/mcp
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Claude Desktop / generic MCP config
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"mcpServers": {
|
|
26
|
+
"sapient": {
|
|
27
|
+
"command": "npx",
|
|
28
|
+
"args": ["-y", "@thesapientcompany/mcp"],
|
|
29
|
+
"env": { "SAPIENT_API_KEY": "sk_live_…" }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Remote / HTTP note
|
|
36
|
+
|
|
37
|
+
This package is a **stdio** server (it runs locally and talks to the hosted Sapient API at `https://www.thesapientcompany.com`). A hosted, remote MCP endpoint over Streamable HTTP is on the roadmap; until then, run this stdio server locally with your `sk_live_` key. You can point it at a different API host with `SAPIENT_BASE_URL`.
|
|
38
|
+
|
|
39
|
+
## Configuration
|
|
40
|
+
|
|
41
|
+
| Env var | Required | Description |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| `SAPIENT_API_KEY` | yes | Your `sk_live_…` key (Settings → API at thesapientcompany.com). |
|
|
44
|
+
| `SAPIENT_BASE_URL` | no | Override the API base URL (default `https://www.thesapientcompany.com`). |
|
|
45
|
+
|
|
46
|
+
## Tools
|
|
47
|
+
|
|
48
|
+
| Tool | What it does |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `run_scan` | Submit content (`url` **or** `text`); pick `lenses` / `options`; set `wait: true` to poll to completion and return the full result. |
|
|
51
|
+
| `get_scan` | Fetch one scan by `scan_id` — `{ status }` while running, the rich result (timeline, moments, summary) when complete. |
|
|
52
|
+
| `list_lenses` | The 7 selectable lenses with one-line descriptions. |
|
|
53
|
+
| `my_history` | Your scan history (`GET /v1/scans`). |
|
|
54
|
+
| `my_intelligence` | Your cross-scan patterns + semantic recall (`GET /v1/intelligence?q=`). |
|
|
55
|
+
|
|
56
|
+
### The 7 lenses
|
|
57
|
+
|
|
58
|
+
`attention`, `purchase_intent`, `manipulation`, `emotion`, `cognitive_effort`, `memory`, `surprise`. The default top-3 (when you don't choose) are **attention, purchase_intent, manipulation**.
|
|
59
|
+
|
|
60
|
+
## Resources
|
|
61
|
+
|
|
62
|
+
| URI | What it is |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `sapient://knowledge-base` | Full-text explanation of how the Sapient model works — the pipeline, the 7 networks, the lenses, and how to read a scan. |
|
|
65
|
+
|
|
66
|
+
## Errors
|
|
67
|
+
|
|
68
|
+
- **401** → `SAPIENT_API_KEY` is missing/invalid — set a valid `sk_live_` key.
|
|
69
|
+
- **402** → out of credits — add API credits in Settings → API.
|
|
70
|
+
- **403** → a paid plan is required to use the API.
|
|
71
|
+
- **404** → no such scan, or it isn't yours.
|
|
72
|
+
- **429** → rate limited; retry with backoff.
|
|
73
|
+
|
|
74
|
+
## Build from source
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm install
|
|
78
|
+
npm run build # tsc → dist/
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin typed client for the Sapient API (v1). Uses global fetch (Node >= 18).
|
|
3
|
+
*
|
|
4
|
+
* Base URL defaults to https://www.thesapientcompany.com and may be overridden
|
|
5
|
+
* with SAPIENT_BASE_URL. Auth is a Bearer `sk_live_…` key from SAPIENT_API_KEY.
|
|
6
|
+
*
|
|
7
|
+
* Endpoints:
|
|
8
|
+
* POST /v1/scans submit a scan -> { scan_id, status, lenses }
|
|
9
|
+
* GET /v1/scans/{id} poll one scan -> rich shape when complete
|
|
10
|
+
* GET /v1/scans caller's history -> { object, total, scans, ... }
|
|
11
|
+
* GET /v1/intelligence?q= cross-scan rollup -> { scan_count, ... , recall? }
|
|
12
|
+
*/
|
|
13
|
+
export const DEFAULT_BASE_URL = 'https://www.thesapientcompany.com';
|
|
14
|
+
/** The 7 selectable lenses, with one-line descriptions. */
|
|
15
|
+
export const LENSES = [
|
|
16
|
+
{ key: 'attention', description: 'How much the content holds focus, second by second.' },
|
|
17
|
+
{ key: 'purchase_intent', description: 'Approach-vs-avoid buy signal — desire to act.' },
|
|
18
|
+
{ key: 'manipulation', description: 'Emotional pull vs. reasoning — how much it pushes vs. persuades.' },
|
|
19
|
+
{ key: 'emotion', description: 'Emotional valence and intensity of the response.' },
|
|
20
|
+
{ key: 'cognitive_effort', description: 'How hard the brain is working to process the content.' },
|
|
21
|
+
{ key: 'memory', description: 'How likely the content is to stick and be recalled later.' },
|
|
22
|
+
{ key: 'surprise', description: 'Involuntary salience — how much something grabs attention.' },
|
|
23
|
+
];
|
|
24
|
+
export const LENS_KEYS = LENSES.map((l) => l.key);
|
|
25
|
+
export const DEFAULT_LENSES = ['attention', 'purchase_intent', 'manipulation'];
|
|
26
|
+
export class SapientApiError extends Error {
|
|
27
|
+
status;
|
|
28
|
+
code;
|
|
29
|
+
constructor(status, message, code) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = 'SapientApiError';
|
|
32
|
+
this.status = status;
|
|
33
|
+
this.code = code;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Map an HTTP error into a clear, actionable message. */
|
|
37
|
+
function explain(status, bodyMsg, bodyCode) {
|
|
38
|
+
switch (status) {
|
|
39
|
+
case 401:
|
|
40
|
+
return 'Unauthorized (401): set SAPIENT_API_KEY to a valid sk_live_ key.';
|
|
41
|
+
case 402:
|
|
42
|
+
return 'Insufficient balance (402): add API credits in Settings → API at thesapientcompany.com.';
|
|
43
|
+
case 403:
|
|
44
|
+
return `Plan required (403): ${bodyMsg || 'upgrade to a paid plan to use the API.'}`;
|
|
45
|
+
case 404:
|
|
46
|
+
return 'Not found (404): no such scan, or it does not belong to your org.';
|
|
47
|
+
case 429:
|
|
48
|
+
return 'Rate limited (429): retry with backoff.';
|
|
49
|
+
default:
|
|
50
|
+
return bodyMsg
|
|
51
|
+
? `Sapient API error (${status}${bodyCode ? ` ${bodyCode}` : ''}): ${bodyMsg}`
|
|
52
|
+
: `Sapient API error (${status}).`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export class SapientClient {
|
|
56
|
+
apiKey;
|
|
57
|
+
baseUrl;
|
|
58
|
+
constructor(opts) {
|
|
59
|
+
this.apiKey = (opts?.apiKey ?? process.env.SAPIENT_API_KEY ?? '').trim();
|
|
60
|
+
this.baseUrl = (opts?.baseUrl ?? process.env.SAPIENT_BASE_URL ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
61
|
+
}
|
|
62
|
+
hasKey() {
|
|
63
|
+
return Boolean(this.apiKey);
|
|
64
|
+
}
|
|
65
|
+
requireKey() {
|
|
66
|
+
if (!this.apiKey) {
|
|
67
|
+
throw new SapientApiError(401, 'Unauthorized (401): set SAPIENT_API_KEY to a valid sk_live_ key.', 'unauthorized');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async request(method, path, body) {
|
|
71
|
+
this.requireKey();
|
|
72
|
+
const url = `${this.baseUrl}${path}`;
|
|
73
|
+
let res;
|
|
74
|
+
try {
|
|
75
|
+
res = await fetch(url, {
|
|
76
|
+
method,
|
|
77
|
+
headers: {
|
|
78
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
79
|
+
'Content-Type': 'application/json',
|
|
80
|
+
Accept: 'application/json',
|
|
81
|
+
},
|
|
82
|
+
body: body != null ? JSON.stringify(body) : undefined,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
throw new SapientApiError(0, `Network error calling ${url}: ${e?.message || e}`);
|
|
87
|
+
}
|
|
88
|
+
const text = await res.text();
|
|
89
|
+
let json = null;
|
|
90
|
+
try {
|
|
91
|
+
json = text ? JSON.parse(text) : null;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
/* non-JSON body */
|
|
95
|
+
}
|
|
96
|
+
if (!res.ok) {
|
|
97
|
+
throw new SapientApiError(res.status, explain(res.status, json?.message, json?.error), json?.error);
|
|
98
|
+
}
|
|
99
|
+
return json ?? {};
|
|
100
|
+
}
|
|
101
|
+
/** POST /v1/scans — submit. Returns { scan_id, status, lenses }. */
|
|
102
|
+
async submitScan(input) {
|
|
103
|
+
const hasUrl = typeof input.url === 'string' && input.url.trim();
|
|
104
|
+
const hasText = typeof input.text === 'string' && input.text.trim();
|
|
105
|
+
if (!hasUrl && !hasText) {
|
|
106
|
+
throw new SapientApiError(400, 'Provide either a url or text to scan.', 'bad_request');
|
|
107
|
+
}
|
|
108
|
+
const inputObj = hasUrl
|
|
109
|
+
? { type: 'url', url: input.url.trim(), ...(input.modality ? { modality: input.modality } : {}) }
|
|
110
|
+
: { type: 'text', text: input.text.trim() };
|
|
111
|
+
const payload = { input: inputObj };
|
|
112
|
+
if (input.lenses && input.lenses.length)
|
|
113
|
+
payload.lenses = input.lenses;
|
|
114
|
+
if (input.options)
|
|
115
|
+
payload.options = input.options;
|
|
116
|
+
if (input.webhook_url)
|
|
117
|
+
payload.webhook_url = input.webhook_url;
|
|
118
|
+
return this.request('POST', '/v1/scans', payload);
|
|
119
|
+
}
|
|
120
|
+
/** GET /v1/scans/{id}. While in-flight: { scan_id, status }. Complete: rich shape. */
|
|
121
|
+
async getScan(scanId) {
|
|
122
|
+
return this.request('GET', `/v1/scans/${encodeURIComponent(scanId)}`);
|
|
123
|
+
}
|
|
124
|
+
/** GET /v1/scans — caller's history. */
|
|
125
|
+
async listScans(opts) {
|
|
126
|
+
const qs = new URLSearchParams();
|
|
127
|
+
if (opts?.limit != null)
|
|
128
|
+
qs.set('limit', String(opts.limit));
|
|
129
|
+
if (opts?.offset != null)
|
|
130
|
+
qs.set('offset', String(opts.offset));
|
|
131
|
+
const suffix = qs.toString() ? `?${qs}` : '';
|
|
132
|
+
return this.request('GET', `/v1/scans${suffix}`);
|
|
133
|
+
}
|
|
134
|
+
/** GET /v1/intelligence?q= — cross-scan rollup + optional semantic recall. */
|
|
135
|
+
async intelligence(q) {
|
|
136
|
+
const suffix = q && q.trim() ? `?q=${encodeURIComponent(q.trim())}` : '';
|
|
137
|
+
return this.request('GET', `/v1/intelligence${suffix}`);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Submit then poll GET /v1/scans/{id} until status is terminal.
|
|
141
|
+
* Returns the final scan object. Throws on timeout.
|
|
142
|
+
*/
|
|
143
|
+
async runScanToCompletion(input, opts) {
|
|
144
|
+
const submitted = await this.submitScan(input);
|
|
145
|
+
const intervalMs = opts?.intervalMs ?? 5000;
|
|
146
|
+
const timeoutMs = opts?.timeoutMs ?? 5 * 60 * 1000;
|
|
147
|
+
const started = Date.now();
|
|
148
|
+
// Small initial delay so the run can register.
|
|
149
|
+
await sleep(Math.min(intervalMs, 2000));
|
|
150
|
+
while (true) {
|
|
151
|
+
const scan = await this.getScan(submitted.scan_id);
|
|
152
|
+
const status = scan?.status;
|
|
153
|
+
opts?.onTick?.(status);
|
|
154
|
+
if (status === 'complete' || status === 'error' || status === 'cancelled')
|
|
155
|
+
return scan;
|
|
156
|
+
if (Date.now() - started > timeoutMs) {
|
|
157
|
+
throw new SapientApiError(0, `Timed out after ${Math.round(timeoutMs / 1000)}s waiting for scan ${submitted.scan_id} (last status: ${status}). It is still running — poll get_scan later.`);
|
|
158
|
+
}
|
|
159
|
+
await sleep(intervalMs);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
export function sleep(ms) {
|
|
164
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
165
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Sapient MCP server (stdio).
|
|
4
|
+
*
|
|
5
|
+
* Exposes the Sapient brain-response API to any MCP client (Claude Desktop,
|
|
6
|
+
* Claude Code, etc.) as tools + a knowledge-base resource.
|
|
7
|
+
*
|
|
8
|
+
* Tools:
|
|
9
|
+
* run_scan submit content (url or text); optionally poll to completion
|
|
10
|
+
* get_scan poll one scan by id
|
|
11
|
+
* list_lenses the 7 selectable lenses + one-line descriptions
|
|
12
|
+
* my_history GET /v1/scans (caller's scan history)
|
|
13
|
+
* my_intelligence GET /v1/intelligence?q= (cross-scan patterns + recall)
|
|
14
|
+
*
|
|
15
|
+
* Resources:
|
|
16
|
+
* sapient://knowledge-base the full Sapient knowledge base (how the model works)
|
|
17
|
+
*
|
|
18
|
+
* Config (env):
|
|
19
|
+
* SAPIENT_API_KEY required — an sk_live_ key
|
|
20
|
+
* SAPIENT_BASE_URL optional — override the API base URL
|
|
21
|
+
*/
|
|
22
|
+
import { readFileSync } from 'node:fs';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
import { dirname, join } from 'node:path';
|
|
25
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
26
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
27
|
+
import { z } from 'zod';
|
|
28
|
+
import { SapientClient, SapientApiError, LENSES, LENS_KEYS } from './client.js';
|
|
29
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
/** Load the bundled knowledge base (shipped next to dist/). */
|
|
31
|
+
function loadKnowledgeBase() {
|
|
32
|
+
const candidates = [
|
|
33
|
+
join(__dirname, '..', 'knowledge-base.txt'),
|
|
34
|
+
join(__dirname, 'knowledge-base.txt'),
|
|
35
|
+
];
|
|
36
|
+
for (const p of candidates) {
|
|
37
|
+
try {
|
|
38
|
+
return readFileSync(p, 'utf8');
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
/* try next */
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return 'Sapient knowledge base not bundled. See https://docs.thesapientcompany.com';
|
|
45
|
+
}
|
|
46
|
+
const KNOWLEDGE_BASE = loadKnowledgeBase();
|
|
47
|
+
const client = new SapientClient();
|
|
48
|
+
const server = new McpServer({ name: 'sapient', version: '0.1.0' });
|
|
49
|
+
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
50
|
+
function ok(text) {
|
|
51
|
+
return { content: [{ type: 'text', text }] };
|
|
52
|
+
}
|
|
53
|
+
function fail(text) {
|
|
54
|
+
return { content: [{ type: 'text', text }], isError: true };
|
|
55
|
+
}
|
|
56
|
+
function errText(e) {
|
|
57
|
+
if (e instanceof SapientApiError)
|
|
58
|
+
return e.message;
|
|
59
|
+
return `Unexpected error: ${e?.message || String(e)}`;
|
|
60
|
+
}
|
|
61
|
+
function json(value) {
|
|
62
|
+
return JSON.stringify(value, null, 2);
|
|
63
|
+
}
|
|
64
|
+
// ── Resource: knowledge base ─────────────────────────────────────────────────
|
|
65
|
+
server.registerResource('knowledge-base', 'sapient://knowledge-base', {
|
|
66
|
+
title: 'Sapient knowledge base',
|
|
67
|
+
description: 'Full-text explanation of how the Sapient brain-response model works: the pipeline, the 7 networks, the lenses, and how to read a scan.',
|
|
68
|
+
mimeType: 'text/plain',
|
|
69
|
+
}, async (uri) => ({
|
|
70
|
+
contents: [{ uri: uri.href, mimeType: 'text/plain', text: KNOWLEDGE_BASE }],
|
|
71
|
+
}));
|
|
72
|
+
// ── Tool: list_lenses ────────────────────────────────────────────────────────
|
|
73
|
+
server.registerTool('list_lenses', {
|
|
74
|
+
title: 'List lenses',
|
|
75
|
+
description: 'List the 7 selectable Sapient lenses with one-line descriptions. Default top-3 when none are picked: attention, purchase_intent, manipulation.',
|
|
76
|
+
inputSchema: {},
|
|
77
|
+
}, async () => {
|
|
78
|
+
const lines = LENSES.map((l) => `- ${l.key}: ${l.description}`).join('\n');
|
|
79
|
+
return ok(`Sapient lenses (default top-3 = attention, purchase_intent, manipulation):\n\n${lines}`);
|
|
80
|
+
});
|
|
81
|
+
// ── Tool: run_scan ───────────────────────────────────────────────────────────
|
|
82
|
+
server.registerTool('run_scan', {
|
|
83
|
+
title: 'Run a scan',
|
|
84
|
+
description: "Submit content to Sapient and get a brain-response scan. Provide exactly one of `url` (a public video/audio/image URL) or `text`. Optionally choose `lenses` (any of the 7; default top-3) and `options`. Set `wait: true` to poll until the scan completes and return the full result; otherwise returns { scan_id, status } for you to poll later with get_scan.",
|
|
85
|
+
inputSchema: {
|
|
86
|
+
url: z
|
|
87
|
+
.string()
|
|
88
|
+
.url()
|
|
89
|
+
.optional()
|
|
90
|
+
.describe('Public asset URL (video, audio, or image). Provide this OR text.'),
|
|
91
|
+
text: z.string().optional().describe('Raw copy to analyze. Provide this OR url.'),
|
|
92
|
+
modality: z
|
|
93
|
+
.enum(['video', 'audio', 'image'])
|
|
94
|
+
.optional()
|
|
95
|
+
.describe('Optional hint when url is supplied (default video).'),
|
|
96
|
+
lenses: z
|
|
97
|
+
.array(z.enum(LENS_KEYS))
|
|
98
|
+
.optional()
|
|
99
|
+
.describe('Lenses to compute. Default top-3: attention, purchase_intent, manipulation.'),
|
|
100
|
+
options: z
|
|
101
|
+
.object({
|
|
102
|
+
include_reasons: z.boolean().optional(),
|
|
103
|
+
include_raw: z.boolean().optional(),
|
|
104
|
+
include_fmri: z.boolean().optional(),
|
|
105
|
+
include_benchmark: z.boolean().optional(),
|
|
106
|
+
})
|
|
107
|
+
.optional()
|
|
108
|
+
.describe('Toggle reasons / raw networks / fMRI url / benchmark percentiles.'),
|
|
109
|
+
webhook_url: z
|
|
110
|
+
.string()
|
|
111
|
+
.url()
|
|
112
|
+
.optional()
|
|
113
|
+
.describe('Optional http(s) URL to receive a POST when the scan completes.'),
|
|
114
|
+
wait: z
|
|
115
|
+
.boolean()
|
|
116
|
+
.optional()
|
|
117
|
+
.describe('Poll until the scan completes (or fails) and return the full result.'),
|
|
118
|
+
timeout_sec: z
|
|
119
|
+
.number()
|
|
120
|
+
.int()
|
|
121
|
+
.positive()
|
|
122
|
+
.max(600)
|
|
123
|
+
.optional()
|
|
124
|
+
.describe('Max seconds to wait when wait=true (default 300).'),
|
|
125
|
+
},
|
|
126
|
+
}, async (args) => {
|
|
127
|
+
try {
|
|
128
|
+
const input = {
|
|
129
|
+
url: args.url,
|
|
130
|
+
text: args.text,
|
|
131
|
+
modality: args.modality,
|
|
132
|
+
lenses: args.lenses,
|
|
133
|
+
options: args.options,
|
|
134
|
+
webhook_url: args.webhook_url,
|
|
135
|
+
};
|
|
136
|
+
if (args.wait) {
|
|
137
|
+
const scan = await client.runScanToCompletion(input, {
|
|
138
|
+
timeoutMs: (args.timeout_sec ?? 300) * 1000,
|
|
139
|
+
});
|
|
140
|
+
if (scan?.status !== 'complete') {
|
|
141
|
+
return fail(`Scan ${scan?.scan_id ?? ''} ended with status "${scan?.status}".\n\n${json(scan)}`);
|
|
142
|
+
}
|
|
143
|
+
return ok(`Scan complete.\n\n${json(scan)}`);
|
|
144
|
+
}
|
|
145
|
+
const submitted = await client.submitScan(input);
|
|
146
|
+
return ok(`Scan submitted (status: ${submitted.status}). Poll get_scan with scan_id="${submitted.scan_id}".\n\n${json(submitted)}`);
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
return fail(errText(e));
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
// ── Tool: get_scan ───────────────────────────────────────────────────────────
|
|
153
|
+
server.registerTool('get_scan', {
|
|
154
|
+
title: 'Get a scan',
|
|
155
|
+
description: 'Fetch one scan by id. While in-flight returns { scan_id, status }; when complete returns the rich result (timeline, moments, summary, optional raw/benchmark/fmri_url).',
|
|
156
|
+
inputSchema: {
|
|
157
|
+
scan_id: z.string().describe('The scan id returned by run_scan.'),
|
|
158
|
+
},
|
|
159
|
+
}, async ({ scan_id }) => {
|
|
160
|
+
try {
|
|
161
|
+
const scan = await client.getScan(scan_id);
|
|
162
|
+
return ok(json(scan));
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
return fail(errText(e));
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
// ── Tool: my_history ─────────────────────────────────────────────────────────
|
|
169
|
+
server.registerTool('my_history', {
|
|
170
|
+
title: 'My scan history',
|
|
171
|
+
description: "List the caller's scan history (most recent first).",
|
|
172
|
+
inputSchema: {
|
|
173
|
+
limit: z.number().int().positive().max(100).optional().describe('How many to return (1–100, default 25).'),
|
|
174
|
+
offset: z.number().int().min(0).optional().describe('Pagination offset.'),
|
|
175
|
+
},
|
|
176
|
+
}, async ({ limit, offset }) => {
|
|
177
|
+
try {
|
|
178
|
+
const data = await client.listScans({ limit, offset });
|
|
179
|
+
return ok(json(data));
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
return fail(errText(e));
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
// ── Tool: my_intelligence ────────────────────────────────────────────────────
|
|
186
|
+
server.registerTool('my_intelligence', {
|
|
187
|
+
title: 'My cross-scan intelligence',
|
|
188
|
+
description: "The caller's long-term, cross-scan brain intelligence: lens averages, average per-second curves, recurring weak spots, and (when q is given) semantic recall across past scans.",
|
|
189
|
+
inputSchema: {
|
|
190
|
+
q: z.string().optional().describe('Optional semantic query to recall matching moments across past scans.'),
|
|
191
|
+
},
|
|
192
|
+
}, async ({ q }) => {
|
|
193
|
+
try {
|
|
194
|
+
const data = await client.intelligence(q);
|
|
195
|
+
return ok(json(data));
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
return fail(errText(e));
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
// ── boot ─────────────────────────────────────────────────────────────────────
|
|
202
|
+
async function main() {
|
|
203
|
+
if (!client.hasKey()) {
|
|
204
|
+
// Don't crash — the handshake should still work so a client can list tools.
|
|
205
|
+
// Tool calls will return a clear 401 message. Surface a hint on stderr.
|
|
206
|
+
process.stderr.write('[sapient-mcp] Warning: SAPIENT_API_KEY is not set. Tool calls will fail with 401 until you set it.\n');
|
|
207
|
+
}
|
|
208
|
+
const transport = new StdioServerTransport();
|
|
209
|
+
await server.connect(transport);
|
|
210
|
+
process.stderr.write('[sapient-mcp] Sapient MCP server running on stdio.\n');
|
|
211
|
+
}
|
|
212
|
+
main().catch((e) => {
|
|
213
|
+
process.stderr.write(`[sapient-mcp] Fatal: ${e?.message || e}\n`);
|
|
214
|
+
process.exit(1);
|
|
215
|
+
});
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Sapient — Knowledge Base (full text)
|
|
2
|
+
|
|
3
|
+
> The brain-response API. Submit content (video, audio, image, or text); Sapient predicts how a real human brain would respond, second by second, then distills that into a score, a grade, and seven lenses. The predicted brain response is the ground truth; the lenses, scores, and grades are plain-language explanations built on top of it.
|
|
4
|
+
|
|
5
|
+
================================================================
|
|
6
|
+
# How Sapient works
|
|
7
|
+
|
|
8
|
+
Sapient is trained on real human fMRI brain responses. When you submit a piece of content, the model predicts how a human brain would respond to it — second by second — and distills that into a single, comparable read.
|
|
9
|
+
|
|
10
|
+
## Brain is the ground truth, AI is the explanation
|
|
11
|
+
|
|
12
|
+
The core idea is simple. The brain response is the measurement — the model predicts the actual pattern of activity a human brain would produce while watching, hearing, or reading your content. Everything above that is interpretation built on top of the measurement.
|
|
13
|
+
|
|
14
|
+
- The brain response is the ground truth: a predicted activation across the whole cortex, every second.
|
|
15
|
+
- The lenses, scores, and grades are supplementary — they translate that response into language a marketer or creator can act on.
|
|
16
|
+
|
|
17
|
+
So when a scan says "attention drops at second 6," that isn't a guess from a language model reading your script. It's a reduction of a predicted brain signal into a plain word.
|
|
18
|
+
|
|
19
|
+
## What a scan measures
|
|
20
|
+
|
|
21
|
+
Every scan produces:
|
|
22
|
+
|
|
23
|
+
- A headline score (0–100) — overall predicted engagement.
|
|
24
|
+
- A letter grade (A–F) — the same signal, at a glance.
|
|
25
|
+
- Seven lenses — interpretive cuts of the response (attention, purchase intent, manipulation, emotion, cognitive effort, memory, surprise).
|
|
26
|
+
- The raw brain output — per-network activations and per-second time series, for teams that want to go deeper.
|
|
27
|
+
|
|
28
|
+
## How to think about it
|
|
29
|
+
|
|
30
|
+
A Sapient score is a directional prediction, not a guarantee. Use it to compare versions of the same idea (A vs B), to find the moment a piece loses attention, or to sanity-check creative before you spend. It is most powerful when you scan often and compare.
|
|
31
|
+
|
|
32
|
+
================================================================
|
|
33
|
+
# How the model works
|
|
34
|
+
|
|
35
|
+
A scan moves through a fixed pipeline. Content goes in, a predicted brain response comes out, and that response is reduced step by step into something you can read at a glance. Each step is a faithful summary of the one before it — nothing is invented along the way.
|
|
36
|
+
|
|
37
|
+
1. Content comes in. You submit a piece of content: a video, an audio clip, an image, or text. This is the raw material the model will respond to.
|
|
38
|
+
2. Content becomes features. The content is turned into machine-readable features — what's being said, heard, and seen over time. Speech is transcribed, sound is characterized, and motion and imagery are tracked frame by frame. These features are the model's senses.
|
|
39
|
+
3. The model predicts a brain response. From those features, the model predicts a full brain response: activity across roughly 20,000 points on the cortex, for every moment of the content. This is the ground-truth measurement everything else is built on.
|
|
40
|
+
4. The brain is reduced to 7 networks per second. The cortex-wide response is summarized into 7 well-established brain networks, one value each, every second. Each network corresponds to a plain-English function — seeing, moving, focusing, alerting, feeling, reasoning, reflecting.
|
|
41
|
+
5. Networks blend into KPIs. The seven per-second networks are blended into 10 KPIs per second — intermediate measures that combine networks into more specific signals.
|
|
42
|
+
6. KPIs roll up into composites. The KPIs are grouped into 5 composites — higher-level summaries of how the content is performing.
|
|
43
|
+
7. Everything resolves into lenses and a score. Finally, the response resolves into the 7 lenses — attention, purchase intent, manipulation, emotion, cognitive effort, memory, and surprise — plus an overall score and grade. These are the human-readable answers.
|
|
44
|
+
|
|
45
|
+
## Why the reduction matters
|
|
46
|
+
|
|
47
|
+
Each layer is a lossy but honest summary of the one beneath it. The deepest layer — the predicted brain response — is the most precise and the least readable. The shallowest layer — the score — is the easiest to act on and the least detailed. You choose how deep to go depending on the question you're asking.
|
|
48
|
+
|
|
49
|
+
================================================================
|
|
50
|
+
# The 7 networks
|
|
51
|
+
|
|
52
|
+
The brain doesn't work as one undivided whole. Decades of fMRI research describe the cortex as a set of large-scale networks, each handling a broad kind of work. Sapient summarizes its predicted brain response into seven of these networks, one value each, every second. Reading the networks is the most direct way to understand why a lens moved. Every lens is a blend of these seven signals.
|
|
53
|
+
|
|
54
|
+
The seven networks:
|
|
55
|
+
|
|
56
|
+
- Visual — Seeing & imagery. How much the content is engaging the eyes — visual detail, motion, and mental imagery.
|
|
57
|
+
- Somatomotor — Body & movement / voice & sound. Response to physical action, gesture, and the texture of voice and sound.
|
|
58
|
+
- Dorsal Attention — Focused attention. How hard the viewer is deliberately concentrating — the focus network.
|
|
59
|
+
- Ventral Attention — Salience & surprise. How much something is grabbing attention involuntarily — the alerting network.
|
|
60
|
+
- Limbic — Emotion & reward. Emotional pull and the sense of reward or desire.
|
|
61
|
+
- Frontoparietal — Reasoning & effort. Active thinking, problem-solving, and mental work.
|
|
62
|
+
- Default Mode — Reflection, memory & self-relevance. Inward processing — relating content to yourself, your memories, and meaning.
|
|
63
|
+
|
|
64
|
+
How to read them:
|
|
65
|
+
|
|
66
|
+
- Visual rises with rich, moving, or detailed imagery; it falls on static or empty frames.
|
|
67
|
+
- Somatomotor lifts with physical action, gesture, and the grain of a voice or a sound.
|
|
68
|
+
- Dorsal Attention is voluntary focus — the viewer choosing to lock in.
|
|
69
|
+
- Ventral Attention is involuntary capture — a cut, a noise, or a twist yanking attention without permission.
|
|
70
|
+
- Limbic is the heart of emotional and reward responses; it's the engine behind both feeling and wanting.
|
|
71
|
+
- Frontoparietal rises when the viewer has to work — to follow, decode, or reason through something.
|
|
72
|
+
- Default Mode rises when content turns inward — memory, self-relevance, reflection, meaning.
|
|
73
|
+
|
|
74
|
+
================================================================
|
|
75
|
+
# From networks to lenses
|
|
76
|
+
|
|
77
|
+
The seven networks are the raw signal. The seven lenses are the answers. Each lens is a question, and each question is best answered by a particular blend of networks. The model reads the second-by-second networks and resolves them into a single 0–100 read per lens — and tracks how that read rises and falls across the content. No language model is guessing here. The lens is a reduction of the predicted brain response, not an opinion about your script.
|
|
78
|
+
|
|
79
|
+
Which networks drive which lens:
|
|
80
|
+
|
|
81
|
+
- Attention — reads most from Dorsal Attention and Ventral Attention. Question: Is the viewer locked in?
|
|
82
|
+
- Purchase Intent — reads most from Limbic (reward). Question: Will they want it?
|
|
83
|
+
- Manipulation — reads most from Limbic and Ventral Attention versus Frontoparietal. Question: Is it pressuring more than persuading?
|
|
84
|
+
- Emotion — reads most from Limbic. Question: How emotionally charged is this moment?
|
|
85
|
+
- Cognitive Effort — reads most from Frontoparietal. Question: How much mental work is this taking?
|
|
86
|
+
- Memory — reads most from Default Mode. Question: Will this be remembered and absorbed?
|
|
87
|
+
- Surprise — reads most from Ventral Attention. Question: Was that unexpected?
|
|
88
|
+
|
|
89
|
+
Why this is the "why": Because every lens traces back to specific networks, you can always ask why a number is what it is. A high Attention read with a low Memory read means the focus networks are firing but the reflection network isn't — the content grabs but doesn't stick. A high Manipulation read means emotional and salience networks are doing the heavy lifting while the reasoning network stays quiet. The lenses are deliberately overlapping cuts of one underlying response — it lets you see the same moment from seven angles at once.
|
|
90
|
+
|
|
91
|
+
================================================================
|
|
92
|
+
# The 7 lenses (overview)
|
|
93
|
+
|
|
94
|
+
A scan returns seven lenses. Each is a 0–100 read on one dimension of how a brain responds to your content — and each traces back to specific brain networks, so every number has a why.
|
|
95
|
+
|
|
96
|
+
- Attention — Whether the viewer is locked in, and where focus slips.
|
|
97
|
+
- Purchase Intent — Reward and desire; whether they'll want it.
|
|
98
|
+
- Manipulation — Persuasive or coercive load; pressure vs. reasoning.
|
|
99
|
+
- Emotion — Emotional salience; how much the content makes you feel.
|
|
100
|
+
- Cognitive Effort — Mental work; how hard the content is to process.
|
|
101
|
+
- Memory — Whether the content will be remembered and absorbed.
|
|
102
|
+
- Surprise — Novelty; how unexpected a moment is.
|
|
103
|
+
|
|
104
|
+
Choosing a lens:
|
|
105
|
+
|
|
106
|
+
- For ads and creative, start with Attention and Purchase Intent.
|
|
107
|
+
- For brand and narrative, lean on Emotion and Memory.
|
|
108
|
+
- For explainers and education, watch Cognitive Effort and Memory.
|
|
109
|
+
- Use Manipulation as a guardrail, and Surprise to find the hook and the twists.
|
|
110
|
+
|
|
111
|
+
================================================================
|
|
112
|
+
# Lens: Attention
|
|
113
|
+
|
|
114
|
+
The Attention lens analyzes for whether the viewer is locked in. It reads both kinds of focus: the deliberate concentration of someone choosing to pay attention, and the involuntary pull of something that grabs them whether they meant to look or not. Tracked second by second, it shows exactly where a piece holds and where it lets go.
|
|
115
|
+
|
|
116
|
+
What it analyzes for: sustained focus across the timeline; the moment attention drops off; whether the opening earns the first few seconds.
|
|
117
|
+
|
|
118
|
+
When to use it:
|
|
119
|
+
- Finding the drop-off in a video ad — spot the exact second viewers disengage.
|
|
120
|
+
- Testing a hook — compare the first three seconds of two openings; the one that holds Attention earlier wins the scroll.
|
|
121
|
+
- Pacing a long-form edit — find slow stretches that need trimming before the viewer drifts.
|
|
122
|
+
|
|
123
|
+
Reading the score:
|
|
124
|
+
- High — the content is holding focus. Viewers are locked in and staying with it.
|
|
125
|
+
- Low — focus is slipping. The content isn't earning continued attention, and viewers are likely to scroll or tune out.
|
|
126
|
+
|
|
127
|
+
================================================================
|
|
128
|
+
# Lens: Purchase Intent
|
|
129
|
+
|
|
130
|
+
The Purchase Intent lens analyzes for desire. It reads the brain's reward response — the pull toward wanting, approaching, and acquiring. Where Attention tells you whether someone is watching, Purchase Intent tells you whether watching is turning into wanting.
|
|
131
|
+
|
|
132
|
+
What it analyzes for: reward and desire as the content unfolds; whether a product reveal lands as appealing; the moment interest tips into intent.
|
|
133
|
+
|
|
134
|
+
When to use it:
|
|
135
|
+
- Timing the product reveal — see whether desire peaks when the product appears.
|
|
136
|
+
- Comparing two offers — read which one drives a stronger reward response.
|
|
137
|
+
- Choosing a thumbnail or hero shot — pick the frame with the highest Purchase Intent read.
|
|
138
|
+
|
|
139
|
+
Reading the score:
|
|
140
|
+
- High — the content is generating real desire. The reward response is firing.
|
|
141
|
+
- Low — the content isn't creating pull. It may inform or entertain, but it isn't making anyone want the thing.
|
|
142
|
+
|
|
143
|
+
================================================================
|
|
144
|
+
# Lens: Manipulation
|
|
145
|
+
|
|
146
|
+
The Manipulation lens analyzes for persuasive and coercive load. It reads how much the content is leaning on emotional pressure and involuntary triggers versus reasoning and genuine appeal. It's best used as a guardrail — a check on how a piece is winning people, not just whether it's working.
|
|
147
|
+
|
|
148
|
+
What it analyzes for: emotional pressure and urgency tactics; coercive load — pushing rather than convincing; the balance between feeling and reasoning.
|
|
149
|
+
|
|
150
|
+
When to use it:
|
|
151
|
+
- Brand-safety review — make sure persuasion isn't tipping into pressure that could damage trust.
|
|
152
|
+
- Diagnosing a high-converting but risky ad — see whether the desire is earned or pressured.
|
|
153
|
+
- Comparing a hard sell vs. a soft sell — see how much each leans on coercive load.
|
|
154
|
+
|
|
155
|
+
Reading the score:
|
|
156
|
+
- High — the content is leaning hard on emotional pressure and involuntary triggers rather than reasoning. A flag worth a second look.
|
|
157
|
+
- Low — the content is persuading through genuine appeal and reasoning, with little coercive load.
|
|
158
|
+
|
|
159
|
+
================================================================
|
|
160
|
+
# Lens: Emotion
|
|
161
|
+
|
|
162
|
+
The Emotion lens analyzes for emotional salience — how much the content moves the viewer. It reads the brain's emotional response and tracks where feeling rises and falls across the timeline.
|
|
163
|
+
|
|
164
|
+
What it analyzes for: the strength of the emotional response; the moment a piece lands or falls flat; whether the emotional beat arrives where you intended.
|
|
165
|
+
|
|
166
|
+
When to use it:
|
|
167
|
+
- Placing the emotional peak in a brand film — confirm the climactic beat produces the strongest response.
|
|
168
|
+
- Testing music or voiceover — read which soundtrack raises emotional salience more.
|
|
169
|
+
- Checking a story that should move people — if Emotion stays flat, the story isn't landing.
|
|
170
|
+
|
|
171
|
+
Reading the score:
|
|
172
|
+
- High — the content is emotionally charged. It's producing a strong feeling response.
|
|
173
|
+
- Low — the content is emotionally flat. It may be clear, but it isn't moving anyone.
|
|
174
|
+
|
|
175
|
+
================================================================
|
|
176
|
+
# Lens: Cognitive Effort
|
|
177
|
+
|
|
178
|
+
The Cognitive Effort lens analyzes for mental work. It reads how hard the viewer's brain is working to follow, decode, and reason through the content. Some effort is good — it means the viewer is engaged. Too much means the content is hard to follow and people give up.
|
|
179
|
+
|
|
180
|
+
What it analyzes for: how much active thinking the content demands; the point where a piece becomes too hard to follow; whether an explanation is clear or overloaded.
|
|
181
|
+
|
|
182
|
+
When to use it:
|
|
183
|
+
- Simplifying an explainer — find the moment effort spikes, where the message gets too dense.
|
|
184
|
+
- Pacing a complex pitch — if effort stays high throughout, spread the load or cut the complexity.
|
|
185
|
+
- Checking a deliberately challenging piece — confirm effort rises enough to signal engagement without tipping into confusion.
|
|
186
|
+
|
|
187
|
+
Reading the score:
|
|
188
|
+
- High — the content demands a lot of mental work. A risk of losing people if it stays high.
|
|
189
|
+
- Low — the content is easy to process. Effortless to follow, though it may not be challenging anyone.
|
|
190
|
+
|
|
191
|
+
================================================================
|
|
192
|
+
# Lens: Memory
|
|
193
|
+
|
|
194
|
+
The Memory lens analyzes for whether the content will stick. It reads the brain's inward, reflective processing — the activity tied to relating content to yourself, your memories, and meaning. Attention gets people watching; Memory tells you whether anything stays with them afterward.
|
|
195
|
+
|
|
196
|
+
What it analyzes for: how likely the content is to be encoded and remembered; whether a message is being absorbed or just passing through; the moments most likely to stay with the viewer.
|
|
197
|
+
|
|
198
|
+
When to use it:
|
|
199
|
+
- Checking whether a brand moment lands — read Memory at the logo or tagline reveal.
|
|
200
|
+
- Diagnosing high attention, low recall — a piece that holds Attention but scores low on Memory grabs focus without sticking.
|
|
201
|
+
- Comparing two ways to deliver a key message — pick the one that drives a stronger Memory read.
|
|
202
|
+
|
|
203
|
+
Reading the score:
|
|
204
|
+
- High — the content is being absorbed. Likely to be encoded and remembered.
|
|
205
|
+
- Low — the content is passing through. People may watch it, but little is staying with them.
|
|
206
|
+
|
|
207
|
+
================================================================
|
|
208
|
+
# Lens: Surprise
|
|
209
|
+
|
|
210
|
+
The Surprise lens analyzes for novelty. It reads the brain's alerting response — the involuntary spike that fires when something unexpected happens. Surprise is what makes a hook stop the scroll and a twist re-capture attention.
|
|
211
|
+
|
|
212
|
+
What it analyzes for: how unexpected each moment is; whether a hook genuinely breaks the pattern; the twists and turns that re-capture attention.
|
|
213
|
+
|
|
214
|
+
When to use it:
|
|
215
|
+
- Testing whether a hook surprises — a strong hook produces a clear spike in the opening seconds.
|
|
216
|
+
- Placing a twist or reveal — confirm the reveal lands as unexpected and isn't telegraphed too early.
|
|
217
|
+
- Refreshing a tired format — if Surprise stays flat, the content is too predictable.
|
|
218
|
+
|
|
219
|
+
Reading the score:
|
|
220
|
+
- High — the content is breaking expectations. Something unexpected is pulling attention in.
|
|
221
|
+
- Low — the content is predictable. It follows the pattern the viewer expected.
|
|
222
|
+
|
|
223
|
+
================================================================
|
|
224
|
+
# Data layers
|
|
225
|
+
|
|
226
|
+
A scan can answer at different depths. The same response can be returned as a precise, cortex-wide brain signal — or as a single sentence. The layers run from deepest (most precise, least readable) to simplest (most readable, least detailed).
|
|
227
|
+
|
|
228
|
+
- Per-vertex fMRI (deepest) — the full predicted brain response, activity across roughly 20,000 points on the cortex, for every moment. The ground truth. Use it for research, custom modeling, or the unreduced signal.
|
|
229
|
+
- Per-second networks — the response summarized into the 7 brain networks, one value each, every second. Use it to understand why the content performs the way it does.
|
|
230
|
+
- Per-second lens scores — the 7 lenses tracked second by second. Use it to see how each lens moves across the timeline.
|
|
231
|
+
- Moments — the notable peaks and drops. Use it to find the exact moments worth keeping or cutting.
|
|
232
|
+
- Summary (simplest) — overall score, grade, and a top-line take on each lens. Use it for a fast verdict or to compare versions.
|
|
233
|
+
|
|
234
|
+
When to use each: summary for a fast verdict; moments to find the fix; per-second lens scores for the full timeline; per-second networks to know why; per-vertex fMRI for research and custom modeling.
|
|
235
|
+
|
|
236
|
+
================================================================
|
|
237
|
+
# Reading a scan
|
|
238
|
+
|
|
239
|
+
A completed scan returns a result object.
|
|
240
|
+
|
|
241
|
+
Score and grade: score is a 0–100 prediction of overall engagement; grade is the same signal as a letter (A–F). Treat them as relative — most useful when comparing two versions of the same content rather than as an absolute pass/fail.
|
|
242
|
+
|
|
243
|
+
Lenses: lenses holds the seven reads (attention, purchase_intent, manipulation, emotion, cognitive_effort, memory, surprise), each 0–100. A high attention score with a low memory score, for example, means the content grabs focus but doesn't stick — a signal to strengthen the payoff.
|
|
244
|
+
|
|
245
|
+
Raw: raw is for teams that want to go deeper — per-network activations, per-second time series, and composite breakdowns. Use it to find the exact second attention peaks or drops.
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thesapientcompany/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Model Context Protocol (MCP) server for the Sapient brain-response API — run scans, read your scan history, query cross-scan intelligence, and read the Sapient knowledge base from any MCP client.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"sapient-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"knowledge-base.txt",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"sapient",
|
|
24
|
+
"mcp",
|
|
25
|
+
"model-context-protocol",
|
|
26
|
+
"brain",
|
|
27
|
+
"fmri",
|
|
28
|
+
"neuro",
|
|
29
|
+
"advertising",
|
|
30
|
+
"ai"
|
|
31
|
+
],
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
34
|
+
"zod": "^3.25.0 || ^4.0.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^20.14.0",
|
|
38
|
+
"typescript": "^5.5.0"
|
|
39
|
+
}
|
|
40
|
+
}
|