Skip to content

Commit 807cc5f

Browse files
feat: otel instrumentation as per MCP semantic convention (#306)
* feat(telemetry): OTel Instrumentation as per MCP Semantic Convention * feat(telemetry): session duration record at session close * feat(telemetry): close adk session for duration capture
1 parent 4bf0a7a commit 807cc5f

File tree

25 files changed

+2621
-322
lines changed

25 files changed

+2621
-322
lines changed

package-lock.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/toolbox-adk/src/toolbox_adk/client.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export class ToolboxClient {
4747
session?: AxiosInstance | null,
4848
clientHeaders?: ClientHeadersConfig | null,
4949
protocol: Protocol = Protocol.MCP,
50+
telemetryEnabled = false,
5051
) {
5152
this.coreClient = new CoreToolboxClient(
5253
url,
@@ -55,6 +56,7 @@ export class ToolboxClient {
5556
protocol,
5657
'toolbox-adk-js',
5758
VERSION,
59+
telemetryEnabled,
5860
);
5961
}
6062

@@ -107,4 +109,12 @@ export class ToolboxClient {
107109
);
108110
return coreTools.map(coreTool => new ToolboxTool(coreTool));
109111
}
112+
113+
/**
114+
* Closes the client and flushes any pending telemetry (e.g. session duration metric).
115+
* Should be called when the client is no longer needed.
116+
*/
117+
async close(): Promise<void> {
118+
await this.coreClient.close();
119+
}
110120
}

packages/toolbox-adk/test/test.client.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {VERSION} from '../src/toolbox_adk/version.js';
2828
type MockCoreClient = {
2929
loadTool: typeof mockLoadTool;
3030
loadToolset: typeof mockLoadToolset;
31+
close: typeof mockClose;
3132
};
3233

3334
// Define the signature of the mock constructor itself
@@ -38,8 +39,11 @@ type MockCoreClientConstructor = (
3839
protocol?: string | null,
3940
clientName?: string,
4041
clientVersion?: string,
42+
telemetryEnabled?: boolean,
4143
) => MockCoreClient;
4244

45+
const mockClose = jest.fn<() => Promise<void>>();
46+
4347
const mockLoadTool =
4448
jest.fn<
4549
(
@@ -64,6 +68,7 @@ const MockCoreToolboxClient = jest
6468
.mockImplementation(() => ({
6569
loadTool: mockLoadTool,
6670
loadToolset: mockLoadToolset,
71+
close: mockClose,
6772
}));
6873

6974
const MockToolboxTool = jest.fn();
@@ -88,6 +93,7 @@ describe('ToolboxClient', () => {
8893
beforeEach(() => {
8994
mockLoadTool.mockReset();
9095
mockLoadToolset.mockReset();
96+
mockClose.mockReset();
9197
MockCoreToolboxClient.mockClear();
9298
MockToolboxTool.mockClear();
9399
});
@@ -105,6 +111,7 @@ describe('ToolboxClient', () => {
105111
'mcp-default',
106112
'toolbox-adk-js',
107113
VERSION,
114+
false,
108115
);
109116
});
110117

@@ -179,4 +186,28 @@ describe('ToolboxClient', () => {
179186

180187
expect(mockLoadToolset).toHaveBeenCalledWith(undefined, {}, {}, false);
181188
});
189+
190+
it.each([false, true])(
191+
'should forward telemetryEnabled=%s to core client',
192+
telemetryEnabled => {
193+
new ToolboxClient(
194+
'http://test.url',
195+
null,
196+
null,
197+
undefined,
198+
telemetryEnabled,
199+
);
200+
const callArgs = MockCoreToolboxClient.mock.calls[0] as unknown[];
201+
expect(callArgs[6]).toBe(telemetryEnabled);
202+
},
203+
);
204+
205+
it('should delegate close() to coreClient.close()', async () => {
206+
mockClose.mockResolvedValue(undefined);
207+
const client = new ToolboxClient('http://test.url');
208+
209+
await client.close();
210+
211+
expect(mockClose).toHaveBeenCalledTimes(1);
212+
});
182213
});

packages/toolbox-core/package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,13 @@
6161
"uuid": "^11.1.0"
6262
},
6363
"peerDependencies": {
64-
"zod": "^3.24.4"
64+
"zod": "^3.24.4",
65+
"@opentelemetry/api": "^1.9.0"
66+
},
67+
"peerDependenciesMeta": {
68+
"@opentelemetry/api": {
69+
"optional": true
70+
}
6571
},
6672
"devDependencies": {
6773
"rimraf": "^6.1.2"

packages/toolbox-core/src/toolbox_core/client.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,14 @@ class ToolboxClient {
5757
* requests. If not provided, a new one will be created.
5858
* @param {ClientHeadersConfig} [clientHeaders] - Optional initial headers to
5959
* be included in each request.
60+
* @param {Protocol} [protocol] - MCP protocol version to use. Defaults to
61+
* the latest supported version. Pass a specific version (e.g.,
62+
* `Protocol.MCP_v20241105`) to pin to an older revision.
6063
* @param {string} [clientName] - Optional name of the client package.
6164
* @param {string} [clientVersion] - Optional version of the client package.
65+
* @param {boolean} [telemetryEnabled] - Set to `true` to enable OpenTelemetry
66+
* tracing and metrics. Requires the optional `@opentelemetry/api` peer
67+
* dependency. Silently ignored if the package is not installed.
6268
*/
6369
constructor(
6470
url: string,
@@ -67,6 +73,7 @@ class ToolboxClient {
6773
protocol: Protocol = Protocol.MCP,
6874
clientName?: string,
6975
clientVersion?: string,
76+
telemetryEnabled = false,
7077
) {
7178
this.#clientHeaders = clientHeaders || {};
7279
warnIfHttpAndHeaders(url, this.#clientHeaders);
@@ -88,6 +95,7 @@ class ToolboxClient {
8895
protocol,
8996
clientName,
9097
clientVersion,
98+
telemetryEnabled,
9199
);
92100
break;
93101
case Protocol.MCP_v20250326:
@@ -97,6 +105,7 @@ class ToolboxClient {
97105
protocol,
98106
clientName,
99107
clientVersion,
108+
telemetryEnabled,
100109
);
101110
break;
102111
case Protocol.MCP_v20250618:
@@ -106,6 +115,7 @@ class ToolboxClient {
106115
protocol,
107116
clientName,
108117
clientVersion,
118+
telemetryEnabled,
109119
);
110120
break;
111121
case Protocol.MCP_v20251125:
@@ -115,6 +125,7 @@ class ToolboxClient {
115125
protocol,
116126
clientName,
117127
clientVersion,
128+
telemetryEnabled,
118129
);
119130
break;
120131
default:
@@ -360,6 +371,14 @@ class ToolboxClient {
360371

361372
return tools;
362373
}
374+
375+
/**
376+
* Closes the client and flushes any pending telemetry (e.g. session duration metric).
377+
* Should be called when the client is no longer needed.
378+
*/
379+
async close(): Promise<void> {
380+
await this.#transport.close();
381+
}
363382
}
364383

365384
export {ToolboxClient};

0 commit comments

Comments
 (0)