Modulewise Toolbelt: Part 2
Published: 7/14/2025
By: Mark Fisher
The first post of this series provided a quick intro to the Modulewise Toolbelt project and described the underlying zero-trust execution model. It is important to have that robust foundation in order to build security in depth.
The next consideration is how to manage access to external systems. We provide Agents with Tools so they can take action, not just generate text. But “clever” Agents can discover weak links, which can lead to unintended consequences. The goal is to provide Tools that enable Agents to act while constraining those Tools to the scope of each Agent’s responsibility. That’s the core idea behind a least-privilege capability model: provide the minimum permissions needed to get a job done.
This post will walk through how Capabilities are managed with Modulewise Toolbelt at both the host and guest levels.
The Example Components
First, in order to understand the need for Capabilities, let’s look at two Tools that will be used in the examples.
Stargazers
The Stargazers Tool invokes the GitHub API for a given organization and repository to retrieve the GitHub usernames of anyone who has starred that repository.
It requires HTTP as a Capability.
It also previews a theme of the next blog post: composability. That’s the underlying superpower. Developers won’t typically even need to implement the Components that are exposed as Tools.
“Composable Integration” includes generating adapters for existing APIs, data services, and messaging systems. The following command generates an adapter from config and composes that with a base Component, which in this case is a generic HTTP proxy:
toolsmith stargazers.toml
That config file looks like this:
[functions]
headers = [
"accept=application/vnd.github+json",
"user-agent=ModulewiseToolbelt/0.1.0"
]
[functions.users]
url = "https://api.github.com/repos/{organization}/{repository}/stargazers"
jq = ".[] | .login"
The resulting Component has a single function that we will expose as a Tool: users(organization, repository)
The toolsmith
command above is in-development and will be launched as another Modulewise open source project soon!
Counter
This one is available in the Modulewise example-components repository and backed by a valkey-client
from the componentized/valkey repository. You can provide a “bucket” name, or rely on the default which is “counts” (tool-level configuration will also be featured in the next blog post).
It requires the host network as a Capability.
The Tool surfaced from that Component has a single increment(key)
function that increments the current count by 1 and returns the new value.
Capabilities on the Host
As emphasized in the last post, the “toolbelt” name aligns with the view that MCP Servers should be lightweight, fine-grained, and customizable at the individual Agent level. That means hosting a highly curated set of Tools correlated to the Agent’s scope of responsibility.
That said, a Modulewise Toolbelt instance can instead be deployed as a more general purpose set of Tools for many Agents. So each instance needs to be configurable based on its environment and context. For a least-privilege capability model, the starting point is deny-by-default.
When running the Toolbelt server without any configuration, all Tools that require Capabilities will be skipped, as shown in the log (the “greeter” Tool requires no Capabilities so acts as a control specimen for these next 2 examples):
The server must be configured to explicitly enable one or more Capabilities. This example server config would support the Stargazers Tool:
[capabilities.http]
uri="wasmtime:http"
exposed=true
The exposed=true
setting indicates the Capability is available to Tools, else it’s limited to other Capabilities (some Capabilities can themselves require lower-level Capabilities, another case of composition that we’ll discuss in the next post).
With that server config provided, the Stargazers Tool is indeed loaded:
Capabilities for the Guest
But not so fast!
Even if the Capability is available on the server and exposed to Tools, the deny-by-default rule carries over to each individual Tool as well. Here’s an example defining two instances of the Stargazers Tool and two instances of the Counter Tool. Notice that one of each should fail because of a missing Capability:
[stargazers]
uri = "file://./toolsmith/components/stargazers-tool.wasm"
capabilities = ["http"]
[shoegazers]
uri = "file://./toolsmith/components/stargazers-tool.wasm"
[happy]
uri = "file://./example-components/lib/default-counter.wasm"
capabilities = ["inherit-network"]
[sad]
uri = "file://./example-components/lib/default-counter.wasm"
The MCP Inspector screenshots below show all four Tools hosted on a Toolbelt instance that has enabled both http and inherit-network Capabilities. The “shoegazers” and the “sad” counter both fail, because their Tool definitions did not include those Capabilities. The “stargazers” and “happy” counter are fine, and in the underlying valkey instance we have happy dogs but no sad pandas:
This is a strictly enforceable model. Components are only able to interact with an external system if explicitly granted the corresponding Capability. Unlike a traditional app that has an embedded client library even if it hasn’t been granted credentials, in this case, the “library” is not even present in the scope of the consuming Component.
Extending Capabilities and Inverting Control
Instead of directly consuming their dependencies as libraries, Components rely upon explicit interfaces. That allows for context-driven late-binding of specific implementations, either through composition at load time or through linking at runtime. That centrality of interface-based contracts facilitates extensibility and promotes inversion of control.
Extensibility will be a key part of the Modulewise Toolbelt experience. The ability to add Capabilities through composition is one of the main reasons, even beyond the security aspects, that the Component is the unit of deployment. This enables the contextual adaptation that will be increasingly important as agentic systems grow in complexity.
Combined with deny-by-default and layered configuration, this extensibility provides a more dynamic yet enforceable approach to the separation of concerns required in any large scale multi-role software delivery lifecycle. Developers can enable all the Capabilities they need in their own development environments, but those configurations cannot leak into environments that are the responsibility of an operations team. That team has full control over the mappings from developer intent to external systems.
What’s Next?
So far we’ve seen the zero-trust execution model for sandboxed Components and the least-privilege capability model for granting those Components access to external systems. The next post will dive into composition and how encapsulation and access-control can even apply at the boundaries between nested Components.
We’ll also explore how composition radically simplifies the developer experience. How it maximizes reuse and allows for late-binding customizations, so that in a variety of cases, developers can generate Tools without writing any code, Wasm or otherwise. The examples will also shift to the business domain level including no-code integration with existing business APIs.
The Toolsmith project will support several common base Components in addition to the http-proxy that enabled the Stargazers Tool above. For example, a similar configuration-driven approach could be used with database query templates. Generated Components can be further customized via configuration and composition, and they will be able to use business domain types as both input and output to increase control amidst the ambiguity and non-determinism in agentic systems.
Stay tuned for Part 3!