Something went wrong. Try again.
My version of https://github.com/vercel-labs/fx
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472const std = @import("std");const capability_retrieval = @import("capability_retrieval.zig");const types = @import("../shared/types.zig");const context_limits = @import("../config/context_limits.zig");const elicitation = @import("../mcp/elicitation.zig");const mcp_contract = @import("../mcp/mcp_contract.zig");const access_policy = @import("../mcp/access_policy.zig");
const Allocator = std.mem.Allocator;
pub const HasToolFn = *const fn (*anyopaque, []const u8, Access) bool;pub const ValidateToolFn = *const fn (*anyopaque, Allocator, []const u8, []const u8, Access) anyerror!ValidationResult;pub const CallToolFn = *const fn (*anyopaque, Allocator, []const u8, []const u8, usize, CallOptions) anyerror!?CallResult;pub const SearchRequest = capability_retrieval.Request;pub const SearchToolsFn = *const fn (*anyopaque, Allocator, SearchRequest, types.PermissionRuleSet, context_limits.Values, Access, ?*std.atomic.Value(bool)) anyerror!SearchResult;pub const ToolSchemaFn = *const fn (*anyopaque, Allocator, []const u8, types.PermissionRuleSet, context_limits.Values, Access, ?*std.atomic.Value(bool)) anyerror!?ToolSchemaResult;pub const FeatureCallFn = *const fn ( *anyopaque, Allocator, FeatureRequest, FeatureCallOptions,) anyerror!FeatureResult;
pub const Progress = mcp_contract.Progress;pub const ProgressSink = mcp_contract.ProgressSink;
pub const ResolvedLiveView = struct { /// Generation of the MCP-specific immutable view. authority_generation: u64, view: access_policy.View, /// Full child authority generation for final action precommit. action_authority_generation: u64 = 0,
pub fn deinit(self: *ResolvedLiveView, alloc: Allocator) void { self.view.deinit(alloc); self.* = undefined; }};
const LiveViewProvider = struct { context: *anyopaque, resolve_fn: *const fn (*anyopaque, Allocator) anyerror!ResolvedLiveView,
pub fn resolve(self: LiveViewProvider, alloc: Allocator) !ResolvedLiveView { return self.resolve_fn(self.context, alloc); }};
pub const Access = union(enum) { unrestricted, disabled, scoped: struct { captured: *const access_policy.View, admission_authority_generation: u64, live: LiveViewProvider, action_authority_generation: u64 = 0, },
pub fn allowsTool(self: Access, runtime_generation: u64, name: []const u8) bool { return switch (self) { .unrestricted => true, .disabled => false, .scoped => |scope| scope.captured.runtime_generation == runtime_generation and scope.captured.tool(name) != null, }; }
pub fn allowsFeatureServer(self: Access, runtime_generation: u64, name: []const u8) bool { return switch (self) { .unrestricted => true, .disabled => false, .scoped => |scope| scope.captured.runtime_generation == runtime_generation and scope.captured.features_visible and scope.captured.server(name) != null, }; }};
pub const DefinitionSnapshot = union(enum) { current, unavailable, updated: SelectedTool,};
pub const SnapshotToolFn = *const fn (*anyopaque, Allocator, []const u8, Binding, types.PermissionRuleSet, context_limits.Values, Access) anyerror!DefinitionSnapshot;
pub const Binding = types.McpToolBinding;
pub const CallOptions = struct { expected_binding: ?Binding = null, expected_runtime_generation: ?u64 = null, cancel_flag: ?*std.atomic.Value(bool) = null, progress: ?ProgressSink = null, input_responder: ?InputResponder = null, continuation: ?Continuation = null, access: Access = .unrestricted,};
pub const InputRequired = struct { input_requests_json: []const u8, request_state_json: ?[]const u8 = null, legacy_retry_without_responses: bool = false, legacy_url_phase: LegacyUrlPhase = .consent, legacy_url_completion_signal: ?*const LegacyUrlCompletionSignal = null,};
const LegacyUrlPhase = enum { consent, await_completion,};
pub const LegacyUrlCompletionStatus = enum(u8) { pending, completed, cancelled,};
/// Borrowed wake state for one legacy URL-required interaction. The runtime/// owns its lifetime; product adapters may observe it only during a responder/// callback and must not retain either pointer.pub const LegacyUrlCompletionSignal = struct { wake: std.atomic.Value(bool) = .init(false), status: std.atomic.Value(LegacyUrlCompletionStatus) = .init(.pending),};
pub const InputOrigin = struct { wire: elicitation.Wire, server_name: []const u8, operation: elicitation.Operation, runtime_generation: u64 = 0, connection_generation: u64, client_generation: u64, catalog_generation: u64, request_generation: u64, auth_generation: u64, deadline_ms: i64, lifecycle_cancel_flag: ?*const std.atomic.Value(bool) = null,};
pub const InputIdentityWitness = struct { runtime_generation: u64 = 0, connection_generation: u64, client_generation: u64, catalog_generation: u64, auth_generation: u64,};
pub const InputResponder = struct { context: *anyopaque, capabilities: elicitation.Capabilities = .{}, legacy_url_manual_completion: bool = false, callback: *const fn (*anyopaque, Allocator, InputOrigin, InputRequired) anyerror![]const u8, continuation_terminal: ?*const fn (*anyopaque, Allocator, InputOrigin, ContinuationTerminal) void = null,
pub fn finish(self: InputResponder, alloc: Allocator, origin: InputOrigin, outcome: ContinuationTerminal) void { const callback = self.continuation_terminal orelse return; callback(self.context, alloc, origin, outcome); }};
pub const InputCompletion = struct { responder: InputResponder, origin: InputOrigin,
pub fn finish(self: InputCompletion, alloc: Allocator, outcome: ContinuationTerminal) void { self.responder.finish(alloc, self.origin, outcome); }};
pub const LegacyUrlCompletion = struct { server_name: []const u8, elicitation_id: []const u8, runtime_generation: u64 = 0, connection_generation: u64, client_generation: u64, auth_generation: u64,};
pub const LegacyUrlCompletionSink = struct { context: *anyopaque, /// Accepts the unique outbound ACP elicitation id. The legacy wire id is /// not unique across recovered connections and cannot own cleanup. accept: *const fn (*anyopaque, InputOrigin, []const u8) LegacyUrlAcceptTransition, consume: *const fn (*anyopaque, LegacyUrlCompletion) LegacyUrlConsumeTransition, publish: *const fn (*anyopaque, []u8) void,};
pub const LegacyUrlConsumeTransition = union(enum) { missing, consumed: ?[]u8,};
pub const LegacyUrlAcceptStatus = enum { awaiting_completion, completed,};
pub const LegacyUrlAcceptTransition = union(enum) { missing, awaiting_completion, completed: []u8,};
pub const ContinuationTerminal = enum { completed, abandoned,};
pub const Continuation = struct { input_responses_json: []const u8, request_state_json: ?[]const u8 = null,};
pub const FeatureAction = enum { resource_list, resource_templates, resource_read, prompt_list, prompt_get, prompt_complete, resource_complete,};
pub const FeatureArgument = struct { name: []const u8, value: []const u8,};
pub const FeatureRequest = struct { action: FeatureAction, server_name: []const u8, identity: []const u8 = "", argument_name: []const u8 = "", value: []const u8 = "", arguments_json: []const u8 = "{}", context_arguments: []const FeatureArgument = &.{},};
pub const FeatureCallOptions = struct { cancel_flag: ?*std.atomic.Value(bool) = null, input_responder: ?InputResponder = null, output_limit_bytes: usize, access: Access = .unrestricted,};
pub const FeatureResult = struct { model_output: []u8, images: []types.ToolImage = &.{},
pub fn deinit(self: *FeatureResult, alloc: Allocator) void { alloc.free(self.model_output); types.freeToolImages(alloc, self.images); self.* = undefined; }};
pub const CallStatus = enum { success, tool_failure, protocol_failure, input_required,};
pub const CallResult = struct { model_output: []const u8, images: []types.ToolImage = &.{}, status: CallStatus = .success, input_required: ?InputRequired = null,
pub fn deinit(self: *CallResult, alloc: Allocator) void { alloc.free(self.model_output); types.freeToolImages(alloc, self.images); self.deinitInputRequired(alloc); self.* = undefined; }
pub fn takeImages(self: *CallResult) []types.ToolImage { const images = self.images; self.images = &.{}; return images; }
pub fn takeModelOutput(self: *CallResult, alloc: Allocator) []const u8 { const model_output = self.model_output; types.freeToolImages(alloc, self.images); self.deinitInputRequired(alloc); self.* = undefined; return model_output; }
fn deinitInputRequired(self: *CallResult, alloc: Allocator) void { if (self.input_required) |required| { alloc.free(required.input_requests_json); if (required.request_state_json) |state| alloc.free(state); } }};
pub const ValidationResult = union(enum) { valid: u64, invalid: []const u8, not_available,};
/// Owned by a search result, or borrowed from a tool execution arena.pub const SelectedTool = struct { name: []const u8, schema_json: []const u8, mcp_binding: ?Binding = null,
pub fn deinit(self: SelectedTool, alloc: Allocator) void { alloc.free(self.name); alloc.free(self.schema_json); }};
pub fn freeSelectedTools(alloc: Allocator, tools: []const SelectedTool) void { for (tools) |tool| tool.deinit(alloc); alloc.free(tools);}
pub const SearchResult = struct { model_output: []u8, notice: ?[]u8 = null, selected_tools: []const SelectedTool = &.{},
pub fn deinit(self: *SearchResult, alloc: Allocator) void { alloc.free(self.model_output); freeSelectedTools(alloc, self.selected_tools); if (self.notice) |notice| alloc.free(notice); self.* = undefined; }};
pub const ToolSchemaResult = union(enum) { selected: Payload, rejected: Payload,
pub const Payload = struct { model_output: []u8, notice: ?[]u8 = null, mcp_binding: ?Binding = null, };
pub fn deinit(self: *ToolSchemaResult, alloc: Allocator) void { const payload = switch (self.*) { .selected => |value| value, .rejected => |value| value, }; alloc.free(payload.model_output); if (payload.notice) |notice| alloc.free(notice); self.* = undefined; }};
pub const RuntimeCapabilities = struct { context: ?*anyopaque = null, has_tool: ?HasToolFn = null, validate_tool: ?ValidateToolFn = null, call_tool: ?CallToolFn = null, access: Access = .unrestricted,};
pub fn validateAdvertisedDynamicTool( input: AvailabilityInput, arena: Allocator, name: []const u8, arguments_json: []const u8,) !ValidationResult { if (input.is_registered_tool) return .not_available; const context = input.runtime.context orelse return .not_available; const validate_tool = input.runtime.validate_tool orelse return .not_available; return try validate_tool(context, arena, name, arguments_json, input.runtime.access);}
const AvailabilityInput = struct { is_registered_tool: bool, advertised_dynamic_tool_names: []const []const u8, runtime: RuntimeCapabilities,};
const CallInput = struct { advertised_dynamic_tool_names: []const []const u8, runtime: RuntimeCapabilities, max_tool_result_bytes: usize, options: CallOptions = .{},};pub fn isAdvertisedDynamicToolName( advertised_dynamic_tool_names: []const []const u8, name: []const u8,) bool { for (advertised_dynamic_tool_names) |advertised| { if (std.mem.eql(u8, advertised, name)) return true; } return false;}
pub fn isAvailableDynamicTool(input: AvailabilityInput, name: []const u8) bool { if (input.is_registered_tool) return false; if (!isAdvertisedDynamicToolName(input.advertised_dynamic_tool_names, name)) return false; const context = input.runtime.context orelse return false; const has_tool = input.runtime.has_tool orelse return false; return has_tool(context, name, input.runtime.access);}
fn callAdvertisedDynamicTool( input: CallInput, arena: Allocator, name: []const u8, arguments_json: []const u8,) !?CallResult { if (!isAdvertisedDynamicToolName(input.advertised_dynamic_tool_names, name)) return null; const context = input.runtime.context orelse return null; const call_tool = input.runtime.call_tool orelse return null; return try call_tool( context, arena, name, arguments_json, input.max_tool_result_bytes, input.options, );}pub fn notSelectedOutput(arena: Allocator, name: []const u8) ![]const u8 { return std.fmt.allocPrint( arena, "Dynamic MCP tool not selected for this model step: {s}. Use capability_search or mcp_select_tool to load its definition; the selected tool can be called on the next model step after its schema is advertised.", .{name}, );}test "MCP advertised dynamic tool executes only through its supplied capability" { var fixture = Fixture.CountingContext{}; const advertised = [_][]const u8{"mcp_fs_read"}; const input = CallInput{ .advertised_dynamic_tool_names = &advertised, .runtime = .{ .context = @ptrCast(&fixture), .call_tool = Fixture.callCounting, }, .max_tool_result_bytes = 1024, };
const result = (try callAdvertisedDynamicTool( input, std.testing.allocator, "mcp_fs_read", "{}", )).?; defer std.testing.allocator.free(result.model_output);
try std.testing.expectEqualStrings("mcp ok", result.model_output); try std.testing.expectEqual(@as(usize, 1), fixture.calls); try std.testing.expect((try callAdvertisedDynamicTool( input, std.testing.allocator, "mcp_other", "{}", )) == null); try std.testing.expectEqual(@as(usize, 1), fixture.calls);}
const Fixture = struct { const CountingContext = struct { calls: usize = 0, };
fn callCounting(raw_context: *anyopaque, arena: Allocator, _: []const u8, _: []const u8, _: usize, _: CallOptions) anyerror!?CallResult { const context: *CountingContext = @ptrCast(@alignCast(raw_context)); context.calls += 1; return .{ .model_output = try arena.dupe(u8, "mcp ok") }; }};