Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions packages/mcp/src/sf-mcp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,38 @@ export class SfMcpServer extends McpServer implements ToolMethodSignatures {
};
}

/**
* Calculates the total character count from tool result content and structured output.
* Used for token usage. Accounts for both:
* - content: text (and other) content items
* - structuredContent: structured tool output when the tool defines an outputSchema
* @see https://modelcontextprotocol.io/specification/2025-11-25/server/tools#output-schema
* @param result - The CallToolResult from tool execution
* @returns Total character count across text content and structured content
*/
private calculateResponseCharCount(result: CallToolResult): number {
let total = 0;

// Plain text (and other) content items
if (result.content && Array.isArray(result.content)) {
total += result.content
.filter((item): item is { type: 'text'; text: string } => item.type === 'text')
.reduce((sum, item) => sum + item.text.length, 0);
}

// Structured content (JSON object per outputSchema)
const structured = (result as CallToolResult & { structuredContent?: unknown }).structuredContent;
if (structured !== undefined && structured !== null && typeof structured === 'object') {
try {
total += JSON.stringify(structured).length;
} catch {
// ignore serialization errors
}
}

return total;
}

public registerTool<InputArgs extends ZodRawShape, OutputArgs extends ZodRawShape>(
name: string,
config: {
Expand Down Expand Up @@ -141,6 +173,9 @@ export class SfMcpServer extends McpServer implements ToolMethodSignatures {
this.logger.debug(`Tool ${name} completed in ${runtimeMs}ms`);
if (result.isError) this.logger.debug(`Tool ${name} errored`);

// Calculate response character count for token usage
const responseCharCount = this.calculateResponseCharCount(result);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method only considers plain text from tool responses but tools can also have structured output, see:

Image

https://modelcontextprotocol.io/specification/2025-11-25/server/tools#output-schema

I think the safe way would be to account for both since a tool seems to be able to return plain text + structured output


this.telemetry?.sendEvent('TOOL_CALLED', {
name,
runtimeMs,
Expand All @@ -151,6 +186,7 @@ export class SfMcpServer extends McpServer implements ToolMethodSignatures {
//
// https://modelcontextprotocol.io/specification/2025-06-18/schema#calltoolresult
isError: result.isError ?? false,
responseCharCount: responseCharCount.toString(),
});

this.telemetry?.sendPdpEvent({
Expand Down
Loading