Skip to content

Commit 78a1f3c

Browse files
authored
fix: DH-21945: listVariables and openVariablePanels now use right connection (#309)
DH-21945: Most of the MCP tools can handle an agent passing a DHE server URL instead of a worker URL. There was a subtle bug with listVariables and openVariablePanels where the call would succeed but found zero variables due to checking the server URL. I also added a better error message for panelUrls missing on earlier DHE servers ### Testing #### List Fixes - Connect to a DHE server. And create at least 1 table on the worker - Ask the agent to call `listVariables` tool telling it to use the server URL explicitly instead of the worker URL. It should find it. Before it would get zero results - Same general test for `openVariablePanels` but it should open the panel in VS Code #### Panel URL message - Connect to a grizzly server and create at least 1 table on the worker - Ask agent about the panelUrl for the table. It should see a message saying it isn't supported instead of speculating why it isn't there
1 parent 5d527df commit 78a1f3c

7 files changed

Lines changed: 117 additions & 21 deletions

File tree

skills/deephaven-vscode-using/SKILL.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,10 @@ When choosing from multiple servers in `listServers` response, look for:
6969

7070
**Panel URLs (for UI verification):**
7171

72-
- **DHC**: `{origin}/iframe/widget/?name={variableTitle}`
73-
- **DHE**: `{origin}/iriside/embed/widget/serial/{serial}/{variableTitle}`
72+
Community servers always support panel URLs. Enterprise servers require Grizzly+ or later.
73+
74+
- **DHC (Community)**: `{origin}/iframe/widget/?name={variableTitle}`
75+
- **DHE (Enterprise)**: `{origin}/iriside/embed/widget/serial/{serial}/{variableTitle}` (Grizzly+ or later only)
7476

7577
### Remote File Sources
7678

src/mcp/tools/listVariables.spec.ts

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,13 @@ describe('listVariables', () => {
7676
variables: [],
7777
},
7878
])('should list panel variables: $label', async ({ variables }) => {
79+
const mockConnection = {
80+
serverUrl: MOCK_PARSED_URL,
81+
} as IDhcService;
82+
7983
vi.mocked(getFirstConnectionOrCreate).mockResolvedValue({
8084
success: true,
81-
connection: {} as IDhcService,
85+
connection: mockConnection,
8286
panelUrlFormat: MOCK_PANEL_URL_FORMAT,
8387
});
8488
vi.mocked(panelService.getVariables).mockReturnValue(variables);
@@ -93,7 +97,9 @@ describe('listVariables', () => {
9397
connectionUrl: MOCK_PARSED_URL,
9498
serverManager,
9599
});
96-
expect(panelService.getVariables).toHaveBeenCalledWith(MOCK_PARSED_URL);
100+
expect(panelService.getVariables).toHaveBeenCalledWith(
101+
mockConnection.serverUrl
102+
);
97103
expect(result.structuredContent).toEqual(
98104
mcpSuccessResult(`Found ${variables.length} panel variable(s)`, {
99105
panelUrlFormat: MOCK_PANEL_URL_FORMAT,
@@ -155,10 +161,49 @@ describe('listVariables', () => {
155161
expect(panelService.getVariables).not.toHaveBeenCalled();
156162
});
157163

164+
it('should use connection serverUrl when DHE server URL is provided', async () => {
165+
// Simulates DHE scenario where server URL differs from worker URL
166+
const serverUrl = 'http://enterprise.deephaven.io';
167+
const workerUrl = new URL('http://enterprise.deephaven.io/worker-123');
168+
const mockConnection = {
169+
serverUrl: workerUrl,
170+
} as IDhcService;
171+
172+
vi.mocked(getFirstConnectionOrCreate).mockResolvedValue({
173+
success: true,
174+
connection: mockConnection,
175+
panelUrlFormat: MOCK_PANEL_URL_FORMAT,
176+
});
177+
vi.mocked(panelService.getVariables).mockReturnValue(MOCK_VARIABLES);
178+
179+
const tool = createListVariablesTool({
180+
panelService,
181+
serverManager,
182+
});
183+
const result = await tool.handler({ connectionUrl: serverUrl });
184+
185+
// Should call with the worker URL, not the server URL
186+
expect(panelService.getVariables).toHaveBeenCalledWith(workerUrl);
187+
expect(result.structuredContent).toEqual(
188+
mcpSuccessResult(`Found ${MOCK_VARIABLES.length} panel variable(s)`, {
189+
panelUrlFormat: MOCK_PANEL_URL_FORMAT,
190+
variables: MOCK_VARIABLES.map(({ id, title, type }) => ({
191+
id,
192+
title,
193+
type,
194+
})),
195+
})
196+
);
197+
});
198+
158199
it('should handle errors from panelService', async () => {
200+
const mockConnection = {
201+
serverUrl: MOCK_PARSED_URL,
202+
} as IDhcService;
203+
159204
vi.mocked(getFirstConnectionOrCreate).mockResolvedValue({
160205
success: true,
161-
connection: {} as IDhcService,
206+
connection: mockConnection,
162207
panelUrlFormat: MOCK_PANEL_URL_FORMAT,
163208
});
164209
vi.mocked(panelService.getVariables).mockImplementation(() => {

src/mcp/tools/listVariables.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,15 @@ export function createListVariablesTool({
8181
return response.errorWithHint(errorMessage, error, hint, details);
8282
}
8383

84-
const variables = [...panelService.getVariables(parsedUrl.value)].map(
85-
({ id, title, type }) => ({
86-
id,
87-
title,
88-
type,
89-
})
90-
);
84+
const { connection } = firstConnectionResult;
85+
86+
const variables = [
87+
...panelService.getVariables(connection.serverUrl),
88+
].map(({ id, title, type }) => ({
89+
id,
90+
title,
91+
type,
92+
}));
9193

9294
const message = `Found ${variables.length} panel variable(s)`;
9395

src/mcp/tools/openVariablePanels.spec.ts

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,13 @@ describe('openVariablePanels', () => {
4747
});
4848

4949
it('should open variable panels successfully', async () => {
50+
const mockConnection = {
51+
serverUrl: MOCK_PARSED_URL,
52+
} as IDhcService;
53+
5054
vi.mocked(getFirstConnectionOrCreate).mockResolvedValue({
5155
success: true,
52-
connection: {} as IDhcService,
56+
connection: mockConnection,
5357
panelUrlFormat: 'mock.panelUrlFormat',
5458
});
5559

@@ -64,7 +68,7 @@ describe('openVariablePanels', () => {
6468
connectionUrl: MOCK_PARSED_URL,
6569
});
6670
expect(execOpenVariablePanels).toHaveBeenCalledWith(
67-
MOCK_PARSED_URL,
71+
mockConnection.serverUrl,
6872
MOCK_VARIABLES
6973
);
7074
expect(result.structuredContent).toEqual(
@@ -137,10 +141,44 @@ describe('openVariablePanels', () => {
137141
);
138142
});
139143

144+
it('should use connection serverUrl when DHE server URL is provided', async () => {
145+
// Simulates DHE scenario where server URL differs from worker URL
146+
const serverUrl = 'http://enterprise.deephaven.io';
147+
const workerUrl = new URL('http://enterprise.deephaven.io/worker-123');
148+
const mockConnection = {
149+
serverUrl: workerUrl,
150+
} as IDhcService;
151+
152+
vi.mocked(getFirstConnectionOrCreate).mockResolvedValue({
153+
success: true,
154+
connection: mockConnection,
155+
panelUrlFormat: 'mock.panelUrlFormat',
156+
});
157+
158+
const tool = createOpenVariablePanelsTool({ serverManager });
159+
const result = await tool.handler({
160+
connectionUrl: serverUrl,
161+
variables: MOCK_VARIABLES,
162+
});
163+
164+
// Should call with the worker URL, not the server URL
165+
expect(execOpenVariablePanels).toHaveBeenCalledWith(
166+
workerUrl,
167+
MOCK_VARIABLES
168+
);
169+
expect(result.structuredContent).toEqual(
170+
mcpSuccessResult('Variable panels opened successfully')
171+
);
172+
});
173+
140174
it('should handle errors from execOpenVariablePanels', async () => {
175+
const mockConnection = {
176+
serverUrl: MOCK_PARSED_URL,
177+
} as IDhcService;
178+
141179
vi.mocked(getFirstConnectionOrCreate).mockResolvedValue({
142180
success: true,
143-
connection: {} as IDhcService,
181+
connection: mockConnection,
144182
panelUrlFormat: 'mock.panelUrlFormat',
145183
});
146184
vi.mocked(execOpenVariablePanels).mockRejectedValue(

src/mcp/tools/openVariablePanels.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,11 @@ export function createOpenVariablePanelsTool({
8181
);
8282
}
8383

84+
const { connection } = firstConnectionResult;
85+
8486
try {
8587
await execOpenVariablePanels(
86-
parsedUrl.value,
88+
connection.serverUrl,
8789
variables as unknown as NonEmptyArray<VariableDefintion>
8890
);
8991

src/mcp/utils/panelUtils.spec.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,21 +64,24 @@ describe('getDhePanelUrlFormat', () => {
6464
embedDashboardsAndWidgets: false,
6565
},
6666
workerInfo: undefined,
67-
expected: undefined,
67+
expected:
68+
'Panel URLs are not supported by this Enterprise server version (requires Grizzly+ or later)',
6869
},
6970
{
7071
scenario: 'embedDashboardsAndWidgets feature is not defined',
7172
dheService: mockDheService,
7273
features: { createQueryIframe: true },
7374
workerInfo: undefined,
74-
expected: undefined,
75+
expected:
76+
'Panel URLs are not supported by this Enterprise server version (requires Grizzly+ or later)',
7577
},
7678
{
7779
scenario: 'getServerFeatures returns undefined',
7880
dheService: mockDheService,
7981
features: undefined,
8082
workerInfo: undefined,
81-
expected: undefined,
83+
expected:
84+
'Panel URLs are not supported by this Enterprise server version (requires Grizzly+ or later)',
8285
},
8386
{
8487
scenario: 'worker info is not available',

src/mcp/utils/panelUtils.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,15 @@ export async function getDhePanelUrlFormat(
2929
): Promise<string | undefined> {
3030
const dheService = await serverManager.getDheServiceForWorker(connectionUrl);
3131

32-
const features = dheService?.getServerFeatures()?.features;
33-
if (features?.embedDashboardsAndWidgets !== true) {
32+
if (dheService == null) {
3433
return undefined;
3534
}
3635

36+
const features = dheService.getServerFeatures()?.features;
37+
if (features?.embedDashboardsAndWidgets !== true) {
38+
return 'Panel URLs are not supported by this Enterprise server version (requires Grizzly+ or later)';
39+
}
40+
3741
// Get worker info for DHE servers to include serial ID in panel URLs
3842
const workerInfo = await serverManager.getWorkerInfo(connectionUrl);
3943
if (workerInfo == null) {

0 commit comments

Comments
 (0)