---
contentId: 4ff51ee2-926d-4924-86f5-97cd9e378fa7
title: "A Model Context Protocol (MCP) Server for My Website"
description: "Exploring the potential of the Model Context Protocol (MCP) by integrating it into my workflow and optimizing my website's backend operations."
date: 2025-05-10
---

**Large Language Models** (LLMs) are ubiquitous these days. The pace of advancements is staggering, and it's challenging to keep up with the constant updates, it's absolutely insane!

Amidst the overwhelming information, I found the concept of an **agent**, especially with the [**Model Context Protocol**](https://modelcontextprotocol.io/introduction) (MCP), particularly intriguing and promising. So, I decided to experiment with it and see how it could enhance my workflow.

> [!NOTE]
> Prefer watching a video? Check out the video version of this article on [YouTube](https://www.youtube.com/watch?v=NnLIYresumQ).

## The challenge with identifiers

Currently, my website consists of two key components:

- The **frontend**, built with [**VitePress**](https://vitepress.dev), where each page is a Markdown file.
- The **backend**, a [**Laravel**](https://laravel.com) application that adds additional features and functionality.

To ensure the correct content is served and stored for each frontend page, I need to share an identifier between these components. Each Markdown file has a unique identifier in its frontmatter.

```md [src/posts/a-simple-markdown-file.md]
---
id: 1
title:
description:
---
```

This identifier fetches the corresponding content from the backend when loading the page. For everything to work seamlessly, the backend must also store this identifier in the database. Without it, maintaining a functional relational database is impossible, as my database includes a `posts` table.

To obtain these identifiers, the backend routinely fetches a JSON file generated at build time by **VitePress**. This file contains all the Markdown files and their frontmatter. This approach works well, but there's a catch.

When writing a new Markdown file, I must access the backend administration page, sort the `posts` table by the `id` column, and find the last used identifier. Then, I write this identifier, incremented by one, into the file I'm working on. It's a tedious task that adds no real value and consumes time, a process I thoroughly dislike.

Simultaneously, I frequently leverage **LLMs** to enhance my articles' technical content, including **SEO**, grammar, and spelling. This led me to wonder: **Can I integrate an agent within the current LLM process to ensure that the identifier is always correct?**

_Short answer: yes!_

## Implementing a new backend route

MCP servers aren't mystical; they provide a means for LLMs to interact with data sources. Hence, I need to create a backend API route to return the next post identifier. This route will be accessed by the MCP server via the tools capability.

Creating this API route in my **Laravel** application and setting up an invokable controller is fairly straightforward:

```php [routes/api.php]
use App\Http\Controllers\NextPostIdController;
use Illuminate\Support\Facades\Route;

Route::get('posts/next-id', NextPostIdController::class)
    ->name('posts.next-id');
```

```php [app/Http/Controllers/NextPostIdController.php]
<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\JsonResponse;

class NextPostIdController extends Controller
{
    /**
     * Handle the incoming request.
     */
    public function __invoke(): JsonResponse
    {
        $nextPostId = Post::max('id') + 1;

        return response()->json(['next_post_id' => $nextPostId]);
    }
}
```

This setup, although simple, is crucial for an effective MCP server.

## Configuring the MCP server

For the MCP server, I'm utilizing the official [**TypeScript SDK**](https://github.com/modelcontextprotocol/typescript-sdk). It's straightforward and highly efficient.

```ts [src/index.ts]
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import {
  CallToolRequestSchema,
  ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js'
import { ofetch } from 'ofetch'
import { z } from 'zod'
import { zodToJsonSchema } from 'zod-to-json-schema'

const server = new Server(
  {
    name: 'mcp.soubiran.dev',
    version: '0.0.0'
  },
  {
    capabilities: {
      tools: {}
    }
  }
)

const GetNextId = z.object({})

server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: 'next_post_id',
        description: 'Get the next post ID for a post on soubiran.dev',
        inputSchema: zodToJsonSchema(GetNextId),
      },
    ]
  }
})

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === 'next_post_id') {
    try {
      const data = await ofetch<{ next_post_id: number }>('http://localhost:8000/api/posts/next-id')

      return {
        content: [{
          type: 'text',
          text: data.next_post_id
        }]
      }
    }
    catch (error) {
      throw new Error('Error fetching next post ID')
    }
  }

  throw new Error('Unknown tool')
})

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

This code establishes a STDIO MCP server with two key request handlers:

1. The `ListToolsRequest` handler returns the available tools to the agent. Currently, there is one tool: `next_post_id`.
2. The `CallToolRequest` handler is activated when the agent uses the tool. It calls the backend API route to retrieve the next post ID.

The tool call process is simple; it fetches the next post ID from the backend API route I've defined, using the `ofetch` library for ease. The post ID is then returned as a number.

Consequently, any agent invoking this tool will receive the next post ID as a response within its context. This allows me to draft Markdown files without worrying about post IDs, the agent handles it perfectly!

> [!NOTE]
> For the complete code, watch the [YouTube video](https://www.youtube.com/watch?v=NnLIYresumQ).

## Does it work?

Yes, it works seamlessly! See it in action:

<figure>
  <video autoplay loop muted playsinline>
    <source src="https://images.soubiran.dev/posts/a-model-context-protocol-mcp-server-for-my-website/working-mcp-soubiran-dev.mp4" type="video/mp4">
  </video>
  <figcaption>Requesting the agent to enhance a file and insert the correct ID automatically using the next post ID from the MCP server.</figcaption>
</figure>

With this **MCP server** operational, I can now interact with my Markdown files while supplying context to the agent. This allows me to focus on content rather than technical details. My ultimate aim is to hasten my writing process, making it more efficient and enjoyable, and this software is a step towards that goal.

I'm thoroughly impressed by the MCP SDK's user-friendliness and integration with **VS Code**. The future of agents is promising, and I highly recommend experimenting with them!
