Slide 1

Slide 1 text

&YQMPSJOH3VCP$PQXJUI.$1 ,PJDIJ*50&4. *OD 3VCZ,BJHJ "QSJM  Possibilities Enabled by Streamable HTTP )BLPEBUF$JUJ[FO)BMM

Slide 2

Slide 2 text

w 3VCP$PQDPSFUFBN w 3VCZ4%,UFBNPG.$1TUFFSJOHHSPVQ w &OHJOFFSJOH.BOBHFSBOE %JTUJOHVJTIFE&OHJOFFSPG&4. *OD w 3VCZ,BJHJTQFBLFSBU  -5 5BLFPVU  5BLFPVU      BOE !LPJD

Slide 3

Slide 3 text

IUUQTSBJMTIPTUJOHDPNXIJDISVCZHFNTEPZPVMPWF

Slide 4

Slide 4 text

IUUQTSBJMTIPTUJOHDPNXIJDISVCZHFNTGSVTUSBUFZPVUIFNPTU

Slide 5

Slide 5 text

.BJOUBJO044&WFSZ%BZ

Slide 6

Slide 6 text

&4. *OD BHJMFFTNDPKQ

Slide 7

Slide 7 text

%SJOLVQ4QPOTPS &4.%SJOLVQ %BZ 5IV"QSJMBU Pre-registration required

Slide 8

Slide 8 text

&4./PWFMUZ -JNJUFE2VBOUJUZ

Slide 9

Slide 9 text

ΰ²Ίΰ°†ΰ€€ *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

Slide 10

Slide 10 text

Support OSS community

Slide 11

Slide 11 text

+PJOVT w-FUTIBDLUPHFUIFS w:PVDBOXPSL SFNPUFMZGSPN BOZXIFSFJO+BQBO w4FFBHJMFFTNDPKQ πŸ—Ύ

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

The Age of Artificial Intelligence .PEFSO%FWFMPQNFOU

Slide 14

Slide 14 text

Generative Pre-trained Transformer

Slide 15

Slide 15 text

P(next_token ∣ context)

Slide 16

Slide 16 text

P(xt+1 ∣ x1 , …, xt )

Slide 17

Slide 17 text

"/FX6TFS w )VNBOT $-* &EJUPST*%&T  w 1SPHSBNT -41 *OUFHSBUJPOT  w "HFOUT "DSPTT"MM  Exploring the AI ​ ​ Era

Slide 18

Slide 18 text

(VBSESBJMTGPS"*"HFOUT w 3VCP$PQTFSWFTBTBHVBSESBJMUIBU WFSJ fi FT"*HFOFSBUFEDPEFXJUIPVU IVNBOJOUFSWFOUJPO w &OTVSJOHOPEFQSFDBUFE"1*TBSFVTFE w &OTVSJOHDPOGPSNBODFUPUIFFYQFDUFE TUZMF

Slide 19

Slide 19 text

5PPM*OWPDBUJPOGSPN"*"HFOUT w #BTI $-* rubocopDPNNBOE w .$11SPUPDPMCBTFE.$1DBMMT w 4LJMMT/BUVSBMMBOHVBHF MFTT3VCZ,BJHJMJLF w -413VCZ-41JOUFHSBUJPO w "OFXBQQSPBDIZFUUPDPNFπŸ€” IUUQTHJUIVCDPNBOUISPQJDTDMBVEFQMVHJOTP ffi DJBMQVMM

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

$POUFOUT **3VCP$PQY.$1 * .$13VCZ4%, Ruby 4.0.3, RuboCop 1.86.1, MCP Ruby SDK 0.13.0

Slide 22

Slide 22 text

Scaffolding...

Slide 23

Slide 23 text

ruby.sdk.modelcontextprotocol.io *.$13VCZ4%,

Slide 24

Slide 24 text

4UBOEBSEJ[FTIPXDPOUFYUBOEUPPMTBSF JOUFHSBUFEBDSPTT"*BQQMJDBUJPOT .$1*OTQJSFECZ-41 IUUQTNPEFMDPOUFYUQSPUPDPMJPEPDTHFUUJOHTUBSUFEJOUSP #BTFEPO+40/31$

Slide 25

Slide 25 text

"WBJMBCMF0G fi DJBM.$14%,T IUUQTNPEFMDPOUFYUQSPUPDPMJPEPDTTEL

Slide 26

Slide 26 text

.$13VCZ4%,BOE.F w *OJUJBMMZTUBSUFECZBGFXFOHJOFFSTBU 4IPQJGZ*OD w +PJOFEFBSMZ XJUI3VCP$PQJONJOE w "UFγΆ†(Γ–SBM !BUFTHPSBM JOWJUFENFUP CFDPNFBDPNNJUUFS w 1VCMJTIFEBTUIFNDQHFN

Slide 27

Slide 27 text

0G fi DJBM.$14%,T w -JDFOTFEVOEFS"QBDIF USBOTJUJPOJOHGSPN.*5  HPWFSOFECZUIF"HFOUJD"*'PVOEBUJPO -JOVY'PVOEBUJPO  w 1SPWJEFT4%,TGPSCVJMEJOHCPUI.$1 TFSWFSTBOEDMJFOUT w (PWFSOFEVOEFSUIF4%,5JFSJOH4ZTUFN 

Slide 28

Slide 28 text

4%,5JFSJOH4ZTUFN 4%,NBUVSJUZDMBTTJ fi DBUJPOCZ.$1TQFD DPWFSBHFCBTFEPOGFBUVSFTBOENBJOUFOBODF w 5JFS'VMMZTVQQPSUFE w 5JFS$PNNJUNFOUUPGVMMTVQQPSU w 5JFS&YQFSJNFOUBM 3VCZ4%,JTIFSF

Slide 29

Slide 29 text

$POGPSNBODF5FTU w 0 ffi DJBMUFTUTVJUFUIBU4%,TNVTUQBTT w -JLFSVCZTQFD CVUGPS.$1 w 3VCZ4%,TFSWFS DMJFOU BDIJFWFE 5JFSSFRVJSFTPOCPUI  w 5JFSSFRVJSFTW TP"1*TUBCJMJUZBOE RVBMJUZBSFQSJPSJUJ[FE

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

5PBEE.$1TVQQPSUUP3VCP$PQ * fi STUFYQMPSFE IPXUPCVJMEBOEDPNNVOJDBUFXJUIBO.$1TFSWFS .$14FSWFSBOE5SBOTQPSUT IUUQTNPEFMDPOUFYUQSPUPDPMJPEPDTHFUUJOHTUBSUFEJOUSP #BTFEPO+40/31$

Slide 32

Slide 32 text

.$15SBOTQPSUT w .$1IBTUXPDMJFOUTFSWFSUSBOTQPSUT TUEJPBOE4USFBNBCMF)551 w 'PS3VCP$PQBMPOF TUEJPJTTV ffi DJFOU w 'PSXFCBQQMJDBUJPOT FH 3BJMT3BDL  4USFBNBCMF)551CFDPNFTJNQPSUBOU w $IPPTFCBTFEPOMPDBMQSPDFTTPS)551 πŸ’‘

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

4USFBNBCMF)551#BTJD.FDIBOJTN w &OBCMFTCJEJSFDUJPOBMDPNNVOJDBUJPOPWFS )551CFUXFFODMJFOUBOETFSWFS w *NQSPWFTPOUIFQSFWJPVT)551 44& 4FSWFS4FOU&WFOUT USBOTQPSU w 6TFTBDVTUPNIFBEFS MCP-Session-Id  UPFTUBCMJTITFTTJPOCBTFEDPNNVOJDBUJPO

Slide 37

Slide 37 text

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  

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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+

Slide 40

Slide 40 text

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+

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

4FTTJPO.BOBHFNFOU w 4UEJPJTDMJFOUTFSWFS TPOPTFTTJPO NBOBHFNFOUOFFEFE 4UEJPJOUFSOBMMZDSFBUFTBTFTTJPOGPSDPOTJTUFODZ  w 4USFBNBCMF)551JTODMJFOUTFSWFS  TPTFTTJPONBOBHFNFOUJTSFRVJSFE w 5IF4%,JNQMFNFOUTTFTTJPONBOBHFNFOU VTJOHMCP-Session-IdEF fi OFEJO UIF.$1TQFD

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

⚠1SPDFTTBOE5ISFBE w @sessionsJTIFMECZUIF 4USFBNBCMF)5515SBOTQPSUJOTUBODFQFSQSPDFTT w "TFTTJPOJTDSFBUFEPOFBDIDMJFOUT fi STUSFRVFTU w $SFBUJOHBOFX4USFBNBCMF)5515SBOTQPSU QFSSFRVFTUDMFBST@sessions w 3FRVJSFT1VNB TJOHMFQSPDFTT NVMUJUISFBE UP LFFQMCP-Session-IdBDSPTTSFRVFTUT UISFBET

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

$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 

Slide 49

Slide 49 text

# 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 

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

$MJFOU4%, %FWFMPQNFOU4UBUVT w "1*GPS.$1TFSWFSJOWPDBUJPO w 6TFGVMGPSCVJMEJOHDPEJOHBHFOUT JO3VCZXJUI.$1 w #BTJDGFBUVSFTBWBJMBCMF w $POGPSNBODFDPWFSBHFDPNJOHTPPO

Slide 52

Slide 52 text

'VUVSF8PSLT w 3VCZ4%,JTDVSSFOUMZ5JFS &YQFSJNFOUBM BJNJOHGPS5JFS w 5JFSSFRVJSFTW w WOFFET.$1TQFDDPWFSBHFBOE"1* TUBCJMJUZ NZQSJPSJUZ  w 5JFSSPBENBQUPGPMMPXBGUFS5JFS

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

Exploring...

Slide 55

Slide 55 text

rubocop/rubocop#14911 **3VCP$PQY.$1

Slide 56

Slide 56 text

3VCP$PQT#VJMUJO.$1 w "OFYQFSJNFOUBMGFBUVSFJOUSPEVDFEJO 3VCP$PQ w 3FRVJSFTgem 'mcp'JOUIF(FN fi MF w "WBJMBCMFCZSFHJTUFSJOHUIF.$1TFSWFS VTJOHNDQKTPO FH JO$MBVEF$PEF

Slide 57

Slide 57 text

8IBUJOUFSFTUJOHUIJOHTDPVMECFEPOFCZ DPNCJOJOHBO--.XJUI3VCP$PQ 5IF0SJHJOBM*EFB IUUQTNPEFMDPOUFYUQSPUPDPMJPEPDTHFUUJOHTUBSUFEJOUSP #BTFEPO+40/31$

Slide 58

Slide 58 text

w 1SPWJEFBO.$1SFTPVSDFGPSDPQTOPU DPWFSFECZ--.QSFUSBJOJOH w $POUFYUTFSWFTBEJ ff FSFOUQVSQPTF w /PUXPSUIUIFUSBEFP f "CBOEPOFE*EFB

Slide 59

Slide 59 text

.$13FTPVSDF-JNJUBUJPOT w 1BDLBHJOHEPDTJOUIFHFNJODSFBTFTTJ[F w File.read fi MMTUIFDPOUFYUXJOEPXπŸ™… w $PQEPDTUPPMBSHF QFSEFQBSUNFOU  w 3FRVJSFTVQGSPOUEPDEFTJHO FH TQMJUUJOH fi MFT SVCPDPQ

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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$

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

*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

Slide 65

Slide 65 text

*OJUJBM.PUJWBUJPO w 5PPMJOQVUEF fi OFECZ+40/4DIFNBGPS TUSVDUVSFEJOUFSBDUJPO w 1SPUPDPMCBTFEUPPMEJTDPWFSZBOE JOWPDBUJPO OPUBEIPDDPNNBOET w 3FVTFPGUIF-41EJBHOPTUJDGPSNBUUP NJOJNJ[FJNQMFNFOUBUJPODPTU

Slide 66

Slide 66 text

w --.TBSFBMSFBEZGBNJMJBSXJUICBTJD 3VCP$PQDPNNBOETBOEGFBUVSFT w #BTIXJUIGPSNBUKTPOJTTJNQMFSUIBO .$1XSBQQJOH w .$1XSBQQJOHBEETUIFNPTUWBMVFGPS VOGBNJMJBSUPPMTPSGFBUVSFT *T.$1/FFEFEGPS8FMM,OPXO$-*T

Slide 67

Slide 67 text

1SPCBCJMJTUJDWT%FUFSNJOJTUJD w .$1BOE"HFOU4LJMMTBSFOPU HVBSBOUFFEUPFYFDVUF w "HFOU4LJMMTTQFDTBZTdescription EF fi OFTXIFOUPVTF w 3VCP$PQBVUPDPSSFDUPOBHFOUDPEF TIPVMEVTF1PTU5PPM6TFIPPLT OPU.$1 IUUQTDPEFDMBVEFDPNEPDTFOIPPLTQPTUUPPMVTF

Slide 68

Slide 68 text

{ "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

Slide 69

Slide 69 text

More to Explore...

Slide 70

Slide 70 text

-41WT.$1$POTUSBJOFEWT0QFO&OEFE w -41EF fi OFTB fi YFETFUPGDBQBCJMJUJFT  TPXIBUUPCVJMEJTDPOTUSBJOFECZUIF QSPUPDPM w .$1JTPQFOFOEFEXJUIQSPCBCJMJTUJD OBUVSBMMBOHVBHF TPXIBUUPCVJME EFQFOETPOJNBHJOBUJPO

Slide 71

Slide 71 text

%FUFSNJOJTNBOE1SPCBCJMJTUJD3FWJTJUFE w 3VCP$PQEFUFSNJOJTUJD "45CBTFE BOBMZTJTBOEBVUPDPSSFDUJPO w --.TQSPCBCJMJTUJD BVUPSFHSFTTJWF OFYUUPLFOQSFEJDUJPO w 8IBUIBQQFOTXIFODPNCJOFE P(next_token ∣ context)

Slide 72

Slide 72 text

%PZPVIBWF"OZ*EFBT πŸ€” w FH JOTQFDUXIJDIDPQTBSFBWBJMBCMF FOBCMF EJTBCMFEDPQTUFNQPSBSJMZ BOEDIFDLDPEF JODMVEJOHEJTBCMFEDPQT w #VJMEUIFCFTUDPO fi HGPSBQSPKFDUTDPEF w 0UIFSWBHVFJEFBTTPGBS MFWFSBHJOHQSPCBCJMJTUJDFNFSHFODFUISPVHI )VNBOJOUIF-PPQπŸ€”

Slide 73

Slide 73 text

-FTTPOTGSPN$IBU(15 w 'SPNDPNQMFUJPOUPDIBU"1*T w $IBU.- SPMFCBTFE 6* BOE69 FOBCMFEDPOWFSTBUJPOBUTDBMF w $POWFSTBUJPOXJUI--.TNJHIUCF UIFFTTFODFπŸ€”

Slide 74

Slide 74 text

$POUFYU8JOEPXBT$POWFSTBUJPO IUUQTQMBUGPSNDMBVEFDPNEPDTFOCVJMEXJUIDMBVEFDPOUFYUXJOEPXTUIFDPOUFYUXJOEPXXJUIFYUFOEFEUIJOLJOHBOEUPPMVTF

Slide 75

Slide 75 text

Introducing the probabilistic into deterministic tools

Slide 76

Slide 76 text

4FSWFSUP$MJFOU3FRVFTUT w 'PS--.BOEGPSIVNBO w 4BNQMJOH GPS--.  w &MJDJUBUJPO GPSIVNBO  w "WBJMBCMF)VNBOJOUIF -PPQ Λ‘44&NFTTBHFTGSPNTFSWFS IUUQTNPEFMDPOUFYUQSPUPDPMJPTQFDJ fi DBUJPOCBTJDUSBOTQPSUT

Slide 77

Slide 77 text

4BNQMJOH --. w 5IFTFSWFSEFMFHBUFT--.DBMMTUPUIFDMJFOU  TUBZJOHNPEFMBHOPTUJDBOE"1*LFZGSFF w $MBVEF$PEFEPFTOPUDVSSFOUMZTVQQPSU .$14BNQMJOH BOUISPQJDTDMBVEFDPEF  w 8PSLBSPVOE5PPMTDBODBMMUIF--."1* EJSFDUMZ

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

&MJDJUBUJPO )VNBO w .$1TFSWFSQBVTFTUBTLFYFDVUJPOUP SFRVFTUVTFSJOQVUWJBGPSNPS63- BQQSPWBM FH 0"VUI  w &OBCMFT)VNBOJOUIF-PPQ w $MBVEF$PEFTVQQPSUTUIJTTJODFW QFS$)"/(&-0(NEPGBOUISPQJDTDMBVEFDPEF

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

3FGSBNJOHBU3VCZ,BJHJ w "HFOUJDDPEJOHJTCFDPNJOHUIFOPSN w 'SBNJOHQSPCMFNTBOENBLJOHEFDJTJPOT NBUUFSNPSF w "UUIJT,BJHJ USZUIJOHTPVUBOETIBSF ZPVSJEFBT

Slide 82

Slide 82 text

w $PNCJOJOHEFUFSNJOJTUJDUPPMTXJUI UIFQSPCBCJMJTUJDOBUVSFPG--.TWJB .$1TFSWFSTNBZFOBCMFOFX QPTTJCJMJUJFT w &YQMPSBUJPOPGFYUFOEJOHUPPMTXJUI 3VCZΒΊ"*IBTKVTUCFHVO 1BSU**,FZ1PJOUT

Slide 83

Slide 83 text

#P[IJEBS#BUTPW "UFγΆ†(Γ–SBM 5PQIFS#VMMPDL +POBUIBO)FGOFS )BWF'VO (JU)VC!LPJD 4QFDJBM5IBOLT

Slide 84

Slide 84 text

The exploration continues... Attention(Q, K, V) = softmax ( QKT dk ) V Thank you for your...