Wanna do an MCP? Try this :-)

MCP is a mechanism to expose things to AI agents.

Turns out it’s super easy to do, and it gives you some amazing leverage for tools! For example, I gave my kanban board a MCP server and now I can just say “move the note “whatever” to done” and it works ;-)

This post is to announce GitHub - ralsina/mcp: A crystal shard to create Model Context Protocol servers which is how I implemented that MCP as a spinoff shard.

It supports stdio MCP servers (nice way to expose a CLI tool to AI agents) and web MCP servers (with kemal at least)

Here is a FULL example of a MCP with a single tool, in stdio mode:

require "mcp"

# A simple tool that returns the answer to any question
class AnswerTool < MCP::AbstractTool
  @@tool_name = "get_answer"
  @@tool_description = "Returns 42 as the answer to any question you ask"
  @@tool_input_schema = {
    "type"       => "object",
    "properties" => {
      "question" => {
        "type"        => "string",
        "description" => "The question you want answered",
      },
    },
    "required" => ["question"],
  }.to_json

  def invoke(params : Hash(String, JSON::Any), env : HTTP::Server::Context? = nil)
    question = params["question"]?.try(&.as_s) || "unknown question"
    {
      "answer"   => 42,
      "question" => question,
    }
  end
end

# Start the stdio server - that's it! One line and you have a complete MCP server.
MCP::StdioHandler.start_server

Have fun and let me know if something is not ergnomic, I am trying to make this as boilerplate-free as possible.

6 Likes

When MCP servers started to become popular, I had Claude read the MCP specification and tried creating a Crystal library through vibe coding. I didn’t understand much then, and I still don’t now. But the code quickly grew beyond 10,000 lines, and I felt something wasn’t right, so I stopped. I think something smaller and more imperfect will eventually become widespread.

This implements most of the spec (didn’t implement resources because I don’t use them yet) and is 391 LOC (that includes the Kemal handler)

The spec is super simple.

It’s basically 4 entry points (initialize, list tools, invoke tool, resources) where the “complicated” one is invoke tool, which is jsonrpc, which is pretty simple.

2 Likes

I actually looked through the codebase and confirmed that the implementation is quite simple.
I still have some vague concerns about the future of MCP, but that’s another story.

Using this thread to also introduce another implementation of MCP protocol mcp.cr

1 Like

Really nice to see new members with real and interesting projects in Crystal.

The ToCry project is really nice, @ralsina!

1 Like

I have added a (fairly untested) implementation for resources and prompts so this now implements the whole MCP protocol.

1 Like

tried my hand at writing bindings a few months ago GitHub - nobodywasishere/mcprotocol: Crystal object bindings and serialization for the Model Context Protocol

Tried both shards, could not make them even build, decided to reimplement.

If you don’t mind, what was the issue you ran into?

I think in the case of your shard it was mostly that there are no docs or examples.

1 Like

so with the other one what was the issue you ran into?

I may use your shard when I rewrite my MCP server from Python to Crystal. It may happen soon.

1 Like

IIRC I got in a mess when specifying return types?

That was probably 99% my fault but was why I decided to go with “just return something that serializes to JSON”

1 Like