@brainmcp/brainmcp 0.1.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.
@@ -0,0 +1,329 @@
1
+ /** Canonical brainMCP endpoint and product identity. */
2
+ export const BRAIN_MCP_ENDPOINT = 'https://mcp.brainmcp.ai/mcp';
3
+ export const BRAIN_MCP_SERVER_NAME = 'brain';
4
+ export const BRAIN_PRODUCT_ALIASES = ['brain', 'brainmcp', 'brAIn'];
5
+ export const BRAIN_DASHBOARD_URL = 'https://dash.brainmcp.ai';
6
+ export const BRAIN_DOCS_URL = 'https://www.brainmcp.ai/docs';
7
+ export const BRAIN_PROACTIVE_DOCS_URL = 'https://www.brainmcp.ai/docs/configure-proactive-use';
8
+ export const BRAIN_TROUBLESHOOTING_DOCS_URL = 'https://www.brainmcp.ai/docs/troubleshooting';
9
+ export const BRAIN_OAUTH_AUTH_METADATA_URL = 'https://dash.brainmcp.ai/.well-known/oauth-authorization-server';
10
+ export const BRAIN_OAUTH_RESOURCE_METADATA_URL = 'https://mcp.brainmcp.ai/.well-known/oauth-protected-resource';
11
+ /** Bump when always-on guidance text changes in a way clients should refresh. */
12
+ export const GUIDANCE_VERSION = '2026.07.10';
13
+ export const MCP_OAUTH_CLIENT_IDS = {
14
+ claudeCode: 'claude-code',
15
+ cursor: 'cursor',
16
+ generic: 'generic-mcp',
17
+ };
18
+ export const MCP_CLIENT_PROFILES = {
19
+ 'claude-code': {
20
+ id: 'claude-code',
21
+ label: 'Claude Code',
22
+ oauthClientId: MCP_OAUTH_CLIENT_IDS.claudeCode,
23
+ supportsProjectInstructions: true,
24
+ supportsPlugins: true,
25
+ supportsHooks: true,
26
+ notes: 'Best path: plugin (MCP + Skill + SessionStart/post-compaction reminder). Hooks inject context; they do not guarantee a tool call.',
27
+ },
28
+ cursor: {
29
+ id: 'cursor',
30
+ label: 'Cursor',
31
+ oauthClientId: MCP_OAUTH_CLIENT_IDS.cursor,
32
+ supportsProjectInstructions: true,
33
+ supportsPlugins: true,
34
+ supportsHooks: false,
35
+ notes: 'Best path: Marketplace plugin or .cursor/rules/brainmcp-workspace.mdc (named .mdc with frontmatter). Cursor Cloud Agents need a separate Dashboard MCP connection.',
36
+ },
37
+ codex: {
38
+ id: 'codex',
39
+ label: 'Codex',
40
+ supportsProjectInstructions: true,
41
+ supportsPlugins: false,
42
+ supportsHooks: false,
43
+ notes: 'Use AGENTS.md / Agent Skills project install plus HTTP MCP config.',
44
+ },
45
+ vscode: {
46
+ id: 'vscode',
47
+ label: 'VS Code / Copilot',
48
+ supportsProjectInstructions: true,
49
+ supportsPlugins: false,
50
+ supportsHooks: false,
51
+ notes: 'Use mcp.json + AGENTS.md / Agent Skills. Prefer static OAuth client when available.',
52
+ },
53
+ generic: {
54
+ id: 'generic',
55
+ label: 'Generic MCP client',
56
+ oauthClientId: MCP_OAUTH_CLIENT_IDS.generic,
57
+ supportsProjectInstructions: true,
58
+ supportsPlugins: false,
59
+ supportsHooks: false,
60
+ notes: 'URL-only setup requires Dynamic Client Registration (or Client ID Metadata Documents). Prefer the static generic-mcp client id when the client supports it.',
61
+ },
62
+ };
63
+ export const CANONICAL_WORKFLOW_STEPS = [
64
+ 'Pick workspace (brain_workspaces_list if needed; overview may omit workspaceId when a default is configured)',
65
+ 'Orient with brain_workspace_overview and follow recommendedNextActions',
66
+ 'Pull context (search / graph_read / node_read / context_handoff; resolve Skills and due Workflows when recommended)',
67
+ 'Do the work outside brain',
68
+ 'Write back durable learnings (brain_change_propose; digest only as disclosed fallback; comments/reports/workflow runs as needed)',
69
+ ];
70
+ /** Overview payload canonicalWorkflow strings (stable, short). */
71
+ export const OVERVIEW_CANONICAL_WORKFLOW = [
72
+ 'Start with brain_workspace_overview (workspaceId optional when a default is configured).',
73
+ 'If recommendations include human review, tell the user before relying on stale graph areas.',
74
+ 'Search relevant nodes with brain_node_search using the current task intent.',
75
+ 'Pull scoped context with brain_context_handoff or brain_graph_read.',
76
+ 'Resolve Skills and due Workflows when the overview recommends them.',
77
+ 'Treat rules returned in this overview as binding for the whole session; other read tools do not repeat them.',
78
+ 'Check summaries.recentDecisions and summaries.recentCommentAcks for human feedback on your prior proposals and reports.',
79
+ 'After meaningful work, propose durable graph updates with brain_change_propose.',
80
+ 'Use brain_session_digest only as a disclosed fallback when the user asks to capture unstructured session learnings — not as an automatic dump of every session.',
81
+ ];
82
+ /** Short always-on project instruction block (CLAUDE.md / AGENTS.md / Cursor rules). */
83
+ export function buildProjectInstructionBlock(options) {
84
+ const workspaceId = options?.workspaceId?.trim();
85
+ const includeEndpoint = options?.includeEndpoint ?? true;
86
+ const lines = [
87
+ '## brain — shared project memory',
88
+ '',
89
+ 'This project uses brain (also called brainmcp or brAIn) as shared, reviewable agent memory over MCP.',
90
+ 'When the user says "brain" or "brainmcp", use these brain_* tools.',
91
+ ];
92
+ if (includeEndpoint) {
93
+ lines.push(`MCP endpoint: ${BRAIN_MCP_ENDPOINT}`);
94
+ }
95
+ if (workspaceId) {
96
+ lines.push(`Workspace id: ${workspaceId}`);
97
+ }
98
+ lines.push('', '- Orient at session start: call brain_workspace_overview (workspaceId optional when this authorization has a default). Follow recommendedNextActions and treat returned Rules/Skills/Workflows as binding.', '- If the target workspace is unclear or the user asks to switch, call brain_workspaces_list and pass the chosen workspaceId to later tools. Keep later reads/writes explicit.', '- Before non-trivial work, pull relevant context with brain_node_search, then brain_graph_read / brain_node_read / brain_context_handoff. Resolve Skills and due Workflows when recommended.', '- After meaningful work, propose precise durable changes with brain_change_propose. Use brain_session_digest only as a disclosed fallback when the user asks to capture unstructured session learnings — not as an automatic dump of every session.', '- brain_change_propose is the only precise graph write path; review-required proposals do not change the graph until a human approves them.', '', `Guidance version: ${GUIDANCE_VERSION}`);
99
+ return lines.join('\n');
100
+ }
101
+ /** Compact proactive reminder injected by SessionStart / post-compaction hooks. */
102
+ export function buildLifecycleOrientationReminder(options) {
103
+ const workspaceHint = options?.workspaceId?.trim()
104
+ ? ` Prefer workspaceId ${options.workspaceId.trim()} when calling tools.`
105
+ : ' Prefer the authorization default workspace when overview omits workspaceId.';
106
+ return [
107
+ 'brainMCP reminder: orient with brain_workspace_overview, pull context before non-trivial work, follow Rules/Skills/Workflows, and propose precise durable changes after meaningful work.',
108
+ 'Session digests are a disclosed fallback, not an automatic dump.',
109
+ workspaceHint.trim(),
110
+ `Guidance ${GUIDANCE_VERSION}.`,
111
+ ].join(' ');
112
+ }
113
+ export const WRITE_BACK_REMINDER = 'After meaningful work, propose precise durable graph changes with brain_change_propose. Use brain_session_digest only when the user asks to capture unstructured session learnings as a pending digest — not as an automatic dump of every session.';
114
+ export const AGENT_SKILL_NAME = 'using-brainmcp';
115
+ export const AGENT_SKILL_DESCRIPTION = 'Use brain / brainmcp shared project memory: orient, pull context before work, follow Rules/Skills/Workflows, and write durable learnings back as reviewable proposals.';
116
+ export function buildAgentSkillMarkdown(options) {
117
+ const workspaceId = options?.workspaceId?.trim();
118
+ return [
119
+ '---',
120
+ `name: ${AGENT_SKILL_NAME}`,
121
+ `description: ${AGENT_SKILL_DESCRIPTION}`,
122
+ '---',
123
+ '',
124
+ '# Using brainMCP',
125
+ '',
126
+ 'Trigger this skill when the user mentions brain, brainmcp, project memory, shared agent context, durable learnings, workspace overview, or asks you to save/check/update project knowledge.',
127
+ '',
128
+ '## Do this',
129
+ '',
130
+ '1. Call `brain_workspace_overview`' +
131
+ (workspaceId ? ` with workspaceId \`${workspaceId}\`` : ' (omit workspaceId only when a default is configured)') +
132
+ '.',
133
+ '2. Follow `recommendedNextActions`, Rules, Skills, and due Workflows.',
134
+ '3. Before non-trivial work: `brain_node_search` → read/handoff tools.',
135
+ '4. After meaningful work: `brain_change_propose` for precise durable changes.',
136
+ '5. Use `brain_session_digest` only as a disclosed fallback when asked to capture unstructured session learnings.',
137
+ '',
138
+ '## Do not',
139
+ '',
140
+ '- Assume MCP server instructions are already in context — still call overview.',
141
+ '- Dump every session into a digest automatically.',
142
+ '- Write the graph by any path other than `brain_change_propose`.',
143
+ '',
144
+ buildProjectInstructionBlock({ workspaceId, includeEndpoint: true }),
145
+ ].join('\n');
146
+ }
147
+ export function renderClaudeMdBlock(options) {
148
+ return buildProjectInstructionBlock(options);
149
+ }
150
+ export function renderAgentsMdBlock(options) {
151
+ return buildProjectInstructionBlock(options);
152
+ }
153
+ export function renderCursorMdc(options) {
154
+ const alwaysApply = options?.alwaysApply ?? true;
155
+ return [
156
+ '---',
157
+ 'description: Use brainMCP shared project memory proactively',
158
+ `alwaysApply: ${alwaysApply}`,
159
+ '---',
160
+ '',
161
+ buildProjectInstructionBlock(options),
162
+ ].join('\n');
163
+ }
164
+ /** Shared HTTP MCP JSON shape used by Cursor, VS Code, and generic clients. */
165
+ export function buildHttpMcpJson(options) {
166
+ const server = {
167
+ url: BRAIN_MCP_ENDPOINT,
168
+ };
169
+ if (options?.oauthClientId) {
170
+ server.auth = { CLIENT_ID: options.oauthClientId };
171
+ }
172
+ return JSON.stringify({
173
+ mcpServers: {
174
+ [BRAIN_MCP_SERVER_NAME]: server,
175
+ },
176
+ }, null, 2);
177
+ }
178
+ export function buildCursorMcpJson(options) {
179
+ return buildHttpMcpJson({
180
+ oauthClientId: options?.oauthClientId ?? MCP_OAUTH_CLIENT_IDS.cursor,
181
+ });
182
+ }
183
+ /**
184
+ * Claude Code `.mcp.json` server entry. URL-only: Claude Code registers itself
185
+ * via DCR with its exact loopback callback. (A pre-configured client id would
186
+ * need the `oauth: { clientId }` shape plus a fixed callback port.)
187
+ */
188
+ export function buildClaudeCodeMcpJson() {
189
+ return JSON.stringify({
190
+ mcpServers: {
191
+ [BRAIN_MCP_SERVER_NAME]: { type: 'http', url: BRAIN_MCP_ENDPOINT },
192
+ },
193
+ }, null, 2);
194
+ }
195
+ /**
196
+ * VS Code project `.vscode/mcp.json` — VS Code uses a top-level `servers` map
197
+ * (not `mcpServers`). URL-only; the client completes OAuth via DCR.
198
+ */
199
+ export function buildVscodeMcpJson() {
200
+ return JSON.stringify({
201
+ servers: {
202
+ [BRAIN_MCP_SERVER_NAME]: { type: 'http', url: BRAIN_MCP_ENDPOINT },
203
+ },
204
+ }, null, 2);
205
+ }
206
+ /** Generic / custom client JSON with the static `generic-mcp` client id. */
207
+ export function buildGenericMcpJson() {
208
+ return buildHttpMcpJson({ oauthClientId: MCP_OAUTH_CLIENT_IDS.generic });
209
+ }
210
+ /** Codex `.codex/config.toml` brain server block (display / manual paste). */
211
+ export function buildCodexConfigToml() {
212
+ return [`[mcp_servers.${BRAIN_MCP_SERVER_NAME}]`, `url = "${BRAIN_MCP_ENDPOINT}"`, ''].join('\n');
213
+ }
214
+ /**
215
+ * Canonical Claude Code registration. URL-only on purpose: Claude Code's OAuth
216
+ * callback is `http://localhost:<random-port>/callback`, and the authorization
217
+ * server gives registered `localhost` redirects no port flexibility (RFC 8252
218
+ * §8.3), so Dynamic Client Registration — which registers the exact callback —
219
+ * is the reliable path. Use the static fallback only when DCR is unavailable.
220
+ */
221
+ export function buildClaudeCodeMcpAddCommand() {
222
+ return `claude mcp add --transport http ${BRAIN_MCP_SERVER_NAME} ${BRAIN_MCP_ENDPOINT}`;
223
+ }
224
+ /** Fixed callback port registered for the static `claude-code` OAuth client. */
225
+ export const CLAUDE_CODE_STATIC_FALLBACK_CALLBACK_PORT = 8765;
226
+ /** Static-client fallback for environments where DCR is blocked. The pinned
227
+ * callback port must match a redirect URI registered on the `claude-code`
228
+ * client (see packages/db/scripts/seed-oauth-clients.ts). */
229
+ export function buildClaudeCodeStaticFallbackCommand() {
230
+ return `claude mcp add --transport http ${BRAIN_MCP_SERVER_NAME} ${BRAIN_MCP_ENDPOINT} --client-id ${MCP_OAUTH_CLIENT_IDS.claudeCode} --callback-port ${CLAUDE_CODE_STATIC_FALLBACK_CALLBACK_PORT}`;
231
+ }
232
+ /** npm package name for the publishable setup CLI (`npx @brainmcp/brainmcp`). */
233
+ export const BRAINMCP_CLI_PACKAGE = '@brainmcp/brainmcp';
234
+ export function buildBrainmcpInitCommand(options) {
235
+ const parts = [`npx ${BRAINMCP_CLI_PACKAGE} init`];
236
+ if (options?.client)
237
+ parts.push(`--client ${options.client}`);
238
+ if (options?.workspaceId)
239
+ parts.push(`--workspace ${options.workspaceId}`);
240
+ parts.push('--yes');
241
+ return parts.join(' ');
242
+ }
243
+ export function buildBrainmcpDoctorCommand(options) {
244
+ const parts = [`npx ${BRAINMCP_CLI_PACKAGE} doctor`];
245
+ if (options?.workspaceId)
246
+ parts.push(`--workspace ${options.workspaceId}`);
247
+ if (options?.client)
248
+ parts.push(`--client ${options.client}`);
249
+ return parts.join(' ');
250
+ }
251
+ export function buildServerInstructions() {
252
+ return [
253
+ 'brain is this project\'s shared, persistent, reviewable memory MCP. It is also called brainmcp; every tool is prefixed brain_. When the user says "use brain", "check brain", "save this to brain", or "use brainmcp", they mean this server.',
254
+ '',
255
+ 'WHAT IT IS: a graph-native knowledge base for THIS project that outlives a single chat. Knowledge lives as a graph of Cores (major areas) and Neurons (durable notes) joined by described links, plus reusable Skills and scheduled Workflows. It is shared across every agent and session connected to this workspace, and a human reviews changes and overall project state/context in a dashboard.',
256
+ 'WHY USE IT: pull durable project context instead of rediscovering it each session, and write hard-won learnings back so the next agent has them. Graph writes are reviewable proposals with full history — never silent database writes. Comments, session digests, digest claims, workflow-run recording, and clipboard copies are immediate audited side effects (not proposals).',
257
+ '',
258
+ 'SESSION LOOP:',
259
+ '1. Pick the target workspace — MCP OAuth is account-scoped, so one authorization can access consented workspaces only. If you do not know the target workspaceId, or the user says to switch/change/use another workspace, call brain_workspaces_list and choose by name, slug, or id. brain_workspace_overview may omit workspaceId when this authorization has a default workspace; keep later reads/writes explicit.',
260
+ '2. Orient — call brain_workspace_overview for the chosen workspace. It returns identity, review policy, graph health, pending review, due work, writeBackReminder, and ordered recommendedNextActions; follow them.',
261
+ '3. Pull — brain_node_search to find nodes by task intent, brain_graph_read for surrounding structure, brain_node_read for one node in full, brain_context_handoff to bundle selected nodes into a task packet. Resolve Skills (brain_skill_resolve) and due Workflows (brain_workflow_due) when the overview recommends them.',
262
+ '4. Do the work outside brain.',
263
+ '5. Write back — brain_change_propose for precise durable graph learnings (reviewable proposals); brain_session_digest only when the user asks you to capture unstructured session learnings as a pending digest (disclosed fallback, not an automatic dump); brain_tags_set to organize via proposals; brain_comment_create to leave the human a note or report after completing a user task or Workflow run (immediate); brain_workflow_record_run after running a due Workflow (immediate). To create and link new nodes in one proposal, pre-assign UUID targetIds on create_node and reference them from later operations.',
264
+ '',
265
+ 'CROSS-AGENT CLIPBOARD: brain_copy immediately stores the current relevant message/output (or something the user names) on an account-wide clipboard; brain_paste retrieves the latest clip or the last N (newest first, max 10). No workspaceId is required. Use this to move working context between agents or workspaces without manual copy/paste.',
266
+ '',
267
+ 'RULES: brain_change_propose is the ONLY way to write the graph — never assume direct mutation. Review-required proposals do not change the graph until a human approves them; auto-apply proposals still record full history. When targetRef is omitted, proposals follow the workspace activeRef returned by read tools; pass targetRef.type=live to force live, or targetRef.type=branch with branchId for a draft (always review-required). brain_digest_pending claims digests (mutating). Read tools redact hidden and encrypted-secret content; large responses truncate with a hint to narrow scope.',
268
+ '',
269
+ 'BE PROACTIVE: orient at session start without being asked, pull relevant context before non-trivial work, and offer to save durable decisions, conventions, and fixes back to brain after meaningful work. If the workspace is empty, offer to bootstrap it; if work is due or proposals await review, surface it to the user. MCP server instructions are a protocol hint — still call overview even if you already saw this text.',
270
+ '',
271
+ 'MODELING: use the brain://guide/modeling resource and the overview\'s modelingGuidelines when creating or reshaping graph structure.',
272
+ 'HANDLES: dashboard copy buttons produce brain://node/<id> and brain://comment/<id>; resolve node handles with brain_node_read and comment handles with brain_comment_list using commentId.',
273
+ 'REPORTING: use brain://guide/reporting before authoring rich HTML reports; post failure reports with outcome=\'failure\'.',
274
+ '',
275
+ `Guidance version: ${GUIDANCE_VERSION}`,
276
+ ].join('\n');
277
+ }
278
+ /** Precomputed server instructions string for MCP connect handshake. */
279
+ export const BRAIN_MCP_SERVER_INSTRUCTIONS = buildServerInstructions();
280
+ export function buildCanonicalWorkflowMarkdown(options) {
281
+ const task = options?.task?.trim();
282
+ const taskSection = task
283
+ ? [
284
+ '## Current task',
285
+ task,
286
+ '',
287
+ 'Before working on it: call `brain_workspace_overview`, then `brain_node_search` and/or `brain_context_handoff` for nodes relevant to this task. Resolve skills and due workflows when the overview recommends them.',
288
+ '',
289
+ ]
290
+ : [];
291
+ return [
292
+ '# brain session workflow',
293
+ '',
294
+ 'brain (alias **brainmcp**) is shared, persistent, reviewable project memory. Every tool is prefixed `brain_`.',
295
+ '',
296
+ ...taskSection,
297
+ '## Session loop',
298
+ '1. **Pick workspace** — OAuth is account-scoped and limited to consented workspaces. If `workspaceId` is unknown or the user wants to switch, call `brain_workspaces_list`. `brain_workspace_overview` may omit `workspaceId` when a default is configured; keep later reads/writes explicit.',
299
+ '2. **Orient** — `brain_workspace_overview` returns identity, review policy, graph health, pending review, due work, `writeBackReminder`, and `recommendedNextActions`. Follow them.',
300
+ '3. **Pull** — `brain_node_search` (intent/tags), `brain_graph_read` (structure), `brain_node_read` (one node), `brain_context_handoff` (task packet). Use `brain_skill_resolve` and `brain_workflow_due` when recommended.',
301
+ '4. **Work** — do the task outside brain.',
302
+ '5. **Write back** — `brain_change_propose` for precise durable graph learnings; `brain_session_digest` only when the user asks to capture unstructured session learnings (disclosed fallback, not an automatic dump); `brain_tags_set` to organize via proposals; `brain_comment_create` for human notes/reports; `brain_workflow_record_run` after a due workflow.',
303
+ '',
304
+ '## Cross-agent clipboard',
305
+ '- `brain_copy` — immediately store the last relevant message/output (or something the user names) on the account clipboard.',
306
+ '- `brain_paste` — retrieve the latest clip or the last N (newest first, max 10). No `workspaceId` required.',
307
+ '',
308
+ '## Rules',
309
+ '- `brain_change_propose` is the **only** graph write path — never assume silent mutation.',
310
+ '- Review-required proposals wait for human approval; auto-apply still records full history.',
311
+ '- Omit `targetRef` to follow workspace `activeRef` from reads; use `{ type: "live" }` to force live, or `{ type: "branch", branchId }` for a draft (always review-required).',
312
+ '- Comments, digest capture/claims, workflow-run recording, and clipboard writes are **immediate audited side effects**, not reviewable proposals.',
313
+ '- `brain_session_digest` never mutates graph memory directly; a later processing session turns candidates into reviewed suggested updates.',
314
+ '- `brain_digest_pending` is a mutating claim (not read-only).',
315
+ '- Reads redact hidden and encrypted-secret content; large responses truncate with narrowing hints.',
316
+ '',
317
+ '## First calls (typical)',
318
+ '`brain_workspaces_list` (if needed) → `brain_workspace_overview` → search / read / handoff → work → propose / tags / comments / workflow run',
319
+ '',
320
+ '## Handles and reports',
321
+ '- Resolve `brain://node/<id>` with `brain_node_read` and `brain://comment/<id>` with `brain_comment_list` using `commentId`.',
322
+ '- Use `brain://guide/reporting` before writing rich HTML reports. Scheduled workflow reports should be posted first with `targetNodeId=<workflowId>`, then linked from `brain_workflow_record_run` with `reportCommentId`.',
323
+ '',
324
+ '## Empty workspace',
325
+ 'If the graph is empty, offer to bootstrap: study the repo and propose initial Cores/Neurons/links in one reviewable `brain_change_propose`, using pre-assigned UUID `targetId`s for nodes referenced by same-proposal edges/tags/moves.',
326
+ '',
327
+ `Guidance version: ${GUIDANCE_VERSION}`,
328
+ ].join('\n');
329
+ }
@@ -0,0 +1,17 @@
1
+ import { type ClientId, type Scope } from '../lib/config.js';
2
+ export type CommandOptions = {
3
+ root: string;
4
+ client?: ClientId;
5
+ workspace?: string;
6
+ scope: Scope;
7
+ yes: boolean;
8
+ fetch?: typeof fetch;
9
+ confirm?: (message: string) => Promise<boolean>;
10
+ output: (message: string) => void;
11
+ };
12
+ export declare function detectClients(root: string): Promise<ClientId[]>;
13
+ export declare function chooseClient(options: CommandOptions): Promise<ClientId>;
14
+ export declare function init(options: CommandOptions): Promise<void>;
15
+ export declare function remove(options: CommandOptions): Promise<void>;
16
+ export declare function printConfig(options: CommandOptions): Promise<void>;
17
+ export declare function doctor(options: CommandOptions): Promise<void>;
@@ -0,0 +1,153 @@
1
+ import { homedir } from 'node:os';
2
+ import { join, resolve } from 'node:path';
3
+ import { BRAIN_MCP_ENDPOINT } from '../agent-config.js';
4
+ import { clientLabel, renderGuidance, resolveWorkspace, targetFor } from '../lib/config.js';
5
+ import { applyChanges, exists, readText, renderDiff, toAbsolute } from '../lib/files.js';
6
+ import { parseJsonc, removeJsonServer, removeMarkdownBlock, removeTomlBlock, upsertJsonServer, upsertMarkdownBlock, upsertTomlBlock } from '../lib/managed.js';
7
+ export async function detectClients(root) {
8
+ const candidates = [
9
+ ['cursor', ['.cursor', '.cursor/mcp.json']],
10
+ ['claude-code', ['CLAUDE.md', '.mcp.json']],
11
+ ['codex', ['.codex', '.codex/config.toml']],
12
+ ['vscode', ['.vscode', '.vscode/mcp.json']],
13
+ ];
14
+ const detected = [];
15
+ for (const [client, paths] of candidates) {
16
+ if (await Promise.all(paths.map((path) => exists(resolve(root, path)))).then((values) => values.some(Boolean)))
17
+ detected.push(client);
18
+ }
19
+ return detected;
20
+ }
21
+ export async function chooseClient(options) {
22
+ if (options.client)
23
+ return options.client;
24
+ const clients = await detectClients(options.root);
25
+ if (clients.length === 1)
26
+ return clients[0];
27
+ if (clients.length === 0)
28
+ return 'generic';
29
+ throw new Error(`Detected multiple clients (${clients.join(', ')}). Choose one with --client.`);
30
+ }
31
+ export async function init(options) {
32
+ const client = await chooseClient(options);
33
+ const workspace = resolveWorkspace(options.workspace);
34
+ const changes = await plannedChanges(options.root, client, options.scope, workspace, 'init');
35
+ await previewAndApply(changes, options);
36
+ options.output(`BrainMCP configured for ${clientLabel(client)}. OAuth is discovered at first client connection; no OAuth secret was written.`);
37
+ }
38
+ export async function remove(options) {
39
+ const client = await chooseClient(options);
40
+ const changes = await plannedChanges(options.root, client, options.scope, undefined, 'remove');
41
+ if (changes.every((change) => change.before === change.after)) {
42
+ options.output('No BrainMCP-managed configuration found.');
43
+ return;
44
+ }
45
+ await previewAndApply(changes, options);
46
+ options.output(`Removed BrainMCP-managed ${clientLabel(client)} configuration.`);
47
+ }
48
+ export async function printConfig(options) {
49
+ const client = await chooseClient(options);
50
+ const workspace = resolveWorkspace(options.workspace);
51
+ const target = resolveTarget(options.root, targetFor(client, options.scope), options.scope);
52
+ options.output(`# ${clientLabel(client)} (${options.scope} scope)\n\nMCP configuration: ${target.configPath}\n\n${target.format === 'toml' ? upsertTomlBlock(undefined, client) : upsertJsonServer(undefined, client, target.rootKey)}\nGuidance: ${target.guidancePath}\n\n${renderGuidance(client, workspace)}`);
53
+ }
54
+ export async function doctor(options) {
55
+ const client = await chooseClient(options);
56
+ const target = resolveTarget(options.root, targetFor(client, options.scope), options.scope);
57
+ const checks = [];
58
+ const config = await readText(target.configPath);
59
+ const guidance = await readText(target.guidancePath);
60
+ checks.push(['configuration placement', config !== undefined, config ? target.configPath : `missing ${target.configPath}`]);
61
+ checks.push(['guidance placement', guidance !== undefined, guidance ? target.guidancePath : `missing ${target.guidancePath}`]);
62
+ if (config !== undefined) {
63
+ try {
64
+ if (target.format === 'jsonc') {
65
+ const json = parseJsonc(config);
66
+ const server = json[target.rootKey]?.brain;
67
+ checks.push(['configuration syntax and endpoint', server?.url === BRAIN_MCP_ENDPOINT, server?.url ?? 'brain server missing']);
68
+ }
69
+ else {
70
+ checks.push(['configuration syntax and endpoint', /\[mcp_servers\.brain\][\s\S]*url\s*=\s*"https:\/\/mcp\.brainmcp\.ai\/mcp"/.test(config), 'brain TOML block']);
71
+ }
72
+ }
73
+ catch (error) {
74
+ checks.push(['configuration syntax and endpoint', false, error instanceof Error ? error.message : String(error)]);
75
+ }
76
+ }
77
+ if (guidance !== undefined) {
78
+ const occurrences = (guidance.match(/brainmcp:managed:start/g) ?? []).length;
79
+ checks.push(['managed guidance markers', occurrences === 1 || client === 'cursor', occurrences === 1 ? 'one managed block' : `${occurrences} managed blocks`]);
80
+ checks.push(['workspace binding', !options.workspace || guidance.includes(`Workspace id: ${resolveWorkspace(options.workspace)}`), options.workspace ? 'requested workspace binding' : 'no workspace requested']);
81
+ }
82
+ const fetchImpl = options.fetch ?? fetch;
83
+ try {
84
+ const discovery = await fetchImpl('https://mcp.brainmcp.ai/.well-known/oauth-authorization-server', { headers: { accept: 'application/json' } });
85
+ checks.push(['OAuth discovery', discovery.ok, `HTTP ${discovery.status}`]);
86
+ }
87
+ catch (error) {
88
+ checks.push(['OAuth discovery', false, error instanceof Error ? error.message : String(error)]);
89
+ }
90
+ try {
91
+ const challenge = await fetchImpl(BRAIN_MCP_ENDPOINT, { headers: { accept: 'application/json, text/event-stream' } });
92
+ checks.push(['endpoint challenge', challenge.ok || challenge.status === 401 || challenge.status === 405, `HTTP ${challenge.status}`]);
93
+ }
94
+ catch (error) {
95
+ checks.push(['endpoint challenge', false, error instanceof Error ? error.message : String(error)]);
96
+ }
97
+ const duplicate = await duplicateGuidance(options.root);
98
+ checks.push(['duplicate guidance', duplicate.length <= 1, duplicate.length ? duplicate.join(', ') : 'none found']);
99
+ for (const [name, passed, detail] of checks)
100
+ options.output(`${passed ? '✓' : '✗'} ${name}: ${detail}`);
101
+ if (checks.some(([, passed]) => !passed))
102
+ throw new Error('BrainMCP doctor found configuration issues.');
103
+ }
104
+ async function plannedChanges(root, client, scope, workspace, mode) {
105
+ const target = resolveTarget(root, targetFor(client, scope), scope);
106
+ const [config, guidance] = await Promise.all([readText(target.configPath), readText(target.guidancePath)]);
107
+ return [
108
+ {
109
+ path: target.configPath,
110
+ before: config,
111
+ after: mode === 'init'
112
+ ? target.format === 'toml' ? upsertTomlBlock(config, client) : upsertJsonServer(config, client, target.rootKey)
113
+ : target.format === 'toml' ? removeTomlBlock(config) : removeJsonServer(config, target.rootKey),
114
+ },
115
+ {
116
+ path: target.guidancePath,
117
+ before: guidance,
118
+ after: mode === 'init' ? upsertMarkdownBlock(guidance, renderGuidance(client, workspace)) : removeMarkdownBlock(guidance),
119
+ },
120
+ ];
121
+ }
122
+ function resolveTarget(root, target, scope) {
123
+ const base = scope === 'user' ? homedir() : root;
124
+ return {
125
+ ...target,
126
+ configPath: toAbsolute(base, target.configPath),
127
+ guidancePath: toAbsolute(base, target.guidancePath),
128
+ format: target.configFormat,
129
+ rootKey: target.configRootKey,
130
+ };
131
+ }
132
+ async function previewAndApply(changes, options) {
133
+ const changed = changes.filter((change) => change.before !== change.after);
134
+ if (!changed.length) {
135
+ options.output('BrainMCP configuration is already up to date.');
136
+ return;
137
+ }
138
+ options.output(changed.map(renderDiff).join('\n\n'));
139
+ if (!options.yes && !(await options.confirm?.('Apply these changes?') ?? false)) {
140
+ throw new Error('Aborted. Re-run with --yes after reviewing the preview.');
141
+ }
142
+ const backups = await applyChanges(changed);
143
+ options.output(backups.length ? `Backups: ${backups.join(', ')}` : 'Applied new files atomically.');
144
+ }
145
+ async function duplicateGuidance(root) {
146
+ const paths = ['CLAUDE.md', 'AGENTS.md', '.cursor/rules/brainmcp-workspace.mdc'];
147
+ const matches = [];
148
+ for (const path of paths)
149
+ if ((await readText(join(root, path)))?.includes('brainmcp:managed:start'))
150
+ matches.push(path);
151
+ return matches;
152
+ }
153
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+ import { argv, cwd, stdin, stdout } from 'node:process';
3
+ import { createInterface } from 'node:readline/promises';
4
+ import { doctor, init, printConfig, remove } from './commands/index.js';
5
+ import { assertClientId } from './lib/config.js';
6
+ const HELP = `brainmcp — configure BrainMCP for an agent project
7
+
8
+ Usage:
9
+ brainmcp init [--client <client>] [--workspace <id>] [--scope project|user] [--yes]
10
+ brainmcp doctor [--client <client>] [--workspace <id>] [--scope project|user]
11
+ brainmcp print [--client <client>] [--workspace <id>] [--scope project|user]
12
+ brainmcp remove [--client <client>] [--scope project|user] [--yes]
13
+
14
+ Clients: claude-code, cursor, codex, vscode, generic
15
+ Project scope is the default. User scope is explicit and writes under your home directory.
16
+ Every mutation previews a diff and needs confirmation unless --yes is supplied.`;
17
+ async function main() {
18
+ const [command, ...rawOptions] = argv.slice(2);
19
+ if (!command || command === '--help' || command === '-h' || command === 'help') {
20
+ console.log(HELP);
21
+ return;
22
+ }
23
+ const values = parseOptions(rawOptions);
24
+ const options = {
25
+ root: values.root ?? cwd(),
26
+ client: values.client,
27
+ workspace: values.workspace,
28
+ scope: values.scope ?? 'project',
29
+ yes: values.yes,
30
+ output: console.log,
31
+ confirm: confirm,
32
+ };
33
+ switch (command) {
34
+ case 'init':
35
+ await init(options);
36
+ return;
37
+ case 'doctor':
38
+ await doctor(options);
39
+ return;
40
+ case 'print':
41
+ await printConfig(options);
42
+ return;
43
+ case 'remove':
44
+ await remove(options);
45
+ return;
46
+ default:
47
+ throw new Error(`Unknown command: ${command}\n\n${HELP}`);
48
+ }
49
+ }
50
+ function parseOptions(args) {
51
+ const result = { yes: false };
52
+ for (let index = 0; index < args.length; index += 1) {
53
+ const option = args[index];
54
+ if (option === '--yes' || option === '-y')
55
+ result.yes = true;
56
+ else if (option === '--client')
57
+ result.client = assertClientId(readValue(args, ++index, option));
58
+ else if (option === '--workspace')
59
+ result.workspace = readValue(args, ++index, option);
60
+ else if (option === '--scope') {
61
+ const scope = readValue(args, ++index, option);
62
+ if (scope !== 'project' && scope !== 'user')
63
+ throw new Error('--scope must be project or user.');
64
+ result.scope = scope;
65
+ }
66
+ else if (option === '--root')
67
+ result.root = readValue(args, ++index, option);
68
+ else
69
+ throw new Error(`Unknown option: ${option}`);
70
+ }
71
+ return result;
72
+ }
73
+ function readValue(args, index, option) {
74
+ const value = args[index];
75
+ if (!value || value.startsWith('-'))
76
+ throw new Error(`${option} requires a value.`);
77
+ return value;
78
+ }
79
+ async function confirm(message) {
80
+ if (!stdin.isTTY || !stdout.isTTY)
81
+ return false;
82
+ const readline = createInterface({ input: stdin, output: stdout });
83
+ try {
84
+ return (await readline.question(`${message} [y/N] `)).trim().toLowerCase() === 'y';
85
+ }
86
+ finally {
87
+ readline.close();
88
+ }
89
+ }
90
+ main().catch((error) => {
91
+ console.error(error instanceof Error ? error.message : String(error));
92
+ process.exitCode = 1;
93
+ });
94
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { type McpClientProfileId } from '@braincp/agent-config';
2
+ export type ClientId = McpClientProfileId;
3
+ export type Scope = 'project' | 'user';
4
+ export declare const BRAIN_ENDPOINT = "https://mcp.brainmcp.ai/mcp";
5
+ export declare const CLIENT_IDS: readonly ClientId[];
6
+ export declare function assertClientId(value: string): ClientId;
7
+ export type ClientTarget = {
8
+ client: ClientId;
9
+ configPath: string;
10
+ guidancePath: string;
11
+ configFormat: 'jsonc' | 'toml';
12
+ /** Top-level server map key: VS Code uses `servers`, everything else `mcpServers`. */
13
+ configRootKey: 'mcpServers' | 'servers';
14
+ };
15
+ export declare const MARKER_START = "<!-- brainmcp:managed:start version=2026.07.10 -->";
16
+ export declare const MARKER_END = "<!-- brainmcp:managed:end -->";
17
+ export declare const TOML_MARKER_START = "# brainmcp:managed:start version=2026.07.10";
18
+ export declare const TOML_MARKER_END = "# brainmcp:managed:end";
19
+ export declare function targetFor(client: ClientId, scope: Scope): ClientTarget;
20
+ export declare function clientLabel(client: ClientId): string;
21
+ export declare function resolveWorkspace(value: string | undefined): string | undefined;
22
+ export declare function renderGuidance(client: ClientId, workspaceId?: string): string;
23
+ export declare function brainServerConfig(client: ClientId): Record<string, unknown>;
24
+ export declare function renderTomlServer(client: ClientId): string;
@@ -0,0 +1,105 @@
1
+ import { BRAIN_MCP_ENDPOINT, MCP_CLIENT_PROFILES, buildCodexConfigToml, buildProjectInstructionBlock, renderCursorMdc, } from '../agent-config.js';
2
+ export const BRAIN_ENDPOINT = BRAIN_MCP_ENDPOINT;
3
+ export const CLIENT_IDS = ['claude-code', 'cursor', 'codex', 'vscode', 'generic'];
4
+ export function assertClientId(value) {
5
+ if (CLIENT_IDS.includes(value))
6
+ return value;
7
+ throw new Error(`Unknown --client "${value}". Expected one of: ${CLIENT_IDS.join(', ')}.`);
8
+ }
9
+ export const MARKER_START = '<!-- brainmcp:managed:start version=2026.07.10 -->';
10
+ export const MARKER_END = '<!-- brainmcp:managed:end -->';
11
+ export const TOML_MARKER_START = '# brainmcp:managed:start version=2026.07.10';
12
+ export const TOML_MARKER_END = '# brainmcp:managed:end';
13
+ export function targetFor(client, scope) {
14
+ const home = scope === 'user';
15
+ const prefix = home ? '~/' : '';
16
+ switch (client) {
17
+ case 'cursor':
18
+ return {
19
+ client,
20
+ configPath: `${prefix}.cursor/mcp.json`,
21
+ guidancePath: `${prefix}.cursor/rules/brainmcp-workspace.mdc`,
22
+ configFormat: 'jsonc',
23
+ configRootKey: 'mcpServers',
24
+ };
25
+ case 'claude-code':
26
+ return {
27
+ client,
28
+ configPath: `${prefix}.mcp.json`,
29
+ guidancePath: `${prefix}CLAUDE.md`,
30
+ configFormat: 'jsonc',
31
+ configRootKey: 'mcpServers',
32
+ };
33
+ case 'codex':
34
+ return {
35
+ client,
36
+ configPath: `${prefix}.codex/config.toml`,
37
+ guidancePath: `${prefix}AGENTS.md`,
38
+ configFormat: 'toml',
39
+ configRootKey: 'mcpServers',
40
+ };
41
+ case 'vscode':
42
+ return {
43
+ client,
44
+ configPath: `${prefix}.vscode/mcp.json`,
45
+ guidancePath: `${prefix}AGENTS.md`,
46
+ configFormat: 'jsonc',
47
+ configRootKey: 'servers',
48
+ };
49
+ case 'generic':
50
+ return {
51
+ client,
52
+ configPath: `${prefix}.mcp.json`,
53
+ guidancePath: `${prefix}AGENTS.md`,
54
+ configFormat: 'jsonc',
55
+ configRootKey: 'mcpServers',
56
+ };
57
+ }
58
+ }
59
+ export function clientLabel(client) {
60
+ return MCP_CLIENT_PROFILES[client].label;
61
+ }
62
+ export function resolveWorkspace(value) {
63
+ const workspace = value?.trim();
64
+ if (!workspace)
65
+ return undefined;
66
+ if (!/^[a-zA-Z0-9_-]+$/.test(workspace)) {
67
+ throw new Error('--workspace must contain only letters, numbers, underscores, or hyphens.');
68
+ }
69
+ return workspace;
70
+ }
71
+ export function renderGuidance(client, workspaceId) {
72
+ if (client === 'cursor') {
73
+ const rendered = renderCursorMdc({ workspaceId });
74
+ const frontmatter = rendered.match(/^---\n[\s\S]*?\n---\n*/)?.[0];
75
+ if (!frontmatter)
76
+ throw new Error('Cursor guidance renderer did not return frontmatter.');
77
+ return `${frontmatter}\n${MARKER_START}\n${rendered.slice(frontmatter.length)}\n${MARKER_END}\n`;
78
+ }
79
+ return `${MARKER_START}\n${buildProjectInstructionBlock({ workspaceId })}\n${MARKER_END}\n`;
80
+ }
81
+ export function brainServerConfig(client) {
82
+ switch (client) {
83
+ case 'cursor':
84
+ case 'generic':
85
+ // Cursor's documented static-client shape; also the shape we document for
86
+ // generic clients that support a pre-registered CLIENT_ID.
87
+ return {
88
+ url: BRAIN_MCP_ENDPOINT,
89
+ auth: { CLIENT_ID: MCP_CLIENT_PROFILES[client].oauthClientId },
90
+ };
91
+ case 'claude-code':
92
+ case 'vscode':
93
+ case 'codex':
94
+ // URL-only: these clients complete OAuth via DCR with their own exact
95
+ // loopback callback; a static client id cannot match their random ports.
96
+ return { type: 'http', url: BRAIN_MCP_ENDPOINT };
97
+ }
98
+ }
99
+ export function renderTomlServer(client) {
100
+ // Codex is the only TOML client today; keep markers around the canonical block.
101
+ void client;
102
+ const body = buildCodexConfigToml().trimEnd();
103
+ return `${TOML_MARKER_START}\n${body}\n${TOML_MARKER_END}\n`;
104
+ }
105
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export type FileChange = {
2
+ path: string;
3
+ before: string | undefined;
4
+ after: string | undefined;
5
+ };
6
+ export declare function readText(path: string): Promise<string | undefined>;
7
+ export declare function exists(path: string): Promise<boolean>;
8
+ export declare function toAbsolute(root: string, path: string): string;
9
+ export declare function renderDiff(change: FileChange): string;
10
+ export declare function applyChanges(changes: FileChange[]): Promise<string[]>;
@@ -0,0 +1,58 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { access, copyFile, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
3
+ import { dirname, resolve } from 'node:path';
4
+ export async function readText(path) {
5
+ try {
6
+ return await readFile(path, 'utf8');
7
+ }
8
+ catch (error) {
9
+ if (isNotFound(error))
10
+ return undefined;
11
+ throw error;
12
+ }
13
+ }
14
+ export async function exists(path) {
15
+ try {
16
+ await access(path);
17
+ return true;
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ export function toAbsolute(root, path) {
24
+ return resolve(root, path.replace(/^~\//, ''));
25
+ }
26
+ export function renderDiff(change) {
27
+ const before = change.before?.split('\n') ?? [];
28
+ const after = change.after?.split('\n') ?? [];
29
+ const header = `--- ${change.before === undefined ? '/dev/null' : change.path}\n+++ ${change.after === undefined ? '/dev/null' : change.path}`;
30
+ const removed = before.filter((line) => !after.includes(line)).map((line) => `-${line}`);
31
+ const added = after.filter((line) => !before.includes(line)).map((line) => `+${line}`);
32
+ return [header, ...removed, ...added].join('\n');
33
+ }
34
+ export async function applyChanges(changes) {
35
+ const backups = [];
36
+ for (const change of changes) {
37
+ if (change.before === change.after)
38
+ continue;
39
+ await mkdir(dirname(change.path), { recursive: true });
40
+ if (change.before !== undefined && (await exists(change.path))) {
41
+ const backup = `${change.path}.brainmcp.bak-${Date.now()}-${createHash('sha1').update(change.path).digest('hex').slice(0, 6)}`;
42
+ await copyFile(change.path, backup);
43
+ backups.push(backup);
44
+ }
45
+ if (change.after === undefined) {
46
+ await rm(change.path, { force: true });
47
+ continue;
48
+ }
49
+ const temporary = `${change.path}.brainmcp-tmp-${randomUUID()}`;
50
+ await writeFile(temporary, change.after, 'utf8');
51
+ await rename(temporary, change.path);
52
+ }
53
+ return backups;
54
+ }
55
+ function isNotFound(error) {
56
+ return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';
57
+ }
58
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/lib/files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { type ClientId } from './config.js';
2
+ export declare function hasManagedBlock(text: string, format: 'markdown' | 'toml'): boolean;
3
+ export declare function assertNoConflictingManagedBlock(text: string, format: 'markdown' | 'toml'): void;
4
+ export declare function upsertMarkdownBlock(existing: string | undefined, block: string): string;
5
+ export declare function removeMarkdownBlock(existing: string | undefined): string | undefined;
6
+ export declare function upsertTomlBlock(existing: string | undefined, client: ClientId): string;
7
+ export declare function removeTomlBlock(existing: string | undefined): string | undefined;
8
+ export declare function upsertJsonServer(existing: string | undefined, client: ClientId, rootKey?: 'mcpServers' | 'servers'): string;
9
+ export declare function removeJsonServer(existing: string | undefined, rootKey?: 'mcpServers' | 'servers'): string | undefined;
10
+ export declare function parseJsonc(source: string): Record<string, unknown>;
@@ -0,0 +1,139 @@
1
+ import { BRAIN_ENDPOINT, brainServerConfig, renderTomlServer, MARKER_END, MARKER_START, TOML_MARKER_END, TOML_MARKER_START } from './config.js';
2
+ export function hasManagedBlock(text, format) {
3
+ return text.includes(format === 'toml' ? TOML_MARKER_START : MARKER_START);
4
+ }
5
+ export function assertNoConflictingManagedBlock(text, format) {
6
+ const [start, end] = format === 'toml' ? [TOML_MARKER_START, TOML_MARKER_END] : [MARKER_START, MARKER_END];
7
+ const startCount = text.split(start).length - 1;
8
+ const endCount = text.split(end).length - 1;
9
+ if ((startCount !== 0 || endCount !== 0) && (startCount !== 1 || endCount !== 1 || text.indexOf(end) < text.indexOf(start))) {
10
+ throw new Error(`Refusing to modify conflicting manual BrainMCP markers. Repair ${start} / ${end} first.`);
11
+ }
12
+ }
13
+ export function upsertMarkdownBlock(existing, block) {
14
+ const current = existing ?? '';
15
+ assertNoConflictingManagedBlock(current, 'markdown');
16
+ if (hasManagedBlock(current, 'markdown')) {
17
+ return current.replace(new RegExp(`${escapeRegex(MARKER_START)}[\\s\\S]*?${escapeRegex(MARKER_END)}\\n?`), block);
18
+ }
19
+ return `${current}${current && !current.endsWith('\n') ? '\n' : ''}${current ? '\n' : ''}${block}`;
20
+ }
21
+ export function removeMarkdownBlock(existing) {
22
+ if (existing === undefined)
23
+ return undefined;
24
+ assertNoConflictingManagedBlock(existing, 'markdown');
25
+ const result = existing.replace(new RegExp(`\\n?${escapeRegex(MARKER_START)}[\\s\\S]*?${escapeRegex(MARKER_END)}\\n?`), '').replace(/\n{3,}/g, '\n\n');
26
+ return result.trim() ? result : undefined;
27
+ }
28
+ export function upsertTomlBlock(existing, client) {
29
+ const current = existing ?? '';
30
+ assertNoConflictingManagedBlock(current, 'toml');
31
+ const block = renderTomlServer(client);
32
+ if (hasManagedBlock(current, 'toml')) {
33
+ return current.replace(new RegExp(`${escapeRegex(TOML_MARKER_START)}[\\s\\S]*?${escapeRegex(TOML_MARKER_END)}\\n?`), block);
34
+ }
35
+ if (/\[mcp_servers\.brain\]/.test(current)) {
36
+ throw new Error('Refusing to overwrite a manually-managed [mcp_servers.brain] block. Remove it or use BrainMCP markers.');
37
+ }
38
+ return `${current}${current && !current.endsWith('\n') ? '\n' : ''}${current ? '\n' : ''}${block}`;
39
+ }
40
+ export function removeTomlBlock(existing) {
41
+ if (existing === undefined)
42
+ return undefined;
43
+ assertNoConflictingManagedBlock(existing, 'toml');
44
+ const result = existing.replace(new RegExp(`\\n?${escapeRegex(TOML_MARKER_START)}[\\s\\S]*?${escapeRegex(TOML_MARKER_END)}\\n?`), '').replace(/\n{3,}/g, '\n\n');
45
+ return result.trim() ? result : undefined;
46
+ }
47
+ export function upsertJsonServer(existing, client, rootKey = 'mcpServers') {
48
+ const source = existing?.trim() || `{\n "${rootKey}": {}\n}\n`;
49
+ const parsed = parseJsonc(source);
50
+ const servers = parsed[rootKey];
51
+ if (servers !== undefined && (typeof servers !== 'object' || servers === null || Array.isArray(servers))) {
52
+ throw new Error(`${rootKey} must be an object.`);
53
+ }
54
+ const existingBrain = servers?.brain;
55
+ if (existingBrain !== undefined && !isBrainConfig(existingBrain)) {
56
+ throw new Error('Refusing to overwrite a manually-managed "brain" MCP server entry.');
57
+ }
58
+ if (existingBrain !== undefined)
59
+ return source;
60
+ if (servers !== undefined)
61
+ return insertJsonServer(source, brainServerConfig(client), rootKey);
62
+ const next = { ...parsed, [rootKey]: { brain: brainServerConfig(client) } };
63
+ return `${JSON.stringify(next, null, 2)}\n`;
64
+ }
65
+ export function removeJsonServer(existing, rootKey = 'mcpServers') {
66
+ if (existing === undefined)
67
+ return undefined;
68
+ const parsed = parseJsonc(existing);
69
+ const parsedServers = parsed[rootKey];
70
+ if (!parsedServers || typeof parsedServers !== 'object' || Array.isArray(parsedServers))
71
+ return existing;
72
+ const servers = { ...parsedServers };
73
+ if (!isBrainConfig(servers.brain)) {
74
+ if (servers.brain !== undefined)
75
+ throw new Error('Refusing to remove a manually-managed "brain" MCP server entry.');
76
+ return existing;
77
+ }
78
+ delete servers.brain;
79
+ const next = { ...parsed, [rootKey]: servers };
80
+ if (Object.keys(next).length === 1 && Object.keys(servers).length === 0)
81
+ return undefined;
82
+ return `${JSON.stringify(next, null, 2)}\n`;
83
+ }
84
+ export function parseJsonc(source) {
85
+ try {
86
+ return JSON.parse(source.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/.*$/gm, '$1').replace(/,\s*([}\]])/g, '$1'));
87
+ }
88
+ catch (error) {
89
+ throw new Error(`Invalid JSON/JSONC configuration: ${error instanceof Error ? error.message : String(error)}`);
90
+ }
91
+ }
92
+ function isBrainConfig(value) {
93
+ return typeof value === 'object' && value !== null && value.url === BRAIN_ENDPOINT;
94
+ }
95
+ function escapeRegex(value) {
96
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
97
+ }
98
+ /** Adds a server without reformatting existing JSONC comments or sibling entries. */
99
+ function insertJsonServer(source, server, rootKey) {
100
+ const match = new RegExp(`"${rootKey}"\\s*:\\s*\\{`).exec(source);
101
+ if (!match)
102
+ throw new Error(`Could not locate the ${rootKey} object.`);
103
+ const open = source.indexOf('{', match.index);
104
+ const close = matchingBrace(source, open);
105
+ const inner = source.slice(open + 1, close);
106
+ const baseIndent = source.match(new RegExp(`\\n(\\s+)"${rootKey}"`))?.[1] ?? '';
107
+ const entryIndent = `${baseIndent} `;
108
+ const rendered = `"brain": ${JSON.stringify(server, null, 2).replace(/\n/g, `\n${entryIndent}`)}`;
109
+ if (!inner.trim())
110
+ return `${source.slice(0, open + 1)}\n${entryIndent}${rendered}\n${baseIndent}${source.slice(close)}`;
111
+ const end = open + 1 + inner.search(/\s*$/);
112
+ const comma = source.slice(open + 1, end).trimEnd().endsWith(',') ? '' : ',';
113
+ return `${source.slice(0, end)}${comma}\n${entryIndent}${rendered}${source.slice(end)}`;
114
+ }
115
+ function matchingBrace(source, open) {
116
+ let depth = 0;
117
+ let quote = false;
118
+ let escaped = false;
119
+ for (let index = open; index < source.length; index += 1) {
120
+ const character = source[index];
121
+ if (quote) {
122
+ if (escaped)
123
+ escaped = false;
124
+ else if (character === '\\')
125
+ escaped = true;
126
+ else if (character === '"')
127
+ quote = false;
128
+ continue;
129
+ }
130
+ if (character === '"')
131
+ quote = true;
132
+ else if (character === '{')
133
+ depth += 1;
134
+ else if (character === '}' && --depth === 0)
135
+ return index;
136
+ }
137
+ throw new Error('Unterminated mcpServers object.');
138
+ }
139
+ //# sourceMappingURL=managed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"managed.js","sourceRoot":"","sources":["../../src/lib/managed.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /** Emit stub — replaced by bundled agent-config.js after `tsc`. */
2
+ export type McpClientProfileId = 'claude-code' | 'cursor' | 'codex' | 'vscode' | 'generic';
3
+ export declare const BRAIN_MCP_ENDPOINT = "https://mcp.brainmcp.ai/mcp";
4
+ export declare const GUIDANCE_VERSION = "2026.07.10";
5
+ export declare const MCP_CLIENT_PROFILES: Record<McpClientProfileId, {
6
+ id: McpClientProfileId;
7
+ label: string;
8
+ oauthClientId?: string;
9
+ supportsProjectInstructions: boolean;
10
+ supportsPlugins: boolean;
11
+ supportsHooks: boolean;
12
+ notes: string;
13
+ }>;
14
+ export declare function buildProjectInstructionBlock(_options?: {
15
+ workspaceId?: string;
16
+ includeEndpoint?: boolean;
17
+ }): string;
18
+ export declare function renderCursorMdc(_options?: {
19
+ workspaceId?: string;
20
+ alwaysApply?: boolean;
21
+ }): string;
22
+ export declare function buildCursorMcpJson(_options?: {
23
+ oauthClientId?: string;
24
+ }): string;
25
+ export declare function buildClaudeCodeMcpAddCommand(): string;
26
+ export declare function buildBrainmcpInitCommand(_options?: {
27
+ workspaceId?: string;
28
+ client?: McpClientProfileId;
29
+ }): string;
30
+ export declare function buildAgentSkillMarkdown(_options?: {
31
+ workspaceId?: string;
32
+ }): string;
33
+ export declare function buildCodexConfigToml(): string;
@@ -0,0 +1,25 @@
1
+ export const BRAIN_MCP_ENDPOINT = 'https://mcp.brainmcp.ai/mcp';
2
+ export const GUIDANCE_VERSION = '2026.07.10';
3
+ export const MCP_CLIENT_PROFILES = {};
4
+ export function buildProjectInstructionBlock(_options) {
5
+ return '';
6
+ }
7
+ export function renderCursorMdc(_options) {
8
+ return '';
9
+ }
10
+ export function buildCursorMcpJson(_options) {
11
+ return '{}';
12
+ }
13
+ export function buildClaudeCodeMcpAddCommand() {
14
+ return '';
15
+ }
16
+ export function buildBrainmcpInitCommand(_options) {
17
+ return '';
18
+ }
19
+ export function buildAgentSkillMarkdown(_options) {
20
+ return '';
21
+ }
22
+ export function buildCodexConfigToml() {
23
+ return '';
24
+ }
25
+ //# sourceMappingURL=agent-config-stub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-config-stub.js","sourceRoot":"","sources":["../../src/types/agent-config-stub.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,MAAM,kBAAkB,GAAG,6BAA6B,CAAC;AAChE,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC;AAC7C,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAWlC,CAAC;AAEF,MAAM,UAAU,4BAA4B,CAAC,QAG5C;IACC,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,QAA0D;IACxF,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,QAAqC;IACtE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC1C,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,QAGxC;IACC,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,QAAmC;IACzE,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,oBAAoB;IAClC,OAAO,EAAE,CAAC;AACZ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@brainmcp/brainmcp",
3
+ "version": "0.1.0",
4
+ "description": "Safely configure BrainMCP project guidance and MCP connections",
5
+ "type": "module",
6
+ "bin": {
7
+ "brainmcp": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "exports": {
13
+ ".": "./dist/index.js"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "scripts": {
19
+ "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json && node scripts/bundle-agent-config.mjs && chmod +x dist/index.js",
20
+ "typecheck": "tsc --noEmit -p tsconfig.json",
21
+ "test": "node --import tsx --test 'src/**/*.test.ts'",
22
+ "prepublishOnly": "pnpm run build"
23
+ },
24
+ "devDependencies": {
25
+ "@braincp/agent-config": "workspace:*",
26
+ "@types/node": "^26.1.0",
27
+ "tsx": "^4.23.0",
28
+ "typescript": "6.0.3"
29
+ },
30
+ "engines": {
31
+ "node": ">=22"
32
+ },
33
+ "license": "MIT"
34
+ }