Extend tools and integrations
Last updated:
Prerequisites: understand module boundaries and decide whether the capability is general-purpose or business-owned. Begin with one small, verifiable capability.
Tool extension path
- Put hand-written
IAgwTool,IContextualTool, andIToolBlockimplementations inAgw.Tools; the global catalog scans only that assembly for hand-written tools. Business tools belong in their module’sApplication/Tools, declared as attributed containers or supplied through a Skill, with DTOs inContracts/Tools. - Reference
Agw.Tools.Abstractions; addAgw.Tools.Generatorsas an Analyzer for attributed declarations. - Explicitly declare permissions, argument descriptions, and return types. Standalone tools and attributed containers stay stateless; session state belongs in a Provider, session, or owned storage.
- Register services and generated declarations in the owning module. Choose Skill exposure or explicit global catalog inclusion.
- Verify discovery, arguments, permissions, project binding, and error mapping.
The generator emits metadata, JSON Schema, and direct invocation delegates. Do not add runtime reflection scanning as a fallback. Skill tools come from two members: IAgentSkillRegistration.Tools supplies hand-written IProjectScopedAgwTool instances, and ToolTypes supplies attributed container types generated through IAgwToolSet<T>. They bind to a Project during execution. Registering a generated module does not automatically expose every tool globally.
Integration extension path
IPluginCatalog owns Plugin, Connector, authentication, and capability-source definitions. Definitions are code/content assets. User setup is PluginInstallation; selectable accounts or endpoints are Connection. Do not collapse these into global configuration.
Add the catalog definition and required capability source, then verify per-user setup, Ready state, binding, and invocation. Infrastructure protects and resolves credentials. Reads and execution retain owner checks. Credential injection over HTTP/SSE requires HTTPS.
Choose global or Skill-owned tools
Expose independently selectable general-purpose operations explicitly in the global catalog. Register tools used only by a Skill through that Skill, so instructions and tools reach the agent together. For example, agw-job supplies job-management tools owned by the Jobs module.
After compilation, verify that the agent can discover the tool. If it is missing, check generated declarations and registration. If invocation fails, check Project binding, permissions, and arguments. Compilation alone does not verify runtime integration.
Option 1: define a Tool with an interface
Use IAgwTool for one independently callable operation per class. Declare a stable name, category, Plan availability, and permission, and implement the one required member, ToAITool(). Repository tools put the operation in an Execute method and wrap it in ToAITool() with AgwAIFunctionFactory.CreateParameterObjectFunction. That factory is internal to Agw.Tools, so the example lives in Agw.Tools. This minimal example performs no external I/O:
IAgwTool extends the metadata interface IAgwToolMeta; ToAITool() wraps the execution method as a model-callable function. Describe the method and input DTO so the model knows when and how to call it. Use asynchronous methods and propagate CancellationToken for real I/O. Business DTOs belong in the owner module’s Contracts/Tools.
Register and use it
- Put the implementation in
Agw.Tools/Impl/Tools. The global catalog scans only theAgw.Toolsassembly for hand-written tools, so anIAgwToolplaced in a business module is never discovered. Business modules use Option 2’s attributed containers instead, or placeIProjectScopedAgwToolinstances in a Skill’sIAgentSkillRegistration.Tools. Keep declarations stateless; do not store the current user, Project, or conversation in fields. - In
src/server/Agw.Shared/Tooling/ToolValueObject.cs, add the tool name toToolDefinitionNamesand itsAlllist, plus a concreteToolDefinition, a[JsonDerivedType]name mapping, and an empty or real Options type. Definitions and implementations must match one to one. An unregistered name fails registration with “does not have a registered ToolDefinition”. - Register dependencies through the owner module’s DI entry point. Verify /api/tools and bind the tool to an Agent or Project before using it.
- Ask the agent to invoke it and inspect arguments and output. Direct C# calls test the operation but do not verify runtime permission or Project binding.
For standalone tools requiring a Project or runtime directory, use IContextualTool.MaterializeAsync (defined in Agw.Tools/Contracts/Abstractions and, like other hand-written tools, discovered only in the Agw.Tools assembly) and bind authorized context into contributed functions. Do not let a model-supplied Project ID determine resource ownership.
Option 2: define Tools with attributes
Attributes work well for multiple operations in a service or business capabilities supplied by a Skill. Add these project references, adjusting relative paths:
This implements the same echo operation as the interface example. Choose one implementation; registering both creates a name collision.
The example container is an ordinary sealed class whose methods are all static. IAgwToolSet<TextTools>, used later, requires a non-static class as its type argument, so the container cannot be a static class. AgwToolContainer selects public ordinary methods declared directly on the type. AgwTool supplies a name and permission; AgwToolIgnore excludes helpers. Default names use the method name minus a terminal Async. Permissions must be explicitly declared or inherited from the container; AllowInPlanMode is independent.
Instance containers use explicit constructor injection and must be registered in DI. Static methods can use parameters marked AgwToolService. Service and CancellationToken parameters are excluded from model-facing schemas. Each invocation gets an independent asynchronous DI scope.
Generate, register, and select
Compilation generates metadata, input/output schemas, and direct invocation delegates. For an assembly named My.Module, register its generated module, then explicitly select container types if they belong in the global catalog:
Import the generated contracts and relevant registration extensions. Containers with only static methods need no instance registration; containers with instance methods also need AddScoped
For Skill-only tools, make the registration partial and implement IAgwToolSetservices.AddSingleton<IAgentSkillRegistration, YourSkillRegistration>(), along with its generated module and instance containers; see JobManagementSkillRegistration in the Jobs module. Bind the Skill to an Agent/Project to contribute its tools. Hand-written Skill tools implement IProjectScopedAgwTool and go in IAgentSkillRegistration.Tools; they do not need global catalog entries.
Fix generator diagnostics rather than adding reflection fallbacks: AGWTOOL001 identifies unsupported signatures; AGWTOOL002 identifies invalid declarations. See the tool abstraction guide for complete instance-container and Skill examples.
ToolBlocks: tools with shared state
Use a ToolBlock when operations such as adding, completing, and listing todos must maintain one coherent state. Members are selected as a group. An attribute container is a declaration mechanism, not session-state isolation.
Here is the complete repository TodoToolBlock:
Todo state and lifetime
- Descriptor.Members declares all five members, permissions, and Plan availability. Members cannot be separately registered as global tools.
- MaterializeAsync creates an AgwTodoProvider in ToolContribution.ContextProviders and adds a loop evaluator.
- AgwTodoProvider keeps AgwTodoState in the current AgentSession.StateBag and declares its key through StateKeys. Operations read, modify, and save the current session’s state; a static list or singleton must not hold everyone’s todos.
- Later calls in the same session reuse state; different sessions are isolated. Durable recovery depends on the surrounding session save/restore pipeline, not merely storing data in a Provider field.
- TodoCompletionLoopEvaluator checks outstanding items. When Mode is enabled, it evaluates in Execute mode. Custom ToolBlocks only need an evaluator if their behavior requires one.
Implement your own stateful ToolBlock
- Define the data and its lifetime: turn, session, or persistent project storage. Use AgwTodoState for session state and Project Memory for project storage as references.
- In
Agw.Shared/Tooling/ToolValueObject.cs, add the name toToolBlockDefinitionNamesand itsAlllist, plus a concrete ToolBlockDefinition, Options, and[JsonDerivedType]mapping. Then add a runtime name in ToolBlockNames that references that constant. The startup coverage check rejects ToolBlocks missing a definition or an implementation. - Implement IToolBlock and declare every member’s permission and allowInPlanMode.
- Create Providers in MaterializeAsync, bind functions to the current context, and transfer lifetime ownership to ToolContribution. Do not cache Providers or scoped services across users.
- Wire group selection into catalog and definition resolution. Test adding, completing, removing, session isolation, save/restore, Plan restrictions, and approvals.
Read the Todo Provider and Todo state; copying the descriptor alone omits state loading and saving.
Plugins: from catalog definition to invocation
A Plugin here is code and content in AGW’s integration catalog. GitHub is the built-in example. Adding a Plugin requires changing and building the server; arbitrary uploaded packages are not executed. Available integrations shows catalog definitions; Configured integrations shows user accounts or endpoints.
| Developer object | Responsibility |
|---|---|
| PluginDefinition | Stable ID, version, display name, connectors, optional Skill content |
| ConnectorDefinition | Service or protocol variant, such as GitHub Cloud |
| AuthSchemeDefinition | Authentication method, configuration fields, OAuth settings |
| CapabilitySourceDefinition | Internal C# tools or tools obtained from MCP |
| PluginInstallation | Per-user setup, such as OAuth client ID/secret |
| Connection | User account, credentials, Ready state; bindings use ConnectionId |
Define the catalog and authentication
Use this existing GitHub catalog as a complete structural reference. Replace OAuth endpoints, scopes, and fields with those required by your target service:
Keep Plugin, Connector, authentication, and source IDs stable. Validate the full catalog with PluginCatalogValidator. InstallationFields describe per-user setup; account fields belong in the authentication scheme’s connection fields. Use Secret field types and never embed real credentials in definitions.
Implement capability sources
Native: the definition’s Provider = “github” matches IConnectionNativeCapabilityProvider.Provider. Implement CreateTools(ConnectionNativeCapabilityContext), binding resolved ConnectionId, Alias, and ProjectId. Use names such as {alias}__{operation} to distinguish accounts.
Follow GitHubConnectionNativeCapabilityProvider: functions bind the account and Project, then create a scope and resolve IGitHubConnectionInvoker when invoked. The Invoker checks ownership, Ready state, and credentials. Do not accept arbitrary model-supplied ConnectionIds or cache account secrets in a singleton. Wire new operations into the source’s permission metadata and approval pipeline.
MCP: use McpCapabilitySourceDefinition with stdio, HTTP, or SSE transport. CredentialBindings map installation fields, connection fields, or OAuth tokens to environment variables or HTTP headers. Use HTTPS when sending credentials over the network and keep field references consistent with authentication definitions. This path retains connection authorization and runtime validation rather than passing unchecked URLs to agents.
Register, package content, and test
- Maintain catalog registration in the Integrations DI entry point. Register Native providers as IConnectionNativeCapabilityProvider and invocation services with suitable lifetimes, such as the scoped GitHub Invoker.
- Place optional Skill content in the Plugin content directory and point PluginSkillDefinition.ContentPath to SKILL.md. It provides instructions, not automatic execution of third-party scripts. Ensure published artifacts include these files.
- Find the definition in Available integrations, configure setup and an account for a test user, complete authentication, verify Ready, and bind it to an Agent or Project.
- Test a read and a controlled write, including tool names, arguments, permissions, and errors. Tests use real implementations, never mocks or fake implementations; tests that need real accounts or OAuth authorization stay out of the default suite.
- Cover foreign ConnectionIds, unready accounts, expired credentials, invalid catalog fields, duplicate tool names, and configuration changes. Updating one user’s installation settings must affect only that user’s connections.
There is currently no remote Marketplace download, signature, or automatic upgrade mechanism. Follow the GitHub Native Provider, GitHub Invoker, and capability-source definitions.
Verify
Cover successful invocation, invalid arguments, insufficient permissions, foreign Connections, and non-Ready Connections. Keep compile-time diagnostics effective. Tests use real implementations, never mocks or fake implementations; tests that depend on real accounts or external CLIs are opt-in and stay out of the default suite.