Skip to content

Commit a0fb99e

Browse files
committed
use type narrowing instead of casting
1 parent d1e9fdc commit a0fb99e

File tree

4 files changed

+59
-58
lines changed

4 files changed

+59
-58
lines changed

packages/core/src/integrations/mcp-server/resultExtraction.ts

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
MCP_TOOL_RESULT_CONTENT_COUNT_ATTRIBUTE,
1111
MCP_TOOL_RESULT_IS_ERROR_ATTRIBUTE,
1212
} from './attributes';
13+
import { isValidContentItem } from './validation';
1314

1415
/**
1516
* Build attributes for tool result content items
@@ -22,11 +23,10 @@ function buildAllContentItemAttributes(content: unknown[]): Record<string, strin
2223
};
2324

2425
for (const [i, item] of content.entries()) {
25-
if (typeof item !== 'object' || item === null) {
26+
if (!isValidContentItem(item)) {
2627
continue;
2728
}
2829

29-
const contentItem = item as Record<string, unknown>;
3030
const prefix = content.length === 1 ? 'mcp.tool.result' : `mcp.tool.result.${i}`;
3131

3232
const safeSet = (key: string, value: unknown): void => {
@@ -35,13 +35,13 @@ function buildAllContentItemAttributes(content: unknown[]): Record<string, strin
3535
}
3636
};
3737

38-
safeSet('content_type', contentItem.type);
39-
safeSet('mime_type', contentItem.mimeType);
40-
safeSet('uri', contentItem.uri);
41-
safeSet('name', contentItem.name);
38+
safeSet('content_type', item.type);
39+
safeSet('mime_type', item.mimeType);
40+
safeSet('uri', item.uri);
41+
safeSet('name', item.name);
4242

43-
if (typeof contentItem.text === 'string') {
44-
const text = contentItem.text;
43+
if (typeof item.text === 'string') {
44+
const text = item.text;
4545
const maxLength = 500;
4646
if (text.length > maxLength) {
4747
attributes[`${prefix}.content`] = `${text.slice(0, maxLength - 3)}...`;
@@ -50,15 +50,14 @@ function buildAllContentItemAttributes(content: unknown[]): Record<string, strin
5050
}
5151
}
5252

53-
if (typeof contentItem.data === 'string') {
54-
attributes[`${prefix}.data_size`] = contentItem.data.length;
53+
if (typeof item.data === 'string') {
54+
attributes[`${prefix}.data_size`] = item.data.length;
5555
}
5656

57-
const resource = contentItem.resource;
58-
if (typeof resource === 'object' && resource !== null) {
59-
const res = resource as Record<string, unknown>;
60-
safeSet('resource_uri', res.uri);
61-
safeSet('resource_mime_type', res.mimeType);
57+
const resource = item.resource;
58+
if (isValidContentItem(resource)) {
59+
safeSet('resource_uri', resource.uri);
60+
safeSet('resource_mime_type', resource.mimeType);
6261
}
6362
}
6463

@@ -72,16 +71,15 @@ function buildAllContentItemAttributes(content: unknown[]): Record<string, strin
7271
*/
7372
export function extractToolResultAttributes(result: unknown): Record<string, string | number | boolean> {
7473
let attributes: Record<string, string | number | boolean> = {};
75-
if (typeof result !== 'object' || result === null) {
74+
if (!isValidContentItem(result)) {
7675
return attributes;
7776
}
7877

79-
const resultObj = result as Record<string, unknown>;
80-
if (typeof resultObj.isError === 'boolean') {
81-
attributes[MCP_TOOL_RESULT_IS_ERROR_ATTRIBUTE] = resultObj.isError;
78+
if (typeof result.isError === 'boolean') {
79+
attributes[MCP_TOOL_RESULT_IS_ERROR_ATTRIBUTE] = result.isError;
8280
}
83-
if (Array.isArray(resultObj.content)) {
84-
attributes = { ...attributes, ...buildAllContentItemAttributes(resultObj.content) };
81+
if (Array.isArray(result.content)) {
82+
attributes = { ...attributes, ...buildAllContentItemAttributes(result.content) };
8583
}
8684
return attributes;
8785
}
@@ -93,26 +91,23 @@ export function extractToolResultAttributes(result: unknown): Record<string, str
9391
*/
9492
export function extractPromptResultAttributes(result: unknown): Record<string, string | number | boolean> {
9593
const attributes: Record<string, string | number | boolean> = {};
96-
if (typeof result !== 'object' || result === null) {
94+
if (!isValidContentItem(result)) {
9795
return attributes;
9896
}
9997

100-
const resultObj = result as Record<string, unknown>;
101-
102-
if (typeof resultObj.description === 'string') {
103-
attributes[MCP_PROMPT_RESULT_DESCRIPTION_ATTRIBUTE] = resultObj.description;
98+
if (typeof result.description === 'string') {
99+
attributes[MCP_PROMPT_RESULT_DESCRIPTION_ATTRIBUTE] = result.description;
104100
}
105101

106-
if (Array.isArray(resultObj.messages)) {
107-
attributes[MCP_PROMPT_RESULT_MESSAGE_COUNT_ATTRIBUTE] = resultObj.messages.length;
102+
if (Array.isArray(result.messages)) {
103+
attributes[MCP_PROMPT_RESULT_MESSAGE_COUNT_ATTRIBUTE] = result.messages.length;
108104

109-
const messages = resultObj.messages;
105+
const messages = result.messages;
110106
for (const [i, message] of messages.entries()) {
111-
if (typeof message !== 'object' || message === null) {
107+
if (!isValidContentItem(message)) {
112108
continue;
113109
}
114110

115-
const messageObj = message as Record<string, unknown>;
116111
const prefix = messages.length === 1 ? 'mcp.prompt.result' : `mcp.prompt.result.${i}`;
117112

118113
const safeSet = (key: string, value: unknown): void => {
@@ -122,10 +117,10 @@ export function extractPromptResultAttributes(result: unknown): Record<string, s
122117
}
123118
};
124119

125-
safeSet('role', messageObj.role);
120+
safeSet('role', message.role);
126121

127-
if (typeof messageObj.content === 'object' && messageObj.content !== null) {
128-
const content = messageObj.content as Record<string, unknown>;
122+
if (isValidContentItem(message.content)) {
123+
const content = message.content;
129124
if (typeof content.text === 'string') {
130125
const attrName = messages.length === 1 ? `${prefix}.message_content` : `${prefix}.content`;
131126
attributes[attrName] = content.text;

packages/core/src/integrations/mcp-server/sessionExtraction.ts

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
getSessionDataForTransport,
2323
} from './sessionManagement';
2424
import type { ExtraHandlerData, JsonRpcRequest, MCPTransport, PartyInfo, SessionData } from './types';
25+
import { isValidContentItem } from './validation';
2526

2627
/**
2728
* Extracts and validates PartyInfo from an unknown object
@@ -31,16 +32,15 @@ import type { ExtraHandlerData, JsonRpcRequest, MCPTransport, PartyInfo, Session
3132
function extractPartyInfo(obj: unknown): PartyInfo {
3233
const partyInfo: PartyInfo = {};
3334

34-
if (obj && typeof obj === 'object' && obj !== null) {
35-
const source = obj as Record<string, unknown>;
36-
if (typeof source.name === 'string') {
37-
partyInfo.name = source.name;
35+
if (isValidContentItem(obj)) {
36+
if (typeof obj.name === 'string') {
37+
partyInfo.name = obj.name;
3838
}
39-
if (typeof source.title === 'string') {
40-
partyInfo.title = source.title;
39+
if (typeof obj.title === 'string') {
40+
partyInfo.title = obj.title;
4141
}
42-
if (typeof source.version === 'string') {
43-
partyInfo.version = source.version;
42+
if (typeof obj.version === 'string') {
43+
partyInfo.version = obj.version;
4444
}
4545
}
4646

@@ -54,13 +54,12 @@ function extractPartyInfo(obj: unknown): PartyInfo {
5454
*/
5555
export function extractSessionDataFromInitializeRequest(request: JsonRpcRequest): SessionData {
5656
const sessionData: SessionData = {};
57-
if (request.params && typeof request.params === 'object' && request.params !== null) {
58-
const params = request.params as Record<string, unknown>;
59-
if (typeof params.protocolVersion === 'string') {
60-
sessionData.protocolVersion = params.protocolVersion;
57+
if (isValidContentItem(request.params)) {
58+
if (typeof request.params.protocolVersion === 'string') {
59+
sessionData.protocolVersion = request.params.protocolVersion;
6160
}
62-
if (params.clientInfo) {
63-
sessionData.clientInfo = extractPartyInfo(params.clientInfo);
61+
if (request.params.clientInfo) {
62+
sessionData.clientInfo = extractPartyInfo(request.params.clientInfo);
6463
}
6564
}
6665
return sessionData;
@@ -73,13 +72,12 @@ export function extractSessionDataFromInitializeRequest(request: JsonRpcRequest)
7372
*/
7473
export function extractSessionDataFromInitializeResponse(result: unknown): Partial<SessionData> {
7574
const sessionData: Partial<SessionData> = {};
76-
if (result && typeof result === 'object') {
77-
const resultObj = result as Record<string, unknown>;
78-
if (typeof resultObj.protocolVersion === 'string') {
79-
sessionData.protocolVersion = resultObj.protocolVersion;
75+
if (isValidContentItem(result)) {
76+
if (typeof result.protocolVersion === 'string') {
77+
sessionData.protocolVersion = result.protocolVersion;
8078
}
81-
if (resultObj.serverInfo) {
82-
sessionData.serverInfo = extractPartyInfo(resultObj.serverInfo);
79+
if (result.serverInfo) {
80+
sessionData.serverInfo = extractPartyInfo(result.serverInfo);
8381
}
8482
}
8583
return sessionData;

packages/core/src/integrations/mcp-server/transport.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
} from './sessionManagement';
1919
import { buildMcpServerSpanConfig, createMcpNotificationSpan, createMcpOutgoingNotificationSpan } from './spans';
2020
import type { ExtraHandlerData, MCPTransport } from './types';
21-
import { isJsonRpcNotification, isJsonRpcRequest, isJsonRpcResponse } from './validation';
21+
import { isJsonRpcNotification, isJsonRpcRequest, isJsonRpcResponse, isValidContentItem } from './validation';
2222

2323
/**
2424
* Wraps transport.onmessage to create spans for incoming messages.
@@ -90,9 +90,8 @@ export function wrapTransportSend(transport: MCPTransport): void {
9090
captureJsonRpcErrorResponse(message.error);
9191
}
9292

93-
if (message.result && typeof message.result === 'object') {
94-
const result = message.result as Record<string, unknown>;
95-
if (result.protocolVersion || result.serverInfo) {
93+
if (isValidContentItem(message.result)) {
94+
if (message.result.protocolVersion || message.result.serverInfo) {
9695
try {
9796
const serverData = extractSessionDataFromInitializeResponse(message.result);
9897
updateSessionDataForTransport(this, serverData);

packages/core/src/integrations/mcp-server/validation.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,12 @@ export function validateMcpServerInstance(instance: unknown): boolean {
7575
DEBUG_BUILD && debug.warn('Did not patch MCP server. Interface is incompatible.');
7676
return false;
7777
}
78+
79+
/**
80+
* Check if the item is a valid content item
81+
* @param item - The item to check
82+
* @returns True if the item is a valid content item, false otherwise
83+
*/
84+
export function isValidContentItem(item: unknown): item is Record<string, unknown> {
85+
return item != null && typeof item === 'object';
86+
}

0 commit comments

Comments
 (0)