{"_id":"@atmos.build/extension","_rev":"3-db8166f6643279b238c136a95e7df9a1","name":"@atmos.build/extension","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.0":{"name":"@atmos.build/extension","version":"0.1.0","license":"UNLICENSED","_id":"@atmos.build/extension@0.1.0","maintainers":[{"name":"woddlepad","email":"daniel.teigland.developer@gmail.com"}],"dist":{"shasum":"bb76234f383acb09627681d0a664db24cc9a91f7","tarball":"https://registry.npmjs.org/@atmos.build/extension/-/extension-0.1.0.tgz","fileCount":6,"integrity":"sha512-ILWKOgIdYkMJc5z+Y9PDBHri/z94NaL3HCB7cQt0MrsGC7uooWIZ4hKuWFgZbWC0B5taRWhC/njKwN+odPOHuQ==","signatures":[{"sig":"MEUCIQCVRyv6HJXkHwEMizZ0D5EQ9eIq75kQOF8scBShbz6FawIgUHEMMcYaxdANJDFL/6CraotxTTUK7JGTl/X5uoMvXak=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":28325},"type":"module","_from":"file:/Users/daniel/dev/atmOS/dist/npm/atmos.build-extension-0.1.0.tgz","engines":{"node":">=24.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./server":{"types":"./dist/server.d.ts","import":"./dist/server.js"}},"_npmUser":{"name":"woddlepad","email":"daniel.teigland.developer@gmail.com"},"_resolved":"/Users/daniel/dev/atmOS/dist/npm/atmos.build-extension-0.1.0.tgz","_integrity":"sha512-ILWKOgIdYkMJc5z+Y9PDBHri/z94NaL3HCB7cQt0MrsGC7uooWIZ4hKuWFgZbWC0B5taRWhC/njKwN+odPOHuQ==","_npmVersion":"11.8.0","description":"Typed MCP extension contracts and a local SQLite runtime.","directories":{},"sideEffects":false,"_nodeVersion":"24.13.1","dependencies":{"zod":"4.4.3","@modelcontextprotocol/sdk":"1.29.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/extension_0.1.0_1788012478320_0.9425128142441208","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@atmos.build/extension","version":"0.1.1","license":"UNLICENSED","_id":"@atmos.build/extension@0.1.1","maintainers":[{"name":"woddlepad","email":"daniel.teigland.developer@gmail.com"}],"dist":{"shasum":"2f62c64ae6301da7e0aeb1dc77b0106b1e9fdec4","tarball":"https://registry.npmjs.org/@atmos.build/extension/-/extension-0.1.1.tgz","fileCount":6,"integrity":"sha512-i4XMtyEd2UvOR4IaHuusrfAg+aipdjTgeFzffeV7mLbU+iuNc4h0U+/3RvHC/CzOoep0FOa4DC/ZnZzNY5GlPQ==","signatures":[{"sig":"MEUCIQCPAGNfMitbfSuUP1mRRkHHbn2LUxRdIyhATFeM93ivTgIgU4UQnYzkhs3frq61BYE+jBBxl32z15SRfw7VdO0BIxA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":28780},"type":"module","_from":"file:dist/npm/atmos.build-extension-0.1.1.tgz","engines":{"node":">=24.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./server":{"types":"./dist/server.d.ts","import":"./dist/server.js"}},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:43b34dc3-b248-4688-b349-04165c35ef3c"}},"_resolved":"/home/runner/work/atmOS/atmOS/dist/npm/atmos.build-extension-0.1.1.tgz","_integrity":"sha512-i4XMtyEd2UvOR4IaHuusrfAg+aipdjTgeFzffeV7mLbU+iuNc4h0U+/3RvHC/CzOoep0FOa4DC/ZnZzNY5GlPQ==","_npmVersion":"12.0.2","description":"Typed MCP extension contracts and a local SQLite runtime.","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","dependencies":{"zod":"4.4.3","@modelcontextprotocol/sdk":"1.29.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/extension_0.1.1_1788279624674_0.14537812277033502","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"_id":"@atmos.build/extension@0.1.2","dist":{"shasum":"a89cdda23b376de857bb3d025d5ae54e6719b5c6","tarball":"https://registry.npmjs.org/@atmos.build/extension/-/extension-0.1.2.tgz","fileCount":8,"integrity":"sha512-AM2ffLo26BWYfy5UyJtZogpt3/9UKJdy9NIF1fSjewPHHC5AFK/Jww20jWoNanJJIc2lFzbLMiId6jildlIVsQ==","signatures":[{"sig":"MEUCIQC8Bf92iDJiTcNCDWg5DKG62m57F29+iUA+ibGlzqO2mAIgOLCCV3OwiZW7wpjc9ikYpRe1kI/TIFeQ391Ow+Z1C0U=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIFT/zhpe83f9V2X+ftloLNKAY2EeoMHZllJGUcLQtpGwAiEAz63VaSaMXCVEvOh4xkDildIotPLbeVAR6hQ4oYOtWGM="}],"unpackedSize":40004},"name":"@atmos.build/extension","type":"module","_from":"file:dist/npm/atmos.build-extension-0.1.2.tgz","engines":{"node":">=24.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./server":{"types":"./dist/server.d.ts","import":"./dist/server.js"}},"license":"UNLICENSED","version":"0.1.2","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:43b34dc3-b248-4688-b349-04165c35ef3c"}},"_resolved":"/home/runner/work/atmOS/atmOS/dist/npm/atmos.build-extension-0.1.2.tgz","_integrity":"sha512-AM2ffLo26BWYfy5UyJtZogpt3/9UKJdy9NIF1fSjewPHHC5AFK/Jww20jWoNanJJIc2lFzbLMiId6jildlIVsQ==","_npmVersion":"12.0.2","description":"Typed MCP extension contracts and a local SQLite runtime.","directories":{},"maintainers":[{"name":"woddlepad","email":"daniel.teigland.developer@gmail.com"}],"sideEffects":false,"_nodeVersion":"24.20.0","dependencies":{"zod":"4.4.3","jose":"6.2.3","@modelcontextprotocol/sdk":"1.29.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/extension_0.1.2_1789470758055_0.47313045889598326"}}},"time":{"created":"2026-08-29T14:07:58.146Z","modified":"2026-09-15T11:12:38.330Z","0.1.0":"2026-08-29T14:07:58.468Z","0.1.1":"2026-09-01T16:20:24.842Z","0.1.2":"2026-09-15T11:12:38.173Z"},"license":"UNLICENSED","description":"Typed MCP extension contracts and a local SQLite runtime.","maintainers":[{"name":"woddlepad","email":"daniel.teigland.developer@gmail.com"}],"readme":"# atmOS Extension\n\n`@atmos.build/extension` is the typed authoring layer for Naut-owned MCP Extensions. Define\neach resource and tool once with Zod, share that contract with the server and App, and keep\ndurable records in a local SQLite file.\n\n```sh\nnpm install --save-exact @atmos.build/extension\n```\n\nThe package requires Node 24 or newer. Its root export is browser-safe; SQLite and MCP server\ndependencies are reachable only through the explicit `@atmos.build/extension/server` subpath.\n\n## One shared contract\n\n```ts\nimport { defineExtension, resource, tool, z } from '@atmos.build/extension'\n\nexport const Todo = z.object({\n  id: z.string().uuid(),\n  title: z.string().trim().min(1).max(200),\n  done: z.boolean(),\n})\n\nexport const todos = resource({\n  uri: 'atmos://todos/rows',\n  name: 'Todo rows',\n  schema: z.object({ todos: z.array(Todo) }),\n})\n\nexport const createTodo = tool({\n  name: 'create_todo',\n  description: 'Add one todo.',\n  input: z.object({ title: z.string().trim().min(1).max(200) }),\n  output: z.object({ todo: Todo }),\n  text: ({ todo }) => `Created \"${todo.title}\".`,\n})\n\nexport const todoExtension = defineExtension({\n  name: 'todos',\n  version: '1.0.0',\n  resources: { todos },\n  tools: { createTodo },\n})\n```\n\nZod supplies both the TypeScript types and runtime validation. Tool callers receive the input\ntype, server handlers receive the parsed input type, and resource and tool results are checked\nagain at the protocol boundary.\n\nThe App imports the same descriptors. No URI, tool name, argument interface, or response\ninterface is repeated:\n\n```tsx\nimport { useMcpResource, useMcpTool, type App } from '@atmos.build/ui/mcp-app'\n\nimport { createTodo, todos } from '../contract'\n\nexport function useTodos(app: App | null) {\n  const rows = useMcpResource(app, todos)\n  const create = useMcpTool(app, createTodo)\n\n  return {\n    todos: rows.data?.todos ?? [],\n    createTodo: create.call,\n    loading: rows.loading || create.loading,\n    error: rows.error ?? create.error,\n  }\n}\n```\n\n## SQLite server\n\n```ts\nimport { randomUUID } from 'node:crypto'\n\nimport { createSqliteExtension } from '@atmos.build/extension/server'\n\nimport { Todo, createTodo, todoExtension, todos } from './contract.js'\n\nconst extension = createSqliteExtension(todoExtension, { root: import.meta.url })\n\nextension.resource(todos, ({ db }) => ({\n  todos: db.all(Todo, 'SELECT id, title, done FROM todos ORDER BY created_at DESC'),\n}))\n\nextension.mutation(createTodo, {\n  invalidates: [todos],\n  run: ({ db }, { title }) => {\n    const todo = { id: randomUUID(), title, done: false }\n    db.run(\n      'INSERT INTO todos (id, title, done, created_at) VALUES (?, ?, ?, ?)',\n      todo.id,\n      todo.title,\n      Number(todo.done),\n      new Date().toISOString(),\n    )\n    return { todo }\n  },\n})\n\nawait extension.listen()\n```\n\n`createSqliteExtension` opens `app.sqlite` in `--data-dir`, else in the `ATMOS_EXTENSION_DATA_DIR`\nthe Habitat injects for a deployed server, else in `data/`. It applies committed `db/NNN_name.sql`\nmigrations, enables foreign keys and WAL, and validates query rows with the schema supplied to\n`db.all`, `db.required`, or `db.maybe`. A mutation runs synchronously inside `BEGIN IMMEDIATE`,\nvalidates its output, commits, and only then notifies subscribers of every resource listed in\n`invalidates`.\n\nKeep `db/` in Git. Keep `data/.gitignore` in Git with the following contents so the database,\nWAL, and shared-memory files remain local to that checkout:\n\n```gitignore\n*\n!.gitignore\n```\n\nNever edit or remove an applied migration. Add the next monotonically numbered SQL file.\nExtensions and agent sessions read state through typed MCP resources and change it through\ndomain tools; they do not open the SQLite file themselves.\n\n## Who is calling\n\natmOS signs the principal behind every tool call it forwards to a registered MCP server. The\ntoken travels in the request itself, under `params._meta[\"atmos.build/caller\"]`, so it reaches a\nserver on a Machine over stdio and a server in the cloud over HTTP alike. It is an RS256 JWT\nissued by the atmOS identity service, bound to one server and one tool, valid for five minutes.\n\nThe runtime verifies it for you and hands the result to tool and mutation handlers as\n`context.caller`:\n\n```ts\nimport { requireCaller } from '@atmos.build/extension/server'\n\nextension.mutation(approveLead, {\n  invalidates: [leads],\n  run: (context, { id }) => {\n    const { userId, kind, nautId } = requireCaller(context, approveLead.name)\n    context.db.run(\n      'UPDATE leads SET approved_by = ?, approved_at = ? WHERE id = ?',\n      userId,\n      now(),\n      id,\n    )\n    return { id, approvedBy: userId, viaNaut: kind === 'naut' ? nautId : undefined }\n  },\n})\n```\n\n`Caller` carries `kind` (`user` or `naut`), `userId` (the acting principal user; on a Naut call\nthe Naut's own principal user), `orgId`, `nautId`, and, when the call came out of an Agent\nSession, `agentSessionId`, `runId`, and `toolCallId`, plus the token's `issuedAt` and\n`expiresAt`. A call that names no caller leaves\n`context.caller` undefined; `requireCaller` turns that into a tool error. A token that does not\nverify fails the call rather than running the handler anonymously.\n\nVerification needs to know which atmOS to trust and which server this is. A managed server on a\nMachine gets both injected as `ATMOS_ISSUER` and `ATMOS_MCP_SERVER_ID`. A server you host\nyourself sets them from the id atmOS assigned it at registration, or passes them explicitly:\n\n```ts\nconst extension = createSqliteExtension(contract, {\n  root: import.meta.url,\n  caller: { issuer: 'https://app.atmos.build', serverId: process.env.MY_SERVER_ID! },\n})\n```\n\n`caller: false` switches verification off; the token is then ignored and handlers never see a\ncaller. Public keys come from `<issuer>/.well-known/jwks.json`. A server you host yourself can\npoint elsewhere with `jwksUrl` or `ATMOS_JWKS_URL`, or pin keys outright with `keys`; on a Machine\nall three names are reserved, so a managed server always verifies against the issuer it was given.\n\n### Without this package\n\nAny MCP server can verify the token with a standard JWT library:\n\n1. Read the string at `params._meta[\"atmos.build/caller\"]` of the `tools/call` request. Absent\n   means the call names no caller.\n2. Verify an RS256 signature against the keys at `<issuer>/.well-known/jwks.json`, selecting the\n   key by the token's `kid` header, and require `iss` to equal the issuer and `aud` to contain your\n   server id.\n3. Require `exp` to lie in the future and `tool` to equal the tool being called.\n4. Read the claims: `sub` (acting user id), `kind`, `org_id`, `naut_id`, `agent_session_id`,\n   `run_id`, `tool_call_id`, `server_id`.\n\nRefuse the call when any step fails. Never fall back to what the client says about itself.\n\n## MCP Apps\n\nAn App document and its opening tool use descriptors instead of hand-written protocol metadata:\n\n```ts\nimport { appResource, tool, z } from '@atmos.build/extension'\n\nexport const appDocument = appResource({\n  uri: 'ui://todos/app.html',\n  name: 'Todos',\n})\n\nexport const openTodos = tool({\n  name: 'open_todos',\n  input: z.object({}),\n  output: z.object({ ready: z.literal(true) }),\n  app: {\n    document: appDocument,\n    icon: 'notebook-tabs',\n    resources: [todos],\n  },\n})\n```\n\nAdd both descriptors to `defineExtension`, then call `extension.app(appDocument)`. The server\nreads the conventional `app.html` shell and safely inlines `app-build/app.css` and\n`app-build/app.js` when\ntheir scaffold markers are present. A custom handler remains available for an App that deliberately\nuses a different document pipeline.\n","readmeFilename":"README.md"}