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:
|
To run commands:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ bb -m cli-cms.main create
|
$ bb -m cli.main create
|
||||||
```
|
```
|
||||||
|
|
||||||
Or to play around and call individual functions using the REPL:
|
Or to play around and call individual functions using the REPL:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ rlwrap bb repl
|
$ 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=> (def mock-snippet {:title "mock snippet from repl" :slug "mock" :tags '("mock") :markdown "# This is a mock"})
|
||||||
#'user/mock-snippet
|
#'user/mock-snippet
|
||||||
user=> (command/create-snippet mock-snippet)
|
user=> (command/create-snippet mock-snippet)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env bb
|
#!/usr/bin/env bb
|
||||||
|
|
||||||
(ns snippets-cms
|
(ns snippets-cms
|
||||||
(:require [cli-cms.main]))
|
(:require [cli.main]))
|
||||||
|
|
||||||
(when (= *file* (System/getProperty "babashka.file"))
|
(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
|
(:require
|
||||||
[babashka.fs :as fs]
|
[babashka.fs :as fs]
|
||||||
[clojure.string :as str]))
|
[clojure.string :as str]))
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
(ns cli-cms.create
|
(ns cli.create
|
||||||
(:require
|
(:require
|
||||||
[com.travisshears.gum-utils :as utils]
|
[com.travisshears.gum-utils :as utils]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[cli-cms.config :refer [config]]
|
[cli.config :refer [config]]
|
||||||
[babashka.http-client :as http]
|
[babashka.http-client :as http]
|
||||||
[cheshire.core :as json]
|
[cheshire.core :as json]
|
||||||
[babashka.process :refer [shell]]))
|
[babashka.process :refer [shell]]))
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
(ns cli-cms.delete
|
(ns cli.delete
|
||||||
(:require
|
(:require
|
||||||
[com.travisshears.gum-utils :as utils]
|
[com.travisshears.gum-utils :as utils]
|
||||||
[cli-cms.config :refer [config]]
|
[cli.config :refer [config]]
|
||||||
[clojure.pprint :refer [pprint]]
|
[clojure.pprint :refer [pprint]]
|
||||||
[babashka.http-client :as http]
|
[babashka.http-client :as http]
|
||||||
[cheshire.core :as json]))
|
[cheshire.core :as json]))
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
(ns cli-cms.edit
|
(ns cli.edit
|
||||||
(:require
|
(:require
|
||||||
[babashka.http-client :as http]
|
[babashka.http-client :as http]
|
||||||
[clojure.pprint :refer [pprint]]
|
[clojure.pprint :refer [pprint]]
|
||||||
[cli-cms.config :refer [config]]
|
[cli.config :refer [config]]
|
||||||
[cli-cms.view :as view]
|
[cli.view :as view]
|
||||||
[cheshire.core :as json]
|
[cheshire.core :as json]
|
||||||
[com.travisshears.gum-utils :as utils]))
|
[com.travisshears.gum-utils :as utils]))
|
||||||
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
(ns cli-cms.main
|
(ns cli.main
|
||||||
(:require
|
(:require
|
||||||
[cli-cms.create :as create]
|
[cli.create :as create]
|
||||||
[cli-cms.edit :as edit]
|
[cli.edit :as edit]
|
||||||
[cli-cms.delete :as delete]
|
[cli.delete :as delete]
|
||||||
[com.travisshears.gum-utils :as utils]
|
[com.travisshears.gum-utils :as utils]
|
||||||
[cli-cms.view :as view]))
|
[cli.view :as view]))
|
||||||
|
|
||||||
(defn color-test []
|
(defn color-test []
|
||||||
(doseq [color (range 0 255)]
|
(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
|
(:require
|
||||||
[babashka.http-client :as http]
|
[babashka.http-client :as http]
|
||||||
[clojure.pprint :refer [pprint]]
|
[clojure.pprint :refer [pprint]]
|
||||||
[cli-cms.config :refer [config]]
|
[cli.config :refer [config]]
|
||||||
[cheshire.core :as json]
|
[cheshire.core :as json]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[com.travisshears.gum-utils :as utils]))
|
[com.travisshears.gum-utils :as utils]))
|
||||||
Loading…
Add table
Add a link
Reference in a new issue