Share
Embed
Exploring RuboCop with MCP
RubyKaigi 2026 (https://rubykaigi.org/2026/presentations/koic.html)
Koichi ITO
March 24, 2026
Transcript
84 slides
-
&YQMPSJOH3VCP$PQXJUI.$1 ,PJDIJ*50&4. *OD 3VCZ,BJHJ "QSJM Possibilities Enabled by Streamable
HTTP )BLPEBUF$JUJ[FO)BMM -
ఆऀ *ODPNJOH "EWJTPS "EWJTPS 1FPQMF"TTPDJBUFEXJUI&4. *OD 4 Q FBLFS -5
!LPJD !GVHBLLCO !OTHD !NBJNVYY !LBTVNJQPO !XBJEPJ !IBNDBQ !N@QJYZ " UUFOEFF " UUFOEFF " UUFOEFF " UUFOEFF " UUFOEFF " UUFOEFF " UUFOEFF !4)(".&-*/,4 !IBSVHVDIJZVNB !KVOL " UUFOEFF " UUFOEFF 4 Q FBLFS " UUFOEFF " UUFOEFF " UUFOEFF !NON !ZVDBPIPVST !XBUBSP 5#" !LBLVUBOJ !BNBUTVEB " UUFOEFF 0 SHBOJ[FS " UUFOEFF !GLJOP 0 SHBOJ[FS "DPNNVOJUZ GPSCVJMEFST :PVBSF XFMDPNFIFSF -
"/FX6TFS w )VNBOT $-* &EJUPST*%&T w 1SPHSBNT -41 *OUFHSBUJPOT
w "HFOUT "DSPTT"MM </&8> Exploring the AI Era -
(VBSESBJMTGPS"*"HFOUT w 3VCP$PQTFSWFTBTBHVBSESBJMUIBU WFSJ fi FT"*HFOFSBUFEDPEFXJUIPVU IVNBOJOUFSWFOUJPO w &OTVSJOHOPEFQSFDBUFE"1*TBSFVTFE w
&OTVSJOHDPOGPSNBODFUPUIFFYQFDUFE TUZMF -
5PPM*OWPDBUJPOGSPN"*"HFOUT w #BTI $-* rubocopDPNNBOE w .$11SPUPDPMCBTFE.$1DBMMT w 4LJMMT/BUVSBMMBOHVBHF MFTT3VCZ,BJHJMJLF
w -413VCZ-41JOUFHSBUJPO w "OFXBQQSPBDIZFUUPDPNF🤔 IUUQTHJUIVCDPNBOUISPQJDTDMBVEFQMVHJOTP ffi DJBMQVMM -
5PPM*OWPDBUJPOGSPN"*"HFOUT w #BTI $-* rubocopDPNNBOE w .$1-BZJOHUIFTDB ff PMEJOH fi
STU w 4LJMMT/BUVSBMMBOHVBHF MFTT3VCZ,BJHJMJLF w -413VCZ-41JOUFHSBUJPO w "OFXBQQSPBDIZFUUPDPNF🤔 IUUQTHJUIVCDPNBOUISPQJDTDMBVEFQMVHJOTP ffi DJBMQVMM -
.$13VCZ4%,BOE.F w *OJUJBMMZTUBSUFECZBGFXFOHJOFFSTBU 4IPQJGZ*OD w +PJOFEFBSMZ XJUI3VCP$PQJONJOE w "UF㶆(ÖSBM !BUFTHPSBM
JOWJUFENFUP CFDPNFBDPNNJUUFS w 1VCMJTIFEBTUIFNDQHFN -
0G fi DJBM.$14%,T w -JDFOTFEVOEFS"QBDIF USBOTJUJPOJOHGSPN.*5 HPWFSOFECZUIF"HFOUJD"*'PVOEBUJPO -JOVY'PVOEBUJPO
w 1SPWJEFT4%,TGPSCVJMEJOHCPUI.$1 TFSWFSTBOEDMJFOUT w (PWFSOFEVOEFSUIF4%,5JFSJOH4ZTUFN -
$POGPSNBODF5FTU w 0 ffi DJBMUFTUTVJUFUIBU4%,TNVTUQBTT w -JLFSVCZTQFD CVUGPS.$1 w 3VCZ4%,TFSWFS
DMJFOU BDIJFWFE 5JFSSFRVJSFTPOCPUI w 5JFSSFRVJSFTW TP"1*TUBCJMJUZBOE RVBMJUZBSFQSJPSJUJ[FE -
4FSWFS4%, .PTUVTFSTXJMMVTFUIJT4%,GPSCVJMEJOH .$1TFSWFSTXJUIUIFTFNBJOGFBUVSFT w 5PPM%F fi OFUPPMTGPS.$1DMJFOUTUPDBMM w 3FTPVSDF&YQPTFSFTPVSDFTUPDMJFOUT w
1SPNQU1SPWJEFQSPNQUUFNQMBUFT It matters that Ruby remains an official MCP SDK, beyond just RuboCop -
.$15SBOTQPSUT w .$1IBTUXPDMJFOUTFSWFSUSBOTQPSUT TUEJPBOE4USFBNBCMF)551 w 'PS3VCP$PQBMPOF TUEJPJTTV ffi DJFOU w
'PSXFCBQQMJDBUJPOT FH 3BJMT3BDL 4USFBNBCMF)551CFDPNFTJNQPSUBOU w $IPPTFCBTFEPOMPDBMQSPDFTTPS)551 💡 -
4UEJP#BTJD.FDIBOJTN w $MJFOUTUBSUTTFSWFSWJBOpen3.popen3 JOUFSOBMMZTQBXO w $MJFOUTFOETSFRVFTUTWJB stdin#puts(json) w 4FSWFSSFBETSFRVFTUTWJB
stdin#gets w 4FSWFSXSJUFTSFTQPOTFTWJB stdout#puts(json) w $MJFOUSFBETSFTQPOTFTWJB stdout#gets IUUQTNPEFMDPOUFYUQSPUPDPMJPTQFDJ fi DBUJPOCBTJDUSBOTQPSUT MPPQ -
require 'mcp' server = MCP::Server.new( name: 'weather', version: '1.0.0', tools:
[GetForecast] ) transport = MCP::Server::Transports:: StdioTransport.new(server) transport.open class GetForecast < MCP::Tool description "Get forecast for a location" input_schema( properties: { latitude: {type: "number"}, longitude: {type: "number"} }, required: [ "latitude", "longitude" ] ) def self.call(latitude:, longitude:) forecast = fetch_forecast(latitude, longitude) MCP::Tool::Response.new([{ type: "text", text: forecast }]) end 4UEJP&YBNQMF.$14FSWFS IUUQTHJUIVCDPNNPEFMDPOUFYUQSPUPDPMRVJDLTUBSUSFTPVSDFTCMPCNBJOXFBUIFSTFSWFSSVCZXFBUIFSSC weather.rb 'FUDIFTXFBUIFSEBUBGSPN BOFYUFSOBM"1* 5PPMBSHT EF fi OFEBT +40/4DIFNB GPS--. 5IF.$1TFSWFS3VCZ fi MF TQBXOFECZ BO.$1DMJFOU UPDPOOFDUXJUI UIFQBSFOU.$1DMJFOUQSPDFTTWJBTUEJP -
class StdioTransport < Transport def open @open = true @session
= ServerSession.new(server: @server, transport: self) while @open && (line = $stdin.gets) response = @session.handle_json(line.strip) send_response(response) if response end rescue Interrupt warn("\nExiting..."); exit(STATUS_INTERRUPTED) end def send_response(message) json_message = message.is_a?(String) ? message : JSON.generate(message) $stdout.puts(json_message) $stdout.flush end *OTJEF4UEJP5SBOTQPSUPQFO IUUQTHJUIVCDPNNPEFMDPOUFYUQSPUPDPMSVCZTELCMPCWMJCNDQTFSWFSUSBOTQPSUTTUEJP@USBOTQPSUSC -PPQTPWFS$stdin.gets DBMMThandle_json(json) BOETFOETUIFSFTQPOTF 8SJUFUIF+40/SFTQPOTFUP TUEPVUGPSUIF.$1DMJFOU -
4USFBNBCMF)551#BTJD.FDIBOJTN w &OBCMFTCJEJSFDUJPOBMDPNNVOJDBUJPOPWFS )551CFUXFFODMJFOUBOETFSWFS w *NQSPWFTPOUIFQSFWJPVT)551 44& 4FSWFS4FOU&WFOUT USBOTQPSU w
6TFTBDVTUPNIFBEFS MCP-Session-Id UPFTUBCMJTITFTTJPOCBTFEDPNNVOJDBUJPO -
4USFBNBCMF)551 w *OJUJBMJ[BUJPOIBOETIBLF w 'JSTU TFOEB1045SFRVFTUUP JOJUJBMJ[FBOESFDFJWFUIFSFTQPOTF
JODMVEJOHUIFMCP-Session-Id BOETFSWFSDBQBCJMJUJFT w 5IFO TFOECBDL UIFMCP-Session-IdUPDPO fi SN UIFTFTTJPO *OJUJBMJ[BUJPO IUUQTNPEFMDPOUFYUQSPUPDPMJPTQFDJ fi DBUJPOCBTJDUSBOTQPSUT -
4USFBNBCMF)551 $MJFOU3FRVFTUT w 4FOEB1045SFRVFTUXJUI MCP-Session-Id FH UPPMTMJTU w
5IFTFSWFSSFTQPOETXJUI BO44&TUSFBNContent-Type: text/event-stream w 0SBTJOHMF+40/SFTQPOTF Content-Type: application/ json IUUQTNPEFMDPOUFYUQSPUPDPMJPTQFDJ fi DBUJPOCBTJDUSBOTQPSUT -
server = MCP::Server.new( name: 'my_server', title: 'Example Server Display Name',
version: '1.0.0', instructions: 'Use the tools of this server...', tools: [SomeTool, AnotherTool] ) transport = MCP::Server::Transports:: StreamableHTTPTransport.new(server) Rails.application.routes.draw do mount transport => '/mcp' end config/routes.rb 4USFBNBCMF)551&YBNQMF.$14FSWFS 4USFBNBCMF)5515SBOTQPSUBT B3BDLBQQMJDBUJPO 4UBUFGVM FH 3BDL Mechanism of MCP Ruby SDK 0.13+ -
class StreamableHTTPTransport < Transport # Rack app interface. This transport
can be mounted as a Rack app. def call(env) handle_request(Rack::Request.new(env)) end 3BJMT*OUFHSBUJPO *OUFHSBUF4USFBNBCMF)5515SBOTQPSUJOUPB3BDLBQQMJDBUJPOTVDIBT3BJMT Mechanism of MCP Ruby SDK 0.13+ -
class StreamableHTTPTransport < Transport def handle_request(request) case request.env["REQUEST_METHOD"] when "POST"
handle_post(request) when "GET" handle_get(request) when "DELETE" handle_delete(request) else method_not_allowed_response end end def handle_post(request) accept_error = validate_accept_header(request, REQUIRED_POST_ACCEPT_TYPES) return accept_error if accept_error body_string = request.body.read session_id = extract_session_id(request) *OTJEF4USFBNBCMF)5515SBOTQPSUIBOEMF@SFRVFTU 3PVUFTSFRVFTUTCZ)551NFUIPE 1045(&5%&-&5& &YUSBDUMCP-Session-IdBOE QSPDFTTFTUIFSFRVFTUBDDPSEJOHMZ def extract_session_id(request) request.env["HTTP_MCP_SESSION_ID"] end -
4FTTJPO.BOBHFNFOU w 4UEJPJTDMJFOUTFSWFS TPOPTFTTJPO NBOBHFNFOUOFFEFE 4UEJPJOUFSOBMMZDSFBUFTBTFTTJPOGPSDPOTJTUFODZ w 4USFBNBCMF)551JTODMJFOUTFSWFS
TPTFTTJPONBOBHFNFOUJTSFRVJSFE w 5IF4%,JNQMFNFOUTTFTTJPONBOBHFNFOU VTJOHMCP-Session-IdEF fi OFEJO UIF.$1TQFD -
4FTTJPO.BOBHFNFOUJO4USFBNBCMF)551 Client A ──┐ ┌── 1 Server Client B ──┤
Streamable HTTP │ ├── tools, prompts, resources Client C ──┘ │ └── handlers, capabilities │ └── 1 StreamableHTTPTransport @sessions = { A => { stream: io_a, session_a } B => { stream: io_b, session_b } C => { stream: io_c, session_c } } ┌──────────────┬──────────────┐ ▼ ▼ ▼ session_a session_b session_c @client=A @client=B @client=C @logging=error @logging=debug @logging=info @writer=io_a @writer=io_b @writer=io_c notify_*→io_a notify_*→io_b notify_*→io_c N sessions × M concurrent requests on Puma "TFTTJPOJTDSFBUFE GPSFBDIDMJFOU )PMETPOMZTIBSFEDPO fi H MCP-Session-IdJTUIFLFZPG 5SBOTQPSUTJOTUBODFWBSJBCMF DPOOFDUTDMJFOUBOETFSWFSWJB MCP-Session-IdFTUBCMJTIFE EVSJOHUIFJOJUJBMJ[FIBOETIBLF &BDITFTTJPO IPMETQFSDMJFOU DPO fi HVSBUJPO -
⚠1SPDFTTBOE5ISFBE w @sessionsJTIFMECZUIF 4USFBNBCMF)5515SBOTQPSUJOTUBODFQFSQSPDFTT w "TFTTJPOJTDSFBUFEPOFBDIDMJFOUT fi STUSFRVFTU w $SFBUJOHBOFX4USFBNBCMF)5515SBOTQPSU
QFSSFRVFTUDMFBST@sessions w 3FRVJSFT1VNB TJOHMFQSPDFTT NVMUJUISFBE UP LFFQMCP-Session-IdBDSPTTSFRVFTUT UISFBET -
4FSWFSUP$MJFOU.FTTBHFT w 4USFBNBCMF)551TVQQPSUTCJEJSFDUJPOBM DPNNVOJDBUJPO )5511045 44& w 4FSWFSVTFT44&UPTFOENFTTBHFTUP UIFDMJFOU
w 5XPUZQFT fi SFBOEGPSHFU -PHHJOH /PUJ fi DBUJPO BOESFTQPOTFBXBJUJOH 3PPUT 4BNQMJOH &MJDJUBUJPO w 3FTQPOTFBXBJUJOHPOFTSFDFJWF SFTQPOTFTWJBBTFQBSBUFSFRVFTU ˡ44&NFTTBHFTGSPNTFSWFS IUUQTNPEFMDPOUFYUQSPUPDPMJPTQFDJ fi DBUJPOCBTJDUSBOTQPSUT -
44& 4FSWFS4FOU&WFOUT w "OPSNBM)551SFTQPOTF application/json SFUVSOTPOF+40/BOEDMPTFT /PU44& w
44& text/event-stream LFFQTUIFDPOOFDUJPO PQFOBOETFOETNVMUJQMFNFTTBHFTBTdata:MJOFT JOBTJOHMF)551SFTQPOTF HTTP/1.1 200 OK Content-Type: text/event-stream Cache-Control: no-cache data: {"jsonrpc":"2.0","method":"notifications/progress","params":{...}} data: {"jsonrpc":"2.0","method":"notifications/progress","params":{...}} data: {"jsonrpc":"2.0","id":"1","result":{"tools":[...]}} )5513FTQPOTF -
44& 4FSWFS4FOU&WFOUT def DataProcessingTool.call(server_context:) server_context.report_progress(0, total: 100) data = fetch_data
server_context.report_progress(50, total: 100) result = process(data) server_context.report_progress(100, total: 100) # data: {"method":"notifications/progress",...}\n\n MCP::Tool::Response.new([{type: "text", text: result}]) end def handle_request_with_sse_response(...) body = proc do |stream| @sessions[session_id][:post_request_streams][request_id] = stream response = dispatch_handle_json(body_string, server_session) send_to_stream(stream, response) # data: {"jsonrpc":"2.0","id":"1","result":{...}}\n\n stream.close end [200, SSE_HEADERS, body] end def send_to_stream(stream, data) stream.write( "data: #{data.to_json}\n\n" ) stream.flush end 5SBOTQPSU 4%,JOUFSOBM 5PPM VTFSDPEF DBMMFEJOTJEFEJTQBUDI@IBOEMF@KTPO 3BDL4USFBNJOH#PEZ 1VNBDBMMTCPEZDBMM TUSFBN # data: {"jsonrpc":"2.0","id":"1","result":{...}}\n\n # data: {"jsonrpc":"2.0","id":"1","result":{...}}\n\n # data: {"jsonrpc":"2.0","id":"1","result":{...}}\n\n # data: {"jsonrpc":"2.0","id":"1","result":{...}}\n\n -
$SPTTSFRVFTUTZODWJB2VFVF @pending_responses (shared hash on StreamableHTTPTransport) ┌──────────────────────────────────┐ │ request_id_A =>
{ queue: QueueA }│←protected by @mutex (prevents TOCTOU) │ request_id_B => { queue: QueueB }│ └──────────────────────────────────┘ ↑ register ↑ lookup │ │ Request 1 Request 2 (tools/call) (sampling result POST with │ with request_id_A) Generate request_id_A │ Create QueueA │ Store in @pending │ │ │ ←── Send request_id_A │ to client via SSE │ │ Lookup QueueA by request_id_A │ │ QueueA.pop ←──────── QueueA.push(result) (block) (unblock) │ Receives result Resumes tool execution ←── Sends Tool::Response via SSE )BTILFZFECZ SFRVFTU@JE SFRVFTUUPPMTDBMM (FOFSBUF SFRVFTU@JEBOE 2VFVF" 3FTQPOTFUPDMJFOU GSPNSFRVFTU #MPDLIFSF SFRVFTU3FTQPOTF GSPNDMJFOUXJUI SFRVFTU@JE -PPLVQ2VFVF" GSPN !QFOEJOH@SFTQPOTFT 1VTIUP2VFVF" QPQSFRSFTVNFT 💡When a response from the client is needed inside tools/call 3FTQPOETUPDMJFOU BOEDPNQMFUFT -
# Request 2 # client response POST def handle_response(body, ...)
pending = @pending_responses[body[:id]] pending[:queue].push(body[:result]) end # Request 1 # tools/call def send_request(method, ...) request_id = generate_request_id # SecureRandom.uuid queue = Queue.new @pending_responses[request_id] = {queue:} request = {jsonrpc: "2.0", id: request_id, method:} send_to_stream(stream, request) response = queue.pop # returns to caller, resumes tool execution. response end $SPTTSFRVFTUTZODWJB2VFVF IUUQTEPDTSVCZMBOHPSHFO5ISFBE2VFVFIUNM 2VFVF JOJWBS $SFBUFBRVFVF 8BJUUIFRVFVF &ORVFVFUIF DMJFOUSFTQPOTF %FRVFVF UIFSFTQPOTF -
class McpController < ActionController::API def create # POST server =
MCP::Server.new( name: 'weather', version: '1.0.0', tools: [GetAlert, GetForecast] ) transport = MCP::Server::Transports:: StreamableHTTPTransport.new(server, stateless: true) server.transport = transport status, headers, body = transport.handle_request(request) render(json: body.first, status:, headers:) end 4USFBNBCMF)551&YBNQMF.$14FSWFS 💡Stateless Mode: available on Puma (workers >= 2) or Unicorn 4UBUFMFTT FH "DUJPO.FUIPET TUBUFMFTTUSVFCFDBVTFB OFXUSBOTQPSUJOTUBODFJT DSFBUFEQFSSFRVFTU TP MCP-Session-IdDBOOPU CFTIBSFE .$1QSPUPDPMSFRVFTUIBOEMJOH -
'VUVSF8PSLT w 3VCZ4%,JTDVSSFOUMZ5JFS &YQFSJNFOUBM BJNJOHGPS5JFS w 5JFSSFRVJSFTW w WOFFET.$1TQFDDPWFSBHFBOE"1* TUBCJMJUZ
NZQSJPSJUZ w 5JFSSPBENBQUPGPMMPXBGUFS5JFS -
1BSU*,FZ1PJOUT w 5IFVOEFSMZJOHNFDIBOJTNTSFNBJO OJY BOEXFCGVOEBNFOUBMT w 4JNQMF.$1TFSWFSTDBOCFCVJMUXJUI TUEJPVTJOHUIFQSPWJEFECBTJD"1*T w .$1JT"WBJMBCMFJO3VCZ
Now that the MCP foundation is in place, let's think about calling Ruby tools -
3VCP$PQT#VJMUJO.$1 w "OFYQFSJNFOUBMGFBUVSFJOUSPEVDFEJO 3VCP$PQ w 3FRVJSFTgem 'mcp'JOUIF(FN fi MF w
"WBJMBCMFCZSFHJTUFSJOHUIF.$1TFSWFS VTJOHNDQKTPO FH JO$MBVEF$PEF -
.$13FTPVSDF-JNJUBUJPOT w 1BDLBHJOHEPDTJOUIFHFNJODSFBTFTTJ[F w File.read fi MMTUIFDPOUFYUXJOEPX🙅 w $PQEPDTUPPMBSHF QFSEFQBSUNFOU
w 3FRVJSFTVQGSPOUEPDEFTJHO FH TQMJUUJOH fi MFT SVCPDPQ -
w "VUPDPSSFDU--.HFOFSBUFE3VCZDPEF OFFETMJOUJOHGPSNBUUJOH w *OTQFDUJPO-41+40/31$TUZMF OPU DMBOHTUZMFEJBHOPTUJDT XJUIP ff FOTFTVNNBSZ
'FBUVSFTJO3VCP$PQ ❯ /mcp ─────────────────────────────────────────────────────────────────────────────── Tools for rubocop 2 tools ❯ 1. RuboCop's inspection read-only 2. RuboCop's autocorrection destructive $MBVEF$PEF -
w *T$MBOHGPSNBU QSPHSBNNBUJDBMMZ QSPDFTTBCMF w +40/31$JO-41BT BXFMMLOPXOGPSNBU w +40/JTFBTJFSGPS BHFOUTUPQSPDFTT
QSPHSBNNBUJDBMMZ -41GPSNBU Offenses: test.rb:10:20: C: Style/RedundantLineContinuation: Redundant line continuation. "longlong-string \ ... ^ 1 file inspected, 1 offense detected { "method": "textDocument/publishDiagnostics", "params": { "uri": "file:///path/to/test.rb", "diagnostics": [{ "message": "Redundant line continuation.", "code": "Style/RedundantLineContinuation", "range": { "start": { "line": 9, "character": 19 }, "end": { "line": 9, "character": 20 } } }] } } $MBOH'PSNBU -41 +40/31$ -
Built-in LSP 3VCP$PQ 3VOOFS SVO DBMM SFRVFTU EFMFHBUF 3VCP$PQ$-* $PNNBOE
-41 rubocop --lsp 3VCP$PQ-41 4FSWFS 1MVHJO &EJUPS*%& 3VCP$PQ-41 3PVUFT 3VCP$PQ-41 3VOUJNF P ff FOTFT GPSNBU XSJUF TUBSU IUUQTSVCZLBJHJPSHQSFTFOUBUJPOTLPJDIUNMEBZ 3VCP$PQTCVJMUJO-41 Request / response (JSON-RPC) 💡This design was first introduced at RubyKaigi 2023 -
Built-in LSP Request / response (JSON-RPC) 3VCP$PQ 3VOOFS SVO DBMM
SFRVFTU EFMFHBUF 3VCP$PQ$-* $PNNBOE -41 rubocop --lsp 3VCP$PQ-41 4FSWFS 1MVHJO &EJUPS*%& 3VCP$PQ-41 3PVUFT 3VCP$PQ-41 3VOUJNF P ff FOTFT GPSNBU XSJUF TUBSU SVCPDPQNDQ "HFOU Built-in MCP 3VC$PQ$-* $PNNBOE .$1 3VCP$PQ.$1 4FSWFS 3VCP$PQTCVJMUJO.$1 --. .$1$MJFOU Request / Response (JSON-RPC) "HFOU TUBSU 💡Based on RuboCop's built-in LSP 6TFTTUEJPUSBOTQPSU BOEUPPMTJOUFSOBMMZ -
*OTJEF3VCP$PQ.$14FSWFS class RuboCop::MCP::Server def initialize(config_store) = (@runtime = RuboCop::LSP::Runtime.new(config_store)) def
start server = ::MCP::Server.new( name: 'rubocop_mcp_server', version: RuboCop::Version::STRING, tools: [inspection_tool] ) ::MCP::Server::Transports::StdioTransport.new(server).open end private def inspection_tool ::MCP::Tool.define( name: 'rubocop_inspection', description: 'Inspect Ruby code for offenses.', input_schema: { properties: {path: {type: 'string'}, source_code: {type: 'string'}} } ) do |path: nil, source_code: nil| offenses = @runtime.offenses(path || 'example.rb', source_code) ::MCP::Tool::Response.new([{type: 'text', text: offenses.to_json}]) end end end -
*OJUJBM.PUJWBUJPO w 5PPMJOQVUEF fi OFECZ+40/4DIFNBGPS TUSVDUVSFEJOUFSBDUJPO w 1SPUPDPMCBTFEUPPMEJTDPWFSZBOE JOWPDBUJPO OPUBEIPDDPNNBOET
w 3FVTFPGUIF-41EJBHOPTUJDGPSNBUUP NJOJNJ[FJNQMFNFOUBUJPODPTU -
1SPCBCJMJTUJDWT%FUFSNJOJTUJD w .$1BOE"HFOU4LJMMTBSFOPU HVBSBOUFFEUPFYFDVUF w "HFOU4LJMMTTQFDTBZTdescription EF fi OFTXIFOUPVTF w
3VCP$PQBVUPDPSSFDUPOBHFOUDPEF TIPVMEVTF1PTU5PPM6TFIPPLT OPU.$1 IUUQTDPEFDMBVEFDPNEPDTFOIPPLTQPTUUPPMVTF -
{ "hooks": { "PostToolUse": [{ "matcher": "Edit|Write", "hooks": [{ "type":
"command", "command": "file_path=$(cat - | jq -r '.tool_input.file_path // empty') && [ -n \"$file_path\" ] && bundle exec rubocop -a --force-exclusion \"$file_path\" 2>/dev/null || true" }] }] } } 1PTU5PPM6TFXJUI3VCP$PQ 3VOBVUPDPSSFDUPOUIFHFOFSBUFE fi MFTVTJOHrubocop -a .claude/settings.json ⚠Hooks always execute, so there is a trade-off with token consumption -
-41WT.$1$POTUSBJOFEWT0QFO&OEFE w -41EF fi OFTB fi YFETFUPGDBQBCJMJUJFT TPXIBUUPCVJMEJTDPOTUSBJOFECZUIF QSPUPDPM
w .$1JTPQFOFOEFEXJUIQSPCBCJMJTUJD OBUVSBMMBOHVBHF TPXIBUUPCVJME EFQFOETPOJNBHJOBUJPO -
%FUFSNJOJTNBOE1SPCBCJMJTUJD3FWJTJUFE w 3VCP$PQEFUFSNJOJTUJD "45CBTFE BOBMZTJTBOEBVUPDPSSFDUJPO w --.TQSPCBCJMJTUJD BVUPSFHSFTTJWF OFYUUPLFOQSFEJDUJPO w
8IBUIBQQFOTXIFODPNCJOFE P(next_token ∣ context) -
%PZPVIBWF"OZ*EFBT 🤔 w FH JOTQFDUXIJDIDPQTBSFBWBJMBCMF FOBCMF EJTBCMFEDPQTUFNQPSBSJMZ BOEDIFDLDPEF JODMVEJOHEJTBCMFEDPQT w
#VJMEUIFCFTUDPO fi HGPSBQSPKFDUTDPEF w 0UIFSWBHVFJEFBTTPGBS MFWFSBHJOHQSPCBCJMJTUJDFNFSHFODFUISPVHI )VNBOJOUIF-PPQ🤔 -
-FTTPOTGSPN$IBU(15 w 'SPNDPNQMFUJPOUPDIBU"1*T w $IBU.- SPMFCBTFE 6* BOE69 FOBCMFEDPOWFSTBUJPOBUTDBMF w
$POWFSTBUJPOXJUI--.TNJHIUCF UIFFTTFODF🤔 -
4FSWFSUP$MJFOU3FRVFTUT w 'PS--.BOEGPSIVNBO w 4BNQMJOH GPS--. w &MJDJUBUJPO GPSIVNBO
w "WBJMBCMF)VNBOJOUIF -PPQ ˡ44&NFTTBHFTGSPNTFSWFS IUUQTNPEFMDPOUFYUQSPUPDPMJPTQFDJ fi DBUJPOCBTJDUSBOTQPSUT -
4BNQMJOH --. w 5IFTFSWFSEFMFHBUFT--.DBMMTUPUIFDMJFOU TUBZJOHNPEFMBHOPTUJDBOE"1*LFZGSFF w $MBVEF$PEFEPFTOPUDVSSFOUMZTVQQPSU .$14BNQMJOH BOUISPQJDTDMBVEFDPEF
w 8PSLBSPVOE5PPMTDBODBMMUIF--."1* EJSFDUMZ -
class SummarizeTool < MCP::Tool description "Summarize text using LLM" input_schema(
properties: {text: {type: "string"}}, required: ["text"] ) def self.call(text:, server_context:) result = server_context.create_sampling_message( messages: [ {role: "user", content: {type: "text", text: "Please summarize: #{text}"}} ], max_tokens: 500 ) MCP::Tool::Response.new([{ type: "text", text: result[:content][:text] }]) end end 4BNQMJOH&YBNQMF.$14FSWFS create_sampling_message TZODISPOPVTMZTFOETBO--. SFRVFTUUPUIFDMJFOUWJB44&BOE CMPDLTVOUJMUIFSFTQPOTFJTSFUVSOFE 3FUVSOTUIFSFTQPOTFUPUIFDMJFOU JODPSQPSBUJOHUIF4BNQMJOHSFTVMU -
&MJDJUBUJPO )VNBO w .$1TFSWFSQBVTFTUBTLFYFDVUJPOUP SFRVFTUVTFSJOQVUWJBGPSNPS63- BQQSPWBM FH 0"VUI w
&OBCMFT)VNBOJOUIF-PPQ w $MBVEF$PEFTVQQPSUTUIJTTJODFW QFS$)"/(&-0(NEPGBOUISPQJDTDMBVEFDPEF -
class ConfirmDeleteTool < MCP::Tool description "Delete a record with user
confirmation" input_schema(properties: {id: {type: "string"}}, required: ["id"]) def self.call(id:, server_context:) result = server_context.create_form_elicitation( message: "Are you sure you want to delete record #{id}?", requested_schema: { type: "object", properties: {confirm: {type: "boolean", description: "Confirm deletion"}}, required: ["confirm"] } ) if result[:content][:confirm] delete_record(id) text = "Record #{id} deleted." else text = "Deletion cancelled." end MCP::Tool::Response.new([{type: "text", text: text}]) end end &MJDJUBUJPO&YBNQMF.$14FSWFS 3FUVSOTUIFSFTQPOTFUPUIFDMJFOU JODPSQPSBUJOHUIF&MJDJUBUJPOSFTVMU create_elicitation TFOETBGPSNSFRVFTU UPUIFDMJFOUWJB44& BOECMPDLTVOUJMUIF VTFSTVCNJUTUIFJOQVU requested_schemaEF fi OFTUIFGPSN fi FMET UIFVTFSNVTU fi MMJO +40/4DIFNB -
The exploration continues... Attention(Q, K, V) = softmax ( QKT
dk ) V Thank you for your...