@tenpo/mcp 0.4.1 → 0.5.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 (2) hide show
  1. package/dist/index.js +102 -11
  2. 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
- * Per MCP spec, `serverInfo.instructions` is THE field for telling the host AI
108
- * how to behave when this server is loaded. Without this, the host falls back
109
- * to its generic system promptand Tenpo MCP responses end up generic.
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
- * Distilled from `/skills/tenpo-soul/SKILL.md` (472 lines ~1.6KB). Covers:
112
- * identity, zero-fabrication, response standard, tool chaining, sparse-data
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 TENPO_OPERATOR_BRIEF = `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.
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: read by every MCP host at connection time and
173
- // injected into the host AI's context. This is how we ship Tenpo's
174
- // operator psychology to coding agents (Claude Desktop, Cursor, Claude
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.1",
3
+ "version": "0.5.0",
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",