contextwise 0.1.0 → 0.2.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/README.md +64 -0
- package/dist/{chunk-P7JW7EPW.js → chunk-5ZMX5TEM.js} +395 -1190
- package/dist/chunk-5ZMX5TEM.js.map +1 -0
- package/dist/chunk-OYCA37PJ.js +710 -0
- package/dist/chunk-OYCA37PJ.js.map +1 -0
- package/dist/chunk-WP7RH2Z2.js +417 -0
- package/dist/chunk-WP7RH2Z2.js.map +1 -0
- package/dist/cli.js +26 -89
- package/dist/cli.js.map +1 -1
- package/dist/client-3GQWPGI3.js +9 -0
- package/dist/client-3GQWPGI3.js.map +1 -0
- package/dist/index.d.ts +56 -24
- package/dist/index.js +49 -31
- package/dist/sync_manager-2WYXXGCN.js +10 -0
- package/dist/sync_manager-2WYXXGCN.js.map +1 -0
- package/package.json +2 -1
- package/dist/chunk-P7JW7EPW.js.map +0 -1
|
@@ -1,329 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
headers: z.record(z.string()).default({}),
|
|
13
|
-
transport: z.enum(["streamable-http", "sse", "auto"]).default("auto"),
|
|
14
|
-
autoReconnect: z.boolean().default(true)
|
|
15
|
-
});
|
|
16
|
-
var UpstreamServerConfigSchema = z.union([
|
|
17
|
-
StdioUpstreamConfigSchema,
|
|
18
|
-
HttpUpstreamConfigSchema
|
|
19
|
-
]);
|
|
20
|
-
function warnIfInsecureHttp(urlStr, headers) {
|
|
21
|
-
try {
|
|
22
|
-
const parsed = new URL(urlStr);
|
|
23
|
-
if (parsed.protocol === "http:") {
|
|
24
|
-
const isLocalhost = ["localhost", "127.0.0.1", "::1", "[::1]"].includes(parsed.hostname);
|
|
25
|
-
if (!isLocalhost && headers) {
|
|
26
|
-
const hasAuthHeader = Object.keys(headers).some(
|
|
27
|
-
(h) => ["authorization", "cookie", "x-api-key", "api-key", "token"].includes(h.toLowerCase())
|
|
28
|
-
);
|
|
29
|
-
if (hasAuthHeader) {
|
|
30
|
-
console.warn(
|
|
31
|
-
`[SECURITY WARNING] Upstream URL "${urlStr}" is using unencrypted HTTP with sensitive authentication headers to non-localhost destination "${parsed.hostname}".`
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
} catch {
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
function isStdioUpstream(config) {
|
|
40
|
-
return "command" in config;
|
|
41
|
-
}
|
|
42
|
-
function isHttpUpstream(config) {
|
|
43
|
-
return "url" in config;
|
|
44
|
-
}
|
|
45
|
-
var ProxyConfigSchema = z.object({
|
|
46
|
-
transport: z.enum(["stdio", "sse", "http"]).default("stdio"),
|
|
47
|
-
port: z.number().int().min(1024).max(65535).default(3456),
|
|
48
|
-
logLevel: z.enum(["debug", "info", "warn", "error", "silent"]).default("info")
|
|
49
|
-
});
|
|
50
|
-
var RoutingConfigSchema = z.object({
|
|
51
|
-
strategy: z.enum(["hybrid", "bm25", "vector", "passthrough"]).default("hybrid"),
|
|
52
|
-
topK: z.number().int().min(1).max(50).default(5),
|
|
53
|
-
similarityThreshold: z.number().min(0).max(1).default(0.45),
|
|
54
|
-
pinnedTools: z.array(z.string()).default([]),
|
|
55
|
-
maxActiveTools: z.number().int().min(1).max(100).default(10),
|
|
56
|
-
enableBrowseServers: z.boolean().default(false),
|
|
57
|
-
enableAddServer: z.boolean().default(false),
|
|
58
|
-
allowCustomCommands: z.boolean().default(false),
|
|
59
|
-
persistAddedServers: z.boolean().default(false)
|
|
60
|
-
});
|
|
61
|
-
var GuardrailsConfigSchema = z.object({
|
|
62
|
-
enableCache: z.boolean().default(true),
|
|
63
|
-
cacheTtlSeconds: z.number().int().min(1).default(120),
|
|
64
|
-
maxCallsPerMinute: z.number().int().min(1).default(60),
|
|
65
|
-
loopBreakerThreshold: z.number().int().min(1).default(3),
|
|
66
|
-
callTimeoutMs: z.number().int().min(1e3).default(3e4)
|
|
67
|
-
});
|
|
68
|
-
var ContextWiseConfigSchema = z.object({
|
|
69
|
-
$schema: z.string().optional(),
|
|
70
|
-
version: z.string().default("1.0.0"),
|
|
71
|
-
proxy: ProxyConfigSchema.default({}),
|
|
72
|
-
routing: RoutingConfigSchema.default({}),
|
|
73
|
-
guardrails: GuardrailsConfigSchema.default({}),
|
|
74
|
-
upstreams: z.record(UpstreamServerConfigSchema).default({})
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// src/vault/redaction.ts
|
|
78
|
-
var RedactionFilter = class _RedactionFilter {
|
|
79
|
-
static instance = null;
|
|
80
|
-
registeredSecrets = /* @__PURE__ */ new Set();
|
|
81
|
-
static getInstance() {
|
|
82
|
-
if (!_RedactionFilter.instance) {
|
|
83
|
-
_RedactionFilter.instance = new _RedactionFilter();
|
|
84
|
-
}
|
|
85
|
-
return _RedactionFilter.instance;
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Registers a plaintext secret value to be masked across all output streams.
|
|
89
|
-
*/
|
|
90
|
-
registerSecret(secret) {
|
|
91
|
-
if (secret && typeof secret === "string") {
|
|
92
|
-
const trimmed = secret.trim();
|
|
93
|
-
if (trimmed.length >= 4) {
|
|
94
|
-
this.registeredSecrets.add(trimmed);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
registerSecrets(secrets) {
|
|
99
|
-
for (const s of secrets) {
|
|
100
|
-
this.registerSecret(s);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
getRegisteredCount() {
|
|
104
|
-
return this.registeredSecrets.size;
|
|
105
|
-
}
|
|
106
|
-
clear() {
|
|
107
|
-
this.registeredSecrets.clear();
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Redacts registered secrets and known API key patterns from an input string.
|
|
111
|
-
*/
|
|
112
|
-
redact(input) {
|
|
113
|
-
if (!input || typeof input !== "string") {
|
|
114
|
-
return input;
|
|
115
|
-
}
|
|
116
|
-
let sanitized = input;
|
|
117
|
-
for (const secret of this.registeredSecrets) {
|
|
118
|
-
sanitized = sanitized.replaceAll(secret, "[REDACTED_SECRET]");
|
|
119
|
-
}
|
|
120
|
-
sanitized = sanitized.replace(/ghp_[a-zA-Z0-9]{36}/g, "ghp_[REDACTED_GITHUB_TOKEN]");
|
|
121
|
-
sanitized = sanitized.replace(/sk-ant-[a-zA-Z0-9_-]{20,}/g, "sk-ant-[REDACTED_ANTHROPIC_KEY]");
|
|
122
|
-
sanitized = sanitized.replace(/sk-[a-zA-Z0-9]{32,}/g, "sk-[REDACTED_OPENAI_KEY]");
|
|
123
|
-
sanitized = sanitized.replace(
|
|
124
|
-
/([a-zA-Z0-9+]+:\/\/[^:]+:)([^@]+)(@.+)/g,
|
|
125
|
-
"$1[REDACTED_PASSWORD]$3"
|
|
126
|
-
);
|
|
127
|
-
return sanitized;
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
var redactionFilter = RedactionFilter.getInstance();
|
|
131
|
-
|
|
132
|
-
// src/utils/logger.ts
|
|
133
|
-
var LOG_LEVELS = {
|
|
134
|
-
debug: 10,
|
|
135
|
-
info: 20,
|
|
136
|
-
warn: 30,
|
|
137
|
-
error: 40,
|
|
138
|
-
silent: 100
|
|
139
|
-
};
|
|
140
|
-
var Logger = class {
|
|
141
|
-
level = "info";
|
|
142
|
-
prefix;
|
|
143
|
-
constructor(prefix = "ContextWise", level = "info") {
|
|
144
|
-
this.prefix = prefix;
|
|
145
|
-
this.level = level;
|
|
146
|
-
}
|
|
147
|
-
setLevel(level) {
|
|
148
|
-
this.level = level;
|
|
149
|
-
}
|
|
150
|
-
getLevel() {
|
|
151
|
-
return this.level;
|
|
152
|
-
}
|
|
153
|
-
shouldLog(level) {
|
|
154
|
-
return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
|
|
155
|
-
}
|
|
156
|
-
formatMessage(level, message, meta) {
|
|
157
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
158
|
-
const metaStr = meta !== void 0 ? ` ${typeof meta === "object" ? JSON.stringify(meta) : String(meta)}` : "";
|
|
159
|
-
const raw = `[${timestamp}] [${level.toUpperCase()}] [${this.prefix}] ${message}${metaStr}
|
|
160
|
-
`;
|
|
161
|
-
return redactionFilter.redact(raw);
|
|
162
|
-
}
|
|
163
|
-
debug(message, meta) {
|
|
164
|
-
if (this.shouldLog("debug")) {
|
|
165
|
-
process.stderr.write(this.formatMessage("DEBUG", message, meta));
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
info(message, meta) {
|
|
169
|
-
if (this.shouldLog("info")) {
|
|
170
|
-
process.stderr.write(this.formatMessage("INFO", message, meta));
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
warn(message, meta) {
|
|
174
|
-
if (this.shouldLog("warn")) {
|
|
175
|
-
process.stderr.write(this.formatMessage("WARN", message, meta));
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
error(message, meta) {
|
|
179
|
-
if (this.shouldLog("error")) {
|
|
180
|
-
process.stderr.write(this.formatMessage("ERROR", message, meta));
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
var logger = new Logger("ContextWise");
|
|
185
|
-
|
|
186
|
-
// src/config/loader.ts
|
|
187
|
-
import { existsSync, readFileSync } from "fs";
|
|
188
|
-
import { homedir } from "os";
|
|
189
|
-
import { isAbsolute, join, resolve } from "path";
|
|
190
|
-
var ConfigLoader = class {
|
|
191
|
-
/**
|
|
192
|
-
* Discovers and loads configuration from contextwise.json or imported client configs.
|
|
193
|
-
*/
|
|
194
|
-
static load(options = {}) {
|
|
195
|
-
const cwd = options.cwd ?? process.cwd();
|
|
196
|
-
if (process.env.CONTEXTWISE_CONFIG && !options.configPath) {
|
|
197
|
-
const envPath = isAbsolute(process.env.CONTEXTWISE_CONFIG) ? process.env.CONTEXTWISE_CONFIG : resolve(cwd, process.env.CONTEXTWISE_CONFIG);
|
|
198
|
-
if (existsSync(envPath)) {
|
|
199
|
-
return this.parseConfigFile(envPath);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
if (options.configPath) {
|
|
203
|
-
const explicitPath = isAbsolute(options.configPath) ? options.configPath : resolve(cwd, options.configPath);
|
|
204
|
-
if (existsSync(explicitPath)) {
|
|
205
|
-
return this.parseConfigFile(explicitPath);
|
|
206
|
-
}
|
|
207
|
-
throw new Error(`Configuration file not found at: ${explicitPath}`);
|
|
208
|
-
}
|
|
209
|
-
const candidatePaths = [
|
|
210
|
-
resolve(cwd, "contextwise.json"),
|
|
211
|
-
resolve(cwd, ".contextwise.json"),
|
|
212
|
-
resolve(cwd, ".contextwise/config.json"),
|
|
213
|
-
resolve(homedir(), ".contextwise/config.json")
|
|
214
|
-
];
|
|
215
|
-
for (const candidate of candidatePaths) {
|
|
216
|
-
if (existsSync(candidate)) {
|
|
217
|
-
logger.debug(`Loaded configuration from ${candidate}`);
|
|
218
|
-
return this.parseConfigFile(candidate);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
if (options.autoImport !== false) {
|
|
222
|
-
const imported = this.autoImportClientConfigs(cwd);
|
|
223
|
-
if (imported && Object.keys(imported.upstreams).length > 0) {
|
|
224
|
-
logger.info(`Auto-discovered MCP servers from existing client configurations`);
|
|
225
|
-
return imported;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
logger.debug("No configuration file found. Using defaults.");
|
|
229
|
-
return ContextWiseConfigSchema.parse({});
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* Parses and validates a JSON file against ContextWiseConfigSchema.
|
|
233
|
-
*/
|
|
234
|
-
static parseConfigFile(filePath) {
|
|
235
|
-
try {
|
|
236
|
-
const raw = readFileSync(filePath, "utf-8");
|
|
237
|
-
const parsed = JSON.parse(raw);
|
|
238
|
-
return ContextWiseConfigSchema.parse(parsed);
|
|
239
|
-
} catch (err) {
|
|
240
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
241
|
-
throw new Error(`Failed to parse config at ${filePath}: ${msg}`);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Scans for Cursor and Claude Desktop configs to auto-import upstreams.
|
|
246
|
-
*/
|
|
247
|
-
static autoImportClientConfigs(cwd) {
|
|
248
|
-
const upstreams = {};
|
|
249
|
-
const cursorMcp = resolve(cwd, ".cursor", "mcp.json");
|
|
250
|
-
if (existsSync(cursorMcp)) {
|
|
251
|
-
try {
|
|
252
|
-
const raw = JSON.parse(readFileSync(cursorMcp, "utf-8"));
|
|
253
|
-
if (raw.mcpServers && typeof raw.mcpServers === "object") {
|
|
254
|
-
for (const [name, server] of Object.entries(raw.mcpServers)) {
|
|
255
|
-
if (name === "contextwise") continue;
|
|
256
|
-
const s = server;
|
|
257
|
-
if (typeof s.command === "string") {
|
|
258
|
-
upstreams[name] = {
|
|
259
|
-
command: s.command,
|
|
260
|
-
args: Array.isArray(s.args) ? s.args : [],
|
|
261
|
-
env: s.env ?? {},
|
|
262
|
-
autoRestart: true
|
|
263
|
-
};
|
|
264
|
-
} else if (typeof s.url === "string") {
|
|
265
|
-
upstreams[name] = {
|
|
266
|
-
url: s.url,
|
|
267
|
-
headers: s.headers ?? {},
|
|
268
|
-
transport: "auto",
|
|
269
|
-
autoReconnect: true
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
} catch (err) {
|
|
275
|
-
logger.warn(`Failed reading ${cursorMcp}: ${err}`);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
const claudePath = this.getClaudeDesktopConfigPath();
|
|
279
|
-
if (claudePath && existsSync(claudePath)) {
|
|
280
|
-
try {
|
|
281
|
-
const raw = JSON.parse(readFileSync(claudePath, "utf-8"));
|
|
282
|
-
if (raw.mcpServers && typeof raw.mcpServers === "object") {
|
|
283
|
-
for (const [name, server] of Object.entries(raw.mcpServers)) {
|
|
284
|
-
if (name === "contextwise") continue;
|
|
285
|
-
const s = server;
|
|
286
|
-
if (typeof s.command === "string" && !upstreams[name]) {
|
|
287
|
-
upstreams[name] = {
|
|
288
|
-
command: s.command,
|
|
289
|
-
args: Array.isArray(s.args) ? s.args : [],
|
|
290
|
-
env: s.env ?? {},
|
|
291
|
-
autoRestart: true
|
|
292
|
-
};
|
|
293
|
-
} else if (typeof s.url === "string" && !upstreams[name]) {
|
|
294
|
-
upstreams[name] = {
|
|
295
|
-
url: s.url,
|
|
296
|
-
headers: s.headers ?? {},
|
|
297
|
-
transport: "auto",
|
|
298
|
-
autoReconnect: true
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
} catch (err) {
|
|
304
|
-
logger.warn(`Failed reading Claude config at ${claudePath}: ${err}`);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
if (Object.keys(upstreams).length === 0) {
|
|
308
|
-
return null;
|
|
309
|
-
}
|
|
310
|
-
return ContextWiseConfigSchema.parse({
|
|
311
|
-
upstreams
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
static getClaudeDesktopConfigPath() {
|
|
315
|
-
const platform3 = process.platform;
|
|
316
|
-
const home = homedir();
|
|
317
|
-
if (platform3 === "win32") {
|
|
318
|
-
const appData = process.env.APPDATA || join(home, "AppData", "Roaming");
|
|
319
|
-
return join(appData, "Claude", "claude_desktop_config.json");
|
|
320
|
-
} else if (platform3 === "darwin") {
|
|
321
|
-
return join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
322
|
-
} else {
|
|
323
|
-
return join(home, ".config", "Claude", "claude_desktop_config.json");
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
ContextWiseConfigSchema,
|
|
3
|
+
EncryptedFileVaultDriver,
|
|
4
|
+
isHttpUpstream,
|
|
5
|
+
isStdioUpstream,
|
|
6
|
+
warnIfInsecureHttp
|
|
7
|
+
} from "./chunk-OYCA37PJ.js";
|
|
8
|
+
import {
|
|
9
|
+
logger,
|
|
10
|
+
redactionFilter
|
|
11
|
+
} from "./chunk-WP7RH2Z2.js";
|
|
327
12
|
|
|
328
13
|
// src/core/supervisor.ts
|
|
329
14
|
import { execSync } from "child_process";
|
|
@@ -419,239 +104,9 @@ var ProcessSupervisor = class _ProcessSupervisor {
|
|
|
419
104
|
};
|
|
420
105
|
var supervisor = ProcessSupervisor.getInstance();
|
|
421
106
|
|
|
422
|
-
// src/vault/crypto.ts
|
|
423
|
-
import {
|
|
424
|
-
createCipheriv,
|
|
425
|
-
createDecipheriv,
|
|
426
|
-
createHash,
|
|
427
|
-
createPrivateKey,
|
|
428
|
-
createPublicKey,
|
|
429
|
-
diffieHellman,
|
|
430
|
-
generateKeyPairSync,
|
|
431
|
-
pbkdf2,
|
|
432
|
-
randomBytes
|
|
433
|
-
} from "crypto";
|
|
434
|
-
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
435
|
-
import { arch, homedir as homedir2, hostname, platform, userInfo } from "os";
|
|
436
|
-
import { join as join2 } from "path";
|
|
437
|
-
function encryptAesGcm(plaintext, key) {
|
|
438
|
-
if (key.length !== 32) {
|
|
439
|
-
throw new Error(`Invalid AES-256 key length: expected 32 bytes, got ${key.length}`);
|
|
440
|
-
}
|
|
441
|
-
const iv = randomBytes(12);
|
|
442
|
-
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
443
|
-
let ciphertext = cipher.update(plaintext, "utf-8", "base64");
|
|
444
|
-
ciphertext += cipher.final("base64");
|
|
445
|
-
const authTag = cipher.getAuthTag();
|
|
446
|
-
return {
|
|
447
|
-
iv: iv.toString("base64"),
|
|
448
|
-
authTag: authTag.toString("base64"),
|
|
449
|
-
ciphertext
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
function decryptAesGcm(ciphertext, key, ivBase64, authTagBase64) {
|
|
453
|
-
if (key.length !== 32) {
|
|
454
|
-
throw new Error(`Invalid AES-256 key length: expected 32 bytes, got ${key.length}`);
|
|
455
|
-
}
|
|
456
|
-
const iv = Buffer.from(ivBase64, "base64");
|
|
457
|
-
const authTag = Buffer.from(authTagBase64, "base64");
|
|
458
|
-
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
459
|
-
decipher.setAuthTag(authTag);
|
|
460
|
-
let decrypted = decipher.update(ciphertext, "base64", "utf-8");
|
|
461
|
-
decrypted += decipher.final("utf-8");
|
|
462
|
-
return decrypted;
|
|
463
|
-
}
|
|
464
|
-
var VAULT_PBKDF2_ITERATIONS = 21e4;
|
|
465
|
-
function deriveKeyFromPassphrase(passphrase, salt, iterations = VAULT_PBKDF2_ITERATIONS) {
|
|
466
|
-
return new Promise((resolve4, reject) => {
|
|
467
|
-
pbkdf2(passphrase, salt, iterations, 32, "sha512", (err, derivedKey) => {
|
|
468
|
-
if (err) reject(err);
|
|
469
|
-
else resolve4(derivedKey);
|
|
470
|
-
});
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
async function getOrCreateMachineKey() {
|
|
474
|
-
const saltDir = join2(homedir2(), ".contextwise");
|
|
475
|
-
const saltPath = join2(saltDir, ".machine_salt");
|
|
476
|
-
let salt;
|
|
477
|
-
if (existsSync2(saltPath)) {
|
|
478
|
-
salt = readFileSync2(saltPath);
|
|
479
|
-
} else {
|
|
480
|
-
mkdirSync(saltDir, { recursive: true });
|
|
481
|
-
salt = randomBytes(32);
|
|
482
|
-
writeFileSync(saltPath, salt, { mode: 384 });
|
|
483
|
-
}
|
|
484
|
-
if (process.env.CONTEXTWISE_VAULT_PASSPHRASE) {
|
|
485
|
-
return deriveKeyFromPassphrase(process.env.CONTEXTWISE_VAULT_PASSPHRASE, salt, VAULT_PBKDF2_ITERATIONS);
|
|
486
|
-
}
|
|
487
|
-
const user = (() => {
|
|
488
|
-
try {
|
|
489
|
-
return userInfo().username;
|
|
490
|
-
} catch {
|
|
491
|
-
return "default_user";
|
|
492
|
-
}
|
|
493
|
-
})();
|
|
494
|
-
const machineId = `${hostname()}-${user}-${platform()}-${arch()}-contextwise-vault-v1`;
|
|
495
|
-
return deriveKeyFromPassphrase(machineId, salt, VAULT_PBKDF2_ITERATIONS);
|
|
496
|
-
}
|
|
497
|
-
function generateKeyPairX25519() {
|
|
498
|
-
const { publicKey, privateKey } = generateKeyPairSync("x25519", {
|
|
499
|
-
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
500
|
-
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
|
501
|
-
});
|
|
502
|
-
return { publicKey, privateKey };
|
|
503
|
-
}
|
|
504
|
-
function deriveSharedSecretX25519(privateKeyPem, publicKeyPem) {
|
|
505
|
-
const privateKey = createPrivateKey(privateKeyPem);
|
|
506
|
-
const publicKey = createPublicKey(publicKeyPem);
|
|
507
|
-
const shared = diffieHellman({
|
|
508
|
-
privateKey,
|
|
509
|
-
publicKey
|
|
510
|
-
});
|
|
511
|
-
return createHash("sha256").update(shared).digest();
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// src/vault/file_driver.ts
|
|
515
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
516
|
-
import { homedir as homedir3 } from "os";
|
|
517
|
-
import { dirname as dirname2, join as join3 } from "path";
|
|
518
|
-
function getDefaultVaultFilePath() {
|
|
519
|
-
if (process.env.CONTEXTWISE_VAULT_PATH) {
|
|
520
|
-
return process.env.CONTEXTWISE_VAULT_PATH;
|
|
521
|
-
}
|
|
522
|
-
if (process.env.CONTEXTWISE_STORAGE_DIR) {
|
|
523
|
-
return join3(process.env.CONTEXTWISE_STORAGE_DIR, "vault.enc.json");
|
|
524
|
-
}
|
|
525
|
-
return join3(homedir3(), ".contextwise", "vault.enc.json");
|
|
526
|
-
}
|
|
527
|
-
var EncryptedFileVaultDriver = class {
|
|
528
|
-
name = "encrypted_file";
|
|
529
|
-
filePath;
|
|
530
|
-
keyPromise;
|
|
531
|
-
cache = null;
|
|
532
|
-
constructor(filePath, customKey) {
|
|
533
|
-
this.filePath = filePath ?? getDefaultVaultFilePath();
|
|
534
|
-
this.keyPromise = customKey ? Promise.resolve(customKey) : getOrCreateMachineKey();
|
|
535
|
-
}
|
|
536
|
-
async isAvailable() {
|
|
537
|
-
return true;
|
|
538
|
-
}
|
|
539
|
-
async load() {
|
|
540
|
-
if (this.cache) {
|
|
541
|
-
return this.cache;
|
|
542
|
-
}
|
|
543
|
-
this.cache = /* @__PURE__ */ new Map();
|
|
544
|
-
if (!existsSync3(this.filePath)) {
|
|
545
|
-
return this.cache;
|
|
546
|
-
}
|
|
547
|
-
try {
|
|
548
|
-
const raw = readFileSync3(this.filePath, "utf-8");
|
|
549
|
-
const payload = JSON.parse(raw);
|
|
550
|
-
if (payload.cipher !== "aes-256-gcm" || !payload.data) {
|
|
551
|
-
logger.warn(`Corrupt or incompatible vault file at ${this.filePath}`);
|
|
552
|
-
return this.cache;
|
|
553
|
-
}
|
|
554
|
-
const key = await this.keyPromise;
|
|
555
|
-
const decryptedJson = decryptAesGcm(payload.data, key, payload.iv, payload.authTag);
|
|
556
|
-
const entries = JSON.parse(decryptedJson);
|
|
557
|
-
for (const [k, v] of Object.entries(entries)) {
|
|
558
|
-
this.cache.set(k, v);
|
|
559
|
-
}
|
|
560
|
-
} catch (err) {
|
|
561
|
-
logger.warn(`Failed to decrypt vault file at ${this.filePath}: ${err}`);
|
|
562
|
-
}
|
|
563
|
-
return this.cache;
|
|
564
|
-
}
|
|
565
|
-
async persist() {
|
|
566
|
-
if (!this.cache) return;
|
|
567
|
-
const dir = dirname2(this.filePath);
|
|
568
|
-
if (!existsSync3(dir)) {
|
|
569
|
-
mkdirSync2(dir, { recursive: true });
|
|
570
|
-
}
|
|
571
|
-
const key = await this.keyPromise;
|
|
572
|
-
const entries = {};
|
|
573
|
-
for (const [k, v] of this.cache.entries()) {
|
|
574
|
-
entries[k] = v;
|
|
575
|
-
}
|
|
576
|
-
const plaintext = JSON.stringify(entries);
|
|
577
|
-
const { iv, authTag, ciphertext } = encryptAesGcm(plaintext, key);
|
|
578
|
-
const payload = {
|
|
579
|
-
version: 1,
|
|
580
|
-
kdf: {
|
|
581
|
-
algorithm: "pbkdf2-sha512",
|
|
582
|
-
salt: "machine-bound",
|
|
583
|
-
iterations: VAULT_PBKDF2_ITERATIONS
|
|
584
|
-
},
|
|
585
|
-
cipher: "aes-256-gcm",
|
|
586
|
-
iv,
|
|
587
|
-
authTag,
|
|
588
|
-
data: ciphertext
|
|
589
|
-
};
|
|
590
|
-
const tempPath = `${this.filePath}.${Date.now()}.tmp`;
|
|
591
|
-
writeFileSync2(tempPath, JSON.stringify(payload, null, 2), { mode: 384, encoding: "utf-8" });
|
|
592
|
-
try {
|
|
593
|
-
renameSync(tempPath, this.filePath);
|
|
594
|
-
} catch {
|
|
595
|
-
writeFileSync2(this.filePath, JSON.stringify(payload, null, 2), { mode: 384, encoding: "utf-8" });
|
|
596
|
-
try {
|
|
597
|
-
unlinkSync(tempPath);
|
|
598
|
-
} catch {
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
async get(key) {
|
|
603
|
-
const store = await this.load();
|
|
604
|
-
const entry = store.get(key);
|
|
605
|
-
return entry ? entry.value : null;
|
|
606
|
-
}
|
|
607
|
-
async set(key, value, scope = "personal") {
|
|
608
|
-
const store = await this.load();
|
|
609
|
-
const now = Date.now();
|
|
610
|
-
const existing = store.get(key);
|
|
611
|
-
store.set(key, {
|
|
612
|
-
value,
|
|
613
|
-
metadata: {
|
|
614
|
-
key,
|
|
615
|
-
scope,
|
|
616
|
-
backend: this.name,
|
|
617
|
-
createdAt: existing?.metadata.createdAt ?? now,
|
|
618
|
-
updatedAt: now
|
|
619
|
-
}
|
|
620
|
-
});
|
|
621
|
-
await this.persist();
|
|
622
|
-
logger.debug(`Stored secret "${key}" in ${this.name} vault`);
|
|
623
|
-
}
|
|
624
|
-
async delete(key) {
|
|
625
|
-
const store = await this.load();
|
|
626
|
-
if (!store.has(key)) {
|
|
627
|
-
return false;
|
|
628
|
-
}
|
|
629
|
-
store.delete(key);
|
|
630
|
-
await this.persist();
|
|
631
|
-
logger.debug(`Deleted secret "${key}" from ${this.name} vault`);
|
|
632
|
-
return true;
|
|
633
|
-
}
|
|
634
|
-
async list() {
|
|
635
|
-
const store = await this.load();
|
|
636
|
-
return Array.from(store.values()).map((e) => e.metadata);
|
|
637
|
-
}
|
|
638
|
-
/**
|
|
639
|
-
* Resets and clears all cached and persisted secrets.
|
|
640
|
-
*/
|
|
641
|
-
async clear() {
|
|
642
|
-
this.cache = /* @__PURE__ */ new Map();
|
|
643
|
-
if (existsSync3(this.filePath)) {
|
|
644
|
-
try {
|
|
645
|
-
unlinkSync(this.filePath);
|
|
646
|
-
} catch {
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
};
|
|
651
|
-
|
|
652
107
|
// src/vault/os_driver.ts
|
|
653
108
|
import { execFileSync } from "child_process";
|
|
654
|
-
import { platform
|
|
109
|
+
import { platform } from "os";
|
|
655
110
|
var OsKeystoreDriver = class {
|
|
656
111
|
name = "os_keystore";
|
|
657
112
|
available = null;
|
|
@@ -660,7 +115,7 @@ var OsKeystoreDriver = class {
|
|
|
660
115
|
if (this.available !== null) {
|
|
661
116
|
return this.available;
|
|
662
117
|
}
|
|
663
|
-
const currentPlatform =
|
|
118
|
+
const currentPlatform = platform();
|
|
664
119
|
try {
|
|
665
120
|
if (currentPlatform === "darwin") {
|
|
666
121
|
execFileSync("/usr/bin/security", ["list-keychains"], { stdio: "ignore" });
|
|
@@ -678,7 +133,7 @@ var OsKeystoreDriver = class {
|
|
|
678
133
|
if (!await this.isAvailable()) {
|
|
679
134
|
return null;
|
|
680
135
|
}
|
|
681
|
-
const currentPlatform =
|
|
136
|
+
const currentPlatform = platform();
|
|
682
137
|
try {
|
|
683
138
|
if (currentPlatform === "darwin") {
|
|
684
139
|
const stdout = execFileSync(
|
|
@@ -697,7 +152,7 @@ var OsKeystoreDriver = class {
|
|
|
697
152
|
if (!await this.isAvailable()) {
|
|
698
153
|
throw new Error("OS Keystore driver is not available on this system.");
|
|
699
154
|
}
|
|
700
|
-
const currentPlatform =
|
|
155
|
+
const currentPlatform = platform();
|
|
701
156
|
if (currentPlatform === "darwin") {
|
|
702
157
|
execFileSync(
|
|
703
158
|
"/usr/bin/security",
|
|
@@ -712,7 +167,7 @@ var OsKeystoreDriver = class {
|
|
|
712
167
|
if (!await this.isAvailable()) {
|
|
713
168
|
return false;
|
|
714
169
|
}
|
|
715
|
-
const currentPlatform =
|
|
170
|
+
const currentPlatform = platform();
|
|
716
171
|
try {
|
|
717
172
|
if (currentPlatform === "darwin") {
|
|
718
173
|
execFileSync(
|
|
@@ -889,9 +344,9 @@ var VaultAuditor = class {
|
|
|
889
344
|
};
|
|
890
345
|
|
|
891
346
|
// src/vault/resolver.ts
|
|
892
|
-
import { existsSync
|
|
893
|
-
import { homedir
|
|
894
|
-
import { join
|
|
347
|
+
import { existsSync, readFileSync } from "fs";
|
|
348
|
+
import { homedir } from "os";
|
|
349
|
+
import { join } from "path";
|
|
895
350
|
var SecretResolver = class {
|
|
896
351
|
/**
|
|
897
352
|
* Resolves a single secret token reference (vault://, auth://, env://).
|
|
@@ -927,10 +382,10 @@ var SecretResolver = class {
|
|
|
927
382
|
if (!serverKey) {
|
|
928
383
|
throw new Error('Invalid auth reference format: expected "auth://<server>"');
|
|
929
384
|
}
|
|
930
|
-
const mcpAuthPath =
|
|
931
|
-
if (
|
|
385
|
+
const mcpAuthPath = join(homedir(), ".local", "share", "opencode", "mcp-auth.json");
|
|
386
|
+
if (existsSync(mcpAuthPath)) {
|
|
932
387
|
try {
|
|
933
|
-
const authData = JSON.parse(
|
|
388
|
+
const authData = JSON.parse(readFileSync(mcpAuthPath, "utf-8"));
|
|
934
389
|
const token = authData[serverKey]?.tokens?.accessToken;
|
|
935
390
|
if (token) {
|
|
936
391
|
redactionFilter.registerSecret(token);
|
|
@@ -1731,7 +1186,7 @@ var ToolRegistry = class {
|
|
|
1731
1186
|
var toolRegistry = new ToolRegistry();
|
|
1732
1187
|
|
|
1733
1188
|
// src/guardrails/validator.ts
|
|
1734
|
-
import { createHash
|
|
1189
|
+
import { createHash } from "crypto";
|
|
1735
1190
|
import Ajv from "ajv";
|
|
1736
1191
|
import addFormats from "ajv-formats";
|
|
1737
1192
|
var ToolArgumentValidator = class {
|
|
@@ -1750,7 +1205,7 @@ var ToolArgumentValidator = class {
|
|
|
1750
1205
|
}
|
|
1751
1206
|
computeSchemaHash(schema) {
|
|
1752
1207
|
try {
|
|
1753
|
-
return
|
|
1208
|
+
return createHash("sha256").update(JSON.stringify(schema ?? "")).digest("hex").slice(0, 16);
|
|
1754
1209
|
} catch {
|
|
1755
1210
|
return "static";
|
|
1756
1211
|
}
|
|
@@ -1797,7 +1252,7 @@ var ToolArgumentValidator = class {
|
|
|
1797
1252
|
var argumentValidator = new ToolArgumentValidator();
|
|
1798
1253
|
|
|
1799
1254
|
// src/utils/hash.ts
|
|
1800
|
-
import { createHash as
|
|
1255
|
+
import { createHash as createHash2 } from "crypto";
|
|
1801
1256
|
function canonicalJsonStringify(obj, seen = /* @__PURE__ */ new WeakSet()) {
|
|
1802
1257
|
if (obj === null || obj === void 0) {
|
|
1803
1258
|
return "null";
|
|
@@ -1823,7 +1278,7 @@ function canonicalJsonStringify(obj, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
1823
1278
|
return "{" + pairs.join(",") + "}";
|
|
1824
1279
|
}
|
|
1825
1280
|
function sha256(input) {
|
|
1826
|
-
return
|
|
1281
|
+
return createHash2("sha256").update(input).digest("hex");
|
|
1827
1282
|
}
|
|
1828
1283
|
function generateToolCallCacheKey(serverName, toolName, args = {}) {
|
|
1829
1284
|
const canonicalArgs = canonicalJsonStringify(args);
|
|
@@ -2015,9 +1470,9 @@ var RunawayLoopBreaker = class {
|
|
|
2015
1470
|
var loopBreaker = new RunawayLoopBreaker();
|
|
2016
1471
|
|
|
2017
1472
|
// src/metrics/collector.ts
|
|
2018
|
-
import { existsSync as
|
|
2019
|
-
import { homedir as
|
|
2020
|
-
import { dirname
|
|
1473
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "fs";
|
|
1474
|
+
import { homedir as homedir2 } from "os";
|
|
1475
|
+
import { dirname, join as join2 } from "path";
|
|
2021
1476
|
var SCHEMA_TOKENS_PER_TOOL = 150;
|
|
2022
1477
|
var CACHE_TOKENS_PER_HIT = 800;
|
|
2023
1478
|
var LOOP_PREVENTION_TOKENS_PER_TRIP = 2500;
|
|
@@ -2043,7 +1498,7 @@ function getDefaultMetricsPath() {
|
|
|
2043
1498
|
if (process.env.CONTEXTWISE_METRICS_PATH) {
|
|
2044
1499
|
return process.env.CONTEXTWISE_METRICS_PATH;
|
|
2045
1500
|
}
|
|
2046
|
-
return
|
|
1501
|
+
return join2(homedir2(), ".contextwise", "metrics.json");
|
|
2047
1502
|
}
|
|
2048
1503
|
var MetricsCollector = class {
|
|
2049
1504
|
storagePath;
|
|
@@ -2091,8 +1546,8 @@ var MetricsCollector = class {
|
|
|
2091
1546
|
}
|
|
2092
1547
|
loadFromDisk() {
|
|
2093
1548
|
try {
|
|
2094
|
-
if (
|
|
2095
|
-
const raw =
|
|
1549
|
+
if (existsSync2(this.storagePath)) {
|
|
1550
|
+
const raw = readFileSync2(this.storagePath, "utf-8");
|
|
2096
1551
|
const parsed = JSON.parse(raw);
|
|
2097
1552
|
if (parsed && typeof parsed === "object") {
|
|
2098
1553
|
this.data = {
|
|
@@ -2126,17 +1581,17 @@ var MetricsCollector = class {
|
|
|
2126
1581
|
this.debounceTimer = null;
|
|
2127
1582
|
}
|
|
2128
1583
|
try {
|
|
2129
|
-
const dir =
|
|
2130
|
-
if (!
|
|
2131
|
-
|
|
1584
|
+
const dir = dirname(this.storagePath);
|
|
1585
|
+
if (!existsSync2(dir)) {
|
|
1586
|
+
mkdirSync(dir, { recursive: true });
|
|
2132
1587
|
}
|
|
2133
1588
|
const tempFile = `${this.storagePath}.${Date.now()}.tmp`;
|
|
2134
1589
|
const json = JSON.stringify(this.data, null, 2);
|
|
2135
|
-
|
|
2136
|
-
|
|
1590
|
+
writeFileSync(tempFile, json, "utf-8");
|
|
1591
|
+
renameSync(tempFile, this.storagePath);
|
|
2137
1592
|
} catch {
|
|
2138
1593
|
try {
|
|
2139
|
-
|
|
1594
|
+
writeFileSync(this.storagePath, JSON.stringify(this.data, null, 2), "utf-8");
|
|
2140
1595
|
} catch (err) {
|
|
2141
1596
|
logger.debug(`Failed persisting metrics: ${err}`);
|
|
2142
1597
|
}
|
|
@@ -2272,6 +1727,9 @@ var SEARCH_TOOLS_NAME = "contextwise_search_tools";
|
|
|
2272
1727
|
var EXECUTE_TOOL_NAME = "contextwise_execute_tool";
|
|
2273
1728
|
var BROWSE_SERVERS_NAME = "contextwise_browse_servers";
|
|
2274
1729
|
var ADD_SERVER_NAME = "contextwise_add_server";
|
|
1730
|
+
var CLOUD_PUSH_NAME = "contextwise_cloud_push";
|
|
1731
|
+
var CLOUD_PULL_NAME = "contextwise_cloud_pull";
|
|
1732
|
+
var SYNC_STATUS_NAME = "contextwise_sync_status";
|
|
2275
1733
|
var SEARCH_TOOLS_DEFINITION = {
|
|
2276
1734
|
name: SEARCH_TOOLS_NAME,
|
|
2277
1735
|
description: "Searches ContextWise tool catalog for relevant tools based on natural language intent. Returns matched tool signatures, parameter descriptions, and automatically makes them available to invoke.",
|
|
@@ -2383,6 +1841,40 @@ var ADD_SERVER_DEFINITION = {
|
|
|
2383
1841
|
required: ["name"]
|
|
2384
1842
|
}
|
|
2385
1843
|
};
|
|
1844
|
+
var CLOUD_PUSH_DEFINITION = {
|
|
1845
|
+
name: CLOUD_PUSH_NAME,
|
|
1846
|
+
description: "Pushes the host ContextWise workspace snapshot (config + encrypted vault) to ContextWise Cloud. Usage-metered via MCPaid.",
|
|
1847
|
+
inputSchema: {
|
|
1848
|
+
type: "object",
|
|
1849
|
+
properties: {
|
|
1850
|
+
workspaceId: {
|
|
1851
|
+
type: "string",
|
|
1852
|
+
description: "Optional workspace ID (defaults to the active workspace)"
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
};
|
|
1857
|
+
var CLOUD_PULL_DEFINITION = {
|
|
1858
|
+
name: CLOUD_PULL_NAME,
|
|
1859
|
+
description: "Pulls and applies the latest ContextWise Cloud workspace revision to the host. Usage-metered via MCPaid.",
|
|
1860
|
+
inputSchema: {
|
|
1861
|
+
type: "object",
|
|
1862
|
+
properties: {
|
|
1863
|
+
workspaceId: {
|
|
1864
|
+
type: "string",
|
|
1865
|
+
description: "Optional workspace ID (defaults to the active workspace)"
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
};
|
|
1870
|
+
var SYNC_STATUS_DEFINITION = {
|
|
1871
|
+
name: SYNC_STATUS_NAME,
|
|
1872
|
+
description: "Reports ContextWise Cloud sync status for the host instance (login state, workspace, local revision). Always free.",
|
|
1873
|
+
inputSchema: {
|
|
1874
|
+
type: "object",
|
|
1875
|
+
properties: {}
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
2386
1878
|
|
|
2387
1879
|
// src/registry/known_servers.ts
|
|
2388
1880
|
var KNOWN_SERVERS = [
|
|
@@ -2939,25 +2431,25 @@ var ServerRecommender = class {
|
|
|
2939
2431
|
};
|
|
2940
2432
|
|
|
2941
2433
|
// src/registry/manager.ts
|
|
2942
|
-
import { existsSync as
|
|
2943
|
-
import { dirname as
|
|
2434
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
2435
|
+
import { dirname as dirname2, resolve } from "path";
|
|
2944
2436
|
var ServerManager = class {
|
|
2945
2437
|
/**
|
|
2946
2438
|
* Finds the path to contextwise.json in the current working directory.
|
|
2947
2439
|
*/
|
|
2948
2440
|
static findConfigPath(customPath, cwd = process.cwd()) {
|
|
2949
2441
|
if (customPath) {
|
|
2950
|
-
return
|
|
2442
|
+
return resolve(cwd, customPath);
|
|
2951
2443
|
}
|
|
2952
2444
|
if (process.env.CONTEXTWISE_CONFIG) {
|
|
2953
|
-
return
|
|
2445
|
+
return resolve(cwd, process.env.CONTEXTWISE_CONFIG);
|
|
2954
2446
|
}
|
|
2955
|
-
const standardPath =
|
|
2956
|
-
if (
|
|
2447
|
+
const standardPath = resolve(cwd, "contextwise.json");
|
|
2448
|
+
if (existsSync3(standardPath)) {
|
|
2957
2449
|
return standardPath;
|
|
2958
2450
|
}
|
|
2959
|
-
const dotPath =
|
|
2960
|
-
if (
|
|
2451
|
+
const dotPath = resolve(cwd, ".contextwise.json");
|
|
2452
|
+
if (existsSync3(dotPath)) {
|
|
2961
2453
|
return dotPath;
|
|
2962
2454
|
}
|
|
2963
2455
|
return standardPath;
|
|
@@ -2967,14 +2459,14 @@ var ServerManager = class {
|
|
|
2967
2459
|
*/
|
|
2968
2460
|
static loadConfig(configPath, cwd = process.cwd()) {
|
|
2969
2461
|
const filePath = this.findConfigPath(configPath, cwd);
|
|
2970
|
-
if (!
|
|
2462
|
+
if (!existsSync3(filePath)) {
|
|
2971
2463
|
return {
|
|
2972
2464
|
config: ContextWiseConfigSchema.parse({}),
|
|
2973
2465
|
path: filePath
|
|
2974
2466
|
};
|
|
2975
2467
|
}
|
|
2976
2468
|
try {
|
|
2977
|
-
const raw =
|
|
2469
|
+
const raw = readFileSync3(filePath, "utf-8");
|
|
2978
2470
|
return {
|
|
2979
2471
|
config: ContextWiseConfigSchema.parse(JSON.parse(raw)),
|
|
2980
2472
|
path: filePath
|
|
@@ -2993,11 +2485,11 @@ var ServerManager = class {
|
|
|
2993
2485
|
static saveUpstream(name, serverConfig, configPath, cwd = process.cwd()) {
|
|
2994
2486
|
const { config, path: filePath } = this.loadConfig(configPath, cwd);
|
|
2995
2487
|
config.upstreams[name] = serverConfig;
|
|
2996
|
-
const dir =
|
|
2997
|
-
if (!
|
|
2998
|
-
|
|
2488
|
+
const dir = dirname2(filePath);
|
|
2489
|
+
if (!existsSync3(dir)) {
|
|
2490
|
+
mkdirSync2(dir, { recursive: true });
|
|
2999
2491
|
}
|
|
3000
|
-
|
|
2492
|
+
writeFileSync2(filePath, JSON.stringify(config, null, 2), "utf-8");
|
|
3001
2493
|
logger.info(`Persisted upstream "${name}" into ${filePath}`);
|
|
3002
2494
|
return filePath;
|
|
3003
2495
|
}
|
|
@@ -3010,15 +2502,15 @@ var ServerManager = class {
|
|
|
3010
2502
|
return false;
|
|
3011
2503
|
}
|
|
3012
2504
|
delete config.upstreams[name];
|
|
3013
|
-
|
|
2505
|
+
writeFileSync2(filePath, JSON.stringify(config, null, 2), "utf-8");
|
|
3014
2506
|
logger.info(`Removed upstream "${name}" from ${filePath}`);
|
|
3015
2507
|
return true;
|
|
3016
2508
|
}
|
|
3017
2509
|
};
|
|
3018
2510
|
|
|
3019
2511
|
// src/router/workspace.ts
|
|
3020
|
-
import { existsSync as
|
|
3021
|
-
import { resolve as
|
|
2512
|
+
import { existsSync as existsSync4, readdirSync } from "fs";
|
|
2513
|
+
import { resolve as resolve2 } from "path";
|
|
3022
2514
|
var WorkspaceContextPrimer = class {
|
|
3023
2515
|
/**
|
|
3024
2516
|
* Scans a workspace directory to detect technical domains and pre-prime relevant tools.
|
|
@@ -3027,14 +2519,14 @@ var WorkspaceContextPrimer = class {
|
|
|
3027
2519
|
const domains = /* @__PURE__ */ new Set();
|
|
3028
2520
|
const queries = /* @__PURE__ */ new Set();
|
|
3029
2521
|
try {
|
|
3030
|
-
if (!
|
|
2522
|
+
if (!existsSync4(dirPath)) {
|
|
3031
2523
|
return { detectedDomains: [], suggestedQueries: [] };
|
|
3032
2524
|
}
|
|
3033
|
-
if (
|
|
2525
|
+
if (existsSync4(resolve2(dirPath, ".git"))) {
|
|
3034
2526
|
domains.add("git");
|
|
3035
2527
|
queries.add("git status and diff tools");
|
|
3036
2528
|
}
|
|
3037
|
-
if (
|
|
2529
|
+
if (existsSync4(resolve2(dirPath, "Dockerfile")) || existsSync4(resolve2(dirPath, "docker-compose.yml")) || existsSync4(resolve2(dirPath, "compose.yaml"))) {
|
|
3038
2530
|
domains.add("docker");
|
|
3039
2531
|
queries.add("docker and container tools");
|
|
3040
2532
|
}
|
|
@@ -3072,6 +2564,7 @@ var ContextRouter = class {
|
|
|
3072
2564
|
maxActiveTools: config.maxActiveTools ?? 10,
|
|
3073
2565
|
enableBrowseServers: config.enableBrowseServers ?? false,
|
|
3074
2566
|
enableAddServer: config.enableAddServer ?? false,
|
|
2567
|
+
enableCloudSync: config.enableCloudSync ?? true,
|
|
3075
2568
|
allowCustomCommands: config.allowCustomCommands ?? false,
|
|
3076
2569
|
persistAddedServers: config.persistAddedServers ?? false
|
|
3077
2570
|
};
|
|
@@ -3170,6 +2663,9 @@ ${manifest}` : SEARCH_TOOLS_DEFINITION.description
|
|
|
3170
2663
|
if (this.config.enableAddServer) {
|
|
3171
2664
|
metaTools.push(ADD_SERVER_DEFINITION);
|
|
3172
2665
|
}
|
|
2666
|
+
if (this.config.enableCloudSync) {
|
|
2667
|
+
metaTools.push(CLOUD_PUSH_DEFINITION, CLOUD_PULL_DEFINITION, SYNC_STATUS_DEFINITION);
|
|
2668
|
+
}
|
|
3173
2669
|
const metaToolNames = new Set(metaTools.map((m) => m.name));
|
|
3174
2670
|
const safeActiveTools = activeTools.filter((t) => !metaToolNames.has(t.name));
|
|
3175
2671
|
return [...metaTools, ...safeActiveTools];
|
|
@@ -3247,15 +2743,33 @@ import {
|
|
|
3247
2743
|
CallToolRequestSchema,
|
|
3248
2744
|
ListToolsRequestSchema
|
|
3249
2745
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
2746
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2747
|
+
import { parseAndVerifyEdgeReceipt, MemoryReceiptStore } from "@mcpaid/sdk";
|
|
2748
|
+
var requestContext = new AsyncLocalStorage();
|
|
3250
2749
|
var ContextWiseProxy = class {
|
|
3251
2750
|
server;
|
|
3252
2751
|
multiplexer;
|
|
3253
2752
|
router;
|
|
3254
2753
|
transport;
|
|
2754
|
+
httpServers = [];
|
|
2755
|
+
httpTransports = [];
|
|
3255
2756
|
config;
|
|
3256
2757
|
isRunning = false;
|
|
2758
|
+
// Single-use receipt registry: bounded (FIFO cap) with expired-first eviction.
|
|
2759
|
+
claimedReceiptNonces = new MemoryReceiptStore();
|
|
3257
2760
|
constructor() {
|
|
3258
|
-
this.server =
|
|
2761
|
+
this.server = this.createSessionServer();
|
|
2762
|
+
this.multiplexer = new UpstreamMultiplexer();
|
|
2763
|
+
this.router = contextRouter;
|
|
2764
|
+
}
|
|
2765
|
+
/**
|
|
2766
|
+
* Builds a Session-scoped MCP Server whose handlers delegate to the shared
|
|
2767
|
+
* engine (router, multiplexer, cloud clients). One instance per HTTP
|
|
2768
|
+
* session — the MCP SDK binds a Server to a single transport, and sharing
|
|
2769
|
+
* one Server across sessions breaks repeat handshakes.
|
|
2770
|
+
*/
|
|
2771
|
+
createSessionServer() {
|
|
2772
|
+
const server = new Server(
|
|
3259
2773
|
{
|
|
3260
2774
|
name: "contextwise",
|
|
3261
2775
|
version: "0.1.0"
|
|
@@ -3268,12 +2782,12 @@ var ContextWiseProxy = class {
|
|
|
3268
2782
|
}
|
|
3269
2783
|
}
|
|
3270
2784
|
);
|
|
3271
|
-
this.
|
|
3272
|
-
|
|
3273
|
-
this.setupRequestHandlers();
|
|
2785
|
+
this.setupRequestHandlers(server);
|
|
2786
|
+
return server;
|
|
3274
2787
|
}
|
|
3275
|
-
setupRequestHandlers() {
|
|
3276
|
-
|
|
2788
|
+
setupRequestHandlers(server) {
|
|
2789
|
+
const target = server ?? this.server;
|
|
2790
|
+
target.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
3277
2791
|
const exposedTools = this.router.getExposedTools();
|
|
3278
2792
|
const allToolsCount = toolRegistry.getAllTools().length;
|
|
3279
2793
|
metricsCollector.updateToolCounts(allToolsCount, exposedTools.length);
|
|
@@ -3285,7 +2799,7 @@ var ContextWiseProxy = class {
|
|
|
3285
2799
|
tools: exposedTools
|
|
3286
2800
|
};
|
|
3287
2801
|
});
|
|
3288
|
-
|
|
2802
|
+
target.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
3289
2803
|
const { name, arguments: args = {} } = request.params;
|
|
3290
2804
|
return this.handleToolCall(name, args);
|
|
3291
2805
|
});
|
|
@@ -3293,7 +2807,7 @@ var ContextWiseProxy = class {
|
|
|
3293
2807
|
/**
|
|
3294
2808
|
* Main tool execution pipeline handling meta-tools and proxied calls.
|
|
3295
2809
|
*/
|
|
3296
|
-
async handleToolCall(name, args = {}) {
|
|
2810
|
+
async handleToolCall(name, args = {}, receipt) {
|
|
3297
2811
|
if (name === SEARCH_TOOLS_NAME) {
|
|
3298
2812
|
return this.handleSearchToolsCall(args);
|
|
3299
2813
|
}
|
|
@@ -3319,6 +2833,15 @@ var ContextWiseProxy = class {
|
|
|
3319
2833
|
if (name === ADD_SERVER_NAME) {
|
|
3320
2834
|
return this.handleAddServerCall(args);
|
|
3321
2835
|
}
|
|
2836
|
+
if (name === CLOUD_PUSH_NAME) {
|
|
2837
|
+
return this.handleCloudPushCall(args, receipt);
|
|
2838
|
+
}
|
|
2839
|
+
if (name === CLOUD_PULL_NAME) {
|
|
2840
|
+
return this.handleCloudPullCall(args, receipt);
|
|
2841
|
+
}
|
|
2842
|
+
if (name === SYNC_STATUS_NAME) {
|
|
2843
|
+
return this.handleSyncStatusCall();
|
|
2844
|
+
}
|
|
3322
2845
|
return this.dispatchProxiedToolCall(name, args);
|
|
3323
2846
|
}
|
|
3324
2847
|
/**
|
|
@@ -3576,6 +3099,68 @@ These tools are now indexed, ${persistMsg}and ready for immediate invocation in
|
|
|
3576
3099
|
]
|
|
3577
3100
|
};
|
|
3578
3101
|
}
|
|
3102
|
+
/**
|
|
3103
|
+
* Handles metered cloud-sync meta-tool calls. These operate on the host
|
|
3104
|
+
* instance's workspace via its stored ContextWise Cloud session.
|
|
3105
|
+
*/
|
|
3106
|
+
async handleCloudPushCall(args, receipt) {
|
|
3107
|
+
const { syncManager } = await import("./sync_manager-2WYXXGCN.js");
|
|
3108
|
+
const { cloudClient } = await import("./client-3GQWPGI3.js");
|
|
3109
|
+
if (!cloudClient.isAuthenticated()) {
|
|
3110
|
+
return {
|
|
3111
|
+
isError: true,
|
|
3112
|
+
content: [{ type: "text", text: 'Error: Host instance is not logged in to ContextWise Cloud. Run "contextwise login" on the host.' }]
|
|
3113
|
+
};
|
|
3114
|
+
}
|
|
3115
|
+
try {
|
|
3116
|
+
const workspaceId = typeof args.workspaceId === "string" ? args.workspaceId : void 0;
|
|
3117
|
+
const effectiveReceipt = receipt || requestContext.getStore()?.receipt;
|
|
3118
|
+
const res = await syncManager.push(workspaceId, effectiveReceipt);
|
|
3119
|
+
if (res.status === "committed") {
|
|
3120
|
+
return { content: [{ type: "text", text: `Cloud sync push committed as revision #${res.revision}.` }] };
|
|
3121
|
+
}
|
|
3122
|
+
return {
|
|
3123
|
+
isError: true,
|
|
3124
|
+
content: [{ type: "text", text: `Push conflict: server has revision #${res.serverRevision}. Pull first, then retry.` }]
|
|
3125
|
+
};
|
|
3126
|
+
} catch (err) {
|
|
3127
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3128
|
+
return { isError: true, content: [{ type: "text", text: redactionFilter.redact(`Cloud push failed: ${msg}`) }] };
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3131
|
+
async handleCloudPullCall(args, receipt) {
|
|
3132
|
+
const { syncManager } = await import("./sync_manager-2WYXXGCN.js");
|
|
3133
|
+
const { cloudClient } = await import("./client-3GQWPGI3.js");
|
|
3134
|
+
if (!cloudClient.isAuthenticated()) {
|
|
3135
|
+
return {
|
|
3136
|
+
isError: true,
|
|
3137
|
+
content: [{ type: "text", text: 'Error: Host instance is not logged in to ContextWise Cloud. Run "contextwise login" on the host.' }]
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
try {
|
|
3141
|
+
const workspaceId = typeof args.workspaceId === "string" ? args.workspaceId : void 0;
|
|
3142
|
+
const effectiveReceipt = receipt || requestContext.getStore()?.receipt;
|
|
3143
|
+
const res = await syncManager.pull(workspaceId, effectiveReceipt);
|
|
3144
|
+
if (res) {
|
|
3145
|
+
return { content: [{ type: "text", text: `Applied cloud revision #${res.revision} to host workspace.` }] };
|
|
3146
|
+
}
|
|
3147
|
+
return { content: [{ type: "text", text: "Host workspace is already up to date." }] };
|
|
3148
|
+
} catch (err) {
|
|
3149
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3150
|
+
return { isError: true, content: [{ type: "text", text: redactionFilter.redact(`Cloud pull failed: ${msg}`) }] };
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
async handleSyncStatusCall() {
|
|
3154
|
+
const { syncManager } = await import("./sync_manager-2WYXXGCN.js");
|
|
3155
|
+
const status = syncManager.getStatus();
|
|
3156
|
+
const lines = [
|
|
3157
|
+
`Logged in: ${status.isLoggedIn ? "yes" : "no"}`,
|
|
3158
|
+
`Workspace: ${status.workspaceId}`,
|
|
3159
|
+
`Local revision: ${status.revision}`,
|
|
3160
|
+
`Last synced: ${status.lastSyncedAt > 0 ? new Date(status.lastSyncedAt).toISOString() : "never"}`
|
|
3161
|
+
];
|
|
3162
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
3163
|
+
}
|
|
3579
3164
|
/**
|
|
3580
3165
|
* Dispatches tool call through guardrails and upstream multiplexer.
|
|
3581
3166
|
*/
|
|
@@ -3698,9 +3283,9 @@ ${validation.errors?.join("\n")}`;
|
|
|
3698
3283
|
}
|
|
3699
3284
|
}
|
|
3700
3285
|
/**
|
|
3701
|
-
*
|
|
3286
|
+
* Shared boot: config, guardrails, upstreams, catalog. Transport-agnostic.
|
|
3702
3287
|
*/
|
|
3703
|
-
async
|
|
3288
|
+
async boot(config) {
|
|
3704
3289
|
this.config = config;
|
|
3705
3290
|
this.isRunning = true;
|
|
3706
3291
|
logger.setLevel(config.proxy.logLevel);
|
|
@@ -3723,605 +3308,225 @@ ${validation.errors?.join("\n")}`;
|
|
|
3723
3308
|
}
|
|
3724
3309
|
});
|
|
3725
3310
|
this.router.primeWorkspace(process.cwd());
|
|
3726
|
-
this.transport = new StdioServerTransport();
|
|
3727
|
-
await this.server.connect(this.transport);
|
|
3728
|
-
logger.info("ContextWise MCP Proxy is running and ready for client connections.");
|
|
3729
3311
|
}
|
|
3730
3312
|
/**
|
|
3731
|
-
*
|
|
3313
|
+
* Starts ContextWise proxy over stdio (default: local AI clients).
|
|
3732
3314
|
*/
|
|
3733
|
-
async
|
|
3734
|
-
|
|
3735
|
-
this.
|
|
3736
|
-
|
|
3737
|
-
logger.info("
|
|
3738
|
-
await this.multiplexer.closeAll();
|
|
3739
|
-
if (this.transport) {
|
|
3740
|
-
await this.transport.close();
|
|
3741
|
-
}
|
|
3742
|
-
await this.server.close();
|
|
3743
|
-
logger.info("ContextWise proxy stopped cleanly.");
|
|
3744
|
-
}
|
|
3745
|
-
getMultiplexer() {
|
|
3746
|
-
return this.multiplexer;
|
|
3747
|
-
}
|
|
3748
|
-
getRouter() {
|
|
3749
|
-
return this.router;
|
|
3750
|
-
}
|
|
3751
|
-
};
|
|
3752
|
-
var proxy = new ContextWiseProxy();
|
|
3753
|
-
|
|
3754
|
-
// src/cloud/client.ts
|
|
3755
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
3756
|
-
import { homedir as homedir6 } from "os";
|
|
3757
|
-
import { join as join6 } from "path";
|
|
3758
|
-
var ContextWiseCloudClient = class {
|
|
3759
|
-
apiUrl;
|
|
3760
|
-
storageDir;
|
|
3761
|
-
allowOfflineSimulation;
|
|
3762
|
-
token = null;
|
|
3763
|
-
constructor(options = {}) {
|
|
3764
|
-
this.apiUrl = options.apiUrl || process.env.CONTEXTWISE_API_URL || "https://contextwise.dev";
|
|
3765
|
-
this.storageDir = options.storageDir || process.env.CONTEXTWISE_STORAGE_DIR || join6(homedir6(), ".contextwise");
|
|
3766
|
-
this.allowOfflineSimulation = options.allowOfflineSimulation ?? false;
|
|
3767
|
-
this.loadToken();
|
|
3768
|
-
}
|
|
3769
|
-
getTokenPath() {
|
|
3770
|
-
return join6(this.storageDir, "auth.json");
|
|
3771
|
-
}
|
|
3772
|
-
loadToken() {
|
|
3773
|
-
const tokenPath = this.getTokenPath();
|
|
3774
|
-
if (existsSync8(tokenPath)) {
|
|
3775
|
-
try {
|
|
3776
|
-
const raw = readFileSync7(tokenPath, "utf-8");
|
|
3777
|
-
this.token = JSON.parse(raw);
|
|
3778
|
-
} catch {
|
|
3779
|
-
this.token = null;
|
|
3780
|
-
}
|
|
3781
|
-
}
|
|
3782
|
-
}
|
|
3783
|
-
saveToken(token) {
|
|
3784
|
-
if (!existsSync8(this.storageDir)) {
|
|
3785
|
-
mkdirSync5(this.storageDir, { recursive: true });
|
|
3786
|
-
}
|
|
3787
|
-
this.token = token;
|
|
3788
|
-
writeFileSync5(this.getTokenPath(), JSON.stringify(token, null, 2), {
|
|
3789
|
-
mode: 384,
|
|
3790
|
-
encoding: "utf-8"
|
|
3791
|
-
});
|
|
3792
|
-
}
|
|
3793
|
-
clearToken() {
|
|
3794
|
-
this.token = null;
|
|
3795
|
-
const tokenPath = this.getTokenPath();
|
|
3796
|
-
if (existsSync8(tokenPath)) {
|
|
3797
|
-
try {
|
|
3798
|
-
unlinkSync2(tokenPath);
|
|
3799
|
-
} catch {
|
|
3800
|
-
}
|
|
3801
|
-
}
|
|
3802
|
-
}
|
|
3803
|
-
getToken() {
|
|
3804
|
-
if (!this.token) {
|
|
3805
|
-
this.loadToken();
|
|
3806
|
-
}
|
|
3807
|
-
return this.token;
|
|
3808
|
-
}
|
|
3809
|
-
isAuthenticated() {
|
|
3810
|
-
const token = this.getToken();
|
|
3811
|
-
if (!token) return false;
|
|
3812
|
-
return token.expiresAt > Date.now();
|
|
3315
|
+
async start(config) {
|
|
3316
|
+
await this.boot(config);
|
|
3317
|
+
this.transport = new StdioServerTransport();
|
|
3318
|
+
await this.server.connect(this.transport);
|
|
3319
|
+
logger.info("ContextWise MCP Proxy is running and ready for client connections.");
|
|
3813
3320
|
}
|
|
3814
3321
|
/**
|
|
3815
|
-
* Starts
|
|
3322
|
+
* Starts ContextWise proxy over Streamable HTTP (for MCPaid edge publishing
|
|
3323
|
+
* via `mcpaid publish`, or any remote MCP client). Stateless: each request
|
|
3324
|
+
* gets its own transport, so no session affinity is required.
|
|
3816
3325
|
*/
|
|
3817
|
-
async
|
|
3818
|
-
const
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
throw new Error(
|
|
3326
|
+
async startHttp(config, options = {}) {
|
|
3327
|
+
const { createServer } = await import("http");
|
|
3328
|
+
let StreamableHTTPServerTransport;
|
|
3329
|
+
try {
|
|
3330
|
+
({ StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js"));
|
|
3331
|
+
} catch {
|
|
3332
|
+
throw new Error(
|
|
3333
|
+
"HTTP transport requires @modelcontextprotocol/sdk with Streamable HTTP support. Run: npm install @modelcontextprotocol/sdk@latest"
|
|
3334
|
+
);
|
|
3824
3335
|
}
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
const
|
|
3833
|
-
while (Date.now() - start < maxWaitMs) {
|
|
3834
|
-
await new Promise((r) => setTimeout(r, intervalMs));
|
|
3336
|
+
await this.boot(config);
|
|
3337
|
+
const port = options.port ?? 3e3;
|
|
3338
|
+
const mcpPath = options.path ?? "/mcp";
|
|
3339
|
+
const { randomUUID } = await import("crypto");
|
|
3340
|
+
const { isInitializeRequest } = await import("@modelcontextprotocol/sdk/types.js");
|
|
3341
|
+
const httpSessions = /* @__PURE__ */ new Map();
|
|
3342
|
+
this.httpTransports.push(httpSessions);
|
|
3343
|
+
const httpServer = createServer(async (req, res) => {
|
|
3835
3344
|
try {
|
|
3836
|
-
const
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
if (res.ok) {
|
|
3842
|
-
const data = await res.json();
|
|
3843
|
-
const authToken = {
|
|
3844
|
-
token: data.token,
|
|
3845
|
-
userId: data.userId,
|
|
3846
|
-
email: data.email,
|
|
3847
|
-
expiresAt: data.expiresAt
|
|
3848
|
-
};
|
|
3849
|
-
this.saveToken(authToken);
|
|
3850
|
-
return authToken;
|
|
3345
|
+
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
3346
|
+
if (url.pathname !== mcpPath) {
|
|
3347
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
3348
|
+
res.end(JSON.stringify({ error: "not_found", message: `Expected POST ${mcpPath}` }));
|
|
3349
|
+
return;
|
|
3851
3350
|
}
|
|
3852
|
-
if (
|
|
3853
|
-
|
|
3351
|
+
if (req.method !== "POST" && req.method !== "GET" && req.method !== "DELETE") {
|
|
3352
|
+
res.writeHead(405, { "Content-Type": "application/json" });
|
|
3353
|
+
res.end(JSON.stringify({ error: "method_not_allowed" }));
|
|
3354
|
+
return;
|
|
3854
3355
|
}
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3356
|
+
let body;
|
|
3357
|
+
if (req.method === "POST") {
|
|
3358
|
+
const chunks = [];
|
|
3359
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
3360
|
+
try {
|
|
3361
|
+
body = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
|
|
3362
|
+
} catch {
|
|
3363
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
3364
|
+
res.end(JSON.stringify({ error: "invalid_json" }));
|
|
3365
|
+
return;
|
|
3366
|
+
}
|
|
3860
3367
|
}
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3368
|
+
const rawReceipt = req.headers["x-mcpaid-receipt"];
|
|
3369
|
+
const receiptHeader = Array.isArray(rawReceipt) ? rawReceipt[0] : rawReceipt;
|
|
3370
|
+
const receiptSecret = (process.env.MCPAID_RECEIPT_SECRET || "").trim();
|
|
3371
|
+
const previousReceiptSecret = (process.env.MCPAID_PREVIOUS_RECEIPT_SECRET || "").trim();
|
|
3372
|
+
if (receiptSecret && body && typeof body === "object") {
|
|
3373
|
+
const rpcRequests = Array.isArray(body) ? body : [body];
|
|
3374
|
+
for (const rpc of rpcRequests) {
|
|
3375
|
+
if (rpc && typeof rpc === "object" && rpc.method === "tools/call" && rpc.params?.name) {
|
|
3376
|
+
const toolName = String(rpc.params.name);
|
|
3377
|
+
if (toolName === CLOUD_PUSH_NAME || toolName === CLOUD_PULL_NAME) {
|
|
3378
|
+
const minAmountMicro = toolName === CLOUD_PUSH_NAME ? 10000n : 5000n;
|
|
3379
|
+
const verifyResult = await parseAndVerifyEdgeReceipt(
|
|
3380
|
+
{
|
|
3381
|
+
secret: receiptSecret,
|
|
3382
|
+
previousSecret: previousReceiptSecret || void 0,
|
|
3383
|
+
store: this.claimedReceiptNonces
|
|
3384
|
+
},
|
|
3385
|
+
{
|
|
3386
|
+
receipt: receiptHeader,
|
|
3387
|
+
expectedServer: "contextwise",
|
|
3388
|
+
expectedTool: toolName,
|
|
3389
|
+
minAmountMicro
|
|
3390
|
+
}
|
|
3391
|
+
);
|
|
3392
|
+
if (!verifyResult.valid) {
|
|
3393
|
+
logger.warn(`Rejected unverified tool call [${toolName}]: ${verifyResult.error}`);
|
|
3394
|
+
res.writeHead(402, { "Content-Type": "application/json" });
|
|
3395
|
+
res.end(
|
|
3396
|
+
JSON.stringify({
|
|
3397
|
+
jsonrpc: "2.0",
|
|
3398
|
+
id: rpc.id ?? null,
|
|
3399
|
+
error: {
|
|
3400
|
+
code: -32002,
|
|
3401
|
+
message: `Payment Required: ${verifyResult.error}`
|
|
3402
|
+
}
|
|
3403
|
+
})
|
|
3404
|
+
);
|
|
3405
|
+
return;
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3879
3410
|
}
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3411
|
+
const isGatewayOrigin = req.headers["x-mcpaid-gateway"] === "true";
|
|
3412
|
+
const rawSession = req.headers["mcp-session-id"];
|
|
3413
|
+
const sessionId = Array.isArray(rawSession) ? rawSession[0] : rawSession;
|
|
3414
|
+
let session = sessionId && httpSessions.get(sessionId) || void 0;
|
|
3415
|
+
if (!session && isGatewayOrigin && req.method === "POST") {
|
|
3416
|
+
const transport = new StreamableHTTPServerTransport({
|
|
3417
|
+
sessionIdGenerator: void 0
|
|
3418
|
+
});
|
|
3419
|
+
const server = this.createSessionServer();
|
|
3420
|
+
session = { transport, server };
|
|
3421
|
+
await server.connect(transport);
|
|
3422
|
+
try {
|
|
3423
|
+
await requestContext.run({ receipt: receiptHeader }, async () => {
|
|
3424
|
+
await session.transport.handleRequest(req, res, body);
|
|
3425
|
+
});
|
|
3426
|
+
} finally {
|
|
3427
|
+
try {
|
|
3428
|
+
await transport.close();
|
|
3429
|
+
} catch {
|
|
3430
|
+
}
|
|
3431
|
+
try {
|
|
3432
|
+
await server.close();
|
|
3433
|
+
} catch {
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
return;
|
|
3884
3437
|
}
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
async whoami() {
|
|
3914
|
-
const token = this.getToken();
|
|
3915
|
-
if (!token) {
|
|
3916
|
-
throw new Error('Not logged in. Run "contextwise login" first.');
|
|
3917
|
-
}
|
|
3918
|
-
try {
|
|
3919
|
-
const res = await fetch(`${this.apiUrl}/v1/user/profile`, {
|
|
3920
|
-
headers: { Authorization: `Bearer ${token.token}` }
|
|
3921
|
-
});
|
|
3922
|
-
if (res.ok) {
|
|
3923
|
-
return await res.json();
|
|
3924
|
-
}
|
|
3925
|
-
if (!this.allowOfflineSimulation) {
|
|
3926
|
-
throw new Error(`Failed to fetch profile: HTTP ${res.status} ${res.statusText}`);
|
|
3927
|
-
}
|
|
3928
|
-
} catch (err) {
|
|
3929
|
-
if (!this.allowOfflineSimulation) {
|
|
3930
|
-
throw err;
|
|
3931
|
-
}
|
|
3932
|
-
}
|
|
3933
|
-
return {
|
|
3934
|
-
userId: token.userId,
|
|
3935
|
-
email: token.email,
|
|
3936
|
-
plan: "free",
|
|
3937
|
-
subscriptionStatus: "inactive",
|
|
3938
|
-
currentPeriodEnd: null,
|
|
3939
|
-
workspaces: [
|
|
3940
|
-
{
|
|
3941
|
-
id: "ws_default",
|
|
3942
|
-
name: "Personal Workspace",
|
|
3943
|
-
role: "owner",
|
|
3944
|
-
activeRevision: 1,
|
|
3945
|
-
updatedAt: Date.now()
|
|
3438
|
+
if (!session) {
|
|
3439
|
+
if (req.method === "POST" && body && typeof body === "object" && isInitializeRequest(body)) {
|
|
3440
|
+
const transport = new StreamableHTTPServerTransport({
|
|
3441
|
+
sessionIdGenerator: () => randomUUID(),
|
|
3442
|
+
onsessioninitialized: (sid) => {
|
|
3443
|
+
httpSessions.set(sid, session);
|
|
3444
|
+
}
|
|
3445
|
+
});
|
|
3446
|
+
const server = this.createSessionServer();
|
|
3447
|
+
session = { transport, server };
|
|
3448
|
+
transport.onclose = () => {
|
|
3449
|
+
const sid = transport.sessionId;
|
|
3450
|
+
if (sid && httpSessions.get(sid)?.transport === transport) {
|
|
3451
|
+
httpSessions.delete(sid);
|
|
3452
|
+
}
|
|
3453
|
+
};
|
|
3454
|
+
await server.connect(transport);
|
|
3455
|
+
} else {
|
|
3456
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
3457
|
+
res.end(
|
|
3458
|
+
JSON.stringify({
|
|
3459
|
+
jsonrpc: "2.0",
|
|
3460
|
+
id: null,
|
|
3461
|
+
error: { code: -32e3, message: "Bad Request: No valid session ID provided" }
|
|
3462
|
+
})
|
|
3463
|
+
);
|
|
3464
|
+
return;
|
|
3465
|
+
}
|
|
3946
3466
|
}
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
const res = await fetch(`${this.apiUrl}/v1/billing/checkout`, {
|
|
3960
|
-
method: "POST",
|
|
3961
|
-
headers: {
|
|
3962
|
-
"Content-Type": "application/json",
|
|
3963
|
-
Authorization: `Bearer ${token.token}`
|
|
3964
|
-
},
|
|
3965
|
-
body: JSON.stringify({ plan, interval })
|
|
3966
|
-
});
|
|
3967
|
-
if (!res.ok) {
|
|
3968
|
-
const errData = await res.json().catch(() => ({}));
|
|
3969
|
-
throw new Error(errData.message || `Failed to create checkout session: HTTP ${res.status}`);
|
|
3970
|
-
}
|
|
3971
|
-
return await res.json();
|
|
3972
|
-
}
|
|
3973
|
-
/**
|
|
3974
|
-
* Generates Stripe Customer Portal session to manage subscription and invoices.
|
|
3975
|
-
*/
|
|
3976
|
-
async createPortalSession() {
|
|
3977
|
-
const token = this.getToken();
|
|
3978
|
-
if (!token) {
|
|
3979
|
-
throw new Error('Not logged in. Run "contextwise login" first.');
|
|
3980
|
-
}
|
|
3981
|
-
const res = await fetch(`${this.apiUrl}/v1/billing/portal`, {
|
|
3982
|
-
method: "POST",
|
|
3983
|
-
headers: {
|
|
3984
|
-
"Content-Type": "application/json",
|
|
3985
|
-
Authorization: `Bearer ${token.token}`
|
|
3986
|
-
}
|
|
3987
|
-
});
|
|
3988
|
-
if (!res.ok) {
|
|
3989
|
-
const errData = await res.json().catch(() => ({}));
|
|
3990
|
-
throw new Error(errData.message || `Failed to open billing portal: HTTP ${res.status}`);
|
|
3991
|
-
}
|
|
3992
|
-
return await res.json();
|
|
3993
|
-
}
|
|
3994
|
-
/**
|
|
3995
|
-
* Pushes a workspace snapshot (config + encrypted vault) to the Cloudflare Workers / D1 backend.
|
|
3996
|
-
*/
|
|
3997
|
-
async pushSync(payload) {
|
|
3998
|
-
const token = this.getToken();
|
|
3999
|
-
if (!token) {
|
|
4000
|
-
throw new Error('Not logged in. Run "contextwise login" first.');
|
|
4001
|
-
}
|
|
4002
|
-
try {
|
|
4003
|
-
const res = await fetch(`${this.apiUrl}/v1/sync/push`, {
|
|
4004
|
-
method: "POST",
|
|
4005
|
-
headers: {
|
|
4006
|
-
"Content-Type": "application/json",
|
|
4007
|
-
Authorization: `Bearer ${token.token}`
|
|
4008
|
-
},
|
|
4009
|
-
body: JSON.stringify(payload)
|
|
4010
|
-
});
|
|
4011
|
-
if (res.ok) {
|
|
4012
|
-
return await res.json();
|
|
4013
|
-
}
|
|
4014
|
-
if (res.status === 402) {
|
|
4015
|
-
const payData = await res.json().catch(() => ({}));
|
|
4016
|
-
throw new Error(
|
|
4017
|
-
`${payData.message || "Cloud Sync requires an upgraded subscription."}
|
|
4018
|
-
Upgrade at: ${payData.upgradeUrl || "https://contextwise.dev/#pricing"}`
|
|
4019
|
-
);
|
|
4020
|
-
}
|
|
4021
|
-
if (res.status === 409) {
|
|
4022
|
-
const conflictData = await res.json();
|
|
4023
|
-
return {
|
|
4024
|
-
status: "conflict",
|
|
4025
|
-
revision: payload.baseRevision,
|
|
4026
|
-
serverRevision: conflictData.serverRevision,
|
|
4027
|
-
serverPayload: conflictData.serverPayload
|
|
4028
|
-
};
|
|
4029
|
-
}
|
|
4030
|
-
throw new Error(`Push sync failed: HTTP ${res.status} ${res.statusText}`);
|
|
4031
|
-
} catch (err) {
|
|
4032
|
-
if (err instanceof Error && (err.message.includes("requires an upgraded subscription") || err.message.includes("Cloud Sync requires"))) {
|
|
4033
|
-
throw err;
|
|
4034
|
-
}
|
|
4035
|
-
if (this.allowOfflineSimulation) {
|
|
4036
|
-
logger.debug(`Cloud API push endpoint unreachable (${err}). Using local snapshot commit.`);
|
|
4037
|
-
return {
|
|
4038
|
-
status: "committed",
|
|
4039
|
-
revision: payload.baseRevision + 1
|
|
4040
|
-
};
|
|
4041
|
-
}
|
|
4042
|
-
throw err;
|
|
4043
|
-
}
|
|
4044
|
-
}
|
|
4045
|
-
/**
|
|
4046
|
-
* Pulls the latest workspace snapshot (config + encrypted vault) from the cloud backend.
|
|
4047
|
-
*/
|
|
4048
|
-
async pullSync(workspaceId, sinceRevision = 0) {
|
|
4049
|
-
const token = this.getToken();
|
|
4050
|
-
if (!token) {
|
|
4051
|
-
throw new Error('Not logged in. Run "contextwise login" first.');
|
|
4052
|
-
}
|
|
4053
|
-
try {
|
|
4054
|
-
const res = await fetch(
|
|
4055
|
-
`${this.apiUrl}/v1/sync/pull?workspaceId=${encodeURIComponent(
|
|
4056
|
-
workspaceId
|
|
4057
|
-
)}&since=${sinceRevision}`,
|
|
4058
|
-
{
|
|
4059
|
-
headers: { Authorization: `Bearer ${token.token}` }
|
|
3467
|
+
await requestContext.run({ receipt: receiptHeader }, async () => {
|
|
3468
|
+
await session.transport.handleRequest(req, res, body);
|
|
3469
|
+
});
|
|
3470
|
+
} catch (err) {
|
|
3471
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3472
|
+
logger.error(`HTTP transport error: ${msg}`);
|
|
3473
|
+
if (!res.headersSent) {
|
|
3474
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
3475
|
+
}
|
|
3476
|
+
try {
|
|
3477
|
+
res.end(JSON.stringify({ error: "internal_error", message: msg }));
|
|
3478
|
+
} catch {
|
|
4060
3479
|
}
|
|
4061
|
-
);
|
|
4062
|
-
if (res.ok) {
|
|
4063
|
-
return await res.json();
|
|
4064
|
-
}
|
|
4065
|
-
if (res.status === 304) {
|
|
4066
|
-
return null;
|
|
4067
|
-
}
|
|
4068
|
-
} catch (err) {
|
|
4069
|
-
logger.debug(`Cloud API pull endpoint unreachable: ${err}`);
|
|
4070
|
-
}
|
|
4071
|
-
return null;
|
|
4072
|
-
}
|
|
4073
|
-
};
|
|
4074
|
-
var cloudClient = new ContextWiseCloudClient();
|
|
4075
|
-
|
|
4076
|
-
// src/cloud/crypto.ts
|
|
4077
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
4078
|
-
import { homedir as homedir7 } from "os";
|
|
4079
|
-
import { join as join7 } from "path";
|
|
4080
|
-
import { randomBytes as randomBytes2 } from "crypto";
|
|
4081
|
-
function createEnvelope(workspaceKey, recipientPublicKeyPem) {
|
|
4082
|
-
const ephemeral = generateKeyPairX25519();
|
|
4083
|
-
const sharedKey = deriveSharedSecretX25519(ephemeral.privateKey, recipientPublicKeyPem);
|
|
4084
|
-
const { iv, authTag, ciphertext } = encryptAesGcm(
|
|
4085
|
-
workspaceKey.toString("base64"),
|
|
4086
|
-
sharedKey
|
|
4087
|
-
);
|
|
4088
|
-
return {
|
|
4089
|
-
recipientPublicKey: recipientPublicKeyPem,
|
|
4090
|
-
ephemeralPublicKey: ephemeral.publicKey,
|
|
4091
|
-
iv,
|
|
4092
|
-
authTag,
|
|
4093
|
-
ciphertext
|
|
4094
|
-
};
|
|
4095
|
-
}
|
|
4096
|
-
function openEnvelope(envelope, recipientPrivateKeyPem) {
|
|
4097
|
-
const sharedKey = deriveSharedSecretX25519(
|
|
4098
|
-
recipientPrivateKeyPem,
|
|
4099
|
-
envelope.ephemeralPublicKey
|
|
4100
|
-
);
|
|
4101
|
-
const decryptedBase64 = decryptAesGcm(
|
|
4102
|
-
envelope.ciphertext,
|
|
4103
|
-
sharedKey,
|
|
4104
|
-
envelope.iv,
|
|
4105
|
-
envelope.authTag
|
|
4106
|
-
);
|
|
4107
|
-
return Buffer.from(decryptedBase64, "base64");
|
|
4108
|
-
}
|
|
4109
|
-
function getOrCreateDeviceIdentity(storageDir) {
|
|
4110
|
-
const dir = storageDir || process.env.CONTEXTWISE_STORAGE_DIR || join7(homedir7(), ".contextwise");
|
|
4111
|
-
const filePath = join7(dir, "device.json");
|
|
4112
|
-
if (existsSync9(filePath)) {
|
|
4113
|
-
try {
|
|
4114
|
-
const data = JSON.parse(readFileSync8(filePath, "utf-8"));
|
|
4115
|
-
if (data.deviceId && data.publicKey && data.privateKey) {
|
|
4116
|
-
return data;
|
|
4117
|
-
}
|
|
4118
|
-
} catch {
|
|
4119
|
-
}
|
|
4120
|
-
}
|
|
4121
|
-
if (!existsSync9(dir)) {
|
|
4122
|
-
mkdirSync6(dir, { recursive: true });
|
|
4123
|
-
}
|
|
4124
|
-
const { publicKey, privateKey } = generateKeyPairX25519();
|
|
4125
|
-
const deviceId = `cw_dev_${randomBytes2(8).toString("hex")}`;
|
|
4126
|
-
const identity = {
|
|
4127
|
-
deviceId,
|
|
4128
|
-
publicKey,
|
|
4129
|
-
privateKey
|
|
4130
|
-
};
|
|
4131
|
-
writeFileSync6(filePath, JSON.stringify(identity, null, 2), {
|
|
4132
|
-
mode: 384,
|
|
4133
|
-
encoding: "utf-8"
|
|
4134
|
-
});
|
|
4135
|
-
return identity;
|
|
4136
|
-
}
|
|
4137
|
-
|
|
4138
|
-
// src/cloud/sync_manager.ts
|
|
4139
|
-
import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
4140
|
-
import { homedir as homedir8 } from "os";
|
|
4141
|
-
import { join as join8 } from "path";
|
|
4142
|
-
var SyncManager = class {
|
|
4143
|
-
syncStatePath;
|
|
4144
|
-
localConfigPath;
|
|
4145
|
-
constructor(storageDir, localConfigPath) {
|
|
4146
|
-
const dir = storageDir || process.env.CONTEXTWISE_STORAGE_DIR || join8(homedir8(), ".contextwise");
|
|
4147
|
-
this.syncStatePath = join8(dir, "sync.json");
|
|
4148
|
-
this.localConfigPath = localConfigPath;
|
|
4149
|
-
}
|
|
4150
|
-
loadSyncState() {
|
|
4151
|
-
if (existsSync10(this.syncStatePath)) {
|
|
4152
|
-
try {
|
|
4153
|
-
const raw = readFileSync9(this.syncStatePath, "utf-8");
|
|
4154
|
-
return JSON.parse(raw);
|
|
4155
|
-
} catch {
|
|
4156
3480
|
}
|
|
4157
|
-
}
|
|
4158
|
-
return {
|
|
4159
|
-
workspaceId: "ws_default",
|
|
4160
|
-
revision: 0,
|
|
4161
|
-
lastSyncedAt: 0
|
|
4162
|
-
};
|
|
4163
|
-
}
|
|
4164
|
-
saveSyncState(state) {
|
|
4165
|
-
writeFileSync7(this.syncStatePath, JSON.stringify(state, null, 2), {
|
|
4166
|
-
mode: 384,
|
|
4167
|
-
encoding: "utf-8"
|
|
4168
3481
|
});
|
|
3482
|
+
await new Promise((resolve3) => httpServer.listen(port, "127.0.0.1", resolve3));
|
|
3483
|
+
this.httpServers.push(httpServer);
|
|
3484
|
+
logger.info(`ContextWise MCP Proxy listening on http://127.0.0.1:${port}${mcpPath} (Streamable HTTP).`);
|
|
3485
|
+
return httpServer;
|
|
4169
3486
|
}
|
|
4170
3487
|
/**
|
|
4171
|
-
*
|
|
4172
|
-
*/
|
|
4173
|
-
async push(workspaceId) {
|
|
4174
|
-
const state = this.loadSyncState();
|
|
4175
|
-
const wsId = workspaceId || state.workspaceId;
|
|
4176
|
-
const device = getOrCreateDeviceIdentity();
|
|
4177
|
-
const localConfig = ConfigLoader.load();
|
|
4178
|
-
const vaultPath = getDefaultVaultFilePath();
|
|
4179
|
-
let encryptedVault;
|
|
4180
|
-
if (existsSync10(vaultPath)) {
|
|
4181
|
-
try {
|
|
4182
|
-
const rawVault = readFileSync9(vaultPath, "utf-8");
|
|
4183
|
-
encryptedVault = JSON.parse(rawVault);
|
|
4184
|
-
} catch {
|
|
4185
|
-
encryptedVault = this.createEmptyEncryptedVault();
|
|
4186
|
-
}
|
|
4187
|
-
} else {
|
|
4188
|
-
encryptedVault = this.createEmptyEncryptedVault();
|
|
4189
|
-
}
|
|
4190
|
-
const payload = {
|
|
4191
|
-
workspaceId: wsId,
|
|
4192
|
-
deviceId: device.deviceId,
|
|
4193
|
-
baseRevision: state.revision,
|
|
4194
|
-
config: localConfig,
|
|
4195
|
-
encryptedVault,
|
|
4196
|
-
timestamp: Date.now()
|
|
4197
|
-
};
|
|
4198
|
-
const result = await cloudClient.pushSync(payload);
|
|
4199
|
-
if (result.status === "committed") {
|
|
4200
|
-
state.workspaceId = wsId;
|
|
4201
|
-
state.revision = result.revision;
|
|
4202
|
-
state.lastSyncedAt = Date.now();
|
|
4203
|
-
this.saveSyncState(state);
|
|
4204
|
-
logger.info(`Successfully pushed revision #${result.revision} to ContextWise Cloud.`);
|
|
4205
|
-
}
|
|
4206
|
-
return result;
|
|
4207
|
-
}
|
|
4208
|
-
/**
|
|
4209
|
-
* Pulls the latest cloud configuration and merges upstream servers.
|
|
4210
|
-
*/
|
|
4211
|
-
async pull(workspaceId) {
|
|
4212
|
-
const state = this.loadSyncState();
|
|
4213
|
-
const wsId = workspaceId || state.workspaceId;
|
|
4214
|
-
const pullResult = await cloudClient.pullSync(wsId, state.revision);
|
|
4215
|
-
if (!pullResult) {
|
|
4216
|
-
logger.info("Local configuration is already up to date.");
|
|
4217
|
-
return null;
|
|
4218
|
-
}
|
|
4219
|
-
if (pullResult.config && pullResult.config.upstreams) {
|
|
4220
|
-
this.mergeConfigIntoLocal(pullResult.config);
|
|
4221
|
-
}
|
|
4222
|
-
if (pullResult.encryptedVault && pullResult.encryptedVault.data) {
|
|
4223
|
-
const vaultPath = getDefaultVaultFilePath();
|
|
4224
|
-
writeFileSync7(vaultPath, JSON.stringify(pullResult.encryptedVault, null, 2), {
|
|
4225
|
-
mode: 384,
|
|
4226
|
-
encoding: "utf-8"
|
|
4227
|
-
});
|
|
4228
|
-
}
|
|
4229
|
-
state.revision = pullResult.revision;
|
|
4230
|
-
state.lastSyncedAt = Date.now();
|
|
4231
|
-
this.saveSyncState(state);
|
|
4232
|
-
logger.info(`Pulled and applied cloud revision #${pullResult.revision}.`);
|
|
4233
|
-
return pullResult;
|
|
4234
|
-
}
|
|
4235
|
-
/**
|
|
4236
|
-
* Semantically merges remote configuration into local contextwise.json.
|
|
3488
|
+
* Gracefully shuts down proxy and upstream connections.
|
|
4237
3489
|
*/
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
3490
|
+
async stop() {
|
|
3491
|
+
if (!this.isRunning) return;
|
|
3492
|
+
this.isRunning = false;
|
|
3493
|
+
metricsCollector.flushSync();
|
|
3494
|
+
logger.info("Shutting down ContextWise proxy...");
|
|
3495
|
+
await this.multiplexer.closeAll();
|
|
3496
|
+
for (const srv of this.httpServers) {
|
|
3497
|
+
await new Promise((resolve3) => srv.close(() => resolve3()));
|
|
3498
|
+
}
|
|
3499
|
+
this.httpServers = [];
|
|
3500
|
+
for (const t of this.httpTransports) {
|
|
3501
|
+
if (t instanceof Map) {
|
|
3502
|
+
for (const { transport } of t.values()) {
|
|
3503
|
+
await transport.close().catch(() => {
|
|
3504
|
+
});
|
|
3505
|
+
}
|
|
3506
|
+
} else {
|
|
3507
|
+
await t.close().catch(() => {
|
|
3508
|
+
});
|
|
4247
3509
|
}
|
|
4248
|
-
} else {
|
|
4249
|
-
localConfig = ContextWiseConfigSchema.parse({});
|
|
4250
3510
|
}
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
`[Cloud Sync Security] Remote configuration contains new stdio upstream "${name}" (${s.command}). Verify this server in contextwise.json before execution.`
|
|
4255
|
-
);
|
|
4256
|
-
}
|
|
3511
|
+
this.httpTransports = [];
|
|
3512
|
+
if (this.transport) {
|
|
3513
|
+
await this.transport.close();
|
|
4257
3514
|
}
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
...localConfig.upstreams || {}
|
|
4261
|
-
};
|
|
4262
|
-
const merged = {
|
|
4263
|
-
...localConfig,
|
|
4264
|
-
upstreams: mergedUpstreams
|
|
4265
|
-
};
|
|
4266
|
-
writeFileSync7(configPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
3515
|
+
await this.server.close();
|
|
3516
|
+
logger.info("ContextWise proxy stopped cleanly.");
|
|
4267
3517
|
}
|
|
4268
|
-
|
|
4269
|
-
return
|
|
4270
|
-
version: 1,
|
|
4271
|
-
kdf: {
|
|
4272
|
-
algorithm: "pbkdf2-sha512",
|
|
4273
|
-
salt: "empty",
|
|
4274
|
-
iterations: 5e4
|
|
4275
|
-
},
|
|
4276
|
-
cipher: "aes-256-gcm",
|
|
4277
|
-
iv: "",
|
|
4278
|
-
authTag: "",
|
|
4279
|
-
data: ""
|
|
4280
|
-
};
|
|
3518
|
+
getMultiplexer() {
|
|
3519
|
+
return this.multiplexer;
|
|
4281
3520
|
}
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
const state = this.loadSyncState();
|
|
4285
|
-
const device = getOrCreateDeviceIdentity();
|
|
4286
|
-
return {
|
|
4287
|
-
isLoggedIn: cloudClient.isAuthenticated(),
|
|
4288
|
-
userEmail: token?.email,
|
|
4289
|
-
workspaceId: state.workspaceId,
|
|
4290
|
-
revision: state.revision,
|
|
4291
|
-
lastSyncedAt: state.lastSyncedAt,
|
|
4292
|
-
deviceId: device.deviceId
|
|
4293
|
-
};
|
|
3521
|
+
getRouter() {
|
|
3522
|
+
return this.router;
|
|
4294
3523
|
}
|
|
4295
3524
|
};
|
|
4296
|
-
var
|
|
3525
|
+
var proxy = new ContextWiseProxy();
|
|
4297
3526
|
|
|
4298
3527
|
export {
|
|
4299
|
-
StdioUpstreamConfigSchema,
|
|
4300
|
-
HttpUpstreamConfigSchema,
|
|
4301
|
-
UpstreamServerConfigSchema,
|
|
4302
|
-
warnIfInsecureHttp,
|
|
4303
|
-
isStdioUpstream,
|
|
4304
|
-
isHttpUpstream,
|
|
4305
|
-
ProxyConfigSchema,
|
|
4306
|
-
RoutingConfigSchema,
|
|
4307
|
-
GuardrailsConfigSchema,
|
|
4308
|
-
ContextWiseConfigSchema,
|
|
4309
|
-
RedactionFilter,
|
|
4310
|
-
redactionFilter,
|
|
4311
|
-
Logger,
|
|
4312
|
-
logger,
|
|
4313
|
-
ConfigLoader,
|
|
4314
3528
|
ProcessSupervisor,
|
|
4315
3529
|
supervisor,
|
|
4316
|
-
encryptAesGcm,
|
|
4317
|
-
decryptAesGcm,
|
|
4318
|
-
VAULT_PBKDF2_ITERATIONS,
|
|
4319
|
-
deriveKeyFromPassphrase,
|
|
4320
|
-
getOrCreateMachineKey,
|
|
4321
|
-
generateKeyPairX25519,
|
|
4322
|
-
deriveSharedSecretX25519,
|
|
4323
|
-
getDefaultVaultFilePath,
|
|
4324
|
-
EncryptedFileVaultDriver,
|
|
4325
3530
|
OsKeystoreDriver,
|
|
4326
3531
|
SecretVault,
|
|
4327
3532
|
secretVault,
|
|
@@ -4356,10 +3561,16 @@ export {
|
|
|
4356
3561
|
EXECUTE_TOOL_NAME,
|
|
4357
3562
|
BROWSE_SERVERS_NAME,
|
|
4358
3563
|
ADD_SERVER_NAME,
|
|
3564
|
+
CLOUD_PUSH_NAME,
|
|
3565
|
+
CLOUD_PULL_NAME,
|
|
3566
|
+
SYNC_STATUS_NAME,
|
|
4359
3567
|
SEARCH_TOOLS_DEFINITION,
|
|
4360
3568
|
EXECUTE_TOOL_DEFINITION,
|
|
4361
3569
|
BROWSE_SERVERS_DEFINITION,
|
|
4362
3570
|
ADD_SERVER_DEFINITION,
|
|
3571
|
+
CLOUD_PUSH_DEFINITION,
|
|
3572
|
+
CLOUD_PULL_DEFINITION,
|
|
3573
|
+
SYNC_STATUS_DEFINITION,
|
|
4363
3574
|
KNOWN_SERVERS,
|
|
4364
3575
|
KnownServerRegistry,
|
|
4365
3576
|
OfficialRegistryClient,
|
|
@@ -4370,14 +3581,8 @@ export {
|
|
|
4370
3581
|
WorkspaceContextPrimer,
|
|
4371
3582
|
ContextRouter,
|
|
4372
3583
|
contextRouter,
|
|
3584
|
+
requestContext,
|
|
4373
3585
|
ContextWiseProxy,
|
|
4374
|
-
proxy
|
|
4375
|
-
ContextWiseCloudClient,
|
|
4376
|
-
cloudClient,
|
|
4377
|
-
createEnvelope,
|
|
4378
|
-
openEnvelope,
|
|
4379
|
-
getOrCreateDeviceIdentity,
|
|
4380
|
-
SyncManager,
|
|
4381
|
-
syncManager
|
|
3586
|
+
proxy
|
|
4382
3587
|
};
|
|
4383
|
-
//# sourceMappingURL=chunk-
|
|
3588
|
+
//# sourceMappingURL=chunk-5ZMX5TEM.js.map
|