Skip to content

Commit 431c8ae

Browse files
JAORMXclaude
andcommitted
fix(dashboard): add defensive null checks for MCP host compatibility
Add null/undefined checks throughout the MCP client and dashboard code to handle hosts that may pass null values in callbacks. This improves compatibility with different MCP Apps hosts. Changes: - Add null checks in ontoolresult, ontoolinput, onhostcontextchanged handlers - Wrap supportsServerTools() in try/catch with null check - Add null check for callServerTool result - Add defensive checks in handleToolResult and handleToolInput Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent a9fc3f0 commit 431c8ae

File tree

2 files changed

+51
-6
lines changed

2 files changed

+51
-6
lines changed

ui/compliance-dashboard/src/dashboard.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,15 +153,23 @@ export function initDashboard(): void {
153153
* This is called when the LLM invokes a Minder tool and the host pushes the result.
154154
*/
155155
function handleToolResult(result: ToolResultParams): void {
156+
// Defensive null check - result may be null/undefined from some hosts
157+
if (!result) {
158+
console.warn('[Dashboard] Received null/undefined tool result');
159+
return;
160+
}
161+
156162
// Extract the tool name and content from the result
157163
// The result may contain structuredContent or content array
158164
let data: unknown = null;
159165

160166
// Check for structuredContent first (preferred for rich data)
167+
// Use defensive access to avoid "Cannot convert undefined or null to object"
161168
const structuredContent = result.structuredContent as
162169
| Record<string, unknown>
163-
| undefined;
164-
if (structuredContent) {
170+
| undefined
171+
| null;
172+
if (structuredContent && typeof structuredContent === 'object') {
165173
data = structuredContent;
166174
} else if (
167175
result.content &&
@@ -267,6 +275,11 @@ function isRepositoriesApiResponse(data: unknown): data is RepositoriesApiRespon
267275
* Extracts project_id from arguments for use in subsequent calls.
268276
*/
269277
function handleToolInput(input: ToolInputParams): void {
278+
// Defensive null check - input may be null/undefined from some hosts
279+
if (!input) {
280+
console.warn('[Dashboard] Received null/undefined tool input');
281+
return;
282+
}
270283
// Extract project_id from the tool arguments if present
271284
const args = input.arguments as Record<string, unknown> | undefined;
272285
if (args && typeof args.project_id === 'string' && args.project_id) {

ui/compliance-dashboard/src/mcp-client.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ export class MCPAppsClient {
6666
this.onToolResultCallback = callback;
6767
this.app.ontoolresult = (result): void => {
6868
console.log('[MCP] Received tool result notification:', result);
69+
// Defensive null check - some hosts may pass null/undefined
70+
if (!result) {
71+
console.warn('[MCP] Received null/undefined tool result');
72+
return;
73+
}
6974
if (this.onToolResultCallback) {
7075
this.onToolResultCallback(result);
7176
}
@@ -80,6 +85,11 @@ export class MCPAppsClient {
8085
this.onToolInputCallback = callback;
8186
this.app.ontoolinput = (input): void => {
8287
console.log('[MCP] Received tool input notification:', input);
88+
// Defensive null check - some hosts may pass null/undefined
89+
if (!input) {
90+
console.warn('[MCP] Received null/undefined tool input');
91+
return;
92+
}
8393
if (this.onToolInputCallback) {
8494
this.onToolInputCallback(input);
8595
}
@@ -94,6 +104,11 @@ export class MCPAppsClient {
94104
this.onDimensionsCallback = callback;
95105
this.app.onhostcontextchanged = (context): void => {
96106
console.log('[MCP] Received host context change:', context);
107+
// Defensive null check - some hosts (e.g., Goose) may pass null/undefined
108+
if (!context) {
109+
console.warn('[MCP] Received null/undefined host context change');
110+
return;
111+
}
97112
if (context.containerDimensions && this.onDimensionsCallback) {
98113
const dims = context.containerDimensions as ContainerDimensions;
99114
this.onDimensionsCallback(dims);
@@ -117,9 +132,19 @@ export class MCPAppsClient {
117132
* Check if the host supports calling server tools directly.
118133
*/
119134
supportsServerTools(): boolean {
120-
const caps = this.app.getHostCapabilities();
121-
// serverTools is an object when supported, undefined when not
122-
return caps?.serverTools !== undefined;
135+
try {
136+
const caps = this.app.getHostCapabilities();
137+
// serverTools is an object when supported, undefined when not
138+
// Some hosts may return null instead of an object
139+
if (!caps) {
140+
console.log('[MCP] Host capabilities is null/undefined');
141+
return false;
142+
}
143+
return caps.serverTools !== undefined;
144+
} catch (error) {
145+
console.warn('[MCP] Error checking host capabilities:', error);
146+
return false;
147+
}
123148
}
124149

125150
async disconnect(): Promise<void> {
@@ -166,9 +191,15 @@ export class MCPAppsClient {
166191

167192
const result = await this.app.callServerTool({
168193
name,
169-
arguments: args,
194+
arguments: args ?? {},
170195
});
171196

197+
// Defensive null check for result
198+
if (!result) {
199+
console.warn('[MCP] callServerTool returned null/undefined');
200+
throw new Error('Tool call returned no result');
201+
}
202+
172203
// Extract the content from the tool result
173204
if (
174205
result.content &&
@@ -177,6 +208,7 @@ export class MCPAppsClient {
177208
) {
178209
const firstContent = result.content[0];
179210
if (
211+
firstContent &&
180212
firstContent.type === 'text' &&
181213
typeof firstContent.text === 'string'
182214
) {

0 commit comments

Comments
 (0)