melchizedek-agents 1.2.0 → 1.3.1
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/.env.example +9 -0
- package/CHANGELOG.md +174 -0
- package/DOCUMENTATION.md +107 -10
- package/ONBOARDING.md +50 -11
- package/QUICKSTART.md +1 -1
- package/README.md +22 -2
- package/config/agents/examples/connectors.yaml +82 -0
- package/config/agents/syndicate.schema.json +324 -26
- package/dist/lib/a2a/app.js +3 -0
- package/dist/lib/a2a/oauthSetup.js +20 -5
- package/dist/lib/chatgpt/adapter.d.ts +50 -0
- package/dist/lib/chatgpt/adapter.js +107 -0
- package/dist/lib/chatgpt/cli.d.ts +23 -0
- package/dist/lib/chatgpt/cli.js +77 -0
- package/dist/lib/chatgpt/oauth.d.ts +104 -0
- package/dist/lib/chatgpt/oauth.js +472 -0
- package/dist/lib/chatgpt/state.d.ts +98 -0
- package/dist/lib/chatgpt/state.js +145 -0
- package/dist/lib/chatgpt/store.d.ts +24 -0
- package/dist/lib/chatgpt/store.js +94 -0
- package/dist/lib/compile.js +40 -25
- package/dist/lib/dispatch.js +6 -2
- package/dist/lib/doctor.d.ts +34 -3
- package/dist/lib/doctor.js +63 -5
- package/dist/lib/loadSyndicate.d.ts +6 -0
- package/dist/lib/mcp/server.d.ts +195 -0
- package/dist/lib/mcp/server.js +564 -0
- package/dist/lib/memory/supabaseMemoryService.js +2 -2
- package/dist/lib/models/adapterResolver.js +6 -1
- package/dist/lib/models/gateway.d.ts +1 -1
- package/dist/lib/models/gateway.js +4 -2
- package/dist/lib/models/gptAdapter.js +11 -1
- package/dist/lib/onboarding.d.ts +4 -3
- package/dist/lib/onboarding.js +48 -9
- package/dist/lib/runtime/syndicateTurn.js +30 -16
- package/dist/lib/syndicateSchema.d.ts +146 -8
- package/dist/lib/syndicateSchema.js +118 -11
- package/dist/lib/tools/credentialUses.d.ts +7 -1
- package/dist/lib/tools/credentialUses.js +10 -2
- package/dist/lib/tools/generateImageTool.js +2 -1
- package/dist/lib/tools/inspectImageTool.js +1 -1
- package/dist/lib/tools/mcpToolFactory.d.ts +29 -1
- package/dist/lib/tools/mcpToolFactory.js +71 -25
- package/dist/lib/tools/mcpTransports.d.ts +12 -0
- package/dist/lib/tools/mcpTransports.js +11 -0
- package/dist/lib/tools/oauthConsent.d.ts +18 -3
- package/dist/lib/tools/oauthConsent.js +49 -14
- package/dist/lib/tools/oauthDiscovery.d.ts +202 -0
- package/dist/lib/tools/oauthDiscovery.js +616 -0
- package/dist/lib/tools/oauthTools.d.ts +44 -12
- package/dist/lib/tools/oauthTools.js +79 -22
- package/dist/lib/tools/xApiSearchTool.js +2 -2
- package/dist/scripts/a2a_server.js +8 -0
- package/dist/scripts/assistant_worker.js +11 -1
- package/dist/scripts/doctor.d.ts +1 -1
- package/dist/scripts/doctor.js +1 -1
- package/dist/scripts/mcp_server.d.ts +12 -0
- package/dist/scripts/mcp_server.js +345 -0
- package/dist/scripts/setup.d.ts +2 -0
- package/dist/scripts/setup.js +27 -0
- package/dist/scripts/syndicate_chat.js +12 -3
- package/package.json +15 -12
- package/skills/README.md +2 -2
- package/skills/briefs/skills-readme.md +1 -1
- package/skills/melchizedek-onboard/SKILL.md +28 -6
- package/skills/melchizedek-onboard-keys/SKILL.md +17 -5
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* scripts/mcp_server.ts — the `melchizedek-mcp` bin: syndicates as MCP tools
|
|
4
|
+
* (ADR 0125). Thin: it reads flags and the environment, loads the syndicates,
|
|
5
|
+
* refuses unsafe binds, and hands everything to lib/mcp/server.ts.
|
|
6
|
+
*
|
|
7
|
+
* claude mcp add melch -- npx melchizedek-mcp
|
|
8
|
+
* melchizedek-mcp --syndicate research_desk --syndicate tutor
|
|
9
|
+
* melchizedek-mcp --http --port 4100
|
|
10
|
+
*
|
|
11
|
+
* Every console line goes to stderr: on stdio, stdout is the protocol.
|
|
12
|
+
*
|
|
13
|
+
* Flags:
|
|
14
|
+
* --syndicate <id> serve this syndicate (repeatable; a bare id may also
|
|
15
|
+
* resolve to the shipped examples/ and templates/).
|
|
16
|
+
* Positional ids work too. None: every YAML at the
|
|
17
|
+
* agents directory's root.
|
|
18
|
+
* --agents-dir <dir> the agents directory (MELCHIZEDEK_AGENTS_DIR, else
|
|
19
|
+
* <cwd>/config/agents)
|
|
20
|
+
* --http Streamable HTTP at /mcp instead of stdio
|
|
21
|
+
* --host <addr> HTTP bind (MCP_HOST, default 127.0.0.1)
|
|
22
|
+
* --port <n> HTTP port (MCP_PORT, default 4100)
|
|
23
|
+
*
|
|
24
|
+
* Environment (all optional):
|
|
25
|
+
* MCP_SERVER_SECRET bearer every HTTP request presents; required, 32+
|
|
26
|
+
* characters, on a bind beyond loopback
|
|
27
|
+
* MCP_ALLOWED_HOSTS comma list: Host names a non-loopback bind answers to
|
|
28
|
+
* MCP_TRUST_PROXY Express trust proxy (hop count, true/false, subnets)
|
|
29
|
+
* MCP_RATE_LIMIT_PER_MINUTE HTTP requests per minute per IP (240)
|
|
30
|
+
* MCP_USER_ID the scope a stdio client's sessions are stored under (default)
|
|
31
|
+
* DATABASE_URL Postgres for sessions, memory, turn locks, audit, credentials
|
|
32
|
+
* A2A_TASK_TIMEOUT_MS, A2A_MAX_CONCURRENT_TASKS, A2A_MAX_CONCURRENT_PER_SCOPE,
|
|
33
|
+
* A2A_MAX_CONCURRENT_PER_CALLER, A2A_TURN_LOCK_WAIT_MS, A2A_BUDGETS,
|
|
34
|
+
* A2A_SHUTDOWN_GRACE_MS, MELCHIZEDEK_CREDENTIAL_KEY, OAUTH_REDIRECT_URI,
|
|
35
|
+
* OAUTH_CALLBACK_IDENTITY, MELCHIZEDEK_OAUTH_HOSTS, MELCHIZEDEK_CREDENTIAL_HOSTS
|
|
36
|
+
* as melchizedek-serve reads them (scripts/a2a_server.ts)
|
|
37
|
+
*/
|
|
38
|
+
import { readdirSync, realpathSync } from 'node:fs';
|
|
39
|
+
import path from 'node:path';
|
|
40
|
+
import { pathToFileURL } from 'node:url';
|
|
41
|
+
/** The bin's flags. Throws, naming the flag, on one it does not know. */
|
|
42
|
+
export function parseMcpArgs(argv) {
|
|
43
|
+
const out = { syndicates: [], http: false, help: false };
|
|
44
|
+
for (let i = 0; i < argv.length; i++) {
|
|
45
|
+
const a = argv[i];
|
|
46
|
+
const value = () => {
|
|
47
|
+
const v = argv[++i];
|
|
48
|
+
if (v === undefined || v.startsWith('--'))
|
|
49
|
+
throw new Error(`${a} needs a value`);
|
|
50
|
+
return v;
|
|
51
|
+
};
|
|
52
|
+
if (a === '--help' || a === '-h')
|
|
53
|
+
out.help = true;
|
|
54
|
+
else if (a === '--http')
|
|
55
|
+
out.http = true;
|
|
56
|
+
else if (a === '--stdio')
|
|
57
|
+
out.http = false;
|
|
58
|
+
else if (a === '--syndicate')
|
|
59
|
+
out.syndicates.push(value());
|
|
60
|
+
else if (a === '--agents-dir')
|
|
61
|
+
out.agentsDir = value();
|
|
62
|
+
else if (a === '--host')
|
|
63
|
+
out.host = value();
|
|
64
|
+
else if (a === '--port') {
|
|
65
|
+
const n = Number(value());
|
|
66
|
+
if (!Number.isInteger(n) || n < 0 || n > 65535)
|
|
67
|
+
throw new Error('--port must be a port number');
|
|
68
|
+
out.port = n;
|
|
69
|
+
}
|
|
70
|
+
else if (a.startsWith('-'))
|
|
71
|
+
throw new Error(`Unknown flag ${a} (see --help)`);
|
|
72
|
+
else
|
|
73
|
+
out.syndicates.push(a);
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
const HELP = [
|
|
78
|
+
'Usage: melchizedek-mcp [--syndicate <id>]... [--agents-dir <dir>] [--http [--host <addr>] [--port <n>]]',
|
|
79
|
+
'',
|
|
80
|
+
'Serves syndicates as MCP tools: one tool per syndicate ({ message, session_id? }),',
|
|
81
|
+
'plus melch_resume for a turn that paused (an approval, a question, an authorization).',
|
|
82
|
+
'stdio by default (Claude Code: claude mcp add melch -- npx melchizedek-mcp);',
|
|
83
|
+
'--http serves Streamable HTTP at /mcp on 127.0.0.1; a wider --host needs MCP_SERVER_SECRET.',
|
|
84
|
+
'Environment variables are listed at the top of scripts/mcp_server.ts.',
|
|
85
|
+
].join('\n');
|
|
86
|
+
function envInt(name, fallback) {
|
|
87
|
+
const raw = process.env[name]?.trim();
|
|
88
|
+
if (!raw)
|
|
89
|
+
return fallback;
|
|
90
|
+
const n = Number(raw);
|
|
91
|
+
if (!Number.isFinite(n) || n < 0)
|
|
92
|
+
throw new Error(`${name} must be a non-negative number`);
|
|
93
|
+
return Math.floor(n);
|
|
94
|
+
}
|
|
95
|
+
function trustProxy() {
|
|
96
|
+
const raw = process.env.MCP_TRUST_PROXY?.trim();
|
|
97
|
+
if (!raw)
|
|
98
|
+
return false;
|
|
99
|
+
if (/^\d+$/.test(raw))
|
|
100
|
+
return Number(raw);
|
|
101
|
+
if (raw === 'true')
|
|
102
|
+
return true;
|
|
103
|
+
if (raw === 'false')
|
|
104
|
+
return false;
|
|
105
|
+
return raw;
|
|
106
|
+
}
|
|
107
|
+
function fatal(message) {
|
|
108
|
+
console.error(`[MCP] ✗ FATAL: ${message}`);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
export async function startMcpServer(argv = process.argv.slice(2)) {
|
|
112
|
+
let args;
|
|
113
|
+
try {
|
|
114
|
+
args = parseMcpArgs(argv);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
118
|
+
}
|
|
119
|
+
if (args.help) {
|
|
120
|
+
console.error(HELP);
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
if (args.agentsDir)
|
|
124
|
+
process.env.MELCHIZEDEK_AGENTS_DIR = path.resolve(args.agentsDir);
|
|
125
|
+
const { loadEnv, isPlaceholderValue } = await import('../lib/loadEnv.js');
|
|
126
|
+
loadEnv(import.meta.url);
|
|
127
|
+
// Sign in with ChatGPT is local only (ADR 0126). Over stdio this bin is one person's process, spawned by
|
|
128
|
+
// their own MCP client; --http can be reached by others, so it refuses like melchizedek-serve.
|
|
129
|
+
if (args.http) {
|
|
130
|
+
const { refuseChatGptSignInOnServedSurface } = await import('../lib/chatgpt/state.js');
|
|
131
|
+
try {
|
|
132
|
+
refuseChatGptSignInOnServedSurface('melchizedek-mcp --http');
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (process.env.OTEL_CONSOLE_SPANS === undefined)
|
|
139
|
+
process.env.OTEL_CONSOLE_SPANS = 'false';
|
|
140
|
+
const { setLogLevel } = await import('../lib/runtime/logging.js');
|
|
141
|
+
setLogLevel('warn');
|
|
142
|
+
const log = (m) => console.error(`[MCP] ${m}`);
|
|
143
|
+
const warn = (m) => console.error(`[MCP] ⚠ ${m}`);
|
|
144
|
+
const { createMcpServer, mcpBindProblem, mcpHttpApp, serveMcpStdio, toolNameFor } = await import('../lib/mcp/server.js');
|
|
145
|
+
const { loadSyndicate, nestedLoader } = await import('../lib/loadSyndicate.js');
|
|
146
|
+
const { syndicateOAuthHostProblems } = await import('../lib/tools/oauthTools.js');
|
|
147
|
+
const { syndicateCredentialHostProblems } = await import('../lib/tools/credentialUses.js');
|
|
148
|
+
// ── Transport posture first: a refused bind loads nothing ────────────────
|
|
149
|
+
const host = args.host ?? (process.env.MCP_HOST?.trim() || '127.0.0.1');
|
|
150
|
+
const port = args.port ?? envInt('MCP_PORT', 4100);
|
|
151
|
+
let secret = process.env.MCP_SERVER_SECRET?.trim() || undefined;
|
|
152
|
+
if (secret && isPlaceholderValue(secret))
|
|
153
|
+
fatal('MCP_SERVER_SECRET is still a placeholder. Generate one: openssl rand -hex 32');
|
|
154
|
+
if (args.http) {
|
|
155
|
+
const problem = mcpBindProblem({ host, ...(secret ? { secret } : {}) });
|
|
156
|
+
if (problem)
|
|
157
|
+
fatal(problem);
|
|
158
|
+
}
|
|
159
|
+
// ── The syndicates: the exposure ──────────────────────────────────────────
|
|
160
|
+
const bindings = () => ({
|
|
161
|
+
current_date: new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }),
|
|
162
|
+
});
|
|
163
|
+
const agentsDir = path.resolve(process.env.MELCHIZEDEK_AGENTS_DIR ?? path.join(process.cwd(), 'config', 'agents'));
|
|
164
|
+
let files = args.syndicates.map((id) => (id.endsWith('.yaml') || id.endsWith('.yml') ? id : `${id}.yaml`));
|
|
165
|
+
const named = files.length > 0;
|
|
166
|
+
if (!named) {
|
|
167
|
+
try {
|
|
168
|
+
files = readdirSync(agentsDir)
|
|
169
|
+
.filter((f) => (f.endsWith('.yaml') || f.endsWith('.yml')) && f !== 'syndicateSchema.yaml')
|
|
170
|
+
.sort();
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
files = [];
|
|
174
|
+
}
|
|
175
|
+
if (!files.length)
|
|
176
|
+
fatal(`No syndicates at the root of ${agentsDir}. Name one: melchizedek-mcp --syndicate <id> (a shipped example works: --syndicate tutor).`);
|
|
177
|
+
}
|
|
178
|
+
const syndicates = [];
|
|
179
|
+
for (const file of files) {
|
|
180
|
+
try {
|
|
181
|
+
const config = loadSyndicate(file, { bindings: bindings(), shippedFallback: named });
|
|
182
|
+
// The operator's host allowlists hold here as on the A2A server (ADR 0114, ADR 0122).
|
|
183
|
+
const nested = nestedLoader(config, (ref) => loadSyndicate(ref, { bindings: bindings() }));
|
|
184
|
+
const load = (ref) => {
|
|
185
|
+
try {
|
|
186
|
+
return nested(ref);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return {};
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
const problems = [...syndicateOAuthHostProblems([config], { load }), ...syndicateCredentialHostProblems([config], { load })];
|
|
193
|
+
if (problems.length)
|
|
194
|
+
throw new Error(`refused by the operator's host allowlists:\n - ${problems.join('\n - ')}`);
|
|
195
|
+
syndicates.push({ id: toolNameFor(path.basename(file)), config });
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
const why = err?.code === 'ENOENT' ? 'not found' : err instanceof Error ? err.message : String(err);
|
|
199
|
+
if (named)
|
|
200
|
+
fatal(`${file}: ${why}`);
|
|
201
|
+
warn(`Skipped ${file}: ${why.split('\n')[0]}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (!syndicates.length)
|
|
205
|
+
fatal('No syndicate loaded.');
|
|
206
|
+
// ── Storage, budgets, tool credentials: as melchizedek-serve ─────────────
|
|
207
|
+
const databaseUrl = process.env.DATABASE_URL?.trim() || undefined;
|
|
208
|
+
let pg;
|
|
209
|
+
if (databaseUrl) {
|
|
210
|
+
const { postgresStorage } = await import('../lib/storage/postgres/index.js');
|
|
211
|
+
const { dbSchema } = await import('../lib/storage/schema.js');
|
|
212
|
+
pg = postgresStorage({
|
|
213
|
+
connectionString: databaseUrl,
|
|
214
|
+
schema: dbSchema(),
|
|
215
|
+
memory: { apiKey: process.env.GOOGLE_GENAI_API_KEY || process.env.GEMINI_API_KEY || '' },
|
|
216
|
+
});
|
|
217
|
+
const { compareSchema, schemaBehindMessage, shippedSchemaVersion } = await import('../lib/storage/schemaVersion.js');
|
|
218
|
+
const check = compareSchema(await pg.schemaVersion(), shippedSchemaVersion());
|
|
219
|
+
if (check.state === 'behind' && process.env.ALLOW_SCHEMA_MISMATCH?.trim().toLowerCase() !== 'true')
|
|
220
|
+
fatal(schemaBehindMessage(check));
|
|
221
|
+
}
|
|
222
|
+
let policy;
|
|
223
|
+
const budgetsJson = process.env.A2A_BUDGETS?.trim();
|
|
224
|
+
if (budgetsJson) {
|
|
225
|
+
const { budgets, memoryUsageStore, parseBudgets, postgresUsageStore } = await import('../lib/a2a/policy.js');
|
|
226
|
+
try {
|
|
227
|
+
policy = budgets(parseBudgets(budgetsJson), { store: pg ? postgresUsageStore(pg.pool) : memoryUsageStore() });
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const { oauthServerSetup, callbackIdentity } = await import('../lib/a2a/oauthSetup.js');
|
|
234
|
+
const { postgresCredentialRows } = await import('../lib/storage/postgres/credentialStore.js');
|
|
235
|
+
let oauth;
|
|
236
|
+
try {
|
|
237
|
+
oauth = oauthServerSetup({
|
|
238
|
+
configs: syndicates.map((s) => s.config),
|
|
239
|
+
load: (ref) => loadSyndicate(ref),
|
|
240
|
+
env: process.env,
|
|
241
|
+
...(pg ? { rows: postgresCredentialRows(pg.pool), audit: pg.audit } : {}),
|
|
242
|
+
allowPrivate: process.env.ALLOW_PRIVATE_OPENAPI === 'true' || process.env.ALLOW_PRIVATE_MCP === 'true',
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
247
|
+
}
|
|
248
|
+
for (const w of oauth.warnings)
|
|
249
|
+
warn(w);
|
|
250
|
+
const consent = oauth.toolCredentials?.consent;
|
|
251
|
+
if (consent && !args.http) {
|
|
252
|
+
warn('stdio serves no OAuth consent callback: a consent pause completes only where OAUTH_REDIRECT_URI is served by a server sharing this credential store.');
|
|
253
|
+
}
|
|
254
|
+
const mcp = createMcpServer({
|
|
255
|
+
syndicates,
|
|
256
|
+
...(pg ? { sessionService: pg.sessionService, turnLock: pg.turnLock, audit: pg.audit } : {}),
|
|
257
|
+
...(pg?.memoryService ? { memoryService: pg.memoryService } : {}),
|
|
258
|
+
taskTimeoutMs: envInt('A2A_TASK_TIMEOUT_MS', 15 * 60 * 1000),
|
|
259
|
+
maxConcurrentTasks: envInt('A2A_MAX_CONCURRENT_TASKS', 0),
|
|
260
|
+
maxConcurrentPerScope: envInt('A2A_MAX_CONCURRENT_PER_SCOPE', 4),
|
|
261
|
+
maxConcurrentPerCaller: envInt('A2A_MAX_CONCURRENT_PER_CALLER', 0),
|
|
262
|
+
turnLockWaitMs: envInt('A2A_TURN_LOCK_WAIT_MS', 30_000),
|
|
263
|
+
...(policy ? { policy } : {}),
|
|
264
|
+
...(oauth.toolCredentials ? { toolCredentials: oauth.toolCredentials } : {}),
|
|
265
|
+
log,
|
|
266
|
+
warn,
|
|
267
|
+
});
|
|
268
|
+
const toolList = syndicates.map((s) => s.id).join(', ');
|
|
269
|
+
const graceMs = envInt('A2A_SHUTDOWN_GRACE_MS', 25_000);
|
|
270
|
+
let stopping = false;
|
|
271
|
+
let closeHttp;
|
|
272
|
+
const stop = async (why) => {
|
|
273
|
+
if (stopping)
|
|
274
|
+
return;
|
|
275
|
+
stopping = true;
|
|
276
|
+
log(`${why}: waiting up to ${graceMs} ms for running turns.`);
|
|
277
|
+
const canceled = await mcp.shutdown(graceMs);
|
|
278
|
+
if (canceled > 0)
|
|
279
|
+
warn(`${canceled} turn(s) did not finish in time and were canceled.`);
|
|
280
|
+
await closeHttp?.().catch(() => { });
|
|
281
|
+
const { flushTracing } = await import('../lib/observability/tracer.js');
|
|
282
|
+
await flushTracing().catch(() => { });
|
|
283
|
+
await pg?.close().catch(() => { });
|
|
284
|
+
process.exit(0);
|
|
285
|
+
};
|
|
286
|
+
process.on('SIGTERM', () => void stop('SIGTERM'));
|
|
287
|
+
process.on('SIGINT', () => void stop('SIGINT'));
|
|
288
|
+
if (!args.http) {
|
|
289
|
+
const scope = process.env.MCP_USER_ID?.trim() || 'default';
|
|
290
|
+
const { HEADER_VALUE_PATTERN } = await import('../lib/a2a/identity.js');
|
|
291
|
+
if (!HEADER_VALUE_PATTERN.test(scope))
|
|
292
|
+
fatal('MCP_USER_ID must match [A-Za-z0-9._-]{1,64}');
|
|
293
|
+
await serveMcpStdio(mcp, { caller: { scopeKey: scope, caller: 'local' } });
|
|
294
|
+
log(`✓ Serving ${syndicates.length} syndicate tool(s) over stdio: ${toolList}`);
|
|
295
|
+
process.stdin.on('end', () => void stop('stdin closed'));
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const allowedHosts = process.env.MCP_ALLOWED_HOSTS?.split(',').map((s) => s.trim()).filter(Boolean);
|
|
299
|
+
const { consentCallback } = await import('../lib/a2a/app.js');
|
|
300
|
+
const app = mcpHttpApp(mcp, {
|
|
301
|
+
host,
|
|
302
|
+
port,
|
|
303
|
+
...(secret ? { secret } : {}),
|
|
304
|
+
...(allowedHosts?.length ? { allowedHosts } : {}),
|
|
305
|
+
trustProxy: trustProxy(),
|
|
306
|
+
rateLimitPerMinute: envInt('MCP_RATE_LIMIT_PER_MINUTE', 240),
|
|
307
|
+
...(consent
|
|
308
|
+
? {
|
|
309
|
+
beforeAuth: (a) => a.get(new URL(consent.redirectUri).pathname, consentCallback(consent, { requireCallerIdentity: callbackIdentity(process.env) === 'required', log, warn })),
|
|
310
|
+
}
|
|
311
|
+
: {}),
|
|
312
|
+
warn,
|
|
313
|
+
});
|
|
314
|
+
closeHttp = app.closeSessions;
|
|
315
|
+
// The secret is not needed past this point.
|
|
316
|
+
secret = undefined;
|
|
317
|
+
await new Promise((resolve) => {
|
|
318
|
+
app.listen(port, host, () => resolve());
|
|
319
|
+
});
|
|
320
|
+
log(`✓ Serving ${syndicates.length} syndicate tool(s) at http://${host.includes(':') ? `[${host}]` : host}:${port}/mcp: ${toolList}`);
|
|
321
|
+
log(` auth ${process.env.MCP_SERVER_SECRET?.trim() ? 'bearer MCP_SERVER_SECRET' : 'none (loopback only)'}`);
|
|
322
|
+
log(` storage ${pg ? 'postgres' : 'in-memory (lost on restart)'}`);
|
|
323
|
+
log(` oauth ${oauth.summary}`);
|
|
324
|
+
}
|
|
325
|
+
// Run-as-main guard on the realpath of argv[1] (the bin is a symlink; see a2a_server.ts).
|
|
326
|
+
const invokedAsMain = (() => {
|
|
327
|
+
if (!process.argv[1])
|
|
328
|
+
return false;
|
|
329
|
+
try {
|
|
330
|
+
return import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
return import.meta.url === `file://${process.argv[1]}`;
|
|
334
|
+
}
|
|
335
|
+
})();
|
|
336
|
+
if (invokedAsMain) {
|
|
337
|
+
// Before anything else loads: stdout is the stdio protocol channel.
|
|
338
|
+
console.log = console.error;
|
|
339
|
+
console.info = console.error;
|
|
340
|
+
console.debug = console.error;
|
|
341
|
+
startMcpServer().catch((error) => {
|
|
342
|
+
console.error(`[MCP] ✗ ${error instanceof Error ? error.message : String(error)}`);
|
|
343
|
+
process.exit(1);
|
|
344
|
+
});
|
|
345
|
+
}
|
package/dist/scripts/setup.d.ts
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* npx melchizedek-setup --level 2 --write-env # also create .env from .env.example
|
|
17
17
|
* npx melchizedek-setup --list
|
|
18
18
|
* npx melchizedek-setup --markdown # ONBOARDING.md, regenerated
|
|
19
|
+
* npx melchizedek-setup --chatgpt-signin # Sign in with ChatGPT, local only (ADR 0126)
|
|
20
|
+
* npx melchizedek-setup --chatgpt-signout | --chatgpt-status
|
|
19
21
|
*
|
|
20
22
|
* Never prints a value. `--write-env` never overwrites `.env`, leaves every
|
|
21
23
|
* name blank, and refuses unless `git check-ignore` confirms `.env` is ignored.
|
package/dist/scripts/setup.js
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* npx melchizedek-setup --level 2 --write-env # also create .env from .env.example
|
|
17
17
|
* npx melchizedek-setup --list
|
|
18
18
|
* npx melchizedek-setup --markdown # ONBOARDING.md, regenerated
|
|
19
|
+
* npx melchizedek-setup --chatgpt-signin # Sign in with ChatGPT, local only (ADR 0126)
|
|
20
|
+
* npx melchizedek-setup --chatgpt-signout | --chatgpt-status
|
|
19
21
|
*
|
|
20
22
|
* Never prints a value. `--write-env` never overwrites `.env`, leaves every
|
|
21
23
|
* name blank, and refuses unless `git check-ignore` confirms `.env` is ignored.
|
|
@@ -24,6 +26,7 @@ import { realpathSync } from 'node:fs';
|
|
|
24
26
|
import { createInterface } from 'node:readline/promises';
|
|
25
27
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
26
28
|
import { loadEnv } from '../lib/loadEnv.js';
|
|
29
|
+
import { runChatGptSignIn, runChatGptSignOut, runChatGptStatus } from '../lib/chatgpt/cli.js';
|
|
27
30
|
import { runDoctor } from '../lib/doctor.js';
|
|
28
31
|
import { detectLevels, highestLevel, LEVELS, levelById, renderAuto, renderGuide, renderMenu, renderOnboardingDoc, writeEnvForLevel, } from '../lib/onboarding.js';
|
|
29
32
|
const USAGE = `melchizedek-setup — start from what you have
|
|
@@ -34,6 +37,12 @@ const USAGE = `melchizedek-setup — start from what you have
|
|
|
34
37
|
melchizedek-setup --list the levels
|
|
35
38
|
melchizedek-setup --markdown every guide as Markdown (ONBOARDING.md)
|
|
36
39
|
|
|
40
|
+
melchizedek-setup --chatgpt-signin [--port <n>]
|
|
41
|
+
sign in with ChatGPT in your browser; OpenAI ids then run
|
|
42
|
+
on your ChatGPT plan on this machine (local only)
|
|
43
|
+
melchizedek-setup --chatgpt-signout remove the stored tokens and revoke them at OpenAI
|
|
44
|
+
melchizedek-setup --chatgpt-status where the sign-in is stored, and whether it is used
|
|
45
|
+
|
|
37
46
|
--write-env with --level or --auto: create .env from .env.example with the level's
|
|
38
47
|
names left blank (never overwrites; requires git to ignore .env)
|
|
39
48
|
--clone | --package spell commands for a clone or an installed package (default: detected)
|
|
@@ -80,6 +89,24 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
80
89
|
console.log(renderMenu());
|
|
81
90
|
return 0;
|
|
82
91
|
}
|
|
92
|
+
if (args.includes('--chatgpt-signin')) {
|
|
93
|
+
loadEnv();
|
|
94
|
+
const port = flag('port');
|
|
95
|
+
const n = port === undefined ? undefined : Number(port);
|
|
96
|
+
if (n !== undefined && !(Number.isInteger(n) && n >= 0 && n <= 65535)) {
|
|
97
|
+
console.error('✗ --port must be a port number (0 picks a free one)');
|
|
98
|
+
return 2;
|
|
99
|
+
}
|
|
100
|
+
return runChatGptSignIn(n !== undefined ? { port: n } : {});
|
|
101
|
+
}
|
|
102
|
+
if (args.includes('--chatgpt-signout')) {
|
|
103
|
+
loadEnv();
|
|
104
|
+
return runChatGptSignOut();
|
|
105
|
+
}
|
|
106
|
+
if (args.includes('--chatgpt-status')) {
|
|
107
|
+
loadEnv();
|
|
108
|
+
return runChatGptStatus();
|
|
109
|
+
}
|
|
83
110
|
if (args.includes('--markdown')) {
|
|
84
111
|
process.stdout.write(renderOnboardingDoc());
|
|
85
112
|
return 0;
|
|
@@ -11,6 +11,8 @@ import { runtimeSetting } from '../lib/runtime/runtimeFlag.js';
|
|
|
11
11
|
import { setLogLevel } from '../lib/runtime/logging.js';
|
|
12
12
|
import { randomUUID } from 'node:crypto';
|
|
13
13
|
import { loadEnv } from '../lib/loadEnv.js';
|
|
14
|
+
import { runChatGptSignIn } from '../lib/chatgpt/cli.js';
|
|
15
|
+
import { chatGptSignInRoutesOpenAi } from '../lib/chatgpt/state.js';
|
|
14
16
|
// ── Model routing ─────────────────────────────────────────────────────────────
|
|
15
17
|
// A model id resolves when an agent first calls it (lib/models/registry.ts),
|
|
16
18
|
// after loadEnv() has read the keys from .env.
|
|
@@ -97,6 +99,11 @@ async function main() {
|
|
|
97
99
|
process.env.OTEL_CONSOLE_SPANS = 'false';
|
|
98
100
|
// MELCHIZEDEK_RUNTIME=adk (removed in 1.0.0) stops the chat here, naming the release.
|
|
99
101
|
runtimeSetting();
|
|
102
|
+
// --chatgpt-signin: the browser sign-in first, then the chat (ADR 0126, local only).
|
|
103
|
+
if (process.argv.includes('--chatgpt-signin')) {
|
|
104
|
+
if ((await runChatGptSignIn()) !== 0)
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
100
107
|
const apiKey = process.env.GOOGLE_GENAI_API_KEY || process.env.GEMINI_API_KEY;
|
|
101
108
|
if (apiKey) {
|
|
102
109
|
process.env.GEMINI_API_KEY = apiKey;
|
|
@@ -148,10 +155,12 @@ async function main() {
|
|
|
148
155
|
: ['gemini']);
|
|
149
156
|
if (!config.orchestrator.model)
|
|
150
157
|
requiredProviders.add('gemini');
|
|
151
|
-
const missingKeys = [...requiredProviders].filter((p) => !providerKeyPresent(p));
|
|
158
|
+
const missingKeys = [...requiredProviders].filter((p) => !providerKeyPresent(p) && !(p === 'openai' && chatGptSignInRoutesOpenAi()));
|
|
152
159
|
if (missingKeys.length > 0) {
|
|
153
160
|
for (const p of missingKeys) {
|
|
154
161
|
console.error(`${c.yellow}⚠ ${PROVIDERS[p].label} requires ${PROVIDERS[p].keyEnv}, which is not set.${c.reset}`);
|
|
162
|
+
if (p === 'openai')
|
|
163
|
+
console.error(`${c.dim} (Or sign in with ChatGPT on this machine: add --chatgpt-signin, or run melchizedek-setup --chatgpt-signin.)${c.reset}`);
|
|
155
164
|
}
|
|
156
165
|
console.error(`${c.dim} (Only syndicates whose every agent uses an ollama/* model run keyless.)${c.reset}`);
|
|
157
166
|
process.exit(1);
|
|
@@ -202,8 +211,8 @@ async function main() {
|
|
|
202
211
|
const rawArgs = process.argv.slice(2);
|
|
203
212
|
const queryParts = [];
|
|
204
213
|
for (let i = 0; i < rawArgs.length; i++) {
|
|
205
|
-
if (rawArgs[i] === '--')
|
|
206
|
-
continue; // drop bare separator
|
|
214
|
+
if (rawArgs[i] === '--' || rawArgs[i] === '--chatgpt-signin')
|
|
215
|
+
continue; // drop bare separator and the sign-in flag
|
|
207
216
|
if ((rawArgs[i] === '--bind' || rawArgs[i] === '--bindings' || rawArgs[i] === '--syndicate') && i + 1 < rawArgs.length) {
|
|
208
217
|
i++; // skip the value
|
|
209
218
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "melchizedek-agents",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "A multi-model, multi-agent orchestration framework — YAML-defined syndicates on the engine's own agent loop, Supabase or Postgres session persistence, pgvector long-term memory. Companion repo to the lyceumagents.com curriculum.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"./a2a/identity": "./dist/lib/a2a/identity.js",
|
|
30
30
|
"./a2a/policy": "./dist/lib/a2a/policy.js",
|
|
31
31
|
"./a2a/limits": "./dist/lib/a2a/redisLimits.js",
|
|
32
|
+
"./mcp": "./dist/lib/mcp/server.js",
|
|
32
33
|
"./registry": "./dist/lib/registry.js",
|
|
33
34
|
"./server": "./dist/scripts/a2a_server.js",
|
|
34
35
|
"./guards": "./dist/lib/guards/index.js",
|
|
@@ -47,16 +48,17 @@
|
|
|
47
48
|
"./package.json": "./package.json"
|
|
48
49
|
},
|
|
49
50
|
"bin": {
|
|
50
|
-
"melchizedek-serve": "
|
|
51
|
-
"melchizedek-
|
|
52
|
-
"melchizedek-
|
|
53
|
-
"melchizedek-
|
|
54
|
-
"melchizedek-
|
|
55
|
-
"melchizedek-
|
|
56
|
-
"melchizedek-
|
|
57
|
-
"melchizedek-
|
|
58
|
-
"melchizedek-
|
|
59
|
-
"melchizedek-
|
|
51
|
+
"melchizedek-serve": "dist/scripts/a2a_server.js",
|
|
52
|
+
"melchizedek-mcp": "dist/scripts/mcp_server.js",
|
|
53
|
+
"melchizedek-chat": "dist/scripts/syndicate_chat.js",
|
|
54
|
+
"melchizedek-doctor": "dist/scripts/doctor.js",
|
|
55
|
+
"melchizedek-skills": "dist/scripts/skills_install.js",
|
|
56
|
+
"melchizedek-worker": "dist/scripts/assistant_worker.js",
|
|
57
|
+
"melchizedek-db": "dist/scripts/db.js",
|
|
58
|
+
"melchizedek-init": "dist/scripts/init.js",
|
|
59
|
+
"melchizedek-setup": "dist/scripts/setup.js",
|
|
60
|
+
"melchizedek-registry": "dist/scripts/registry.js",
|
|
61
|
+
"melchizedek-codemod": "dist/scripts/yaml_codemod.js"
|
|
60
62
|
},
|
|
61
63
|
"files": [
|
|
62
64
|
"dist",
|
|
@@ -112,13 +114,14 @@
|
|
|
112
114
|
"mcp:demo": "node --disable-warning=DEP0040 --experimental-strip-types scripts/demo_mcp_server.ts",
|
|
113
115
|
"mcp:wiki": "node --disable-warning=DEP0040 --experimental-strip-types scripts/wiki/mcp_server.ts",
|
|
114
116
|
"mcp:science": "node --disable-warning=DEP0040 --experimental-strip-types scripts/science_mcp_server.ts",
|
|
117
|
+
"mcp:serve": "node --disable-warning=DEP0040 --experimental-strip-types scripts/mcp_server.ts",
|
|
115
118
|
"wiki:init": "node --disable-warning=DEP0040 --experimental-strip-types scripts/wiki/init.ts",
|
|
116
119
|
"wiki:build": "node --disable-warning=DEP0040 --experimental-strip-types scripts/wiki/build.ts",
|
|
117
120
|
"wiki:check": "node --disable-warning=DEP0040 --experimental-strip-types scripts/wiki/check.ts",
|
|
118
121
|
"schema:gen": "node --disable-warning=DEP0040 --experimental-strip-types scripts/gen_syndicate_schema.ts",
|
|
119
122
|
"yaml:codemod": "node --disable-warning=DEP0040 --experimental-strip-types scripts/yaml_codemod.ts",
|
|
120
123
|
"start:a2a": "node --disable-warning=DEP0040 --experimental-strip-types scripts/a2a_server.ts",
|
|
121
|
-
"test": "node --disable-warning=DEP0040 --experimental-strip-types --test tests/**/*.test.ts",
|
|
124
|
+
"test": "node --disable-warning=DEP0040 --experimental-strip-types --import ./tests/helpers/isolateLocalState.ts --test tests/**/*.test.ts",
|
|
122
125
|
"setup": "node --disable-warning=DEP0040 --experimental-strip-types scripts/setup.ts",
|
|
123
126
|
"init": "node --disable-warning=DEP0040 --experimental-strip-types scripts/init.ts",
|
|
124
127
|
"db": "node --disable-warning=DEP0040 --experimental-strip-types scripts/db.ts",
|
package/skills/README.md
CHANGED
|
@@ -15,9 +15,9 @@ This directory contains a suite of eleven Agent Skills built to the open SKILL.m
|
|
|
15
15
|
|
|
16
16
|
Five onboarding skills route a person by what they have and walk the startup guide for it (`npx melchizedek-setup --level <id>` prints each guide):
|
|
17
17
|
|
|
18
|
-
- `melchizedek-onboard`: The triage. Asks what the person has and routes to one of
|
|
18
|
+
- `melchizedek-onboard`: The triage. Asks what the person has and routes to one of ten authentication levels and the skill below that owns it.
|
|
19
19
|
- `melchizedek-onboard-local`: No key: Ollama and the keyless syndicates.
|
|
20
|
-
- `melchizedek-onboard-keys`: One provider's key, several, a gateway key, and the honest answer for a
|
|
20
|
+
- `melchizedek-onboard-keys`: One provider's key, several, a gateway key, Sign in with ChatGPT on the person's own machine, and the honest answer for a Codex, Claude.ai or Gemini CLI sign-in.
|
|
21
21
|
- `melchizedek-onboard-cloud`: Vertex AI, Amazon Bedrock and Azure OpenAI credentials.
|
|
22
22
|
- `melchizedek-onboard-serve`: Serving to others: per-caller keys (BYOK), caller identities (`A2A_AUTH`), and OAuth tool grants.
|
|
23
23
|
|
|
@@ -14,7 +14,7 @@ The skills (name → what it teaches):
|
|
|
14
14
|
- `melchizedek-memory` → Supabase sessions and long-term memory: modes, schema, tools, extraction rules, inspection, erasure.
|
|
15
15
|
- `melchizedek-models` → model ids and providers, keys, keyless Ollama, the gateway fallback, per-agent settings, the errors.
|
|
16
16
|
- `melchizedek-scribe` → writing documents from a brief with the Scribe syndicate.
|
|
17
|
-
- Five onboarding skills, written by hand rather than by the Scribe (they print their facts from `npx melchizedek-setup --level <id>`): `melchizedek-onboard` (triage to one of
|
|
17
|
+
- Five onboarding skills, written by hand rather than by the Scribe (they print their facts from `npx melchizedek-setup --level <id>`): `melchizedek-onboard` (triage to one of ten authentication levels), `melchizedek-onboard-local`, `melchizedek-onboard-keys`, `melchizedek-onboard-cloud`, `melchizedek-onboard-serve`.
|
|
18
18
|
- `--agents-md` also writes a pointer block into the project's `AGENTS.md`.
|
|
19
19
|
Install (three ways):
|
|
20
20
|
- From the package, into this project's `.claude/skills/` and `.agents/skills/` (the two locations that between them reach Claude Code, Codex, Cursor, OpenCode and Gemini CLI): `npx melchizedek-skills install` after `npm install melchizedek-agents`. `--for <targets>` picks platforms from `claude, agents, codex, cursor, opencode, gemini, all`; `--global` writes the home-directory locations instead; `--dir <path>` writes one explicit directory; `--only <names>` installs a subset; `--force` overwrites a file that differs; `--dry-run` prints without writing. `npx melchizedek-skills paths` prints the locations; `npx melchizedek-skills list` prints the skills. In a clone: `npm run skills:install -- <same flags>`.
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: melchizedek-onboard
|
|
3
|
-
description: "Onboard a person to Melchizedek from whatever credentials they have: ask what they have (nothing, one or several provider API keys, a gateway key, Vertex AI / Bedrock / Azure access, a server to put in front of other people, OAuth tools,
|
|
3
|
+
description: "Onboard a person to Melchizedek from whatever credentials they have: ask what they have (nothing, one or several provider API keys, a gateway key, Vertex AI / Bedrock / Azure access, a server to put in front of other people, OAuth tools, a ChatGPT plan to use on their own machine, or a Codex, Claude.ai or Gemini CLI sign-in), route them to one authentication level, and walk its startup guide without ever handling a key. Use when someone is setting up melchizedek-agents for the first time, asks which keys they need, says 'I have an X key', 'I have ChatGPT Plus' or 'I have a Codex login', or asks how to get started."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
## What this skill does
|
|
7
7
|
|
|
8
|
-
It turns "what do you have?" into one of
|
|
8
|
+
It turns "what do you have?" into one of ten authentication levels and the startup guide for it. The guides are generated by the engine itself, so you print them instead of restating them:
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
npx melchizedek-setup --list # the
|
|
11
|
+
npx melchizedek-setup --list # the ten levels
|
|
12
12
|
npx melchizedek-setup --level <id|number> # one level's guide
|
|
13
13
|
npx melchizedek-setup --auto # detect from the environment, names only
|
|
14
14
|
```
|
|
@@ -20,12 +20,13 @@ In a clone of the repository, use `npm run setup -- <flags>` instead of `npx mel
|
|
|
20
20
|
- Never ask the person to paste a key, token or secret into the chat, and never read one from their files or shell history. A pasted key lives in the transcript forever.
|
|
21
21
|
- Never print a value from `.env`. To check what is set, run `npx melchizedek-setup --auto` or `npx melchizedek-doctor`; both report names and set or unset only.
|
|
22
22
|
- The person types every value into `.env` in their own editor. You may create `.env` with names left blank: `npx melchizedek-setup --level <id> --write-env`. It never overwrites an existing `.env` and refuses unless git ignores `.env`.
|
|
23
|
-
- Never reuse
|
|
23
|
+
- Never reuse another app's sign-in (the Codex CLI's login, Claude.ai, Gemini CLI) as a model credential, and never read its token files. Route that person to level 10, which names the sanctioned alternative.
|
|
24
|
+
- A ChatGPT plan is used only through the engine's own Sign in with ChatGPT (level 9): the person runs `npx melchizedek-setup --chatgpt-signin` and approves in their own browser. It is for their own machine only; never set it up for a server.
|
|
24
25
|
|
|
25
26
|
## Triage
|
|
26
27
|
|
|
27
28
|
1. Run `npx melchizedek-setup --auto`. If it already detects a level beyond 1, confirm with the person that this is what they want and continue from that guide.
|
|
28
|
-
2. Otherwise ask one question: "What do you have: nothing yet, an API key from a model provider, a gateway key, cloud platform access,
|
|
29
|
+
2. Otherwise ask one question: "What do you have: nothing yet, an API key from a model provider, a gateway key, cloud platform access, a ChatGPT plan, or a Claude or Gemini sign-in? And will other people or backends call your agents?"
|
|
29
30
|
3. Route by the answer:
|
|
30
31
|
|
|
31
32
|
| They have | Level | Follow |
|
|
@@ -38,12 +39,33 @@ In a clone of the repository, use `npm run setup -- <flags>` instead of `npx mel
|
|
|
38
39
|
| Callers who fund their own model calls | 6 `byok` | `melchizedek-onboard-serve` |
|
|
39
40
|
| Other backends or users calling the server | 7 `caller-tokens` | `melchizedek-onboard-serve` |
|
|
40
41
|
| Tools that act for each user on a third-party API | 8 `oauth-grants` | `melchizedek-onboard-serve` |
|
|
41
|
-
| A ChatGPT
|
|
42
|
+
| A ChatGPT plan, to use on their own machine | 9 `chatgpt-signin` | `melchizedek-onboard-keys` |
|
|
43
|
+
| A Codex CLI login, a Claude.ai or a Gemini CLI sign-in only | 10 `subscription-signin` | `melchizedek-onboard-keys` |
|
|
42
44
|
|
|
43
45
|
Levels 6 to 8 sit on top of a model level (1 to 5): settle how models are paid for first.
|
|
44
46
|
|
|
45
47
|
4. Print the level's guide with `--level <id>` and walk it step by step: the person sets the variables, you run the doctor command the guide names and read its verdicts back to them, then you run the first command.
|
|
46
48
|
|
|
49
|
+
## Use melch from Claude Code or Codex
|
|
50
|
+
|
|
51
|
+
Once a level is confirmed, the person can call their syndicates from inside their coding agent: `melchizedek-mcp` makes each syndicate an MCP tool (`{ message, session_id? }`), plus `melch_resume` for a turn that pauses. Register it from the project directory, where `.env` and `config/agents/` live:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
claude mcp add melch -- npx melchizedek-mcp --syndicate <id>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
For Codex, add to `~/.codex/config.toml`:
|
|
58
|
+
|
|
59
|
+
```toml
|
|
60
|
+
[mcp_servers.melch]
|
|
61
|
+
command = "npx"
|
|
62
|
+
args = ["-y", "-p", "melchizedek-agents", "melchizedek-mcp", "--syndicate", "<id>"]
|
|
63
|
+
cwd = "/path/to/the/project"
|
|
64
|
+
tool_timeout_sec = 900
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
When a result says an approval is waiting, ask the person and pass their decision to `melch_resume` with `approve`; never approve on their behalf. Keys stay in `.env`: the MCP registration names no key.
|
|
68
|
+
|
|
47
69
|
## Done when
|
|
48
70
|
|
|
49
71
|
The doctor shows the level's confirmation (the guide's **Confirm** line), and the first command has answered once. Then hand over to the `melchizedek` skill for the catalog of syndicates.
|