SkynestSkynest

Docs

Skynest — hosted Context Nest MCP server on Vercel.

What is Skynest?

Skynest is a hosted Context Nest MCP server. Context Nest (by PromptOwl) organizes knowledge as interconnected typed documents — nodes with graph relationships, pack bundles, semantic indexing, and full version history — purpose-built for AI consumption via the Model Context Protocol (MCP).

The upstream project runs as a local stdio MCP server tied to one machine. Skynest lifts this to the cloud: vault files live in Vercel Blob and every team member signs in over OAuth 2.1. Identity, vault sync, and authorization are all pluggable per deployment — run it GitHub-native (GitHub sign-in, writes committed to a private repo under each user's identity) or Azure-native (Microsoft Entra ID sign-in, writes recorded as versioned Azure Blob uploads). Either way you get a tamper-evident audit trail of who changed what.

Always on
Vault is reachable over HTTPS 24/7 — not just when your Mac is open.
Multi-user
Each teammate signs in with their own GitHub or Microsoft Entra account. Writes are attributed to them.
Versioned audit trail
Every change is recorded — as a real git commit or an Azure Blob version — with full history and rollback.
read.ai integration
Meeting transcripts are ingested automatically when a meeting ends.

Skynest exposes 19 Context Nest MCP tools — read_document, search, create_document, update_document, verify_integrity, governance tools, and more — over a standard HTTPS endpoint compatible with Claude Code, Cursor, and any MCP-capable AI tool.

Architecture

   ┌──────────────────────────────────────────────────────────────────────┐
   │ Skynest — Hosted Context Nest MCP server (Vercel / Next.js)           │
   │   • Next.js App Router, mcp-handler, /api/mcp route                   │
   │   • GitHub OAuth 2.1 (PKCE + dynamic client registration + RS256 JWT) │
   │   • Context Nest engine (vendored fork) used as a library             │
   │     — StorageProvider interface; BlobStorageProvider on Vercel        │
   │   • Vault files: Vercel Blob (read/write) + GitHub API (commit/history)│
   │   • /api/webhooks/[apikey]/[vaultId]/readai (read.ai ingest)           │
   └───────▲──────────────────────────────────────────────┬────────────────┘
           │ MCP over HTTPS (GitHub OAuth)                 │ GitHub API
           │                                               ▼
   Claude Code / Cursor /                        Private git repo
   any MCP-compatible tool                       <owner>/contextnest-vault
   (each user's own GitHub                       (source of truth +
    account → attributed commits)                 full version history)
           ▲
           │ webhook POST (HMAC signed)
   read.ai (meeting_end event)

Storage: Vault document files are stored in Vercel Blob (fast reads, always available). Every write also goes to a private GitHub repository via the GitHub REST API — this is the source of truth for version history and git attribution.

Auth: OAuth 2.1 with PKCE and RS256 JWTs. The identity provider is pluggable via AUTH_PROVIDER — GitHub or Microsoft Entra ID. MCP clients receive a short-lived Bearer token carrying mcp:read/mcp:write scopes decided by an AuthorizationProvider (see Access control). In GitHub mode the user's access token is embedded so writes are committed under their identity.

Vault sync: pluggable via VAULT_SYNC_PROVIDER. github commits each write to a private repo through the GitHub API; azure records writes as versioned Azure Blob uploads (with a tombstone version on delete); none disables sync. Vercel Blob remains the primary read/write document store in all modes.

Engine: The @promptowl/contextnest-engine package is vendored and used as a library — no stdio process, no vault path on disk. The storage layer is abstracted behind a StorageProvider interface; Skynest provides the Blob implementation.

Tech stack: Next.js 15 App Router · TypeScript · pnpm · mcp-handler · @vercel/blob · NextAuth v5 (GitHub provider) · jose (RS256 JWTs) · zod

Deploying Skynest

Choose an identity mode

Skynest runs in one of two modes, selected by the AUTH_PROVIDER environment variable. Pick one per deployment — there is no in-app sign-in picker.

GitHub-native (default)
GitHub sign-in · access by repo permission · writes committed to a private repo under each user's identity.
Azure-native (Entra ID)
Microsoft sign-in · access by security-group membership · writes recorded as versioned Azure Blob uploads.

Prerequisites

  • A Vercel account (Hobby works; Pro unlocks longer function timeouts)
  • An Azure Blob Storage account — required in both modes for the persistent OAuth client registry
  • Either a GitHub account (GitHub mode) or a Microsoft Entra tenant with admin access (Entra mode)
  • pnpm ≥ 9 and Node.js ≥ 20 locally, plus the Vercel CLI (pnpm add -g vercel)

Common setup (both modes)

Do these regardless of the mode you chose.

1. Fork, clone, and install

git clone https://github.com/<you>/skynest.git
cd skynest
pnpm install

2. Generate OAuth signing keys

Skynest issues RS256-signed JWTs for MCP OAuth. Generate the key pair and paste each full PEM block (including the -----BEGIN...----- lines) into Vercel as OAUTH_PRIVATE_KEY and OAUTH_PUBLIC_KEY:

pnpm oauth:gen-keypair

3. Create the Vercel project and connect Vercel Blob

  1. Import your fork at vercel.com/new (Next.js is auto-detected) — don't deploy yet.
  2. Open the project's Storage tab → Create Database → Blob, then Connect. This injects BLOB_READ_WRITE_TOKEN automatically — do not set it by hand.

4. Provision Azure Blob for the OAuth client registry

The dynamic OAuth client registry is stored in Azure Blob so registrations survive across serverless instances. This is required in both modes. Create a storage account and set one credential plus (optionally) the container name:

AZURE_STORAGE_ACCOUNT_URL=https://<account>.blob.core.windows.net
# or: AZURE_STORAGE_CONNECTION_STRING=<connection-string>
AZURE_BLOB_CONTAINER=skynest          # optional, defaults to "skynest"

5. Set the common environment variables

AUTH_SECRET=<random>                  # openssl rand -base64 32
NEXTAUTH_URL=https://<your-app>.vercel.app
OAUTH_PRIVATE_KEY=<from step 2>
OAUTH_PUBLIC_KEY=<from step 2>

# Primary document store (Vercel Blob)
CONTEXTNEST_STORAGE=blob
CONTEXTNEST_STORAGE_PROVIDER=vercel   # or "azure" to store documents in Azure too
CONTEXTNEST_BLOB_PREFIX=vault

The full, categorized list — including optional and webhook variables — is in the Environment variables section.

Mode-specific setup

Follow the block matching your chosen mode.

Mode A — GitHub-native

Users sign in with GitHub, access is decided by their permission on a vault repo, and every write is committed to that repo under their own identity.

A1. Create the vault repository

The vault repo is a private GitHub repository that stores a version-controlled copy of every document. Create it, then push any existing vault content:

bash scripts/init-vault.sh /path/to/your/vault https://github.com/<you>/my-vault.git

Starting fresh? Create an empty private repo — the vault initializes on first write. Add each team member as a collaborator so their tokens can commit and so they pass the access check.

A2. Register a GitHub OAuth App

Go to GitHub → Settings → Developer settings → OAuth Apps → New OAuth App.

Application nameSkynest
Homepage URLhttps://<your-app>.vercel.app
Authorization callback URLhttps://<your-app>.vercel.app/api/auth/callback/github

Note the Client ID and generate a Client Secret — these become GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET. The app requests the repo scope so the user's token can also commit vault writes.

A3. Set the GitHub-mode environment variables

In addition to the common variables, set:

AUTH_PROVIDER=github                 # default; may be omitted
GITHUB_CLIENT_ID=<oauth-app-client-id>
GITHUB_CLIENT_SECRET=<oauth-app-client-secret>

# Authorization: repo whose collaborator permission gates access
AUTHZ_GITHUB_REPO=<you>/my-vault      # push/admin → write, pull → read, none → 403

# Vault sync: commit every write to the repo
VAULT_SYNC_PROVIDER=github            # default in GitHub mode when VAULT_REPO is set
VAULT_REPO=<you>/my-vault
VAULT_BRANCH=main                     # optional

See the Environment variables reference for defaults and the full list.

Mode B — Azure-native (Microsoft Entra ID)

Users sign in with Microsoft Entra ID, access is decided by security-group membership, and every write is recorded as a versioned Azure Blob upload. Use this when your organization is Microsoft-native and GitHub identities aren't an option.

B1. Register an Entra app registration

In the Entra admin center → App registrations → New registration, then configure:

Redirect URI (Web)https://<your-app>.vercel.app/api/auth/callback/microsoft-entra-id
API permissionsMicrosoft Graph → GroupMember.Read.All (delegated)
Token configurationAdd the groups claim (security groups) to the ID token
Client secretCreate one under Certificates & secrets

Record the Directory (tenant) ID, Application (client) ID, and the client secret. Grant admin consent for the Graph permission so the group-membership fallback works.

B2. Create read / write security groups

In Entra → Groups, create two security groups — one for write access, one for read-only — and copy each group's Object ID. Add users to the appropriate group. Anyone in neither group is denied (403) at token exchange.

B3. Provision the Azure Blob vault-sync container

Create a storage account/container for the audit trail and enable blob versioning and soft-delete on it — the provider uploads a new version per write and a zero-byte tombstone version per delete, but it does not turn those features on itself.

Attribution in Azure mode is an app-asserted claim carried in blob metadata, not a cryptographically-signed commit the way GitHub mode is — an accepted tradeoff where no per-user write credential exists.

B4. Set the Entra-mode environment variables

AUTH_PROVIDER=entra
ENTRA_TENANT_ID=<directory-tenant-id>
ENTRA_CLIENT_ID=<application-client-id>
ENTRA_CLIENT_SECRET=<client-secret>

# Authorization: group membership decides access (comma-separated for multiple)
AUTHZ_ENTRA_WRITE_GROUP_ID=<write-group-object-id>
AUTHZ_ENTRA_READ_GROUP_ID=<read-group-object-id>

# Vault sync: record writes as Azure Blob versions
VAULT_SYNC_PROVIDER=azure
VAULT_AZURE_STORAGE_ACCOUNT_URL=https://<account>.blob.core.windows.net
# or: VAULT_AZURE_STORAGE_CONNECTION_STRING=<connection-string>
VAULT_AZURE_CONTAINER=skynest-vault-sync   # optional

VAULT_SYNC_PROVIDER=github is rejected in Entra mode — there is no repo-scoped user token to authenticate git commits. See the Environment variables reference for the full list.

Finish

6. Deploy

Push to your default branch — Vercel auto-deploys:

git push origin main

If you registered your OAuth App / Entra redirect URI before the final URL was known, update it now to the deployed URL and redeploy.

7. Verify

/Home page — confirms the app is running
/adminSign in to confirm OAuth works in your chosen mode
/api/mcpReturns 405 (no GET) — confirms the MCP route is live
/.well-known/oauth-protected-resourceLists the authorization server(s); shows the trusted issuer too if configured

Then connect an AI tool following Connecting your AI tool.

Environment variables

Every variable Skynest reads, grouped by subsystem. “Required” is conditional: GitHub mode and Entra mode mark variables needed only under that AUTH_PROVIDER, and auto-set variables are injected by the platform.

Note: Azure Blob (AZURE_STORAGE_*) backs the persistent OAuth client registry in both modes, so you need an Azure Blob account in production even when running GitHub-native.

Identity & sign-in

A single AUTH_PROVIDER selects the whole deployment’s identity mode — there is no dual sign-in picker.

VariableRequiredDescription
AUTH_PROVIDERoptionalIdentity mode: 'github' or 'entra'. Also drives authorization and the default vault-sync backend. Default: github.
GITHUB_CLIENT_IDGitHub modeGitHub OAuth App client ID. The app requests the repo scope so the user token doubles as the git-write credential.
GITHUB_CLIENT_SECRETGitHub modeGitHub OAuth App client secret.
ENTRA_TENANT_IDEntra modeEntra tenant ID. Builds the issuer https://login.microsoftonline.com/<tenant>/v2.0. Throws if unset in Entra mode.
ENTRA_CLIENT_IDEntra modeEntra app registration client ID for the Microsoft sign-in provider.
ENTRA_CLIENT_SECRETEntra modeEntra app registration client secret.
AUTH_SECRETrequiredNextAuth session/JWT encryption secret (openssl rand -base64 32). Read implicitly by NextAuth.
NEXTAUTH_URLoptionalProduction base URL for NextAuth. Skynest also derives the base URL per-request, so this is mainly a NextAuth hint.

Authorization (access control)

Decides whether a signed-in caller gets mcp:write, mcp:read, or a 403. See the Access control section.

VariableRequiredDescription
AUTHZ_GITHUB_REPOGitHub modeowner/repo whose GitHub collaborator permission decides access (push/admin → write, pull → read, none → 403). Usually the same as VAULT_REPO.
AUTHZ_ENTRA_WRITE_GROUP_IDEntra modeComma-separated Entra group object ID(s) whose members get mcp:read + mcp:write. Default: (none → no write).
AUTHZ_ENTRA_READ_GROUP_IDEntra modeComma-separated Entra group object ID(s) whose members get mcp:read only. Default: (none → no read).

OAuth token signing & registration

VariableRequiredDescription
OAUTH_PRIVATE_KEYrequiredRS256 private key (full PEM) used to sign authorization codes and access tokens. From pnpm oauth:gen-keypair.
OAUTH_PUBLIC_KEYrequiredRS256 public key (full PEM) used to verify self-issued tokens and published as JWKS.
ACCESS_TOKEN_TTL_SECONDSoptionalLifetime of issued OAuth access tokens. Default: 604800 (7 days).
OAUTH_REGISTRATION_SECREToptionalIf set, dynamic client registration requires a matching Bearer token. If unset, registration falls back to loopback / allowlisted-origin checks.
OAUTH_ALLOWED_REDIRECT_ORIGINSoptionalComma-separated HTTPS origins allowed as redirect URIs during registration (e.g. https://claude.ai for the Claude Desktop app). Default: '' (loopback only).

Trusted issuer (dual authorization server)

Optional. Leave both unset to keep self-issued-token-only behavior. Setting the issuer makes the audience mandatory (fails closed).

VariableRequiredDescription
MCP_TRUSTED_ISSUERoptionalExternal issuer URL (e.g. https://login.microsoftonline.com/<tenant>/v2.0). Tokens whose iss matches are verified against that tenant’s JWKS instead of the self-issued path.
MCP_TRUSTED_AUDIENCEoptionalExact aud expected on trusted-issuer tokens (the api://<app-id> Identifier URI). Required whenever MCP_TRUSTED_ISSUER is set.

Primary document store

Vercel Blob is the default cloud store; Azure Blob and local filesystem are the alternatives.

VariableRequiredDescription
CONTEXTNEST_STORAGEoptionalStorage mode: 'blob' (cloud) or 'fs' (local filesystem, for dev). Default: blob.
CONTEXTNEST_STORAGE_PROVIDERoptionalWhen storage=blob, the backend: 'vercel' (Vercel Blob) or 'azure' (Azure Blob). Default: vercel.
CONTEXTNEST_BLOB_PREFIXrequiredNamespace prefix for Vercel Blob objects (e.g. 'vault'). Required when provider=vercel.
BLOB_READ_WRITE_TOKENauto-setInjected automatically when a Vercel Blob store is connected. Do not set manually.
CONTEXTNEST_VAULT_PATHoptionalLocal filesystem vault path. Required only when CONTEXTNEST_STORAGE=fs (local dev).
CONTEXTNEST_DEFAULT_VAULT_IDoptionalVault ID used when a request does not specify one. Default: default.

Azure Blob — document store & OAuth client registry

The OAuth client registry is always Azure-backed, so one of these credentials is required in production in BOTH modes. The same account also serves the primary store when CONTEXTNEST_STORAGE_PROVIDER=azure. Distinct from the VAULT_AZURE_* sync vars below.

VariableRequiredDescription
AZURE_STORAGE_CONNECTION_STRINGrequiredAzure Blob connection string. Takes precedence over the account URL. One of this or AZURE_STORAGE_ACCOUNT_URL is required.
AZURE_STORAGE_ACCOUNT_URLrequiredAzure Blob account URL used with managed identity (DefaultAzureCredential). Alternative to the connection string.
AZURE_BLOB_CONTAINERoptionalContainer for documents and oauth-clients/<clientId>.json records. Default: skynest.

Vault sync (audit trail)

Records every write to a durable backend. All vars support a per-vault suffix override (e.g. VAULT_REPO_<VAULTID>).

VariableRequiredDescription
VAULT_SYNC_PROVIDERoptionalForce the sync backend. 'github' requires AUTH_PROVIDER=github (needs the repo-scoped user token). Default: computed ('github'/'azure'/'none').
VAULT_REPOGitHub modeowner/repo target for GitHub vault-sync commits. Required when the sync provider resolves to 'github'.
VAULT_BRANCHoptionalGit branch for vault-sync commits. Default: main.
VAULT_AZURE_STORAGE_CONNECTION_STRINGEntra modeAzure connection string for the Azure vault-sync mirror. One of this or the account URL is required when the sync provider is 'azure'.
VAULT_AZURE_STORAGE_ACCOUNT_URLEntra modeAzure account URL (managed identity) for the vault-sync mirror.
VAULT_AZURE_CONTAINERoptionalContainer name for the vault-sync mirror. Default: skynest-vault-sync.

Vault admin endpoint (init-from-git)

Only needed to use the /api/vault/init-from-git initialization endpoint.

VariableRequiredDescription
VAULT_ADMIN_TOKENoptionalBearer token compared against the Authorization header to authorize the headless vault-init flow.
VAULT_GITHUB_ADMIN_TOKENoptionalGitHub admin token used by init-from-git to read/clone the vault repo during initialization.

read.ai webhook

Only needed if you use the read.ai meeting-ingest webhook.

VariableRequiredDescription
WEBHOOK_API_KEYoptionalSecret embedded in the webhook URL path and compared against the [apikey] segment.
READ_AI_SIGNING_KEYoptionalHMAC-SHA256 key used to verify the read.ai webhook signature.
BOT_GITHUB_TOKENoptionalGitHub PAT (repo scope) the webhook bot uses to commit meeting notes (no logged-in user exists). GitHub-sync deployments only.
AI_GATEWAY_API_KEYoptionalVercel AI Gateway key for the Claude Haiku meeting classifier. Resolved automatically from the Vercel OIDC token when deployed; set it explicitly only off-Vercel or locally.

Local development & escape hatch

Never set MCP_AUTH_DISABLED in a production deployment. See Local development.

VariableRequiredDescription
MCP_AUTH_DISABLEDoptionalWhen 'true', bypasses ALL bearer-token verification and grants full mcp:read + mcp:write with a synthetic identity. Local/testing only.
MCP_AUTH_DISABLED_USERoptionalSynthetic user attributed to writes while auth is disabled. Default: auth-disabled@skynest.

The vendored Context Nest engine also recognizes a few internal variables (CONTEXTNEST_TELEMETRY_URL, PROMPTOWL_API_URL, DEBUG) used only by its CLI — none are needed to host Skynest.

Access control & authorization

Authenticating proves who you are; authorization decides what you can do. When an MCP client exchanges its authorization code for an access token, Skynest asks an AuthorizationProvider to grade the caller into one of three access levels and encodes the result as OAuth scopes on the token.

Access levelToken scopesWhat the caller can do
writemcp:read mcp:writeFull access — can read and run every write/governance tool.
readmcp:readRead-only — write and governance tools are rejected up front.
noneDenied — the token endpoint returns 403 access_denied and issues no token at all.

How the level is decided

The check runs against whichever identity mode is active (see Deploying Skynest):

  • GitHub mode — the caller's permission level on the repo named by AUTHZ_GITHUB_REPO is checked with their own GitHub token. Push/admin access grants write, pull access grants read, and no access is none.
  • Entra mode — group membership decides the level. Members of AUTHZ_ENTRA_WRITE_GROUP_ID get write, members of AUTHZ_ENTRA_READ_GROUP_ID get read, and everyone else is none. Membership is read from the ID token's groups claim, falling back to a Microsoft Graph checkMemberGroups call when the claim is absent (large group memberships get overaged out of the token). Each variable accepts a comma-separated list of group IDs.

Enforced twice

Scope is enforced at issuance and at the point of use. Even if a read-only token somehow reached a write path, all seven write tools (create_document, update_document, delete_document, publish_document, stage_drift_suggestion, approve_suggestion, reject_suggestion) re-check for mcp:write and reject the call before touching storage.

Trusted issuer (dual authorization server)

Skynest normally issues its own OAuth tokens. Optionally, it can also accept tokens issued directly by a trusted Entra tenant — for example an on-behalf-of token from a Copilot Studio connector — without changing the self-issued flow at all. Set MCP_TRUSTED_ISSUER and MCP_TRUSTED_AUDIENCE to enable it.

When set, an incoming token whose iss matches the trusted issuer is verified against that tenant's JWKS with an exact audience match, then run through the same AuthorizationProvider as above (its groups claim feeds the Entra check). Every other token — and all tokens when the vars are unset — takes the normal self-issued path unchanged. The .well-known/oauth-protected-resource document advertises the trusted issuer as a second entry in authorization_servers whenever it is configured.

Fail-closed: if MCP_TRUSTED_ISSUER is set but MCP_TRUSTED_AUDIENCE is left unset, external tokens are rejected rather than accepted with audience validation skipped.

Connecting your AI tool

Prerequisites

  • An account with the deployment's identity provider — a GitHub account (GitHub mode) or a Microsoft Entra account (Azure mode). You'll be prompted to authorize on first connection.
  • Vault access — ask your Skynest admin to grant you access: collaborator on the vault repository (GitHub mode) or membership in the read/write group (Entra mode). Without it, token exchange returns 403 and no token is issued.

Replace YOUR_SKYNEST_URL with your actual deployment URL in all examples below. To connect to a specific vault, append the vault ID to the URL: https://YOUR_SKYNEST_URL/api/mcp/YOUR_VAULT_ID. Omitting the vault ID uses the default vault configured on the server.

Claude Code CLI

claude mcp add --transport http skynest https://YOUR_SKYNEST_URL/api/mcp

Restart Claude Code. On first use you'll be prompted to sign in with GitHub.

Manual config alternative

Add to ~/.claude/mcp.json:

{
  "mcpServers": {
    "skynest": {
      "type": "http",
      "url": "https://YOUR_SKYNEST_URL/api/mcp"
    }
  }
}

Claude Desktop App

Admin setup required: Claude Desktop uses a web-based OAuth callback (https://claude.ai/...). Add OAUTH_ALLOWED_REDIRECT_ORIGINS=https://claude.ai to your Skynest deployment environment variables before connecting.

  1. Open the Claude Desktop app and go to Settings.
  2. Navigate to Integrations and click Add MCP Server.
  3. Enter:
    Name:      skynest
    URL:       https://YOUR_SKYNEST_URL/api/mcp
  4. Save. You'll be redirected to sign in with GitHub on first use.

Claude Code App (Desktop)

  1. Open Claude Code and go to Settings.
  2. Navigate to MCP Servers and click Add Server.
  3. Enter:
    Name:      skynest
    Transport: HTTP
    URL:       https://YOUR_SKYNEST_URL/api/mcp
  4. Save and restart. You'll be prompted to sign in with GitHub on first use.

Cursor

Add to .cursor/mcp.json (or the global Cursor MCP config):

{
  "mcpServers": {
    "skynest": {
      "url": "https://YOUR_SKYNEST_URL/api/mcp",
      "transport": "http"
    }
  }
}

Reload Cursor. On first use, a browser window will open for GitHub OAuth sign-in.

Other MCP-compatible tools

Skynest exposes a standard MCP HTTP endpoint with OAuth 2.1. Any tool that supports MCP over HTTP with OAuth 2.1 should work — use https://YOUR_SKYNEST_URL/api/mcp as the endpoint (or https://YOUR_SKYNEST_URL/api/mcp/YOUR_VAULT_ID for a specific vault).

Claude Code skill

A Claude Code skill is a Markdown file that Claude loads on demand to guide how it interacts with a specific tool or service. Creating a Skynest skill teaches Claude your vault's structure and gives it a repeatable protocol for querying and saving knowledge — so you don't have to re-explain it each session.

Create the skill file

Save the following template to ~/.claude/skills/skynest/skill.md (create the directory if it doesn't exist). Customise the description frontmatter and the vault structure section to match your own vault layout and tags.

---
name: skynest
description: Use when starting any session that involves your vault, when needing context on stored knowledge, when asked to remember something, or when completing work that should be persisted.
---

# Skynest — Persistent Knowledge Vault

## Overview

Your Skynest vault is a knowledge base accessible via MCP. It stores documents,
notes, processes, and any structured knowledge you choose to save.
**Query it at session start** when the work touches topics you've previously
documented. **Save significant learnings** before ending a session.

## MCP Connection

The vault is accessed via the **skynest** MCP server. All tools are prefixed
`mcp__skynest__*`.

Before calling any tool, load the schema via ToolSearch:
```
ToolSearch({ query: "select:mcp__skynest__vault_info" })
```

For bulk loading all skynest tools:
```
ToolSearch({ query: "skynest", max_results: 30 })
```

## Session Start Protocol

At the start of any substantive session, run at minimum:

```
mcp__skynest__vault_info          → confirm connection, orient to vault identity
mcp__skynest__search(query)       → find documents relevant to the current task
```

## Selector Syntax (for `resolve` tool)

| Pattern        | Meaning                     | Example               |
|----------------|-----------------------------|-----------------------|
| `#tag`         | Documents with this tag     | `#project`            |
| `#tag1 + #tag2`| AND — must have both        | `#project + #active`  |
| `#tag1 | #tag2`| OR — has either             | `#project | #process` |
| `type:document`| Filter by node type         | `type:document`       |

**Hops** (graph traversal depth): default 2.
Use `hops: 1` for fast single-doc lookups; `hops: 3` for deep context on
interconnected topics.

## Common Operations

```javascript
// Search by topic
mcp__skynest__search({ query: "your topic here", hops: 2 })

// Find by tag
mcp__skynest__resolve({ selector: "#project + #active", hops: 2 })

// Read a specific document
mcp__skynest__read_document({ uri: "nodes/your/path" })

// List all documents
mcp__skynest__list_documents({ type: "document", status: "published" })
```

## Saving Knowledge

Use this when asked to remember something, or when you complete work with
lasting value:

```
1. mcp__skynest__create_document({ path, title, type, tags, body })
2. mcp__skynest__update_document({ path, body })   ← if document already exists
3. mcp__skynest__publish_document({ path, author: "claude@claude.ai", note: "..." })
```

**Document types:** `document`, `snippet`, `glossary`, `persona`, `prompt`,
`source`, `tool`, `reference`, `skill`

**Always publish after creating/updating** — drafts are not visible to AI agents.

## Quick Reference

| Goal              | Tool                              | Key Params                          |
|-------------------|-----------------------------------|-------------------------------------|
| Orient to vault   | `mcp__skynest__vault_info`        | —                                   |
| Search by topic   | `mcp__skynest__search`            | `query`, `hops`                     |
| Filter by tag     | `mcp__skynest__resolve`           | `selector`, `hops`                  |
| Read one doc      | `mcp__skynest__read_document`     | `uri`                               |
| Read a pack       | `mcp__skynest__read_pack`         | `id`, `hops`                        |
| List documents    | `mcp__skynest__list_documents`    | `type`, `tag`, `status`             |
| Create new        | `mcp__skynest__create_document`   | `path`, `title`, `type`, `tags`, `body` |
| Update existing   | `mcp__skynest__update_document`   | `path`, `body`                      |
| Publish/version   | `mcp__skynest__publish_document`  | `path`, `note`                      |
| Audit trail       | `mcp__skynest__verify_integrity`  | —                                   |

## Common Mistakes

- **Not publishing after saving**: Always call `publish_document` after
  create/update or the content won't be visible to AI agents.
- **Wrong selector syntax**: Tags use a `#` prefix (`#project`, not `tag:project`).
- **Skipping vault at session start**: Query first — don't assume you know the
  current state of your documents.
- **Saving code implementations**: The vault is for knowledge, not source code;
  those belong in git.

Activate the skill

Once the file is in place, invoke the skill by typing /skynest in Claude Code. Claude will load the skill and follow its protocol for the rest of the session.

You can also configure the skill's description so that Claude activates it automatically when the session context matches — for example, whenever you start a session related to a project or topic your vault covers.

Customising the template

  • Update the vault structure section with your actual nodes/ paths and tags once you've organised your vault.
  • Add workflow packs if you create named packs in your vault — call them with mcp__skynest__read_pack({ id: "pack-name", hops: 2 }).
  • Extend the common queries section with tag combinations specific to your domain.

Available MCP tools

All tools from the upstream Context Nest MCP server are available over HTTPS. Write tools require a token carrying the mcp:write scope (see Access control) and record each change to the configured vault-sync backend under the authenticated user's identity.

Read tools

vault_infoGet vault identity (CONTEXT.md) and configuration summary
resolveExecute a selector query with graph traversal (e.g. '#engineering + type:document')
read_documentRead a document by URI (contextnest://nodes/foo) or path (nodes/foo)
list_documentsList documents with optional type, status, and tag filters
document_formatGet the document format spec and frontmatter fields — call before creating docs
read_indexReturn the context.yaml document graph index
read_packResolve and return a context pack with documents and agent instructions
searchFull-text search across vault documents with graph traversal
verify_integrityVerify all hash chains in the vault (tamper detection)
list_checkpointsList recent vault checkpoints
read_versionRead a specific historical version of a document

Write tools — require the mcp:write scope; each write is recorded to the vault-sync backend under your identity

create_documentCreate a new document. Supports all node types including skill nodes.
update_documentUpdate a document's title, tags, status, or body
delete_documentDelete a document and its version history
publish_documentExplicitly publish a document: bump version, compute checksum, create checkpoint

Governance tools

Capture and resolve out-of-band edits through a review workflow without breaking the hash chain.

stage_drift_suggestionCapture an out-of-band edit as a staged suggestion without modifying the canonical document
list_suggestionsList all staged suggestions for a document
approve_suggestionApprove a suggestion: apply the patch, bump version, archive the suggestion
reject_suggestionReject a suggestion: archive without modifying the canonical document

Selector syntax (used by resolve)

#tagDocuments with this tag
#tag1 + #tag2Must have both tags (AND)
#tag1 | #tag2Has either tag (OR)
type:documentFilter by node type
status:publishedFilter by status
pack:nameAll documents in a pack

read.ai Webhook

Skynest can automatically ingest meeting transcripts from read.ai when a meeting ends. The webhook uses HMAC-SHA256 signature verification and deduplication by request_id. A Claude Haiku model classifies each meeting (client, tags, summary, action items) before writing a structured node to the vault.

Required environment variables

WEBHOOK_API_KEYSecret key embedded in the webhook URL path
READ_AI_SIGNING_KEYHMAC signing key from the read.ai dashboard
BOT_GITHUB_TOKENGitHub PAT with repo scope for vault commits — GitHub-sync deployments only (the webhook has no logged-in user)
AI_GATEWAY_API_KEYVercel AI Gateway key for the Claude Haiku classifier — resolved from the Vercel OIDC token automatically when deployed

Webhook URL

Configure this URL in your read.ai workspace settings:

https://YOUR_SKYNEST_URL/api/webhooks/YOUR_WEBHOOK_API_KEY/default/readai

Replace YOUR_WEBHOOK_API_KEY with the value of your WEBHOOK_API_KEY environment variable. Replace default with your vault ID if you're using a named vault (CONTEXTNEST_DEFAULT_VAULT_ID).

Set the signing key in read.ai to the value of READ_AI_SIGNING_KEY.

What happens on each meeting

  1. read.ai POSTs the transcript payload to the webhook URL
  2. Skynest verifies the HMAC-SHA256 signature using READ_AI_SIGNING_KEY
  3. Deduplication check by request_id (returns 200 immediately if duplicate)
  4. Claude Haiku analyzes the meeting: client classification, tags, summary, action items
  5. A structured markdown node is written to nodes/meetings/YYYY-MM-DD-slug.md
  6. The file is recorded to the configured vault-sync backend under the bot's identity (after the response is sent)
Note: If the vault write fails, Skynest returns a 500 so read.ai will retry. The vault-sync write runs asynchronously after the response — a sync failure is logged but does not affect the response.

Local development

# Install dependencies
pnpm install

# Build the vendored engine
pnpm --filter @promptowl/contextnest-engine build

# Copy .env.example and fill in values
cp .env.example .env.local

# Start the dev server
pnpm dev

The app runs at http://localhost:3000. The MCP endpoint is at http://localhost:3000/api/mcp.

For local dev, use the filesystem storage backend by setting these in .env.local:

# Use a local vault directory instead of Vercel Blob
CONTEXTNEST_STORAGE=fs
CONTEXTNEST_VAULT_PATH=/path/to/your/vault

# Disable git sync
VAULT_SYNC_PROVIDER=none

Bypassing auth locally

To exercise the MCP endpoint without wiring up a full OAuth provider, set MCP_AUTH_DISABLED=true. This skips all bearer-token verification, issues codes without a real sign-in, and grants full mcp:read mcp:write access under a synthetic identity (override the attributed user with MCP_AUTH_DISABLED_USER). It is a local/testing escape hatch only — never set it in a production deployment, as it disables every access control described above.

Other useful commands

pnpm testRun the test suite
pnpm test:watchRun tests in watch mode
pnpm lintType-check without emitting
pnpm oauth:gen-keypairGenerate RS256 key pair for OAuth JWT signing
pnpm test:webhookSend a test payload to the read.ai webhook