Skip to content

Commit a76b23d

Browse files
authored
fix: make sure tool calls are processed sequentially (#22)
1 parent 5776575 commit a76b23d

File tree

2 files changed

+81
-28
lines changed

2 files changed

+81
-28
lines changed

src/Mutex.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google Inc.
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
export class Mutex {
8+
static Guard = class Guard {
9+
#mutex: Mutex;
10+
#onRelease?: () => void;
11+
constructor(mutex: Mutex, onRelease?: () => void) {
12+
this.#mutex = mutex;
13+
this.#onRelease = onRelease;
14+
}
15+
dispose(): void {
16+
this.#onRelease?.();
17+
return this.#mutex.release();
18+
}
19+
};
20+
21+
#locked = false;
22+
#acquirers: Array<() => void> = [];
23+
24+
// This is FIFO.
25+
async acquire(
26+
onRelease?: () => void,
27+
): Promise<InstanceType<typeof Mutex.Guard>> {
28+
if (!this.#locked) {
29+
this.#locked = true;
30+
return new Mutex.Guard(this);
31+
}
32+
const {resolve, promise} = Promise.withResolvers<void>();
33+
this.#acquirers.push(resolve);
34+
await promise;
35+
return new Mutex.Guard(this, onRelease);
36+
}
37+
38+
release(): void {
39+
const resolve = this.#acquirers.shift();
40+
if (!resolve) {
41+
this.#locked = false;
42+
return;
43+
}
44+
resolve();
45+
}
46+
}

src/index.ts

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import * as snapshotTools from './tools/snapshot.js';
3333
import path from 'node:path';
3434
import fs from 'node:fs';
3535
import assert from 'node:assert';
36+
import {Mutex} from './Mutex.js';
3637

3738
export const cliOptions = {
3839
browserUrl: {
@@ -169,6 +170,8 @@ Avoid sharing sensitive or personal information that you do want to share with M
169170
);
170171
};
171172

173+
const toolMutex = new Mutex();
174+
172175
function registerTool(tool: ToolDefinition): void {
173176
server.registerTool(
174177
tool.name,
@@ -178,35 +181,39 @@ function registerTool(tool: ToolDefinition): void {
178181
annotations: tool.annotations,
179182
},
180183
async (params): Promise<CallToolResult> => {
181-
logger(`${tool.name} request: ${JSON.stringify(params, null, ' ')}`);
182-
const context = await getContext();
183-
const response = new McpResponse();
184-
await tool.handler(
185-
{
186-
params,
187-
},
188-
response,
189-
context,
190-
);
184+
const guard = await toolMutex.acquire();
191185
try {
192-
const content = await response.handle(tool.name, context);
193-
194-
return {
195-
content,
196-
};
197-
} catch (error) {
198-
const errorText =
199-
error instanceof Error ? error.message : String(error);
200-
201-
return {
202-
content: [
203-
{
204-
type: 'text',
205-
text: errorText,
206-
},
207-
],
208-
isError: true,
209-
};
186+
logger(`${tool.name} request: ${JSON.stringify(params, null, ' ')}`);
187+
const context = await getContext();
188+
const response = new McpResponse();
189+
await tool.handler(
190+
{
191+
params,
192+
},
193+
response,
194+
context,
195+
);
196+
try {
197+
const content = await response.handle(tool.name, context);
198+
return {
199+
content,
200+
};
201+
} catch (error) {
202+
const errorText =
203+
error instanceof Error ? error.message : String(error);
204+
205+
return {
206+
content: [
207+
{
208+
type: 'text',
209+
text: errorText,
210+
},
211+
],
212+
isError: true,
213+
};
214+
}
215+
} finally {
216+
guard.dispose();
210217
}
211218
},
212219
);

0 commit comments

Comments
 (0)