@widgentic/mcp/sdk exports one function, createWidgenticServer(options?), returning a connectable McpServer from the official @modelcontextprotocol/sdk with the full wiring: the seven tools, the MCP Apps declaration, the app template resource, output slimming and image inlining. The SDK packages are optional peers, so install them alongside:
The stdio example
The repository’sexamples/mcp-server/main.ts is the template for a deployment with your own widgets compiled in. Run it from a checkout with npm run mcp, or register it with a host:
widgets/ module shape), register them into a catalog and hand it to the assembly. Compiled-in widgets bind their actions inline, so the action source walks the same definitions:
execute.
Options
With no options the assembly serves the built-ins; everything else is your explicit choice.
Catalog and themes are passed in because the trust decision belongs where the API key is read.
Transports and environment
The assembly is transport-agnostic; hosts connect it to stdio, Streamable HTTP or in-memory pipes. Over Streamable HTTP, resolve the caller’s principal from the presented API key before constructing that request’s server and connect a fresh one per request — composition caches nothing, so one principal’s widgets never reach another’s session. A key that resolves to no principal degrades to the anonymous catalog, never to an error, and is never logged. Two environment variables are read each time a server is constructed:WIDGENTIC_ASSUME_UI—1ortrueslims the default-formatrender_widgetresult (a one-line confirmation instead of the full HTML text block) when no UI capability could be negotiated — on stateless HTTP thetools/callrequest builds a server that never sawinitialize. A negotiated capability overrides it in either direction; explicitformatvalues are never slimmed.WIDGENTIC_INLINE_IMAGES—0orfalsedisables server-side image inlining. By default the iframe-facing surfaces of a result (structuredContent.html,structuredContent.treeand theui://widgentic/page/<kind>resource) gethttpsimage sources rewritten todata:URIs through the guarded fetch, because Apps-host sandboxes block external images.
Give it a store
@widgentic/mcp/store turns an API key into a principal. createMemoryStore(seed?, limits?, options?) serves tests and demos; createFileStore(dir, options?) reads this layout:
limits (how many widgets, themes, schemas, actions and secrets a principal may hold, the bytes per entry and the template nodes per widget — the defaults are on Limits), an onDiagnostic sink and a cipher for secrets. Per request:
composeCatalog and composeThemes return fresh instances every time, each with a diagnostics array: an invalid, oversized or built-in-shadowing entry is skipped with a diagnostic, never fatal. Keys are stored as sha256: digests and compared in constant time (generateKey, hashKey, verifyKey); a key carries read and optionally execute, fixed at creation.
Cosmos DB
@widgentic/mcp/store/cosmos exports createCosmosStore(options), a WritableWidgetStore over two containers: data, partitioned by /principalId with one document per entry (profile, widget:<kind>, theme:<name>, schema:<name>, action:<name>, secret:<name>), so a principal’s catalog is one single-partition query; and keys, partitioned by /digest, so key resolution is a point read. It takes an endpoint and an Azure credential (managed identity in deployment) plus optional databaseId, dataContainerId, keysContainerId, limits, cipher and log — deliberately no account-key or connection-string option. Under Cosmos RBAC the serving identity can hold the read-only role, so a write from the MCP server fails at the service. @azure/cosmos and @azure/identity are optional peers of this entry.
Secrets
Http actions reference secrets by name;@widgentic/mcp/secrets stores them as envelope-encrypted records. encryptSecret(value, cipher) generates a fresh 256-bit data key, encrypts the value with AES-256-GCM, wraps the data key through the cipher port and returns { alg, kekVersion, wrappedKey, iv, ciphertext, tag }; decryptSecret reverses it and rewrapSecret moves a record to a newer key version without decrypting the value. Values are 8 to 4096 bytes.
A SecretCipher only wraps and unwraps data keys. createLocalCipher(hexKey) keeps a 64-hex-character key in memory (generateLocalKek() makes one) — for file-store rigs and tests, never production. @widgentic/mcp/secrets/keyvault exports createKeyVaultCipher({ keyId, credential, previous? }), which wraps and unwraps through the vault’s cryptographic operations so the key-encryption key never enters the process; the identity needs only the wrap/unwrap role on that key, and previous maps older key versions to clients until records are re-wrapped. Hand the cipher to the store’s cipher option; without one the store refuses putSecret and secretValue with NO_CIPHER. Secrets are injected only at execution, never displayed, and redacted from every message the server emits.