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
5 changes: 5 additions & 0 deletions .changeset/gentle-snakes-flash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ai-sdk/mcp": patch
---

feat(mcp): surface 'serverInfo' exposed from the MCP server
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-dumb-server',
version: '2.000.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 @@ -14,6 +14,7 @@ export {
} from './tool/mcp-client';
export { ElicitationRequestSchema, ElicitResultSchema } from './tool/types';
export type {
Configuration,
ElicitationRequest,
ElicitResult,
ListToolsResult,
Expand Down
42 changes: 42 additions & 0 deletions packages/mcp/src/tool/mcp-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,48 @@ describe('MCPClient', () => {
}
});

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

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

expect(client.serverInfo).toMatchInlineSnapshot(`
{
"name": "my-server",
"title": "My Server",
"version": "2.0.0",
}
`);
});

it('should expose serverInfo without title when server omits it', async () => {
client = await createMCPClient({
transport: { type: 'sse', url: 'https://example.com/sse' },
});

expect(client.serverInfo).toMatchInlineSnapshot(`
{
"name": "mock-mcp-server",
"version": "1.0.0",
}
`);
});

it('should close transport when client is closed', async () => {
const mockTransport = new MockMCPTransport();
const closeSpy = vi.spyOn(mockTransport, 'close');
Expand Down
13 changes: 13 additions & 0 deletions packages/mcp/src/tool/mcp-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
CallToolResult,
CallToolResultSchema,
ClientCapabilities,
Configuration,
Configuration as ClientConfiguration,
ElicitationRequest,
ElicitationRequestSchema,
Expand Down Expand Up @@ -119,6 +120,12 @@ export async function createMCPClient(
}

export interface MCPClient {
/**
* Information about the connected MCP server, as reported during initialization.
* @see https://modelcontextprotocol.io/specification/2025-11-25/schema#implementation
*/
readonly serverInfo: Configuration;

tools<TOOL_SCHEMAS extends ToolSchemas = 'automatic'>(options?: {
schemas?: TOOL_SCHEMAS;
}): Promise<McpToolSet<TOOL_SCHEMAS>>;
Expand Down Expand Up @@ -201,6 +208,7 @@ class DefaultMCPClient implements MCPClient {
(response: JSONRPCResponse | Error) => void
> = new Map();
private serverCapabilities: ServerCapabilities = {};
private _serverInfo: Configuration = { name: '', version: '' };
private isClosed = true;
private elicitationRequestHandler?: (
request: ElicitationRequest,
Expand Down Expand Up @@ -247,6 +255,10 @@ class DefaultMCPClient implements MCPClient {
};
}

get serverInfo(): Configuration {
return this._serverInfo;
}

async init(): Promise<this> {
try {
await this.transport.start();
Expand Down Expand Up @@ -277,6 +289,7 @@ class DefaultMCPClient implements MCPClient {
}

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

// Complete initialization handshake:
await this.notification({
Expand Down
2 changes: 2 additions & 0 deletions packages/mcp/src/tool/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +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()),
});

// Maps to `Implementation` in the MCP specification
export type Configuration = z.infer<typeof ClientOrServerImplementationSchema>;

export const BaseParamsSchema = z.looseObject({
Expand Down
Loading