Releases / TinyStore 0.7.0
TinyStore 0.7.0
TinyStore 0.7.0 came out on 3 November 2026. Config can keep secrets out of a settings file that is committed beside the code, and a setting can be declared with no value in every language. A Rust program's MCP server runs its calls side by side, stops a call that the agent's host cancels, and serves tools that await on the program's own async runtime. A Rust MCP tool's closure now takes the call's token, so the closures of 0.6.0 need a second argument.
- Config
- Keep secrets out of a committed file: a file marked
secrets: falserefuses a secret. - Settings with no value:
nulluntil a layer gives one, a secret still masked.
- Keep secrets out of a committed file: a file marked
- MCP
- Slow calls and cancels: calls run side by side, and a cancelled one stops.
- Tools that await:
tinystore::aio::mcpon your own runtime.
Keep secrets out of a committed file
A settings file that is committed beside the code must not hold a password. Mark the file so that it refuses secrets, and each secret then has to come from the environment (#56):
const config = await store.config('dashbin', [decl, file('dashbin.toml', { secrets: false }), env('DASHBIN')])config = await store.config("dashbin", [Settings, file("dashbin.toml", secrets=False), env("DASHBIN")])config, err := tinystore.OpenConfig(ctx, store, "dashbin", defaults,
tinystore.File("dashbin.toml", tinystore.NoSecrets()), tinystore.Env("DASHBIN"))If dashbin.toml gives a secret, the config doesn't open. The error names
the variable to use instead, and never the secret's value:
config dashbin: dashbin.toml: db.password: a secret is not read from dashbin.toml; set DASHBIN_DB_PASSWORD or DASHBIN_DB_PASSWORD_FILE: invalid- A secret is refused wherever it stands: in a group, in an entry, or in a group that a setting chooses (contract CFG-16).
- The error for a missing required secret names its variable and its
_FILE, and no longer the file. - The mark is on the file, not on a setting. One mark keeps every secret out of the file, the secrets of a plugin's settings among them.
- A file without the mark may hold secrets, as before.
Keep secrets out of a committed file has the details.
Settings with no value
Some settings have no sensible default: the password of a database that uses
trust or peer authentication may stay unset. Declare such a setting with no
value. It is null until a layer gives it, and a secret declared this way
is still a secret (#57):
const config = await store.config('pg', [
{ password: optional(secret(String)), port: optional(int({ min: 1 })) },
file('pg.toml'),
env('PG'),
])@dataclass
class Pg:
password: Secret | None = None
port: Annotated[int, Range(min=1)] | None = Nonetype Pg struct {
Password *tinystore.Secret `json:"password"`
Port *int `json:"port" config:"min=1"`
}#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(default)]
struct Pg {
password: Option<Secret<String>>,
port: Option<u16>,
}- In a declaration as data,
"default": nulldeclares a setting with no value, of any kind:{ "kind": "text", "secret": true, "default": null }. - A value that a layer gives is checked against the setting's kind and rules, like any other (contract CFG-15).
explainshows the setting asunsetuntil a layer gives it a value.
Settings with no value has the details.
Slow calls and cancels
A Rust program's MCP server now runs each call on a thread of its own, so a
slow tool holds up nothing. While it runs, the server answers ping,
tools/list and other calls. When the agent's host cancels a call, the
server cancels the call's token and writes no answer for it
(#58):
Tool::reads("dashboard_check", "Runs each panel's query.", panels, |args, token| {
let mut checked = Vec::new();
for panel in args["panels"].as_array().ok_or("dashboard_check takes panels")? {
if token.is_cancelled() {
return Err("cancelled".into()); // nobody reads it: the host gave up on the call
}
checked.push(run_query(panel)?);
}
Ok(json!(checked))
})- A read through a TinyStore store stops by itself with
ErrorKind::Cancelled, the reads ofmcp::read_toolsamong them (contract MCP-07). - Work outside the store stops only when your closure checks the token.
CancellationTokencomes with themcpfeature alone, without a store.
Slow calls and cancels has the details.
Tools that await
With the mcp and async features, tinystore::aio::mcp serves tools whose
closures return futures, so a program on tokio or another runtime awaits its
own work in them, with no runtime of TinyStore's and no block_on:
use tinystore::aio::mcp::{self, Tool};
mcp::server("dashbin", env!("CARGO_PKG_VERSION"))
.tool(Tool::reads("query", "Rows of a read-only statement.", sql, move |args, _| {
let pool = pool.clone();
async move {
let sql = args["sql"].as_str().ok_or("query takes sql")?;
Ok(json!({ "rows": sqlx::query(sql).fetch_all(&pool).await?.len() }))
}
}))
.serve()
.await?; // answers stdin until it closes- Calls run side by side as futures that
serve's own future polls. Nothing is spawned. - A cancelled call's future is dropped, and it gets no answer (contract MCP-08).
- A blocking
tinystore::mcp::Tool,mcp::read_toolsamong them, mounts on the same server, and each of its calls runs on a thread of its own. - With
mcpandasyncand no engine, a program links no SQLite (contract MCP-09).
Async Rust has the details.
Changed names
-
Rust: the closure of
mcp::Tool::reads,writesandremovestakes the call's arguments and its&CancellationToken:Tool::reads("dashboards_list", "The project's dashboards.", schema, |_| Ok(json!([]))) // 0.6.0 Tool::reads("dashboards_list", "The project's dashboards.", schema, |_, _| Ok(json!([]))) // 0.7.0 -
Rust: the
asyncfeature no longer turns onstore. It awaits what is built beside it: the store and the engines you turn on, and the MCP server withmcp. Every engine turns onstore, so a program with an engine changes nothing.
Fixes
- In Rust, an
Option<Secret<String>>leftNoneis a secret. Before, a password that a file or a variable gave it printed in clear inexplainand in errors. - In Rust, an
Option<String>leftNonereads a variable as text. Before,APP_ID=123456was read as a number, and the config failed to open. tinystore config explain,setandresetread a config whose secret has a default. Before, they failed with "a setting has a default, or is required".- A tool whose closure panics fails its call with the panic's message, and the MCP server goes on. Before, the panic ended the server.
- The sentence that the MCP server adds to its instructions says which tools
change anything:
Only dashboard_save changes anything; every other tool only reads.Before, a model read it as advice about the file. - A tool that removes and that
--writedoesn't name is refused as one that removes:dashboard_delete removes, and dashbin mcp was not started with --write dashboard_delete.
Install
bun add @tinyshed/tinystore@0.7.0
npm install @tinyshed/tinystore@0.7.0pip install tinyshed-tinystore==0.7.0go get tinyshed.org/tinystore@v0.7.0cargo add tinystore@0.7.0
cargo install tinystore-cli@0.7.00.7.0 changes no file of a store, and 0.6.0 opens what 0.7.0 wrote. Only
0.6.0's tinystore config can't read a config that 0.7.0 kept with a
setting of no value: use the 0.7.0 command line. If your
program reaches a server, update the server with the SDK. A 0.6.0 server
reads a secret from a file marked secrets: false, and refuses a setting
declared with no value. The server image is
ghcr.io/tinyshed/tinystore:0.7.0.
Every commit since 0.6.0 is in the GitHub release.