Skip to content

Commit b129cd4

Browse files
author
Natallia Harshunova
committed
Address comments
1 parent 2ca5392 commit b129cd4

File tree

7 files changed

+115
-52
lines changed

7 files changed

+115
-52
lines changed

src/McpResponse.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import type {
2525
} from './tools/ToolDefinition.js';
2626
import type {InsightName, TraceResult} from './trace-processing/parse.js';
2727
import {getInsightOutput, getTraceSummary} from './trace-processing/parse.js';
28+
import type {InstalledExtension} from './utils/ExtensionRegistry.js';
2829
import {paginate} from './utils/pagination.js';
2930
import type {PaginationOptions} from './utils/types.js';
3031

@@ -60,6 +61,7 @@ export class McpResponse implements Response {
6061
types?: string[];
6162
includePreservedMessages?: boolean;
6263
};
64+
#listExtensions?: boolean;
6365
#devToolsData?: DevToolsData;
6466
#tabId?: string;
6567

@@ -81,6 +83,10 @@ export class McpResponse implements Response {
8183
};
8284
}
8385

86+
setListExtensions(): void {
87+
this.#listExtensions = true;
88+
}
89+
8490
setIncludeNetworkRequests(
8591
value: boolean,
8692
options?: PaginationOptions & {
@@ -99,9 +105,9 @@ export class McpResponse implements Response {
99105
pagination:
100106
options?.pageSize || options?.pageIdx
101107
? {
102-
pageSize: options.pageSize,
103-
pageIdx: options.pageIdx,
104-
}
108+
pageSize: options.pageSize,
109+
pageIdx: options.pageIdx,
110+
}
105111
: undefined,
106112
resourceTypes: options?.resourceTypes,
107113
includePreservedRequests: options?.includePreservedRequests,
@@ -126,9 +132,9 @@ export class McpResponse implements Response {
126132
pagination:
127133
options?.pageSize || options?.pageIdx
128134
? {
129-
pageSize: options.pageSize,
130-
pageIdx: options.pageIdx,
131-
}
135+
pageSize: options.pageSize,
136+
pageIdx: options.pageIdx,
137+
}
132138
: undefined,
133139
types: options?.types,
134140
includePreservedMessages: options?.includePreservedMessages,
@@ -297,6 +303,11 @@ export class McpResponse implements Response {
297303
}
298304
}
299305

306+
let extensions: InstalledExtension[] | undefined;
307+
if (this.#listExtensions) {
308+
extensions = context.listExtensions();
309+
}
310+
300311
let consoleMessages: Array<ConsoleFormatter | IssueFormatter> | undefined;
301312
if (this.#consoleDataOptions?.include) {
302313
let messages = context.getConsoleData(
@@ -395,6 +406,7 @@ export class McpResponse implements Response {
395406
networkRequests,
396407
traceInsight: this.#attachedTraceInsight,
397408
traceSummary: this.#attachedTraceSummary,
409+
extensions,
398410
});
399411
}
400412

@@ -409,6 +421,7 @@ export class McpResponse implements Response {
409421
networkRequests?: NetworkFormatter[];
410422
traceSummary?: TraceResult;
411423
traceInsight?: TraceInsightData;
424+
extensions?: InstalledExtension[];
412425
},
413426
): {content: Array<TextContent | ImageContent>; structuredContent: object} {
414427
const response = [`# ${toolName} response`];
@@ -474,6 +487,7 @@ Call ${handleDialog.name} to handle it before continuing.`);
474487
consoleMessages?: object[];
475488
traceSummary?: string;
476489
traceInsights?: Array<{insightName: string; insightKey: string}>;
490+
extensions?: object[];
477491
} = {};
478492

479493
if (this.#tabId) {
@@ -531,6 +545,25 @@ Call ${handleDialog.name} to handle it before continuing.`);
531545
data.detailedConsoleMessage.toJSONDetailed();
532546
}
533547

548+
if (data.extensions) {
549+
structuredContent.extensions = data.extensions;
550+
response.push('## Extensions');
551+
if (data.extensions.length === 0) {
552+
response.push('No extensions installed.');
553+
} else {
554+
const parts = [];
555+
for (const extension of data.extensions) {
556+
parts.push([
557+
`Name: ${extension.name}`,
558+
`ID: ${extension.id}`,
559+
`Version: ${extension.version}`,
560+
`Status: ${extension.isEnabled ? 'Enabled' : 'Disabled'}`,
561+
].join('\n'));
562+
}
563+
response.push(parts.join('\n\n'));
564+
}
565+
}
566+
534567
if (this.#networkRequestsOptions?.include) {
535568
let requests = context.getNetworkRequests(
536569
this.#networkRequestsOptions?.includePreservedRequests,

src/tools/ToolDefinition.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export interface Response {
9393
insightSetId: string,
9494
insightName: InsightName,
9595
): void;
96+
setListExtensions(): void;
9697
}
9798

9899
/**

src/tools/extensions.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,7 @@ export const listExtensions = defineTool({
6060
conditions: [EXTENSIONS_CONDITION],
6161
},
6262
schema: {},
63-
handler: async (request, response, context) => {
64-
const extensions = context.listExtensions();
65-
if (extensions.length === 0) {
66-
response.appendResponseLine('No extensions installed.');
67-
return;
68-
}
69-
const table = extensions.map(ext => ({
70-
Name: ext.name,
71-
ID: ext.id,
72-
Version: ext.version,
73-
Enabled: ext.isEnabled ? 'Yes' : 'No',
74-
}));
75-
response.appendResponseLine(JSON.stringify(table, null, 2));
63+
handler: async (_request, response, _context) => {
64+
response.setListExtensions();
7665
},
7766
});

src/utils/ExtensionRegistry.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,15 @@ export class ExtensionRegistry {
2525
const name = manifest.name ?? 'Unknown';
2626
const version = manifest.version ?? 'Unknown';
2727

28-
const extension: InstalledExtension = {
28+
const extension = {
2929
id,
3030
name,
3131
version,
3232
isEnabled: true,
3333
path: extensionPath,
3434
};
35-
this.add(extension);
36-
return extension;
37-
}
38-
39-
add(extension: InstalledExtension): void {
4035
this.#extensions.set(extension.id, extension);
36+
return extension;
4137
}
4238

4339
remove(id: string): void {

tests/McpResponse.test.js.snapshot

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,3 +480,38 @@ exports[`McpResponse network request filtering > shows no requests when filter m
480480
## Network requests
481481
No requests found.
482482
`;
483+
484+
exports[`extensions > lists extensions 1`] = `
485+
# test response
486+
## Extensions
487+
Name: Extension 1
488+
ID: id1
489+
Version: 1.0
490+
Status: Enabled
491+
492+
Name: Extension 2
493+
ID: id2
494+
Version: 2.0
495+
Status: Disabled
496+
`;
497+
498+
exports[`extensions > lists extensions 2`] = `
499+
{
500+
"extensions": [
501+
{
502+
"id": "id1",
503+
"name": "Extension 1",
504+
"version": "1.0",
505+
"isEnabled": true,
506+
"path": "/path/to/ext1"
507+
},
508+
{
509+
"id": "id2",
510+
"name": "Extension 2",
511+
"version": "2.0",
512+
"isEnabled": false,
513+
"path": "/path/to/ext2"
514+
}
515+
]
516+
}
517+
`;

tests/McpResponse.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,3 +713,35 @@ describe('McpResponse network pagination', () => {
713713
});
714714
});
715715
});
716+
717+
describe('extensions', () => {
718+
it('lists extensions', async t => {
719+
await withMcpContext(async (response, context) => {
720+
context.listExtensions = () => [
721+
{
722+
id: 'id1',
723+
name: 'Extension 1',
724+
version: '1.0',
725+
isEnabled: true,
726+
path: '/path/to/ext1',
727+
},
728+
{
729+
id: 'id2',
730+
name: 'Extension 2',
731+
version: '2.0',
732+
isEnabled: false,
733+
path: '/path/to/ext2',
734+
},
735+
];
736+
response.setListExtensions();
737+
const { content, structuredContent } = await response.handle(
738+
'test',
739+
context,
740+
);
741+
742+
t.assert.snapshot?.(getTextContent(content[0]));
743+
t.assert.snapshot?.(JSON.stringify(structuredContent, null, 2));
744+
});
745+
});
746+
});
747+

tests/tools/extensions.test.ts

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import assert from 'node:assert';
88
import path from 'node:path';
99
import {describe, it} from 'node:test';
1010

11+
import sinon from 'sinon';
12+
1113
import {
1214
installExtension,
1315
uninstallExtension,
@@ -74,34 +76,9 @@ describe('extension', () => {
7476
});
7577
it('lists installed extensions', async () => {
7678
await withMcpContext(async (response, context) => {
77-
await installExtension.handler(
78-
{ params: { path: EXTENSION_PATH } },
79-
response,
80-
context,
81-
);
82-
79+
const setListExtensionsSpy = sinon.spy(response, 'setListExtensions');
8380
await listExtensions.handler({ params: {} }, response, context);
84-
85-
const listResponseLine = response.responseLines[1];
86-
assert.ok(listResponseLine, 'Response should not be empty');
87-
const extensions = JSON.parse(listResponseLine);
88-
assert.strictEqual(extensions.length, 1);
89-
assert.strictEqual(extensions[0].Name, 'Test Extension');
90-
assert.strictEqual(extensions[0].Version, '1.0');
91-
assert.strictEqual(extensions[0].Enabled, 'Yes');
92-
93-
const extensionId = extensions[0].ID;
94-
await uninstallExtension.handler(
95-
{ params: { id: extensionId } },
96-
response,
97-
context,
98-
);
99-
100-
response.resetResponseLineForTesting();
101-
await listExtensions.handler({ params: {} }, response, context);
102-
103-
const emptyListResponse = response.responseLines[0];
104-
assert.strictEqual(emptyListResponse, 'No extensions installed.');
81+
assert.ok(setListExtensionsSpy.calledOnce, 'setListExtensions should be called');
10582
});
10683
});
10784
});

0 commit comments

Comments
 (0)