The work from my first step puts me in a positon to build the first tool - list instances. This MCP tool will

  • Get a list of instances
  • Obtain a summary set of data for each one
  • Return the response as a JSON document to the LLM / Agent

All that is need is to write the tool handler and spec. Lets get into that

The spec

This is the relativelty simple part. Decide on the tool name and the description. The description is rather important. It needs to tell the LLM / Agent what the tools does and how to use it in a concise manner.

Here’s what I’ve come up with

package outcomes

import (
 "github.com/mark3labs/mcp-go/mcp"
)

func ListInstancesSpec() mcp.Tool {
 return mcp.NewTool("list-instances",
  mcp.WithDescription(`
  Retrieve a list of instances in Neo4j Aura cloud.  An empty list means there are no instances or the user does not have access.`),
  mcp.WithTitleAnnotation("List instances"),
  mcp.WithReadOnlyHintAnnotation(true),
  mcp.WithIdempotentHintAnnotation(true),
  mcp.WithDestructiveHintAnnotation(false),
  mcp.WithOpenWorldHintAnnotation(true),
 )
}

The handler

Here’s where we do the work to obtain the information from the Aura API using the Aura Client ( Aura Client being a Go wrapper around the Aura API , avoiding having the manage that ). A list of instances is obtained using .Instances.List(). The result is then looped around to use the Id of each entry to read the details with .Instances.Get(Id) with all of this being added to an array which is converted to JSON and sent back to the calling LLM / Agent. Here’s what that looks like

package outcomes

import (
 "context"
 "encoding/json"
 "log/slog"

 "github.com/LackOfMorals/mcp4AuraAPI/internal/tools"
 "github.com/mark3labs/mcp-go/mcp"
)

// ListInstancesHandler returns a handler function for the list-instances tool
func ListInstancesHandler(deps *tools.ToolDependencies) func(context.Context, mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 return func(ctx context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  return handleListInstances(ctx, deps)
 }
}

// handleListInstances retrieves a list of instances using Aura Client
func handleListInstances(ctx context.Context, deps *tools.ToolDependencies) (*mcp.CallToolResult, error) {

 type instanceDetail struct {
  Id            string
  Name          string
  CloudProvider string
  Memory        string
  Type          string
  URL           string
  Status        string
 }

 type instanceList []instanceDetail

 if deps.AClient == nil {
  errMessage := "Aura API Client is not initialized"
  slog.Error(errMessage)
  return mcp.NewToolResultError(errMessage), nil
 }

 slog.Info("retrieving instances from aura api")

 // Get the list of instances
 instances, err := deps.AClient.Instances.List()
 if err != nil {
  errMessage := "Failed to list instances"
  slog.Error(errMessage)
 }

 // Create an empty list
 records := instanceList{}

 // Now get the details for each instance
 // And add them to records list
 for _, inst := range instances.Data {
  instanceInfo, err := deps.AClient.Instances.Get(string(inst.Id))
  if err != nil {
   errMessage := "Failed to get instance details for " + string(inst.Name)
   slog.Error(errMessage)
  }

  records = append(records, instanceDetail{
   Name:          instanceInfo.Data.Name,
   Id:            instanceInfo.Data.Id,
   Status:        instanceInfo.Data.Status,
   CloudProvider: instanceInfo.Data.CloudProvider,
   Memory:        instanceInfo.Data.Memory,
   Type:          instanceInfo.Data.Type,
   URL:           instanceInfo.Data.ConnectionUrl,
  })

 }

 if len(records) == 0 {
  slog.Warn("No instances retrieved.")
  return mcp.NewToolResultText("The tool executed successfully; however, no instance information was returned."), nil
 }

 jsonData, err := json.Marshal(records)
 if err != nil {
  slog.Error("failed to serialize to JSON", "error", err)
  return mcp.NewToolResultError(err.Error()), nil

 }
 return mcp.NewToolResultText(string(jsonData)), nil
}

If you want to try this out, then jump over to the GitHUb Repo

What’s next?

Writing a single tool didn’t take that much effort as the vast bulk of the work had already been done. I’d already written a wrapper for Aura API and basing the MCP server itself on the official MCP Server for Neo4j has saved a lot of time. Listing Instances is very much a quick win.

I could write a bunch more tools to achieve various outcomes - the Aura API offers lot of flexibility. What I’m more interested in is seeing how only having three tools could work

  • A tool to provide a summary of available outcomes
  • A tool that gets the details for an outcome
  • A tool that executes an outcome

Now I realise that I’ll still have to code each outcome - there’s no getting away from that.

Next step, go write those three tools and go from there.


<
Previous Post
A Go client for Aura API
>
Blog Archive
Archive of all previous blog posts