Toolkit.CreateAsync
C# · package Toolnexus · SPEC §4
public sealed class Toolkit : IAsyncDisposable{ public static Task<Toolkit> CreateAsync(Options opts, CancellationToken cancellationToken = default);
public sealed class Options { public object? McpConfig { get; set; } // path, raw JSON, or parsed dict
public List<string>? SkillsDir { get; set; } // one or more skill roots public IReadOnlyList<SkillSource.SkillDef>? Skills { get; set; } // skills as data (§3 S1) public Func<IEnumerable<SkillSource.SkillDef>>? SkillProvider { get; set; } public IReadOnlyDictionary<string, bool>? SkillsFilter { get; set; } public int SkillSampleLimit { get; set; } // 0=default 10, n>0=cap, -1=omit
public IReadOnlyList<string>? DisableTools { get; set; } // drop by final exposed name public IReadOnlyList<string>? DisableSkills { get; set; } // drop by skill name
public List<ITool>? ExtraTools { get; set; } // custom/native/http tools public List<object>? AnnotatedObjects { get; set; } // scanned via Tools.FromObject
public object? Builtins { get; set; } // bool | {disabled|enabled|tools:{...}} public List<Agent>? Agents { get; set; } // remote A2A agents
public Func<Request, Task<Answer>>? WaitFor { get; set; } // §10 MCP elicitation bridge }}Collapse MCP servers, skills, built-ins, A2A agents and your own functions into one tool list.
Toolkit.CreateAsync is the one entry point that turns every tool source toolnexus knows about —
a connected mcp.json, a skills/ folder, the 10 built-in file/shell/web tools, remote A2A agent
skills, and plain C# methods — into a single flat ITool list, deduped and ready to hand to
LlmClient.RunAsync.
When to use it
Section titled “When to use it”Call it once per agent, before the first RunAsync/StreamAsync. Leave every field but the ones
you need unset: an empty new Toolkit.Options() still gives you all 10 built-ins (SPEC §4A), and
each additional field only turns on one more source — an mcp.json, a skills directory, your own
ITools — composed onto that baseline rather than replacing it.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”1. The smallest useful call — built-ins only
Section titled “1. The smallest useful call — built-ins only”using Toolnexus;
// no fields set: the 10 built-in tools (bash/read/write/edit/grep/glob/webfetch/question/// apply_patch/todowrite) are on by default (SPEC §4A) — nothing else to configure.await using var tk = await Toolkit.CreateAsync(new Toolkit.Options());Console.WriteLine(tk.Tools().Count); // 102. The realistic case — MCP servers + a skills folder
Section titled “2. The realistic case — MCP servers + a skills folder”using Toolnexus;
await using var tk = await Toolkit.CreateAsync(new Toolkit.Options{ McpConfig = "mcp.json", // connects every server, local stdio + remote HTTP SkillsDir = new[] { "skills" }, // globs **/SKILL.md, exposes one `skill` tool});
var client = LlmClient.Create(new LlmClient.Options{ BaseUrl = "https://api.openai.com/v1", Style = "openai", Model = "gpt-4o-mini", ApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"),});
var res = await client.RunAsync("Use the available tools to triage today's alerts.", tk);Console.WriteLine(res.Text);3. The full surface — filters, drop-lists, extras, and the §10 bridge
Section titled “3. The full surface — filters, drop-lists, extras, and the §10 bridge”using Toolnexus;
await using var mcp = await StartMcp(); // a connected MCP server exposing tools "a", "b", "c"
await using var tk = await Toolkit.CreateAsync(new Toolkit.Options{ McpConfig = new Dictionary<string, object?> { ["srv"] = new Dictionary<string, object?> { ["url"] = mcp.Url }, },
SkillsDir = new[] { "skills" }, Skills = new List<SkillSource.SkillDef> { new("inline-skill", "short desc", "full instructions"), }, SkillsFilter = new Dictionary<string, bool> { ["inline-skill"] = true }, SkillSampleLimit = 5,
// drop by final exposed name / skill name, after aggregation DisableTools = new[] { "srv_b" }, DisableSkills = new[] { "some-noisy-skill" },
// your own C# functions, alongside every other source ExtraTools = new List<ITool> { NativeTool.Of("ping", "health check", null, _ => "pong") },
// turn off the whole built-in source (an ExtraTools entry with the same name always wins anyway) Builtins = false,
// remote A2A agents — each advertised skill becomes a tool (source "a2a") Agents = new List<Agent> { remoteAgent },
// §10: bridge MCP server elicitation onto the same WaitFor the client uses WaitFor = async request => await MyApprovalQueue.ResolveAsync(request),});
Console.WriteLine(tk.Get("srv_b")); // null — dropped by DisableToolsConsole.WriteLine(tk.Get("srv_a")); // present — other tools from the same server are untouchedConsole.WriteLine(tk.Get("ping")); // present — extra toolSee also
Section titled “See also”LlmClient.Create— The unified client this toolkit’s tool list is meant to be passed to.McpSource.LoadAsync— Read anmcp.json, connect every local stdio and remote streamable-HTTP server, expose each server tool as aTool.McpSource.ParseConfig— Parse anmcp.jsonpath/string/object into the normalized server configMcpConfigaccepts.SkillSource.Load— Discover everySKILL.mdunder one or more directories and expose a singleskilltool with progressive disclosure.SkillSource.LoadWith— The options-object form behindSkillsDir/Skills/SkillProvider/SkillsFilter/SkillSampleLimit.SkillSource.ListSkills— Inventory every discovered skill — and the candidates that were skipped and why.BuiltinTools.Create— Construct every built-in tool directly, outside a toolkit.BuiltinTools.Select— The selection logic behind theBuiltinsoption (bool / disabled / enabled / per-tool map).LlmClient.WaitFor— The single hook where the host resolves a suspension — the same function this toolkit’sWaitForbridges MCP elicitation onto.