Skip to content

Commit 4684d61

Browse files
chore: Refactors configure to leverage @gleanwork/mcp-config-schema (#265)
1 parent eb06356 commit 4684d61

File tree

13 files changed

+480
-594
lines changed

13 files changed

+480
-594
lines changed

packages/configure-mcp-server/package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,13 @@
4747
},
4848
"dependencies": {
4949
"@gleanwork/local-mcp-server": "workspace:*",
50+
"@gleanwork/mcp-config-schema": "^0.7.0",
5051
"@gleanwork/mcp-server-utils": "workspace:*",
5152
"dotenv": "^17.2.1",
5253
"mcp-remote": "^0.1.18",
5354
"meow": "^13.2.0",
54-
"open": "^10.2.0",
5555
"tldts": "^7.0.10",
56-
"yaml": "^2.8.0",
57-
"zod": "^3.25.65",
58-
"zod-to-json-schema": "^3.24.6"
56+
"yaml": "^2.8.0"
5957
},
6058
"devDependencies": {
6159
"@eslint/eslintrc": "^3.3.1",
@@ -73,6 +71,7 @@
7371
"globals": "^16.3.0",
7472
"msw": "^2.10.2",
7573
"npm-run-all": "^4.1.5",
74+
"open": "^10.2.0",
7675
"rimraf": "^6.0.1",
7776
"sort-package-json": "^3.4.0",
7877
"tsx": "^4.20.3",

packages/configure-mcp-server/src/configure/client/claude-code.ts

Lines changed: 5 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -4,104 +4,12 @@
44
* Similar to Claude Desktop but uses a different config path
55
*/
66

7-
import path from 'path';
8-
import {
9-
MCPConfigPath,
10-
createBaseClient,
11-
MCPServersConfig,
12-
StandardMCPConfig,
13-
buildMcpServerName,
14-
} from './index.js';
15-
import type { ConfigureOptions } from '../index.js';
7+
import { createBaseClient } from './index.js';
8+
import { CLIENT } from '@gleanwork/mcp-config-schema';
169

17-
export const claudeCodeConfigPath: MCPConfigPath = {
18-
configDir: '',
19-
configFileName: '.claude.json',
20-
};
21-
22-
function claudeCodePathResolver(homedir: string) {
23-
const baseDir = process.env.GLEAN_MCP_CONFIG_DIR || homedir;
24-
return path.join(baseDir, claudeCodeConfigPath.configFileName);
25-
}
26-
27-
function mcpServersHook(servers: MCPServersConfig): StandardMCPConfig {
28-
return {
29-
mcpServers: servers,
30-
};
31-
}
32-
33-
const claudeCodeClient = createBaseClient(
34-
'Claude Code',
35-
claudeCodeConfigPath,
36-
['Run `claude mcp list` and verify the server is listed'],
37-
claudeCodePathResolver,
38-
mcpServersHook,
39-
);
40-
41-
function createClaudeCodeMcpServersConfig(
42-
instanceOrUrl: string,
43-
apiToken?: string,
44-
options?: ConfigureOptions,
45-
): MCPServersConfig {
46-
const env = {};
47-
48-
// local set up
49-
if (!options?.remote) {
50-
const args = ['serve'];
51-
if (options?.agents) {
52-
args.push('--agents');
53-
}
54-
if (instanceOrUrl) {
55-
args.push('--instance', instanceOrUrl);
56-
}
57-
if (apiToken) {
58-
args.push('--token', apiToken);
59-
}
60-
61-
const mcpServerName = buildMcpServerName(options || {});
62-
return {
63-
[mcpServerName]: {
64-
command: 'npx',
65-
args: ['-y', '@gleanwork/local-mcp-server', ...args],
66-
type: 'stdio',
67-
env,
68-
},
69-
};
70-
}
71-
72-
// Remote configuration requires a full URL
73-
if (
74-
!instanceOrUrl.startsWith('http://') &&
75-
!instanceOrUrl.startsWith('https://')
76-
) {
77-
throw new Error(
78-
'Remote configuration requires a full URL (starting with http:// or https://)',
79-
);
80-
}
81-
82-
const serverUrl = instanceOrUrl;
83-
const mcpServerName = buildMcpServerName(options || {}, serverUrl);
84-
85-
return {
86-
[mcpServerName]: {
87-
type: 'http',
88-
url: serverUrl,
89-
},
90-
};
91-
}
92-
93-
claudeCodeClient.configTemplate = (
94-
subdomainOrUrl?: string,
95-
apiToken?: string,
96-
options?: ConfigureOptions,
97-
): StandardMCPConfig => {
98-
const servers = createClaudeCodeMcpServersConfig(
99-
subdomainOrUrl || '<glean instance name or URL>',
100-
apiToken,
101-
options,
102-
);
103-
return { mcpServers: servers };
104-
};
10+
const claudeCodeClient = createBaseClient(CLIENT.CLAUDE_CODE, [
11+
'Run `claude mcp list` and verify the server is listed',
12+
]);
10513

10614
claudeCodeClient.successMessage = (
10715
configPath: string,

packages/configure-mcp-server/src/configure/client/claude.ts

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,46 +4,13 @@
44
* https://modelcontextprotocol.io/quickstart/user
55
*/
66

7-
import path from 'path';
8-
import { MCPConfigPath, createBaseClient } from './index.js';
7+
import { createBaseClient } from './index.js';
8+
import { CLIENT } from '@gleanwork/mcp-config-schema';
99

10-
export const claudeConfigPath: MCPConfigPath = {
11-
configDir: 'Claude',
12-
configFileName: 'claude_desktop_config.json',
13-
};
14-
15-
function claudePathResolver(homedir: string) {
16-
let baseDir: string;
17-
18-
if (process.env.GLEAN_MCP_CONFIG_DIR) {
19-
baseDir = process.env.GLEAN_MCP_CONFIG_DIR;
20-
} else if (process.platform === 'darwin') {
21-
baseDir = path.join(homedir, 'Library', 'Application Support');
22-
} else if (process.platform === 'win32') {
23-
baseDir = process.env.APPDATA || '';
24-
} else {
25-
throw new Error('Unsupported platform for Claude Desktop');
26-
}
27-
28-
return path.join(
29-
baseDir,
30-
claudeConfigPath.configDir,
31-
claudeConfigPath.configFileName,
32-
);
33-
}
34-
35-
/**
36-
* Claude Desktop client configuration
37-
*/
38-
const claudeClient = createBaseClient(
39-
'Claude Desktop',
40-
claudeConfigPath,
41-
[
42-
'Restart Claude Desktop',
43-
'You should see a hammer icon in the input box, indicating MCP tools are available',
44-
'Click the hammer to see available tools',
45-
],
46-
claudePathResolver,
47-
);
10+
const claudeClient = createBaseClient(CLIENT.CLAUDE_DESKTOP, [
11+
'Restart Claude Desktop',
12+
'MCP tools will be available in your conversations',
13+
'The model will have access to Glean search and other configured tools',
14+
]);
4815

4916
export default claudeClient;

packages/configure-mcp-server/src/configure/client/cursor.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,10 @@
44
* https://docs.cursor.com/context/model-context-protocol
55
*/
66

7-
import { MCPConfigPath, createBaseClient } from './index.js';
7+
import { createBaseClient } from './index.js';
8+
import { CLIENT } from '@gleanwork/mcp-config-schema';
89

9-
export const cursorConfigPath: MCPConfigPath = {
10-
configDir: '.cursor',
11-
configFileName: 'mcp.json',
12-
};
13-
14-
/**
15-
* Cursor client configuration
16-
*/
17-
const cursorClient = createBaseClient('Cursor', cursorConfigPath, [
10+
const cursorClient = createBaseClient(CLIENT.CURSOR, [
1811
'Restart Cursor',
1912
'Agent will now have access to Glean tools',
2013
"You'll be asked for approval when Agent uses these tools",
Lines changed: 66 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,83 @@
1-
import path from 'path';
1+
import { createBaseClient } from './index.js';
2+
import type { ConfigureOptions } from '../index.js';
23
import {
3-
MCPConfigPath,
4-
MCPClientConfig,
5-
GooseConfig,
6-
GooseExtensionConfig,
7-
createBaseClient,
4+
CLIENT,
5+
MCPConfigRegistry,
86
buildMcpServerName,
9-
MCPServersConfig,
10-
} from './index.js';
11-
import type { ConfigureOptions } from '../index.js';
7+
} from '@gleanwork/mcp-config-schema';
8+
import yaml from 'yaml';
9+
import mcpRemotePackageJson from 'mcp-remote/package.json' with { type: 'json' };
1210

13-
export const gooseConfigPath: MCPConfigPath = {
14-
configDir: path.join('.config', 'goose'),
15-
configFileName: 'config.yaml',
16-
};
11+
const mcpRemoteVersion = mcpRemotePackageJson.version;
1712

18-
function goosePathResolver(homedir: string) {
19-
if (process.platform === 'win32') {
20-
return path.join(process.env.APPDATA || homedir, 'goose', 'config.yaml');
21-
}
13+
const gooseClient = createBaseClient(CLIENT.GOOSE, ['Restart Goose']);
2214

23-
const baseDir = process.env.GLEAN_MCP_CONFIG_DIR || homedir;
24-
return path.join(
25-
baseDir,
26-
gooseConfigPath.configDir,
27-
gooseConfigPath.configFileName,
28-
);
29-
}
15+
// Override configTemplate to use the package's YAML output directly
16+
gooseClient.configTemplate = (
17+
instanceOrUrl?: string,
18+
apiToken?: string,
19+
options?: ConfigureOptions,
20+
) => {
21+
const registry = new MCPConfigRegistry();
22+
const builder = registry.createBuilder(CLIENT.GOOSE);
23+
const isRemote = options?.remote === true;
3024

31-
function toGooseConfig(servers: MCPServersConfig): GooseConfig {
32-
const extensions: Record<string, GooseExtensionConfig> = {};
33-
for (const [name, server] of Object.entries(servers)) {
34-
if (!server) continue;
25+
const configOutput = builder.buildConfiguration({
26+
mode: isRemote ? 'remote' : 'local',
27+
serverUrl: isRemote ? instanceOrUrl : undefined,
28+
instance: !isRemote ? instanceOrUrl : undefined,
29+
apiToken: apiToken,
30+
serverName: isRemote
31+
? buildMcpServerName({
32+
mode: 'remote',
33+
serverUrl: instanceOrUrl,
34+
agents: options?.agents,
35+
})
36+
: undefined,
37+
includeWrapper: false,
38+
});
3539

36-
// Goose only supports stdio configs (with command and args)
37-
// This shouldn't happen in practice since Goose uses createMcpServersConfig
38-
// which always generates stdio configs, but check defensively
39-
if (!server.command || !server.args) {
40-
console.warn(
41-
`Skipping server ${name}: Goose requires stdio configuration`,
42-
);
43-
continue;
44-
}
40+
// Parse the YAML to pin mcp-remote version if needed
41+
const parsed = yaml.parse(configOutput);
4542

46-
extensions[name] = {
47-
args: server.args,
48-
bundled: null,
49-
cmd: server.command,
50-
description: '',
51-
enabled: true,
52-
env_keys: [],
53-
envs: server.env || {},
54-
name: 'glean',
55-
timeout: 300,
56-
type: server.type ?? 'stdio',
57-
};
43+
if (isRemote) {
44+
for (const [, serverConfig] of Object.entries(parsed)) {
45+
if (
46+
serverConfig &&
47+
typeof serverConfig === 'object' &&
48+
'cmd' in serverConfig
49+
) {
50+
const config = serverConfig as any;
51+
if (config.cmd === 'npx' && config.args) {
52+
config.args = config.args.map((arg: string) => {
53+
if (arg === 'mcp-remote' || arg.startsWith('mcp-remote@')) {
54+
return `mcp-remote@${mcpRemoteVersion}`;
55+
}
56+
return arg;
57+
});
58+
}
59+
}
60+
}
5861
}
59-
return { extensions };
60-
}
6162

62-
function updateConfig(
63+
return parsed;
64+
};
65+
66+
// Custom updateConfig for Goose's flat structure
67+
gooseClient.updateConfig = (
6368
existingConfig: Record<string, any>,
6469
newConfig: Record<string, any>,
65-
options: ConfigureOptions,
66-
): Record<string, any> {
70+
): Record<string, any> => {
6771
const result = { ...existingConfig };
68-
result.extensions = result.extensions || {};
69-
const mcpServerName = buildMcpServerName(options);
70-
result.extensions[mcpServerName] = newConfig.extensions[mcpServerName];
71-
return result;
72-
}
7372

74-
const gooseClient: MCPClientConfig = createBaseClient(
75-
'Goose',
76-
gooseConfigPath,
77-
['Restart Goose'],
78-
goosePathResolver,
79-
toGooseConfig,
80-
);
73+
// For Goose, servers are at the top level
74+
for (const serverName in newConfig) {
75+
if (serverName === 'glean' || serverName.startsWith('glean_')) {
76+
result[serverName] = newConfig[serverName];
77+
}
78+
}
8179

82-
gooseClient.updateConfig = updateConfig;
80+
return result;
81+
};
8382

8483
export default gooseClient;

0 commit comments

Comments
 (0)