Skip to content

Commit 0640e11

Browse files
committed
fix(cli): update mcp sdk
- Updated the package dependency in package.json and pnpm-lock.yaml. - Modified the NOTICE.md and README.md files to reflect the new server package. - Adjusted the MCP server implementation in index.ts to utilize the new server API. - Updated tests to ensure compatibility with the new server structure. Signed-off-by: Cory Rylan <crylan@nvidia.com>
1 parent 5c63566 commit 0640e11

8 files changed

Lines changed: 150 additions & 81 deletions

File tree

‎NOTICE.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ This product includes the following bundled third-party software:
2828
-@modelcontextprotocol/ext-apps v1.7.5 [MIT] (used by: @nvidia-elements/cli)
2929
Copyright: Olivier Chafik
3030

31-
-@modelcontextprotocol/sdk v1.30.0 [MIT] (used by: @nvidia-elements/cli)
31+
-@modelcontextprotocol/server v2.0.0 [MIT] (used by: @nvidia-elements/cli)
3232
Copyright: Anthropic, PBC (https://anthropic.com)
3333

3434
- adm-zip v0.5.17 [MIT] (used by: @nvidia-elements/cli)
@@ -308,7 +308,7 @@ The following bundled components are provided under the MIT license:
308308
@html-eslint/parser v0.61.0 - Copyright yeonjuan (https://github.com/yeonjuan)
309309
@inquirer/prompts v8.5.0 - Copyright Simon Boudrias <admin@simonboudrias.com>
310310
@modelcontextprotocol/ext-apps v1.7.5 - Copyright Olivier Chafik
311-
@modelcontextprotocol/sdk v1.30.0 - Copyright Anthropic, PBC (https://anthropic.com)
311+
@modelcontextprotocol/server v2.0.0 - Copyright Anthropic, PBC (https://anthropic.com)
312312
adm-zip v0.5.17 - Copyright Nasca Iacob <sy@another-d-mention.ro> (https://github.com/cthackers)
313313
archiver v8.0.0 - Copyright Chris Talkington (http://christalkington.com/)
314314
markdown-it v14.3.0 - Copyright Unknown

‎pnpm-lock.yaml‎

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

‎projects/cli/DEVELOPMENT.md‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
The package has two entry points:
1818

1919
-**`./dist/index.js`** (`nve` command) - Interactive CLI using Yargs with Inquirer for prompts
20-
-**`./dist/mcp/index.js`** (`nve mcp` command) - MCP server using @modelcontextprotocol/sdk
20+
-**`./dist/mcp/index.js`** (`nve mcp` command) - MCP server using `@modelcontextprotocol/server`
2121

2222
### Tool System
2323

@@ -60,7 +60,7 @@ tools.forEach(tool => {
6060
#### MCP Mode (`src/mcp/index.ts`)
6161

6262
- Sets `process.env.ELEMENTS_ENV = 'mcp'`
63-
-Creates MCP server instance with stdio transport
63+
-Serves an MCP server factory over stdio
6464
-**Tool registration** - Registers all tools with MCP server using Zod schemas
6565
-**Prompt registration** - Registers 6 built-in prompts (about, doctor, search, playground, new-project, migrate)
6666
-**Structured output** - Returns results with status, message, and structured content
@@ -69,13 +69,14 @@ Example MCP tool registration:
6969

7070
```typescript
7171
// Convert JSON Schema to Zod and register with MCP
72-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
73-
tools: tools.map(tool=> ({
74-
name: tool.toolName,
72+
server.registerTool(
73+
tool.toolName,
74+
{
7575
description: tool.description,
76-
inputSchema: zodToJsonSchema(jsonSchemaToZod(tool.inputSchema))
77-
}))
78-
}));
76+
inputSchema: jsonSchemaToZod(tool.inputSchema)
77+
},
78+
asyncparams=>tool(params)
79+
);
7980
```
8081

8182
## Data Flow

‎projects/cli/NOTICE.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ This project includes the following bundled third-party software:
1111
-@modelcontextprotocol/ext-apps v1.7.5 [MIT]
1212
Copyright: Olivier Chafik
1313

14-
-@modelcontextprotocol/sdk v1.30.0 [MIT]
14+
-@modelcontextprotocol/server v2.0.0 [MIT]
1515
Copyright: Anthropic, PBC (https://anthropic.com)
1616

1717
- adm-zip v0.5.17 [MIT]
@@ -49,7 +49,7 @@ The following bundled components are provided under the MIT license:
4949

5050
@inquirer/prompts v8.5.0 - Copyright Simon Boudrias <admin@simonboudrias.com>
5151
@modelcontextprotocol/ext-apps v1.7.5 - Copyright Olivier Chafik
52-
@modelcontextprotocol/sdk v1.30.0 - Copyright Anthropic, PBC (https://anthropic.com)
52+
@modelcontextprotocol/server v2.0.0 - Copyright Anthropic, PBC (https://anthropic.com)
5353
adm-zip v0.5.17 - Copyright Nasca Iacob <sy@another-d-mention.ro> (https://github.com/cthackers)
5454
archiver v8.0.0 - Copyright Chris Talkington (http://christalkington.com/)
5555
marked v18.0.3 - Copyright Christopher Jeffrey

‎projects/cli/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,4 @@ This hosted route does not install the Elements CLI or configure the MCP server.
197197
-[Changelog](https://NVIDIA.github.io/elements/docs/changelog/)
198198
-[GitHub Repo](https://github.com/NVIDIA/elements)
199199
-[npm](https://www.npmjs.com/package/@nvidia-elements/cli)
200+
-[MCP Registry](https://registry.modelcontextprotocol.io/?q=io.github.NVIDIA%2Felements)

‎projects/cli/package.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"dist/**/*.js"
3636
],
3737
"scripts": {
38-
"dev": "pnpm run cli:install && npx @modelcontextprotocol/inspector@0.22.0 node ./dist/index.js mcp",
38+
"dev": "pnpm run cli:install && npx @modelcontextprotocol/inspector@2.0.0 node ./dist/index.js mcp",
3939
"ci": "wireit",
4040
"build": "wireit",
4141
"lint": "wireit",
@@ -47,8 +47,8 @@
4747
},
4848
"dependencies": {
4949
"@inquirer/prompts": "8.5.0",
50-
"@modelcontextprotocol/sdk": "catalog:",
5150
"@modelcontextprotocol/ext-apps": "catalog:",
51+
"@modelcontextprotocol/server": "2.0.0",
5252
"@nvidia-elements/code": "workspace:*",
5353
"@nvidia-elements/lint": "workspace:^",
5454
"adm-zip": "0.5.17",

‎projects/cli/src/mcp/index.test.ts‎

Lines changed: 70 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ const {
88
mockRegisterPrompt,
99
mockRegisterResource,
1010
mockRegisterCapabilities,
11-
mockConnect,
11+
mockServeStdio,
12+
mockNotify,
1213
mcpTool,
1314
cliTool,
1415
allTool,
1516
uiTool,
16-
mockPrompt,
17-
mockZodSchema
17+
mockPrompt
1818
}=vi.hoisted(()=>{
19-
constzodSchema={shape: {},optional: ()=>zodSchema};
19+
conststdioHandle={close: vi.fn().mockResolvedValue(undefined)};
2020

2121
constcreateMockTool=(overrides: Record<string,unknown>)=>{
2222
constfn=vi.fn().mockResolvedValue({status: 'complete',result: 'test'});
@@ -40,8 +40,11 @@ const {
4040
mockRegisterPrompt: vi.fn(),
4141
mockRegisterResource: vi.fn(),
4242
mockRegisterCapabilities: vi.fn(),
43-
mockConnect: vi.fn().mockResolvedValue(undefined),
44-
mockZodSchema: zodSchema,
43+
mockServeStdio: vi.fn((factory: ()=>unknown)=>{
44+
factory();
45+
returnstdioHandle;
46+
}),
47+
mockNotify: vi.fn().mockResolvedValue(undefined),
4548
mcpTool: createMockTool({
4649
support: 1,
4750
toolName: 'mcp_tool',
@@ -83,28 +86,37 @@ const {
8386
};
8487
});
8588

86-
vi.mock('@modelcontextprotocol/sdk/server/mcp.js',()=>({
89+
vi.mock('@modelcontextprotocol/server',()=>({
8790
McpServer: vi.fn(function(){
8891
return{
8992
registerTool: mockRegisterTool,
9093
registerPrompt: mockRegisterPrompt,
9194
registerResource: mockRegisterResource,
92-
server: {registerCapabilities: mockRegisterCapabilities},
93-
connect: mockConnect
95+
server: {registerCapabilities: mockRegisterCapabilities}
9496
};
9597
})
9698
}));
9799

98-
vi.mock('@modelcontextprotocol/sdk/server/stdio.js',()=>({
99-
StdioServerTransport: vi.fn()
100+
vi.mock('@modelcontextprotocol/server/stdio',()=>({
101+
serveStdio: mockServeStdio
100102
}));
101103

102-
vi.mock('@internals/tools',()=>({
103-
tools: [mcpTool,cliTool,allTool,uiTool],
104-
prompts: [mockPrompt],
105-
jsonSchemaToZod: vi.fn(()=>mockZodSchema),
106-
ToolSupport: {None: 0,MCP: 1,CLI: 2,All: 3}
107-
}));
104+
vi.mock('@internals/tools',async()=>{
105+
const{default: z}=awaitimport('zod');
106+
return{
107+
tools: [mcpTool,cliTool,allTool,uiTool],
108+
prompts: [mockPrompt],
109+
jsonSchemaToZod: vi.fn(()=>z.object({})),
110+
ToolSupport: {None: 0,MCP: 1,CLI: 2,All: 3}
111+
};
112+
});
113+
114+
constcreateRequestContext=()=>({
115+
mcpReq: {
116+
_meta: {},
117+
notify: mockNotify
118+
}
119+
});
108120

109121
describe('MCP server',()=>{
110122
beforeEach(()=>{
@@ -151,12 +163,12 @@ describe('MCP server', () => {
151163
);
152164
});
153165

154-
it('should handle tools without inputSchema',async()=>{
166+
it('should register tools without inputSchema using an empty Zod object',async()=>{
155167
const{ startMcpServer }=awaitimport('./index.js');
156168
awaitstartMcpServer();
157169
// mcpTool has no inputSchema — should still register without error
158170
constmcpToolCall=mockRegisterTool.mock.calls.find(call=>call[0]==='mcp_tool');
159-
expect(mcpToolCall[1].inputSchema).toEqual({});
171+
expect(mcpToolCall[1].inputSchema.shape).toEqual({});
160172
});
161173

162174
it('should register prompts',async()=>{
@@ -182,17 +194,19 @@ describe('MCP server', () => {
182194
expect(result).toEqual({messages: []});
183195
});
184196

185-
it('should connect to stdio transport',async()=>{
197+
it('should serve an MCP server factory over stdio',async()=>{
186198
const{ startMcpServer }=awaitimport('./index.js');
187199
awaitstartMcpServer();
188-
expect(mockConnect).toHaveBeenCalledTimes(1);
200+
expect(mockServeStdio).toHaveBeenCalledWith(expect.any(Function),{
201+
onerror: expect.any(Function)
202+
});
189203
});
190204

191205
it('should return string result as text content',async()=>{
192206
const{ startMcpServer }=awaitimport('./index.js');
193207
awaitstartMcpServer();
194208
consthandler=mockRegisterTool.mock.calls[0][2];
195-
constresult=awaithandler({});
209+
constresult=awaithandler({},createRequestContext());
196210
expect(result).toEqual({
197211
structuredContent: {status: 'complete',result: 'test'},
198212
content: [{type: 'text',text: 'test'}]
@@ -205,7 +219,7 @@ describe('MCP server', () => {
205219
consthandler=mockRegisterTool.mock.calls[0][2];
206220
consterrorResult={status: 'error',message: 'failed'};
207221
mcpTool.mockResolvedValueOnce(errorResult);
208-
constresult=awaithandler({});
222+
constresult=awaithandler({},createRequestContext());
209223
expect(result.content[0].text).toBe(JSON.stringify(errorResult));
210224
});
211225

@@ -215,10 +229,37 @@ describe('MCP server', () => {
215229
consthandler=mockRegisterTool.mock.calls[0][2];
216230
constobjResult={status: 'complete',result: {key: 'value'}};
217231
mcpTool.mockResolvedValueOnce(objResult);
218-
constresult=awaithandler({});
232+
constresult=awaithandler({},createRequestContext());
219233
expect(result.content[0].text).toBe(JSON.stringify(objResult));
220234
});
221235

236+
it('should report progress through the v2 request context',async()=>{
237+
const{ startMcpServer }=awaitimport('./index.js');
238+
awaitstartMcpServer();
239+
consthandler=mockRegisterTool.mock.calls[0][2];
240+
constparams: Record<string,unknown>={};
241+
awaithandler(params,{
242+
mcpReq: {
243+
_meta: {progressToken: 'progress-token'},
244+
notify: mockNotify
245+
}
246+
});
247+
248+
if(typeofparams.onProgress!=='function'){
249+
thrownewTypeError('Expected an onProgress callback');
250+
}
251+
params.onProgress('Loading metadata');
252+
253+
expect(mockNotify).toHaveBeenCalledWith({
254+
method: 'notifications/progress',
255+
params: {
256+
progressToken: 'progress-token',
257+
progress: 1,
258+
message: 'Loading metadata'
259+
}
260+
});
261+
});
262+
222263
it('should advertise the io.modelcontextprotocol/ui extension capability',async()=>{
223264
const{ startMcpServer }=awaitimport('./index.js');
224265
awaitstartMcpServer();
@@ -359,15 +400,17 @@ describe('MCP server', () => {
359400
expect(mcpToolCall![1]._meta).toBeUndefined();
360401
});
361402

362-
it('should exit on connection error',async()=>{
363-
mockConnect.mockRejectedValueOnce(newError('Connection failed'));
403+
it('should exit on stdio error',async()=>{
364404
constexitSpy=vi.spyOn(process,'exit').mockImplementation(()=>undefinedasnever);
365405
consterrorSpy=vi.spyOn(console,'error').mockImplementation(()=>{});
366406

367407
const{ startMcpServer }=awaitimport('./index.js');
368408
awaitstartMcpServer();
409+
constoptions=mockServeStdio.mock.calls[0][1];
410+
consterror=newError('Connection failed');
411+
options.onerror(error);
369412

370-
expect(errorSpy).toHaveBeenCalledWith(expect.any(Error));
413+
expect(errorSpy).toHaveBeenCalledWith(error);
371414
expect(exitSpy).toHaveBeenCalledWith(1);
372415
});
373416
});

0 commit comments

Comments
 (0)