diff --git a/README.md b/README.md index 7ad7636f..8417272c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,26 @@ -# MCP TypeScript SDK ![NPM Version](https://img.shields.io/npm/v/%40modelcontextprotocol%2Fsdk) +# MCP TypeScript SDK ![NPM Version](https://img.shields.io/npm/v/%40modelcontextprotocol%2Fsdk) ![MIT licensed](https://img.shields.io/npm/l/%40modelcontextprotocol%2Fsdk) -TypeScript implementation of the [Model Context Protocol](https://modelcontextprotocol.io) (MCP), providing both client and server capabilities for integrating with LLM surfaces. +## Table of Contents +- [Overview](#overview) +- [Installation](#installation) +- [Quickstart](#quickstart) +- [What is MCP?](#what-is-mcp) +- [Core Concepts](#core-concepts) + - [Server](#server) + - [Resources](#resources) + - [Tools](#tools) + - [Prompts](#prompts) +- [Running Your Server](#running-your-server) + - [Development Mode](#development-mode) + - [Claude Desktop Integration](#claude-desktop-integration) + - [Direct Execution](#direct-execution) +- [Examples](#examples) + - [Echo Server](#echo-server) + - [SQLite Explorer](#sqlite-explorer) +- [Advanced Usage](#advanced-usage) + - [Low-Level Server](#low-level-server) + - [Writing MCP Clients](#writing-mcp-clients) + - [Server Capabilities](#server-capabilities) ## Overview @@ -19,158 +39,377 @@ npm install @modelcontextprotocol/sdk ## Quick Start -### Creating a Client +Let's create a simple MCP server that exposes a calculator tool and some data: ```typescript -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; -import { - ListResourcesRequestSchema, - ReadResourceRequestSchema, -} from "@modelcontextprotocol/sdk/types.js"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; -const transport = new StdioClientTransport({ - command: "path/to/server", +// Create an MCP server +const server = new McpServer({ + name: "Demo", + version: "1.0.0" }); -const client = new Client({ - name: "example-client", - version: "1.0.0", -}, { - capabilities: {} -}); - -await client.connect(transport); - -// List available resources -const resources = await client.request( - { method: "resources/list" }, - ListResourcesResultSchema +// Add an addition tool +server.tool("add", + { a: z.number(), b: z.number() }, + async ({ a, b }) => ({ + content: [{ type: "text", text: String(a + b) }] + }) ); -// Read a specific resource -const resourceContent = await client.request( - { - method: "resources/read", - params: { - uri: "file:///example.txt" - } - }, - ReadResourceResultSchema +// Add a dynamic greeting resource +server.resource( + "greeting", + "greeting://{name}", + async (uri, { name }) => ({ + contents: [{ + uri: uri.href, + text: `Hello, ${name}!` + }] + }) ); ``` -### Creating a Server +## What is MCP? -The SDK provides two ways to create a server: using the low-level `Server` class or the simplified `McpServer` class with an Express-style API. +The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can: -#### Using McpServer (Recommended) +- Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context) +- Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect) +- Define interaction patterns through **Prompts** (reusable templates for LLM interactions) +- And more! + +## Core Concepts + +### Server + +The McpServer is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing: ```typescript -import { McpServer } from "@modelcontextprotocol/sdk/server/index.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { z } from "zod"; - const server = new McpServer({ - name: "example-server", - version: "1.0.0", + name: "My App", + version: "1.0.0" }); +``` -// Define a simple tool with no parameters -server.tool("save", async () => { - return { - content: [{ type: "text", text: "Saved successfully." }] - }; -}); +### Resources -// Define a tool with parameters -server.tool("add", { a: z.number(), b: z.number() }, async ({ a, b }) => { - return { - content: [{ type: "text", text: String(a + b) }] - }; -}); +Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects: -// Define a static resource +```typescript +// Static resource server.resource( - "welcome-message", - "file:///welcome.txt", + "config", + "config://app", async (uri) => ({ contents: [{ uri: uri.href, - text: "Welcome to the server!" + text: "App configuration here" }] }) ); -// Define a prompt with parameters +// Dynamic resource with parameters +server.resource( + "user-profile", + "users://{userId}/profile", + async (uri, { userId }) => ({ + contents: [{ + uri: uri.href, + text: `Profile data for user ${userId}` + }] + }) +); +``` + +### Tools + +Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects: + +```typescript +// Simple tool with parameters +server.tool( + "calculate-bmi", + { + weightKg: z.number(), + heightM: z.number() + }, + async ({ weightKg, heightM }) => ({ + content: [{ + type: "text", + text: String(weightKg / (heightM * heightM)) + }] + }) +); + +// Async tool with external API call +server.tool( + "fetch-weather", + { city: z.string() }, + async ({ city }) => { + const response = await fetch(`https://api.weather.com/${city}`); + const data = await response.text(); + return { + content: [{ type: "text", text: data }] + }; + } +); +``` + +### Prompts + +Prompts are reusable templates that help LLMs interact with your server effectively: + +```typescript server.prompt( - "greeting", - { name: z.string(), language: z.string().optional() }, - ({ name, language }) => ({ + "review-code", + { code: z.string() }, + ({ code }) => ({ messages: [{ - role: "assistant", + role: "user", content: { type: "text", - text: `${language === "es" ? "¡Hola" : "Hello"} ${name}!` + text: `Please review this code:\n\n${code}` } }] }) ); - -const transport = new StdioServerTransport(); -await server.connect(transport); ``` -#### Using Server (Low-level API) +## Examples + +### Echo Server + +A simple server demonstrating resources, tools, and prompts: + +```typescript +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; + +const server = new McpServer({ + name: "Echo", + version: "1.0.0" +}); + +server.resource( + "echo", + "echo://{message}", + async (uri, { message }) => ({ + contents: [{ + uri: uri.href, + text: `Resource echo: ${message}` + }] + }) +); + +server.tool( + "echo", + { message: z.string() }, + async ({ message }) => ({ + content: [{ type: "text", text: `Tool echo: ${message}` }] + }) +); + +server.prompt( + "echo", + { message: z.string() }, + ({ message }) => ({ + messages: [{ + role: "user", + content: { + type: "text", + text: `Please process this message: ${message}` + } + }] + }) +); +``` + +### SQLite Explorer + +A more complex example showing database integration: + +```typescript +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import sqlite3 from "sqlite3"; +import { promisify } from "util"; +import { z } from "zod"; + +const server = new McpServer({ + name: "SQLite Explorer", + version: "1.0.0" +}); + +// Helper to create DB connection +const getDb = () => { + const db = new sqlite3.Database("database.db"); + return { + all: promisify(db.all.bind(db)), + close: promisify(db.close.bind(db)) + }; +}; + +server.resource( + "schema", + "schema://main", + async (uri) => { + const db = getDb(); + try { + const tables = await db.all( + "SELECT sql FROM sqlite_master WHERE type='table'" + ); + return { + contents: [{ + uri: uri.href, + text: tables.map((t: {sql: string}) => t.sql).join("\n") + }] + }; + } finally { + await db.close(); + } + } +); + +server.tool( + "query", + { sql: z.string() }, + async ({ sql }) => { + const db = getDb(); + try { + const results = await db.all(sql); + return { + content: [{ + type: "text", + text: JSON.stringify(results, null, 2) + }] + }; + } catch (err: unknown) { + const error = err as Error; + return { + content: [{ + type: "text", + text: `Error: ${error.message}` + }], + isError: true + }; + } finally { + await db.close(); + } + } +); +``` + +## Advanced Usage + +### Low-Level Server + +For more control, you can use the low-level Server class directly: ```typescript import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { - ListResourcesRequestSchema, - ReadResourceRequestSchema, + ListPromptsRequestSchema, + GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types.js"; -const server = new Server({ - name: "example-server", - version: "1.0.0", -}, { - capabilities: { - resources: {} +const server = new Server( + { + name: "example-server", + version: "1.0.0" + }, + { + capabilities: { + prompts: {} + } } -}); +); -server.setRequestHandler(ListResourcesRequestSchema, async () => { +server.setRequestHandler(ListPromptsRequestSchema, async () => { return { - resources: [ - { - uri: "file:///example.txt", - name: "Example Resource", - }, - ], + prompts: [{ + name: "example-prompt", + description: "An example prompt template", + arguments: [{ + name: "arg1", + description: "Example argument", + required: true + }] + }] }; }); -server.setRequestHandler(ReadResourceRequestSchema, async (request) => { - if (request.params.uri === "file:///example.txt") { - return { - contents: [ - { - uri: "file:///example.txt", - mimeType: "text/plain", - text: "This is the content of the example resource.", - }, - ], - }; - } else { - throw new Error("Resource not found"); +server.setRequestHandler(GetPromptRequestSchema, async (request) => { + if (request.params.name !== "example-prompt") { + throw new Error("Unknown prompt"); } + return { + description: "Example prompt", + messages: [{ + role: "user", + content: { + type: "text", + text: "Example prompt text" + } + }] + }; }); const transport = new StdioServerTransport(); await server.connect(transport); ``` +### Writing MCP Clients + +The SDK provides a high-level client interface: + +```typescript +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; + +const transport = new StdioClientTransport({ + command: "node", + args: ["server.js"] +}); + +const client = new Client( + { + name: "example-client", + version: "1.0.0" + }, + { + capabilities: { + prompts: {}, + resources: {}, + tools: {} + } + } +); + +await client.connect(transport); + +// List prompts +const prompts = await client.listPrompts(); + +// Get a prompt +const prompt = await client.getPrompt("example-prompt", { + arg1: "value" +}); + +// List resources +const resources = await client.listResources(); + +// Read a resource +const resource = await client.readResource("file:///example.txt"); + +// Call a tool +const result = await client.callTool("example-tool", { + arg1: "value" +}); +``` + ## Documentation - [Model Context Protocol documentation](https://modelcontextprotocol.io) diff --git a/package-lock.json b/package-lock.json index 44a876ac..627ba057 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@modelcontextprotocol/sdk", - "version": "1.1.0", + "version": "1.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/sdk", - "version": "1.1.0", + "version": "1.1.1", "license": "MIT", "dependencies": { "content-type": "^1.0.5",