@tenpo/mcp 0.4.1 → 0.4.2
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/dist/index.js +102 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -100,19 +100,103 @@ async function ensureApiKey() {
|
|
|
100
100
|
log(`Failed to issue API key: ${String(err).slice(0, 120)}`);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
+
// ── Soul loading (full operator persona) ─────────────────────────────────────
|
|
104
|
+
//
|
|
105
|
+
// The host AI's serverInfo.instructions is the one place where ~62 KB of
|
|
106
|
+
// operator psychology can ship for "free": Claude prompt-caches it after
|
|
107
|
+
// the first turn, so subsequent turns pay zero tokens for the soul content
|
|
108
|
+
// while still carrying the entire body in context.
|
|
109
|
+
//
|
|
110
|
+
// Versus inlining soul in every tenpo_think pack — that pays the full token
|
|
111
|
+
// cost every call AND blows past context limits.
|
|
112
|
+
//
|
|
113
|
+
// We fetch /api/v1/resources/soul (5 always-on skills concatenated:
|
|
114
|
+
// tenpo-soul + tenpo-db-core + tenpo-memory + tenpo-operator-core +
|
|
115
|
+
// tenpo-psychology-core). Disk-cached with 24h TTL so subsequent MCP
|
|
116
|
+
// starts are instant.
|
|
117
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
118
|
+
import { homedir } from "node:os";
|
|
119
|
+
import { dirname as pathDirname } from "node:path";
|
|
120
|
+
const SOUL_CACHE_PATH = `${homedir()}/.tenpo-mcp/soul-cache.txt`;
|
|
121
|
+
const SOUL_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
122
|
+
function readSoulFromDisk() {
|
|
123
|
+
try {
|
|
124
|
+
if (!existsSync(SOUL_CACHE_PATH))
|
|
125
|
+
return null;
|
|
126
|
+
const content = readFileSync(SOUL_CACHE_PATH, "utf8");
|
|
127
|
+
// First line is timestamp; rest is content
|
|
128
|
+
const newline = content.indexOf("\n");
|
|
129
|
+
if (newline < 0)
|
|
130
|
+
return null;
|
|
131
|
+
const ts = parseInt(content.slice(0, newline), 10);
|
|
132
|
+
if (!Number.isFinite(ts) || Date.now() - ts > SOUL_CACHE_TTL_MS)
|
|
133
|
+
return null;
|
|
134
|
+
return content.slice(newline + 1);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function writeSoulToDisk(soul) {
|
|
141
|
+
try {
|
|
142
|
+
const dir = pathDirname(SOUL_CACHE_PATH);
|
|
143
|
+
if (!existsSync(dir))
|
|
144
|
+
mkdirSync(dir, { recursive: true });
|
|
145
|
+
writeFileSync(SOUL_CACHE_PATH, `${Date.now()}\n${soul}`, "utf8");
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Cache miss next time — not fatal
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async function loadFullSoul() {
|
|
152
|
+
// Try disk first
|
|
153
|
+
const cached = readSoulFromDisk();
|
|
154
|
+
if (cached && cached.length > 1000) {
|
|
155
|
+
log(`Soul loaded from cache (${cached.length} chars)`);
|
|
156
|
+
return cached;
|
|
157
|
+
}
|
|
158
|
+
// Fetch from Tenpo. Soul resource doesn't require merchant context.
|
|
159
|
+
try {
|
|
160
|
+
await ensureApiKey();
|
|
161
|
+
const result = (await tenpoFetch("/api/v1/resources/soul"));
|
|
162
|
+
const skills = result?.data?.always_on_skills ?? [];
|
|
163
|
+
if (skills.length === 0) {
|
|
164
|
+
log("Soul resource returned empty — falling back to lite brief");
|
|
165
|
+
return "";
|
|
166
|
+
}
|
|
167
|
+
const fullSoul = skills
|
|
168
|
+
.map((s) => `## ${s.id}\n${s.description ? `_${s.description}_\n\n` : ""}${s.content}`)
|
|
169
|
+
.join("\n\n---\n\n");
|
|
170
|
+
writeSoulToDisk(fullSoul);
|
|
171
|
+
log(`Soul loaded from Tenpo (${fullSoul.length} chars, ${skills.length} skills, cached to disk)`);
|
|
172
|
+
return fullSoul;
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
log(`Failed to load soul: ${String(err).slice(0, 120)}`);
|
|
176
|
+
return "";
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const FULL_SOUL = await loadFullSoul();
|
|
103
180
|
// ── MCP server ──────────────────────────────────────────────────────────────
|
|
104
181
|
/**
|
|
105
182
|
* Operator brief injected into every host AI's context at MCP connection time.
|
|
106
183
|
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
184
|
+
* serverInfo.instructions is THE field for shipping operator psychology to
|
|
185
|
+
* the host AI. It's loaded ONCE at MCP handshake and prompt-cached for the
|
|
186
|
+
* rest of the session by Claude — so the 62 KB of soul content is
|
|
187
|
+
* effectively free after the first turn.
|
|
188
|
+
*
|
|
189
|
+
* Layout:
|
|
190
|
+
* 1. Operational pointer (how to use Tenpo: call tenpo_think first, read
|
|
191
|
+
* every pack layer, etc.)
|
|
192
|
+
* 2. FULL SOUL — the 5 always-on skills (tenpo-soul + tenpo-db-core +
|
|
193
|
+
* tenpo-memory + tenpo-operator-core + tenpo-psychology-core) that the
|
|
194
|
+
* in-app Tenpo agent loads. Without these the host AI sounds generic.
|
|
110
195
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* fallback, playbook library, and internal-name scrubbing.
|
|
196
|
+
* Per-pack `psychology` field stays slim (just a pointer back to "see your
|
|
197
|
+
* instructions") because the soul content is already in the session context.
|
|
114
198
|
*/
|
|
115
|
-
const
|
|
199
|
+
const TENPO_OPERATOR_BRIEF_POINTER = `You have Tenpo — the operator system for this merchant. 200+ tools, 215 expert playbooks, per-merchant DuckDB, network telemetry. Tenpo handles orchestration; your model does the reasoning.
|
|
116
200
|
|
|
117
201
|
# Step 1: call tenpo_think FIRST for any non-trivial question
|
|
118
202
|
|
|
@@ -156,6 +240,14 @@ Format: ONE headline sentence with the surprising number → markdown table →
|
|
|
156
240
|
7. Custom integrations are universal — tenpo_add_custom_connector + tenpo_call_integration for ANY HTTP service.
|
|
157
241
|
|
|
158
242
|
You ARE the operator running alongside this store. Direct, dense, data-grounded. Every sentence carries information.`;
|
|
243
|
+
// Final operator instructions = pointer + full soul concatenated.
|
|
244
|
+
// This gets loaded into host AI context ONCE at MCP connect and prompt-cached
|
|
245
|
+
// for the rest of the session. Subsequent tool calls pay zero tokens for the
|
|
246
|
+
// soul content. ~65 KB total: 2 KB pointer + 60 KB soul (5 always-on skills).
|
|
247
|
+
const TENPO_OPERATOR_BRIEF = TENPO_OPERATOR_BRIEF_POINTER +
|
|
248
|
+
(FULL_SOUL
|
|
249
|
+
? `\n\n# ═══════════════════════════════════════════════════════════════\n# TENPO OPERATOR PSYCHOLOGY — full content, always-on\n# These 5 skills define how you think. Internalize them. They tell\n# you WHO you are (operator, not generic AI), WHAT you can't do\n# (fabricate), and HOW to respond (output format, charts, tables).\n# Per-pack \`psychology\` field is a pointer back to this section —\n# you already have the full content here, no need to re-fetch.\n# ═══════════════════════════════════════════════════════════════\n\n${FULL_SOUL}`
|
|
250
|
+
: "");
|
|
159
251
|
const server = new Server({
|
|
160
252
|
name: "tenpo",
|
|
161
253
|
version: "0.2.0",
|
|
@@ -169,10 +261,9 @@ const server = new Server({
|
|
|
169
261
|
// Desktop / Claude Code) handle it, others return method-not-found and
|
|
170
262
|
// we fall back to BM25 gracefully.
|
|
171
263
|
},
|
|
172
|
-
// serverInfo.instructions:
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
// Code, Codex, etc.) without an LLM running server-side.
|
|
264
|
+
// serverInfo.instructions: ONE-TIME context injection at MCP handshake.
|
|
265
|
+
// Host AI loads + prompt-caches the entire 65 KB. Per-pack psychology
|
|
266
|
+
// field stays slim because soul is already in session context.
|
|
176
267
|
instructions: TENPO_OPERATOR_BRIEF,
|
|
177
268
|
});
|
|
178
269
|
// ── Sampling capability detection ───────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tenpo/mcp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Tenpo — the operator that runs alongside your store. Connects to 45+ commerce platforms (Shopify, Klaviyo, Meta Ads, GA4, Stripe…) and gives any AI host (Claude Desktop, Cursor, Claude Code, ChatGPT) deterministic answers about your sales, ads, email, inventory, suppliers, customers, finance, and competitors. Free tier, no card required.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://tenpo.ai",
|