init MCP server
This commit is contained in:
parent
fff03fa0d5
commit
8ea0eee299
11 changed files with 253 additions and 21 deletions
149
MCP_SERVER_GUIDE.md
Normal file
149
MCP_SERVER_GUIDE.md
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
# MCP Server for Code Snippets Database
|
||||
|
||||
## Overview
|
||||
|
||||
Building an MCP (Model Context Protocol) server to create a CRUD interface for managing code snippets. The goal is to enable Claude to save commands/code directly to your snippets database through natural conversation.
|
||||
|
||||
## What is an MCP Server?
|
||||
|
||||
An MCP server is an executable that communicates with clients (like Claude IDE) by exchanging JSON messages. At its core:
|
||||
|
||||
1. **Reads JSON** from stdin (messages from the client)
|
||||
2. **Writes JSON** to stdout (responses back)
|
||||
3. Implements the MCP protocol using JSON-RPC 2.0
|
||||
|
||||
Official docs: https://modelcontextprotocol.io/docs/concepts/architecture
|
||||
|
||||
## Transport Mechanisms
|
||||
|
||||
MCP servers support three transport types (choose one):
|
||||
|
||||
### 1. **Stdio** (Most Common)
|
||||
- Server runs as a subprocess
|
||||
- Client spawns the executable and pipes JSON over stdin/stdout
|
||||
- Best for: local integrations, Claude IDE plugins
|
||||
|
||||
### 2. **HTTP**
|
||||
- Server runs as an HTTP service (listens on a port)
|
||||
- Client sends JSON over HTTP requests
|
||||
- Best for: remote servers, shared services, multiple clients
|
||||
|
||||
### 3. **SSE** (Server-Sent Events)
|
||||
- Used alongside HTTP for server→client streaming
|
||||
- Less common
|
||||
|
||||
For this project, **stdio is the natural choice** — simple implementation and works seamlessly with Claude IDE.
|
||||
|
||||
## Protocol Flow
|
||||
|
||||
### 1. Initialize Handshake
|
||||
|
||||
**Client → Server:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "initialize",
|
||||
"params": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {},
|
||||
"clientInfo": {
|
||||
"name": "claude-code",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Server → Client Response:**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {
|
||||
"tools": {
|
||||
"listChanged": true
|
||||
},
|
||||
"resources": {
|
||||
"listChanged": true
|
||||
},
|
||||
"prompts": {
|
||||
"listChanged": true
|
||||
}
|
||||
},
|
||||
"serverInfo": {
|
||||
"name": "snippets-server",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Key points:
|
||||
- **JSON-RPC 2.0 format**: includes `jsonrpc`, `id`, and `method` fields
|
||||
- **Server declares capabilities**: tells client what it offers (tools, resources, prompts)
|
||||
- **Protocol version agreement**: both sides confirm compatibility
|
||||
- **Server info**: client learns about your server
|
||||
|
||||
### 2. Subsequent Calls
|
||||
|
||||
After initialization, the client can call methods like:
|
||||
- `tools/list` - get list of available tools
|
||||
- `tools/call` - execute a specific tool
|
||||
- `resources/list` - get available resources
|
||||
- `resources/read` - read a resource
|
||||
|
||||
## Implementation Plan for Snippets Server
|
||||
|
||||
Your MCP server should expose CRUD tools:
|
||||
|
||||
1. **`create_snippet`** - Save a new snippet (language, description, code)
|
||||
2. **`list_snippets`** - List all snippets or filter by language/tag
|
||||
3. **`get_snippet`** - Retrieve a specific snippet by ID
|
||||
4. **`update_snippet`** - Modify an existing snippet
|
||||
5. **`delete_snippet`** - Remove a snippet
|
||||
6. **`search_snippets`** - Search snippets by content or description
|
||||
|
||||
Each tool definition includes:
|
||||
- Name and description
|
||||
- Input schema (JSON Schema format)
|
||||
- Output schema
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Choose a language (Node.js/Bun, Python, Go, Rust recommended)
|
||||
2. Create a basic stdio-based server that:
|
||||
- Reads from stdin
|
||||
- Parses JSON-RPC messages
|
||||
- Implements initialize handshake
|
||||
- Implements tools/list and tools/call
|
||||
3. Connect to your snippets database/storage
|
||||
4. Test by running the server and sending JSON messages
|
||||
|
||||
## Useful Resources
|
||||
|
||||
- MCP Protocol Spec: https://modelcontextprotocol.io/
|
||||
- Official example servers: https://github.com/modelcontextprotocol/servers
|
||||
- JSON-RPC 2.0: https://www.jsonrpc.org/specification
|
||||
|
||||
## Use Case Flow
|
||||
|
||||
```
|
||||
User (Claude IDE):
|
||||
"Save this bash command to my snippets"
|
||||
↓
|
||||
Claude analyzes and calls:
|
||||
tools/call with tool="create_snippet"
|
||||
↓
|
||||
MCP Server receives JSON-RPC request
|
||||
↓
|
||||
Server creates snippet in database
|
||||
↓
|
||||
Returns success response to Claude
|
||||
↓
|
||||
User sees confirmation and snippet is saved
|
||||
```
|
||||
|
||||
This creates a seamless workflow where your snippet database grows through natural conversation!
|
||||
|
|
@ -12,14 +12,14 @@ Interactive elements are powered by [Gum](https://github.com/charmbracelet/gum).
|
|||
To run commands:
|
||||
|
||||
```shell
|
||||
$ bb -m cli-cms.main create
|
||||
$ bb -m cli.main create
|
||||
```
|
||||
|
||||
Or to play around and call individual functions using the REPL:
|
||||
|
||||
```shell
|
||||
$ rlwrap bb repl
|
||||
user=> (require '[cli-cms.create :as command])
|
||||
user=> (require '[cli.create :as command])
|
||||
user=> (def mock-snippet {:title "mock snippet from repl" :slug "mock" :tags '("mock") :markdown "# This is a mock"})
|
||||
#'user/mock-snippet
|
||||
user=> (command/create-snippet mock-snippet)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env bb
|
||||
|
||||
(ns snippets-cms
|
||||
(:require [cli-cms.main]))
|
||||
(:require [cli.main]))
|
||||
|
||||
(when (= *file* (System/getProperty "babashka.file"))
|
||||
(apply cli-cms.main/-main *command-line-args*))
|
||||
(apply cli.main/-main *command-line-args*))
|
||||
|
|
|
|||
7
snippets_mcp.bb
Executable file
7
snippets_mcp.bb
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env bb
|
||||
|
||||
(ns snippets-mcp
|
||||
(:require [cli.mcp]))
|
||||
|
||||
(when (= *file* (System/getProperty "babashka.file"))
|
||||
(apply cli.mcp/-main *command-line-args*))
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(ns cli-cms.config
|
||||
(ns cli.config
|
||||
(:require
|
||||
[babashka.fs :as fs]
|
||||
[clojure.string :as str]))
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
(ns cli-cms.create
|
||||
(ns cli.create
|
||||
(:require
|
||||
[com.travisshears.gum-utils :as utils]
|
||||
[clojure.string :as str]
|
||||
[cli-cms.config :refer [config]]
|
||||
[cli.config :refer [config]]
|
||||
[babashka.http-client :as http]
|
||||
[cheshire.core :as json]
|
||||
[babashka.process :refer [shell]]))
|
||||
|
||||
(defn create-snippet [{:keys [title slug markdown tags]}]
|
||||
(http/post (str (:backend-host config) "/api/snippet")
|
||||
{:headers {:content-type "application/json"}
|
||||
:body (json/encode {:title title :slug slug :markdown markdown :tags tags})}))
|
||||
{:headers {:content-type "application/json"}
|
||||
:body (json/encode {:title title :slug slug :markdown markdown :tags tags})}))
|
||||
|
||||
(defn run []
|
||||
(let [title (utils/prompt-for "title")
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
(ns cli-cms.delete
|
||||
(ns cli.delete
|
||||
(:require
|
||||
[com.travisshears.gum-utils :as utils]
|
||||
[cli-cms.config :refer [config]]
|
||||
[cli.config :refer [config]]
|
||||
[clojure.pprint :refer [pprint]]
|
||||
[babashka.http-client :as http]
|
||||
[cheshire.core :as json]))
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
(ns cli-cms.edit
|
||||
(ns cli.edit
|
||||
(:require
|
||||
[babashka.http-client :as http]
|
||||
[clojure.pprint :refer [pprint]]
|
||||
[cli-cms.config :refer [config]]
|
||||
[cli-cms.view :as view]
|
||||
[cli.config :refer [config]]
|
||||
[cli.view :as view]
|
||||
[cheshire.core :as json]
|
||||
[com.travisshears.gum-utils :as utils]))
|
||||
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
(ns cli-cms.main
|
||||
(ns cli.main
|
||||
(:require
|
||||
[cli-cms.create :as create]
|
||||
[cli-cms.edit :as edit]
|
||||
[cli-cms.delete :as delete]
|
||||
[cli.create :as create]
|
||||
[cli.edit :as edit]
|
||||
[cli.delete :as delete]
|
||||
[com.travisshears.gum-utils :as utils]
|
||||
[cli-cms.view :as view]))
|
||||
[cli.view :as view]))
|
||||
|
||||
(defn color-test []
|
||||
(doseq [color (range 0 255)]
|
||||
76
src/cli/mcp.clj
Normal file
76
src/cli/mcp.clj
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
(ns cli.mcp
|
||||
(:require
|
||||
[cli.view :as view]
|
||||
[cheshire.core :as json]))
|
||||
|
||||
;; Tool definitions
|
||||
(def available-tools
|
||||
[{:name "list_snippets"
|
||||
:description "List all available code snippets"
|
||||
:inputSchema {:type "object"
|
||||
:properties {}
|
||||
:required []}}])
|
||||
|
||||
;; Tool implementations
|
||||
(defn list-snippets-impl []
|
||||
(try
|
||||
(let [snippets (view/fetch-snippets)]
|
||||
{:success true
|
||||
:snippets (map #(select-keys % [:id :title :slug :tags]) snippets)})
|
||||
(catch Exception e
|
||||
{:success false
|
||||
:error (.getMessage e)})))
|
||||
|
||||
;; Handle tools/list request
|
||||
(defn handle-tools-list [id]
|
||||
{:jsonrpc "2.0"
|
||||
:id id
|
||||
:result {:tools available-tools}})
|
||||
|
||||
;; Handle tools/call request
|
||||
(defn handle-tools-call [id tool-name tool-input]
|
||||
{:jsonrpc "2.0"
|
||||
:id id
|
||||
:result (case tool-name
|
||||
"list_snippets" (list-snippets-impl)
|
||||
{:error (str "Unknown tool: " tool-name)})})
|
||||
|
||||
;; Handle initialize request
|
||||
(defn handle-initialize [id]
|
||||
{:jsonrpc "2.0"
|
||||
:id id
|
||||
:result {:protocolVersion "2024-11-05"
|
||||
:capabilities {:tools {:listChanged true}
|
||||
:resources {:listChanged true}
|
||||
:prompts {:listChanged true}}
|
||||
:serverInfo {:name "snippets-server"
|
||||
:version "1.0.0"}}})
|
||||
|
||||
;; Main request dispatcher
|
||||
(defn handle-request [request]
|
||||
(let [method (:method request)
|
||||
id (:id request)
|
||||
params (:params request)]
|
||||
(case method
|
||||
"initialize" (handle-initialize id)
|
||||
"tools/list" (handle-tools-list id)
|
||||
"tools/call" (handle-tools-call id (:name params) (:arguments params))
|
||||
{:jsonrpc "2.0"
|
||||
:id id
|
||||
:error {:code -32601
|
||||
:message (str "Method not found: " method)}})))
|
||||
|
||||
(defn -main [& args]
|
||||
;; Read JSON-RPC requests from stdin and process them
|
||||
(loop []
|
||||
(let [line (try (read-line) (catch Exception _))]
|
||||
(when line
|
||||
(try
|
||||
(let [request (json/parse-string line true)
|
||||
response (handle-request request)]
|
||||
(println (json/encode response)))
|
||||
(catch Exception e
|
||||
(println (json/encode {:jsonrpc "2.0"
|
||||
:error {:code -32700
|
||||
:message "Parse error"}}))))
|
||||
(recur)))))
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
(ns cli-cms.view
|
||||
(ns cli.view
|
||||
(:require
|
||||
[babashka.http-client :as http]
|
||||
[clojure.pprint :refer [pprint]]
|
||||
[cli-cms.config :refer [config]]
|
||||
[cli.config :refer [config]]
|
||||
[cheshire.core :as json]
|
||||
[clojure.string :as str]
|
||||
[com.travisshears.gum-utils :as utils]))
|
||||
Loading…
Add table
Add a link
Reference in a new issue