Friday, 25 April 2025

Model Context Protocol Server

 What Is a Model Context Protocol Server?

An MCP server acts as a bridge between AI models and external functionalities. It exposes three primary types of capabilities:​


Resources: Data entities like files, documents, or database entries that the AI can read or query.


Tools: Functions or APIs that the AI can invoke to perform actions, such as sending emails or querying a database.

(expose REST API to AI, didnt expose to user)


Prompts: Predefined templates or instructions that guide the AI's behavior or responses.​

Wikipedia, la enciclopedia libre


---------------purpose for AI to access users' databse using API with Model Context Protocol----------


https://github.com/elastic/mcp-server-elasticsearch


for MCP elastic search 

  • An Elasticsearch instance
  • Elasticsearch authentication credentials (API key or username/password)
  • MCP Client (e.g. Claude Desktop)

  1. Configure MCP Client

    • Open your MCP Client. See the list of MCP Clients, here we are configuring Claude Desktop.
    • Go to Settings > Developer > MCP Servers
    • Click Edit Config and add a new MCP Server with the following configuration:
    {
      "mcpServers": {
        "elasticsearch-mcp-server": {
          "command": "npx",
          "args": [
            "-y",
            "@elastic/mcp-server-elasticsearch"
          ],
          "env": {
            "ES_URL": "your-elasticsearch-url",
            "ES_API_KEY": "your-api-key"
          }
        }
      }
    }

--------------------------------------------------------------------------

How does it really work in backend?

To talk to the Elastic MCP server over STDIO you must speak the MCP “JSON-RPC 2.0” protocol—not just dump raw ES DSL. Under the hood, Claude (or any MCP client) does exactly this: it spawns the server as a child process, writes newline-delimited JSON-RPC requests to its stdin, then reads newline-delimited JSON-RPC responses from its stdout


-----------------------------------

 n your CallMCP helper you need to:

Wrap your ES query in an MCP JSON-RPC call, e.g.:

jsonc
Copy
Edit
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "search",            // one of the tools the server exposes
  "params": {
    "index": "your-index-name",
    "body": { /* your ES DSL here */ }
  }
}
Serialize that object, append a \n, write it to the server’s stdin.

Read exactly one line from its stdout, parse it as JSON, check for an error field or extract the result.

Return the result to your Go code.

Below is an updated CallMCP (in mcp.go) showing exactly that:

go
Copy
Edit
package main

import (
  "bufio"
  "encoding/json"
  "fmt"
  "io"
  "os/exec"
)

type mcpRequest struct {
  Jsonrpc string      `json:"jsonrpc"`
  ID      int         `json:"id"`
  Method  string      `json:"method"`
  Params  interface{} `json:"params"`
}

type mcpResponse struct {
  Jsonrpc string           `json:"jsonrpc"`
  ID      int              `json:"id"`
  Result  json.RawMessage  `json:"result"`
  Error   *struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
  } `json:"error,omitempty"`
}

// CallMCP runs the MCP server on stdin/stdout and performs one JSON-RPC call.
func CallMCP(method string, params interface{}) (json.RawMessage, error) {
  // 1) Launch the MCP server as a child process
  cmd := exec.Command("node", "mcp-server-elasticsearch/dist/index.js")
  cmd.Env = append(cmd.Env,
    "ES_URL=http://localhost:9200",
    "ES_USERNAME=elastic",
    "ES_PASSWORD=changeme",
  )

  stdin, err := cmd.StdinPipe()
  if err != nil {
    return nil, fmt.Errorf("stdin pipe: %w", err)
  }
  stdout, err := cmd.StdoutPipe()
  if err != nil {
    return nil, fmt.Errorf("stdout pipe: %w", err)
  }
  stderr, _ := cmd.StderrPipe()

  if err := cmd.Start(); err != nil {
    return nil, fmt.Errorf("start MCP server: %w", err)
  }

  // 2) Prepare JSON-RPC request
  reqObj := mcpRequest{
    Jsonrpc: "2.0",
    ID:      1,
    Method:  method,
    Params:  params,
  }
  reqBytes, err := json.Marshal(reqObj)
  if err != nil {
    return nil, fmt.Errorf("marshal request: %w", err)
  }

  // 3) Send the request (and newline delimiter)
  if _, err := stdin.Write(append(reqBytes, '\n')); err != nil {
    return nil, fmt.Errorf("write stdin: %w", err)
  }
  stdin.Close()

  // 4) Log any stderr for troubleshooting
  go func() {
    if b, _ := io.ReadAll(stderr); len(b) > 0 {
      fmt.Printf("MCP stderr: %s\n", string(b))
    }
  }()

  // 5) Read exactly one JSON-RPC response line
  reader := bufio.NewReader(stdout)
  line, err := reader.ReadBytes('\n')
  if err != nil {
    return nil, fmt.Errorf("read stdout: %w", err)
  }

  // 6) Parse the response
  var resp mcpResponse
  if err := json.Unmarshal(line, &resp); err != nil {
    return nil, fmt.Errorf("unmarshal response: %w", err)
  }
  if resp.Error != nil {
    return nil, fmt.Errorf("MCP error %d: %s", resp.Error.Code, resp.Error.Message)
  }

  // 7) Wait for the process to exit cleanly
  cmd.Wait()

  // 8) Return the raw JSON result (could be an array, object, etc.)
  return resp.Result, nil
}
method must be one of the tools the server exposes—per the README: list_indices, get_mappings, search or get_shards 
GitHub
.

params is a Go struct or map matching the tool’s expected parameters, e.g.:

go
Copy
Edit
params := map[string]interface{}{
  "index": "my-index",
  "body": map[string]interface{}{
    "query": map[string]interface{}{
      "match": map[string]interface{}{"title": "foo"},
    },
  },
}
resJSON, err := CallMCP("search", params)
This STDIO JSON-RPC approach is exactly how MCP clients like Claude Desktop communicate with your server locally 
Cline Documentation | Cline
. Once you have resJSON, you can unmarshal it into whatever Go type represents your documents and return it to your HTTP handler.



No comments:

Post a Comment