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.
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.
mcp-handler · @vercel/blob · NextAuth v5 (GitHub provider) · jose (RS256 JWTs) · zodDeploying 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.
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 install2. 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-keypair3. Create the Vercel project and connect Vercel Blob
- Import your fork at vercel.com/new (Next.js is auto-detected) — don't deploy yet.
- Open the project's Storage tab → Create Database → Blob, then Connect. This injects
BLOB_READ_WRITE_TOKENautomatically — 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=vaultThe 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.gitStarting 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 name | Skynest |
| Homepage URL | https://<your-app>.vercel.app |
| Authorization callback URL | https://<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 # optionalSee 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 permissions | Microsoft Graph → GroupMember.Read.All (delegated) |
| Token configuration | Add the groups claim (security groups) to the ID token |
| Client secret | Create 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.
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 # optionalVAULT_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 mainIf 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 |
| /admin | Sign in to confirm OAuth works in your chosen mode |
| /api/mcp | Returns 405 (no GET) — confirms the MCP route is live |
| /.well-known/oauth-protected-resource | Lists 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.
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.
| Variable | Required | Description |
|---|---|---|
| AUTH_PROVIDER | optional | Identity mode: 'github' or 'entra'. Also drives authorization and the default vault-sync backend. Default: github. |
| GITHUB_CLIENT_ID | GitHub mode | GitHub OAuth App client ID. The app requests the repo scope so the user token doubles as the git-write credential. |
| GITHUB_CLIENT_SECRET | GitHub mode | GitHub OAuth App client secret. |
| ENTRA_TENANT_ID | Entra mode | Entra tenant ID. Builds the issuer https://login.microsoftonline.com/<tenant>/v2.0. Throws if unset in Entra mode. |
| ENTRA_CLIENT_ID | Entra mode | Entra app registration client ID for the Microsoft sign-in provider. |
| ENTRA_CLIENT_SECRET | Entra mode | Entra app registration client secret. |
| AUTH_SECRET | required | NextAuth session/JWT encryption secret (openssl rand -base64 32). Read implicitly by NextAuth. |
| NEXTAUTH_URL | optional | Production 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.
| Variable | Required | Description |
|---|---|---|
| AUTHZ_GITHUB_REPO | GitHub mode | owner/repo whose GitHub collaborator permission decides access (push/admin → write, pull → read, none → 403). Usually the same as VAULT_REPO. |
| AUTHZ_ENTRA_WRITE_GROUP_ID | Entra mode | Comma-separated Entra group object ID(s) whose members get mcp:read + mcp:write. Default: (none → no write). |
| AUTHZ_ENTRA_READ_GROUP_ID | Entra mode | Comma-separated Entra group object ID(s) whose members get mcp:read only. Default: (none → no read). |
OAuth token signing & registration
| Variable | Required | Description |
|---|---|---|
| OAUTH_PRIVATE_KEY | required | RS256 private key (full PEM) used to sign authorization codes and access tokens. From pnpm oauth:gen-keypair. |
| OAUTH_PUBLIC_KEY | required | RS256 public key (full PEM) used to verify self-issued tokens and published as JWKS. |
| ACCESS_TOKEN_TTL_SECONDS | optional | Lifetime of issued OAuth access tokens. Default: 604800 (7 days). |
| OAUTH_REGISTRATION_SECRET | optional | If set, dynamic client registration requires a matching Bearer token. If unset, registration falls back to loopback / allowlisted-origin checks. |
| OAUTH_ALLOWED_REDIRECT_ORIGINS | optional | Comma-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).
| Variable | Required | Description |
|---|---|---|
| MCP_TRUSTED_ISSUER | optional | External 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_AUDIENCE | optional | Exact 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.
| Variable | Required | Description |
|---|---|---|
| CONTEXTNEST_STORAGE | optional | Storage mode: 'blob' (cloud) or 'fs' (local filesystem, for dev). Default: blob. |
| CONTEXTNEST_STORAGE_PROVIDER | optional | When storage=blob, the backend: 'vercel' (Vercel Blob) or 'azure' (Azure Blob). Default: vercel. |
| CONTEXTNEST_BLOB_PREFIX | required | Namespace prefix for Vercel Blob objects (e.g. 'vault'). Required when provider=vercel. |
| BLOB_READ_WRITE_TOKEN | auto-set | Injected automatically when a Vercel Blob store is connected. Do not set manually. |
| CONTEXTNEST_VAULT_PATH | optional | Local filesystem vault path. Required only when CONTEXTNEST_STORAGE=fs (local dev). |
| CONTEXTNEST_DEFAULT_VAULT_ID | optional | Vault 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.
| Variable | Required | Description |
|---|---|---|
| AZURE_STORAGE_CONNECTION_STRING | required | Azure Blob connection string. Takes precedence over the account URL. One of this or AZURE_STORAGE_ACCOUNT_URL is required. |
| AZURE_STORAGE_ACCOUNT_URL | required | Azure Blob account URL used with managed identity (DefaultAzureCredential). Alternative to the connection string. |
| AZURE_BLOB_CONTAINER | optional | Container 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>).
| Variable | Required | Description |
|---|---|---|
| VAULT_SYNC_PROVIDER | optional | Force the sync backend. 'github' requires AUTH_PROVIDER=github (needs the repo-scoped user token). Default: computed ('github'/'azure'/'none'). |
| VAULT_REPO | GitHub mode | owner/repo target for GitHub vault-sync commits. Required when the sync provider resolves to 'github'. |
| VAULT_BRANCH | optional | Git branch for vault-sync commits. Default: main. |
| VAULT_AZURE_STORAGE_CONNECTION_STRING | Entra mode | Azure 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_URL | Entra mode | Azure account URL (managed identity) for the vault-sync mirror. |
| VAULT_AZURE_CONTAINER | optional | Container 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.
| Variable | Required | Description |
|---|---|---|
| VAULT_ADMIN_TOKEN | optional | Bearer token compared against the Authorization header to authorize the headless vault-init flow. |
| VAULT_GITHUB_ADMIN_TOKEN | optional | GitHub 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.
| Variable | Required | Description |
|---|---|---|
| WEBHOOK_API_KEY | optional | Secret embedded in the webhook URL path and compared against the [apikey] segment. |
| READ_AI_SIGNING_KEY | optional | HMAC-SHA256 key used to verify the read.ai webhook signature. |
| BOT_GITHUB_TOKEN | optional | GitHub PAT (repo scope) the webhook bot uses to commit meeting notes (no logged-in user exists). GitHub-sync deployments only. |
| AI_GATEWAY_API_KEY | optional | Vercel 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.
| Variable | Required | Description |
|---|---|---|
| MCP_AUTH_DISABLED | optional | When 'true', bypasses ALL bearer-token verification and grants full mcp:read + mcp:write with a synthetic identity. Local/testing only. |
| MCP_AUTH_DISABLED_USER | optional | Synthetic 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 level | Token scopes | What the caller can do |
|---|---|---|
| write | mcp:read mcp:write | Full access — can read and run every write/governance tool. |
| read | mcp:read | Read-only — write and governance tools are rejected up front. |
| none | — | Denied — 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_REPOis 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_IDget write, members ofAUTHZ_ENTRA_READ_GROUP_IDget read, and everyone else is none. Membership is read from the ID token'sgroupsclaim, falling back to a Microsoft GraphcheckMemberGroupscall 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.
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/mcpRestart 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.
- Open the Claude Desktop app and go to Settings.
- Navigate to Integrations and click Add MCP Server.
- Enter:
Name: skynest URL: https://YOUR_SKYNEST_URL/api/mcp - Save. You'll be redirected to sign in with GitHub on first use.
Claude Code App (Desktop)
- Open Claude Code and go to Settings.
- Navigate to MCP Servers and click Add Server.
- Enter:
Name: skynest Transport: HTTP URL: https://YOUR_SKYNEST_URL/api/mcp - 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_info | Get vault identity (CONTEXT.md) and configuration summary |
| resolve | Execute a selector query with graph traversal (e.g. '#engineering + type:document') |
| read_document | Read a document by URI (contextnest://nodes/foo) or path (nodes/foo) |
| list_documents | List documents with optional type, status, and tag filters |
| document_format | Get the document format spec and frontmatter fields — call before creating docs |
| read_index | Return the context.yaml document graph index |
| read_pack | Resolve and return a context pack with documents and agent instructions |
| search | Full-text search across vault documents with graph traversal |
| verify_integrity | Verify all hash chains in the vault (tamper detection) |
| list_checkpoints | List recent vault checkpoints |
| read_version | Read 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_document | Create a new document. Supports all node types including skill nodes. |
| update_document | Update a document's title, tags, status, or body |
| delete_document | Delete a document and its version history |
| publish_document | Explicitly 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_suggestion | Capture an out-of-band edit as a staged suggestion without modifying the canonical document |
| list_suggestions | List all staged suggestions for a document |
| approve_suggestion | Approve a suggestion: apply the patch, bump version, archive the suggestion |
| reject_suggestion | Reject a suggestion: archive without modifying the canonical document |
Selector syntax (used by resolve)
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_KEY | Secret key embedded in the webhook URL path |
| READ_AI_SIGNING_KEY | HMAC signing key from the read.ai dashboard |
| BOT_GITHUB_TOKEN | GitHub PAT with repo scope for vault commits — GitHub-sync deployments only (the webhook has no logged-in user) |
| AI_GATEWAY_API_KEY | Vercel 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/readaiReplace 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
- read.ai POSTs the transcript payload to the webhook URL
- Skynest verifies the HMAC-SHA256 signature using
READ_AI_SIGNING_KEY - Deduplication check by
request_id(returns 200 immediately if duplicate) - Claude Haiku analyzes the meeting: client classification, tags, summary, action items
- A structured markdown node is written to
nodes/meetings/YYYY-MM-DD-slug.md - The file is recorded to the configured vault-sync backend under the bot's identity (after the response is sent)
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 devThe 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=noneBypassing 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 test | Run the test suite |
| pnpm test:watch | Run tests in watch mode |
| pnpm lint | Type-check without emitting |
| pnpm oauth:gen-keypair | Generate RS256 key pair for OAuth JWT signing |
| pnpm test:webhook | Send a test payload to the read.ai webhook |
