@chatpanel/gateway 0.6.35 → 0.6.36

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 CHANGED
@@ -172,6 +172,62 @@ Streaming (SSE) is supported on all three: placeholders are restored on the fly,
172
172
  holding back a tail so a token split across chunks (`[[PER` … `SON_1]]`) still
173
173
  restores cleanly.
174
174
 
175
+ ## Use it as an MCP server (Codex, Claude Code, any MCP client)
176
+
177
+ Point one MCP server at the gateway and any CLI agent can reach your **local history**
178
+ (past chats, meeting transcripts, notes — redacted on the way out) **and every skill
179
+ installed on your machine** — across Claude Code, Codex, Copilot, Gemini, Hermes,
180
+ `~/.agents/skills` and any folder you configure. History is served by the gateway; skills
181
+ are proxied from the [bridge](https://github.com/chatpanel/chatpanel-bridge) (optional — if
182
+ it is not running, the history tools still work and the skill tools say so).
183
+
184
+ It is a stdio MCP server: `chatpanel-gateway mcp`.
185
+
186
+ **Codex** — add to `~/.codex/config.toml`:
187
+
188
+ ```toml
189
+ [mcp_servers.chatpanel]
190
+ command = "chatpanel-gateway"
191
+ args = ["mcp"]
192
+ ```
193
+
194
+ **Claude Code** — one command (`--scope user` makes it available in every project):
195
+
196
+ ```bash
197
+ claude mcp add --scope user chatpanel chatpanel-gateway mcp
198
+ ```
199
+
200
+ **Any other MCP client** — run the stdio server `chatpanel-gateway mcp`, or point at it the
201
+ way your client configures a `command` + `args` stdio server.
202
+
203
+ > If your client launches with a stripped `PATH` and cannot find `chatpanel-gateway`, use
204
+ > the absolute path (find it with `which chatpanel-gateway`).
205
+
206
+ ### Tools it exposes
207
+
208
+ | Tool | What it does |
209
+ |------|--------------|
210
+ | `search_history` | Full-text search your chats, meetings and notes by relevance |
211
+ | `get_record` | Fetch one record's full text by id (`chat:…`, `meeting:…`, `note:…`) |
212
+ | `list_history` | Browse/page the corpus (newest first, no bodies) |
213
+ | `list_skills` | List every installed skill (name + one-line description, and where it came from) |
214
+ | `open_skill` | Load one skill's full instructions by name |
215
+ | `read_skill_file` | Read a reference file a skill's instructions point at |
216
+
217
+ Everything a history tool returns is **redacted** with the same engine
218
+ ([`@chatpanel/pii`](https://github.com/chatpanel/chatpanel-pii)) the gateway uses for model
219
+ traffic — the real values never leave your device. Skill scripts are never served as text.
220
+
221
+ ### One local view
222
+
223
+ ```bash
224
+ chatpanel-gateway local
225
+ ```
226
+
227
+ prints what is running — the gateway (this) and the bridge (your agents + skills) — so you
228
+ can see the whole local runtime at a glance. The bridge is an optional companion; a missing
229
+ one is reported plainly, never as an error.
230
+
175
231
  ## How it fits with ChatPanel
176
232
 
177
233
  The [extension](https://github.com/chatpanel/chatpanel-extension) redacts inside
@@ -192,6 +248,22 @@ agent's own multi-turn loop is blinded, not just the first prompt.
192
248
  edits. The default tier touches only structured secrets and (in `full`) detected
193
249
  entities — keep your dictionary prose-focused.
194
250
 
251
+ **Using it as an MCP server:**
252
+
253
+ - The **gateway must be running** for any of its tools to work — it is a background
254
+ service (`chatpanel-gateway --install` registers it to start at login). If a tool
255
+ returns *"the ChatPanel gateway is not running"*, start it and retry; the message tells
256
+ you the command.
257
+ - **Skills need the bridge** (an optional companion). Without it, the history tools still
258
+ work and the skill tools return a one-line *"the bridge is not running — install it
259
+ with …"* — no silent empty result.
260
+ - History tools search the gateway's **warm store**, which is seeded from your ChatPanel
261
+ backups. If `list_history` says it is empty, the gateway has not been seeded yet — open
262
+ ChatPanel so a backup lands, or check the [ingest docs](#endpoints).
263
+ - Everything returned is **redacted** at the configured tier. A tool result may contain a
264
+ placeholder like `[[EMAIL_1]]` where a value was blinded — that is the privacy guarantee
265
+ working, not a bug.
266
+
195
267
  ## License
196
268
 
197
269
  Source-available under the same license as the ChatPanel extension and bridge —
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatpanel/gateway",
3
- "version": "0.6.35",
3
+ "version": "0.6.36",
4
4
  "description": "Local privacy gateway \u2014 redacts PII out of OpenAI/Anthropic API traffic before it reaches a model, then restores it in the reply. Point opencode, codex, aider, Claude Code, etc. at it.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "scripts": {
14
14
  "start": "node bin/chatpanel-gateway.js",
15
- "test": "node --test",
15
+ "test": "node scripts/run-tests.mjs",
16
16
  "typecheck": "tsc -p tsconfig.json",
17
17
  "build:bin": "bash scripts/build-binaries.sh"
18
18
  },
@@ -42,9 +42,9 @@ async function probe(url, path = '/health') {
42
42
  * A structured picture of the local runtime — for the `local` command and for the gateway
43
43
  * to log at startup. Pure except the two probes; the caller decides how to render it.
44
44
  */
45
- export async function localStatus() {
46
- const gwUrl = gatewayUrl();
47
- const brUrl = bridgeUrl();
45
+ export async function localStatus({ gatewayUrl: gwOverride, bridgeUrl: brOverride } = {}) {
46
+ const gwUrl = gwOverride || gatewayUrl();
47
+ const brUrl = brOverride || bridgeUrl();
48
48
  const [gw, br] = await Promise.all([probe(gwUrl), probe(brUrl)]);
49
49
  const skills = br.ok ? await probe(brUrl, '/skills').then((r) => (r.ok ? (r.data.skills || []).length : null)).catch(() => null) : null;
50
50
  return {
@@ -94,11 +94,12 @@ export function formatLocalStatus(s) {
94
94
  * One-line note for the gateway to log at startup, so the operator sees the unified picture
95
95
  * without running anything. Never throws; a probe failure just says "not detected".
96
96
  */
97
- export async function bridgePresenceNote() {
98
- const br = await probe(bridgeUrl());
97
+ export async function bridgePresenceNote(brOverride) {
98
+ const url = brOverride || bridgeUrl();
99
+ const br = await probe(url);
99
100
  if (br.ok) {
100
101
  const n = (br.data.skills?.count ?? null);
101
- return `bridge detected at ${bridgeUrl()} (v${br.data.version}${n != null ? `, ${n} skills` : ''}) — its agents and skills are available through this gateway.`;
102
+ return `bridge detected at ${url} (v${br.data.version}${n != null ? `, ${n} skills` : ''}) — its agents and skills are available through this gateway.`;
102
103
  }
103
- return `bridge not detected at ${bridgeUrl()} — local agents/skills are unavailable until it runs (curl -fsSL https://dl.chatpanel.net/bridge/install.sh | bash). The gateway runs fine without it.`;
104
+ return `bridge not detected at ${url} — local agents/skills are unavailable until it runs (curl -fsSL https://dl.chatpanel.net/bridge/install.sh | bash). The gateway runs fine without it.`;
104
105
  }
package/src/mcp.js CHANGED
@@ -120,9 +120,16 @@ const TOOLS = [
120
120
  ];
121
121
 
122
122
  async function gatewayJson(path, init) {
123
- const res = await fetch(baseUrl() + path, init);
123
+ let res;
124
+ try {
125
+ res = await fetch(baseUrl() + path, init);
126
+ } catch (e) {
127
+ // The most common cause by far: the gateway service is not running. Say so, and how to
128
+ // fix it, so the agent can relay something actionable instead of a bare fetch error.
129
+ throw new Error(`the ChatPanel gateway is not running at ${baseUrl()} — start it with "chatpanel-gateway --install" (or run "chatpanel-gateway"), then retry. [${e.message}]`);
130
+ }
124
131
  const data = await res.json().catch(() => ({}));
125
- if (!res.ok) throw new Error(data?.error?.message || `gateway ${res.status}`);
132
+ if (!res.ok) throw new Error(data?.error?.message || `the gateway returned HTTP ${res.status} for ${path}`);
126
133
  return data;
127
134
  }
128
135
 
@@ -153,7 +160,7 @@ async function callTool(name, args = {}) {
153
160
  if (name === 'list_skills') {
154
161
  let data;
155
162
  try { data = await bridgeJson('/skills'); }
156
- catch (e) { return `The ChatPanel bridge is not reachable (${e.message}), so installed skills are unavailable. Start it to use skills.`; }
163
+ catch (e) { return `Installed skills are unavailable because the ChatPanel bridge is not running at ${bridgeBase()}. Install/start it with:\n curl -fsSL https://dl.chatpanel.net/bridge/install.sh | bash\nThen retry. History tools work without the bridge. [${e.message}]`; }
157
164
  const rows = data.skills || [];
158
165
  if (!rows.length) return 'No skills installed on this machine yet.';
159
166
  return [`${rows.length} skill(s) installed:`, ...rows.map((r) => `- ${r.command || r.id}: ${r.description || r.name}${r.origin?.source ? ` (from ${r.origin.source})` : ''}`)].join('\n') + '\n\nUse open_skill with a name to load its instructions.';
@@ -161,7 +168,7 @@ async function callTool(name, args = {}) {
161
168
  if (name === 'open_skill') {
162
169
  let data;
163
170
  try { data = await bridgeJson(`/skills/${encodeURIComponent(String(args.name || '').trim())}`); }
164
- catch (e) { return `Could not open "${args.name}": ${e.message}`; }
171
+ catch (e) { return `Could not open "${args.name}": ${e.message}. If the bridge isn't running, start it: curl -fsSL https://dl.chatpanel.net/bridge/install.sh | bash`; }
165
172
  return data.skill?.prompt || '(this skill has no extra instructions — just apply it.)';
166
173
  }
167
174
  if (name === 'read_skill_file') {
@@ -169,7 +176,7 @@ async function callTool(name, args = {}) {
169
176
  const path = String(args.path || '').trim().split('/').map(encodeURIComponent).join('/');
170
177
  let data;
171
178
  try { data = await bridgeJson(`/skills/${skill}/file/${path}`); }
172
- catch (e) { return `Could not read ${args.path}: ${e.message}`; }
179
+ catch (e) { return `Could not read ${args.path}: ${e.message}. If the bridge isn't running, start it: curl -fsSL https://dl.chatpanel.net/bridge/install.sh | bash`; }
173
180
  return data.text || '(empty)';
174
181
  }
175
182
  throw new Error(`unknown tool: ${name}`);
package/src/server.js CHANGED
@@ -45,7 +45,7 @@ import * as openai from './openai.js';
45
45
  import * as responses from './responses.js';
46
46
  import * as anthropic from './anthropic.js';
47
47
 
48
- export const VERSION = '0.6.35';
48
+ export const VERSION = '0.6.36';
49
49
 
50
50
  // WARM search tier — SQLite + FTS5 record store (falls back to an encrypted-JSON
51
51
  // store if SQLite can't load), fed by the extension's ingest sync + backup-ingest.