@chatpanel/gateway 0.6.33 → 0.6.34
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/package.json +2 -2
- package/src/mcp.js +81 -0
- package/src/server.js +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatpanel/gateway",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"description": "Local privacy gateway
|
|
3
|
+
"version": "0.6.34",
|
|
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": {
|
|
7
7
|
"chatpanel-gateway": "bin/chatpanel-gateway.js"
|
package/src/mcp.js
CHANGED
|
@@ -7,8 +7,15 @@
|
|
|
7
7
|
// It PROXIES to the already-running gateway's HTTP API (127.0.0.1:<port>), so there
|
|
8
8
|
// is exactly one warm store (the service's) and this process never opens the DB.
|
|
9
9
|
// JSON-RPC 2.0 over stdio, newline-delimited — implemented directly (zero deps).
|
|
10
|
+
//
|
|
11
|
+
// The gateway is the SUPERSET (docs/bridge-gateway-unification.md, U2): this one MCP
|
|
12
|
+
// server exposes both the gateway's warm history (chats/meetings/notes, redacted) AND the
|
|
13
|
+
// bridge's on-disk skills — so a CLI adds ONE server and gets everything ChatPanel local
|
|
14
|
+
// can do. History tools proxy to the gateway; skill tools proxy to the bridge. The bridge
|
|
15
|
+
// is optional: if it is not running, the skill tools say so instead of failing the connect.
|
|
10
16
|
|
|
11
17
|
import { loadConfig } from './config.js';
|
|
18
|
+
import { readBridgeToken } from './bridge.js';
|
|
12
19
|
|
|
13
20
|
const PROTOCOL_VERSION = '2024-11-05';
|
|
14
21
|
const SERVER = { name: 'chatpanel-history', version: '1.0.0' };
|
|
@@ -25,6 +32,32 @@ function baseUrl() {
|
|
|
25
32
|
return `http://127.0.0.1:${port}`;
|
|
26
33
|
}
|
|
27
34
|
|
|
35
|
+
// The bridge the gateway fronts. Its skills live on disk, so the skill tools proxy here
|
|
36
|
+
// rather than through the gateway's history API.
|
|
37
|
+
function bridgeBase() {
|
|
38
|
+
try {
|
|
39
|
+
return String(loadConfig().bridge?.url || 'http://127.0.0.1:4319').replace(/\/+$/, '');
|
|
40
|
+
} catch {
|
|
41
|
+
return 'http://127.0.0.1:4319';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function bridgeToken() {
|
|
45
|
+
try {
|
|
46
|
+
return readBridgeToken(loadConfig().bridge?.token || '');
|
|
47
|
+
} catch {
|
|
48
|
+
return readBridgeToken('');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function bridgeJson(path) {
|
|
52
|
+
const token = bridgeToken();
|
|
53
|
+
const res = await fetch(bridgeBase() + path, {
|
|
54
|
+
headers: { Accept: 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
|
|
55
|
+
});
|
|
56
|
+
const data = await res.json().catch(() => ({}));
|
|
57
|
+
if (!res.ok || data.ok === false) throw new Error(data?.error || `bridge ${res.status}`);
|
|
58
|
+
return data;
|
|
59
|
+
}
|
|
60
|
+
|
|
28
61
|
const TOOLS = [
|
|
29
62
|
{
|
|
30
63
|
name: 'search_history',
|
|
@@ -58,6 +91,32 @@ const TOOLS = [
|
|
|
58
91
|
},
|
|
59
92
|
},
|
|
60
93
|
},
|
|
94
|
+
{
|
|
95
|
+
name: 'list_skills',
|
|
96
|
+
description: 'List the reusable skills installed on this machine (via the ChatPanel bridge) — across every agent harness (Claude Code, Codex, Copilot, Gemini, Hermes) and any configured folder. Returns each skill\'s name and one-line description. Call open_skill to load the one that fits the task.',
|
|
97
|
+
inputSchema: { type: 'object', properties: {} },
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'open_skill',
|
|
101
|
+
description: 'Load one skill\'s full instructions by name (from list_skills), then follow them. If the instructions point at reference files, read one with read_skill_file.',
|
|
102
|
+
inputSchema: {
|
|
103
|
+
type: 'object',
|
|
104
|
+
properties: { name: { type: 'string', description: 'The skill name from list_skills.' } },
|
|
105
|
+
required: ['name'],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'read_skill_file',
|
|
110
|
+
description: 'Read one reference file a skill\'s instructions point at (any path inside the skill\'s own folder). Use only when the task needs it.',
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: 'object',
|
|
113
|
+
properties: {
|
|
114
|
+
name: { type: 'string', description: 'The skill name.' },
|
|
115
|
+
path: { type: 'string', description: 'The reference path as written in the instructions, e.g. references/auth.md.' },
|
|
116
|
+
},
|
|
117
|
+
required: ['name', 'path'],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
61
120
|
];
|
|
62
121
|
|
|
63
122
|
async function gatewayJson(path, init) {
|
|
@@ -91,6 +150,28 @@ async function callTool(name, args = {}) {
|
|
|
91
150
|
if (!items.length) return 'History is empty (or the gateway has not been seeded yet).';
|
|
92
151
|
return [`${items.length} of ${data.total} records:`, ...items.map((it) => `[${it.id}] ${it.title || '(untitled)'} · ${it.type}${it.date ? ' · ' + new Date(it.date).toISOString().slice(0, 10) : ''} · ${it.chars} chars`)].join('\n');
|
|
93
152
|
}
|
|
153
|
+
if (name === 'list_skills') {
|
|
154
|
+
let data;
|
|
155
|
+
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.`; }
|
|
157
|
+
const rows = data.skills || [];
|
|
158
|
+
if (!rows.length) return 'No skills installed on this machine yet.';
|
|
159
|
+
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.';
|
|
160
|
+
}
|
|
161
|
+
if (name === 'open_skill') {
|
|
162
|
+
let data;
|
|
163
|
+
try { data = await bridgeJson(`/skills/${encodeURIComponent(String(args.name || '').trim())}`); }
|
|
164
|
+
catch (e) { return `Could not open "${args.name}": ${e.message}`; }
|
|
165
|
+
return data.skill?.prompt || '(this skill has no extra instructions — just apply it.)';
|
|
166
|
+
}
|
|
167
|
+
if (name === 'read_skill_file') {
|
|
168
|
+
const skill = encodeURIComponent(String(args.name || '').trim());
|
|
169
|
+
const path = String(args.path || '').trim().split('/').map(encodeURIComponent).join('/');
|
|
170
|
+
let data;
|
|
171
|
+
try { data = await bridgeJson(`/skills/${skill}/file/${path}`); }
|
|
172
|
+
catch (e) { return `Could not read ${args.path}: ${e.message}`; }
|
|
173
|
+
return data.text || '(empty)';
|
|
174
|
+
}
|
|
94
175
|
throw new Error(`unknown tool: ${name}`);
|
|
95
176
|
}
|
|
96
177
|
|
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.
|
|
48
|
+
export const VERSION = '0.6.34';
|
|
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.
|