
Dispatch.pudu
Pudu86 lines3.4 KB
1/** @App.Dispatch.Backbone — answers one input line under the session */2module PuduLangMcp.App.Dispatch34import Std.Json as Json5import Std.Map as Map6import Std.Text as Text7import PuduLangMcp.App.Completion as Completion8import PuduLangMcp.App.Context as Context9import PuduLangMcp.App.Discovery as Discovery10import PuduLangMcp.App.Prompts as Prompts11import PuduLangMcp.App.Resources as Resources12import PuduLangMcp.App.Tools.Registry as Registry13import PuduLangMcp.Constants.Server as Server14import PuduLangMcp.Domain.Protocol.Negotiation as Negotiation15import PuduLangMcp.Domain.Rpc.Message as Message16import PuduLangMcp.Domain.Rpc.Reply as Reply17import PuduLangMcp.Errors.RpcError as RpcError1819/** @App.Dispatch.State — the session carried between input lines */20export type State = { session: Negotiation.Session }212223const PING: Str = "ping"242526export fn initial() -> State {27 State{session: Negotiation.Unopened}28}293031export fn routes() -> Map[Str, fn(&Context.Context, &Json.Json) -> Result[Json.Json, RpcError.ProtocolError]] {32 mapOf([33 ("server/discover", Discovery.discover),34 ("tools/list", Registry.list),35 ("tools/call", Registry.call),36 ("resources/list", Resources.list),37 ("resources/templates/list", Resources.templates),38 ("resources/read", Resources.read),39 ("prompts/list", Prompts.list),40 ("prompts/get", Prompts.get),41 ("completion/complete", Completion.complete)42 ])43}444546export fn respond(context: &Context.Context, state: &State, line: Str) -> (State, Array[Str]) {47 if line.toBytes().length() > Server.MAX_LINE_BYTES {48 return (*state, [Reply.failure(&None, &RpcError.InvalidRequest("a message may be at most " + show(Server.MAX_LINE_BYTES) + " bytes"))])49 }50 if Text.isBlank(line) { return (*state, []) }51 match Message.parse(line) {52 case Message.Refused(id, problem) => (*state, [Reply.failure(&id, &problem)])53 case Message.ClientResponse => (*state, [])54 case Message.Notification(_, _) => (*state, [])55 case Message.Request(id, method, params) => request(context, state, id, method, ¶ms)56 }57}585960fn request(context: &Context.Context, state: &State, id: Message.RequestId, method: Str, params: &Json.Json) -> (State, Array[Str]) {61 let era = match Negotiation.admit(&state.session, method, params) {62 case Ok(admitted) => admitted63 case Err(problem) => { return (*state, [Reply.failure(&Some(id), &problem)]) }64 }65 let unknown = Reply.failure(&Some(id), &RpcError.MethodNotFound(method))66 if Negotiation.isHandshake(method) {67 return match era {68 case Negotiation.LegacyEra(version) => (State{session: Negotiation.Legacy(version)}, [Reply.result(&id, &Discovery.initialize(version))])69 case Negotiation.Modern => (*state, [unknown])70 }71 }72 if method == PING {73 return match era {74 case Negotiation.LegacyEra(_) => (*state, [Reply.result(&id, &Discovery.pong())])75 case Negotiation.Modern => (*state, [unknown])76 }77 }78 match Map.get(&routes(), method) {79 case Some(handler) => match handler(context, params) {80 case Ok(body) => (*state, [Reply.result(&id, &body)])81 case Err(problem) => (*state, [Reply.failure(&Some(id), &problem)])82 }83 case None => (*state, [unknown])84 }85}86