createContext() from @toolgrain/mcp. Export
provider.use under an application-specific name such as useDatabase. Toolgrain
infers the returned value from the resolver, including asynchronous resolvers;
no explicit context type arguments or generic ToolContext are needed.
Define providers and standalone tools
useServices(ctx) starts its
resolver, which calls useDatabase(ctx) to resolve its dependency. Both providers
must be registered explicitly; accessor calls do not register dependencies.
The same providers work with HTTP and both supported MCP revisions.
Resolution and request caching
Every accessor returns aPromise, even when its resolver is synchronous.
Resolve dependencies inside the resolver and await their accessors. Returned
service methods can then use those resolved dependencies. Accessors should be
awaited within the active handler or resolver, rather than used for background work.
Factories run on first access, after authorization and tool input validation.
Discovery, tool listing, unknown tools, invalid input, and unused registrations
never start factories. A context caches one promise per provider: concurrent
readers share the same in-flight work and receive the same value. Failures are
cached too; a later access in that context does not retry the factory. A fresh
request gets a fresh cache, even if the request ID matches another request.
Provider names must be nonempty strings and label diagnostic messages. Identity
belongs to the definition, so two independently created providers with the same
name are distinct. Registering the same identity twice, including its overrides,
is a construction error. Definitions and registration snapshots are immutable.
Providers from compatible installed copies of @toolgrain/mcp interoperate.
Context values may contain functions, class instances, and mutable database
clients. They are returned unchanged, without copying or freezing. Request caching
does not make an existing shared pool request-owned: if the resolver returns a
shared client, requests intentionally receive that client. Applications own
creation and disposal of resources; this version adds no cleanup hooks,
transactions, or automatic pool/connection closing.
Typed overrides and direct tests
.provide(resolver) creates a new binding with the original provider’s identity
and value type. Existing accessors find that binding without changing the original
provider, another server, or another context. Both sync and async overrides are
supported and must return the complete inferred contract.
context array for per-server configuration.
Register the override instead of the original provider. toolContext() defaults
to no providers, with a fresh cache each time; reusing one context reuses its cache.
Direct execute() calls remain raw calls: they bypass server authorization,
argument/output validation, and MCP conversion. Test protected execution through
authorize() and handle() as described in the authorization guide.
Authorization and cancellation
A resolver receivesToolContext with the same request data, AbortSignal, and
verified identity as the tool. Authorization remains eager and independent of
lazy context resolution. Integration-specific useAuth(ctx) stays synchronous:
account and database. The frozen
auth snapshot remains available through nested provider calls. Stdio still has
no HTTP authorization identity.
Accessors check cancellation before access, before factory startup, and after
resolution. Pass ctx.signal to cancellable work. Cancellation remains
cooperative: a factory ignoring the signal can keep running until it settles,
but its late value is not delivered and the cancelled request sends no response.
Existing client cancellation, disconnect, transport close, and server close
behavior applies to providers too.
Errors and type safety
A missing registration rejects when accessed, naming the missing provider. Cycles among pending provider resolutions reject with a diagnostic chain such asContext dependency cycle: services -> database -> services. Concurrent
readers and shared dependency diamonds are valid. Direct accessors reject with
the original error. During server execution, missing providers, cycles, and
resolver exceptions produce generic Tool execution failed tool results. The
original errors and frozen tool/request details reach onError(error, details);
see errors and diagnostics. No new HTTP
authorization status is added.
TypeScript infers accessor results and checks overrides. It cannot prove a
standalone tool’s dynamically accessed providers are registered on every server;
missing registrations and cycles are runtime errors. Construct contexts with
toolContext() rather than hand-written objects.