// What a layer sees, and the chain that puts one in front of another. A layer
// is a function that is handed a call and the rest of the chain, so it decides
// for itself whether the call goes on, is answered on the spot, or is refused.
///|
/// The request kinds a layer is asked about: the three that run a component,
/// and the four that list them.
#derive(Eq, Debug)
pub(all) enum Operation {
ListTools
ListResources
ListTemplates
ListPrompts
CallTool
ReadResource
GetPrompt
}
///|
/// The JSON-RPC method an operation answers.
pub fn Operation::code(self : Operation) -> String {
match self {
ListTools => "tools/list"
ListResources => "resources/list"
ListTemplates => "resources/templates/list"
ListPrompts => "prompts/list"
CallTool => "tools/call"
ReadResource => "resources/read"
GetPrompt => "prompts/get"
}
}
///|
/// Whether the operation lists components rather than running one.
pub fn Operation::is_list(self : Operation) -> Bool {
match self {
ListTools | ListResources | ListTemplates | ListPrompts => true
_ => false
}
}
///|
/// One request on its way through the chain: what it is, what it names, the
/// arguments it carries, and the request context it will be served in.
pub(all) struct Call {
operation : Operation
/// The tool or prompt name, the resource URI, or the empty string for a
/// listing.
name : String
arguments : Map[String, Json]
context : @context.Context
}
///|
/// The call a request makes.
pub fn Call::new(
operation~ : Operation,
name~ : String,
arguments~ : Map[String, Json] = Map([]),
context~ : @context.Context,
) -> Call {
{ operation, name, arguments, context, }
}
///|
/// Who is calling, as the authorisation check named them; empty when the
/// request carried no identity.
pub fn Call::subject(self : Call) -> String {
self.context.subject().unwrap_or("")
}
///|
/// The arguments as one string: keys sorted at every depth, so two calls that
/// mean the same thing also spell the same thing. A cache keys on it, and a
/// rate limiter that counts by argument does too.
pub fn Call::fingerprint(self : Call) -> String {
Json([
Json::string(self.operation.code()),
Json::string(self.name),
Json::string(canonical(Json(self.arguments))),
]).stringify()
}
///|
/// One JSON value written with its object keys in lexicographic order.
fn canonical(value : Json) -> String {
match value {
Object(fields) => {
let keys : Array[String] = fields.keys().collect()
keys.sort_by(String::lexical_compare)
let written = keys.map(key => {
Json::string(key).stringify() + ":" + canonical(fields[key])
})
"{" + written.join(",") + "}"
}
Array(items) => "[" + items.map(canonical).join(",") + "]"
_ => value.stringify()
}
}
///|
/// The rest of the chain, as a layer is handed it.
pub(all) struct Next {
priv run : async (Call) -> Json raise @exceptions.FastMCPError
}
///|
/// Serve the call with everything behind this point in the chain.
pub async fn Next::then(
self : Next,
call : Call,
) -> Json raise @exceptions.FastMCPError {
(self.run)(call)
}
///|
/// One step of a chain (← FastMCP's middleware): a name for the logs and
/// spans, and the function that sees every call passing through it.
///
/// A layer may answer without going on, pass the call along with
/// `next.then(call)`, look at what came back, or refuse it by raising
/// `@exceptions.Mcp` with the code it wants sent.
pub(all) struct Layer {
/// What the layer calls itself, for a log line or a span.
name : String
priv run : async (Call, Next) -> Json raise @exceptions.FastMCPError
}
///|
/// A layer made of a function.
pub fn Layer::new(
name~ : String = "layer",
run~ : async (Call, Next) -> Json raise @exceptions.FastMCPError,
) -> Layer {
{ name, run, }
}
///|
/// Serve the call through this layer alone, with `next` behind it.
pub async fn Layer::serve(
self : Layer,
call : Call,
next : Next,
) -> Json raise @exceptions.FastMCPError {
(self.run)(call, next)
}
///|
/// Serve `call` through `layers` in the order they were added, the first one
/// outermost, and `end` in the middle. An empty list calls `end` directly.
pub async fn chain(
layers : Array[Layer],
call : Call,
end : async (Call) -> Json raise @exceptions.FastMCPError,
) -> Json raise @exceptions.FastMCPError {
step(layers, 0, call, end)
}
///|
async fn step(
layers : Array[Layer],
index : Int32,
call : Call,
end : async (Call) -> Json raise @exceptions.FastMCPError,
) -> Json raise @exceptions.FastMCPError {
if index >= layers.length() {
return end(call)
}
let my = layers[index]
let next : Next = {
run: async fn(reached) -> Json raise @exceptions.FastMCPError {
step(layers, index + 1, reached, end)
},
}
my.serve(call, next)
}
///|
pub extend Operation with Eq::{equal, not_equal}
///|
pub extend Operation with Debug::{to_repr}