Skip to main content
Submit your abstract for AI in Production 2027. The call for talks is now open.

See all results
items
    • Overview 
    • Join Us  
    • Community 
    • Contact 
    • Overview 
    • Course Catalogue 
    • Public Courses 
    • Overview 
    • Posit 
    • Data Science 
    • Engineering 
    • Blog 
    • Case Studies 
    • R Package Validation 
    • Gallery 
    • diffify  
    • Pro Bono Support 

What is an MCP server?

Authors: Ishbel Carlyle & Russ Hyde

Published: October 15, 2026

tags: ai, llm, mcp, claude, r, python

The Model Context Protocol (MCP) is quietly becoming one of the most useful ideas in AI tooling. Here, we describe what MCP is, why it matters, and how to build an MCP server yourself.

MCP is a standard way to link a Large Language Model (LLM) to the outside world. An MCP server is a small piece of software that exposes some data or capability to an AI model. Everyday examples include connecting your AI agent to your calendar or your file system, or allowing it to search the web. These are things an LLM cannot do (or would struggle to do) by itself. On its own, a model only knows what it learned during training, plus whatever you provide it, and it has no way to check, fetch or act on anything beyond that. Even then, it can only give out text responses, which you have to be the one to act on. An MCP server can allow an LLM to carry out actions itself. Less talking, more doing.

Two-panel diagram. Left: an LLM alone, with training data only, frozen at its training
    cut-off with no way to check anything new. Right: an LLM connected via MCP to a database, email,
    a filesystem and the internet, grounded in live, current data.
An LLM alone is frozen at its training cut-off. Connected via MCP, it can check, fetch and act instead of just recalling.

Join us for our AI in Production conference! For more details, check out our conference website!

Why not just use an API?

There are many ways to connect external capabilities to an AI model, usually via an API (Application Programming Interface), and this was the industry standard. However, APIs are all configured slightly differently, and once you start to scale up the number of connections you need between multiple pieces of software, you hit a combinatorial problem: the number of custom connections a product needs multiplies by the number of tools that use it.

Anthropic introduced MCP in late 2024 as an open standard for connecting tools and data to LLM applications. It works like a USB port on a computer: any MCP server can be used with any agent/model, whether you are using Claude, Gemini, ChatGPT or one you’ve made yourself.

Before-and-after diagram. Before: five products each need their own custom connection to
    three AI tools, giving 15 separate connections to build and maintain. After: all five products
    connect through MCP, one standard layer, giving eight total connections through one protocol.
MCP replaces a tangle of custom connections with one standard layer.

Building your own MCP server

Full instructions on how to build an MCP server are available in the MCP documentation, but here is a very quick overview using Claude Code and the Sonnet 5 model.

The good news is that a working MCP server can be as short as a few lines of code. Here’s the shape of it, step by step, using Python.

Step 1: Install the dependencies

The MCP project maintains official SDKs for Python, TypeScript and other languages. The Python ‘mcp’ package embodies this SDK. However, here we are going to use the ‘fastmcp’ package. This is built upon ‘mcp’, but has some benefits (in particular, ‘fastmcp’-based tools can readily be deployed on Posit Connect, see later).

mkdir -p ~/projects/
cd ~/projects
uv init --python=3.15 dice-roll
# Initialized project `dice-roll`
# at `/home/<user>/projects/dice-roll`
cd dice-roll
uv add fastmcp

This gives us the following project structure, which is typical for a ‘uv’ project. We will define our MCP server in ./src/dice_roll/__init__.py, although if the project got much larger, we would split it into separate modules or sub-packages.

tree -L 2
.
├── pyproject.toml
├── README.md
├── src
│   └── dice_roll
└── uv.lock

Step 2: Define a tool

An MCP server exposes “tools”: functions the model can call. Here is a mini example: a server with a single tool that rolls a dice.

# src/dice_roll/__init__.py
from typing import Annotated
import random

from fastmcp import FastMCP
from pydantic import Field

# Give your server a name: this is what shows up when a model connects
mcp = FastMCP("dice-pick")

@mcp.tool
def roll_dice(
    sides: Annotated[int, Field(description="Number of sides on the dice", ge=2)] = 6,
) -> int:
    """Roll a single fair dice with the given number of sides and return the number rolled."""
    return random.randint(1, sides)

def main() -> None:
    mcp.run()

The @mcp.tool docstring (“Roll a single …”) is important. This becomes the description of the tool that the LLM reads. It is like the blurb of a book: the LLM will look at the description and from it decide whether it wishes to use the tool or not. If this description isn’t clear, it may lead to the tool not being used.

The type annotations for both the parameter sides and the value returned by roll_dice() also provide useful metadata for the LLM. Our sides parameter has a constraint that ensures at least 2 sides are present on the dice (ge=2; this is enforced within Python by Pydantic). When describing the tools provided by this MCP server, an LLM will be able to explain that the minimum number of sides is 2 (and more advanced models may not call the tool otherwise).

Step 3: Connect it

Save the code in the project, and we will test whether an MCP-compatible agent can use it. There are a few ways to register our locally-defined MCP server with Claude Code.

Since we have used the fastmcp framework, there is a command line tool that installs the MCP server for use from the project directory:

# From the ~/projects/dice-roll/ directory
uv run \
  fastmcp install claude-code src/dice_roll/__init__.py:mcp --with-editable .

The uv run part is necessary to ensure the correct Python environment is used. The fastmcp command needs to find the mcp object that we have defined in our source code. If we open claude and ask “What dice-pick tools do you know about?”, it replies:

The dice-pick MCP server gives me one tool:

roll_dice: rolls one fair dice and returns the number it lands on.

┌───────────┬─────────┬─────────┬───────────────────┐
│ Parameter |  Type   │ Default │    Constraint     │
├───────────┼─────────┼─────────┼───────────────────┤
│ sides     │ integer │ 6       │ must be 2 or more │
└───────────┴─────────┴─────────┴───────────────────┘

So I can roll a d6, d20, d100 or any other single dice. It can't roll several dice in one call, add
modifiers or pick from a list. For something like 3d6, I'd call it three times.

Then we can use the tool:

❯ Roll a dice
  Called dice-pick
I rolled a 6 on a standard six-sided dice.

...

❯ Roll a 20-side dice
  Called dice-pick
I rolled a 20 on the d20.

That's the highest number on both rolls so far. [... blah blah blah ...]

...

❯ Roll a d12
  Called dice-pick
I rolled a 4 on the d12, so the run of top scores is over.

We have installed our locally in-development MCP server on our machine, but it’s only usable from the ~/projects/dice_roll directory. This is fine while you are testing, but you may want to use your MCP server in other locations once it works well.

Our project is uv-based. If we tell it which module and function should be called to start the project running, we can make the project more easily usable from other locations. But, the entry-point function used to start our MCP server running is dice_roll.main(), which uv has already added as the entry-point to the project in the pyproject.toml:

[project.scripts]
dice-roll = "dice_roll:main"

On running uv run python or uv sync, a dice-roll entry-point will be added to the virtual environment for the project.

For Claude Code, we can then register our MCP server with a single command:

claude mcp add --scope user dice-pick \
  -- /abs/path/to/projects/dice-roll/.venv/bin/dice-roll

Now, you can ask claude to “roll a 20-sided dice” from any directory on your computer and it will call your tool directly rather than guessing at an answer. This is possible because claude mcp add ... appends to the mcpServers entry of a file (~/.claude.json).

A few tips before you build something bigger:

  • Keep tool descriptions (the docstrings for your @mcp.tool-decorated functions) short and specific. The model relies on the docstring to decide when to use a tool, so vague descriptions lead to it being ignored or misused.
  • Think about what should require confirmation. Read-only tools (like looking something up) are usually safe to let a model call freely; tools that change or delete something are worth gating behind an explicit user confirmation step.

You can remove the MCP server from your claude setup by using

claude mcp remove dice-pick

Or just ask Claude to remove it!

Deploying to Posit Connect

A server running on your own machine is fine for testing, but it is only useful to you. MCP servers can be deployed to a range of different cloud providers. Posit Connect, the platform we normally reach for when publishing Shiny apps, Plumber APIs and Quarto reports, added first-class support for hosting MCP servers in 2026, complete with a dedicated icon and its own tab in the content list.

Posit Connect does expect you to write your Python-based MCP servers using the fastmcp framework that we used above. Connect also needs to import a runnable application object that can handle HTTP-based requests. When deployed, ‘fastmcp’-based MCP servers can expose an ASGI app object that handles these requests. Locally run servers use ‘stdio’ routing, but we only have to modify our current source code slightly to deploy to Posit Connect.

# imports ...

mcp = ...

def roll_dice():
    ...

app = mcp.http_app()  # For deployment on Connect

def main():
     mcp.run()  # For running using stdio locally

For deployment of the MCP server to Posit Connect, we also need to create a requirements.txt file. These are used to specify the run-time (and sometimes development) dependencies of a project. Since we used uv to manage the project, this is easy to create:

uv export --no-dev --no-hashes -o requirements.txt

The requirements.txt file contains a line -e . that dynamically includes any source code from src/. The Python environment that is built for your MCP server depends upon requirements.txt and is rebuilt when that file is updated.

We use the rsconnect-python package (and the rsconnect command line tool that it installs) to interact with Posit Connect. You may first have to register with your Connect system using rsconnect add ... or rsconnect login ....

Then you can deploy with the rsconnect tool, using the FastAPI deployment mode and pointing the entrypoint at the app object that we have just added to the source code. This is done using a --entrypoint argument of the form module:object_name. Here this is dice_roll:app, since our app object is in the dice_roll module:

rsconnect deploy fastapi -n <nickname-of-posit-server> --entrypoint dice_roll:app ./

The Posit Publisher extension for VS Code and Positron works too, if you’d rather deploy from your editor. Posit has some good documentation on publishing MCP servers.

On first deployment, Connect scans requirements.txt, recognises fastmcp, and automatically sets the content type to MCP. Once deployed, it keeps at least one process warm so there is no cold-start delay, and gives it its own icon in the content list, with no extra configuration needed.

Using your deployed MCP server

To use an MCP server that is deployed in the cloud, the process is very similar to how we added our locally-defined MCP server. On Posit Connect (or wherever you have published), you will have a URL for the MCP server and you can configure claude to use this URL as follows:

claude mcp add --transport http <mcp-name> https://<connect-server>/content/<content-hash>/mcp

Here we used dice-pick as the name of the MCP server, consistent with the name in the source code. When you next open claude you will be prompted to authenticate before you can use the tool.

Want to see this in action? Watch this walkthrough, recorded live at our AI in Production 2026 conference, of an MCP server being built and used from scratch.

Key takeaways

  • An MCP server is the standard way to give an AI model access to things it can’t do alone: checking a calendar, reading a file, searching the web, or looking up live data.
  • It works like a USB port: build the connection once, and any compatible model can use it.
  • A working MCP server can be as short as a few lines of code. The model calls your tools directly instead of guessing at an answer.
  • Keep tool descriptions short and specific, and gate anything destructive behind explicit confirmation.
  • Posit Connect can host your MCP server directly, so your whole team can use it rather than just your own machine.

Jumping Rivers Logo

You might also like

LLMs in R & Python

Learn how to work with Large Language Models programmatically in R and Python, from sending API requests to generating structured outputs and summarising content.

{ellmer} for Dynamic Alt Text

Ever had trouble adding alt text to your dynamic plots and tables within Shiny apps? That's where ellmer comes in, allowing us to query LLM APIs and generate suitable alt text.

Retrieval-Augmented Generation: Setting up a Knowledge Store in R

Learn about retrieval-augmented generation (RAG) workflows for large language models. This blog provides a hands-on introduction, including example code for creating a working knowledge store using open source packages in R.

You Might Also Like

  • Programming with LLMs in R & Python
  • Using {ellmer} for Dynamic Alt Text Generation in {shiny} Apps
  • Retrieval-Augmented Generation: Setting up a Knowledge Store in R

Recent Posts

  • What is an MCP server? 
  • AI in Production 2027 Call for Speakers: What Makes a Strong Talk 
  • What's New in Python 3.15? 
  • Autumn 2026 Data Science Training Courses 
  • Using GitHub Actions to deploy to Posit Connect Cloud 
  • A Summer, Explained with R 
  • Posit Assistant: Is it worth the switch? 
  • Announcing AI in Production 2027 
  • Is SAS Still Used, and Is It Worth Keeping? 
  • Why Learn the Command-Line Interface? 

Keep Updated

Like data science? R? Python? Stan? Then you’ll love the Jumping Rivers newsletter. The perks of being part of the Jumping Rivers family are:

  • Be the first to know about our latest courses and conferences.
  • Get discounts on the latest courses.
  • Read news on the latest techniques with the Jumping Rivers blog.

We keep your data secure and will never share your details. By subscribing, you agree to our privacy policy.

Follow Us

  • GitHub
  • Bluesky
  • LinkedIn
  • YouTube
  • Eventbrite

Find Us

The Catalyst Newcastle Helix Newcastle, NE4 5TG
Get directions

Contact Us

  • hello@jumpingrivers.com
  • + 44(0) 191 432 4340

Newsletter

Sign up

Events

  • North East Data Scientists Meetup
  • Leeds Data Science Meetup
  • AI in Production
British Assessment Bureau, UKAS Certified logo for ISO 9001 - Quality management British Assessment Bureau, UKAS Certified logo for ISO 27001 - Information security management Cyber Essentials Certified Plus badge
  • Privacy Notice
  • |
  • Booking Terms

©2016 - present. Jumping Rivers Ltd