Skip to content
Merged
Show file tree
Hide file tree
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
46 changes: 46 additions & 0 deletions src/Mutex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* @license
* Copyright 2025 Google Inc.
* SPDX-License-Identifier: Apache-2.0
*/

export class Mutex {
static Guard = class Guard {
#mutex: Mutex;
#onRelease?: () => void;
constructor(mutex: Mutex, onRelease?: () => void) {
this.#mutex = mutex;
this.#onRelease = onRelease;
}
dispose(): void {
this.#onRelease?.();
return this.#mutex.release();
}
};

#locked = false;
#acquirers: Array<() => void> = [];

// This is FIFO.
async acquire(
onRelease?: () => void,
): Promise<InstanceType<typeof Mutex.Guard>> {
if (!this.#locked) {
this.#locked = true;
return new Mutex.Guard(this);
}
const {resolve, promise} = Promise.withResolvers<void>();
this.#acquirers.push(resolve);
await promise;
return new Mutex.Guard(this, onRelease);
}

release(): void {
const resolve = this.#acquirers.shift();
if (!resolve) {
this.#locked = false;
return;
}
resolve();
}
}
63 changes: 35 additions & 28 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import * as snapshotTools from './tools/snapshot.js';
import path from 'node:path';
import fs from 'node:fs';
import assert from 'node:assert';
import {Mutex} from './Mutex.js';

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

const toolMutex = new Mutex();

function registerTool(tool: ToolDefinition): void {
server.registerTool(
tool.name,
Expand All @@ -178,35 +181,39 @@ function registerTool(tool: ToolDefinition): void {
annotations: tool.annotations,
},
async (params): Promise<CallToolResult> => {
logger(`${tool.name} request: ${JSON.stringify(params, null, ' ')}`);
const context = await getContext();
const response = new McpResponse();
await tool.handler(
{
params,
},
response,
context,
);
const guard = await toolMutex.acquire();
try {
const content = await response.handle(tool.name, context);

return {
content,
};
} catch (error) {
const errorText =
error instanceof Error ? error.message : String(error);

return {
content: [
{
type: 'text',
text: errorText,
},
],
isError: true,
};
logger(`${tool.name} request: ${JSON.stringify(params, null, ' ')}`);
const context = await getContext();
const response = new McpResponse();
await tool.handler(
{
params,
},
response,
context,
);
try {
const content = await response.handle(tool.name, context);
return {
content,
};
} catch (error) {
const errorText =
error instanceof Error ? error.message : String(error);

return {
content: [
{
type: 'text',
text: errorText,
},
],
isError: true,
};
}
} finally {
guard.dispose();
}
},
);
Expand Down