brainclaw 1.14.0 → 1.16.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 +16 -263
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli/register-capture.js +209 -0
- package/dist/cli/register-code-map.js +19 -0
- package/dist/cli/register-coordination.js +472 -0
- package/dist/cli/register-federation.js +258 -0
- package/dist/cli/register-lifecycle.js +436 -0
- package/dist/cli/register-memory-context.js +502 -0
- package/dist/cli/register-planning.js +167 -0
- package/dist/cli/register-review.js +149 -0
- package/dist/cli/shared.js +5 -0
- package/dist/cli.js +212 -2015
- package/dist/commands/dispatch-watch.js +25 -2
- package/dist/commands/harvest.js +31 -6
- package/dist/commands/mcp-catalog.js +1438 -0
- package/dist/commands/mcp-contract.js +33 -0
- package/dist/commands/mcp-presentation.js +27 -0
- package/dist/commands/mcp-read-handlers.js +72 -36
- package/dist/commands/mcp-write-admin.js +328 -0
- package/dist/commands/mcp-write-claims.js +864 -0
- package/dist/commands/mcp-write-coordination.js +1825 -0
- package/dist/commands/mcp-write-entities.js +620 -0
- package/dist/commands/mcp-write-memory.js +451 -0
- package/dist/commands/mcp-write-sequences.js +116 -0
- package/dist/commands/mcp-write-support.js +367 -0
- package/dist/commands/mcp.js +261 -5570
- package/dist/commands/update-handoff.js +28 -42
- package/dist/core/agent-capability.js +31 -14
- package/dist/core/agent-files.js +1 -1
- package/dist/core/agent-registry.js +51 -3
- package/dist/core/claims.js +18 -0
- package/dist/core/coordination.js +5 -2
- package/dist/core/cross-project.js +35 -1
- package/dist/core/dispatcher.js +34 -20
- package/dist/core/entity-operations.js +335 -12
- package/dist/core/entity-registry.js +72 -9
- package/dist/core/execution.js +28 -4
- package/dist/core/facade-schema.js +30 -4
- package/dist/core/federation-cloud.js +142 -11
- package/dist/core/federation-outbox.js +292 -0
- package/dist/core/federation-signing.js +115 -0
- package/dist/core/handoff-review.js +35 -0
- package/dist/core/io.js +6 -0
- package/dist/core/protocol-tool-policy.js +113 -0
- package/dist/core/review-loop-close.js +115 -0
- package/dist/core/schema.js +25 -2
- package/dist/core/security-detectors.js +35 -6
- package/dist/core/security.js +32 -12
- package/dist/core/worktree.js +98 -9
- package/dist/facts.js +13 -11
- package/dist/facts.json +12 -10
- package/docs/PROTOCOL.md +7 -3
- package/docs/concepts/coordinator-runbook.md +3 -0
- package/docs/concepts/dispatch-lifecycle.md +4 -4
- package/docs/concepts/loop-engine.md +3 -1
- package/docs/concepts/troubleshooting.md +1 -1
- package/docs/integrations/codex.md +3 -3
- package/docs/integrations/overview.md +1 -1
- package/docs/mcp-schema-changelog.md +153 -2
- package/docs/playbooks/orchestration.md +1 -1
- package/docs/product/entity-model-audit.md +3 -2
- package/docs/security.md +22 -1
- package/package.json +3 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const SCHEMA_VERSION = '1.0.0';
|
|
2
|
+
export const MCP_PROTOCOL_VERSIONS = ['2025-11-25', '2024-11-05'];
|
|
3
|
+
export const MCP_SERVER_NOT_INITIALIZED = -32002;
|
|
4
|
+
export function toolResponse(response, isError = false) {
|
|
5
|
+
const structuredContent = response.structuredContent
|
|
6
|
+
? { ...response.structuredContent, schema_version: SCHEMA_VERSION }
|
|
7
|
+
: undefined;
|
|
8
|
+
return {
|
|
9
|
+
...response,
|
|
10
|
+
structuredContent,
|
|
11
|
+
isError,
|
|
12
|
+
schema_version: SCHEMA_VERSION,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function createToolErrorResponse(kind, message, details) {
|
|
16
|
+
return toolResponse({
|
|
17
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
18
|
+
structuredContent: {
|
|
19
|
+
error: {
|
|
20
|
+
kind,
|
|
21
|
+
message,
|
|
22
|
+
...(details !== undefined ? { details } : {}),
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
}, true);
|
|
26
|
+
}
|
|
27
|
+
export function normaliseFormat(value) {
|
|
28
|
+
if (value === 'json' || value === 'template') {
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
return 'markdown';
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=mcp-contract.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP presentation — renderers that turn built context objects into the
|
|
3
|
+
* strings served over the MCP surface.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from mcp.ts (pln#622 PR1). Importing core/ here is the
|
|
6
|
+
* legitimate downward direction (commands → core); this module must not
|
|
7
|
+
* import mcp.js (assembly point) — enforced by
|
|
8
|
+
* tests/unit/mcp-dependency-direction.test.ts.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import { renderContextMarkdown, renderContextPromptTemplate, renderContextBriefing } from '../core/context.js';
|
|
13
|
+
export function renderContextForMcp(result, format, options) {
|
|
14
|
+
// Briefing profile always uses its own ultra-compact renderer
|
|
15
|
+
if (result.profile === 'briefing') {
|
|
16
|
+
return renderContextBriefing(result);
|
|
17
|
+
}
|
|
18
|
+
if (format === 'json') {
|
|
19
|
+
return JSON.stringify(result, null, 2);
|
|
20
|
+
}
|
|
21
|
+
if (format === 'template') {
|
|
22
|
+
const compact = options.compactTemplate || result.profile === 'openclaw';
|
|
23
|
+
return renderContextPromptTemplate(result, compact);
|
|
24
|
+
}
|
|
25
|
+
return renderContextMarkdown(result, options.explain);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=mcp-presentation.js.map
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
* Extracted from mcp.ts to reduce file size. These handlers do not mutate
|
|
5
5
|
* state — they build context, list items, search, and inspect.
|
|
6
6
|
*
|
|
7
|
+
* pln#622 PR2: the per-call context (effective cwd, project routing, lazy
|
|
8
|
+
* config/state/agent-name loads) is resolved once at the entry point and
|
|
9
|
+
* shared by every handler via {@link ResolvedReadContext}.
|
|
10
|
+
*
|
|
7
11
|
* @module
|
|
8
12
|
*/
|
|
9
13
|
import { applyBootstrapImport, renderBootstrapInterview, renderBootstrapSummary, runBootstrapProfile, uninstallBootstrapImport } from '../core/bootstrap.js';
|
|
@@ -44,10 +48,11 @@ import { listAvailableProjectsForSession, switchProject } from './switch.js';
|
|
|
44
48
|
import { resolveEffectiveCwdInfo } from '../core/store-resolution.js';
|
|
45
49
|
import { resolveProjectCwd } from '../core/cross-project.js';
|
|
46
50
|
import { readUnseenEvents, buildNotificationSummary } from '../core/event-log.js';
|
|
47
|
-
import { boundListResult, DEFAULT_FIND_CHAR_BUDGET } from '../core/entity-operations.js';
|
|
51
|
+
import { boundListResult, DEFAULT_FIND_CHAR_BUDGET, projectAgentForRead } from '../core/entity-operations.js';
|
|
48
52
|
import { handoffDiffPreviewNote } from '../core/handoff-snapshot.js';
|
|
49
53
|
import { BootstrapInterviewAnswerSchema, AssignmentStatusSchema, AgentRunStatusSchema, AgentRunTransportSchema, ActionRequiredStatusSchema, ActionRequiredKindSchema } from '../core/schema.js';
|
|
50
|
-
import { SCHEMA_VERSION, createToolErrorResponse, normaliseFormat,
|
|
54
|
+
import { SCHEMA_VERSION, createToolErrorResponse, normaliseFormat, } from './mcp-contract.js';
|
|
55
|
+
import { renderContextForMcp } from './mcp-presentation.js';
|
|
51
56
|
function normalizeBootstrapInterviewAnswersArg(value) {
|
|
52
57
|
if (!Array.isArray(value)) {
|
|
53
58
|
return [];
|
|
@@ -124,25 +129,46 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
124
129
|
const projectArg = args.project;
|
|
125
130
|
const targetProjectArg = name === 'bclaw_switch' ? undefined : projectArg;
|
|
126
131
|
let projectRoutingApplied = false;
|
|
132
|
+
let routedConfig;
|
|
127
133
|
if (targetProjectArg) {
|
|
128
134
|
cwd = resolveProjectCwd(targetProjectArg, cwd);
|
|
129
135
|
activeSource = 'explicit';
|
|
130
136
|
try {
|
|
131
|
-
|
|
132
|
-
resolvedProject = { path: cwd, name:
|
|
137
|
+
routedConfig = loadConfig(cwd);
|
|
138
|
+
resolvedProject = { path: cwd, name: routedConfig.project_name };
|
|
133
139
|
}
|
|
134
140
|
catch {
|
|
135
141
|
resolvedProject = { path: cwd };
|
|
136
142
|
}
|
|
137
143
|
projectRoutingApplied = true;
|
|
138
144
|
}
|
|
145
|
+
// Memoized lazy loads — seeded by the routing probe above and shared across
|
|
146
|
+
// the bclaw_context delegation, so one tool call never re-reads them.
|
|
147
|
+
let configCache = routedConfig;
|
|
148
|
+
let stateCache;
|
|
149
|
+
let agentNameCache;
|
|
150
|
+
return dispatchReadTool(name, args, {
|
|
151
|
+
cwd,
|
|
152
|
+
activeSource,
|
|
153
|
+
resolvedProject,
|
|
154
|
+
projectRoutingApplied,
|
|
155
|
+
connectionSessionId: context.connectionSessionId,
|
|
156
|
+
getConfig: () => (configCache ??= loadConfig(cwd)),
|
|
157
|
+
getState: () => (stateCache ??= loadState(cwd)),
|
|
158
|
+
getAgentName: () => (agentNameCache ??= resolveCurrentAgentName(cwd)),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function dispatchReadTool(name, args, ctx) {
|
|
162
|
+
const { cwd, activeSource, resolvedProject, projectRoutingApplied } = ctx;
|
|
139
163
|
if (name === 'bclaw_get_context') {
|
|
140
164
|
// pln#542: budget_tokens caps the relevance-ranked fill (~4 chars/token).
|
|
141
165
|
// Explicit maxChars wins when both are given.
|
|
142
166
|
const budgetTokens = typeof args.budget_tokens === 'number' && args.budget_tokens > 0 ? args.budget_tokens : undefined;
|
|
143
167
|
const result = buildContext({
|
|
144
168
|
target: args.path,
|
|
145
|
-
|
|
169
|
+
// Inside dispatch the name is never 'bclaw_switch', so the raw project
|
|
170
|
+
// arg is exactly the entry point's targetProjectArg.
|
|
171
|
+
project: args.project,
|
|
146
172
|
agent: args.agent,
|
|
147
173
|
host: args.host,
|
|
148
174
|
allHosts: args.allHosts,
|
|
@@ -200,7 +226,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
200
226
|
unseenEventCount = result.context_diff.unseen_event_count;
|
|
201
227
|
}
|
|
202
228
|
else {
|
|
203
|
-
const agentName = args.agent ??
|
|
229
|
+
const agentName = args.agent ?? ctx.getAgentName();
|
|
204
230
|
const unseenEvents = readUnseenEvents(agentName, cwd);
|
|
205
231
|
notifications = buildNotificationSummary(unseenEvents);
|
|
206
232
|
unseenEventCount = unseenEvents.length;
|
|
@@ -228,7 +254,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
228
254
|
if (!handoffId) {
|
|
229
255
|
throw new Error('Missing required argument: id');
|
|
230
256
|
}
|
|
231
|
-
const state =
|
|
257
|
+
const state = ctx.getState();
|
|
232
258
|
const handoff = state.open_handoffs.find((item) => item.id === handoffId || item.short_label === handoffId);
|
|
233
259
|
if (!handoff) {
|
|
234
260
|
return {
|
|
@@ -401,7 +427,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
401
427
|
}
|
|
402
428
|
if (name === 'bclaw_get_execution_context') {
|
|
403
429
|
const executionContext = buildExecutionContext({ cwd });
|
|
404
|
-
const config =
|
|
430
|
+
const config = ctx.getConfig();
|
|
405
431
|
const installableUpdate = checkBrainclawInstallableUpdate(config, cwd, { useDefaultNpmSource: true });
|
|
406
432
|
const installableUpdateNotice = renderBrainclawInstallableUpdateNotice(installableUpdate);
|
|
407
433
|
const agentTooling = args.includeAgentTooling ? buildAgentToolingContext({ cwd }) : undefined;
|
|
@@ -425,7 +451,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
425
451
|
};
|
|
426
452
|
}
|
|
427
453
|
if (name === 'bclaw_release_notes') {
|
|
428
|
-
const config =
|
|
454
|
+
const config = ctx.getConfig();
|
|
429
455
|
const updateCheck = checkBrainclawInstallableUpdate(config, cwd, { useDefaultNpmSource: true });
|
|
430
456
|
const arn = updateCheck.agent_release_notes;
|
|
431
457
|
const lines = [];
|
|
@@ -467,9 +493,9 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
467
493
|
};
|
|
468
494
|
}
|
|
469
495
|
if (name === 'bclaw_get_agent_board_summary') {
|
|
470
|
-
const config =
|
|
471
|
-
const state =
|
|
472
|
-
const agent = args.agent ??
|
|
496
|
+
const config = ctx.getConfig();
|
|
497
|
+
const state = ctx.getState();
|
|
498
|
+
const agent = args.agent ?? ctx.getAgentName();
|
|
473
499
|
const currentHost = resolveCurrentHostId();
|
|
474
500
|
const activeClaims = listClaims(cwd).filter((c) => c.status === 'active');
|
|
475
501
|
const pendingActions = listActionRequired(cwd).filter((a) => a.status === 'pending');
|
|
@@ -733,7 +759,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
733
759
|
};
|
|
734
760
|
}
|
|
735
761
|
if (name === 'bclaw_list_plans') {
|
|
736
|
-
let plans =
|
|
762
|
+
let plans = ctx.getState().plan_items;
|
|
737
763
|
// Direct lookup by ID
|
|
738
764
|
if (args.id) {
|
|
739
765
|
const plan = plans.find((p) => p.id === String(args.id) || p.short_label === String(args.id));
|
|
@@ -1200,25 +1226,32 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1200
1226
|
const current = resolveCurrentAgentIdentity(cwd);
|
|
1201
1227
|
const reputation = args.includeReputation ? buildReputationSnapshot(cwd) : undefined;
|
|
1202
1228
|
const reputationById = new Map((reputation?.agents ?? []).map((agent) => [agent.agent_id ?? agent.key, toPublicReputationSummary(agent)]));
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1229
|
+
// pln#625 Phase 2c (ideation loop lop_f8e8d18cb8c27ada) — redact through the
|
|
1230
|
+
// SAME projection as bclaw_find(entity=agent) so there is ONE source of truth
|
|
1231
|
+
// and no key material / invoke.env leaks here. This tool previously spread the
|
|
1232
|
+
// raw identity doc (identity_key.public_key + invoke.env in the clear).
|
|
1233
|
+
// Reputation stays an opt-in add-on.
|
|
1234
|
+
const structuredAgents = agents.map((agent) => {
|
|
1235
|
+
const projected = projectAgentForRead(agent);
|
|
1236
|
+
return args.includeReputation
|
|
1237
|
+
? { ...projected, reputation: reputationById.get(agent.agent_id) }
|
|
1238
|
+
: projected;
|
|
1239
|
+
});
|
|
1209
1240
|
const lines = structuredAgents.length === 0
|
|
1210
1241
|
? ['No registered agents.']
|
|
1211
1242
|
: [
|
|
1212
1243
|
`${structuredAgents.length} registered agent(s):`,
|
|
1213
1244
|
...structuredAgents.map((agent) => {
|
|
1214
1245
|
const reputation = agent.reputation;
|
|
1215
|
-
const
|
|
1216
|
-
const
|
|
1217
|
-
const
|
|
1246
|
+
const capabilities = agent.capabilities ?? [];
|
|
1247
|
+
const fingerprint = agent.fingerprint;
|
|
1248
|
+
const currentLabel = current?.agent_id === agent.id ? ' [current]' : '';
|
|
1249
|
+
const capabilitiesLabel = capabilities.length > 0 ? ` caps=${capabilities.join(',')}` : '';
|
|
1250
|
+
const fingerprintLabel = fingerprint ? ` fp=${fingerprint.slice(0, 12)}` : '';
|
|
1218
1251
|
const reputationLabel = reputation
|
|
1219
1252
|
? ` trust=${reputation.internal_trust} cq=${reputation.contribution_quality} rv=${reputation.review_reliability} ct=${reputation.continuity_hygiene}`
|
|
1220
1253
|
: '';
|
|
1221
|
-
return `- ${agent.
|
|
1254
|
+
return `- ${String(agent.name)} (${String(agent.id)}, kind=${String(agent.kind)})${currentLabel}${reputationLabel}${capabilitiesLabel}${fingerprintLabel}`;
|
|
1222
1255
|
}),
|
|
1223
1256
|
];
|
|
1224
1257
|
return {
|
|
@@ -1231,7 +1264,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1231
1264
|
};
|
|
1232
1265
|
}
|
|
1233
1266
|
if (name === 'bclaw_list_instructions') {
|
|
1234
|
-
const config =
|
|
1267
|
+
const config = ctx.getConfig();
|
|
1235
1268
|
const project = args.project;
|
|
1236
1269
|
const inferredProject = project ?? inferProjectFromTarget(args.path, config);
|
|
1237
1270
|
const resolvedAgent = args.resolved ? resolveAgentScope(args.agent) : args.agent;
|
|
@@ -1438,7 +1471,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1438
1471
|
if (name === 'bclaw_conflict_check') {
|
|
1439
1472
|
const agentNameArg = args.agent;
|
|
1440
1473
|
const agentIdArg = args.agentId;
|
|
1441
|
-
const currentAgentName = agentNameArg ??
|
|
1474
|
+
const currentAgentName = agentNameArg ?? ctx.getAgentName();
|
|
1442
1475
|
const allClaimsForCheck = listClaims(cwd).filter((c) => c.status === 'active');
|
|
1443
1476
|
const myClaimsForCheck = allClaimsForCheck.filter((c) => agentIdArg ? c.agent_id === agentIdArg : c.agent === currentAgentName);
|
|
1444
1477
|
const otherClaimsForCheck = allClaimsForCheck.filter((c) => agentIdArg ? c.agent_id !== agentIdArg : c.agent !== currentAgentName);
|
|
@@ -1471,7 +1504,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1471
1504
|
if (name === 'bclaw_switch') {
|
|
1472
1505
|
if (args.list === true) {
|
|
1473
1506
|
try {
|
|
1474
|
-
const result = listAvailableProjectsForSession(cwd,
|
|
1507
|
+
const result = listAvailableProjectsForSession(cwd, ctx.connectionSessionId);
|
|
1475
1508
|
const lines = result.projects.map(p => {
|
|
1476
1509
|
const marker = p.active ? '→' : ' ';
|
|
1477
1510
|
const label = p.name ? `${p.name} (${p.relative_path})` : p.relative_path;
|
|
@@ -1488,8 +1521,8 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1488
1521
|
}
|
|
1489
1522
|
if (args.clear === true) {
|
|
1490
1523
|
try {
|
|
1491
|
-
const session =
|
|
1492
|
-
? loadSessionById(
|
|
1524
|
+
const session = ctx.connectionSessionId
|
|
1525
|
+
? loadSessionById(ctx.connectionSessionId, cwd)
|
|
1493
1526
|
: loadCurrentSession(cwd);
|
|
1494
1527
|
if (session?.active_project) {
|
|
1495
1528
|
const { active_project: _removed, ...rest } = session;
|
|
@@ -1509,7 +1542,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1509
1542
|
return createToolErrorResponse('validation_error', 'Missing required argument: project (or use list=true / clear=true)');
|
|
1510
1543
|
}
|
|
1511
1544
|
try {
|
|
1512
|
-
const result = switchProject(projectRef, { cwd, sessionOnly: true, sessionId:
|
|
1545
|
+
const result = switchProject(projectRef, { cwd, sessionOnly: true, sessionId: ctx.connectionSessionId });
|
|
1513
1546
|
const text = `✔ Switched to ${result.name ? `"${result.name}"` : result.path} (${result.scope}-scoped)`;
|
|
1514
1547
|
return {
|
|
1515
1548
|
content: [{ type: 'text', text }],
|
|
@@ -1563,7 +1596,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1563
1596
|
}
|
|
1564
1597
|
const result = checkPolicy({
|
|
1565
1598
|
scope,
|
|
1566
|
-
agent: args.agent ??
|
|
1599
|
+
agent: args.agent ?? ctx.getAgentName(),
|
|
1567
1600
|
agentId: args.agentId,
|
|
1568
1601
|
action: args.action,
|
|
1569
1602
|
cwd,
|
|
@@ -1768,7 +1801,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1768
1801
|
};
|
|
1769
1802
|
}
|
|
1770
1803
|
if (name === 'bclaw_read_inbox') {
|
|
1771
|
-
const agentName = args.agent ??
|
|
1804
|
+
const agentName = args.agent ?? ctx.getAgentName();
|
|
1772
1805
|
const markAsRead = args.markAsRead === true; // default: false — reading doesn't imply processing
|
|
1773
1806
|
const result = readInbox({
|
|
1774
1807
|
agent: agentName,
|
|
@@ -1798,15 +1831,18 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1798
1831
|
// Phase 3 slice 3c — unified dispatcher. See docs/concepts/mcp-governance.md
|
|
1799
1832
|
// for the stability contract of the advanced tier.
|
|
1800
1833
|
const kind = String(args.kind ?? '');
|
|
1834
|
+
// Delegation re-enters dispatchReadTool with the SAME resolved context —
|
|
1835
|
+
// store resolution and project routing already happened at the entry
|
|
1836
|
+
// point and must not be recomputed (pln#622 PR2).
|
|
1801
1837
|
switch (kind) {
|
|
1802
1838
|
case 'memory':
|
|
1803
|
-
return
|
|
1839
|
+
return dispatchReadTool('bclaw_get_context', args, ctx);
|
|
1804
1840
|
case 'execution':
|
|
1805
|
-
return
|
|
1841
|
+
return dispatchReadTool('bclaw_get_execution_context', args, ctx);
|
|
1806
1842
|
case 'board':
|
|
1807
|
-
return
|
|
1843
|
+
return dispatchReadTool('bclaw_get_agent_board', args, ctx);
|
|
1808
1844
|
case 'board_summary':
|
|
1809
|
-
return
|
|
1845
|
+
return dispatchReadTool('bclaw_get_agent_board_summary', args, ctx);
|
|
1810
1846
|
case 'cross_project': {
|
|
1811
1847
|
// pln#558 step 3 — lightweight endpoint for the VS Code extension's
|
|
1812
1848
|
// SYSTEM section: returns linked_projects + incoming_signals only,
|
|
@@ -1823,7 +1859,7 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1823
1859
|
if (typeof since !== 'string' || !since) {
|
|
1824
1860
|
throw new Error('bclaw_context(kind="delta") requires `since` (session_id).');
|
|
1825
1861
|
}
|
|
1826
|
-
return
|
|
1862
|
+
return dispatchReadTool('bclaw_get_context', { ...args, since_session: since }, ctx);
|
|
1827
1863
|
}
|
|
1828
1864
|
default:
|
|
1829
1865
|
throw new Error(`bclaw_context: unknown kind '${kind}'. Expected memory | execution | board | board_summary | cross_project | delta.`);
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP admin / provisioning write-tool handlers.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from mcp.ts (pln#622 PR4) — mechanical move of the setup wizard,
|
|
5
|
+
* project init, and capability/tool registration write handlers. Behavior is
|
|
6
|
+
* unchanged; each handler receives the tool-call payload plus a
|
|
7
|
+
* {@link McpWriteAdminContext} carrying the model resolved once per write call.
|
|
8
|
+
*
|
|
9
|
+
* This module must never import ./mcp.js (dependency-direction guard,
|
|
10
|
+
* pln#622 PR1).
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import os from 'node:os';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { loadConfig } from '../core/config.js';
|
|
18
|
+
import { memoryExists } from '../core/io.js';
|
|
19
|
+
import { appendAuditEntry } from '../core/audit.js';
|
|
20
|
+
import { createCapability, createTool as createRegistryTool } from '../core/registries.js';
|
|
21
|
+
import { detectAiAgent } from '../core/ai-agent-detection.js';
|
|
22
|
+
import { checkGitPresence, scanGitRepos, parseRoots, parseRepoSelection, parseAgentSelection, getDetectedSetupAgentNames, getInstalledAgentNames, runGlobalInstall, initReposAndConfigureAgents, readSetupState, ALL_KNOWN_AGENTS, } from './setup.js';
|
|
23
|
+
import { buildAgentInventory } from '../core/agent-inventory.js';
|
|
24
|
+
import { probeForQuickSetup, buildQuickSetupProbeResponse, buildOnboardingPreview, resolveEmptyMemoryRecommendation } from '../core/setup-flow.js';
|
|
25
|
+
import { ensureUserStore, resolveHomeDir } from '../core/setup-state.js';
|
|
26
|
+
import { ensureTrust } from './mcp-write-support.js';
|
|
27
|
+
import { SCHEMA_VERSION, toolResponse, createToolErrorResponse, } from './mcp-contract.js';
|
|
28
|
+
export async function handleBclawSetup(payload, _ctx) {
|
|
29
|
+
const { args, cwd } = payload;
|
|
30
|
+
const step = args.step;
|
|
31
|
+
const choice = args.choice ?? '';
|
|
32
|
+
const rootsArg = args.roots;
|
|
33
|
+
const repoSelectionArg = args.repo_selection;
|
|
34
|
+
const modeArg = args.mode;
|
|
35
|
+
const env = process.env;
|
|
36
|
+
if (!checkGitPresence()) {
|
|
37
|
+
return { response: toolResponse({ content: [{ type: 'text', text: 'Git is not installed or not found in PATH. Install git from https://git-scm.com before running brainclaw setup.' }], structuredContent: { error: 'git_not_found' } }, true) };
|
|
38
|
+
}
|
|
39
|
+
// ─── Quick mode: probe current repo ──────────────────────────────
|
|
40
|
+
if (!step) {
|
|
41
|
+
// Auto-detect mode: if we're in a git repo, use quick mode unless batch is forced
|
|
42
|
+
const forceBatch = modeArg === 'batch';
|
|
43
|
+
if (!forceBatch) {
|
|
44
|
+
const probe = probeForQuickSetup(cwd);
|
|
45
|
+
if (probe.isGitRepo || probe.alreadyInitialized) {
|
|
46
|
+
const response = buildQuickSetupProbeResponse(probe);
|
|
47
|
+
return { response: toolResponse({ content: [{ type: 'text', text: response.text }], structuredContent: response.structured }) };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Fall through to batch mode
|
|
51
|
+
const existingState = readSetupState(env);
|
|
52
|
+
const alreadyRun = existingState ? `Setup was previously run on ${new Date(existingState.completed_at).toLocaleDateString()}. You can re-run it.` : undefined;
|
|
53
|
+
return { response: toolResponse({ content: [{ type: 'text', text: [alreadyRun, "Where are the user's project directories? Please ask the user to provide one or more root paths where their git repositories are located (e.g. ~/Projects, C:\\Users\\user\\code)."].filter(Boolean).join('\n\n') }], structuredContent: { pending_question: 'project_roots', prompt: 'Please ask the user: "Where are your projects? Enter one or more root directories (comma-separated):"', ...(alreadyRun ? { already_run: alreadyRun } : {}) } }) };
|
|
54
|
+
}
|
|
55
|
+
// ─── Quick mode step: init with choices ──────────────────────────
|
|
56
|
+
if (step === 'quick_init') {
|
|
57
|
+
const projectType = args.project_type ?? 'standalone';
|
|
58
|
+
const topology = args.topology ?? 'embedded';
|
|
59
|
+
// Ensure user store exists
|
|
60
|
+
ensureUserStore(env);
|
|
61
|
+
// Map choices to init options
|
|
62
|
+
const projectMode = projectType === 'workspace' ? 'multi-project' : 'auto';
|
|
63
|
+
const topologyMode = topology === 'sidecar' ? 'sidecar' : 'embedded';
|
|
64
|
+
// Run init
|
|
65
|
+
try {
|
|
66
|
+
const { runInit } = await import('./init.js');
|
|
67
|
+
await runInit({
|
|
68
|
+
yes: true,
|
|
69
|
+
cwd,
|
|
70
|
+
skipAgentBootstrap: false,
|
|
71
|
+
projectMode,
|
|
72
|
+
topology: topologyMode,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
return { response: toolResponse({ content: [{ type: 'text', text: `Init failed: ${err instanceof Error ? err.message : String(err)}` }], structuredContent: { error: 'init_failed', details: err instanceof Error ? err.message : String(err) } }, true) };
|
|
77
|
+
}
|
|
78
|
+
// Detect agent and report
|
|
79
|
+
const detected = detectAiAgent(env);
|
|
80
|
+
const summary = [
|
|
81
|
+
`✔ Initialized ${cwd.split(/[\\/]/).pop() ?? cwd} (${projectType}, ${topology})`,
|
|
82
|
+
];
|
|
83
|
+
if (detected) {
|
|
84
|
+
summary.push(`✔ Agent detected: ${detected.name}`);
|
|
85
|
+
}
|
|
86
|
+
summary.push('✔ Full brainclaw MCP catalog activates automatically; reload your agent session only if new tools do not appear.');
|
|
87
|
+
// Bootstrap route follows the shared empty-memory rule; the preview
|
|
88
|
+
// already embeds the same recommendation text when memory is empty.
|
|
89
|
+
const probe = probeForQuickSetup(cwd);
|
|
90
|
+
const bootstrapAvailable = probe.hasContent;
|
|
91
|
+
const emptyMemoryRec = resolveEmptyMemoryRecommendation(cwd);
|
|
92
|
+
const preview = buildOnboardingPreview(cwd);
|
|
93
|
+
return {
|
|
94
|
+
response: toolResponse({
|
|
95
|
+
content: [{ type: 'text', text: summary.join('\n') + '\n\n' + preview }],
|
|
96
|
+
structuredContent: {
|
|
97
|
+
setup_complete: true,
|
|
98
|
+
project_type: projectType,
|
|
99
|
+
topology,
|
|
100
|
+
detected_agent: detected?.name ?? null,
|
|
101
|
+
bootstrap_available: bootstrapAvailable,
|
|
102
|
+
bootstrap_route: emptyMemoryRec.route,
|
|
103
|
+
next_action: emptyMemoryRec.mcp_next_action,
|
|
104
|
+
preview,
|
|
105
|
+
summary,
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (step === 'project_roots') {
|
|
111
|
+
const roots = parseRoots(choice, env);
|
|
112
|
+
if (roots.length === 0) {
|
|
113
|
+
return { response: toolResponse({ content: [{ type: 'text', text: 'No valid directories found from the provided paths. Please ask the user for valid root directories.' }], structuredContent: { error: 'no_valid_roots', provided: choice } }, true) };
|
|
114
|
+
}
|
|
115
|
+
const repos = scanGitRepos(roots);
|
|
116
|
+
const repoList = repos.map((r, i) => ` ${i + 1}) ${r.alreadyInitialised ? '[✔ init]' : '[ ]'} ${r.name} (${r.path})`).join('\n');
|
|
117
|
+
return { response: toolResponse({ content: [{ type: 'text', text: `Found ${repos.length} repository candidate(s):\n${repoList}\n\nAsk the user which repositories to initialise.` }], structuredContent: { pending_question: 'repo_selection', roots: roots.join(','), repos: repos.map((r) => ({ path: r.path, name: r.name, alreadyInitialised: r.alreadyInitialised })), prompt: 'Please ask the user: "Which repositories to initialise? Reply: (a)ll, (c)urrent, or numbers like 1,3"' } }) };
|
|
118
|
+
}
|
|
119
|
+
if (step === 'repo_selection') {
|
|
120
|
+
if (!rootsArg) {
|
|
121
|
+
return { response: toolResponse({ content: [{ type: 'text', text: 'Missing roots parameter. Pass the roots value from the previous step.' }], structuredContent: { error: 'missing_roots' } }, true) };
|
|
122
|
+
}
|
|
123
|
+
const roots = parseRoots(rootsArg, env);
|
|
124
|
+
const repos = scanGitRepos(roots);
|
|
125
|
+
const selectedRepos = parseRepoSelection(choice, repos, cwd);
|
|
126
|
+
const detected = detectAiAgent(env);
|
|
127
|
+
const installedAgents = getInstalledAgentNames(buildAgentInventory(resolveHomeDir(env) ?? os.homedir(), env));
|
|
128
|
+
const detectedSetupAgents = getDetectedSetupAgentNames(detected?.name, installedAgents);
|
|
129
|
+
const agentList = ALL_KNOWN_AGENTS.map((a, i) => {
|
|
130
|
+
const tag = a === detected?.name ? ' ← detected' : installedAgents.includes(a) ? ' ← installed' : '';
|
|
131
|
+
return ` ${i + 1}) ${a}${tag}`;
|
|
132
|
+
}).join('\n');
|
|
133
|
+
const detectedLine = detectedSetupAgents.length > 0 ? `\nDetected install set: ${detectedSetupAgents.join(', ')}\n` : '\n';
|
|
134
|
+
return { response: toolResponse({ content: [{ type: 'text', text: `Selected ${selectedRepos.length} repo(s). Detected AI agent: ${detected?.name ?? 'none'}.${detectedLine}\nAvailable agents:\n${agentList}\n\nAsk the user which agents to configure.` }], structuredContent: { pending_question: 'agent_selection', roots: rootsArg, repo_selection: choice, selected_repos: selectedRepos.map((r) => ({ path: r.path, name: r.name })), detected_agent: detected?.name ?? null, installed_agents: installedAgents, detected_setup_agents: detectedSetupAgents, all_agents: ALL_KNOWN_AGENTS, prompt: 'Please ask the user: "Which agents to configure? Reply: (d)etected installed, (a)ll, or agent names like claude-code,cursor"' } }) };
|
|
135
|
+
}
|
|
136
|
+
if (step === 'agent_selection') {
|
|
137
|
+
if (!rootsArg || !repoSelectionArg) {
|
|
138
|
+
return { response: toolResponse({ content: [{ type: 'text', text: 'Missing roots or repo_selection parameter from previous steps.' }], structuredContent: { error: 'missing_params' } }, true) };
|
|
139
|
+
}
|
|
140
|
+
const roots = parseRoots(rootsArg, env);
|
|
141
|
+
const repos = scanGitRepos(roots);
|
|
142
|
+
const selectedRepos = parseRepoSelection(repoSelectionArg, repos, cwd);
|
|
143
|
+
const detected = detectAiAgent(env);
|
|
144
|
+
const installedAgents = getInstalledAgentNames(buildAgentInventory(resolveHomeDir(env) ?? os.homedir(), env));
|
|
145
|
+
const selectedAgents = parseAgentSelection(choice, detected?.name, installedAgents);
|
|
146
|
+
const summary = [];
|
|
147
|
+
const written = runGlobalInstall(selectedAgents, env);
|
|
148
|
+
for (const f of written)
|
|
149
|
+
summary.push(`✔ Global config: ${f}`);
|
|
150
|
+
const { initialisedRepos, configActions } = await initReposAndConfigureAgents(selectedRepos, selectedAgents, env);
|
|
151
|
+
for (const p of initialisedRepos)
|
|
152
|
+
summary.push(`✔ Initialised repo: ${p}`);
|
|
153
|
+
for (const a of configActions)
|
|
154
|
+
summary.push(a);
|
|
155
|
+
let reloadMsg = '✔ Setup complete! Reload your AI agent session to activate brainclaw MCP tools.';
|
|
156
|
+
if (detected?.name === 'claude-code')
|
|
157
|
+
reloadMsg += '\n → In VS Code: Cmd/Ctrl+Shift+P → "Claude: Reload MCP Servers"';
|
|
158
|
+
else if (detected?.name === 'cursor')
|
|
159
|
+
reloadMsg += '\n → In Cursor: restart the editor';
|
|
160
|
+
else if (detected?.name === 'windsurf')
|
|
161
|
+
reloadMsg += '\n → In Windsurf: restart the editor';
|
|
162
|
+
return { response: toolResponse({ content: [{ type: 'text', text: [reloadMsg, '', ...summary].join('\n') }], structuredContent: { setup_complete: true, initialised_repos: initialisedRepos, global_configs_written: written, agent_configs_written: configActions, detected_agent: detected?.name ?? null, summary } }) };
|
|
163
|
+
}
|
|
164
|
+
return { response: toolResponse({ content: [{ type: 'text', text: `Unknown step: "${step}". Valid steps: project_roots, repo_selection, agent_selection.` }], structuredContent: { error: 'unknown_step', step } }, true) };
|
|
165
|
+
}
|
|
166
|
+
export async function handleBclawInitProject(payload, _ctx) {
|
|
167
|
+
const { args, cwd } = payload;
|
|
168
|
+
const rawPath = typeof args.path === 'string' ? args.path.trim() : '';
|
|
169
|
+
if (!rawPath) {
|
|
170
|
+
return { response: createToolErrorResponse('validation_error', 'path is required') };
|
|
171
|
+
}
|
|
172
|
+
const force = args.force === true;
|
|
173
|
+
const projectModeArg = typeof args.project_mode === 'string' ? args.project_mode : undefined;
|
|
174
|
+
const linkAs = typeof args.link_as === 'string' && args.link_as.trim().length > 0
|
|
175
|
+
? args.link_as.trim()
|
|
176
|
+
: undefined;
|
|
177
|
+
const resolvedPath = path.isAbsolute(rawPath) ? rawPath : path.resolve(cwd, rawPath);
|
|
178
|
+
let wasAlreadyInitialized = false;
|
|
179
|
+
if (memoryExists(resolvedPath) && !force) {
|
|
180
|
+
wasAlreadyInitialized = true;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
184
|
+
try {
|
|
185
|
+
fs.mkdirSync(resolvedPath, { recursive: true });
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
return {
|
|
189
|
+
response: createToolErrorResponse('init_project_failed', `Failed to create target directory '${resolvedPath}': ${err instanceof Error ? err.message : String(err)}`),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
const { runInit } = await import('./init.js');
|
|
195
|
+
await runInit({
|
|
196
|
+
yes: true,
|
|
197
|
+
cwd: resolvedPath,
|
|
198
|
+
force,
|
|
199
|
+
...(projectModeArg ? { projectMode: projectModeArg } : {}),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
return {
|
|
204
|
+
response: createToolErrorResponse('init_project_failed', `runInit failed for '${resolvedPath}': ${err instanceof Error ? err.message : String(err)}`),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
let projectName;
|
|
209
|
+
try {
|
|
210
|
+
projectName = loadConfig(resolvedPath).project_name;
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
projectName = path.basename(resolvedPath);
|
|
214
|
+
}
|
|
215
|
+
let linkName;
|
|
216
|
+
try {
|
|
217
|
+
const { addCrossProjectLink } = await import('../core/cross-project.js');
|
|
218
|
+
const link = addCrossProjectLink({
|
|
219
|
+
path: resolvedPath,
|
|
220
|
+
name: linkAs ?? projectName,
|
|
221
|
+
cwd,
|
|
222
|
+
force,
|
|
223
|
+
});
|
|
224
|
+
linkName = link.name ?? path.basename(resolvedPath);
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
228
|
+
// Treat a duplicate link as idempotent success when the caller did
|
|
229
|
+
// not request --force; the project itself is initialised correctly
|
|
230
|
+
// and the existing link already points at it.
|
|
231
|
+
if (/already exists/i.test(message) && !force) {
|
|
232
|
+
try {
|
|
233
|
+
const { resolveCrossProjectLinks } = await import('../core/cross-project.js');
|
|
234
|
+
const existing = resolveCrossProjectLinks(cwd).find((l) => l.absolutePath === resolvedPath || l.path === rawPath);
|
|
235
|
+
linkName = existing?.name ?? linkAs ?? projectName;
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
linkName = linkAs ?? projectName;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
return {
|
|
243
|
+
response: createToolErrorResponse('init_project_failed', `Failed to register cross_project_link: ${message}`),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const summary = wasAlreadyInitialized
|
|
248
|
+
? `✔ ${resolvedPath} already initialised; linked as '${linkName}'.`
|
|
249
|
+
: `✔ Initialised brainclaw at ${resolvedPath} and linked as '${linkName}'.`;
|
|
250
|
+
return {
|
|
251
|
+
response: toolResponse({
|
|
252
|
+
content: [{ type: 'text', text: summary }],
|
|
253
|
+
structuredContent: {
|
|
254
|
+
status: 'ok',
|
|
255
|
+
project_name: projectName,
|
|
256
|
+
path: resolvedPath,
|
|
257
|
+
link_id: linkName,
|
|
258
|
+
was_already_initialized: wasAlreadyInitialized,
|
|
259
|
+
},
|
|
260
|
+
}),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
export function handleBclawAddCapability(payload, ctx) {
|
|
264
|
+
const { args, cwd, connectionSessionId } = payload;
|
|
265
|
+
const capName = String(args.name ?? '').trim();
|
|
266
|
+
const capDesc = String(args.description ?? '').trim();
|
|
267
|
+
if (!capName || !capDesc) {
|
|
268
|
+
return { response: createToolErrorResponse('validation_error', 'Missing required arguments: name and description') };
|
|
269
|
+
}
|
|
270
|
+
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'contributor', cwd, connectionSessionId);
|
|
271
|
+
if (resolved.error) {
|
|
272
|
+
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
273
|
+
}
|
|
274
|
+
const resolvedIdentity = resolved.identity;
|
|
275
|
+
const extraTags = Array.isArray(args.tags) ? args.tags : [];
|
|
276
|
+
const cap = createCapability({
|
|
277
|
+
name: capName,
|
|
278
|
+
description: capDesc,
|
|
279
|
+
tags: extraTags,
|
|
280
|
+
author: resolvedIdentity.agent_name,
|
|
281
|
+
authorId: resolvedIdentity.agent_id,
|
|
282
|
+
model: ctx.currentModel,
|
|
283
|
+
}, cwd);
|
|
284
|
+
appendAuditEntry({ actor: resolvedIdentity.agent_name, actor_id: resolvedIdentity.agent_id, action: 'create', item_id: cap.id, item_type: 'capability', reason: `capability: ${capName}` }, cwd);
|
|
285
|
+
return {
|
|
286
|
+
response: toolResponse({
|
|
287
|
+
content: [{ type: 'text', text: `✔ Capability registered: [${cap.id}] ${capName}` }],
|
|
288
|
+
id: cap.id,
|
|
289
|
+
name: capName,
|
|
290
|
+
schema_version: SCHEMA_VERSION,
|
|
291
|
+
}),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
export function handleBclawAddTool(payload, ctx) {
|
|
295
|
+
const { args, cwd, connectionSessionId } = payload;
|
|
296
|
+
const toolName = String(args.name ?? '').trim();
|
|
297
|
+
const toolDesc = String(args.description ?? '').trim();
|
|
298
|
+
if (!toolName || !toolDesc) {
|
|
299
|
+
return { response: createToolErrorResponse('validation_error', 'Missing required arguments: name and description') };
|
|
300
|
+
}
|
|
301
|
+
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'contributor', cwd, connectionSessionId);
|
|
302
|
+
if (resolved.error) {
|
|
303
|
+
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
304
|
+
}
|
|
305
|
+
const resolvedIdentity = resolved.identity;
|
|
306
|
+
const toolType = String(args.type ?? 'utility');
|
|
307
|
+
const extraTags = Array.isArray(args.tags) ? args.tags : [];
|
|
308
|
+
const tool = createRegistryTool({
|
|
309
|
+
name: toolName,
|
|
310
|
+
description: toolDesc,
|
|
311
|
+
type: toolType,
|
|
312
|
+
tags: extraTags,
|
|
313
|
+
author: resolvedIdentity.agent_name,
|
|
314
|
+
authorId: resolvedIdentity.agent_id,
|
|
315
|
+
model: ctx.currentModel,
|
|
316
|
+
}, cwd);
|
|
317
|
+
appendAuditEntry({ actor: resolvedIdentity.agent_name, actor_id: resolvedIdentity.agent_id, action: 'create', item_id: tool.id, item_type: 'tool', reason: `tool: ${toolName}` }, cwd);
|
|
318
|
+
return {
|
|
319
|
+
response: toolResponse({
|
|
320
|
+
content: [{ type: 'text', text: `✔ Tool registered: [${tool.id}] ${toolName} (${toolType})` }],
|
|
321
|
+
id: tool.id,
|
|
322
|
+
name: toolName,
|
|
323
|
+
type: toolType,
|
|
324
|
+
schema_version: SCHEMA_VERSION,
|
|
325
|
+
}),
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
//# sourceMappingURL=mcp-write-admin.js.map
|