I’ve been making use of Cursor a lot recently (an AI code editor) and wanted to know how useful MCP plugins could be. While there is a massive set of community and reference servers out there, I wanted to start off by looking at the protocol a little bit and writing my own server.
I’m late to this party so finding an idea for a server I might actually use that doesn’t already exist isn’t so easy. I’ve ended up writing a Perl MCP server that is designed to help Cursor debug and generally write better Perl code. It might be there’s not many people regularly writing Perl who would make an MCP server in their spare time.
You might already be asking yourself, how useful could this possibly be? I would agree—it’s not clear this has any value, but working that out is half the fun.
Server Implementation
To keep things easy I’m using go because there’s a reasonable looking mcp-golang tool to help with the boilerplate. This probably won’t scale past my first tool idea without shelling out to Perl from within Go, but that’s a problem for later.
The tool I’m going to create is fetch-perl-pod
. This will take a Perl module and fetch the POD
(Plain Old Documentation, the standard documentation format for Perl modules)
from metacpan, where many Perl devs host their open source modules.
Luckily the metacpan API is easy to use so we can implement this in a short Go file.
package main
import (
"context"
"errors"
"fmt"
"io"
"net/http"
mcp_golang "github.com/metoro-io/mcp-golang"
"github.com/metoro-io/mcp-golang/transport/stdio"
)
const (
TOOL_NAME = "fetch-perl-pod"
TOOL_DESCRIPTION = "Given a Perl module name, fetch the POD from metacpan"
CPAN_URL = "https://fastapi.metacpan.org/pod/"
)
type args struct {
Module string `json:"module" jsonschema:"required,description=The module to fetch POD for eg Const::Fast"`
}
func main() {
done := make(chan struct{})
server := mcp_golang.NewServer(stdio.NewStdioServerTransport())
if err := server.RegisterTool(TOOL_NAME, TOOL_DESCRIPTION, handler); err != nil {
panic(err)
}
if err := server.Serve(); err != nil {
panic(err)
}
<-done
}
func handler(ctx context.Context, arguments *args) (*mcp_golang.ToolResponse, error) {
if arguments == nil || arguments.Module == "" {
return nil, errors.New("module is required")
}
pod, err := getCpanPod(arguments.Module)
if err != nil {
return nil, err
}
return mcp_golang.NewToolResponse(
mcp_golang.NewTextContent(pod),
), nil
}
func getCpanPod(module string) (string, error) {
url := fmt.Sprintf("%s%s", CPAN_URL, module)
resp, err := http.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close() //nolint:errcheck
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("failed to fetch POD from metacpan: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(body), nil
}
Adding to Cursor
Build the server with:
go build -o mcp-perl main.go
Open mcp.json
for Cursor and add
{
"mcpServers": {
"fetch-perl-pod-from-metacpan": {
"command": "/path/to/mcp-perl",
"args": []
}
}
}
Make sure the MCP tool is enabled in the settings and you should be ready to use it.
You can ask the model to use the new tool and Cursor will ask for your permission.
Once you let it run, the model will have the output in its context—hopefully for more improved debugging and code writing skills.
How useful is it
This is the part I’m most interested in: are all these MCP servers actually useful for day-to-day development, or are they a distraction?
It certainly feels like forcing the model to go get some context on a tool you’re using can be helpful—especially when it makes bad assumptions. But so far, most of the Perl modules I use, it already has context on.
I expect this type of tool will be more useful if you expose private documentation which won’t be in any LLM training data.
In any case, I plan to try this out for a few days and see if it’s worth extending in any way.
The code here is all available, slightly better formatted, at gitlab.com/danwainwright/mcp-perl
If you try this out or have ideas for other useful MCP tools, let me know!