mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-11-14 16:52:09 +08:00
feat(serve): --no-telemetry turns telemetry off for one MCP server entry
A team that shares an MCP config had no way to opt one server entry out of telemetry without an environment variable, which not every client lets a shared config set. Issue 1908 asked for a flag. serve --no-telemetry sets CODEGRAPH_TELEMETRY=0 for the process, the same chokepoint --no-watch uses for CODEGRAPH_NO_WATCH, so there is no new telemetry code path: telemetry reads the variable on every check, and the background daemon a server spawns inherits its environment. A daemon that is already running keeps its own setting until it exits, which TELEMETRY.md states; codegraph telemetry off covers every process. Changes on top of the contributor's commit: the test spawns the server with the WASM runtime flags, as AGENTS.md requires on Windows (without them the re-exec'd grandchild keeps the temp dir open past the child's exit), and the changelog bullet states the limit and credits the author. Validated with real proxy-to-daemon runs on Windows against opted-in private homes, reading the telemetry queue each daemon left on exit: - a flagged client starts the daemon: nothing recorded; - control, no flag: the tool call is recorded; - a flagged client joins a running unflagged daemon: its call is recorded, the documented limit; - an unflagged client joins a flagged daemon: nothing recorded; - codegraph telemetry off with a daemon running: nothing recorded. Original PR #1923 by @danusha2345. Fixes #1908 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Colby Mchenry
co-authored by
Claude Opus 5.5
parent
7889da6697
commit
47526d2078
@@ -22,6 +22,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
- `codegraph files --format package` and `codegraph_files` with `format: "package"` now list a project's files by the package that owns them, the nearest `package.json` or `composer.json` above each file that names one, so a monorepo splits into its workspace members with no setup and files nothing claims get a section of their own (#1871). Thanks @LuisAlbertoMK.
|
||||
- CodeGraph's MCP server now gives agents and editors its display name and icon, so one that shows them, like the Codex app, lists CodeGraph by name and logo instead of a plain `codegraph`. Thanks @fedosov.
|
||||
- `codegraph status` now shows how many of a language's files yielded symbols when that isn't all of them, and warns when none did, so a parser that isn't working no longer looks healthy, and `codegraph status --strict` exits non-zero on that or on an index that's missing, unfinished or out of date, so packaging and CI can gate on it (#1658). Thanks @ferrine.
|
||||
- `codegraph serve --mcp --no-telemetry` turns telemetry off for one MCP server entry, so a shared MCP config can opt out without an environment variable; a background daemon that's already running for the project keeps its own setting until it exits. Thanks @danusha2345. (#1908)
|
||||
|
||||
|
||||
### Fixes
|
||||
|
||||
|
||||
@@ -20,6 +20,13 @@ export CODEGRAPH_TELEMETRY=0 # per-shell / per-CI override
|
||||
export DO_NOT_TRACK=1 # the cross-tool standard — always honored
|
||||
```
|
||||
|
||||
For one MCP server entry, add `--no-telemetry` to its arguments
|
||||
(`"args": ["serve", "--mcp", "--no-telemetry"]`). It works like
|
||||
`CODEGRAPH_TELEMETRY=0` for that server and for the background daemon it starts.
|
||||
A daemon that is already running for the project, started by a server without
|
||||
the flag, keeps its own setting until it exits; `codegraph telemetry off` covers
|
||||
every process.
|
||||
|
||||
`codegraph telemetry status` shows the current state, what decided it, and your machine ID.
|
||||
The interactive installer (`codegraph install`) asks up front with a visible default-on
|
||||
toggle and never re-asks. If you never saw the installer (e.g. `npx` straight into `init`),
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* `codegraph serve --mcp --no-telemetry` records nothing (#1908).
|
||||
*
|
||||
* Drives the real stdio server (direct mode, a temp HOME holding an opted-in
|
||||
* telemetry config, an unreachable endpoint so nothing leaves the machine) and
|
||||
* reads the telemetry queue the server leaves behind on exit. The same run
|
||||
* without the flag is the control: it must queue the tool call, or the
|
||||
* flagged run's empty queue would prove nothing.
|
||||
*/
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import { spawn } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { CodeGraph } from '../src';
|
||||
import { WASM_RUNTIME_FLAGS } from '../src/extraction/wasm-runtime-flags';
|
||||
|
||||
const BIN = path.resolve(__dirname, '../dist/bin/codegraph.js');
|
||||
|
||||
async function runOneToolCall(
|
||||
home: string,
|
||||
project: string,
|
||||
extraArgs: string[],
|
||||
): Promise<{ answered: boolean; queue: string }> {
|
||||
const env: NodeJS.ProcessEnv = {
|
||||
...process.env,
|
||||
HOME: home,
|
||||
USERPROFILE: home,
|
||||
CODEGRAPH_NO_DAEMON: '1',
|
||||
CODEGRAPH_NO_UPDATE_CHECK: '1',
|
||||
CODEGRAPH_TELEMETRY_ENDPOINT: 'http://127.0.0.1:9/',
|
||||
};
|
||||
delete env.CODEGRAPH_TELEMETRY;
|
||||
delete env.DO_NOT_TRACK;
|
||||
const child = spawn(process.execPath, [...WASM_RUNTIME_FLAGS, BIN, 'serve', '--mcp', '--no-watch', '--path', project, ...extraArgs], {
|
||||
env,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
const exited = new Promise<void>((resolve) => child.on('exit', () => resolve()));
|
||||
let out = '';
|
||||
const answered = new Promise<void>((resolve) => {
|
||||
child.stdout.on('data', (chunk) => {
|
||||
out += chunk.toString('utf8');
|
||||
if (out.includes('"id":2')) resolve();
|
||||
});
|
||||
});
|
||||
const send = (msg: object) => child.stdin.write(JSON.stringify(msg) + '\n');
|
||||
send({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'test', version: '0' } } });
|
||||
send({ jsonrpc: '2.0', method: 'notifications/initialized' });
|
||||
send({ jsonrpc: '2.0', id: 2, method: 'tools/call', params: { name: 'codegraph_explore', arguments: { query: 'greet' } } });
|
||||
// A server that rejects the flag exits without answering; don't wait on it.
|
||||
await Promise.race([answered, exited]);
|
||||
child.stdin.end();
|
||||
await exited;
|
||||
const queue = path.join(home, '.codegraph', 'telemetry-queue.jsonl');
|
||||
return {
|
||||
answered: out.includes('"id":2'),
|
||||
queue: fs.existsSync(queue) ? fs.readFileSync(queue, 'utf8') : '',
|
||||
};
|
||||
}
|
||||
|
||||
describe('serve --no-telemetry (#1908)', () => {
|
||||
let root: string;
|
||||
let project: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
root = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-no-telemetry-'));
|
||||
project = path.join(root, 'project');
|
||||
fs.mkdirSync(path.join(project, 'src'), { recursive: true });
|
||||
fs.writeFileSync(path.join(project, 'src', 'a.ts'), 'export function greet() { return 1; }\n');
|
||||
const cg = await CodeGraph.init(project, { index: true });
|
||||
cg.close();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
function optedInHome(name: string): string {
|
||||
const home = path.join(root, name);
|
||||
fs.mkdirSync(path.join(home, '.codegraph'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(home, '.codegraph', 'telemetry.json'),
|
||||
JSON.stringify({
|
||||
enabled: true,
|
||||
machine_id: 'test-machine',
|
||||
consent_source: 'cli',
|
||||
first_run_notice_shown: true,
|
||||
updated_at: new Date().toISOString(),
|
||||
}),
|
||||
);
|
||||
return home;
|
||||
}
|
||||
|
||||
it('queues the tool call without the flag (control)', async () => {
|
||||
const run = await runOneToolCall(optedInHome('home-on'), project, []);
|
||||
expect(run.answered).toBe(true);
|
||||
expect(run.queue).toContain('codegraph_explore');
|
||||
}, 60_000);
|
||||
|
||||
it('queues nothing with --no-telemetry', async () => {
|
||||
const run = await runOneToolCall(optedInHome('home-off'), project, ['--no-telemetry']);
|
||||
expect(run.answered).toBe(true);
|
||||
expect(run.queue).toBe('');
|
||||
}, 60_000);
|
||||
});
|
||||
@@ -2349,7 +2349,8 @@ program
|
||||
.option('-p, --path <path>', 'Project path (optional for MCP mode, uses rootUri from client)')
|
||||
.option('--mcp', 'Run as MCP server (stdio transport)')
|
||||
.option('--no-watch', 'Disable the file watcher (no auto-sync; useful on slow filesystems like WSL2 /mnt drives)')
|
||||
.action(async (options: { path?: string; mcp?: boolean; watch?: boolean }) => {
|
||||
.option('--no-telemetry', 'Send no usage telemetry from this server (same as CODEGRAPH_TELEMETRY=0)')
|
||||
.action(async (options: { path?: string; mcp?: boolean; watch?: boolean; telemetry?: boolean }) => {
|
||||
const projectPath = options.path ? resolveProjectPath(options.path) : undefined;
|
||||
|
||||
// Commander sets watch=false when --no-watch is passed. Route it through
|
||||
@@ -2357,6 +2358,11 @@ program
|
||||
if (options.watch === false) {
|
||||
process.env.CODEGRAPH_NO_WATCH = '1';
|
||||
}
|
||||
// Same for --no-telemetry (#1908): telemetry reads CODEGRAPH_TELEMETRY on
|
||||
// every check, and a daemon this server spawns inherits the env.
|
||||
if (options.telemetry === false) {
|
||||
process.env.CODEGRAPH_TELEMETRY = '0';
|
||||
}
|
||||
|
||||
try {
|
||||
if (options.mcp) {
|
||||
|
||||
Reference in New Issue
Block a user