Model Context Protocol: Application example in TypeScript

The Model Context Protocol aims to standardize the connection of external sources to LLMs. A TypeScript example demonstrates its use.

listen Print view
Robot with its hand on its mouth, surrounded by lots of digital lettering with the word "Data"

(Image: Jirsak/Shutterstock.com)

21 min. read
By
  • Sebastian Springer
Contents

The AI company Anthropic, which was founded in 2021 by former OpenAI employees, has developed the Model Context Protocol (MCP) with the aim of standardizing communication between Large Language Models and external data sources. Tool calling allows the LLM to access predefined interfaces. These can be parameterized and thus allow great flexibility. Most LLM providers support this feature. Each data source requires a link via its own function, which usually requires an adjustment to the program code.

Sebastian Springer
Sebastian Springer

Sebastian Springer weckt als Dozent fĂĽr JavaScript, Sprecher auf zahlreichen Konferenzen und Autor die Begeisterung fĂĽr professionelle Entwicklung mit JavaScript.

The Model Context Protocol is intended to simplify the linking of external data sources considerably and allow external interfaces, databases, company systems or file systems to be connected directly without additional adaptation. This article is dedicated to the technical details of how the MCP works and uses a concrete example with the MCP-TypeScript-SDK to show how integration into an application can succeed.

The official website of the project and the GitHub repo are the central points of contact for resources on the topic of MCP. The documentation, the specification and a whole range of software development kits (SDKs) for various programming languages such as Python, TypeScript, C# or Kotlin are available there.

An application that uses the MCP consists of various components:

  • MCP server: The server is the point at MCP that makes its functionality available via the MCP. This can be data from a database, but also search results from the Internet. The server interfaces are precisely defined and can be used by the client side.
  • MCP client: The opposite side to the server is the client. It communicates with the server and executes the available operations via the interfaces. There are both read and write operations.
  • MCP host: The application with which the user interacts. This can be a web application, a classic desktop application (such as Claude Desktop), a development environment (such as Cursor) or a separate agent based on frameworks such as Langchain. The client is embedded in this application so that the host does not communicate directly with the server via MCP, but via the detour of the client.

Videos by heise

An MCP server can be directly integrated into an application but can also, and this will be the more common case, be a stand-alone service. The MCP initiative provides a range of SDKs in different programming languages that form the basis for server implementation. The server itself has a modular structure and allows various transport mechanisms to be used to communicate with the clients. The TypeScript SDK currently provides the transport mechanisms Streamable HTTP, SSE and stdio. Streamable HTTP and SSE are based on HTTP, whereby the MCP specification of 26.03.2025 marked the SSE transport as deprecated and the version of 18.06.2025 (latest) no longer mentions SSE.

An MCP client can use the HTTP-based transport mechanisms to connect to a remote server. The stdio transport is intended for local applications. Here, the client connects to the server via the standard input and output. This method is significantly faster, but is only suitable for use on one system. In addition to these transport types, the Python SDK defines others such as the ASGI transport (Asynchronous Server Gateway Interface), an interface from the Python world that supports other protocols such as WebSockets or user-defined protocols in addition to communication via HTTP.

The MCP specification divides the features that a server offers its clients into three categories: Resources, Tools and Prompts.

With the Resources feature category, an MCP server makes data available. A typical example is the connection to a database. Resources should only provide read-only data, not perform complex calculations and not have any side effects such as data manipulation. They can be parameterized in order to influence the query and achieve greater flexibility.

The MCP specification stipulates that the format [protocol]://[host]/[path] specifies resources. An example could look like this: postgres://database/users. The response to a resources request can either be in text format such as JSON or as binary data such as PDFs or images.

The MCP server provides a list of available resources via the resources/list endpoint. Each data record has at least the URL of the respective resource and a human-readable name. There is also an optional description and an optional MIME type that specifies the format of the response.

The specification not only provides for read access to resources, but also a notification mechanism via which the server can notify registered clients about changes to the available resources and data changes to an individual resource.

The features in the Tools category may perform calculations and have side effects. Clients must take into account that a tool execution may take longer than a resource query. These tools can be functions that produce a certain output based on an input, but also interfaces to databases and other systems in order to manipulate information stored there. Similar to resources, clients can also read the available tools of a server via an endpoint. This is tools/list.

The description of an MCP tool is somewhat more extensive than that of a resource. It contains a name and an input schema to describe the parameters of the tool. There is also its optional description and optional metadata via the tool annotations. The annotations provide clients or information for developers about the behavior of a tool. Most of the predefined annotations in the MCP specification are Boolean values. For example, the readOnlyHint annotation states that the tool does not modify its environment. As with the resources, a client can register with the server in order to be notified of changes.

Prompts are templates that make it easier for the MCP host to work with the Large Language Model (LLM). This allows an MCP application to standardize communication not only between client and server, but also with the LLM. In an application that offers a code review feature, for example, the prompt is used to embed the code to be reviewed correctly. According to the MCP specification, the prompt templates have a unique name. In addition, the specification also provides the optional fields description for a human-readable description and arguments with a list of the supported arguments of the schema.

The prompt endpoints of an MCP server are used in a similar way to tools. The client asks for the unique identifier and transmits the required values. The server responds with the prompt in which the values are integrated so that the client or the host application can use the prompt directly.for a list of all available prompts, the MCP server offers the prompts/list endpoint.

The SDKs provided by the MCP initiative implement the specification and serve as the basis for the implementation of MCP servers and MCP clients. The following example implements a simple MCP server with one resource, one tool and one prompt each with the TypeScript SDK.

import {
  McpServer,
  ResourceTemplate,
} from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';

const server = new McpServer({
  name: 'mcp-server',
  version: '1.0.0',
});

server.tool(
  'currency-converter',
  'Convert currency between EUR and USD',
  {
    amount: z.number(),
    from: z.enum(['EUR', 'USD']),
    to: z.enum(['EUR', 'USD']),
  },
  async ({ amount, from, to }) => {
    const exchangeRate =
      from === 'EUR' && to === 'USD'
        ? 1.1
        : from === 'USD' && to === 'EUR'
        ? 0.91
        : 1;
    const convertedAmount = amount * exchangeRate;
    return {
      content: [
        {
          type: 'text',
          text: `${amount} ${from} is ${convertedAmount.toFixed(2)} ${to}`,
        },
      ],
    };
  }
);

server.resource(
  'price-list',
  new ResourceTemplate('price-list://products/{category}', {
    list: undefined,
  }),
  async (uri, { category }) => {
    const priceList = [
      { name: 'Apple', category: 'Fruit', price: 1.2 },
      { name: 'Banana', category: 'Fruit', price: 0.8 },
      { name: 'Carrot', category: 'Vegetable', price: 0.5 },
      { name: 'Bread', category: 'Bakery', price: 2.5 },
      { name: 'Milk', category: 'Dairy', price: 1.5 },
    ];

    const filteredList =
      category !== 'all'
        ? priceList.filter(
            (item) => item.category.toLowerCase() === category.toLowerCase()
          )
        : priceList;

    return {
      contents: [
        {
          uri: uri.href,
          text: JSON.stringify(filteredList),
          json: filteredList,
        },
      ],
    };
  }
);

server.prompt(
  'get-product-description-prompt',
  {
    productName: z.string().describe('The name of the product.'),
    length: z
      .enum(['short', 'medium', 'long'])
      .optional()
      .describe('The desired length of the description.'),
  },
  async (input) => {
    const { productName, length } = input;

    let promptInstructions = `Please generate a compelling product description.`;

    let promptCore = `The product is named "${productName}". `;
    promptCore += `Focus on its general benefits and appeal. `;

    let refinements = '';

    if (length) {
      let lengthGuidance = '';
      if (length === 'short')
        lengthGuidance = 'Keep it concise, around 1-2 sentences. ';
      if (length === 'medium')
        lengthGuidance = 'Aim for a paragraph of about 3-5 sentences. ';
      if (length === 'long')
        lengthGuidance =
          'Provide a detailed description, potentially multiple paragraphs. ';
      refinements += lengthGuidance;
    }

    const fullPrompt = `${promptInstructions}\n\nProduct Details:\n${promptCore}\n\nStyle and Constraints:\n${refinements.trim()}`;

    return {
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: fullPrompt,
          },
        },
      ],
    };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);

Listing 1: MCP server implementation

The basis for the example is the TypeScript SDK, which is installed with the command npm install @modelcontextprotocol/sdk. The package provides the basis for the server implementation with the McpServer class. The tool, resource and prompt methods are implemented on the server instance, which represent the interface for registering the various MCP features. Once the application has registered all features, it calls the connect method of the server instance and transfers a transport object. As in the example, this can be an instance of the StdioServerTransport class for the Stdio transport or an instance of the StreamableHTTPServerTransport class for a remote interface via Streamable-HTTP. Alternatively, the TypeScript SDK also supports the SSEServerTransport class for Server Sent Events as a transport method.

A number of rules apply to the implementation of the individual features of the MCP server, which the SDK safeguards with very strict schema validation. If the implementation does not follow these rules, the server process cannot be started. The validation also ensures the consistency of the arguments when called and therefore plays an important role in the security of the application.

To define resources, the SDK provides the resource method of the server object. This method accepts the name of the resource, the associated URL and a callback function that produces the result. The callback function may be asynchronous, so that nothing stands in the way of connecting a database. However, there is one special feature when registering resources: If the URL is specified as a character string, it must not contain any dynamic parameters. In this case, the resource method accepts a ResourceTemplate instance. Here, the parameters in the path are marked with curly brackets. The callback function can access the parameters of the URL via its second argument and modify the result accordingly.

In the example, the resource is a static price list for a range of products. It has the name price-list and can be accessed via the URL price-list://products/{category}, where category stands for a category according to which the callback function filters the data records. The TypeScript SDK works with Zod as a validation library and uses it, for example, to check the return value of the resource callback function so that it must return a certain structure. The contents array in the return value can have a number of objects that must have at least the properties uri and text. The SDK also accepts other properties such as json, in which the data may be present as a JSON object.

Don't miss any news – follow us on Facebook, LinkedIn or Mastodon.

This article was originally published in German. It was translated with technical assistance and editorially reviewed before publication.