within-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -0
- package/README.md +77 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +243 -0
- package/dist/middleware.d.ts +40 -0
- package/dist/middleware.js +562 -0
- package/dist/modules/compatibility.d.ts +18 -0
- package/dist/modules/compatibility.js +99 -0
- package/dist/modules/constants.d.ts +12 -0
- package/dist/modules/constants.js +12 -0
- package/dist/modules/context-parameters.d.ts +13 -0
- package/dist/modules/context-parameters.js +74 -0
- package/dist/modules/diagnostics.d.ts +7 -0
- package/dist/modules/diagnostics.js +12 -0
- package/dist/modules/eventQueue.d.ts +32 -0
- package/dist/modules/eventQueue.js +234 -0
- package/dist/modules/exceptions.d.ts +13 -0
- package/dist/modules/exceptions.js +730 -0
- package/dist/modules/exporters/datadog.d.ts +17 -0
- package/dist/modules/exporters/datadog.js +166 -0
- package/dist/modules/exporters/otlp.d.ts +13 -0
- package/dist/modules/exporters/otlp.js +140 -0
- package/dist/modules/exporters/posthog.d.ts +32 -0
- package/dist/modules/exporters/posthog.js +272 -0
- package/dist/modules/exporters/sentry.d.ts +27 -0
- package/dist/modules/exporters/sentry.js +386 -0
- package/dist/modules/exporters/trace-context.d.ts +8 -0
- package/dist/modules/exporters/trace-context.js +27 -0
- package/dist/modules/index.d.ts +8 -0
- package/dist/modules/index.js +8 -0
- package/dist/modules/internal.d.ts +33 -0
- package/dist/modules/internal.js +195 -0
- package/dist/modules/logging.d.ts +2 -0
- package/dist/modules/logging.js +74 -0
- package/dist/modules/mcp-sdk-compat.d.ts +32 -0
- package/dist/modules/mcp-sdk-compat.js +111 -0
- package/dist/modules/privacy.d.ts +15 -0
- package/dist/modules/privacy.js +179 -0
- package/dist/modules/redaction.d.ts +11 -0
- package/dist/modules/redaction.js +81 -0
- package/dist/modules/sanitization.d.ts +9 -0
- package/dist/modules/sanitization.js +111 -0
- package/dist/modules/session.d.ts +22 -0
- package/dist/modules/session.js +109 -0
- package/dist/modules/telemetry.d.ts +8 -0
- package/dist/modules/telemetry.js +53 -0
- package/dist/modules/tools.d.ts +25 -0
- package/dist/modules/tools.js +119 -0
- package/dist/modules/tracing.d.ts +4 -0
- package/dist/modules/tracing.js +263 -0
- package/dist/modules/tracingV2.d.ts +2 -0
- package/dist/modules/tracingV2.js +300 -0
- package/dist/modules/truncation.d.ts +24 -0
- package/dist/modules/truncation.js +303 -0
- package/dist/modules/validation.d.ts +6 -0
- package/dist/modules/validation.js +51 -0
- package/dist/network.d.ts +83 -0
- package/dist/network.js +411 -0
- package/dist/proxy.d.ts +31 -0
- package/dist/proxy.js +158 -0
- package/dist/thirdparty/ksuid/base-convert-int-array.js +49 -0
- package/dist/thirdparty/ksuid/base62.js +24 -0
- package/dist/thirdparty/ksuid/index.d.ts +30 -0
- package/dist/thirdparty/ksuid/index.js +201 -0
- package/dist/types.d.ts +197 -0
- package/dist/types.js +5 -0
- package/dist/validate.d.ts +53 -0
- package/dist/validate.js +139 -0
- package/package.json +43 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { getServerTrackingData, setServerTrackingData } from "./internal.js";
|
|
2
|
+
import KSUID from "../thirdparty/ksuid/index.js";
|
|
3
|
+
import { createHash } from "crypto";
|
|
4
|
+
import { INACTIVITY_TIMEOUT_IN_MINUTES, SDK_VERSION } from "./constants.js";
|
|
5
|
+
export function newSessionId() {
|
|
6
|
+
return KSUID.withPrefix("ses").randomSync();
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Creates a deterministic KSUID session ID from an MCP sessionId and optional projectId.
|
|
10
|
+
* The same inputs will always produce the same session ID, enabling correlation across server restarts.
|
|
11
|
+
*
|
|
12
|
+
* @param mcpSessionId - The session ID from the MCP protocol
|
|
13
|
+
* @param projectId - Optional Within project ID to include in the hash
|
|
14
|
+
* @returns A KSUID with "ses" prefix derived deterministically from the inputs
|
|
15
|
+
*/
|
|
16
|
+
export function deriveSessionIdFromMCPSession(mcpSessionId, projectId) {
|
|
17
|
+
// Create input string for hashing
|
|
18
|
+
const input = projectId ? `${mcpSessionId}:${projectId}` : mcpSessionId;
|
|
19
|
+
// Hash the input with SHA-256
|
|
20
|
+
const hash = createHash("sha256").update(input).digest();
|
|
21
|
+
// Extract timestamp from first 4 bytes of hash (for deterministic but reasonable timestamp)
|
|
22
|
+
// We'll use a fixed epoch (2024-01-01) plus the hash value to get a deterministic but valid timestamp
|
|
23
|
+
const EPOCH_2024 = new Date("2024-01-01T00:00:00Z").getTime();
|
|
24
|
+
const timestampOffset = hash.readUInt32BE(0) % (365 * 24 * 60 * 60 * 1000); // Max 1 year offset
|
|
25
|
+
const timestamp = EPOCH_2024 + timestampOffset;
|
|
26
|
+
// Use the remaining 16 bytes of hash as the KSUID payload
|
|
27
|
+
const payload = hash.subarray(4, 20);
|
|
28
|
+
// Create deterministic KSUID with prefix
|
|
29
|
+
return KSUID.withPrefix("ses").fromParts(timestamp, payload);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Gets or generates a session ID for the server.
|
|
33
|
+
* Prioritizes MCP protocol sessionId over Within-generated sessionId.
|
|
34
|
+
*
|
|
35
|
+
* @param server - The MCP server instance
|
|
36
|
+
* @param extra - Optional extra data containing MCP sessionId
|
|
37
|
+
* @returns The session ID to use for events
|
|
38
|
+
*/
|
|
39
|
+
export function getServerSessionId(server, extra) {
|
|
40
|
+
const data = getServerTrackingData(server);
|
|
41
|
+
if (!data) {
|
|
42
|
+
throw new Error("Server tracking data not found");
|
|
43
|
+
}
|
|
44
|
+
const mcpSessionId = extra?.sessionId;
|
|
45
|
+
// If MCP sessionId is provided
|
|
46
|
+
if (mcpSessionId) {
|
|
47
|
+
// Derive deterministic KSUID from MCP sessionId
|
|
48
|
+
data.sessionId = deriveSessionIdFromMCPSession(mcpSessionId, data.projectId || undefined);
|
|
49
|
+
data.lastMcpSessionId = mcpSessionId;
|
|
50
|
+
data.sessionSource = "mcp";
|
|
51
|
+
setServerTrackingData(server, data);
|
|
52
|
+
// If MCP sessionId hasn't changed, continue using the existing derived KSUID
|
|
53
|
+
setLastActivity(server);
|
|
54
|
+
return data.sessionId;
|
|
55
|
+
}
|
|
56
|
+
// No MCP sessionId provided - handle Within-generated sessions
|
|
57
|
+
// If we had an MCP sessionId before but it disappeared, keep using the last derived ID
|
|
58
|
+
if (data.sessionSource === "mcp" && data.lastMcpSessionId) {
|
|
59
|
+
setLastActivity(server);
|
|
60
|
+
return data.sessionId;
|
|
61
|
+
}
|
|
62
|
+
// For Within-generated sessions, apply timeout logic
|
|
63
|
+
const now = Date.now();
|
|
64
|
+
const timeoutMs = INACTIVITY_TIMEOUT_IN_MINUTES * 60 * 1000;
|
|
65
|
+
// If last activity timed out
|
|
66
|
+
if (now - data.lastActivity.getTime() > timeoutMs) {
|
|
67
|
+
data.sessionId = newSessionId();
|
|
68
|
+
data.sessionSource = "within";
|
|
69
|
+
setServerTrackingData(server, data);
|
|
70
|
+
}
|
|
71
|
+
setLastActivity(server);
|
|
72
|
+
return data.sessionId;
|
|
73
|
+
}
|
|
74
|
+
export function setLastActivity(server) {
|
|
75
|
+
const data = getServerTrackingData(server);
|
|
76
|
+
if (!data) {
|
|
77
|
+
throw new Error("Server tracking data not found");
|
|
78
|
+
}
|
|
79
|
+
data.lastActivity = new Date();
|
|
80
|
+
setServerTrackingData(server, data);
|
|
81
|
+
}
|
|
82
|
+
export function getSessionInfo(server, data) {
|
|
83
|
+
let clientInfo = {
|
|
84
|
+
name: undefined,
|
|
85
|
+
version: undefined,
|
|
86
|
+
};
|
|
87
|
+
if (!data?.sessionInfo.clientName) {
|
|
88
|
+
clientInfo = server.getClientVersion();
|
|
89
|
+
}
|
|
90
|
+
const actorInfo = data?.identifiedSessions.get(data.sessionId);
|
|
91
|
+
const sessionInfo = {
|
|
92
|
+
ipAddress: undefined, // grab from django
|
|
93
|
+
sdkLanguage: "TypeScript", // hardcoded for now
|
|
94
|
+
sdkVersion: SDK_VERSION,
|
|
95
|
+
serverName: server._serverInfo?.name,
|
|
96
|
+
serverVersion: server._serverInfo?.version,
|
|
97
|
+
clientName: clientInfo?.name,
|
|
98
|
+
clientVersion: clientInfo?.version,
|
|
99
|
+
identifyActorGivenId: actorInfo?.userId ?? data?.sessionInfo.identifyActorGivenId,
|
|
100
|
+
identifyActorName: actorInfo?.userName,
|
|
101
|
+
identifyActorData: actorInfo?.userData || data?.sessionInfo.identifyActorData || {},
|
|
102
|
+
};
|
|
103
|
+
if (!data) {
|
|
104
|
+
return sessionInfo;
|
|
105
|
+
}
|
|
106
|
+
data.sessionInfo = sessionInfo;
|
|
107
|
+
setServerTrackingData(server, data);
|
|
108
|
+
return data.sessionInfo;
|
|
109
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Event, ExporterConfig } from "../types.js";
|
|
2
|
+
export declare class TelemetryManager {
|
|
3
|
+
private exporters;
|
|
4
|
+
constructor(exporterConfigs?: Record<string, ExporterConfig>);
|
|
5
|
+
private createExporter;
|
|
6
|
+
export(event: Event): Promise<void>;
|
|
7
|
+
getExporterCount(): number;
|
|
8
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { writeToLog } from "./logging.js";
|
|
2
|
+
import { OTLPExporter } from "./exporters/otlp.js";
|
|
3
|
+
import { DatadogExporter } from "./exporters/datadog.js";
|
|
4
|
+
import { SentryExporter } from "./exporters/sentry.js";
|
|
5
|
+
import { PostHogExporter } from "./exporters/posthog.js";
|
|
6
|
+
export class TelemetryManager {
|
|
7
|
+
exporters = new Map();
|
|
8
|
+
constructor(exporterConfigs) {
|
|
9
|
+
if (!exporterConfigs)
|
|
10
|
+
return;
|
|
11
|
+
for (const [name, config] of Object.entries(exporterConfigs)) {
|
|
12
|
+
try {
|
|
13
|
+
const exporter = this.createExporter(name, config);
|
|
14
|
+
if (exporter) {
|
|
15
|
+
this.exporters.set(name, exporter);
|
|
16
|
+
writeToLog(`Initialized telemetry exporter: ${name}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
writeToLog(`Failed to initialize exporter ${name}: ${error}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
createExporter(name, config) {
|
|
25
|
+
switch (config.type) {
|
|
26
|
+
case "otlp":
|
|
27
|
+
return new OTLPExporter(config);
|
|
28
|
+
case "datadog":
|
|
29
|
+
return new DatadogExporter(config);
|
|
30
|
+
case "sentry":
|
|
31
|
+
return new SentryExporter(config);
|
|
32
|
+
case "posthog":
|
|
33
|
+
return new PostHogExporter(config);
|
|
34
|
+
default:
|
|
35
|
+
writeToLog(`Unknown exporter type: ${config.type}`);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async export(event) {
|
|
40
|
+
if (this.exporters.size === 0)
|
|
41
|
+
return;
|
|
42
|
+
// Telemetry errors should be logged but not propagated
|
|
43
|
+
for (const [name, exporter] of this.exporters) {
|
|
44
|
+
exporter.export(event).catch((error) => {
|
|
45
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
46
|
+
writeToLog(`Telemetry export failed for ${name}: ${errorMessage}`);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
getExporterCount() {
|
|
51
|
+
return this.exporters.size;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { MCPServerLike } from "../types.js";
|
|
2
|
+
export declare const GET_MORE_TOOLS_NAME: "get_more_tools";
|
|
3
|
+
export declare function getReportMissingToolDescriptor(): {
|
|
4
|
+
readonly name: "get_more_tools";
|
|
5
|
+
readonly description: "Check for additional tools whenever your task might benefit from specialized capabilities - even if existing tools could work as a fallback.";
|
|
6
|
+
readonly inputSchema: {
|
|
7
|
+
readonly type: "object";
|
|
8
|
+
readonly properties: {
|
|
9
|
+
readonly context: {
|
|
10
|
+
readonly type: "string";
|
|
11
|
+
readonly description: "A description of your goal and what kind of tool would help accomplish it.";
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
readonly required: readonly ["context"];
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export declare function handleReportMissing(args: {
|
|
18
|
+
context: string;
|
|
19
|
+
}): {
|
|
20
|
+
content: {
|
|
21
|
+
type: "text";
|
|
22
|
+
text: string;
|
|
23
|
+
}[];
|
|
24
|
+
};
|
|
25
|
+
export declare function setupWithinFeedbackTools(server: MCPServerLike): void;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { writeToLog } from "./logging.js";
|
|
3
|
+
import { getServerTrackingData, handleIdentify } from "./internal.js";
|
|
4
|
+
import { addContextParameterToTools } from "./context-parameters.js";
|
|
5
|
+
import { publishEvent } from "./eventQueue.js";
|
|
6
|
+
import { getServerSessionId } from "./session.js";
|
|
7
|
+
import { WithinEventType } from "./constants.js";
|
|
8
|
+
import { getMCPCompatibleErrorMessage } from "./compatibility.js";
|
|
9
|
+
export const GET_MORE_TOOLS_NAME = "get_more_tools";
|
|
10
|
+
export function getReportMissingToolDescriptor() {
|
|
11
|
+
return {
|
|
12
|
+
name: GET_MORE_TOOLS_NAME,
|
|
13
|
+
description: "Check for additional tools whenever your task might benefit from specialized capabilities - even if existing tools could work as a fallback.",
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
context: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "A description of your goal and what kind of tool would help accomplish it.",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
required: ["context"],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function handleReportMissing(args) {
|
|
27
|
+
writeToLog(`Missing tool reported (context length: ${args?.context?.length ?? 0})`);
|
|
28
|
+
return {
|
|
29
|
+
content: [
|
|
30
|
+
{
|
|
31
|
+
type: "text",
|
|
32
|
+
text: `Unfortunately, we have shown you the full tool list. We have noted your feedback and will work to improve the tool list in the future.`,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function setupWithinFeedbackTools(server) {
|
|
38
|
+
// Store reference to original handlers - need to use the method name, not the schema
|
|
39
|
+
const handlers = server._requestHandlers;
|
|
40
|
+
const originalListToolsHandler = handlers.get("tools/list");
|
|
41
|
+
const originalCallToolHandler = handlers.get("tools/call");
|
|
42
|
+
if (!originalListToolsHandler || !originalCallToolHandler) {
|
|
43
|
+
writeToLog("Warning: Original tool handlers not found. Your tools may not be setup before Within track().");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Override tools list to include get_more_tools and add context parameter
|
|
47
|
+
try {
|
|
48
|
+
server.setRequestHandler(ListToolsRequestSchema, async (request, extra) => {
|
|
49
|
+
let tools = [];
|
|
50
|
+
const data = getServerTrackingData(server);
|
|
51
|
+
let event = {
|
|
52
|
+
sessionId: getServerSessionId(server, extra),
|
|
53
|
+
parameters: {
|
|
54
|
+
request: request,
|
|
55
|
+
extra: extra,
|
|
56
|
+
},
|
|
57
|
+
eventType: WithinEventType.mcpToolsList,
|
|
58
|
+
timestamp: new Date(),
|
|
59
|
+
redactionFn: data?.options.redactSensitiveInformation,
|
|
60
|
+
};
|
|
61
|
+
if (data) {
|
|
62
|
+
await handleIdentify(server, data, request, extra);
|
|
63
|
+
event.sessionId = data.sessionId;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const originalResponse = (await originalListToolsHandler(request, extra));
|
|
67
|
+
tools = originalResponse.tools || [];
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
// If original handler fails, start with empty tools
|
|
71
|
+
writeToLog(`Warning: Original list tools handler failed, this suggests an error Within did not cause - ${error}`);
|
|
72
|
+
event.error = { message: getMCPCompatibleErrorMessage(error) };
|
|
73
|
+
event.isError = true;
|
|
74
|
+
event.duration =
|
|
75
|
+
(event.timestamp &&
|
|
76
|
+
new Date().getTime() - event.timestamp.getTime()) ||
|
|
77
|
+
0;
|
|
78
|
+
publishEvent(server, event);
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
if (!data) {
|
|
82
|
+
writeToLog("Warning: Within SDK is unable to find server tracking data. Please ensure you have called track(server, options) before using tool calls.");
|
|
83
|
+
return { tools };
|
|
84
|
+
}
|
|
85
|
+
if (tools.length === 0) {
|
|
86
|
+
writeToLog("Warning: No tools found in the original list. This is likely due to the tools not being registered before Within.track().");
|
|
87
|
+
event.error = { message: "No tools were sent to MCP client." };
|
|
88
|
+
event.isError = true;
|
|
89
|
+
event.duration =
|
|
90
|
+
(event.timestamp &&
|
|
91
|
+
new Date().getTime() - event.timestamp.getTime()) ||
|
|
92
|
+
0;
|
|
93
|
+
publishEvent(server, event);
|
|
94
|
+
return { tools };
|
|
95
|
+
}
|
|
96
|
+
// Add context parameter to all existing tools if enableToolCallContext is true
|
|
97
|
+
if (data.options.enableToolCallContext) {
|
|
98
|
+
tools = addContextParameterToTools(tools, data.options.customContextDescription);
|
|
99
|
+
}
|
|
100
|
+
// Add report_missing tool if enabled
|
|
101
|
+
if (data.options.enableReportMissing) {
|
|
102
|
+
const alreadyPresent = tools.some((t) => t?.name === GET_MORE_TOOLS_NAME);
|
|
103
|
+
if (!alreadyPresent) {
|
|
104
|
+
tools.push(getReportMissingToolDescriptor());
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
event.response = { tools };
|
|
108
|
+
event.isError = false;
|
|
109
|
+
event.duration =
|
|
110
|
+
(event.timestamp && new Date().getTime() - event.timestamp.getTime()) ||
|
|
111
|
+
0;
|
|
112
|
+
publishEvent(server, event);
|
|
113
|
+
return { tools };
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
writeToLog(`Warning: Failed to override list tools handler - ${error}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { HighLevelMCPServerLike, MCPServerLike } from "../types.js";
|
|
2
|
+
export declare function setupListToolsTracing(highLevelServer: HighLevelMCPServerLike): void;
|
|
3
|
+
export declare function setupInitializeTracing(highLevelServer: HighLevelMCPServerLike): void;
|
|
4
|
+
export declare function setupToolCallTracing(server: MCPServerLike): void;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { CallToolRequestSchema, InitializeRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { writeToLog } from "./logging.js";
|
|
3
|
+
import { handleReportMissing } from "./tools.js";
|
|
4
|
+
import { getServerTrackingData, handleIdentify, resolveEventTags, resolveEventProperties, } from "./internal.js";
|
|
5
|
+
import { getServerSessionId } from "./session.js";
|
|
6
|
+
import { WithinEventType } from "./constants.js";
|
|
7
|
+
import { publishEvent } from "./eventQueue.js";
|
|
8
|
+
import { getMCPCompatibleErrorMessage } from "./compatibility.js";
|
|
9
|
+
import { captureException } from "./exceptions.js";
|
|
10
|
+
import { addContextParameterToTools } from "./context-parameters.js";
|
|
11
|
+
import { GET_MORE_TOOLS_NAME, getReportMissingToolDescriptor, } from "./tools.js";
|
|
12
|
+
function isToolResultError(result) {
|
|
13
|
+
return result && typeof result === "object" && result.isError === true;
|
|
14
|
+
}
|
|
15
|
+
// Track if we've already set up list tools tracing per server instance
|
|
16
|
+
const listToolsTracingSetup = new WeakMap();
|
|
17
|
+
export function setupListToolsTracing(highLevelServer) {
|
|
18
|
+
const server = highLevelServer.server;
|
|
19
|
+
// Check if server supports tools capability
|
|
20
|
+
if (!server._capabilities?.tools) {
|
|
21
|
+
// Server doesn't support tools yet, skip setup
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// Check if we've already set up tracing for this server instance
|
|
25
|
+
if (listToolsTracingSetup.get(server)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const handlers = server._requestHandlers;
|
|
29
|
+
const originalListToolsHandler = handlers.get("tools/list");
|
|
30
|
+
// No handler to override yet
|
|
31
|
+
if (!originalListToolsHandler) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
server.setRequestHandler(ListToolsRequestSchema, async (request, extra) => {
|
|
36
|
+
let tools = [];
|
|
37
|
+
const data = getServerTrackingData(server);
|
|
38
|
+
let event = {
|
|
39
|
+
sessionId: getServerSessionId(server, extra),
|
|
40
|
+
parameters: {
|
|
41
|
+
request: request,
|
|
42
|
+
extra: extra,
|
|
43
|
+
},
|
|
44
|
+
eventType: WithinEventType.mcpToolsList,
|
|
45
|
+
timestamp: new Date(),
|
|
46
|
+
redactionFn: data?.options.redactSensitiveInformation,
|
|
47
|
+
};
|
|
48
|
+
if (data) {
|
|
49
|
+
await handleIdentify(server, data, request, extra);
|
|
50
|
+
event.sessionId = data.sessionId;
|
|
51
|
+
const resolvedTags = await resolveEventTags(data, request, extra);
|
|
52
|
+
if (resolvedTags)
|
|
53
|
+
event.tags = resolvedTags;
|
|
54
|
+
const resolvedProperties = await resolveEventProperties(data, request, extra);
|
|
55
|
+
if (resolvedProperties)
|
|
56
|
+
event.properties = resolvedProperties;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const originalResponse = (await originalListToolsHandler(request, extra));
|
|
60
|
+
tools = originalResponse.tools || [];
|
|
61
|
+
// Inject context parameters AFTER MCP SDK has converted Zod to JSON Schema
|
|
62
|
+
if (data?.options.enableToolCallContext) {
|
|
63
|
+
tools = addContextParameterToTools(tools, data.options.customContextDescription);
|
|
64
|
+
}
|
|
65
|
+
// Add get_more_tools tool when enabled
|
|
66
|
+
if (data?.options.enableReportMissing) {
|
|
67
|
+
const alreadyPresent = tools.some((t) => t?.name === GET_MORE_TOOLS_NAME);
|
|
68
|
+
if (!alreadyPresent)
|
|
69
|
+
tools.push(getReportMissingToolDescriptor());
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
// If original handler fails, start with empty tools
|
|
74
|
+
writeToLog(`Warning: Original list tools handler failed, this suggests an error Within did not cause - ${error}`);
|
|
75
|
+
event.error = { message: getMCPCompatibleErrorMessage(error) };
|
|
76
|
+
event.isError = true;
|
|
77
|
+
event.duration =
|
|
78
|
+
(event.timestamp &&
|
|
79
|
+
new Date().getTime() - event.timestamp.getTime()) ||
|
|
80
|
+
0;
|
|
81
|
+
publishEvent(server, event);
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
if (!data) {
|
|
85
|
+
writeToLog("Warning: Within SDK is unable to find server tracking data. Please ensure you have called track(server, options) before using tool calls.");
|
|
86
|
+
return { tools };
|
|
87
|
+
}
|
|
88
|
+
if (tools.length === 0) {
|
|
89
|
+
writeToLog("Warning: No tools found in the original list. This is likely due to the tools not being registered before Within.track().");
|
|
90
|
+
event.error = { message: "No tools were sent to MCP client." };
|
|
91
|
+
event.isError = true;
|
|
92
|
+
event.duration =
|
|
93
|
+
(event.timestamp &&
|
|
94
|
+
new Date().getTime() - event.timestamp.getTime()) ||
|
|
95
|
+
0;
|
|
96
|
+
publishEvent(server, event);
|
|
97
|
+
return { tools };
|
|
98
|
+
}
|
|
99
|
+
event.response = { tools };
|
|
100
|
+
event.isError = false;
|
|
101
|
+
event.duration =
|
|
102
|
+
(event.timestamp && new Date().getTime() - event.timestamp.getTime()) ||
|
|
103
|
+
0;
|
|
104
|
+
publishEvent(server, event);
|
|
105
|
+
return { tools };
|
|
106
|
+
});
|
|
107
|
+
// Mark as setup successful for this server instance
|
|
108
|
+
listToolsTracingSetup.set(server, true);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
writeToLog(`Warning: Failed to override list tools handler - ${error}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export function setupInitializeTracing(highLevelServer) {
|
|
115
|
+
const server = highLevelServer.server;
|
|
116
|
+
const handlers = server._requestHandlers;
|
|
117
|
+
const originalInitializeHandler = handlers.get("initialize");
|
|
118
|
+
if (originalInitializeHandler) {
|
|
119
|
+
server.setRequestHandler(InitializeRequestSchema, async (request, extra) => {
|
|
120
|
+
const data = getServerTrackingData(server);
|
|
121
|
+
if (!data) {
|
|
122
|
+
writeToLog("Warning: Within SDK is unable to find server tracking data. Please ensure you have called track(server, options) before using tool calls.");
|
|
123
|
+
return await originalInitializeHandler(request, extra);
|
|
124
|
+
}
|
|
125
|
+
const sessionId = getServerSessionId(server, extra);
|
|
126
|
+
// Try to identify the session
|
|
127
|
+
await handleIdentify(server, data, request, extra);
|
|
128
|
+
let event = {
|
|
129
|
+
sessionId: sessionId,
|
|
130
|
+
resourceName: request.params?.name || "Unknown Tool Name",
|
|
131
|
+
eventType: WithinEventType.mcpInitialize,
|
|
132
|
+
parameters: {
|
|
133
|
+
request: request,
|
|
134
|
+
extra: extra,
|
|
135
|
+
},
|
|
136
|
+
timestamp: new Date(),
|
|
137
|
+
redactionFn: data.options.redactSensitiveInformation,
|
|
138
|
+
};
|
|
139
|
+
const resolvedTags = await resolveEventTags(data, request, extra);
|
|
140
|
+
if (resolvedTags)
|
|
141
|
+
event.tags = resolvedTags;
|
|
142
|
+
const resolvedProperties = await resolveEventProperties(data, request, extra);
|
|
143
|
+
if (resolvedProperties)
|
|
144
|
+
event.properties = resolvedProperties;
|
|
145
|
+
const result = await originalInitializeHandler(request, extra);
|
|
146
|
+
event.response = result;
|
|
147
|
+
publishEvent(server, event);
|
|
148
|
+
return result;
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export function setupToolCallTracing(server) {
|
|
153
|
+
try {
|
|
154
|
+
const handlers = server._requestHandlers;
|
|
155
|
+
const originalCallToolHandler = handlers.get("tools/call");
|
|
156
|
+
const originalInitializeHandler = handlers.get("initialize");
|
|
157
|
+
if (originalInitializeHandler) {
|
|
158
|
+
server.setRequestHandler(InitializeRequestSchema, async (request, extra) => {
|
|
159
|
+
const data = getServerTrackingData(server);
|
|
160
|
+
if (!data) {
|
|
161
|
+
writeToLog("Warning: Within SDK is unable to find server tracking data. Please ensure you have called track(server, options) before using tool calls.");
|
|
162
|
+
return await originalInitializeHandler(request, extra);
|
|
163
|
+
}
|
|
164
|
+
const sessionId = getServerSessionId(server, extra);
|
|
165
|
+
// Try to identify the session
|
|
166
|
+
await handleIdentify(server, data, request, extra);
|
|
167
|
+
let event = {
|
|
168
|
+
sessionId: sessionId,
|
|
169
|
+
resourceName: request.params?.name || "Unknown Tool Name",
|
|
170
|
+
eventType: WithinEventType.mcpInitialize,
|
|
171
|
+
parameters: {
|
|
172
|
+
request: request,
|
|
173
|
+
extra: extra,
|
|
174
|
+
},
|
|
175
|
+
timestamp: new Date(),
|
|
176
|
+
redactionFn: data.options.redactSensitiveInformation,
|
|
177
|
+
};
|
|
178
|
+
const resolvedTags = await resolveEventTags(data, request, extra);
|
|
179
|
+
if (resolvedTags)
|
|
180
|
+
event.tags = resolvedTags;
|
|
181
|
+
const resolvedProperties = await resolveEventProperties(data, request, extra);
|
|
182
|
+
if (resolvedProperties)
|
|
183
|
+
event.properties = resolvedProperties;
|
|
184
|
+
const result = await originalInitializeHandler(request, extra);
|
|
185
|
+
event.response = result;
|
|
186
|
+
publishEvent(server, event);
|
|
187
|
+
return result;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
191
|
+
const data = getServerTrackingData(server);
|
|
192
|
+
if (!data) {
|
|
193
|
+
writeToLog("Warning: Within SDK is unable to find server tracking data. Please ensure you have called track(server, options) before using tool calls.");
|
|
194
|
+
return await originalCallToolHandler?.(request, extra);
|
|
195
|
+
}
|
|
196
|
+
const sessionId = getServerSessionId(server, extra);
|
|
197
|
+
let event = {
|
|
198
|
+
sessionId: sessionId,
|
|
199
|
+
resourceName: request.params?.name || "Unknown Tool Name",
|
|
200
|
+
parameters: {
|
|
201
|
+
request: request,
|
|
202
|
+
extra: extra,
|
|
203
|
+
},
|
|
204
|
+
eventType: WithinEventType.mcpToolsCall,
|
|
205
|
+
timestamp: new Date(),
|
|
206
|
+
redactionFn: data.options.redactSensitiveInformation,
|
|
207
|
+
};
|
|
208
|
+
try {
|
|
209
|
+
// Try to identify the session if we haven't already and identify function is provided
|
|
210
|
+
await handleIdentify(server, data, request, extra);
|
|
211
|
+
const resolvedTags = await resolveEventTags(data, request, extra);
|
|
212
|
+
if (resolvedTags)
|
|
213
|
+
event.tags = resolvedTags;
|
|
214
|
+
const resolvedProperties = await resolveEventProperties(data, request, extra);
|
|
215
|
+
if (resolvedProperties)
|
|
216
|
+
event.properties = resolvedProperties;
|
|
217
|
+
const context = request.params?.arguments?.context;
|
|
218
|
+
if (data.options.enableToolCallContext &&
|
|
219
|
+
typeof context === "string") {
|
|
220
|
+
event.userIntent = context;
|
|
221
|
+
}
|
|
222
|
+
let result;
|
|
223
|
+
if (request.params?.name === "get_more_tools") {
|
|
224
|
+
result = await handleReportMissing(request.params.arguments.context);
|
|
225
|
+
event.userIntent = request.params.arguments.context;
|
|
226
|
+
}
|
|
227
|
+
else if (originalCallToolHandler) {
|
|
228
|
+
result = await originalCallToolHandler(request, extra);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
event.isError = true;
|
|
232
|
+
event.error = {
|
|
233
|
+
message: `Tool call handler not found for ${request.params?.name || "unknown"}`,
|
|
234
|
+
};
|
|
235
|
+
event.duration =
|
|
236
|
+
(event.timestamp &&
|
|
237
|
+
new Date().getTime() - event.timestamp.getTime()) ||
|
|
238
|
+
undefined;
|
|
239
|
+
publishEvent(server, event);
|
|
240
|
+
throw new Error(`Unknown tool: ${request.params?.name || "unknown"}`);
|
|
241
|
+
}
|
|
242
|
+
// Check if the result indicates an error
|
|
243
|
+
if (isToolResultError(result)) {
|
|
244
|
+
event.isError = true;
|
|
245
|
+
event.error = captureException(result);
|
|
246
|
+
}
|
|
247
|
+
event.response = result;
|
|
248
|
+
publishEvent(server, event);
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
event.isError = true;
|
|
253
|
+
event.error = captureException(error);
|
|
254
|
+
publishEvent(server, event);
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
writeToLog(`Warning: Failed to setup tool call tracing - ${error}`);
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
}
|