Skip to content
Closed
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
7 changes: 7 additions & 0 deletions .changeset/expose-mcp-server-info.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@ai-sdk/mcp': patch
---

feat(ai/mcp): expose `serverInfo` from MCP initialize handshake

`MCPClient` now exposes the server's `serverInfo` (containing `name`, `version`, and optional `title`) as a readonly property. Previously, this was parsed during initialization but discarded.
16 changes: 16 additions & 0 deletions examples/mcp/src/server-info/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createMCPClient } from '@ai-sdk/mcp';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

async function main() {
const client = await createMCPClient({
transport: new StreamableHTTPClientTransport(
new URL('http://localhost:3000/mcp'),
),
});

console.log('serverInfo:', client.serverInfo);

await client.close();
}

main().catch(console.error);
31 changes: 31 additions & 0 deletions examples/mcp/src/server-info/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import express from 'express';

const app = express();
app.use(express.json());

app.post('/mcp', async (req, res) => {
const server = new McpServer({
name: 'my-weather-server',
version: '2.1.0',
});

server.tool('ping', 'A simple ping tool', async () => {
return { content: [{ type: 'text', text: 'pong' }] };
});

const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
res.on('close', () => {
transport.close();
server.close();
});
});

app.listen(3000, () => {
console.log('server-info example server listening on port 3000');
});
1 change: 1 addition & 0 deletions packages/mcp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type {
ElicitResult,
ListToolsResult,
ClientCapabilities as MCPClientCapabilities,
Implementation as MCPServerInfo,
} from './tool/types';
export { auth, UnauthorizedError } from './tool/oauth';
export type { OAuthClientProvider } from './tool/oauth';
Expand Down
32 changes: 29 additions & 3 deletions packages/mcp/src/tool/mcp-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
ReadResourceResult,
ListPromptsResult,
GetPromptResult,
Configuration,
Implementation,
ElicitationRequestSchema,
} from './types';
import { JSONRPCRequest } from './json-rpc-message';
Expand Down Expand Up @@ -924,6 +924,32 @@ describe('MCPClient', () => {
}
});

it('should expose serverInfo from initialize result', async () => {
createMockTransport.mockImplementation(
() =>
new MockMCPTransport({
initializeResult: {
protocolVersion: '2025-11-25',
serverInfo: {
name: 'my-server',
version: '2.0.0',
title: 'My Awesome Server',
},
capabilities: { tools: {} },
},
}),
);

client = await createMCPClient({
transport: { type: 'sse', url: 'https://example.com/sse' },
});

expectTypeOf(client.serverInfo).toEqualTypeOf<Implementation>();
expect(client.serverInfo.name).toBe('my-server');
expect(client.serverInfo.version).toBe('2.0.0');
expect(client.serverInfo.title).toBe('My Awesome Server');
});

it('should close transport when client is closed', async () => {
const mockTransport = new MockMCPTransport();
const closeSpy = vi.spyOn(mockTransport, 'close');
Expand Down Expand Up @@ -1127,7 +1153,7 @@ describe('MCPClient', () => {
const originalSend = mockTransport.send.bind(mockTransport);
mockTransport.send = vi.fn(async (message: JSONRPCRequest) => {
if (message.method === 'initialize' && message.params) {
capturedClientInfo = message.params.clientInfo as Configuration;
capturedClientInfo = message.params.clientInfo as Implementation;
}
return originalSend(message);
});
Expand All @@ -1148,7 +1174,7 @@ describe('MCPClient', () => {
const originalSend = mockTransport.send.bind(mockTransport);
mockTransport.send = vi.fn(async (message: JSONRPCRequest) => {
if (message.method === 'initialize' && message.params) {
capturedClientInfo = message.params.clientInfo as Configuration;
capturedClientInfo = message.params.clientInfo as Implementation;
}
return originalSend(message);
});
Expand Down
15 changes: 13 additions & 2 deletions packages/mcp/src/tool/mcp-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
CallToolResult,
CallToolResultSchema,
ClientCapabilities,
Configuration as ClientConfiguration,
Implementation,
ElicitationRequest,
ElicitationRequestSchema,
ElicitResult,
Expand Down Expand Up @@ -119,6 +119,11 @@ export async function createMCPClient(
}

export interface MCPClient {
/**
* Server information returned during the MCP initialize handshake.
*/
readonly serverInfo: Implementation;

tools<TOOL_SCHEMAS extends ToolSchemas = 'automatic'>(options?: {
schemas?: TOOL_SCHEMAS;
}): Promise<McpToolSet<TOOL_SCHEMAS>>;
Expand Down Expand Up @@ -193,7 +198,7 @@ export interface MCPClient {
class DefaultMCPClient implements MCPClient {
private transport: MCPTransport;
private onUncaughtError?: (error: unknown) => void;
private clientInfo: ClientConfiguration;
private clientInfo: Implementation;
private clientCapabilities: ClientCapabilities;
private requestMessageId = 0;
private responseHandlers: Map<
Expand All @@ -205,6 +210,11 @@ class DefaultMCPClient implements MCPClient {
private elicitationRequestHandler?: (
request: ElicitationRequest,
) => Promise<ElicitResult> | ElicitResult;
#serverInfo!: Implementation;

get serverInfo(): Implementation {
return this.#serverInfo;
}

constructor({
transport: transportConfig,
Expand Down Expand Up @@ -277,6 +287,7 @@ class DefaultMCPClient implements MCPClient {
}

this.serverCapabilities = result.capabilities;
this.#serverInfo = result.serverInfo;

// Complete initialization handshake:
await this.notification({
Expand Down
3 changes: 2 additions & 1 deletion packages/mcp/src/tool/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ export type McpToolSet<TOOL_SCHEMAS extends ToolSchemas = 'automatic'> =
const ClientOrServerImplementationSchema = z.looseObject({
name: z.string(),
version: z.string(),
title: z.optional(z.string()),
});

export type Configuration = z.infer<typeof ClientOrServerImplementationSchema>;
export type Implementation = z.infer<typeof ClientOrServerImplementationSchema>;

export const BaseParamsSchema = z.looseObject({
_meta: z.optional(z.object({}).loose()),
Expand Down
Loading