Skip to content
This repository was archived by the owner on Jan 13, 2026. It is now read-only.

Commit 5dd89e8

Browse files
Fix --token flag extraction and add comprehensive client tests (#5)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 9a480c9 commit 5dd89e8

14 files changed

+1788
-1
lines changed

src/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,17 @@ export const GLEAN_REGISTRY_OPTIONS: RegistryOptions = {
1616
commandBuilder: {
1717
http: (clientId, options) => {
1818
if (!options.serverUrl) return null;
19-
return `npx -y @gleanwork/configure-mcp-server remote --url ${options.serverUrl} --client ${clientId}`;
19+
20+
let command = `npx -y @gleanwork/configure-mcp-server remote --url ${options.serverUrl} --client ${clientId}`;
21+
22+
// Extract token from Authorization header if present
23+
const authHeader = options.headers?.['Authorization'];
24+
if (authHeader?.startsWith('Bearer ')) {
25+
const token = authHeader.slice(7);
26+
command += ` --token ${token}`;
27+
}
28+
29+
return command;
2030
},
2131
stdio: (clientId, options) => {
2232
const envFlags = Object.entries(options.env || {})

test/clients/chatgpt.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { createGleanRegistry } from '../../src/index.js';
3+
4+
/**
5+
* ChatGPT: web-based client with no local config support
6+
* createBuilder() throws an error - configuration must be done via web UI
7+
*/
8+
describe('Client: chatgpt (web-based only)', () => {
9+
const registry = createGleanRegistry();
10+
11+
describe('createBuilder', () => {
12+
it('throws error because ChatGPT requires web UI configuration', () => {
13+
expect(() => registry.createBuilder('chatgpt')).toThrowErrorMatchingInlineSnapshot(
14+
`[Error: Cannot create builder for ChatGPT: ChatGPT is web-based and requires configuring MCP servers through their web UI. No local configuration file support.]`
15+
);
16+
});
17+
});
18+
});

test/clients/claude-code.test.ts

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { describe, it, expect } from 'vitest';
2+
import {
3+
createGleanRegistry,
4+
createGleanEnv,
5+
createGleanHeaders,
6+
buildGleanServerUrl,
7+
} from '../../src/index.js';
8+
9+
/**
10+
* Claude Code: native CLI client
11+
* Has native `claude mcp add` command - uses schema's native command generation
12+
*/
13+
describe('Client: claude-code', () => {
14+
const registry = createGleanRegistry();
15+
const builder = registry.createBuilder('claude-code');
16+
17+
describe('buildConfiguration', () => {
18+
describe('stdio transport', () => {
19+
it('with token auth', () => {
20+
const config = builder.buildConfiguration({
21+
transport: 'stdio',
22+
env: createGleanEnv('my-company', 'my-api-token'),
23+
});
24+
25+
expect(config).toMatchInlineSnapshot(`
26+
{
27+
"mcpServers": {
28+
"glean_local": {
29+
"args": [
30+
"-y",
31+
"@gleanwork/local-mcp-server",
32+
],
33+
"command": "npx",
34+
"env": {
35+
"GLEAN_API_TOKEN": "my-api-token",
36+
"GLEAN_INSTANCE": "my-company",
37+
},
38+
"type": "stdio",
39+
},
40+
},
41+
}
42+
`);
43+
});
44+
45+
it('with OAuth (instance only, no token)', () => {
46+
const config = builder.buildConfiguration({
47+
transport: 'stdio',
48+
env: createGleanEnv('my-company'),
49+
});
50+
51+
expect(config).toMatchInlineSnapshot(`
52+
{
53+
"mcpServers": {
54+
"glean_local": {
55+
"args": [
56+
"-y",
57+
"@gleanwork/local-mcp-server",
58+
],
59+
"command": "npx",
60+
"env": {
61+
"GLEAN_INSTANCE": "my-company",
62+
},
63+
"type": "stdio",
64+
},
65+
},
66+
}
67+
`);
68+
});
69+
});
70+
71+
describe('http transport', () => {
72+
it('with token auth', () => {
73+
const config = builder.buildConfiguration({
74+
transport: 'http',
75+
serverUrl: buildGleanServerUrl('my-company'),
76+
headers: createGleanHeaders('my-api-token'),
77+
});
78+
79+
expect(config).toMatchInlineSnapshot(`
80+
{
81+
"mcpServers": {
82+
"glean_default": {
83+
"headers": {
84+
"Authorization": "Bearer my-api-token",
85+
},
86+
"type": "http",
87+
"url": "https://my-company-be.glean.com/mcp/default",
88+
},
89+
},
90+
}
91+
`);
92+
});
93+
94+
it('with OAuth (URL only, no token)', () => {
95+
const config = builder.buildConfiguration({
96+
transport: 'http',
97+
serverUrl: buildGleanServerUrl('my-company'),
98+
});
99+
100+
expect(config).toMatchInlineSnapshot(`
101+
{
102+
"mcpServers": {
103+
"glean_default": {
104+
"type": "http",
105+
"url": "https://my-company-be.glean.com/mcp/default",
106+
},
107+
},
108+
}
109+
`);
110+
});
111+
});
112+
});
113+
114+
describe('buildCommand (native CLI)', () => {
115+
describe('stdio transport', () => {
116+
it('with token auth', () => {
117+
const command = builder.buildCommand({
118+
transport: 'stdio',
119+
env: createGleanEnv('my-company', 'my-api-token'),
120+
});
121+
122+
expect(command).toMatchInlineSnapshot(`"claude mcp add glean_local --scope user --env GLEAN_INSTANCE=my-company --env GLEAN_API_TOKEN=my-api-token -- npx -y @gleanwork/local-mcp-server"`);
123+
});
124+
125+
it('with OAuth (instance only, no token)', () => {
126+
const command = builder.buildCommand({
127+
transport: 'stdio',
128+
env: createGleanEnv('my-company'),
129+
});
130+
131+
expect(command).toMatchInlineSnapshot(`"claude mcp add glean_local --scope user --env GLEAN_INSTANCE=my-company -- npx -y @gleanwork/local-mcp-server"`);
132+
});
133+
});
134+
135+
describe('http transport', () => {
136+
it('with token auth', () => {
137+
const command = builder.buildCommand({
138+
transport: 'http',
139+
serverUrl: buildGleanServerUrl('my-company'),
140+
headers: createGleanHeaders('my-api-token'),
141+
});
142+
143+
expect(command).toMatchInlineSnapshot(`"claude mcp add glean_default https://my-company-be.glean.com/mcp/default --transport http --scope user --header "Authorization: Bearer my-api-token""`);
144+
});
145+
146+
it('with OAuth (URL only, no token)', () => {
147+
const command = builder.buildCommand({
148+
transport: 'http',
149+
serverUrl: buildGleanServerUrl('my-company'),
150+
});
151+
152+
expect(command).toMatchInlineSnapshot(`"claude mcp add glean_default https://my-company-be.glean.com/mcp/default --transport http --scope user"`);
153+
});
154+
});
155+
});
156+
157+
describe('supportsCliInstallation', () => {
158+
it('returns status', () => {
159+
const status = builder.supportsCliInstallation();
160+
expect(status).toMatchInlineSnapshot(`
161+
{
162+
"reason": "native_cli",
163+
"supported": true,
164+
}
165+
`);
166+
});
167+
});
168+
});
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { describe, it, expect } from 'vitest';
2+
import {
3+
createGleanRegistry,
4+
createGleanEnv,
5+
createGleanHeaders,
6+
buildGleanServerUrl,
7+
} from '../../src/index.js';
8+
9+
/**
10+
* Claude Desktop: commandBuilder client (no native CLI)
11+
* Uses mcp-remote bridge for HTTP transport
12+
*/
13+
describe('Client: claude-desktop', () => {
14+
const registry = createGleanRegistry();
15+
const builder = registry.createBuilder('claude-desktop');
16+
17+
describe('buildConfiguration', () => {
18+
describe('stdio transport', () => {
19+
it('with token auth', () => {
20+
const config = builder.buildConfiguration({
21+
transport: 'stdio',
22+
env: createGleanEnv('my-company', 'my-api-token'),
23+
});
24+
25+
expect(config).toMatchInlineSnapshot(`
26+
{
27+
"mcpServers": {
28+
"glean_local": {
29+
"args": [
30+
"-y",
31+
"@gleanwork/local-mcp-server",
32+
],
33+
"command": "npx",
34+
"env": {
35+
"GLEAN_API_TOKEN": "my-api-token",
36+
"GLEAN_INSTANCE": "my-company",
37+
},
38+
"type": "stdio",
39+
},
40+
},
41+
}
42+
`);
43+
});
44+
45+
it('with OAuth (instance only, no token)', () => {
46+
const config = builder.buildConfiguration({
47+
transport: 'stdio',
48+
env: createGleanEnv('my-company'),
49+
});
50+
51+
expect(config).toMatchInlineSnapshot(`
52+
{
53+
"mcpServers": {
54+
"glean_local": {
55+
"args": [
56+
"-y",
57+
"@gleanwork/local-mcp-server",
58+
],
59+
"command": "npx",
60+
"env": {
61+
"GLEAN_INSTANCE": "my-company",
62+
},
63+
"type": "stdio",
64+
},
65+
},
66+
}
67+
`);
68+
});
69+
});
70+
71+
describe('http transport (uses mcp-remote bridge)', () => {
72+
it('with token auth', () => {
73+
const config = builder.buildConfiguration({
74+
transport: 'http',
75+
serverUrl: buildGleanServerUrl('my-company'),
76+
headers: createGleanHeaders('my-api-token'),
77+
});
78+
79+
expect(config).toMatchInlineSnapshot(`
80+
{
81+
"mcpServers": {
82+
"glean_default": {
83+
"args": [
84+
"-y",
85+
"mcp-remote",
86+
"https://my-company-be.glean.com/mcp/default",
87+
"--header",
88+
"Authorization: Bearer my-api-token",
89+
],
90+
"command": "npx",
91+
"type": "stdio",
92+
},
93+
},
94+
}
95+
`);
96+
});
97+
98+
it('with OAuth (URL only, no token)', () => {
99+
const config = builder.buildConfiguration({
100+
transport: 'http',
101+
serverUrl: buildGleanServerUrl('my-company'),
102+
});
103+
104+
expect(config).toMatchInlineSnapshot(`
105+
{
106+
"mcpServers": {
107+
"glean_default": {
108+
"args": [
109+
"-y",
110+
"mcp-remote",
111+
"https://my-company-be.glean.com/mcp/default",
112+
],
113+
"command": "npx",
114+
"type": "stdio",
115+
},
116+
},
117+
}
118+
`);
119+
});
120+
});
121+
});
122+
123+
describe('buildCommand', () => {
124+
describe('stdio transport', () => {
125+
it('with token auth', () => {
126+
const command = builder.buildCommand({
127+
transport: 'stdio',
128+
env: createGleanEnv('my-company', 'my-api-token'),
129+
});
130+
131+
expect(command).toMatchInlineSnapshot(`"npx -y @gleanwork/configure-mcp-server local --client claude-desktop --env GLEAN_INSTANCE=my-company --env GLEAN_API_TOKEN=my-api-token"`);
132+
});
133+
134+
it('with OAuth (instance only, no token)', () => {
135+
const command = builder.buildCommand({
136+
transport: 'stdio',
137+
env: createGleanEnv('my-company'),
138+
});
139+
140+
expect(command).toMatchInlineSnapshot(`"npx -y @gleanwork/configure-mcp-server local --client claude-desktop --env GLEAN_INSTANCE=my-company"`);
141+
});
142+
});
143+
144+
describe('http transport', () => {
145+
it('with token auth', () => {
146+
const command = builder.buildCommand({
147+
transport: 'http',
148+
serverUrl: buildGleanServerUrl('my-company'),
149+
headers: createGleanHeaders('my-api-token'),
150+
});
151+
152+
expect(command).toMatchInlineSnapshot(`"npx -y @gleanwork/configure-mcp-server remote --url https://my-company-be.glean.com/mcp/default --client claude-desktop --token my-api-token"`);
153+
});
154+
155+
it('with OAuth (URL only, no token)', () => {
156+
const command = builder.buildCommand({
157+
transport: 'http',
158+
serverUrl: buildGleanServerUrl('my-company'),
159+
});
160+
161+
expect(command).toMatchInlineSnapshot(`"npx -y @gleanwork/configure-mcp-server remote --url https://my-company-be.glean.com/mcp/default --client claude-desktop"`);
162+
});
163+
});
164+
});
165+
166+
describe('supportsCliInstallation', () => {
167+
it('returns status', () => {
168+
const status = builder.supportsCliInstallation();
169+
expect(status).toMatchInlineSnapshot(`
170+
{
171+
"reason": "command_builder",
172+
"supported": true,
173+
}
174+
`);
175+
});
176+
});
177+
});

0 commit comments

Comments
 (0)