Skip to content

Commit ef6a46a

Browse files
authored
feat: add consistent User-Agent string (#14)
Add a consistent user-agent string, for understanding sdk vs cli usage. Uses augment.ctxc.{product}/{version} format: - augment.ctxc.cli/{version} for all CLI commands (search, agent, index) - augment.ctxc.mcp/{version} for MCP server - augment.ctxc.sdk/{version} for SDK/programmatic usage
1 parent 0805159 commit ef6a46a

File tree

13 files changed

+1295
-982
lines changed

13 files changed

+1295
-982
lines changed

package-lock.json

Lines changed: 962 additions & 956 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
}
6969
},
7070
"dependencies": {
71-
"@augmentcode/auggie-sdk": "^0.1.14",
71+
"@augmentcode/auggie-sdk": "^0.1.15",
7272
"cheerio": "^1.1.2",
7373
"commander": "^12.0.0",
7474
"ignore": "^5.3.0",

src/bin/cmd-agent.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as readline from "readline";
77
import { CLIAgent, type Provider } from "../clients/cli-agent.js";
88
import { MultiIndexRunner } from "../clients/multi-index-runner.js";
99
import { CompositeStoreReader, parseIndexSpecs } from "../stores/index.js";
10+
import { buildClientUserAgent } from "../core/utils.js";
1011

1112
const PROVIDER_DEFAULTS: Record<Provider, string> = {
1213
openai: "gpt-5-mini",
@@ -50,9 +51,13 @@ export const agentCommand = new Command("agent")
5051
const store = await CompositeStoreReader.fromSpecs(specs);
5152

5253
// Create multi-index runner
54+
// Build User-Agent for analytics tracking
55+
const clientUserAgent = buildClientUserAgent("cli");
56+
5357
const runner = await MultiIndexRunner.create({
5458
store,
5559
searchOnly: options.searchOnly,
60+
clientUserAgent,
5661
});
5762

5863
console.log("\x1b[1;36mContext Connectors Minimal Agent\x1b[0m");
@@ -73,6 +78,7 @@ export const agentCommand = new Command("agent")
7378
model,
7479
maxSteps: options.maxSteps,
7580
verbose: options.verbose,
81+
clientUserAgent,
7682
});
7783
await agent.initialize();
7884

src/bin/cmd-index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Indexer } from "../core/indexer.js";
77
import { Source } from "../sources/types.js";
88
import { FilesystemStore } from "../stores/filesystem.js";
99
import { getS3Config } from "../stores/s3-config.js";
10+
import { buildClientUserAgent } from "../core/utils.js";
1011

1112
// Shared store options
1213
interface StoreOptions {
@@ -49,7 +50,9 @@ async function runIndex(
4950
sourceType: string
5051
) {
5152
console.log(`Indexing ${sourceType} source...`);
52-
const indexer = new Indexer();
53+
const indexer = new Indexer({
54+
clientUserAgent: buildClientUserAgent("cli"),
55+
});
5356
const result = await indexer.index(source, store, indexKey);
5457

5558
console.log(`\nIndexing complete!`);

src/bin/cmd-search.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { getSourceIdentifier } from "../core/types.js";
99
import { getS3Config } from "../stores/s3-config.js";
1010
import { parseIndexSpec } from "../stores/index-spec.js";
1111
import type { IndexStoreReader } from "../stores/types.js";
12+
import { buildClientUserAgent } from "../core/utils.js";
1213

1314
export const searchCommand = new Command("search")
1415
.description("Search indexed content and answer questions (use --raw for raw results)")
@@ -83,6 +84,7 @@ export const searchCommand = new Command("search")
8384
const client = new SearchClient({
8485
store,
8586
indexName: indexKey,
87+
clientUserAgent: buildClientUserAgent("cli"),
8688
});
8789

8890
await client.initialize();

src/clients/cli-agent.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ export interface CLIAgentSingleConfig {
7979
stream?: boolean;
8080
/** Custom system prompt. Uses a sensible default if not provided. */
8181
systemPrompt?: string;
82+
/**
83+
* Custom User-Agent string for analytics tracking.
84+
* When provided, this is sent to the Augment API for usage analytics.
85+
* Only used when provider is 'augment'.
86+
*/
87+
clientUserAgent?: string;
8288
}
8389

8490
/**
@@ -110,6 +116,12 @@ export interface CLIAgentMultiConfig {
110116
stream?: boolean;
111117
/** Custom system prompt. Uses a sensible default if not provided. */
112118
systemPrompt?: string;
119+
/**
120+
* Custom User-Agent string for analytics tracking.
121+
* When provided, this is sent to the Augment API for usage analytics.
122+
* Only used when provider is 'augment'.
123+
*/
124+
clientUserAgent?: string;
113125
}
114126

115127
/** Configuration for the CLI agent */
@@ -136,7 +148,8 @@ Be concise but thorough. Reference specific files and line numbers when helpful.
136148
*/
137149
async function loadModel(
138150
provider: Provider,
139-
modelName: string
151+
modelName: string,
152+
clientUserAgent?: string
140153
): Promise<LanguageModel> {
141154
switch (provider) {
142155
case "openai": {
@@ -179,6 +192,7 @@ async function loadModel(
179192
return new AugmentLanguageModel(modelName, {
180193
apiKey: credentials.apiKey,
181194
apiUrl: credentials.apiUrl,
195+
clientUserAgent,
182196
}) as unknown as LanguageModel;
183197
}
184198
default:
@@ -223,6 +237,7 @@ export class CLIAgent {
223237
private readonly verbose: boolean;
224238
private readonly stream: boolean;
225239
private readonly systemPrompt: string;
240+
private readonly clientUserAgent?: string;
226241
private readonly tools: ToolSet;
227242
private messages: CoreMessage[] = [];
228243

@@ -242,6 +257,7 @@ export class CLIAgent {
242257
this.verbose = config.verbose ?? false;
243258
this.stream = config.stream ?? true;
244259
this.systemPrompt = config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
260+
this.clientUserAgent = config.clientUserAgent;
245261
this.tools = this.runner ? this.createMultiIndexTools() : this.createSingleClientTools();
246262
}
247263

@@ -393,7 +409,7 @@ export class CLIAgent {
393409
* @throws Error if the provider package is not installed
394410
*/
395411
async initialize(): Promise<void> {
396-
this.model = await loadModel(this.provider, this.modelName);
412+
this.model = await loadModel(this.provider, this.modelName, this.clientUserAgent);
397413
}
398414

399415
/**

src/clients/mcp-server.ts

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ import {
3939
} from "@modelcontextprotocol/sdk/types.js";
4040
import type { IndexStoreReader } from "../stores/types.js";
4141
import { MultiIndexRunner } from "./multi-index-runner.js";
42+
import { buildClientUserAgent, type MCPClientInfo } from "../core/utils.js";
4243
import {
4344
SEARCH_DESCRIPTION,
4445
LIST_FILES_DESCRIPTION,
4546
READ_FILE_DESCRIPTION,
4647
withIndexList,
4748
} from "./tool-descriptions.js";
48-
4949
/**
5050
* Configuration for the MCP server.
5151
*/
@@ -72,7 +72,6 @@ export interface MCPServerConfig {
7272
*/
7373
version?: string;
7474
}
75-
7675
/**
7776
* Create an MCP server instance.
7877
*
@@ -96,18 +95,19 @@ export async function createMCPServer(
9695
config: MCPServerConfig
9796
): Promise<Server> {
9897
// Create shared runner for multi-index operations
98+
// Build User-Agent for analytics tracking
99+
const clientUserAgent = buildClientUserAgent("mcp");
100+
99101
const runner = await MultiIndexRunner.create({
100102
store: config.store,
101103
indexNames: config.indexNames,
102104
searchOnly: config.searchOnly,
105+
clientUserAgent,
103106
});
104-
105107
const { indexNames, indexes } = runner;
106108
const searchOnly = !runner.hasFileOperations();
107-
108109
// Format index list for tool descriptions
109110
const indexListStr = runner.getIndexListString();
110-
111111
// Create MCP server
112112
const server = new Server(
113113
{
@@ -120,7 +120,19 @@ export async function createMCPServer(
120120
},
121121
}
122122
);
123-
123+
// Use the SDK's oninitialized callback to capture MCP client info
124+
// This preserves the SDK's protocol version negotiation
125+
server.oninitialized = () => {
126+
const clientInfo = server.getClientVersion();
127+
if (clientInfo) {
128+
const mcpClientInfo: MCPClientInfo = {
129+
name: clientInfo.name,
130+
version: clientInfo.version,
131+
};
132+
const updatedUserAgent = buildClientUserAgent("mcp", mcpClientInfo);
133+
runner.updateClientUserAgent(updatedUserAgent);
134+
}
135+
};
124136
// Define tool type for type safety
125137
type Tool = {
126138
name: string;
@@ -134,12 +146,10 @@ export async function createMCPServer(
134146
required?: string[];
135147
};
136148
};
137-
138149
// Tool descriptions with available indexes (from shared module)
139150
const searchDescription = withIndexList(SEARCH_DESCRIPTION, indexListStr);
140151
const listFilesDescription = withIndexList(LIST_FILES_DESCRIPTION, indexListStr);
141152
const readFileDescription = withIndexList(READ_FILE_DESCRIPTION, indexListStr);
142-
143153
// List available tools
144154
server.setRequestHandler(ListToolsRequestSchema, async () => {
145155
const tools: Tool[] = [
@@ -167,7 +177,6 @@ export async function createMCPServer(
167177
},
168178
},
169179
];
170-
171180
// Only advertise file tools if not in search-only mode
172181
if (!searchOnly) {
173182
tools.push(
@@ -247,18 +256,14 @@ export async function createMCPServer(
247256
}
248257
);
249258
}
250-
251259
return { tools };
252260
});
253-
254261
// Handle tool calls
255262
server.setRequestHandler(CallToolRequestSchema, async (request) => {
256263
const { name, arguments: args } = request.params;
257-
258264
try {
259265
const indexName = args?.index_name as string;
260266
const client = await runner.getClient(indexName);
261-
262267
switch (name) {
263268
case "search": {
264269
const result = await client.search(args?.query as string, {
@@ -270,7 +275,6 @@ export async function createMCPServer(
270275
],
271276
};
272277
}
273-
274278
case "list_files": {
275279
if (searchOnly) {
276280
return {
@@ -291,7 +295,6 @@ export async function createMCPServer(
291295
content: [{ type: "text", text }],
292296
};
293297
}
294-
295298
case "read_file": {
296299
if (searchOnly) {
297300
return {
@@ -321,7 +324,6 @@ export async function createMCPServer(
321324
content: [{ type: "text", text: result.contents ?? "" }],
322325
};
323326
}
324-
325327
default:
326328
return {
327329
content: [{ type: "text", text: `Unknown tool: ${name}` }],
@@ -335,10 +337,8 @@ export async function createMCPServer(
335337
};
336338
}
337339
});
338-
339340
return server;
340341
}
341-
342342
/**
343343
* Run an MCP server with stdio transport.
344344
*
@@ -369,4 +369,3 @@ export async function runMCPServer(config: MCPServerConfig): Promise<void> {
369369
const transport = new StdioServerTransport();
370370
await server.connect(transport);
371371
}
372-

src/clients/multi-index-runner.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ export interface MultiIndexRunnerConfig {
3636
* When true, only search is available.
3737
*/
3838
searchOnly?: boolean;
39+
/**
40+
* Custom User-Agent string for analytics tracking.
41+
* When provided, this is passed to SearchClient instances for API requests.
42+
*/
43+
clientUserAgent?: string;
3944
}
4045

4146
/** Create a Source from index state metadata */
@@ -65,6 +70,7 @@ async function createSourceFromState(state: IndexStateSearchOnly): Promise<Sourc
6570
export class MultiIndexRunner {
6671
private readonly store: IndexStoreReader;
6772
private readonly searchOnly: boolean;
73+
private clientUserAgent?: string;
6874
private readonly clientCache = new Map<string, SearchClient>();
6975

7076
/** Available index names */
@@ -77,12 +83,14 @@ export class MultiIndexRunner {
7783
store: IndexStoreReader,
7884
indexNames: string[],
7985
indexes: IndexInfo[],
80-
searchOnly: boolean
86+
searchOnly: boolean,
87+
clientUserAgent?: string
8188
) {
8289
this.store = store;
8390
this.indexNames = indexNames;
8491
this.indexes = indexes;
8592
this.searchOnly = searchOnly;
93+
this.clientUserAgent = clientUserAgent;
8694
}
8795

8896
/**
@@ -132,7 +140,18 @@ export class MultiIndexRunner {
132140
throw new Error("No valid indexes available (all indexes failed to load)");
133141
}
134142

135-
return new MultiIndexRunner(store, validIndexNames, indexes, searchOnly);
143+
return new MultiIndexRunner(store, validIndexNames, indexes, searchOnly, config.clientUserAgent);
144+
}
145+
146+
147+
/**
148+
* Update the User-Agent string.
149+
*
150+
* Call this after receiving MCP client info to include the client name/version.
151+
* Note: Only affects future client creations, not existing cached clients.
152+
*/
153+
updateClientUserAgent(newUserAgent: string): void {
154+
this.clientUserAgent = newUserAgent;
136155
}
137156

138157
/**
@@ -158,6 +177,7 @@ export class MultiIndexRunner {
158177
store: this.store,
159178
source,
160179
indexName,
180+
clientUserAgent: this.clientUserAgent,
161181
});
162182
await client.initialize();
163183
this.clientCache.set(indexName, client);

src/clients/search-client.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ export interface SearchClientConfig {
6666
* @default process.env.AUGMENT_API_URL
6767
*/
6868
apiUrl?: string;
69+
/**
70+
* Custom User-Agent string for analytics tracking.
71+
* When provided, this is sent to the Augment API for usage analytics.
72+
*/
73+
clientUserAgent?: string;
6974
}
7075

7176
/**
@@ -106,6 +111,7 @@ export class SearchClient {
106111
private indexName: string;
107112
private apiKey: string;
108113
private apiUrl: string;
114+
private clientUserAgent?: string;
109115

110116
private context: DirectContext | null = null;
111117
private state: IndexStateSearchOnly | null = null;
@@ -123,6 +129,7 @@ export class SearchClient {
123129
this.indexName = config.indexName;
124130
this.apiKey = config.apiKey ?? process.env.AUGMENT_API_TOKEN ?? "";
125131
this.apiUrl = config.apiUrl ?? process.env.AUGMENT_API_URL ?? "";
132+
this.clientUserAgent = config.clientUserAgent;
126133
}
127134

128135
/**
@@ -162,6 +169,7 @@ export class SearchClient {
162169
this.context = await DirectContext.import(this.state.contextState, {
163170
apiKey: this.apiKey,
164171
apiUrl: this.apiUrl,
172+
clientUserAgent: this.clientUserAgent,
165173
});
166174
}
167175

src/core/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ export {
2020
shouldFilterFile,
2121
} from "./file-filter.js";
2222

23-
export { sanitizeKey, isoTimestamp } from "./utils.js";
23+
export { sanitizeKey, isoTimestamp, buildClientUserAgent } from "./utils.js";
24+
export type { ClientProduct, MCPClientInfo } from "./utils.js";
2425

2526
export { Indexer } from "./indexer.js";
2627
export type { IndexerConfig } from "./indexer.js";

0 commit comments

Comments
 (0)