@blockrun/franklin 3.23.1 → 3.24.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/dist/commands/start.js +13 -1
- package/dist/mcp/client.d.ts +11 -0
- package/dist/mcp/client.js +32 -1
- package/dist/mcp/codegraph.d.ts +45 -0
- package/dist/mcp/codegraph.js +105 -0
- package/dist/mcp/config.js +8 -0
- package/package.json +2 -1
package/dist/commands/start.js
CHANGED
|
@@ -15,7 +15,8 @@ import { validateToolDescriptions } from '../tools/validate.js';
|
|
|
15
15
|
import { launchInkUI } from '../ui/app.js';
|
|
16
16
|
import { pickModel, resolveModel } from '../ui/model-picker.js';
|
|
17
17
|
import { loadMcpConfig } from '../mcp/config.js';
|
|
18
|
-
import { connectMcpServers, disconnectMcpServers } from '../mcp/client.js';
|
|
18
|
+
import { connectMcpServers, disconnectMcpServers, getMcpServerInstructions } from '../mcp/client.js';
|
|
19
|
+
import { ensureCodegraphIndex } from '../mcp/codegraph.js';
|
|
19
20
|
export async function startCommand(options) {
|
|
20
21
|
const version = options.version ?? '1.0.0';
|
|
21
22
|
// Early-validate explicit resume ID so a typo fails fast — before wallet
|
|
@@ -282,6 +283,10 @@ export async function startCommand(options) {
|
|
|
282
283
|
const systemInstructions = assembleInstructions(workDir, model);
|
|
283
284
|
// Connect MCP servers (non-blocking — add tools if servers are available)
|
|
284
285
|
const mcpConfig = loadMcpConfig(workDir);
|
|
286
|
+
// Kick off the CodeGraph index build (no-op if disabled/absent/already built).
|
|
287
|
+
// Runs in the background so it never blocks startup; the agent falls back to
|
|
288
|
+
// grep/read until the index is ready.
|
|
289
|
+
ensureCodegraphIndex(workDir);
|
|
285
290
|
let mcpTools = [];
|
|
286
291
|
const mcpServerCount = Object.keys(mcpConfig.mcpServers).filter(k => !mcpConfig.mcpServers[k].disabled).length;
|
|
287
292
|
if (mcpServerCount > 0) {
|
|
@@ -290,6 +295,13 @@ export async function startCommand(options) {
|
|
|
290
295
|
if (mcpTools.length > 0) {
|
|
291
296
|
console.log(chalk.dim(` MCP: ${mcpTools.length} tools from ${mcpServerCount} server(s)`));
|
|
292
297
|
}
|
|
298
|
+
// Fold each connected server's playbook (from its initialize response)
|
|
299
|
+
// into the system prompt. For CodeGraph this is what drives the agent to
|
|
300
|
+
// query the index instead of looping grep — the bulk of the savings.
|
|
301
|
+
const mcpInstructions = getMcpServerInstructions();
|
|
302
|
+
if (mcpInstructions) {
|
|
303
|
+
systemInstructions.push(mcpInstructions);
|
|
304
|
+
}
|
|
293
305
|
}
|
|
294
306
|
catch (err) {
|
|
295
307
|
if (options.debug) {
|
package/dist/mcp/client.d.ts
CHANGED
|
@@ -34,6 +34,17 @@ export declare function connectMcpServers(config: McpConfig, debug?: boolean): P
|
|
|
34
34
|
* Disconnect all MCP servers.
|
|
35
35
|
*/
|
|
36
36
|
export declare function disconnectMcpServers(): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Aggregate server-level instructions from all connected MCP servers into a
|
|
39
|
+
* single system-prompt section, or '' if no server supplied any.
|
|
40
|
+
*
|
|
41
|
+
* These come from the `initialize` response of servers Franklin chose to
|
|
42
|
+
* connect (built-in, or user-configured + trusted), so they're treated as
|
|
43
|
+
* trusted guidance rather than untrusted data. The agent reads this once per
|
|
44
|
+
* session to learn each toolset's playbook (which tool for which question,
|
|
45
|
+
* common chains, anti-patterns) instead of rediscovering it by trial.
|
|
46
|
+
*/
|
|
47
|
+
export declare function getMcpServerInstructions(): string;
|
|
37
48
|
/**
|
|
38
49
|
* List connected MCP servers and their tools.
|
|
39
50
|
*/
|
package/dist/mcp/client.js
CHANGED
|
@@ -151,7 +151,12 @@ async function connectStdio(name, config) {
|
|
|
151
151
|
catch {
|
|
152
152
|
// Server doesn't support resources — that's fine, tools-only mode
|
|
153
153
|
}
|
|
154
|
-
|
|
154
|
+
// Server-level instructions from the initialize response. MCP servers use
|
|
155
|
+
// this to tell the agent HOW to use their tools (selection-by-intent, common
|
|
156
|
+
// chains, anti-patterns) — e.g. CodeGraph's "answer directly, don't grep to
|
|
157
|
+
// re-verify" playbook, which is where most of its tool-call savings come from.
|
|
158
|
+
const instructions = (client.getInstructions() || '').trim() || undefined;
|
|
159
|
+
const connected = { name, client, transport, tools: capabilities, instructions };
|
|
155
160
|
connections.set(name, connected);
|
|
156
161
|
return connected;
|
|
157
162
|
}
|
|
@@ -210,6 +215,32 @@ export async function disconnectMcpServers() {
|
|
|
210
215
|
connections.delete(name);
|
|
211
216
|
}
|
|
212
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Aggregate server-level instructions from all connected MCP servers into a
|
|
220
|
+
* single system-prompt section, or '' if no server supplied any.
|
|
221
|
+
*
|
|
222
|
+
* These come from the `initialize` response of servers Franklin chose to
|
|
223
|
+
* connect (built-in, or user-configured + trusted), so they're treated as
|
|
224
|
+
* trusted guidance rather than untrusted data. The agent reads this once per
|
|
225
|
+
* session to learn each toolset's playbook (which tool for which question,
|
|
226
|
+
* common chains, anti-patterns) instead of rediscovering it by trial.
|
|
227
|
+
*/
|
|
228
|
+
export function getMcpServerInstructions() {
|
|
229
|
+
const blocks = [];
|
|
230
|
+
for (const [name, conn] of connections) {
|
|
231
|
+
if (conn.instructions) {
|
|
232
|
+
blocks.push(`### MCP server: ${name}\n${conn.instructions}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (blocks.length === 0)
|
|
236
|
+
return '';
|
|
237
|
+
return [
|
|
238
|
+
'## Connected MCP tool playbooks',
|
|
239
|
+
'Each connected MCP server below provides guidance on how to use its tools effectively. Follow these playbooks when those tools are relevant.',
|
|
240
|
+
'',
|
|
241
|
+
blocks.join('\n\n'),
|
|
242
|
+
].join('\n');
|
|
243
|
+
}
|
|
213
244
|
/**
|
|
214
245
|
* List connected MCP servers and their tools.
|
|
215
246
|
*/
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodeGraph built-in MCP server integration.
|
|
3
|
+
*
|
|
4
|
+
* CodeGraph (https://github.com/colbymchenry/codegraph, MIT) builds a local
|
|
5
|
+
* SQLite knowledge graph of a repo's symbols, call edges, and files via
|
|
6
|
+
* tree-sitter, then serves it over MCP. For Franklin this is a direct USDC win:
|
|
7
|
+
* agents answer "how does X work / what calls Y / trace this flow" from the
|
|
8
|
+
* pre-built index instead of looping grep + read, which cuts tool calls (and
|
|
9
|
+
* therefore paid LLM round-trips) sharply on real codebases.
|
|
10
|
+
*
|
|
11
|
+
* Shipped as a dependency, so `franklin` users get it with no extra install.
|
|
12
|
+
* The npm package is a thin shim (`npm-shim.js`) that locates a per-platform
|
|
13
|
+
* bundle (vendored Node 24 + app) and execs it — so we always launch it via
|
|
14
|
+
* the user's own node against the shim, never a global `codegraph` on PATH.
|
|
15
|
+
*
|
|
16
|
+
* Opt out with FRANKLIN_CODEGRAPH=0 (or "false").
|
|
17
|
+
*/
|
|
18
|
+
import type { McpServerConfig } from './client.js';
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the CodeGraph npm shim entry point, or null if the dependency
|
|
21
|
+
* isn't installed / resolvable. The shim is plain JS runnable by any node.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveCodegraphShim(): string | null;
|
|
24
|
+
/** Whether CodeGraph is available and not disabled. */
|
|
25
|
+
export declare function isCodegraphEnabled(): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Build the built-in MCP server config for CodeGraph, pinned to `workDir`.
|
|
28
|
+
*
|
|
29
|
+
* We launch via the user's node + the shim (the shim re-execs the bundled
|
|
30
|
+
* Node 24 runtime internally). `--path` is required because Franklin's MCP
|
|
31
|
+
* client doesn't advertise a `roots` capability, so the server can't infer
|
|
32
|
+
* the project from a rootUri — without it CodeGraph wouldn't know which repo
|
|
33
|
+
* to index. Returns null when CodeGraph is unavailable or disabled.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getCodegraphServerConfig(workDir: string): McpServerConfig | null;
|
|
36
|
+
/**
|
|
37
|
+
* Build the initial index for `workDir` if it has no `.codegraph/` yet.
|
|
38
|
+
*
|
|
39
|
+
* Non-blocking: spawns `codegraph init <workDir> -i` detached and returns
|
|
40
|
+
* immediately. The serving MCP process watches the project, so it picks up
|
|
41
|
+
* the freshly built index; until it's ready, codegraph tools report
|
|
42
|
+
* "not initialized" and the agent falls back to grep/read (no regression).
|
|
43
|
+
* No-op when CodeGraph is disabled, unavailable, or already initialized.
|
|
44
|
+
*/
|
|
45
|
+
export declare function ensureCodegraphIndex(workDir: string): void;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodeGraph built-in MCP server integration.
|
|
3
|
+
*
|
|
4
|
+
* CodeGraph (https://github.com/colbymchenry/codegraph, MIT) builds a local
|
|
5
|
+
* SQLite knowledge graph of a repo's symbols, call edges, and files via
|
|
6
|
+
* tree-sitter, then serves it over MCP. For Franklin this is a direct USDC win:
|
|
7
|
+
* agents answer "how does X work / what calls Y / trace this flow" from the
|
|
8
|
+
* pre-built index instead of looping grep + read, which cuts tool calls (and
|
|
9
|
+
* therefore paid LLM round-trips) sharply on real codebases.
|
|
10
|
+
*
|
|
11
|
+
* Shipped as a dependency, so `franklin` users get it with no extra install.
|
|
12
|
+
* The npm package is a thin shim (`npm-shim.js`) that locates a per-platform
|
|
13
|
+
* bundle (vendored Node 24 + app) and execs it — so we always launch it via
|
|
14
|
+
* the user's own node against the shim, never a global `codegraph` on PATH.
|
|
15
|
+
*
|
|
16
|
+
* Opt out with FRANKLIN_CODEGRAPH=0 (or "false").
|
|
17
|
+
*/
|
|
18
|
+
import { createRequire } from 'node:module';
|
|
19
|
+
import { spawn } from 'node:child_process';
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import { logger } from '../logger.js';
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
24
|
+
/** True unless the user explicitly disabled CodeGraph via env. */
|
|
25
|
+
function userEnabled() {
|
|
26
|
+
const v = (process.env.FRANKLIN_CODEGRAPH || '').toLowerCase();
|
|
27
|
+
return v !== '0' && v !== 'false' && v !== 'off';
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the CodeGraph npm shim entry point, or null if the dependency
|
|
31
|
+
* isn't installed / resolvable. The shim is plain JS runnable by any node.
|
|
32
|
+
*/
|
|
33
|
+
export function resolveCodegraphShim() {
|
|
34
|
+
try {
|
|
35
|
+
const shim = require.resolve('@colbymchenry/codegraph/npm-shim.js');
|
|
36
|
+
return fs.existsSync(shim) ? shim : null;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Whether CodeGraph is available and not disabled. */
|
|
43
|
+
export function isCodegraphEnabled() {
|
|
44
|
+
return userEnabled() && resolveCodegraphShim() !== null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Build the built-in MCP server config for CodeGraph, pinned to `workDir`.
|
|
48
|
+
*
|
|
49
|
+
* We launch via the user's node + the shim (the shim re-execs the bundled
|
|
50
|
+
* Node 24 runtime internally). `--path` is required because Franklin's MCP
|
|
51
|
+
* client doesn't advertise a `roots` capability, so the server can't infer
|
|
52
|
+
* the project from a rootUri — without it CodeGraph wouldn't know which repo
|
|
53
|
+
* to index. Returns null when CodeGraph is unavailable or disabled.
|
|
54
|
+
*/
|
|
55
|
+
export function getCodegraphServerConfig(workDir) {
|
|
56
|
+
if (!userEnabled())
|
|
57
|
+
return null;
|
|
58
|
+
const shim = resolveCodegraphShim();
|
|
59
|
+
if (!shim)
|
|
60
|
+
return null;
|
|
61
|
+
return {
|
|
62
|
+
transport: 'stdio',
|
|
63
|
+
command: process.execPath,
|
|
64
|
+
args: [shim, 'serve', '--mcp', '--path', workDir],
|
|
65
|
+
label: 'CodeGraph (built-in)',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Build the initial index for `workDir` if it has no `.codegraph/` yet.
|
|
70
|
+
*
|
|
71
|
+
* Non-blocking: spawns `codegraph init <workDir> -i` detached and returns
|
|
72
|
+
* immediately. The serving MCP process watches the project, so it picks up
|
|
73
|
+
* the freshly built index; until it's ready, codegraph tools report
|
|
74
|
+
* "not initialized" and the agent falls back to grep/read (no regression).
|
|
75
|
+
* No-op when CodeGraph is disabled, unavailable, or already initialized.
|
|
76
|
+
*/
|
|
77
|
+
export function ensureCodegraphIndex(workDir) {
|
|
78
|
+
if (!isCodegraphEnabled())
|
|
79
|
+
return;
|
|
80
|
+
const indexDir = path.join(workDir, '.codegraph');
|
|
81
|
+
if (fs.existsSync(indexDir))
|
|
82
|
+
return; // already initialized — watcher keeps it fresh
|
|
83
|
+
const shim = resolveCodegraphShim();
|
|
84
|
+
if (!shim)
|
|
85
|
+
return;
|
|
86
|
+
try {
|
|
87
|
+
const child = spawn(process.execPath, [shim, 'init', workDir, '-i'], {
|
|
88
|
+
cwd: workDir,
|
|
89
|
+
// Discard output: this is best-effort background indexing. Failures are
|
|
90
|
+
// non-fatal — the agent simply keeps using grep/read until (and if) the
|
|
91
|
+
// index appears. Surfacing a stack trace here would just be noise.
|
|
92
|
+
stdio: 'ignore',
|
|
93
|
+
detached: true,
|
|
94
|
+
});
|
|
95
|
+
child.on('error', (err) => {
|
|
96
|
+
logger.debug(`[franklin] codegraph index build failed: ${err.message}`);
|
|
97
|
+
});
|
|
98
|
+
// Don't keep the event loop alive waiting on the indexer.
|
|
99
|
+
child.unref();
|
|
100
|
+
logger.info(`[franklin] CodeGraph: building initial index for ${workDir}`);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
logger.debug(`[franklin] codegraph index spawn error: ${err.message}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
package/dist/mcp/config.js
CHANGED
|
@@ -8,6 +8,7 @@ import fs from 'node:fs';
|
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { execSync } from 'node:child_process';
|
|
10
10
|
import { BLOCKRUN_DIR } from '../config.js';
|
|
11
|
+
import { getCodegraphServerConfig } from './codegraph.js';
|
|
11
12
|
const GLOBAL_MCP_FILE = path.join(BLOCKRUN_DIR, 'mcp.json');
|
|
12
13
|
/**
|
|
13
14
|
* Load MCP server configurations from global + project files.
|
|
@@ -46,6 +47,13 @@ export function loadMcpConfig(workDir) {
|
|
|
46
47
|
servers[name] = config;
|
|
47
48
|
}
|
|
48
49
|
}
|
|
50
|
+
// Built-in CodeGraph: shipped as a dependency (not on PATH), so it's
|
|
51
|
+
// resolved + pinned to this workDir rather than probed via `which`.
|
|
52
|
+
// User config below can still override or disable it.
|
|
53
|
+
const codegraph = getCodegraphServerConfig(workDir);
|
|
54
|
+
if (codegraph) {
|
|
55
|
+
servers.codegraph = codegraph;
|
|
56
|
+
}
|
|
49
57
|
// 1. Global config
|
|
50
58
|
try {
|
|
51
59
|
if (fs.existsSync(GLOBAL_MCP_FILE)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/franklin",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.24.0",
|
|
4
4
|
"description": "Franklin Agent — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@blockrun/llm": "^2.0.0",
|
|
70
|
+
"@colbymchenry/codegraph": "^0.9.7",
|
|
70
71
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
71
72
|
"@solana/spl-token": "^0.4.14",
|
|
72
73
|
"@solana/web3.js": "^1.98.4",
|