Skip to main content
@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 smallest server serves exactly the built-in kinds and themes over stdio:

The stdio example

The repository’s examples/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:
Author widgets in the designer, export them as TypeScript (the export matches the example’s 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:
A stdio server runs on the operator’s own machine, which is why the example grants 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_UI1 or true slims the default-format render_widget result (a one-line confirmation instead of the full HTML text block) when no UI capability could be negotiated — on stateless HTTP the tools/call request builds a server that never saw initialize. A negotiated capability overrides it in either direction; explicit format values are never slimmed.
  • WIDGENTIC_INLINE_IMAGES0 or false disables server-side image inlining. By default the iframe-facing surfaces of a result (structuredContent.html, structuredContent.tree and the ui://widgentic/page/<kind> resource) get https image sources rewritten to data: 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:
Options carry 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.