Home >Backend Development >Python Tutorial >Building a Model Context Protocol Server using Jina.ai and FastMCP in Python
In this post, we'll discuss the Model Context Protocol, why it might be important and walk through building an MCP Server to help us talk to Jina.ai and be able to add web search and fact-checking functionality in Claude Desktop using Python and FastMCP.
Anthropic announced around Thanksgiving last year. Although it garnered some attention, the recognition it has received may be insufficient, considering it could be a pivotal stepping stone in developing the next layer of the AI software stack.
The Model Context Protocol (MCP) is a standardized communication protocol designed specifically for large language models (LLMs).
Think of it as the "HTTP of AI"—just as HTTP standardized how web browsers communicate with web servers, MCP standardizes how LLM applications communicate with tools and data sources.
The current landscape of LLM development faces several hurdles:
Tool Integration Complexity: Each LLM service (like OpenAI, Anthropic, etc.) has its way of implementing tool calls and function calling, making it complex to build portable tools.
Context Management: LLMs need access to various data sources and tools, but managing this access securely and efficiently has been challenging.
Standardization: Without a standard protocol, developers must rebuild integration layers for each LLM platform they want to support.
MCP solves these challenges by providing:
MCP follows a client-server architecture with three main components:
MCP Server: A service that exposes:
MCP Client: The application connects to MCP servers and manages communication between the LLM and the servers. Client support is in its early stages, with only a handful of tools that implement any part of the protocol specification thus far and some functionality that no clients support yet.
And, of course, the LLM...
The workflow is straightforward:
The security situation is more nuanced. While servers using stdio transport are typically colocated with the client, and thus API keys are not necessarily exposed to the internet. They do seem to get passed around fairly casually, IMO.
These keys needed to be loaded into the client when the server started so they could be passed to the child process, and they even appeared in the desktop app logs, which was...concerning.
The widespread use of API keys is a broader issue affecting Gen AI services, platforms, and tooling. Companies like Okta and Auth0 are working on a solution to manage and authorize Gen AIs without relying on keys.
Anthropic officially supports low-level SDKs for TypeScript, Python, and Kotlin. Some of the boilerplate wrappers that have recently been created already cover some of the boilerplate and have other nice features, such as a CLI for debugging, inspecting, and installing servers on the client to make developing MCP servers easier.
The fast, Pythonic way to build MCP servers.
Model Context Protocol (MCP) servers are a new, standardized way to provide context and tools to your LLMs, and FastMCP makes building MCP servers simple and intuitive. Create tools, expose resources, and define prompts with clean, Pythonic code:
# demo.py from fastmcp import FastMCP mcp = FastMCP("Demo ?") @<span>mcp.tool()</span> def add(a: int, b: int) -> int: """Add two numbers""" return a + b
That's it! Give Claude access to the server by running:
fastmcp install demo.py
FastMCP handles all the complex protocol details and server management, so you can focus on building great tools. It's designed to be high-level and Pythonic - in most cases, decorating a function is all you need.
FastMCP is one such framework. We'll now explore how to create an almost practical tool for reading websites, answering search queries through the web, and fact-checking information. We will be using Jina.ai.
It is a very slick service that provides a "Search Foundation platform" that combines "Embeddings, Rerankers, and Small Language Models" to aid businesses in building Gen AI and Multimodal search experiences.
You will need uv installed. It is the recommended way to create and manage Python projects. It's part of a relatively recent but exciting Python toolchain called astral.sh. I recommend you check it out.
It aims to be a one-stop shop for managing projects, dependencies, virtual environments, versions, linting, and executing Python scripts and modules. It's written in Rust. Do with that information what you will ?.
You will also need to install the Claude Desktop App. For our purposes, the Claude Desktop App will serve as the MCP Client and is a key target Client for Anthropic.
Full Walkthrough here:
https://dev.to/asragab/building-a-model-context-protocol-server-using-jinaai-and-fastmcp-in-python-1od8
Using uv you can initialize a project with:
# demo.py from fastmcp import FastMCP mcp = FastMCP("Demo ?") @<span>mcp.tool()</span> def add(a: int, b: int) -> int: """Add two numbers""" return a + b
This will create a folder called mcp-jinaai-reader and a .python-version along with a pyproject.toml.
fastmcp install demo.py
This will create a virtual env corresponding to the python version we chose.
After creating the environment, it will provide instructions on how to activate it for the session.
uv init mcp-jinaai-reader --python 3.11
Add a src directory and install the one dependency we need
cd mcp-jinaai-reader uv venv
Create a .env file at the project root and add your JINAAI_API_KEY to the file. You can obtain one for free by signing up at Jina. In general, any API keys or other env variables your server needs to run will go in this file.
source .venv/bin/activate
In the src directory, create a server.py file...and we should be able to get to the code.
uv add fastmcp
Starting with the imports: httpx, will be the library we use here to make http requests; we need the urlparse method to help us determine whether a string is possibly a valid URL.
JINAAI_API_KEY=jina_*************
This initializes the server; the first argument is the tool's name. I am not 100% sure why uvicorn needs to be explicitly added as a dependency here since it is a transitive dependency of FastMCP but it does seem to be required.
It is likely due to how the fastmcp cli (more on that shortly) installs the server. If you have other dependencies, you must add them here so the client knows you need to install them before running the client; we will see how that works in a moment.
from fastmcp import FastMCP import httpx from urllib.parse import urlparse import os
You can probably suss out the pattern here, but Jina uses different subdomains to route particular requests. The search endpoint expects a query, the reader endpoint expects a URL, and the grounding endpoint can provide the llm with a specific response or answer.
Grounding is a much larger topic and is used with other techniques, such as RAG and fine-tuning, to assist LLMs in reducing hallucinations and improving decision-making.
# Initialize the MCP server mcp = FastMCP("search", dependencies=["uvicorn"])
The annotation @mcp.tool does a lot of the heavy lifting. Similar annotations for resources and prompts exist in the library. The annotation extracts the details of the function signature and return type to create an input and output schema for the llm to call the tool. It configures the tool so the client understands the server's capabilities. It also registers the function calls as handlers for the configured tool.
Next, you'll notice that the function is async. No runtime configuration is needed, and no asyncio.run stuff either. If you need to, for some reason, run the server as a standalone service, you do need to handle some of this yourself. There is an example in the FastMCP repo for how to do this.
The function body is reasonably uninteresting; it validates whether it is receiving a URL, sets the appropriate headers, calls the Jina endpoint, and returns the text.
# demo.py from fastmcp import FastMCP mcp = FastMCP("Demo ?") @<span>mcp.tool()</span> def add(a: int, b: int) -> int: """Add two numbers""" return a + b
fastmcp install demo.py
And that's it...
uv init mcp-jinaai-reader --python 3.11
Running the above command will start the mcp inspector it's a tool that the sdk provides in order to test and debug server responses. The --with-editable flag allows you to make changes to the server, without having to relaunch the inspector (highly, HIGHLY recommended)
You should see:
cd mcp-jinaai-reader uv venv
By default the inspector runs on port 5173, and the server (the code you just wrote) will run on port 3000, you can change this by setting the SERVER_PORT and CLIENT_PORT before invocation.
source .venv/bin/activate
If all goes well you should see something like the following, on the left you can add the environment variables you'll need, here the JINAAI_API_KEY is the only one.
If you click on Tools on the top menu bar, and then List Tools you should the tools we created, notice that the docstring serves as the description for the tool.
Clicking on a particular tool will bring up the textboxes for you to enter the parameters needed to call the tool.
After you are satisfied things are working as expected, you are now ready to install the server on the Claude Desktop App client.
uv add fastmcp
Will do this, I am sure in the future it will support other clients, but for now, this is all you need to do. The -f .env will pass the env variables to the app client.
What this does under the hood is update the claude_desktop_config.json and provides the necessary command and arguments to run the server. By default this uses uv which must be available on your PATH.
If you now open the Claude Desktop App, and go to the Menu Bar and Click Claude > Settings and then click on Developer you should see the name of your tool you set when initializing the server.
Clicking on it should bring up it's config. Not only will you how it gets executed, but in the Advanced Options you'll see the env variables that have been set.
You can also edit this config directly, but I wouldn't necessarily recommend it here.
If all goes well when you go the Desktop App you should see no errors (if you do, going to the Settings should give you a button to check out the logs and investigate from there).
Additionally you should see a hammer symbol with the number of individual tools you have at your disposal (note: yours should probably be two unless you've installed other MCP servers)
Rather than invoking the tool directly you chat with the app as you would normally, and when it encounters a situation where it deduces that the tool is helpful it will ask if you want to use it. No additional code or configuration here is necessary.
I think it relies both on the tool name and description in order to decide whether it is appropriate, so it's worth crafting a clear simple description of what the tool does.
You will get a prompt like the following:
And you can just "chat" with it, admittedly the tool as written sometimes runs into issues. Occasionally it decides it can't access the internet, sometimes it fails to retrieve results, but sometimes you get this:
This had kind of a natural flow, where it read the page, provided a summary, and you ask it to go to a specific article and read that.
Hopefully, that gave you some insight into MCP Servers. There's plenty to read and watch but one more site I'll recommend is glama.ai they are keeping a fairly comprehensive list of available MCP Servers to download and try out, including other web search tools that more reliable than our toy example. Check it out, and thank you for following along.
The above is the detailed content of Building a Model Context Protocol Server using Jina.ai and FastMCP in Python. For more information, please follow other related articles on the PHP Chinese website!