Gloweet
    • Projects
    • Contact me
    • About me
    • Bio
    • Expertises
    • Stay updated
    • Blogs
    • Newsletter
Language
Language
New project
Antonin Marxer's Blog
Create your own MCP servers
Create your own MCP servers

Create your own MCP servers

Discover how to design and build secure MCP servers using Typescript or Python.

Antonin Marxer

·

Last updated on September 2, 2025

·

17min read

Table of contents

  • MCP architecture & design
  • How to design my MCP? Not like APIs.
  • How do AI and MCP servers communicate?
  • Build & deploy MCP servers for Cloudflare Workers
  • Build Python MCP servers with FastMCP
  • Use Streamable HTTP
  • Use run\async() inside async functions
  • Add unit tests
  • Add explicit error feedback
  • Package your Python MCP Server
  • Add Health Checks
  • Add auhentication
  • Deploy your FastMCP server
  • Turn existing agents into MCP servers with auto-mcp
  • Conclusion
  • Sources

There are many resources on this topic, but I couldn't find a clear overview of MCP servers or a standard method for building one, except for Redditors suggesting to “ask Claude or Windsurf.” I've combined a DevOps perspective, tutorials, and curated lists into this guide, which I hope will clarify the MCP concept and help you implement standard-based MCP servers.

In this article I go through:

  1. MCP Architecture & design:
    Understand MCP client-server communication architecture and how to design an MPC (opiniated)

  2. Build & deploy TypeScript MCP servers for Cloudflare Workers

  3. Build Python MCP servers with fastmcp

  4. Turn existing agents into MCP servers with auto-mcp

MCP architecture & design

As I mentioned in my previous article How to use MCP servers, the Model Context Protocol (MCP) standard sets up a framework for how a tool and an AI agent can interact. An MCP gives the agent a list of tools it can use, and the agent just needs to pick the right one to complete the task.

For example, you want to manage GitHub PRs and get your GitHub repos from your agent.
→ The GitHub MCP Server wraps GitHubs' features and make them accessible to your IDE’s agent

How to design my MCP? Not like APIs.

Agents that use MCPs are generalists. If you base your MCP on an API specs, the available tools will be atomic/granular and must be used in a specific order to work well together.

Let’s take the image of a customer support ticket API that provides the following endpoints:

  • createTicket

  • addMessage

  • assignAgent

  • updateStatus

  • closeTicket

For an LLM, this is like being given every screw, wire, and circuit board instead of a "resolve customer issue" button. It has to infer which order matters (should a ticket be assigned before a response is added?), what status transitions are valid (open → pending → resolved vs. open → closed), and when to stop.

In MCP design, you’d expose higher-level tools like:

  • resolveTicket: handles assignment, messaging, and closing

  • escalateTicket: handles routing and notifying supervisors

  • summarizeConversation: provides a condensed view for the agent

This shifts the burden away from orchestration logic toward goal completion. The LLM doesn’t need to simulate being a support engineer juggling APIs. It just chooses the right tool for the job.

However, an API is an extremly good starting point.

How do AI and MCP servers communicate?

There are three core components in an MPC communication:

  • Host: The environment where AI tasks are executed, such as IDEs or AI-powered applications like Claude Desktop.

  • Client: Acts as an intermediary, managing communication between the host and MCP servers. It handles requests, retrieves server capabilities, and processes notifications.

  • Server: Provides access to external tools, resources, and prompts. It enables AI models to perform operations like data retrieval, API invocation, and workflow optimization.

![MCP workflow](https://cdn.prod.website-files.com/6321a7346f73799791d41a39/67f760d5fa47b33e78f7a1c0_AD_4nXdUTtXUiCEJDVbmTV71N740IUTqi7qd06Ih1xOCnhTYa11lh58btM-KqLDuyuabJQUkzpjqY3DpJHzcpSMiy09fL8lOoxJk1RdM-6zVuejkC6cdwFMmFctDExuckhRVnk8Evpw2.avif align="left")

MCP process

Your IDE acts as an MCP Host by hosting MCP clients that query MCP servers.
At initialization, your IDE’s agent queries MCP servers to discover available tools with the messages ListToolsRequest/ListToolsResult. Then it calls these tools using CallToolRequest/CallToolResult messages.

MCP Hosting

MCP servers may be deployed in local as well as remote environments.
Local MCP servers follow a privacy-first approach and uses local Standard IO transport protocol.

Remote MCP servers offer:

  • Consistency: One server version, same behavior for all clients.

  • Team integration: Add a tool once, instantly available to everyone.

  • Centralization: No duplicated setup on developer machines.

  • Scalability: Heavy workloads handled by remote infra, many clients supported.

  • Isolation: Server exposes only the resources and files it’s meant to.

If your MCP uses sensitive local files or requires very low latency, I suggest to make the MCP server local only.

You’ll see below how to host a MCP server using Cloudflare workers.
In addition to that I’ll give more insights about MCP hosting in a dedicated article
→ stay tuned

MCP Protocols

MCP is transport agnostic, which means the client and server can communicate over different protocols depending on your setup.

However, it always follow an RPC-style pattern (Remote Procedure Call): Think of it as “on demand” requests. It allows clients to invoke methods on remote servers as if they were local function calls. This hides the complexities of model hosting, resource management, and request handling

In your MCP server, responses should follow JSON-RPC 2.0 specification.

![Architecture diagram showing the communication flow from Tools to Client applications](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*17aHWZr0VGP7EGwbzB2QXw.png align="center")

Let’s review available protocols:

  • Standard IO (Stdio): common local bidirectional communication, simple, typical for CLI processes or containers.

  • HTTP with two subtypes:

    1. SSE (Server-Sent Events): unidirectional server-to-client communication. Real-time push via HTTP, text-only since it uses the text/event-stream MIME type.

      It opens a long-lived session in /sse endpoint.

      It receives messages form the /messages endpoint, and replies through the SSE session.

      This is now deprecated: It doesn’t suit ephemeral servers (serverless) as this protocol keeps the SSE session open.
      Use Streamable HTTP instead through the /mcp endpoint ⬇️

    2. Streamable HTTP: unidirectional or bidirectional HTTP communication with chunked transfer encoding. It uses a single endpoint: /mcp

  • WebSocket: A dedicated protocol (ws:// or wss://) enabling interactive bidirectional communication, listening for changes and getting notified when something comes in.

Issues due to remote connections

To push data to clients as needed, SSE & WebSocket connections are long-lived. If you host your MCP server remotely, the connection may remain open for a long time.

Common issues when using HTTP & WebSocket are:

  • Random connection drop halfway through a response

  • Most browsers have a limit of 6 concurrent SSE (EventSource) connections per domain, which may be a problem when musing multiple times.

  • Hard to know where the problem is: MCP client, server or network layer.

As a solution, you should:

  • Use /mcp Streamable HTTP protocol.

  • Use snake_case or dash formats for MCP tool names to maintain client compatibility and avoid subtle request failures.

  • Validate incoming and outgoing payloads against strict JSON schemas to catch errors before processing.

Return common JSON-RPC 2.0 error codes

Implementing meaningful feedback helps handling errors at the MCP Client level.

Code Description Common Cause How to Handle
jso-32000 Authentication Error Missing or invalid token Return WWW-Authenticate header
-32001 Invalid Session Session ID not found Client should reinitialize
-32002 Method Not Found Client called unknown method Check method name
-32003 Invalid Parameters Missing or invalid parameters Validate parameters
-32004 Internal Error Server-side exception Log details for debugging
-32005 Parse Error Invalid JSON Validate request format

Implementing these error-handling strategies ensure that clients receive meaningful feedback when issues occur.

Observability and Troubleshooting

  • Implement detailed structured logging with unique correlation IDs to trace requests, responses, and errors.

  • Expose health check endpoints and set up monitoring and alerting on connection errors and abnormal metrics.

MCP security risks & best practices

When you’re running an MCP server locally, it’s the same as running any other software, with unlimited access to all your files. You should use Docker to run MCP servers securely. I’ll soon make a guide about it.
For remote MCP servers, you must also limit accessible resources, especially when self-hosting.

Remote frameworks integrate security constraints based on these risks:

  • Prompt injection & tool poisoning: Malicious prompts may call harmful MCP tools.
    → Some IDEs allow to blacklist certain MCP tools, or ask to confirm the action

  • Code injection: An MCP server running commands (e.g: filesystem) may run rm -rf / instead of listing files, because a string wasn’t validated.
    → Sanitize strings

  • Credentials leakage: The token you pass to your MCP server may be leaked, exposed in a debug log.
    → Use MCP frameworks that support OAuth 2.1, JWT tokens, HMAC signatures.

    → Log authentication/authorization/activity logs for traceability.

  • Over-permissioning: Follow the principle of least privilege: no resource should ever have more access that it needs to perform is intended tasks
    → Use Role-Based-Access-Control (RBAC)
    → Limit accessible resources

  • Ensure tool integrity: Legitimate MCP servers can be spoofed. Without integrity validation, your daa can be sent to malicious substitute MCP servers.
    → Notify users whenever tools are updated or replaced.
    → Require certificate pinning for all TLS connections.
    → Block endpoints that do not pass TLS or metadata verification.

In the next parts of this article, we’ll see which frameworks comply with those requirements.

MCP use cases

Now that the basics are clear, we can review MCP servers main use cases:

  1. Wrap an existing API: Expose third-party services (GitHub, Jira, Slack, AWS).

    We’ll go through each step using Tadata, which generates MCP servers from OpenAPI specification files.

  2. Framework adapter: Bridge orchestration or agent frameworks (CrewAI, LangGraph, LlamaIndex, OpenAI).
    We’ll use auto-mcp for that usage.

  3. Connect to a remote host from a local-only MCP client:
    Use mcp-remote, a CLI tool that enables existing MCP clients, like Cursor or Windsurf, to connect to remote servers.

    As I mentioned before, Streamable HTTP should be preferred over the SSE protocol. That’s exactly what mcp-remote does:
    When running mcp-remote https://my-mcp.example.com mcp-remote tries the /mcp endpoint first. If it fails wih a 404, it fallbacks to the /sse endpoint.

  4. Custom logic: Implement domain-specific workflows or business rules from scratch. Let’s not reinvent the wheel, but rather use frameworks which have built-in OAuth 2.1 support:

    • Official TypeScript SDK for MCP: typescript-sdk

    • TypeScript FastMCP framework: punkpeye/fastmcp

    • Pythonic way to build MCP servers & clients: jlowin/fastmcp

    For an easy-setup, if you use TypeScript, I recommend using cloudflare mcp servers templates, see cloudflare/ai/demos. We'll get to that later in the section below.

    • remote-mcp-google-oauth: Model Context Protocol (MCP) Server + Google OAuth

    • remote-mcp-auth0: Model Context Protocol (MCP) Server + Auth0

  5. Use MCPs in a Python app: Need to use existing MCPs into your python app.

  6. Use MCPs in a TypeScript app: You can use mcp-client to use any MCP servers in your JS app.

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">import</span>&lt;<span class="hljs-regexp">/span&gt; { &lt;span class=&quot;hljs-title class_&quot;&gt;MCPConnectionManager&lt;/</span>span&gt; } &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">from</span>&lt;<span class="hljs-regexp">/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;mcp-client&amp;#x27;&lt;/</span>span&gt;;
<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>const<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> manager = <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>new<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-title class_&quot;</span>&gt;</span>MCPConnectionManager<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>();
<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>await<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> manager.&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;initialize&lt;<span class="hljs-regexp">/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;./m</span>cp-config.<span class="hljs-property">json</span>&amp;#x27;&lt;/span&gt;);
<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>const<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> client = manager.&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;getClient&lt;<span class="hljs-regexp">/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;memory&amp;#x27;&lt;/</span>span&gt;);
<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>const<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> tools = <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>await<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> client?.&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;listTools&lt;/span&gt;();
<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>const<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> result = <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>await<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> client?.&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;callTool&lt;<span class="hljs-regexp">/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;#x27;toolName&amp;#x27;&lt;/</span>span&gt;, { <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;</span>/* params */<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> });
<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>await<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> manager.&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;cleanup&lt;/span&gt;();
  1. Proxy/gateway: Aggregate multiple services or enforce access policies. The major tools MCP gateways are:
  • Unla MCP Gateway: A lightweight gateway service that instantly transforms existing MCP Servers and APIs into MCP servers with zero code changes. Features Docker deployment and management UI, requiring no infrastructure modifications.

  • Microsoft MCP gateway: reverse proxy and management layer for MCP servers, enabling scalable, session-aware routing and lifecycle management of MCP servers in Kubernetes environments.

You know have a broader view on the MCP ecosystem. Now, let’s create your first MPC server ⬇️

Build & deploy MCP servers for Cloudflare Workers

I suggest using the Cloudflare MCP servers templates, which support OAuth and Streamable-HTTP. They come both in Typescript and Python version, although the Python version cannot be deployed using the Free plan as it goes beyond the size limit. We’ll see another pythonic way in the next secion to share a MCP server.

Streamable-HTTP has been recently added in Cloudflare templates through the /mcp endpoint:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1756814384051/a51232e7-4183-4225-9a92-fce9539e0e09.png align="center")

/sse endpoint is kept as fallback, but if it’s being used by using mcp-outline —sse-first <your_mcp_server_url> then your costs may rise high. Hence I suggest to just remove it, which will throw a 404 automatically.

Start to build your TypeScript-based MCP-Server using Cloudflare Wokers

Let’s say you want your organization members to connect to your custom MCP. If using Google Workspace, you need Google OAuth, if using Auth0, you need Auth0 Oauth, etc.

Pick the template that suits your use-case, then run:

npm create cloudflare@latest -- my-mcp-server-google-auth --template=cloudflare/ai/demos/remote-mcp-google-oauth &lt;span class=<span class="hljs-string">&quot;hljs-built_in&quot;</span>&gt;<span class="hljs-built_in">cd</span>&lt;/span&gt; my-mcp-server-google-auth npx wrangler@latest deploy

The main difference between google and github templates are actually located in the src/index.ts file, where the defaultHandler is set to the GithubHandler:

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">import</span>&lt;<span class="hljs-regexp">/span&gt; &lt;span class=&quot;hljs-title class_&quot;&gt;GitHubHandler&lt;/</span>span&gt; <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>from<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;</span><span class="hljs-symbol">&amp;quot;</span>./github-handler<span class="hljs-symbol">&amp;quot;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>;

<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>export<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>default<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;</span>new<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-title class_&quot;</span>&gt;</span>OAuthProvider<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>({
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-attr&quot;</span>&gt;</span>apiHandlers<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>: {
         <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;</span>// Deprecated SSE protocol - use /mcp instead<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>
        <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;</span><span class="hljs-symbol">&amp;quot;</span>/sse<span class="hljs-symbol">&amp;quot;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>: <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-title class_&quot;</span>&gt;</span>MyMCP<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>.&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;serveSSE&lt;<span class="hljs-regexp">/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;/</span>sse&amp;quot;&lt;/span&gt;),
        <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;</span><span class="hljs-symbol">&amp;quot;</span>/mcp<span class="hljs-symbol">&amp;quot;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>: <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-title class_&quot;</span>&gt;</span>MyMCP<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>.&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;serve&lt;<span class="hljs-regexp">/span&gt;(&lt;span class=&quot;hljs-string&quot;&gt;&amp;quot;mcp&amp;quot;&lt;/</span>span&gt;) &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment">// Streamable-HTTP protocol&lt;/span&gt;</span>
    },
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-attr&quot;</span>&gt;</span>defaultHandler<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>: <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-title class_&quot;</span>&gt;</span>GitHubHandler<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>,
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-attr&quot;</span>&gt;</span>authorizeEndpoint<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>: <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;</span><span class="hljs-symbol">&amp;quot;</span>/authorize<span class="hljs-symbol">&amp;quot;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>,
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-attr&quot;</span>&gt;</span>tokenEndpoint<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>: <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;</span><span class="hljs-symbol">&amp;quot;</span>/token<span class="hljs-symbol">&amp;quot;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>,
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-attr&quot;</span>&gt;</span>clientRegistrationEndpoint<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>: <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;</span><span class="hljs-symbol">&amp;quot;</span>/register<span class="hljs-symbol">&amp;quot;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>
});

You need to create a Google OAuth app to use Google as an IdP (identity provider): one for local development, and one for production.

  1. Go to Google Cloud Console

  2. Navigate to APIs & Services → OAuth consent screen → configure name, email, scopes.

  3. Go to Credentials → Create Credentials → OAuth Client ID.

    • Choose Web Application.

    • Add Authorized redirect URIs:

      • Dev: http://localhost:3000/auth/callback

      • Prod: https://yourdomain.com/auth/callback

  4. Set the secrets locally:

    &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment">// .dev.vars&lt;/span&gt;</span>
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-variable constant_&quot;</span>&gt;</span>GOOGLE_CLIENT_ID<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>=<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;</span><span class="hljs-symbol">&amp;quot;</span>your-client-id<span class="hljs-symbol">&amp;quot;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-variable constant_&quot;</span>&gt;</span>GOOGLE_CLIENT_SECRET<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>=<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;</span><span class="hljs-symbol">&amp;quot;</span>your-client-secret<span class="hljs-symbol">&amp;quot;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>
  5. Run & test locally

    npm start

    This serves your MCP server on http://localhost:8788/sse

  6. Debug the MCP server (local & remote)

    Use MCP Inspector, an interactive MC client that lists discovered tools and invoke them from a web browser.

    npx @modelcontextprotocol/inspector@latest

    Go to http://localhost:5173. In the inspector, enter the URL of your MCP server, http://localhost:8788/sse, and click Connect.
    You should be redirected to a GitHub authorization page. Once authorized, you're redirected to the inspector and can list & invoke tools.

  7. Run remotely

    If you don’t have a Cloudflare account, you must create one. Use the free plan. Then install wrangler and login.
    Now, configure secrets & deploy your MCP server to Cloudflare Workers (serverless):

    &lt;span class=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;# Configure global secrets&lt;/span&gt;
    wrangler secret put -e production HCAPTCHA_SITE_KEY
    wrangler secret put -e production GITHUB_CLIENT_SECRET
    &lt;span class=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;# Deploy&lt;/span&gt;
    wrangler deploy -e production
  8. Configure your IDE to use the remote MCP Server

    claude_desktop_config.json
    {
      &lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;mcpServers&amp;quot;&lt;/span&gt;: {
        &lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;my_cloudflare_worker_mcp&amp;quot;&lt;/span&gt;: {
          &lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;<span class="hljs-built_in">command</span>&amp;quot;&lt;/span&gt;: &lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;npx&amp;quot;&lt;/span&gt;,
          &lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;args&amp;quot;&lt;/span&gt;: [&lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;mcp-remote&amp;quot;&lt;/span&gt;,&lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;https://worker-name.account-name.workers.dev&amp;quot;&lt;/span&gt;]
        }
      }
    }

Build Python MCP servers with FastMCP

For a more feature-rich experience, you can use jlowin/fastmcp, which offers additional features and a more pythonic approach.

Start by adding FastMCP as a dependency:

uv add fastmcp
&lt;span class=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;# or use uv pip install fastmcp&lt;/span&gt;

Create a FastMCP server by instantiating the FastMCP class. Create a server.py file:

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">from</span>&lt;/span&gt; fastmcp &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">import</span>&lt;/span&gt; FastMCP, Tool &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment"># FastMCP 2.0 syntax&lt;/span&gt;</span>

mcp = FastMCP(&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;DocumentMCP&amp;quot;&lt;/span&gt;)

To add a tool, write a function and decorate it with @mcp.tool to register it with the MCP server.
Let’s implement a tool that reads a document based on a doc_id param. We validate the parameter using pydantic:

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">from</span>&lt;/span&gt; fastmcp &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">import</span>&lt;/span&gt; FastMCP, Tool &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment"># FastMCP 2.0 syntax&lt;/span&gt;</span>
&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">from</span>&lt;/span&gt; pydantic &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">import</span>&lt;/span&gt; BaseModel, Field

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">class</span>&lt;/span&gt; &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-title class_&quot;</span>&gt;DocumentQuery&lt;/span&gt;(&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-title class_ inherited__&quot;</span>&gt;BaseModel&lt;/span&gt;):
    doc_id: &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-built_in&quot;</span>&gt;<span class="hljs-built_in">str</span>&lt;/span&gt; = Field(description=&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;ID of the document to retrieve&amp;quot;&lt;/span&gt;)

mcp = FastMCP(&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;DocumentMCP&amp;quot;&lt;/span&gt;)

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-meta&quot;</span>&gt;@mcp.tool&lt;/span&gt;
&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">def</span>&lt;/span&gt; &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;read_document&lt;/span&gt;(&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-params&quot;</span>&gt;query: DocumentQuery&lt;/span&gt;) -&amp;gt; &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-built_in&quot;</span>&gt;<span class="hljs-built_in">str</span>&lt;/span&gt;:
    &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;&amp;quot;&amp;quot;
    Read the contents of a document <span class="hljs-keyword">and</span> <span class="hljs-keyword">return</span> it <span class="hljs-keyword">as</span> a string.
    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment"># Implementation here&lt;/span&gt;</span>
    &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">return</span>&lt;/span&gt; &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;f&amp;quot;Content of document &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-subst&quot;</span>&gt;{query.doc_id}&lt;/span&gt;&amp;quot;&lt;/span&gt;

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">if</span>&lt;/span&gt; __name__ == &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;:
    mcp.run() &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment"># Uses STDIO transport by default&lt;/span&gt;</span>

Use Streamable HTTP

Your MCP servers is local only by default as it uses Standard IO (stidio) protocol. Replace that with Streamable HTTP:

mcp.run(transport=&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;http&amp;quot;&lt;/span&gt;, host=&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>&amp;quot;&lt;/span&gt;, port=&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-number&quot;</span>&gt;<span class="hljs-number">8000</span>&lt;/span&gt;)

Run your application with FastMCP CLI:

fastmcp run server.py

Your server is now accessible at http://localhost:8000/mcp/.

Use run_async() inside async functions

mcp.run() is actually a synchronous wrapper around our async logic.
If your application is already running in an async context, use run_async():

from fastmcp import FastMCP
import asyncio

mcp = FastMCP(name=&lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;MyServer&amp;quot;&lt;/span&gt;)

@mcp.tool
def hello(name: str) -&amp;gt; str:
    &lt;span class=<span class="hljs-string">&quot;hljs-built_in&quot;</span>&gt;<span class="hljs-built_in">return</span>&lt;/span&gt; f&lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;Hello, {name}!&amp;quot;&lt;/span&gt;

async def main():
    &lt;span class=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;# Use run_async() <span class="hljs-keyword">in</span> async contexts&lt;/span&gt;
    await mcp.run_async(transport=&lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;http&amp;quot;&lt;/span&gt;, port=8000)

&lt;span class=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">if</span>&lt;/span&gt; __name__ == &lt;span class=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;:
    asyncio.run(main())

Add unit tests

To test your MCP server, create a pytest unit test file in tests/test_server.py that:

  • Instanciates a Client from our MCP. Clients are asynchronous, so we need to use asyncio.run to run the client.

  • To use the client we must enter a client context with async with client:

Run unit tests by running pytest.

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">import</span>&lt;/span&gt; pytest
&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">import</span>&lt;/span&gt; asyncio
&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">from</span>&lt;/span&gt; fastmcp &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">import</span>&lt;/span&gt; Client
&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">from</span>&lt;/span&gt; server &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">import</span>&lt;/span&gt; mcp

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-meta&quot;</span>&gt;@pytest.mark.asyncio&lt;/span&gt;
&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">async</span>&lt;/span&gt; &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">def</span>&lt;/span&gt; &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;test_read_document&lt;/span&gt;():
    client = Client(mcp)
    &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">async</span>&lt;/span&gt; &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">with</span>&lt;/span&gt; client:
        result = &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">await</span>&lt;/span&gt; client.call_tool(&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;read_document&amp;quot;&lt;/span&gt;, {&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;doc_id&amp;quot;&lt;/span&gt;: &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;<span class="hljs-number">123</span>&amp;quot;&lt;/span&gt;})
        &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">assert</span>&lt;/span&gt; &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;Content of document <span class="hljs-number">123</span>&amp;quot;&lt;/span&gt; &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">in</span>&lt;/span&gt; result

Add explicit error feedback

Handle errors by returning JSON-RCP error codes:

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-meta&quot;</span>&gt;@mcp.tool&lt;/span&gt;
&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">def</span>&lt;/span&gt; &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;update_document&lt;/span&gt;(&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-params&quot;</span>&gt;
    doc_id: &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-built_in&quot;</span>&gt;<span class="hljs-built_in">str</span>&lt;/span&gt; = Field(&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-params&quot;</span>&gt;description=&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;ID of the document to update&amp;quot;&lt;/span&gt;&lt;/span&gt;),
    content: &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-built_in&quot;</span>&gt;<span class="hljs-built_in">str</span>&lt;/span&gt; = Field(&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-params&quot;</span>&gt;description=&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;New content <span class="hljs-keyword">for</span> the document&amp;quot;&lt;/span&gt;&lt;/span&gt;)
&lt;/span&gt;):
    &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">try</span>&lt;/span&gt;:
        &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment"># Implementation here&lt;/span&gt;</span>
        &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">return</span>&lt;/span&gt; {&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;success&amp;quot;&lt;/span&gt;: &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-literal&quot;</span>&gt;<span class="hljs-literal">True</span>&lt;/span&gt;, &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;message&amp;quot;&lt;/span&gt;: &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;f&amp;quot;Document &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-subst&quot;</span>&gt;{doc_id}&lt;/span&gt; updated successfully&amp;quot;&lt;/span&gt;}
    &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">except</span>&lt;/span&gt; KeyError:
        &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment"># Return a structured error response&lt;/span&gt;</span>
        &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">return</span>&lt;/span&gt; {
            &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;error&amp;quot;&lt;/span&gt;: {
                &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;code&amp;quot;&lt;/span&gt;: -&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-number&quot;</span>&gt;<span class="hljs-number">32003</span>&lt;/span&gt;,
                &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;message&amp;quot;&lt;/span&gt;: &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;f&amp;quot;Document <span class="hljs-keyword">with</span> ID &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-subst&quot;</span>&gt;{doc_id}&lt;/span&gt; <span class="hljs-keyword">not</span> found&amp;quot;&lt;/span&gt;,
                &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;data&amp;quot;&lt;/span&gt;: {&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;requested_id&amp;quot;&lt;/span&gt;: doc_id}
            }
        }

Package your Python MCP Server

To make your MCP server easily shareable, you can package it as a Python package:

  1. Create an __init__.py module file:
  •           &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment"># src/my_custom_mcp/__init__.py&lt;/span&gt;</span>
              &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">from</span>&lt;/span&gt; .server &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">import</span>&lt;/span&gt; mcp
  • Define your package metadata in pyproject.toml

  • Build and publish:

  •           pip install build
              python -m build
              &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment"># Use Twine, a utility to publish to PyPI&lt;/span&gt;</span>
              pip install twine
              &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment"># Publish to PyPI (this will ask for a PyPI API token)&lt;/span&gt;</span>
              twine upload dist/*
  • Users can then integrate your package with their IDE:

    claude_desktop_config.json
    {
      &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;mcpServers&amp;quot;&lt;/span&gt;: {
        &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;my_custom_mcp&amp;quot;&lt;/span&gt;: {
          &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;command&amp;quot;&lt;/span&gt;: &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;uvx&amp;quot;&lt;/span&gt;,
          &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;args&amp;quot;&lt;/span&gt;: [        
            &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;server&amp;quot;&lt;/span&gt;
          ]
        }
      }
    }

Add Health Checks

To monitor your MCP server, you can add health endpoints through FastMCP custom routes:

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">from</span>&lt;/span&gt; starlette.responses &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">import</span>&lt;/span&gt; JSONResponse

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-meta&quot;</span>&gt;@mcp.custom_route(&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-params&quot;</span>&gt;&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;/health&amp;quot;&lt;/span&gt;, methods=[&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;GET&amp;quot;&lt;/span&gt;]&lt;/span&gt;)&lt;/span&gt;
&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">async</span>&lt;/span&gt; &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">def</span>&lt;/span&gt; &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;health_check&lt;/span&gt;(&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-params&quot;</span>&gt;request&lt;/span&gt;):
    &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">return</span>&lt;/span&gt; JSONResponse({&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;status&amp;quot;&lt;/span&gt;: &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;healthy&amp;quot;&lt;/span&gt;, &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;service&amp;quot;&lt;/span&gt;: &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;mcp-server&amp;quot;&lt;/span&gt;})

This serves the health endpoint http://localhost:8000/health and can be used to restart your server in case of failure.

Add auhentication

If your MCP server is available remotely, you must add authentication. This can be done very easily with FastMCP, which supports OAuth, Bearer tokens and JWT.

To implement OAuth 2.1 we’re gonna use the RemoteAuthProvider to authenticate to Auth0. This will:

  • Add well known endpoints /{ISSUER}/.well-known/jwks.json

  • Use proper CORS configuration based on OAuth provider

Note: I see that tadata made the fastapi_mcp library to implement those changes.
Please keep relying on fastmcp library only, following the framework standards.

A minimal implementation looks like this:

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">from</span>&lt;/span&gt; fastmcp &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">import</span>&lt;/span&gt; FastMCP
&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">from</span>&lt;/span&gt; fastmcp.server.auth &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">import</span>&lt;/span&gt; RemoteAuthProvider
&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">from</span>&lt;/span&gt; fastmcp.server.auth.providers.jwt &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">import</span>&lt;/span&gt; JWTVerifier
&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">from</span>&lt;/span&gt; pydantic &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">import</span>&lt;/span&gt; AnyHttpUrl
&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">import</span>&lt;/span&gt; os

AUTH0_DOMAIN = os.getenv(&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;AUTH0_DOMAIN&amp;quot;&lt;/span&gt;, &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;your-tenant.us.auth0.com&amp;quot;&lt;/span&gt;)
AUTH0_AUDIENCE = os.getenv(&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;AUTH0_AUDIENCE&amp;quot;&lt;/span&gt;, &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;https://your-api-identifier&amp;quot;&lt;/span&gt;)
RESOURCE_SERVER_URL = os.getenv(&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;RESOURCE_SERVER_URL&amp;quot;&lt;/span&gt;, &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;https://your-domain.example.com/mcp&amp;quot;&lt;/span&gt;)

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment"># Configure JWT verification&lt;/span&gt;</span>
token_verifier = JWTVerifier(
    jwks_uri=&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;f&amp;quot;https://&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-subst&quot;</span>&gt;{AUTH0_DOMAIN}&lt;/span&gt;/.well-known/jwks.json&amp;quot;&lt;/span&gt;,
    issuer=&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;f&amp;quot;https://&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-subst&quot;</span>&gt;{AUTH0_DOMAIN}&lt;/span&gt;/&amp;quot;&lt;/span&gt;,
    audience=AUTH0_AUDIENCE
)

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment"># Wire OAuth to MCP&lt;/span&gt;</span>
auth = RemoteAuthProvider(
    token_verifier=token_verifier,
    authorization_servers=[AnyHttpUrl(&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;f&amp;quot;https://&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-subst&quot;</span>&gt;{AUTH0_DOMAIN}&lt;/span&gt;/&amp;quot;&lt;/span&gt;)],
    resource_server_url=RESOURCE_SERVER_URL
)

mcp = FastMCP(name=&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;Protected MCP Server&amp;quot;&lt;/span&gt;, auth=auth)

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-meta&quot;</span>&gt;@mcp.tool&lt;/span&gt;
&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">def</span>&lt;/span&gt; &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-title function_&quot;</span>&gt;hello&lt;/span&gt;(&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-params&quot;</span>&gt;name: &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-built_in&quot;</span>&gt;<span class="hljs-built_in">str</span>&lt;/span&gt;&lt;/span&gt;) -&amp;gt; &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-built_in&quot;</span>&gt;<span class="hljs-built_in">str</span>&lt;/span&gt;:
    &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">return</span>&lt;/span&gt; &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;f&amp;quot;Hello, &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-subst&quot;</span>&gt;{name}&lt;/span&gt;!&amp;quot;&lt;/span&gt;

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">if</span>&lt;/span&gt; __name__ == &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;:
    mcp.run(transport=&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;http&amp;quot;&lt;/span&gt;, host=&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;<span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>&amp;quot;&lt;/span&gt;, port=&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-number&quot;</span>&gt;<span class="hljs-number">8000</span>&lt;/span&gt;)

Configure an Auth0 application

  1. Create Application in the Auth0 dashboard.

  2. Register an API (your MCP server) with identifier matching AUTH0_AUDIENCE.

  3. Enable OIDC Dynamic Client Registration.

  4. Set Default Audience to your API identifier.

This ensures any MCP client can obtain tokens and authenticate correctly.

From Auth0 you can also enable Google Oauth.

  • In the Auth0 Dashboard, go to Authentication → Social → Google OAuth.

  • Under Applications, turn on the toggle for your application.

  • Save the changes.

Deploy your FastMCP server

Once Auth0 is configured:

  • Deploy the server to your hosting provider (e.g. Render, Fly.io, or your own infrastructure).
    For example, go to Render, create a new web app and use its GitHub integration by connecting your repo.

  • Set the environment variables (AUTH0_DOMAIN, AUTH0_AUDIENCE, RESOURCE_SERVER_URL).

Your OAuth-protected MCP server is now live. Any MCP client that connects will be redirected through the OAuth flow, retrieve an access token, and then be able to call tools and resources securely.

Turn existing agents into MCP servers with auto-mcp

If you've built an app using an agent framework (CrewAI, OpenAI, etc.), you can use Naphta auto-mcp to create an adapter for your existing agent classes.

Note: If you’re using CrewAI, they offer direct MCP integration with the MCPServerAdapter from the crewai-tools, see this article from Plaban Nayak.

Let automcp generate a run_mcp.py file to configure your agent:

uv add naptha-automcp
automcp init -f &amp;lt;framework&amp;gt;
&lt;span class=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;# expected agent flag: crewai, langgraph, llamaindex, openai, pydantic, mcp_agent&lt;/span&gt;

Update the generated run_mcp.py file to use your agent class:

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment"># Replace these imports with your actual agent classes&lt;/span&gt;</span>
&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">from</span>&lt;/span&gt; your_module &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">import</span>&lt;/span&gt; YourCrewClass

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment"># Define the input schema&lt;/span&gt;</span>
&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-keyword&quot;</span>&gt;<span class="hljs-keyword">class</span>&lt;/span&gt; &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-title class_&quot;</span>&gt;InputSchema&lt;/span&gt;(&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-title class_ inherited__&quot;</span>&gt;BaseModel&lt;/span&gt;):
    parameter1: &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-built_in&quot;</span>&gt;<span class="hljs-built_in">str</span>&lt;/span&gt;
    parameter2: &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-built_in&quot;</span>&gt;<span class="hljs-built_in">str</span>&lt;/span&gt;

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment"># Set your agent details&lt;/span&gt;</span>
name = &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;&amp;lt;YOUR_AGENT_NAME&amp;gt;&amp;quot;&lt;/span&gt;
description = &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-string&quot;</span>&gt;&amp;quot;&amp;lt;YOUR_AGENT_DESCRIPTION&amp;gt;&amp;quot;&lt;/span&gt;

&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;hljs-comment&quot;</span>&gt;<span class="hljs-comment"># For CrewAI projects&lt;/span&gt;</span>
mcp_crewai = create_crewai_adapter(
    orchestrator_instance=YourCrewClass().crew(),
    name=name,
    description=description,
    input_schema=InputSchema,
)

Install dependencies and run your MCP server:

automcp serve -t sse

Your MCP server is now up and provides one single MCP tool: Your agent.

Conclusion

We went through the Model Context Protocol (MPC) fundamentals and ecosystem.
It shifts the API-centric approach to an agent-interactive approach, in a goal-oriented paradigm.
This approach is less granular, but more goal-oriented, atomic tools being replaced with “recipe” meta-tools.

MCP servers can be written in any language, it’s just an implementation of a protocol. Just look for frameworks to handle the heavy-lifting, e.g: Cloudflare Workers templates for TypeScript, and FastMCP for Python.

Security and observability aren’t a nice-to-have, frameworks help you implement authentication, but you have to explicitly handle errors, and add traceability if your MCP is remote.

A few words about MCPs evolution

New tools appear, making curated MCP resources longer and longer.

  • Curated list of MCP tools, platforms & services: awesome-mcp-enterprise GitHub repo

  • Curated list of MCP servers: see awesome-mcp-servers GitHub repo

  • Security-centered MCP-Checklists

Best practices emerge and frameworks evolve, improving the overall quality of MCP servers and enforcing standards. Most MCP plugins are coded without frameworks, using tech stacks that are only “Claude” or “Windsurf” based, putting whole communities at risk with, see the article “We Uregntly Need Privilege Management in MCP: A Measurement of API Usage in MCP Ecosystems”.

I’d love to hear your go-to way for building MCP servers, and if they’re remote, how you share them and let your team use them?

Sources

  • Tori Seidenstein. APIs make bad MCPs. Start there anyway. tadata.com [online]. Available on: https://tadata.com/blog/apis-make-bad-mcps-start-there-anyway

  • Olha Diachuk. The DevOps view on MCP architecture. dysnic.com [online]. Available on:
    https://dysnix.com/blog/mcp-architecture

  • Araving Putrevu. How to host your MCP Server. Dev Shorts [online]. Exposing MCP Servers as APIs: Building Bridges Between AI Models and Applications. Available on: https://www.devshorts.in/p/how-to-host-your-mcp-server

  • Sanath Shetty. Exposing MCP Servers as APIs: Building Bridges Between AI Models and Applications. Medium [online]. Available on: https://medium.com/@sanathshetty444/exposing-mcp-servers-as-apis-building-bridges-between-ai-models-and-applications-104ff3803178

  • Maria Paktiti. The complete guide to MCP security: How to secure MCP servers & clients. workos.com [online]. Available on: https://workos.com/blog/mcp-security-risks-best-practices

  • Aravind Putrevu. How to implement OAuth for MCP Server. Dev Shorts [online]. Available on: https://www.devshorts.in/p/how-to-implement-oauth-for-mcp-server

Table of contents

  • MCP architecture & design
  • How to design my MCP? Not like APIs.
  • How do AI and MCP servers communicate?
  • Build & deploy MCP servers for Cloudflare Workers
  • Build Python MCP servers with FastMCP
  • Use Streamable HTTP
  • Use run\async() inside async functions
  • Add unit tests
  • Add explicit error feedback
  • Package your Python MCP Server
  • Add Health Checks
  • Add auhentication
  • Deploy your FastMCP server
  • Turn existing agents into MCP servers with auto-mcp
  • Conclusion
  • Sources
auto-mcpmcpFastMCPTypeScriptCloudflare WorkersSSEStreamable HTTPTwinehostingPyPIOAuth 2.1

Comments

Be the first to comment!

More Articles

MCP servers - Introduction & Guide

MCP servers - Introduction & Guide

In this article, I explain how MCPs (Model Context Protocols) work and how to integrate them into your IDEs to connect external tools to your agent, providing it with context and levers for action to reduce redundant tasks -> Your IDE becomes a true all-in-one tool.

MCP
LLM
JSON
Stdio
SSE
Streamable HTTP
+18 more
6min read
Last updated 9 months ago
Cloud Email Microservices: A Guide to Using AWS Lambda and Cloudflare Workers

Cloud Email Microservices: A Guide to Using AWS Lambda and Cloudflare Workers

Deploy an email microservice on Lambda and handle queues — invoke from Cloudflare Workers or any Node.js backend

AWS Lambda
AWS SQS
remix.runremix.run
Cloudflare Workers
CloudFormation
SAM
15min read
Last updated 10 months ago
Best Practices for an Optimized Contact Page Design

Best Practices for an Optimized Contact Page Design

Build a Contact Page That Connects — and Blocks Spam

4min read
Last updated 10 months ago
7 Ways to Stop Form Spam in Remix / Node.js

7 Ways to Stop Form Spam in Remix / Node.js

Flag bot activity, use built-in rate limit APIs, prevent bounced emails

RemixRemix
Cloudflare Worker
Node.jsNode.js
yup
5min read
Last updated 10 months ago
Send and Receive Custom Domain Emails for Free

Send and Receive Custom Domain Emails for Free

Set up free professional email addresses like you@yourdomain.com without hosting a mail server or paying for Google Workspace. This guide shows how to receive emails using Forward Email and send as your custom domain via Gmail — fast, reliable, and 100% free.

Forward Email
Gmail
Google App Passwords
DNS
SPF
TLS
+1 more
Last updated 10 months ago
CI/CD deep-dive: Deploy a scalable Multi-Environment React App to AWS S3 + CloudFront with GitHub Actions

CI/CD deep-dive: Deploy a scalable Multi-Environment React App to AWS S3 + CloudFront with GitHub Actions

Learn how to build and deploy a scalable multi-environment React + Vite app using Domain-Driven Design, GitHub Actions for CI/CD, and AWS S3 + CloudFront for fast, cost-effective static hosting. Includes environment-specific configs, branch-based workflows, and secure AWS deployment setup.

Vite
ReactReact
S3
CloudFront
TailwindTailwind
Shadcn
+1 more
10min read
Last updated 9 months ago
0

Offer

  • Contact
  • Skills

Resources

  • Blog
  • Newsletter
  • About

Legal

  • Privacy
  • Terms of service
  • Cookies policy
  • Update cookie consent
  • LinkedIn
  • GitHub

Gloweet © 2025. All rights reserved.

Smile, you're alive :)

2.0.0