mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-18 21:31:46 +00:00
feat(langfuse): add tool-level spans with input/output to traces
Wrap each MCP tool handler in withSpan()/withSpanContext() to create individual named spans per tool call: - ask262_search_spec_sections - ask262_get_section_content - ask262_evaluate_in_engine262 Each span includes: - langfuse.observation.input: JSON of tool arguments (query, sectionIds, code) - tool name and relevant metadata as span attributes This makes the Langfuse trace hierarchy show which MCP tool was called and what input it received, nested under the root HTTP/stdio request span.
This commit is contained in:
+44
-2
@@ -36,7 +36,7 @@ import {
|
|||||||
import { DEFAULT_PORT, STORAGE_DIR as STORAGE_DIR_REL } from "./constants.js";
|
import { DEFAULT_PORT, STORAGE_DIR as STORAGE_DIR_REL } from "./constants.js";
|
||||||
import { createEmbeddings } from "./lib/embeddings-factory.js";
|
import { createEmbeddings } from "./lib/embeddings-factory.js";
|
||||||
import { LogOperation, logger } from "./lib/logger.js";
|
import { LogOperation, logger } from "./lib/logger.js";
|
||||||
import { withSpan } from "./lib/tracing.js";
|
import { getSessionMetadata, setupTracing, withSpan } from "./lib/tracing.js";
|
||||||
|
|
||||||
// Resolve storage path relative to this script's directory
|
// Resolve storage path relative to this script's directory
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
@@ -90,6 +90,14 @@ async function createMcpServer() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async ({ query }) => {
|
async ({ query }) => {
|
||||||
|
return await withSpan(
|
||||||
|
"ask262_search_spec_sections",
|
||||||
|
{
|
||||||
|
"langfuse.observation.input": JSON.stringify({ query }),
|
||||||
|
tool: searchSpecToolName,
|
||||||
|
query,
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
const result = await searchSpecTool({ query });
|
const result = await searchSpecTool({ query });
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
||||||
@@ -98,6 +106,8 @@ async function createMcpServer() {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Register get section content tool
|
// Register get section content tool
|
||||||
server.registerTool(
|
server.registerTool(
|
||||||
@@ -113,6 +123,17 @@ async function createMcpServer() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async ({ sectionIds, recursive }) => {
|
async ({ sectionIds, recursive }) => {
|
||||||
|
return await withSpan(
|
||||||
|
"ask262_get_section_content",
|
||||||
|
{
|
||||||
|
"langfuse.observation.input": JSON.stringify({
|
||||||
|
sectionIds,
|
||||||
|
recursive,
|
||||||
|
}),
|
||||||
|
tool: sectionContentToolName,
|
||||||
|
section_count: sectionIds.length,
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
const result = await getSectionContentTool({ sectionIds, recursive });
|
const result = await getSectionContentTool({ sectionIds, recursive });
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
||||||
@@ -121,6 +142,8 @@ async function createMcpServer() {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Register evaluate in engine262 tool
|
// Register evaluate in engine262 tool
|
||||||
server.registerTool(
|
server.registerTool(
|
||||||
@@ -136,9 +159,21 @@ async function createMcpServer() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async ({ code }) => {
|
async ({ code }) => {
|
||||||
|
return await withSpan(
|
||||||
|
"ask262_evaluate_in_engine262",
|
||||||
|
{
|
||||||
|
"langfuse.observation.input": JSON.stringify({
|
||||||
|
code: code.slice(0, 200),
|
||||||
|
}),
|
||||||
|
tool: evaluateToolName,
|
||||||
|
code_length: code.length,
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
const result = await evaluateTool({ code });
|
const result = await evaluateTool({ code });
|
||||||
const isError = result.error !== undefined;
|
const isError = result.error !== undefined;
|
||||||
const text = isError ? result.error : JSON.stringify(result, null, 2);
|
const text = isError
|
||||||
|
? result.error
|
||||||
|
: JSON.stringify(result, null, 2);
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text }],
|
content: [{ type: "text", text }],
|
||||||
structuredContent: result,
|
structuredContent: result,
|
||||||
@@ -146,6 +181,8 @@ async function createMcpServer() {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Register prompt for tool orchestration guidance
|
// Register prompt for tool orchestration guidance
|
||||||
server.registerPrompt("ask", askPromptMetadata, async ({ question }) =>
|
server.registerPrompt("ask", askPromptMetadata, async ({ question }) =>
|
||||||
@@ -156,6 +193,9 @@ async function createMcpServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function main() {
|
export async function main() {
|
||||||
|
// Initialize tracing (registers Langfuse processor when enabled)
|
||||||
|
setupTracing();
|
||||||
|
|
||||||
// Initialize HTTP server logger
|
// Initialize HTTP server logger
|
||||||
const log = await logger.forComponent("http-server");
|
const log = await logger.forComponent("http-server");
|
||||||
|
|
||||||
@@ -217,6 +257,7 @@ export async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle request within trace context (passing trace ID from header if available)
|
// Handle request within trace context (passing trace ID from header if available)
|
||||||
|
const sessionMetadata = getSessionMetadata("http");
|
||||||
return await withSpan(
|
return await withSpan(
|
||||||
LogOperation.HANDLING_MCP_HTTP_REQUEST,
|
LogOperation.HANDLING_MCP_HTTP_REQUEST,
|
||||||
{ method: c.req.method, client_ip: clientIp },
|
{ method: c.req.method, client_ip: clientIp },
|
||||||
@@ -257,6 +298,7 @@ export async function main() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
traceId,
|
traceId,
|
||||||
|
sessionMetadata,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+35
-7
@@ -37,7 +37,12 @@ import {
|
|||||||
import { STORAGE_DIR as STORAGE_DIR_REL } from "./constants.js";
|
import { STORAGE_DIR as STORAGE_DIR_REL } from "./constants.js";
|
||||||
import { createEmbeddings } from "./lib/embeddings-factory.js";
|
import { createEmbeddings } from "./lib/embeddings-factory.js";
|
||||||
import { LogOperation, logger } from "./lib/logger.js";
|
import { LogOperation, logger } from "./lib/logger.js";
|
||||||
import { createProcessScopedTrace, withSpanContext } from "./lib/tracing.js";
|
import {
|
||||||
|
createProcessScopedTrace,
|
||||||
|
getSessionMetadata,
|
||||||
|
setupTracing,
|
||||||
|
withSpanContext,
|
||||||
|
} from "./lib/tracing.js";
|
||||||
|
|
||||||
// Resolve storage path relative to this script's directory
|
// Resolve storage path relative to this script's directory
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
@@ -90,6 +95,9 @@ export interface SearchSpecMCPOutput extends McpToolOutputBase {
|
|||||||
const embeddings = createEmbeddings();
|
const embeddings = createEmbeddings();
|
||||||
|
|
||||||
export async function main() {
|
export async function main() {
|
||||||
|
// Initialize tracing (registers Langfuse processor when enabled)
|
||||||
|
setupTracing();
|
||||||
|
|
||||||
// Initialize stdio server logger
|
// Initialize stdio server logger
|
||||||
const log = await logger.forComponent("stdio-server");
|
const log = await logger.forComponent("stdio-server");
|
||||||
|
|
||||||
@@ -139,8 +147,12 @@ export async function main() {
|
|||||||
async ({ query }: SearchSpecMCPInput): Promise<SearchSpecMCPOutput> => {
|
async ({ query }: SearchSpecMCPInput): Promise<SearchSpecMCPOutput> => {
|
||||||
return await withSpanContext(
|
return await withSpanContext(
|
||||||
sessionTraceId,
|
sessionTraceId,
|
||||||
"vector_search",
|
"ask262_search_spec_sections",
|
||||||
{ tool: searchSpecToolName, query },
|
{
|
||||||
|
"langfuse.observation.input": JSON.stringify({ query }),
|
||||||
|
tool: searchSpecToolName,
|
||||||
|
query,
|
||||||
|
},
|
||||||
async () => {
|
async () => {
|
||||||
const result = await searchSpecTool({ query });
|
const result = await searchSpecTool({ query });
|
||||||
return {
|
return {
|
||||||
@@ -149,6 +161,7 @@ export async function main() {
|
|||||||
isError: false,
|
isError: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
getSessionMetadata("stdio"),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -172,8 +185,15 @@ export async function main() {
|
|||||||
}: GetSectionContentMCPInput): Promise<GetSectionContentMCPOutput> => {
|
}: GetSectionContentMCPInput): Promise<GetSectionContentMCPOutput> => {
|
||||||
return await withSpanContext(
|
return await withSpanContext(
|
||||||
sessionTraceId,
|
sessionTraceId,
|
||||||
"section_fetch",
|
"ask262_get_section_content",
|
||||||
{ tool: sectionContentToolName, section_count: sectionIds.length },
|
{
|
||||||
|
"langfuse.observation.input": JSON.stringify({
|
||||||
|
sectionIds,
|
||||||
|
recursive,
|
||||||
|
}),
|
||||||
|
tool: sectionContentToolName,
|
||||||
|
section_count: sectionIds.length,
|
||||||
|
},
|
||||||
async () => {
|
async () => {
|
||||||
const result = await getSectionContentTool({ sectionIds, recursive });
|
const result = await getSectionContentTool({ sectionIds, recursive });
|
||||||
return {
|
return {
|
||||||
@@ -182,6 +202,7 @@ export async function main() {
|
|||||||
isError: false,
|
isError: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
getSessionMetadata("stdio"),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -202,8 +223,14 @@ export async function main() {
|
|||||||
async ({ code }: EvaluateToolMCPInput): Promise<EvaluateToolMCPOutput> => {
|
async ({ code }: EvaluateToolMCPInput): Promise<EvaluateToolMCPOutput> => {
|
||||||
return await withSpanContext(
|
return await withSpanContext(
|
||||||
sessionTraceId,
|
sessionTraceId,
|
||||||
"code_execution",
|
"ask262_evaluate_in_engine262",
|
||||||
{ tool: evaluateToolName, code_length: code.length },
|
{
|
||||||
|
"langfuse.observation.input": JSON.stringify({
|
||||||
|
code: code.slice(0, 200),
|
||||||
|
}),
|
||||||
|
tool: evaluateToolName,
|
||||||
|
code_length: code.length,
|
||||||
|
},
|
||||||
async () => {
|
async () => {
|
||||||
const result = await evaluateTool({ code });
|
const result = await evaluateTool({ code });
|
||||||
const isError = result.error !== undefined;
|
const isError = result.error !== undefined;
|
||||||
@@ -214,6 +241,7 @@ export async function main() {
|
|||||||
isError,
|
isError,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
getSessionMetadata("stdio"),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user