Skip to content

Commit e77a3b6

Browse files
Merge pull request #203 from gleanwork/codex/add-claude-code-client-and-tests
feat: add Claude Code
2 parents 66059ae + cd49018 commit e77a3b6

File tree

2 files changed

+340
-4
lines changed

2 files changed

+340
-4
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Claude Code MCP Client Implementation
3+
*
4+
* Similar to Claude Desktop but uses a different config path
5+
*/
6+
7+
import path from 'path';
8+
import {
9+
MCPConfigPath,
10+
createBaseClient,
11+
MCPServersConfig,
12+
StandardMCPConfig,
13+
} from './index.js';
14+
15+
export const claudeCodeConfigPath: MCPConfigPath = {
16+
configDir: '',
17+
configFileName: '.claude.json',
18+
};
19+
20+
function claudeCodePathResolver(homedir: string) {
21+
let baseDir: string;
22+
23+
if (process.env.GLEAN_MCP_CONFIG_DIR) {
24+
baseDir = process.env.GLEAN_MCP_CONFIG_DIR;
25+
} else if (process.platform === 'darwin') {
26+
baseDir = homedir;
27+
} else {
28+
throw new Error('Unsupported platform for Claude Code');
29+
}
30+
31+
return path.join(baseDir, claudeCodeConfigPath.configFileName);
32+
}
33+
34+
function mcpServersHook(servers: MCPServersConfig): StandardMCPConfig {
35+
return {
36+
mcpServers: servers,
37+
};
38+
}
39+
40+
const claudeCodeClient = createBaseClient(
41+
'Claude Code',
42+
claudeCodeConfigPath,
43+
[
44+
'Run `claude mcp list` and verify the server is listed',
45+
],
46+
claudeCodePathResolver,
47+
mcpServersHook,
48+
);
49+
50+
51+
export default claudeCodeClient;

packages/configure-mcp-server/src/test/cli.test.ts

Lines changed: 289 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { ConfigFileContents } from '../configure/index.js';
88

99
import { cursorConfigPath } from '../configure/client/cursor.js';
1010
import { claudeConfigPath } from '../configure/client/claude.js';
11+
import { claudeCodeConfigPath } from '../configure/client/claude-code.js';
1112
import { windsurfConfigPath } from '../configure/client/windsurf.js';
1213
import { gooseConfigPath } from '../configure/client/goose.js';
1314
import yaml from 'yaml';
@@ -99,7 +100,7 @@ describe('CLI', () => {
99100
help Show this help message
100101
101102
Options for configure
102-
--client, -c MCP client to configure for (claude, cursor, goose, vscode, windsurf)
103+
--client, -c MCP client to configure for (claude-code, claude, cursor, goose, vscode, windsurf)
103104
--token, -t Glean API token (required)
104105
--instance, -i Glean instance name
105106
--env, -e Path to .env file containing GLEAN_INSTANCE and GLEAN_API_TOKEN
@@ -147,15 +148,15 @@ describe('CLI', () => {
147148
help Show this help message
148149
149150
Options for local
150-
--client, -c MCP client to configure for (claude, cursor, goose, vscode, windsurf)
151+
--client, -c MCP client to configure for (claude-code, claude, cursor, goose, vscode, windsurf)
151152
--token, -t Glean API token (required)
152153
--instance, -i Glean instance name
153154
--env, -e Path to .env file containing GLEAN_INSTANCE and GLEAN_API_TOKEN
154155
--workspace Create workspace configuration instead of global (VS Code only)
155156
156157
Options for remote
157158
--agents Connect your Glean Agents to your MCP client. If unset, will connect the default tools.
158-
--client, -c MCP client to configure for (claude, cursor, goose, vscode, windsurf)
159+
--client, -c MCP client to configure for (claude-code, claude, cursor, goose, vscode, windsurf)
159160
--token, -t Glean API token (required)
160161
--instance, -i Glean instance name
161162
--env, -e Path to .env file containing GLEAN_INSTANCE and GLEAN_API_TOKEN
@@ -197,7 +198,7 @@ describe('CLI', () => {
197198
expect(result.exitCode).toEqual(1);
198199
expect(result.stderr).toMatchInlineSnapshot(`
199200
"Unsupported MCP client: invalid-client
200-
Supported clients: claude, cursor, goose, vscode, windsurf"
201+
Supported clients: claude-code, claude, cursor, goose, vscode, windsurf"
201202
`);
202203
expect(result.stdout).toMatchInlineSnapshot(`""`);
203204
});
@@ -358,6 +359,153 @@ describe('CLI', () => {
358359
`);
359360
});
360361

362+
describe('Claude Code client', () => {
363+
let configPath: string;
364+
let configFilePath: string;
365+
366+
const { configDir, configFileName } = claudeCodeConfigPath;
367+
368+
beforeEach(() => {
369+
configPath = path.join(project.baseDir, configDir);
370+
configFilePath = path.join(configPath, configFileName);
371+
});
372+
373+
it('creates a new config file when none exists', async () => {
374+
const result = await runBin(
375+
'--client',
376+
'claude-code',
377+
'--token',
378+
'glean_api_test',
379+
'--instance',
380+
'test-domain',
381+
{
382+
env: {
383+
GLEAN_MCP_CONFIG_DIR: project.baseDir,
384+
HOME: project.baseDir,
385+
USERPROFILE: project.baseDir,
386+
APPDATA: project.baseDir,
387+
},
388+
},
389+
);
390+
391+
expect(result.exitCode).toEqual(0);
392+
expect(normalizeOutput(result.stdout, project.baseDir))
393+
.toMatchInlineSnapshot(`
394+
"Configuring Glean MCP for Claude Code...
395+
Created new configuration file at: <TMP_DIR>/.claude.json
396+
397+
Claude Code MCP configuration has been configured to: <TMP_DIR>/.claude.json
398+
399+
To use it:
400+
1. Run \`claude mcp list\` and verify the server is listed
401+
"
402+
`);
403+
404+
const configFileContents = fs.readFileSync(configFilePath, 'utf8');
405+
406+
expect(fs.existsSync(configFilePath)).toBe(true);
407+
expect(configFileContents).toMatchInlineSnapshot(`
408+
"{
409+
"mcpServers": {
410+
"glean_local": {
411+
"type": "stdio",
412+
"command": "npx",
413+
"args": [
414+
"-y",
415+
"@gleanwork/local-mcp-server"
416+
],
417+
"env": {
418+
"GLEAN_INSTANCE": "test-domain",
419+
"GLEAN_API_TOKEN": "glean_api_test"
420+
}
421+
}
422+
}
423+
}"
424+
`);
425+
});
426+
427+
it("adds config to existing file that doesn't have Glean config", async () => {
428+
const existingConfig = {
429+
'some-other-config': {
430+
options: {
431+
enabled: true,
432+
},
433+
},
434+
'.mcpServers': {
435+
'github-remote': {
436+
url: 'https://api.githubcopilot.com/mcp',
437+
authorization_token: 'Bearer $MY_TOKEN',
438+
},
439+
},
440+
} as ConfigFileContents;
441+
442+
createConfigFile(configFilePath, existingConfig);
443+
444+
const result = await runBin(
445+
'--client',
446+
'claude-code',
447+
'--token',
448+
'glean_api_test',
449+
'--instance',
450+
'test-domain',
451+
{
452+
env: {
453+
GLEAN_MCP_CONFIG_DIR: project.baseDir,
454+
HOME: project.baseDir,
455+
USERPROFILE: project.baseDir,
456+
APPDATA: project.baseDir,
457+
},
458+
},
459+
);
460+
461+
expect(result.exitCode).toEqual(0);
462+
expect(normalizeOutput(result.stdout, project.baseDir))
463+
.toMatchInlineSnapshot(`
464+
"Configuring Glean MCP for Claude Code...
465+
Updated configuration file at: <TMP_DIR>/.claude.json
466+
467+
Claude Code MCP configuration has been configured to: <TMP_DIR>/.claude.json
468+
469+
To use it:
470+
1. Run \`claude mcp list\` and verify the server is listed
471+
"
472+
`);
473+
474+
const configFileContents = fs.readFileSync(configFilePath, 'utf8');
475+
476+
expect(fs.existsSync(configFilePath)).toBe(true);
477+
expect(configFileContents).toMatchInlineSnapshot(`
478+
"{
479+
"some-other-config": {
480+
"options": {
481+
"enabled": true
482+
}
483+
},
484+
".mcpServers": {
485+
"github-remote": {
486+
"url": "https://api.githubcopilot.com/mcp",
487+
"authorization_token": "Bearer $MY_TOKEN"
488+
}
489+
},
490+
"mcpServers": {
491+
"glean_local": {
492+
"type": "stdio",
493+
"command": "npx",
494+
"args": [
495+
"-y",
496+
"@gleanwork/local-mcp-server"
497+
],
498+
"env": {
499+
"GLEAN_INSTANCE": "test-domain",
500+
"GLEAN_API_TOKEN": "glean_api_test"
501+
}
502+
}
503+
}
504+
}"
505+
`);
506+
});
507+
});
508+
361509
it('uses token auth when both token and instance provided via flags', async () => {
362510
const result = await runBin(
363511
'--client',
@@ -856,6 +1004,143 @@ Error configuring client: API token is required. Please provide a token with the
8561004
});
8571005
});
8581006

1007+
describe('Claude Code client', () => {
1008+
let configPath: string;
1009+
let configFilePath: string;
1010+
1011+
const { configDir, configFileName } = claudeCodeConfigPath;
1012+
1013+
beforeEach(() => {
1014+
configPath = path.join(project.baseDir, configDir);
1015+
configFilePath = path.join(configPath, configFileName);
1016+
});
1017+
1018+
it('creates a new config file when none exists', async () => {
1019+
const result = await runBin(
1020+
'remote',
1021+
'--client',
1022+
'claude-code',
1023+
'--token',
1024+
'glean_api_test',
1025+
'--instance',
1026+
'test-domain',
1027+
{
1028+
env: {
1029+
GLEAN_MCP_CONFIG_DIR: project.baseDir,
1030+
},
1031+
},
1032+
);
1033+
1034+
expect(result.exitCode).toEqual(0);
1035+
expect(normalizeOutput(result.stdout, project.baseDir))
1036+
.toMatchInlineSnapshot(`
1037+
"Configuring Glean MCP for Claude Code...
1038+
Created new configuration file at: <TMP_DIR>/.claude.json
1039+
1040+
Claude Code MCP configuration has been configured to: <TMP_DIR>/.claude.json
1041+
1042+
To use it:
1043+
1. Run \`claude mcp list\` and verify the server is listed
1044+
"
1045+
`);
1046+
1047+
const configFileContents = fs.readFileSync(configFilePath, 'utf8');
1048+
1049+
expect(fs.existsSync(configFilePath)).toBe(true);
1050+
expect(configFileContents).toMatchInlineSnapshot(`
1051+
"{
1052+
"mcpServers": {
1053+
"glean": {
1054+
"command": "npx",
1055+
"args": [
1056+
"-y",
1057+
"@gleanwork/connect-mcp-server",
1058+
"https://test-domain-be.glean.com/mcp/default/sse",
1059+
"--header",
1060+
"Authorization:\${AUTH_HEADER}"
1061+
],
1062+
"type": "stdio",
1063+
"env": {
1064+
"AUTH_HEADER": "Bearer glean_api_test"
1065+
}
1066+
}
1067+
}
1068+
}"
1069+
`);
1070+
});
1071+
1072+
it("adds config to existing file that doesn't have Glean config", async () => {
1073+
const existingConfig = {
1074+
tools: [
1075+
{
1076+
name: 'some-other-tool',
1077+
description: 'Another tool',
1078+
},
1079+
],
1080+
} as ConfigFileContents;
1081+
1082+
createConfigFile(configFilePath, existingConfig);
1083+
1084+
const result = await runBin(
1085+
'remote',
1086+
'--client',
1087+
'claude-code',
1088+
'--token',
1089+
'glean_api_test',
1090+
'--instance',
1091+
'test-domain',
1092+
{
1093+
env: {
1094+
GLEAN_MCP_CONFIG_DIR: project.baseDir,
1095+
},
1096+
},
1097+
);
1098+
1099+
expect(result.exitCode).toEqual(0);
1100+
expect(normalizeOutput(result.stdout, project.baseDir))
1101+
.toMatchInlineSnapshot(`
1102+
"Configuring Glean MCP for Claude Code...
1103+
Updated configuration file at: <TMP_DIR>/.claude.json
1104+
1105+
Claude Code MCP configuration has been configured to: <TMP_DIR>/.claude.json
1106+
1107+
To use it:
1108+
1. Run \`claude mcp list\` and verify the server is listed
1109+
"
1110+
`);
1111+
1112+
const configFileContents = fs.readFileSync(configFilePath, 'utf8');
1113+
1114+
expect(fs.existsSync(configFilePath)).toBe(true);
1115+
expect(configFileContents).toMatchInlineSnapshot(`
1116+
"{
1117+
"tools": [
1118+
{
1119+
"name": "some-other-tool",
1120+
"description": "Another tool"
1121+
}
1122+
],
1123+
"mcpServers": {
1124+
"glean": {
1125+
"command": "npx",
1126+
"args": [
1127+
"-y",
1128+
"@gleanwork/connect-mcp-server",
1129+
"https://test-domain-be.glean.com/mcp/default/sse",
1130+
"--header",
1131+
"Authorization:\${AUTH_HEADER}"
1132+
],
1133+
"type": "stdio",
1134+
"env": {
1135+
"AUTH_HEADER": "Bearer glean_api_test"
1136+
}
1137+
}
1138+
}
1139+
}"
1140+
`);
1141+
});
1142+
});
1143+
8591144
describe('Windsurf client', () => {
8601145
let configPath: string;
8611146
let configFilePath: string;

0 commit comments

Comments
 (0)