mirror of
https://github.com/aljazceru/contextvm-docs.git
synced 2025-12-19 15:04:24 +01:00
init
This commit is contained in:
@@ -4,16 +4,66 @@ import starlight from '@astrojs/starlight';
|
|||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
site: 'https://contextvm.github.io',
|
||||||
|
base: '/',
|
||||||
integrations: [
|
integrations: [
|
||||||
starlight({
|
starlight({
|
||||||
title: 'My Docs',
|
title: 'ContextVM Documentation',
|
||||||
social: [{ icon: 'github', label: 'GitHub', href: 'https://github.com/withastro/starlight' }],
|
description: 'Documentation for the ContextVM SDK - a virtual machine for context-controlled computation',
|
||||||
|
logo: {
|
||||||
|
light: './src/assets/contextvm-logo.svg',
|
||||||
|
dark: './src/assets/contextvm-logo.svg',
|
||||||
|
replacesTitle: false,
|
||||||
|
},
|
||||||
|
social: [
|
||||||
|
{ icon: 'github', label: 'ContextVM', href: 'https://github.com/contextvm/sdk' },
|
||||||
|
{ icon: 'twitter', label: 'Twitter', href: 'https://twitter.com/contextvm' },
|
||||||
|
],
|
||||||
|
editLink: {
|
||||||
|
baseUrl: 'https://github.com/contextvm/sdk/edit/main/docs/',
|
||||||
|
},
|
||||||
sidebar: [
|
sidebar: [
|
||||||
{
|
{
|
||||||
label: 'Guides',
|
label: 'Getting Started',
|
||||||
items: [
|
items: [
|
||||||
// Each item here is one entry in the navigation menu.
|
{ label: 'Quick Overview', slug: 'guides/getting-started/quick-overview' },
|
||||||
{ label: 'Example Guide', slug: 'guides/example' },
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Specification',
|
||||||
|
items: [
|
||||||
|
{ label: 'Specification', slug: 'guides/ctxvm-draft-spec' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Core Concepts',
|
||||||
|
items: [
|
||||||
|
{ label: 'Constants', slug: 'guides/core/constants' },
|
||||||
|
{ label: 'Interfaces', slug: 'guides/core/interfaces' },
|
||||||
|
{ label: 'Encryption', slug: 'guides/core/encryption' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Components',
|
||||||
|
items: [
|
||||||
|
{ label: 'Gateway', slug: 'guides/gateway/overview' },
|
||||||
|
{ label: 'Relay', slug: 'guides/relay/simple-relay-pool' },
|
||||||
|
{ label: 'Signer', slug: 'guides/signer/private-key-signer' },
|
||||||
|
{ label: 'Proxy', slug: 'guides/proxy/overview' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Tutorials',
|
||||||
|
items: [
|
||||||
|
{ label: 'Client-Server Communication', slug: 'guides/tutorials/client-server-communication' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Transports',
|
||||||
|
items: [
|
||||||
|
{ label: 'Base Nostr Transport', slug: 'guides/transports/base-nostr-transport' },
|
||||||
|
{ label: 'Nostr Client', slug: 'guides/transports/nostr-client-transport' },
|
||||||
|
{ label: 'Nostr Server', slug: 'guides/transports/nostr-server-transport' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
474
docs/customization.md
Normal file
474
docs/customization.md
Normal file
@@ -0,0 +1,474 @@
|
|||||||
|
---
|
||||||
|
title: Customizing Starlight
|
||||||
|
description: Learn how to make your Starlight site your own with your logo, custom fonts, landing page design and more.
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Tabs, TabItem, FileTree, Steps } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
Starlight provides sensible default styling and features, so you can get started quickly with no configuration needed.
|
||||||
|
When you want to start customizing the look and feel of your Starlight site, this guide has you covered.
|
||||||
|
|
||||||
|
## Add your logo
|
||||||
|
|
||||||
|
Adding a custom logo to the site header is a quick way to add your individual branding to a Starlight site.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. Add your logo image file to the `src/assets/` directory:
|
||||||
|
|
||||||
|
<FileTree>
|
||||||
|
|
||||||
|
- src/
|
||||||
|
- assets/
|
||||||
|
- **my-logo.svg**
|
||||||
|
- content/
|
||||||
|
- astro.config.mjs
|
||||||
|
|
||||||
|
</FileTree>
|
||||||
|
|
||||||
|
2. Add the path to your logo as Starlight’s [`logo.src`](/reference/configuration/#logo) option in `astro.config.mjs`:
|
||||||
|
|
||||||
|
```diff lang="js"
|
||||||
|
// astro.config.mjs
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import starlight from '@astrojs/starlight';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
integrations: [
|
||||||
|
starlight({
|
||||||
|
title: 'Docs With My Logo',
|
||||||
|
logo: {
|
||||||
|
+ src: './src/assets/my-logo.svg',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
By default, the logo will be displayed alongside your site’s `title`.
|
||||||
|
If your logo image already includes the site title, you can visually hide the title text by setting the `replacesTitle` option.
|
||||||
|
The `title` text will still be included for screen readers so that the header remains accessible.
|
||||||
|
|
||||||
|
```js {5}
|
||||||
|
starlight({
|
||||||
|
title: 'Docs With My Logo',
|
||||||
|
logo: {
|
||||||
|
src: './src/assets/my-logo.svg',
|
||||||
|
replacesTitle: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
```
|
||||||
|
|
||||||
|
### Light and dark logo variants
|
||||||
|
|
||||||
|
You can display different versions of your logo in light and dark modes.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. Add an image file for each variant to `src/assets/`:
|
||||||
|
|
||||||
|
<FileTree>
|
||||||
|
|
||||||
|
- src/
|
||||||
|
- assets/
|
||||||
|
- **light-logo.svg**
|
||||||
|
- **dark-logo.svg**
|
||||||
|
- content/
|
||||||
|
- astro.config.mjs
|
||||||
|
|
||||||
|
</FileTree>
|
||||||
|
|
||||||
|
2. Add the path to your logo variants as the `light` and `dark` options instead of `src` in `astro.config.mjs`:
|
||||||
|
|
||||||
|
```diff lang="js"
|
||||||
|
starlight({
|
||||||
|
title: 'Docs With My Logo',
|
||||||
|
logo: {
|
||||||
|
+ light: './src/assets/light-logo.svg',
|
||||||
|
+ dark: './src/assets/dark-logo.svg',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Enable sitemap
|
||||||
|
|
||||||
|
Starlight has built-in support for generating a sitemap. Enable sitemap generation by setting your URL as `site` in `astro.config.mjs`:
|
||||||
|
|
||||||
|
```js {4}
|
||||||
|
// astro.config.mjs
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
site: 'https://stargazers.club',
|
||||||
|
integrations: [starlight({ title: 'Site with sitemap' })],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Learn how to [add a sitemap link to `robots.txt`](https://docs.astro.build/en/guides/integrations-guide/sitemap/#sitemap-link-in-robotstxt) in the Astro Docs.
|
||||||
|
|
||||||
|
## Page layout
|
||||||
|
|
||||||
|
By default, Starlight pages use a layout with a global navigation sidebar and a table of contents that shows the current page headings.
|
||||||
|
|
||||||
|
You can apply a wider page layout without sidebars by setting [`template: splash`](/reference/frontmatter/#template) in a page’s frontmatter.
|
||||||
|
This works particularly well for landing pages and you can see it in action on the [homepage of this site](/).
|
||||||
|
|
||||||
|
```md {5}
|
||||||
|
---
|
||||||
|
# src/content/docs/index.md
|
||||||
|
|
||||||
|
title: My Landing Page
|
||||||
|
template: splash
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
Starlight displays a table of contents on each page to make it easier for readers to jump to the heading they are looking for.
|
||||||
|
You can customize — or even disable — the table of contents globally in the Starlight integration or on a page-by-page basis in frontmatter.
|
||||||
|
|
||||||
|
By default, `<h2>` and `<h3>` headings are included in the table of contents. Change which headings levels to include site-wide using the `minHeadingLevel` and `maxHeadingLevel` options in your [global `tableOfContents`](/reference/configuration/#tableofcontents). Override these defaults on an individual page by adding the corresponding [frontmatter `tableOfContents`](/reference/frontmatter/#tableofcontents) properties:
|
||||||
|
|
||||||
|
<Tabs syncKey="config-type">
|
||||||
|
<TabItem label="Frontmatter">
|
||||||
|
|
||||||
|
```md {4-6}
|
||||||
|
---
|
||||||
|
# src/content/docs/example.md
|
||||||
|
title: Page with only H2s in the table of contents
|
||||||
|
tableOfContents:
|
||||||
|
minHeadingLevel: 2
|
||||||
|
maxHeadingLevel: 2
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Global config">
|
||||||
|
|
||||||
|
```js {7}
|
||||||
|
// astro.config.mjs
|
||||||
|
|
||||||
|
defineConfig({
|
||||||
|
integrations: [
|
||||||
|
starlight({
|
||||||
|
title: 'Docs with custom table of contents config',
|
||||||
|
tableOfContents: { minHeadingLevel: 2, maxHeadingLevel: 2 },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
Disable the table of contents entirely by setting the `tableOfContents` option to `false`:
|
||||||
|
|
||||||
|
<Tabs syncKey="config-type">
|
||||||
|
<TabItem label="Frontmatter">
|
||||||
|
|
||||||
|
```md {4}
|
||||||
|
---
|
||||||
|
# src/content/docs/example.md
|
||||||
|
title: Page without a table of contents
|
||||||
|
tableOfContents: false
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Global config">
|
||||||
|
|
||||||
|
```js {7}
|
||||||
|
// astro.config.mjs
|
||||||
|
|
||||||
|
defineConfig({
|
||||||
|
integrations: [
|
||||||
|
starlight({
|
||||||
|
title: 'Docs with table of contents disabled globally',
|
||||||
|
tableOfContents: false,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
## Social links
|
||||||
|
|
||||||
|
Starlight has built-in support for adding links to your social media accounts to the site header via the [`social`](/reference/configuration/#social) option in the Starlight integration.
|
||||||
|
|
||||||
|
Each entry in the `social` array must be an object with three properties:
|
||||||
|
|
||||||
|
- `icon`: one of Starlight’s [built-in icons](/reference/icons/), e.g. `"github"`.
|
||||||
|
- `label`: an accessible label for the link, e.g. `"GitHub"`.
|
||||||
|
- `href`: the URL for the link, e.g. `"https://github.com/withastro/starlight"`.
|
||||||
|
|
||||||
|
The following example adds links to the Astro Discord chat and the Starlight GitHub repository:
|
||||||
|
|
||||||
|
```js {9-16}
|
||||||
|
// astro.config.mjs
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import starlight from '@astrojs/starlight';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
integrations: [
|
||||||
|
starlight({
|
||||||
|
title: 'Docs With Social Links',
|
||||||
|
social: [
|
||||||
|
{ icon: 'discord', label: 'Discord', href: 'https://astro.build/chat' },
|
||||||
|
{
|
||||||
|
icon: 'github',
|
||||||
|
label: 'GitHub',
|
||||||
|
href: 'https://github.com/withastro/starlight',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Edit links
|
||||||
|
|
||||||
|
Starlight can display an “Edit page” link in each page’s footer.
|
||||||
|
This makes it easy for a reader to find the file to edit to improve your docs.
|
||||||
|
For open-source projects in particular, this can help encourage contributions from your community.
|
||||||
|
|
||||||
|
To enable edit links, set [`editLink.baseUrl`](/reference/configuration/#editlink) to the URL used to edit your repository in the Starlight integration config.
|
||||||
|
The value of `editLink.baseUrl` will be prepended to the path to the current page to form the full edit link.
|
||||||
|
|
||||||
|
Common patterns include:
|
||||||
|
|
||||||
|
- GitHub: `https://github.com/USER_NAME/REPO_NAME/edit/BRANCH_NAME/`
|
||||||
|
- GitLab: `https://gitlab.com/USER_NAME/REPO_NAME/-/edit/BRANCH_NAME/`
|
||||||
|
|
||||||
|
If your Starlight project is not in the root of your repository, include the path to the project at the end of the base URL.
|
||||||
|
|
||||||
|
This example shows the edit link configured for the Starlight docs, which live in the `docs/` subdirectory on the `main` branch of the `withastro/starlight` repository on GitHub:
|
||||||
|
|
||||||
|
```js {9-11}
|
||||||
|
// astro.config.mjs
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import starlight from '@astrojs/starlight';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
integrations: [
|
||||||
|
starlight({
|
||||||
|
title: 'Docs With Edit Links',
|
||||||
|
editLink: {
|
||||||
|
baseUrl: 'https://github.com/withastro/starlight/edit/main/docs/',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom 404 page
|
||||||
|
|
||||||
|
Starlight sites display a simple 404 page by default.
|
||||||
|
You can customize this by adding a `404.md` (or `404.mdx`) file to your `src/content/docs/` directory:
|
||||||
|
|
||||||
|
<FileTree>
|
||||||
|
|
||||||
|
- src/
|
||||||
|
- content/
|
||||||
|
- docs/
|
||||||
|
- **404.md**
|
||||||
|
- index.md
|
||||||
|
- astro.config.mjs
|
||||||
|
|
||||||
|
</FileTree>
|
||||||
|
|
||||||
|
You can use all of Starlight’s page layout and customization techniques in your 404 page. For example, the default 404 page uses the [`splash` template](#page-layout) and [`hero`](/reference/frontmatter/#hero) component in frontmatter:
|
||||||
|
|
||||||
|
```md {4,6-8}
|
||||||
|
---
|
||||||
|
# src/content/docs/404.md
|
||||||
|
title: '404'
|
||||||
|
template: splash
|
||||||
|
editUrl: false
|
||||||
|
hero:
|
||||||
|
title: '404'
|
||||||
|
tagline: Page not found. Check the URL or try using the search bar.
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disabling the default 404 page
|
||||||
|
|
||||||
|
If your project requires an entirely customized 404 layout, you can create a `src/pages/404.astro` route and set the [`disable404Route`](/reference/configuration/#disable404route) config option to disable Starlight’s default route:
|
||||||
|
|
||||||
|
```js {9}
|
||||||
|
// astro.config.mjs
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import starlight from '@astrojs/starlight';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
integrations: [
|
||||||
|
starlight({
|
||||||
|
title: 'Docs With Custom 404',
|
||||||
|
disable404Route: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom fonts
|
||||||
|
|
||||||
|
By default, Starlight uses sans-serif fonts available on a user’s local device for all text.
|
||||||
|
This ensures documentation loads quickly in a font that is familiar to each user, without requiring extra bandwidth to download large font files.
|
||||||
|
|
||||||
|
If you must add a custom font to your Starlight site, you can set up fonts to use in custom CSS files or with any other [Astro styling technique](https://docs.astro.build/en/guides/styling/).
|
||||||
|
|
||||||
|
### Set up fonts
|
||||||
|
|
||||||
|
If you already have font files, follow the [local set-up guide](#set-up-local-font-files).
|
||||||
|
To use Google Fonts, follow the [Fontsource set-up guide](#set-up-a-fontsource-font).
|
||||||
|
|
||||||
|
#### Set up local font files
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. Add your font files to a `src/fonts/` directory and create an empty `font-face.css` file:
|
||||||
|
|
||||||
|
<FileTree>
|
||||||
|
|
||||||
|
- src/
|
||||||
|
- content/
|
||||||
|
- fonts/
|
||||||
|
- **CustomFont.woff2**
|
||||||
|
- **font-face.css**
|
||||||
|
- astro.config.mjs
|
||||||
|
|
||||||
|
</FileTree>
|
||||||
|
|
||||||
|
2. Add an [`@font-face` declaration](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face) for each of your fonts in `src/fonts/font-face.css`.
|
||||||
|
Use a relative path to the font file in the `url()` function.
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* src/fonts/font-face.css */
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Custom Font';
|
||||||
|
/* Use a relative path to the local font file in `url()`. */
|
||||||
|
src: url('./CustomFont.woff2') format('woff2');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Add the path to your `font-face.css` file to Starlight’s `customCss` array in `astro.config.mjs`:
|
||||||
|
|
||||||
|
```diff lang="js"
|
||||||
|
// astro.config.mjs
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import starlight from '@astrojs/starlight';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
integrations: [
|
||||||
|
starlight({
|
||||||
|
title: 'Docs With a Custom Typeface',
|
||||||
|
customCss: [
|
||||||
|
+ // Relative path to your @font-face CSS file.
|
||||||
|
+ './src/fonts/font-face.css',
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
#### Set up a Fontsource font
|
||||||
|
|
||||||
|
The [Fontsource](https://fontsource.org/) project simplifies using Google Fonts and other open-source fonts.
|
||||||
|
It provides npm modules you can install for the fonts you want to use and includes ready-made CSS files to add to your project.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. Find the font you want to use in [Fontsource’s catalog](https://fontsource.org/).
|
||||||
|
This example will use [IBM Plex Serif](https://fontsource.org/fonts/ibm-plex-serif).
|
||||||
|
|
||||||
|
2. Install the package for your chosen font.
|
||||||
|
You can find the package name by clicking “Install” on the Fontsource font page.
|
||||||
|
|
||||||
|
<Tabs syncKey="pkg">
|
||||||
|
|
||||||
|
<TabItem label="npm">
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @fontsource/ibm-plex-serif
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem label="pnpm">
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm add @fontsource/ibm-plex-serif
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem label="Yarn">
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add @fontsource/ibm-plex-serif
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
3. Add Fontsource CSS files to Starlight’s `customCss` array in `astro.config.mjs`:
|
||||||
|
|
||||||
|
```diff lang="js"
|
||||||
|
// astro.config.mjs
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import starlight from '@astrojs/starlight';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
integrations: [
|
||||||
|
starlight({
|
||||||
|
title: 'Docs With a Custom Typeface',
|
||||||
|
customCss: [
|
||||||
|
+ // Fontsource files for to regular and semi-bold font weights.
|
||||||
|
+ '@fontsource/ibm-plex-serif/400.css',
|
||||||
|
+ '@fontsource/ibm-plex-serif/600.css',
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Fontsource ships multiple CSS files for each font. See the [Fontsource documentation](https://fontsource.org/docs/getting-started/install#4-weights-and-styles) on including different weights and styles to understand which to use.
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
### Use fonts
|
||||||
|
|
||||||
|
To apply the font you set up to your site, use your chosen font’s name in a [custom CSS file](/guides/css-and-tailwind/#custom-css-styles).
|
||||||
|
For example, to override Starlight’s default font everywhere, set the `--sl-font` custom property:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* src/styles/custom.css */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--sl-font: 'IBM Plex Serif', serif;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also write more targeted CSS if you want to apply your font more selectively.
|
||||||
|
For example, to only set a font on the main content, but not the sidebars:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* src/styles/custom.css */
|
||||||
|
|
||||||
|
main {
|
||||||
|
font-family: 'IBM Plex Serif', serif;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Follow the [custom CSS instructions](/guides/css-and-tailwind/#custom-css-styles) to add your styles to your site.
|
||||||
140
docs/getting-started.md
Normal file
140
docs/getting-started.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
---
|
||||||
|
title: Getting Started
|
||||||
|
description: Learn how to start building your next documentation site with Starlight by Astro.
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Tabs, TabItem } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
Starlight is a full-featured documentation theme built on top of the [Astro](https://astro.build) framework.
|
||||||
|
This guide will help you get started with a new project.
|
||||||
|
See the [manual setup instructions](/manual-setup/) to add Starlight to an existing Astro project.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Create a new project
|
||||||
|
|
||||||
|
Create a new Astro + Starlight project by running the following command in your terminal:
|
||||||
|
|
||||||
|
<Tabs syncKey="pkg">
|
||||||
|
<TabItem label="npm">
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm create astro@latest -- --template starlight
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="pnpm">
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm create astro --template starlight
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Yarn">
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn create astro --template starlight
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
This will create a new [project directory](/guides/project-structure/) with all the necessary files and configurations for your site.
|
||||||
|
|
||||||
|
:::tip[See it in action]
|
||||||
|
Try Starlight in your browser:
|
||||||
|
[open the template on StackBlitz](https://stackblitz.com/github/withastro/starlight/tree/main/examples/basics).
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Start the development server
|
||||||
|
|
||||||
|
When working locally, [Astro’s development server](https://docs.astro.build/en/reference/cli-reference/#astro-dev) lets you preview your work and automatically refreshes your browser when you make changes.
|
||||||
|
|
||||||
|
Inside your project directory, run the following command to start the development server:
|
||||||
|
|
||||||
|
<Tabs syncKey="pkg">
|
||||||
|
<TabItem label="npm">
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="pnpm">
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Yarn">
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
This will log a message to your terminal with the URL of your local preview.
|
||||||
|
Open this URL to start browsing your site.
|
||||||
|
|
||||||
|
### Add content
|
||||||
|
|
||||||
|
Starlight is ready for you to add new content, or bring your existing files!
|
||||||
|
|
||||||
|
Add new pages to your site by creating Markdown files in the `src/content/docs/` directory.
|
||||||
|
|
||||||
|
Read more about file-based routing and support for MDX and Markdoc files in the [“Pages”](/guides/pages/) guide.
|
||||||
|
|
||||||
|
### Next steps
|
||||||
|
|
||||||
|
- **Configure:** Learn about common options in [“Customizing Starlight”](/guides/customization/).
|
||||||
|
- **Navigate:** Set up your sidebar with the [“Sidebar Navigation”](/guides/sidebar/) guide.
|
||||||
|
- **Components:** Discover built-in cards, tabs, and more in the [“Components”](/components/using-components/) guide.
|
||||||
|
- **Deploy:** Publish your work with the [“Deploy your site”](https://docs.astro.build/en/guides/deploy/) guide in the Astro docs.
|
||||||
|
|
||||||
|
## Updating Starlight
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Because Starlight is beta software, there will be frequent updates and improvements.
|
||||||
|
Be sure to update Starlight regularly!
|
||||||
|
:::
|
||||||
|
|
||||||
|
Starlight is an Astro integration. You can update it and other Astro packages by running the following command in your terminal:
|
||||||
|
|
||||||
|
<Tabs syncKey="pkg">
|
||||||
|
<TabItem label="npm">
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx @astrojs/upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="pnpm">
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm dlx @astrojs/upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Yarn">
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn dlx @astrojs/upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
See the [Starlight changelog](https://github.com/withastro/starlight/blob/main/packages/starlight/CHANGELOG.md) for a full list of the changes made in each release.
|
||||||
|
|
||||||
|
## Troubleshooting Starlight
|
||||||
|
|
||||||
|
Use the [project configuration](/reference/configuration/) and [individual page frontmatter configuration](/reference/frontmatter/) reference pages to ensure that your Starlight site is configured and functioning properly.
|
||||||
|
See the guides in the sidebar for help adding content and customizing your Starlight site.
|
||||||
|
|
||||||
|
If your answer cannot be found in these docs, please visit the [full Astro Docs](https://docs.astro.build) for complete Astro documentation.
|
||||||
|
Your question may be answered by understanding how Astro works in general, underneath this Starlight theme.
|
||||||
|
|
||||||
|
You can also check for any known [Starlight issues on GitHub](https://github.com/withastro/starlight/issues), and get help in the [Astro Discord](https://astro.build/chat/) from our active, friendly community! Post questions in our `#support` forum with the "starlight" tag, or visit our dedicated `#starlight` channel to discuss current development and more!
|
||||||
134
docs/manual-setup.md
Normal file
134
docs/manual-setup.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
---
|
||||||
|
title: Manual Setup
|
||||||
|
description: Learn how to configure Starlight manually to add it to an existing Astro project.
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Tabs, TabItem } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
The quickest way to create a new Starlight site is using `create astro` as shown in the [Getting Started guide](/getting-started/#create-a-new-project).
|
||||||
|
If you want to add Starlight to an existing Astro project, this guide will explain how.
|
||||||
|
|
||||||
|
## Set up Starlight
|
||||||
|
|
||||||
|
To follow this guide, you’ll need an existing Astro project.
|
||||||
|
|
||||||
|
### Add the Starlight integration
|
||||||
|
|
||||||
|
Starlight is an [Astro integration](https://docs.astro.build/en/guides/integrations-guide/). Add it to your site by running the `astro add` command in your project’s root directory:
|
||||||
|
|
||||||
|
<Tabs syncKey="pkg">
|
||||||
|
<TabItem label="npm">
|
||||||
|
```sh
|
||||||
|
npx astro add starlight
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="pnpm">
|
||||||
|
```sh
|
||||||
|
pnpm astro add starlight
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Yarn">
|
||||||
|
```sh
|
||||||
|
yarn astro add starlight
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
This will install the required dependencies and add Starlight to the `integrations` array in your Astro config file.
|
||||||
|
|
||||||
|
### Configure the integration
|
||||||
|
|
||||||
|
The Starlight integration is configured in your `astro.config.mjs` file.
|
||||||
|
|
||||||
|
Add a `title` to get started:
|
||||||
|
|
||||||
|
```js ins={8}
|
||||||
|
// astro.config.mjs
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import starlight from '@astrojs/starlight';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
integrations: [
|
||||||
|
starlight({
|
||||||
|
title: 'My delightful docs site',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Find all available options in the [Starlight configuration reference](/reference/configuration/).
|
||||||
|
|
||||||
|
### Configure content collections
|
||||||
|
|
||||||
|
Starlight is built on top of Astro’s [content collections](https://docs.astro.build/en/guides/content-collections/), which are configured in the `src/content.config.ts` file.
|
||||||
|
|
||||||
|
Create or update the content config file, adding a `docs` collection that uses Starlight’s [`docsLoader`](/reference/configuration/#docsloader) and [`docsSchema`](/reference/configuration/#docsschema):
|
||||||
|
|
||||||
|
```js ins={3-4,7}
|
||||||
|
// src/content.config.ts
|
||||||
|
import { defineCollection } from 'astro:content';
|
||||||
|
import { docsLoader } from '@astrojs/starlight/loaders';
|
||||||
|
import { docsSchema } from '@astrojs/starlight/schema';
|
||||||
|
|
||||||
|
export const collections = {
|
||||||
|
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Starlight also supports the [`legacy.collections` flag](https://docs.astro.build/en/reference/legacy-flags/) where collections are handled using the legacy content collections implementation.
|
||||||
|
This is useful if you have an existing Astro project and are unable to make any changes to collections at this time to use a loader.
|
||||||
|
|
||||||
|
### Add content
|
||||||
|
|
||||||
|
Starlight is now configured and it’s time to add some content!
|
||||||
|
|
||||||
|
Create a `src/content/docs/` directory and start by adding an `index.md` file.
|
||||||
|
This will be the homepage of your new site:
|
||||||
|
|
||||||
|
```md
|
||||||
|
---
|
||||||
|
# src/content/docs/index.md
|
||||||
|
title: My docs
|
||||||
|
description: Learn more about my project in this docs site built with Starlight.
|
||||||
|
---
|
||||||
|
|
||||||
|
Welcome to my project!
|
||||||
|
```
|
||||||
|
|
||||||
|
Starlight uses file-based routing, which means every Markdown, MDX, or Markdoc file in `src/content/docs/` will turn into a page on your site. Frontmatter metadata (the `title` and `description` fields in the example above) can change how each page is displayed.
|
||||||
|
See all the available options in the [frontmatter reference](/reference/frontmatter/).
|
||||||
|
|
||||||
|
## Tips for existing sites
|
||||||
|
|
||||||
|
If you have an existing Astro project, you can use Starlight to quickly add a documentation section to your site.
|
||||||
|
|
||||||
|
### Use Starlight at a subpath
|
||||||
|
|
||||||
|
To add all Starlight pages at a subpath, place all your docs content inside a subdirectory of `src/content/docs/`.
|
||||||
|
|
||||||
|
For example, if Starlight pages should all start with `/guides/`, add your content in the `src/content/docs/guides/` directory:
|
||||||
|
|
||||||
|
import { FileTree } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
<FileTree>
|
||||||
|
|
||||||
|
- src/
|
||||||
|
- content/
|
||||||
|
- docs/
|
||||||
|
- **guides/**
|
||||||
|
- guide.md
|
||||||
|
- index.md
|
||||||
|
- pages/
|
||||||
|
- astro.config.mjs
|
||||||
|
|
||||||
|
</FileTree>
|
||||||
|
|
||||||
|
In the future, we plan to support this use case better to avoid the need for the extra nested directory in `src/content/docs/`.
|
||||||
|
|
||||||
|
### Use Starlight with SSR
|
||||||
|
|
||||||
|
To enable SSR, follow the [“On-demand Rendering Adapters”](https://docs.astro.build/en/guides/on-demand-rendering/) guide in Astro’s docs to add a server adapter to your Starlight project.
|
||||||
|
|
||||||
|
Documentation pages generated by Starlight are pre-rendered by default regardless of your project's output mode. To opt out of pre-rendering your Starlight pages, set the [`prerender` config option](/reference/configuration/#prerender) to `false`.
|
||||||
47
docs/project-structure.md
Normal file
47
docs/project-structure.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
title: Project Structure
|
||||||
|
description: Learn how to organize files in your Starlight project.
|
||||||
|
---
|
||||||
|
|
||||||
|
This guide will show you how a Starlight project is organized and what the different files in your project do.
|
||||||
|
|
||||||
|
Starlight projects generally follow the same file and directory structure as other Astro projects. See [Astro’s project structure documentation](https://docs.astro.build/en/basics/project-structure/) for more detail.
|
||||||
|
|
||||||
|
## Files and directories
|
||||||
|
|
||||||
|
- `astro.config.mjs` — The Astro configuration file; includes the Starlight integration and configuration.
|
||||||
|
- `src/content.config.ts` — Content collections configuration file; adds Starlight’s frontmatter schemas to your project.
|
||||||
|
- `src/content/docs/` — Content files. Starlight turns each `.md`, `.mdx` or `.mdoc` file in this directory into a page on your site.
|
||||||
|
- `src/content/i18n/` (optional) — Translation data to support [internationalization](/guides/i18n/).
|
||||||
|
- `src/` — Other source code and files (components, styles, images, etc.) for your project.
|
||||||
|
- `public/` — Static assets (fonts, favicon, PDFs, etc.) that will not be processed by Astro.
|
||||||
|
|
||||||
|
## Example project contents
|
||||||
|
|
||||||
|
A Starlight project directory might look like this:
|
||||||
|
|
||||||
|
import { FileTree } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
<FileTree>
|
||||||
|
|
||||||
|
- public/
|
||||||
|
- favicon.svg
|
||||||
|
- src/
|
||||||
|
- assets/
|
||||||
|
- logo.svg
|
||||||
|
- screenshot.jpg
|
||||||
|
- components/
|
||||||
|
- CustomButton.astro
|
||||||
|
- InteractiveWidget.jsx
|
||||||
|
- content/
|
||||||
|
- docs/
|
||||||
|
- guides/
|
||||||
|
- 01-getting-started.md
|
||||||
|
- 02-advanced.md
|
||||||
|
- index.mdx
|
||||||
|
- content.config.ts
|
||||||
|
- astro.config.mjs
|
||||||
|
- package.json
|
||||||
|
- tsconfig.json
|
||||||
|
|
||||||
|
</FileTree>
|
||||||
234
docs/site-search.md
Normal file
234
docs/site-search.md
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
---
|
||||||
|
title: Site Search
|
||||||
|
description: Learn about Starlight’s built-in site search features and how to customize them.
|
||||||
|
tableOfContents:
|
||||||
|
maxHeadingLevel: 4
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Tabs, TabItem, Steps } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
By default, Starlight sites include full-text search powered by [Pagefind](https://pagefind.app/), which is a fast and low-bandwidth search tool for static sites.
|
||||||
|
|
||||||
|
No configuration is required to enable search. Build and deploy your site, then use the search bar in the site header to find content.
|
||||||
|
|
||||||
|
## Hide content in search results
|
||||||
|
|
||||||
|
### Exclude a page
|
||||||
|
|
||||||
|
To exclude a page from your search index, add [`pagefind: false`](/reference/frontmatter/#pagefind) to the page’s frontmatter:
|
||||||
|
|
||||||
|
```md title="src/content/docs/not-indexed.md" ins={3}
|
||||||
|
---
|
||||||
|
title: Content to hide from search
|
||||||
|
pagefind: false
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exclude part of a page
|
||||||
|
|
||||||
|
Pagefind will ignore content inside an element with the [`data-pagefind-ignore`](https://pagefind.app/docs/indexing/#removing-individual-elements-from-the-index) attribute.
|
||||||
|
|
||||||
|
In the following example, the first paragraph will display in search results, but the contents of the `<div>` will not:
|
||||||
|
|
||||||
|
```md title="src/content/docs/partially-indexed.md" ins="data-pagefind-ignore"
|
||||||
|
---
|
||||||
|
title: Partially indexed page
|
||||||
|
---
|
||||||
|
|
||||||
|
This text will be discoverable via search.
|
||||||
|
|
||||||
|
<div data-pagefind-ignore>
|
||||||
|
|
||||||
|
This text will be hidden from search.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Alternative search providers
|
||||||
|
|
||||||
|
### Algolia DocSearch
|
||||||
|
|
||||||
|
If you have access to [Algolia’s DocSearch program](https://docsearch.algolia.com/) and want to use it instead of Pagefind, you can use the official Starlight DocSearch plugin.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. Install `@astrojs/starlight-docsearch`:
|
||||||
|
|
||||||
|
<Tabs syncKey="pkg">
|
||||||
|
|
||||||
|
<TabItem label="npm">
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @astrojs/starlight-docsearch
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem label="pnpm">
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm add @astrojs/starlight-docsearch
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem label="Yarn">
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add @astrojs/starlight-docsearch
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
2. Add DocSearch to your Starlight [`plugins`](/reference/configuration/#plugins) config in `astro.config.mjs` and pass it your Algolia `appId`, `apiKey`, and `indexName`:
|
||||||
|
|
||||||
|
```js ins={4,10-16}
|
||||||
|
// astro.config.mjs
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import starlight from '@astrojs/starlight';
|
||||||
|
import starlightDocSearch from '@astrojs/starlight-docsearch';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
integrations: [
|
||||||
|
starlight({
|
||||||
|
title: 'Site with DocSearch',
|
||||||
|
plugins: [
|
||||||
|
starlightDocSearch({
|
||||||
|
appId: 'YOUR_APP_ID',
|
||||||
|
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||||
|
indexName: 'YOUR_INDEX_NAME',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
With this updated configuration, the search bar on your site will now open an Algolia modal instead of the default search modal.
|
||||||
|
|
||||||
|
#### DocSearch configuration
|
||||||
|
|
||||||
|
The Starlight DocSearch plugin supports customizing the DocSearch component with the following inline options:
|
||||||
|
|
||||||
|
- `maxResultsPerGroup`: Limit the number of results displayed for each search group. Default is `5`.
|
||||||
|
- `disableUserPersonalization`: Prevent DocSearch from saving a user’s recent searches and favorites to local storage. Default is `false`.
|
||||||
|
- `insights`: Enable the Algolia Insights plugin and send search events to your DocSearch index. Default is `false`.
|
||||||
|
- `searchParameters`: An object customizing the [Algolia Search Parameters](https://www.algolia.com/doc/api-reference/search-api-parameters/).
|
||||||
|
|
||||||
|
##### Additional DocSearch options
|
||||||
|
|
||||||
|
A separate configuration file is required to pass function options like `transformItems()` or `resultsFooterComponent()` to the DocSearch component.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. Create a TypeScript file exporting your DocSearch configuration.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/config/docsearch.ts
|
||||||
|
import type { DocSearchClientOptions } from '@astrojs/starlight-docsearch';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
appId: 'YOUR_APP_ID',
|
||||||
|
apiKey: 'YOUR_SEARCH_API_KEY',
|
||||||
|
indexName: 'YOUR_INDEX_NAME',
|
||||||
|
getMissingResultsUrl({ query }) {
|
||||||
|
return `https://github.com/algolia/docsearch/issues/new?title=${query}`;
|
||||||
|
},
|
||||||
|
// ...
|
||||||
|
} satisfies DocSearchClientOptions;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Pass the path to your configuration file to the Starlight DocSearch plugin in `astro.config.mjs`.
|
||||||
|
|
||||||
|
```js {11-13}
|
||||||
|
// astro.config.mjs
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import starlight from '@astrojs/starlight';
|
||||||
|
import starlightDocSearch from '@astrojs/starlight-docsearch';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
integrations: [
|
||||||
|
starlight({
|
||||||
|
title: 'Site with DocSearch',
|
||||||
|
plugins: [
|
||||||
|
starlightDocSearch({
|
||||||
|
clientOptionsModule: './src/config/docsearch.ts',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
See the [DocSearch JavaScript client API Reference](https://docsearch.algolia.com/docs/api/) for all supported options.
|
||||||
|
|
||||||
|
#### Translating the DocSearch UI
|
||||||
|
|
||||||
|
DocSearch only provides English UI strings by default.
|
||||||
|
Add translations of the modal UI for your language using Starlight’s built-in [internationalization system](/guides/i18n/#translate-starlights-ui).
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. Extend Starlight’s `i18n` content collection definition with the DocSearch schema in `src/content.config.ts`:
|
||||||
|
|
||||||
|
```js ins={5} ins=/{ extend: .+ }/
|
||||||
|
// src/content.config.ts
|
||||||
|
import { defineCollection } from 'astro:content';
|
||||||
|
import { docsLoader, i18nLoader } from '@astrojs/starlight/loaders';
|
||||||
|
import { docsSchema, i18nSchema } from '@astrojs/starlight/schema';
|
||||||
|
import { docSearchI18nSchema } from '@astrojs/starlight-docsearch/schema';
|
||||||
|
|
||||||
|
export const collections = {
|
||||||
|
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
|
||||||
|
i18n: defineCollection({
|
||||||
|
loader: i18nLoader(),
|
||||||
|
schema: i18nSchema({ extend: docSearchI18nSchema() }),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Add translations to your JSON files in `src/content/i18n/`.
|
||||||
|
|
||||||
|
These are the English defaults used by DocSearch:
|
||||||
|
|
||||||
|
```json title="src/content/i18n/en.json"
|
||||||
|
{
|
||||||
|
"docsearch.searchBox.resetButtonTitle": "Clear the query",
|
||||||
|
"docsearch.searchBox.resetButtonAriaLabel": "Clear the query",
|
||||||
|
"docsearch.searchBox.cancelButtonText": "Cancel",
|
||||||
|
"docsearch.searchBox.cancelButtonAriaLabel": "Cancel",
|
||||||
|
"docsearch.searchBox.searchInputLabel": "Search",
|
||||||
|
|
||||||
|
"docsearch.startScreen.recentSearchesTitle": "Recent",
|
||||||
|
"docsearch.startScreen.noRecentSearchesText": "No recent searches",
|
||||||
|
"docsearch.startScreen.saveRecentSearchButtonTitle": "Save this search",
|
||||||
|
"docsearch.startScreen.removeRecentSearchButtonTitle": "Remove this search from history",
|
||||||
|
"docsearch.startScreen.favoriteSearchesTitle": "Favorite",
|
||||||
|
"docsearch.startScreen.removeFavoriteSearchButtonTitle": "Remove this search from favorites",
|
||||||
|
|
||||||
|
"docsearch.errorScreen.titleText": "Unable to fetch results",
|
||||||
|
"docsearch.errorScreen.helpText": "You might want to check your network connection.",
|
||||||
|
|
||||||
|
"docsearch.footer.selectText": "to select",
|
||||||
|
"docsearch.footer.selectKeyAriaLabel": "Enter key",
|
||||||
|
"docsearch.footer.navigateText": "to navigate",
|
||||||
|
"docsearch.footer.navigateUpKeyAriaLabel": "Arrow up",
|
||||||
|
"docsearch.footer.navigateDownKeyAriaLabel": "Arrow down",
|
||||||
|
"docsearch.footer.closeText": "to close",
|
||||||
|
"docsearch.footer.closeKeyAriaLabel": "Escape key",
|
||||||
|
"docsearch.footer.searchByText": "Search by",
|
||||||
|
|
||||||
|
"docsearch.noResultsScreen.noResultsText": "No results for",
|
||||||
|
"docsearch.noResultsScreen.suggestedQueryText": "Try searching for",
|
||||||
|
"docsearch.noResultsScreen.reportMissingResultsText": "Believe this query should return results?",
|
||||||
|
"docsearch.noResultsScreen.reportMissingResultsLinkText": "Let us know."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
33
src/assets/contextvm-logo.svg
Normal file
33
src/assets/contextvm-logo.svg
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#2563eb;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#7c3aed;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Main container/cube representation -->
|
||||||
|
<rect x="50" y="50" width="100" height="100" rx="10" fill="url(#gradient)" opacity="0.9"/>
|
||||||
|
|
||||||
|
<!-- Context symbol - three interlocking rings -->
|
||||||
|
<g transform="translate(100,100)">
|
||||||
|
<!-- Ring 1 -->
|
||||||
|
<circle cx="-20" cy="-10" r="25" fill="none" stroke="white" stroke-width="3" opacity="0.8"/>
|
||||||
|
<!-- Ring 2 -->
|
||||||
|
<circle cx="20" cy="-10" r="25" fill="none" stroke="white" stroke-width="3" opacity="0.8"/>
|
||||||
|
<!-- Ring 3 -->
|
||||||
|
<circle cx="0" cy="15" r="25" fill="none" stroke="white" stroke-width="3" opacity="0.8"/>
|
||||||
|
|
||||||
|
<!-- VM symbol - stylized lambda -->
|
||||||
|
<text x="0" y="5" font-family="Arial Black" font-size="20" fill="white" text-anchor="middle">λ</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Subtle grid lines representing computation/network -->
|
||||||
|
<g stroke="rgba(255,255,255,0.2)" stroke-width="0.5">
|
||||||
|
<line x1="70" y1="40" x2="70" y2="160"/>
|
||||||
|
<line x1="130" y1="40" x2="130" y2="160"/>
|
||||||
|
<line x1="40" y1="70" x2="160" y2="70"/>
|
||||||
|
<line x1="40" y1="130" x2="160" y2="130"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
56
src/content/docs/guides/core/constants.md
Normal file
56
src/content/docs/guides/core/constants.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
title: Constants
|
||||||
|
description: A set of constants used throughout the @contextvm/sdk.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
|
||||||
|
The `@contextvm/sdk` exports a set of constants that are used throughout the library for event kinds, tags, and other protocol-specific values. These constants ensure consistency and alignment with the ContextVM specification.
|
||||||
|
|
||||||
|
## Event Kinds
|
||||||
|
|
||||||
|
The ContextVM protocol defines several Nostr event kinds for different types of messages.
|
||||||
|
|
||||||
|
| Constant | Kind | Description |
|
||||||
|
| ----------------------------- | ----- | --------------------------------------------------------------------------- |
|
||||||
|
| `CTXVM_MESSAGES_KIND` | 25910 | The kind for standard, ephemeral ContextVM messages. |
|
||||||
|
| `GIFT_WRAP_KIND` | 1059 | The kind for encrypted messages, wrapped using the NIP-59 gift wrap standard. |
|
||||||
|
| `SERVER_ANNOUNCEMENT_KIND` | 11316 | A replaceable event for announcing a server's presence and basic info. |
|
||||||
|
| `TOOLS_LIST_KIND` | 11317 | A replaceable event for listing a server's available tools. |
|
||||||
|
| `RESOURCES_LIST_KIND` | 11318 | A replaceable event for listing a server's available resources. |
|
||||||
|
| `RESOURCETEMPLATES_LIST_KIND` | 11319 | A replaceable event for listing a server's available resource templates. |
|
||||||
|
| `PROMPTS_LIST_KIND` | 11320 | A replaceable event for listing a server's available prompts. |
|
||||||
|
|
||||||
|
## Nostr Tags
|
||||||
|
|
||||||
|
The SDK defines an object `NOSTR_TAGS` that contains constants for the various Nostr event tags used in the ContextVM protocol.
|
||||||
|
|
||||||
|
| Key | Tag | Description |
|
||||||
|
| -------------------- | -------------------- | ------------------------------------------------------------------------ |
|
||||||
|
| `PUBKEY` | `p` | The public key of the message recipient. |
|
||||||
|
| `EVENT_ID` | `e` | The event ID used to correlate requests and responses. |
|
||||||
|
| `CAPABILITY` | `cap` | A tag for specifying pricing metadata for a tool, resource, or prompt. |
|
||||||
|
| `NAME` | `name` | The human-readable name of a server in an announcement. |
|
||||||
|
| `WEBSITE` | `website` | The URL of a server's website in an announcement. |
|
||||||
|
| `PICTURE` | `picture` | The URL of a server's icon in an announcement. |
|
||||||
|
| `SUPPORT_ENCRYPTION` | `support_encryption` | A tag indicating that a server supports end-to-end encryption. |
|
||||||
|
|
||||||
|
## Announcement Methods
|
||||||
|
|
||||||
|
The `announcementMethods` object maps capability types to their corresponding MCP method names for server announcements.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const announcementMethods = {
|
||||||
|
server: 'initialize',
|
||||||
|
tools: 'tools/list',
|
||||||
|
resources: 'resources/list',
|
||||||
|
resourceTemplates: 'resources/templates/list',
|
||||||
|
prompts: 'prompts/list',
|
||||||
|
} as const;
|
||||||
|
```
|
||||||
|
|
||||||
|
This object is used internally by the `NostrServerTransport` to construct announcement events.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
With a solid understanding of the core modules, you are now ready to explore the **[Transports](../transports/base-nostr-transport.md)**, which are responsible for all network communication in the SDK.
|
||||||
72
src/content/docs/guides/core/encryption.md
Normal file
72
src/content/docs/guides/core/encryption.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
title: Encryption
|
||||||
|
description: An overview of the encryption mechanism in the @contextvm/sdk.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Encryption
|
||||||
|
|
||||||
|
The `@contextvm/sdk` supports optional end-to-end encryption for all communication, providing enhanced privacy and security. This section explains the encryption mechanism, how to enable it, and the underlying principles.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
ContextVM's encryption leverages a simplified version of [NIP-17](https://github.com/nostr-protocol/nips/blob/master/17.md) to ensure:
|
||||||
|
|
||||||
|
1. **Message Content Privacy**: All MCP message content is encrypted using NIP-44.
|
||||||
|
2. **Metadata Protection**: The gift wrap pattern conceals participant identities and other metadata.
|
||||||
|
3. **Selective Encryption**: Clients and servers can negotiate encryption on a per-session basis.
|
||||||
|
|
||||||
|
When encryption is enabled, all ephemeral messages (kind 25910) are wrapped in a kind 1059 gift wrap event. Server announcements and capability lists remain unencrypted for public discoverability.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
The encryption flow is designed to be secure and efficient:
|
||||||
|
|
||||||
|
1. **Content Preparation**: The original MCP message is serialized into a standard Nostr event.
|
||||||
|
2. **Gift Wrapping**: The entire event is then encrypted using `nip44.v2` and wrapped inside a "gift wrap" event (kind 1059). A new, random keypair is generated for each gift wrap.
|
||||||
|
3. **Transmission**: The encrypted gift wrap event is published to the Nostr network.
|
||||||
|
|
||||||
|
The recipient then unwraps the gift using their private key to decrypt the original message.
|
||||||
|
|
||||||
|
### Why a Simplified NIP-17/NIP-59 Pattern?
|
||||||
|
|
||||||
|
The standard implementation of NIP-17 is designed for persistent private messages and includes a "rumor" and "seal" mechanism to prevent message leakage. Since ContextVM messages are ephemeral and not intended to be stored by relays, this complexity is unnecessary. The SDK uses a more direct gift-wrapping approach that provides strong encryption and metadata protection without the overhead of the full NIP-17 standard.
|
||||||
|
|
||||||
|
## Enabling Encryption
|
||||||
|
|
||||||
|
Encryption is configured at the transport level using the `EncryptionMode` enum. You can set the desired mode when creating a `NostrClientTransport` or `NostrServerTransport`.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { NostrClientTransport } from '@ctxvm/sdk/transport';
|
||||||
|
import { EncryptionMode } from '@ctxvm/sdk/core';
|
||||||
|
|
||||||
|
const transport = new NostrClientTransport({
|
||||||
|
// ... other options
|
||||||
|
encryptionMode: EncryptionMode.OPTIONAL, // or REQUIRED, DISABLED
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### `EncryptionMode`
|
||||||
|
|
||||||
|
- **`REQUIRED`**: The transport will only communicate with peers that support encryption. If the other party does not support it, the connection will fail.
|
||||||
|
- **`OPTIONAL`**: (Default) The transport will attempt to use encryption if the peer supports it. If not, it will fall back to unencrypted communication.
|
||||||
|
- **`DISABLED`**: The transport will not use encryption, even if the peer supports it.
|
||||||
|
|
||||||
|
## Encryption Support Discovery
|
||||||
|
|
||||||
|
Clients and servers can discover if a peer supports encryption in two ways:
|
||||||
|
|
||||||
|
1. **Server Announcements**: Public server announcements (kind 11316) include a `support_encryption` tag to indicate that the server is capable of encrypted communication.
|
||||||
|
2. **Initialization Handshake**: During the MCP initialization process, both the client and server can signal their support for encryption.
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
The core encryption functions are exposed in the `core` module:
|
||||||
|
|
||||||
|
- `encryptMessage(message: string, recipientPublicKey: string): NostrEvent`
|
||||||
|
- `decryptMessage(event: NostrEvent, signer: NostrSigner): Promise<string>`
|
||||||
|
|
||||||
|
These functions handle the low-level details of gift wrapping and unwrapping, but in most cases, you will interact with encryption through the transport's `encryptionMode` setting.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Now that you understand how encryption works, let's look at the [Constants](./constants.md) used throughout the SDK.
|
||||||
95
src/content/docs/guides/core/interfaces.md
Normal file
95
src/content/docs/guides/core/interfaces.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
---
|
||||||
|
title: Interfaces
|
||||||
|
description: A deep dive into the core interfaces used in the @contextvm/sdk.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Core Interfaces
|
||||||
|
|
||||||
|
The `@contextvm/sdk` is designed with a modular and extensible architecture, centered around a set of core interfaces. These interfaces define the essential components for signing, relay management, and communication.
|
||||||
|
|
||||||
|
## `NostrSigner`
|
||||||
|
|
||||||
|
The `NostrSigner` interface is fundamental for cryptographic operations within the SDK. It abstracts the logic for signing Nostr events, ensuring that all communications are authenticated and verifiable.
|
||||||
|
|
||||||
|
### Definition
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface NostrSigner {
|
||||||
|
getPublicKey(): Promise<string>;
|
||||||
|
signEvent(event: EventTemplate): Promise<NostrEvent>;
|
||||||
|
|
||||||
|
// Optional NIP-04 encryption support (deprecated)
|
||||||
|
nip04?: {
|
||||||
|
encrypt: (pubkey: string, plaintext: string) => Promise<string>;
|
||||||
|
decrypt: (pubkey: string, ciphertext: string) => Promise<string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Optional NIP-44 encryption support
|
||||||
|
nip44?: {
|
||||||
|
encrypt: (pubkey: string, plaintext: string) => Promise<string>;
|
||||||
|
decrypt: (pubkey: string, ciphertext: string) => Promise<string>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Any object that implements this interface can be used to sign events, allowing you to integrate with various key management systems, such as web, hardware wallets or remote signing services. The SDK provides a default implementation, `PrivateKeySigner`, which signs events using a raw private key.
|
||||||
|
|
||||||
|
- **Learn more:** [`NostrSigner` Deep Dive](../signer/nostr-signer-interface.md)
|
||||||
|
- **Default Implementation:** [`PrivateKeySigner`](../signer/private-key-signer.md)
|
||||||
|
|
||||||
|
## `RelayHandler`
|
||||||
|
|
||||||
|
The `RelayHandler` interface manages interactions with Nostr relays. It is responsible for subscribing to events and publishing events to the Nostr network.
|
||||||
|
|
||||||
|
### Definition
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface RelayHandler {
|
||||||
|
connect(): Promise<void>;
|
||||||
|
disconnect(relayUrls?: string[]): Promise<void>;
|
||||||
|
publish(event: NostrEvent): Promise<void>;
|
||||||
|
subscribe(
|
||||||
|
filters: Filter[],
|
||||||
|
onEvent: (event: NostrEvent) => void,
|
||||||
|
onEose?: () => void,
|
||||||
|
): Promise<void>;
|
||||||
|
unsubscribe(): void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
By implementing this interface, you can create custom relay management logic, such as sophisticated relay selection strategies or custom reconnection policies. The SDK includes `SimpleRelayPool` as a default implementation.
|
||||||
|
|
||||||
|
- **Learn more:** [`RelayHandler` Deep Dive](../relay/relay-handler-interface.md)
|
||||||
|
- **Default Implementation:** [`SimpleRelayPool`](../relay/simple-relay-pool.md)
|
||||||
|
|
||||||
|
## `EncryptionMode`
|
||||||
|
|
||||||
|
The `EncryptionMode` enum defines the encryption policy for a transport.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export enum EncryptionMode {
|
||||||
|
OPTIONAL = 'optional',
|
||||||
|
REQUIRED = 'required',
|
||||||
|
DISABLED = 'disabled',
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This enum is used to configure the encryption behavior of the `NostrClientTransport` and `NostrServerTransport`.
|
||||||
|
|
||||||
|
- **Learn more:** [Encryption](./encryption.md)
|
||||||
|
|
||||||
|
## `AnnouncementMethods`
|
||||||
|
|
||||||
|
The `AnnouncementMethods` interface defines methods for announcing server capabilities on the Nostr network.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface AnnouncementMethods {
|
||||||
|
server: InitializeRequest['method'];
|
||||||
|
tools: ListToolsRequest['method'];
|
||||||
|
resources: ListResourcesRequest['method'];
|
||||||
|
resourceTemplates: ListResourceTemplatesRequest['method'];
|
||||||
|
prompts: ListPromptsRequest['method'];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This interface is used by the `NostrServerTransport` to publish server announcements.
|
||||||
655
src/content/docs/guides/ctxvm-draft-spec.md
Normal file
655
src/content/docs/guides/ctxvm-draft-spec.md
Normal file
@@ -0,0 +1,655 @@
|
|||||||
|
---
|
||||||
|
title: ContextVM Protocol Specification
|
||||||
|
description: Technical specification for the ContextVM protocol
|
||||||
|
---
|
||||||
|
|
||||||
|
**MCP Version:** `mcp:2025-03-26`
|
||||||
|
**Status:** Draft
|
||||||
|
|
||||||
|
## Abstract
|
||||||
|
|
||||||
|
The Context Vending Machine (ContextVM) specification defines how Nostr and Context Vending Machines can be used to expose Model Context Protocol (MCP) server capabilities, enabling standardized usage of these resources through a decentralized, cryptographically secure messaging system.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Introduction](#introduction)
|
||||||
|
- [Public Key Cryptography](#public-key-cryptography)
|
||||||
|
- [Protocol Overview](#protocol-overview)
|
||||||
|
- [Main Actors](#main-actors)
|
||||||
|
- [Event Kinds](#event-kinds)
|
||||||
|
- [Server Discovery](#server-discovery)
|
||||||
|
- [Discovery via Server Announcements (Public Servers)](#discovery-via-server-announcements-public-servers)
|
||||||
|
- [Server Announcement Event](#server-announcement-event)
|
||||||
|
- [Tools List Event](#tools-list-event)
|
||||||
|
- [Resources List Event](#resources-list-event)
|
||||||
|
- [Prompts List Event](#prompts-list-event)
|
||||||
|
- [Capability Pricing](#capability-pricing)
|
||||||
|
- [Pricing Tag Format](#pricing-tag-format)
|
||||||
|
- [Example](#example)
|
||||||
|
- [Payment Handling](#payment-handling)
|
||||||
|
- [Direct Discovery (Private Servers)](#direct-discovery-private-servers)
|
||||||
|
- [Client Initialization Request](#client-initialization-request)
|
||||||
|
- [Server Initialization Response](#server-initialization-response)
|
||||||
|
- [Client Initialized Notification](#client-initialized-notification)
|
||||||
|
- [Capability Operations](#capability-operations)
|
||||||
|
- [List Operations](#list-operations)
|
||||||
|
- [List Request Template](#list-request-template)
|
||||||
|
- [List Response Template](#list-response-template)
|
||||||
|
- [Capability-Specific Item Examples](#capability-specific-item-examples)
|
||||||
|
- [Call Tool Request](#call-tool-request)
|
||||||
|
- [Call Tool Response](#call-tool-response)
|
||||||
|
- [Encryption](#encryption)
|
||||||
|
- [Overview](#overview-1)
|
||||||
|
- [Encryption Support Discovery](#encryption-support-discovery)
|
||||||
|
- [Message Encryption Flow](#message-encryption-flow)
|
||||||
|
- [1. Content Preparation](#1-content-preparation)
|
||||||
|
- [2. Seal Creation (NIP-17)](#2-seal-creation-nip-17)
|
||||||
|
- [3. Gift Wrapping (NIP-59)](#3-gift-wrapping-nip-59)
|
||||||
|
- [Encrypted Event Structure](#encrypted-event-structure)
|
||||||
|
- [Original ContextVM Request](#original-ContextVM-request)
|
||||||
|
- [Encrypted ContextVM Request](#encrypted-ContextVM-request)
|
||||||
|
- [Encrypted Response Structure](#encrypted-response-structure)
|
||||||
|
- [Notifications](#notifications)
|
||||||
|
- [Notification Template](#notification-template)
|
||||||
|
- [Payment Required Notification](#payment-required-notification)
|
||||||
|
- [Error Handling](#error-handling)
|
||||||
|
- [Error Types](#error-types)
|
||||||
|
- [Error Response Template](#error-response-template)
|
||||||
|
- [Implementation Requirements](#implementation-requirements)
|
||||||
|
- [Complete Protocol Flow](#complete-protocol-flow)
|
||||||
|
- [Subscription Management](#subscription-management)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
The [Model Context Protocol](https://modelcontextprotocol.io/introduction) provides a protocol specification to create servers exposing capabilities and clients consuming them. Meanwhile, the Nostr network and Context Vending Machines offer a decentralized way to announce and consume computational services. This specification defines how to bridge these protocols, allowing MCP servers to advertise and provide their services through the Nostr network.
|
||||||
|
|
||||||
|
This specification aims to:
|
||||||
|
|
||||||
|
1. Enable discovery of MCP servers and their capabilities through the Nostr network
|
||||||
|
2. Provide a consistent experience for clients accessing capabilities, and servers exposing their capabilities
|
||||||
|
3. Maintain compatibility with both protocols while preserving their security models
|
||||||
|
|
||||||
|
By integrating these protocols, ContextVM combines the standardized capability framework of MCP with the decentralized, cryptographically secure messaging of Nostr. This integration enables several key advantages:
|
||||||
|
|
||||||
|
- **Discoverability**: MCP servers can be discovered through the Nostr network without centralized registries
|
||||||
|
- **Verifiability**: All messages are cryptographically signed using Nostr's public keys
|
||||||
|
- **Decentralization**: No single point of failure for service discovery or communication
|
||||||
|
- **Protocol Interoperability**: Both MCP and ContextVMs utilize JSON-RPC patterns, enabling seamless communication between the protocols
|
||||||
|
|
||||||
|
The integration preserves the security model of both protocols while enabling new patterns of interaction.
|
||||||
|
|
||||||
|
### Public Key Cryptography
|
||||||
|
|
||||||
|
ContextVM leverages Nostr's public key cryptography to ensure message authenticity and integrity:
|
||||||
|
|
||||||
|
1. **Message Verification**: Every message is cryptographically signed by the sender's private key and can be verified using their public key, ensuring that:
|
||||||
|
- Server announcements come from legitimate providers
|
||||||
|
- Client requests are from authorized users
|
||||||
|
- Responses are from the expected servers
|
||||||
|
|
||||||
|
2. **Identity Management**: Public keys serve as persistent identifiers for all actors in the system:
|
||||||
|
- Providers can maintain consistent identities across relays
|
||||||
|
- Clients can be uniquely identified for authorization purposes
|
||||||
|
|
||||||
|
The cryptographic properties enable secure authorization flows for paid services and private capabilities without requiring centralized authentication services.
|
||||||
|
|
||||||
|
## Protocol Overview
|
||||||
|
|
||||||
|
### Message Structure
|
||||||
|
|
||||||
|
The protocol uses these key design principles for message handling:
|
||||||
|
|
||||||
|
1. **Content Field Structure**: The `content` field of Nostr events contains stringified MCP messages. All MCP message structures, are preserved exactly as defined in the MCP specification
|
||||||
|
|
||||||
|
2. **Nostr Metadata in Tags**: All Nostr-specific metadata uses event tags:
|
||||||
|
- `p`: Public key for addressing providers or clients
|
||||||
|
- `e`: Event id, references for correlating requests and responses
|
||||||
|
- `cap`: Capability tag for tools, resources, and prompts to provide pricing metadata
|
||||||
|
|
||||||
|
3. **Unified Event Kind**: ContextVM uses a single event kind for all communication with specific storage characteristics:
|
||||||
|
- `25910`: All ContextVM messages (ephemeral events)
|
||||||
|
- `11316`-`11320`: Server announcements and capability listings (replaceable events)
|
||||||
|
- `1059`: Encrypted Messages (NIP-59 Gift Wrap)
|
||||||
|
|
||||||
|
These event kinds follow Nostr's conventions in [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md#kinds):
|
||||||
|
- For kind n such that 20000 <= n < 30000, events are ephemeral, which means they are not expected to be stored by relays for a long period, but rather just transmitted.
|
||||||
|
- For kind n such that 10000 <= n < 20000, events are addressable by their kind, pubkey and d tag value -- which means that, for each combination of kind, and pubkey, only the latest event MUST be stored by relays, older versions MAY be discarded.
|
||||||
|
|
||||||
|
### Main Actors
|
||||||
|
|
||||||
|
There are three main actors in this workflow:
|
||||||
|
|
||||||
|
- **Servers**: MCP servers exposing capabilities, operated by a provider using a public key
|
||||||
|
- **Relays**: Core part of Nostr protocol that allows communication between clients and servers
|
||||||
|
- **Clients**: MCP or Nostr clients that discover and consume capabilities from servers
|
||||||
|
|
||||||
|
## Event Kinds
|
||||||
|
|
||||||
|
This specification defines these event kinds:
|
||||||
|
|
||||||
|
| Kind | Description |
|
||||||
|
| ----- | ------------------------------------- |
|
||||||
|
| 25910 | ContextVM Messages |
|
||||||
|
| 1059 | Encrypted Messages (NIP-59 Gift Wrap) |
|
||||||
|
| 11316 | Server Announcement |
|
||||||
|
| 11317 | Tools List |
|
||||||
|
| 11318 | Resources List |
|
||||||
|
| 11319 | Resource Templates List |
|
||||||
|
| 11320 | Prompts List |
|
||||||
|
|
||||||
|
**Note on Encryption**: When encryption is enabled, kind 25910 events are wrapped using [NIP-59](https://github.com/nostr-protocol/nips/blob/master/59.md) and published as kind 1059 events. Addressable events (kinds 11316-11320) remain unencrypted for discoverability.
|
||||||
|
|
||||||
|
## Server Discovery
|
||||||
|
|
||||||
|
ContextVM provides two methods of server discovery, the main differences between these two methods being the visibility of the servers and the way they are advertised. Public servers can advertise themselves and their capabilities to improve discoverability. Private servers may not advertise themselves and their capabilities, but they can be discovered by clients that know the provider's public key.
|
||||||
|
|
||||||
|
### Discovery via Server Announcements (Public Servers)
|
||||||
|
|
||||||
|
Public server announcements act as a service catalog, allowing clients to discover servers and their capabilities through replaceable events on the Nostr network. This mechanism provides an initial overview of what a server offers, and their public keys to connect with them.
|
||||||
|
|
||||||
|
Since each server is uniquely identified by its public key, the announcement events are replaceable (kinds 11316-11320), ensuring that only the most recent version of the server's information is active.
|
||||||
|
|
||||||
|
Providers announce their servers and capabilities by publishing events with kinds 11316 (server), 11317 (tools/list), 11318 (resources/list), 11319 (resource templates/list), and 11320 (prompts/list).
|
||||||
|
|
||||||
|
**Note:** The `content` field of ContextVM events contains stringified MCP messages. The examples below present the `content` as a JSON object for readability; it must be stringified before inclusion in a Nostr event.
|
||||||
|
|
||||||
|
#### Server Announcement Event
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 11316,
|
||||||
|
"pubkey": "<provider-pubkey>",
|
||||||
|
"content": {
|
||||||
|
"protocolVersion": "2025-07-02",
|
||||||
|
"capabilities": {
|
||||||
|
"prompts": {
|
||||||
|
"listChanged": true
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"subscribe": true,
|
||||||
|
"listChanged": true
|
||||||
|
},
|
||||||
|
"tools": {
|
||||||
|
"listChanged": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"serverInfo": {
|
||||||
|
"name": "ExampleServer",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"instructions": "Optional instructions for the client"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
["name", "Example Server"], // Optional: Human-readable server name
|
||||||
|
["about", "Server description"], // Optional: Server description
|
||||||
|
["picture", "https://example.com/server.png"], // Optional: Server icon/avatar URL
|
||||||
|
["website", "https://example.com"], // Optional: Server website
|
||||||
|
["support_encryption"] // Optional: Presence indicates server supports encrypted messages
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Tools List Event
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 11317,
|
||||||
|
"pubkey": "<provider-pubkey>",
|
||||||
|
"content": {
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "get_weather",
|
||||||
|
"description": "Get current weather information for a location",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"location": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "City name or zip code"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["location"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Server Connection and Initialization
|
||||||
|
|
||||||
|
Whether a server is discovered via public announcements or its public key is already known, clients MUST use the MCP initialization process to establish a connection. This flow applies to all servers and involves a client initialization request, a server initialization response, and a client initialized notification:
|
||||||
|
|
||||||
|
#### Client Initialization Request
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 25910,
|
||||||
|
"content": {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 0,
|
||||||
|
"method": "initialize",
|
||||||
|
"params": {
|
||||||
|
"protocolVersion": "2025-07-02",
|
||||||
|
"capabilities": {
|
||||||
|
"roots": {
|
||||||
|
"listChanged": true
|
||||||
|
},
|
||||||
|
"sampling": {}
|
||||||
|
},
|
||||||
|
"clientInfo": {
|
||||||
|
"name": "ExampleClient",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [["p", "<provider-pubkey>"]]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Tags:
|
||||||
|
- `p`: Provider public key, to target all the servers from a provider
|
||||||
|
|
||||||
|
#### Server Initialization Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 25910,
|
||||||
|
"pubkey": "<provider-pubkey>",
|
||||||
|
"content": {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 0,
|
||||||
|
"result": {
|
||||||
|
"protocolVersion": "2025-07-02",
|
||||||
|
"capabilities": {
|
||||||
|
"logging": {},
|
||||||
|
"prompts": {
|
||||||
|
"listChanged": true
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"subscribe": true,
|
||||||
|
"listChanged": true
|
||||||
|
},
|
||||||
|
"tools": {
|
||||||
|
"listChanged": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"serverInfo": {
|
||||||
|
"name": "ExampleServer",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"instructions": "Optional instructions for the client"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
["e", "<client-init-request-id>"],
|
||||||
|
["support_encryption"] // Optional: Presence indicates server supports encrypted messages
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Tags:
|
||||||
|
- `e`: Reference to the client's initialization request event
|
||||||
|
- `support_encryption`: Presence indicates server supports encrypted messages
|
||||||
|
|
||||||
|
#### Client Initialized Notification
|
||||||
|
|
||||||
|
After receiving the server initialization response, the client MUST send an initialized notification to indicate it is ready to begin normal operations:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 25910,
|
||||||
|
"pubkey": "<client-pubkey>",
|
||||||
|
"content": {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "notifications/initialized"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
["p", "<provider-pubkey>"] // Required: Target provider public key
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This notification completes the initialization process and signals to the server that the client has processed the server's capabilities and is ready to begin normal operations.
|
||||||
|
|
||||||
|
## Capability Operations
|
||||||
|
|
||||||
|
After discover a server publicly, or initialization, clients can interact with server capabilities.
|
||||||
|
|
||||||
|
### List Operations
|
||||||
|
|
||||||
|
All list operations follow the same structure described by MCP, with the specific capability type indicated in the method name.
|
||||||
|
|
||||||
|
- Tags:
|
||||||
|
- `p`: Provider public key
|
||||||
|
|
||||||
|
#### List Request Template
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 25910,
|
||||||
|
"pubkey": "<client-pubkey>",
|
||||||
|
"id": "<request-event-id>",
|
||||||
|
"content": {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "<capability>/list", // tools/list, resources/list, or prompts/list
|
||||||
|
"params": {
|
||||||
|
"cursor": "optional-cursor-value"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
["p", "<provider-pubkey>"] // Required: Provider's public key
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### List Response Template
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 25910,
|
||||||
|
"pubkey": "<provider-pubkey>",
|
||||||
|
"content": {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"result": {
|
||||||
|
"<items>": [
|
||||||
|
// "tools", "resources", or "prompts" based on capability
|
||||||
|
// Capability-specific item objects
|
||||||
|
],
|
||||||
|
"nextCursor": "next-page-cursor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
["e", "<request-event-id>"] // Required: Reference to the request event
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Capability-Specific Item Examples
|
||||||
|
|
||||||
|
#### Call Tool Request
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 25910,
|
||||||
|
"id": "<request-event-id>",
|
||||||
|
"pubkey": "<client-pubkey>",
|
||||||
|
"content": {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 2,
|
||||||
|
"method": "tools/call",
|
||||||
|
"params": {
|
||||||
|
"name": "get_weather",
|
||||||
|
"arguments": {
|
||||||
|
"location": "New York"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [["p", "<provider-pubkey>"]]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Call Tool Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 25910,
|
||||||
|
"pubkey": "<provider-pubkey>",
|
||||||
|
"content": {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 2,
|
||||||
|
"result": {
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isError": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [["e", "<request-event-id>"]]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For the rest of capabilities (resources, prompts, completions, ping, etc) the `content` field follows the same pattern as other MCP messages, containing a stringified simplified JSON-RPC object that adheres to the MCP specification.
|
||||||
|
|
||||||
|
### Capability Pricing
|
||||||
|
|
||||||
|
ContextVM supports pricing for capabilities through the use of `cap` tags in capability announcement or list events.
|
||||||
|
|
||||||
|
#### Pricing Tag Format
|
||||||
|
|
||||||
|
Pricing information is conveyed using the `cap` tag with the following format:
|
||||||
|
|
||||||
|
```json
|
||||||
|
["cap", "<capability-identifier>", "<price>", "<currency-unit>"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Where:
|
||||||
|
|
||||||
|
- `<capability-identifier>` is the name of the tool, prompt, or resource URI
|
||||||
|
- `<price>` is a string representing the numerical amount (e.g., "100")
|
||||||
|
- `<currency-unit>` is the currency symbol (e.g., "sats", "usd")
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
A tool list event with pricing for the `get_weather` tool:
|
||||||
|
|
||||||
|
From public server announcements:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 11317,
|
||||||
|
"content": {
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "get_weather",
|
||||||
|
"description": "Get current weather information"
|
||||||
|
// ... other tool properties
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tags": [["cap", "get_weather", "100", "sats"]]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
From capability list events:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 25910,
|
||||||
|
"pubkey": "<provider-pubkey>",
|
||||||
|
"content": {
|
||||||
|
"result": {
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "get_weather",
|
||||||
|
"description": "Get current weather information"
|
||||||
|
// ... other tool properties
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nextCursor": "next-page-cursor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
["e", "<request-event-id>"], // Required: Reference to the request event
|
||||||
|
["cap", "get_weather", "100", "sats"] // Optional: Pricing metadata
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This indicates that using the `get_weather` tool costs 100 satoshis. Clients can use this information to display pricing to users.
|
||||||
|
|
||||||
|
#### Payment Handling
|
||||||
|
|
||||||
|
When a capability has pricing information, clients and servers should handle payments. The lifecycle of request with payment follows these steps:
|
||||||
|
|
||||||
|
1. **Request**: Client sends a capability request to the server
|
||||||
|
2. **Invoice Generation**: Server sends a `notifications/payment_required` notification with a payment request (e.g., Lightning Network invoice, Cashu PaymentRequest, Payment gateway URL, etc.)
|
||||||
|
3. **Payment Verification**: Client pays and the server verifies the payment
|
||||||
|
4. **Capability Access**: Once payment is verified, the server processes the capability request, and responds with the result
|
||||||
|
|
||||||
|
Payment verification is handled by the server and can be implemented using Lightning Network zaps (NIP-57) or other payment methods.
|
||||||
|
|
||||||
|
## Encryption
|
||||||
|
|
||||||
|
ContextVM supports optional end-to-end encryption for enhanced privacy and security. This feature leverages a simplified version of NIP-17 (Private Direct Messages) for secure message encryption and NIP-59 (Gift Wrap) pattern with no 'rumor' with NIP-59 gift wrapping for metadata protection, ensuring that:
|
||||||
|
|
||||||
|
1. **Message Content Privacy**: All ContextVM message content is encrypted using NIP-44 encryption
|
||||||
|
2. **Metadata Protection**: Gift wrapping hides participant identities, timestamps, and message patterns
|
||||||
|
3. **Selective Encryption**: Clients and servers can negotiate encryption on a per-session basis
|
||||||
|
|
||||||
|
Encryption in ContextVM maintains full compatibility with the standard protocol while adding an additional privacy layer. When encryption is enabled, all kind 25910 events are encrypted using the NIP-17/NIP-59 pattern, while replaceable events (server announcements and capability lists) remain unencrypted for discoverability.
|
||||||
|
|
||||||
|
### Encryption Support Discovery
|
||||||
|
|
||||||
|
Encryption support is advertised through the `support_encryption` tag in server announcement events or direct initialization responses. The presence of this tag indicates that the server supports encryption; its absence signifies that the server does not support encryption:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pubkey": "<provider-pubkey>",
|
||||||
|
"content": {
|
||||||
|
/* server details */
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
["support_encryption"] // Presence alone indicates encryption support
|
||||||
|
// ... other tags
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Clients can discover encryption support by:
|
||||||
|
|
||||||
|
1. **Public Server Discovery**: Check for the presence of the `support_encryption` tag in server announcements (kind 11316)
|
||||||
|
2. **Direct Discovery**: Check for the presence of the `support_encryption` tag in initialization responses
|
||||||
|
3. **Encrypted Handshake**: Attempt an encrypted direct discovery, and wait for and encrypted response from the server
|
||||||
|
|
||||||
|
When encryption is enabled, ContextVM messages follow a simplified NIP-17 pattern with no 'rumor' with NIP-59 gift wrapping.
|
||||||
|
|
||||||
|
#### 1. Request Preparation
|
||||||
|
|
||||||
|
The request is prepared as usual, and should be signed:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 25910,
|
||||||
|
"id": "<request-event-id>",
|
||||||
|
"pubkey": "<client-pubkey>",
|
||||||
|
"content": {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 2,
|
||||||
|
"method": "tools/call",
|
||||||
|
"params": {
|
||||||
|
"name": "get_weather",
|
||||||
|
"arguments": {
|
||||||
|
"location": "New York"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [["p", "<provider-pubkey>"]],
|
||||||
|
"sig": "<signature>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The request is converted into a JSON string and gift-wrapped (kind 1059) with a random key, following NIP-44 encryption.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "<gift-wrap-hash>",
|
||||||
|
"pubkey": "<random-pubkey>",
|
||||||
|
"created_at": "<randomized-timestamp>",
|
||||||
|
"kind": 1059,
|
||||||
|
"tags": [["p", "<server-pubkey>"]],
|
||||||
|
"content": "<nip44-encrypted-request>",
|
||||||
|
"sig": "<random-key-signature>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Encrypted Response Structure
|
||||||
|
|
||||||
|
Server responses follow the same pattern. The response is converted into a JSON string and gift-wrapped (kind 1059) with a random key, following NIP-44 encryption.
|
||||||
|
|
||||||
|
The decrypted inner content contains the standard ContextVM response format. The id field used in responses should match the inner id field used in requests, not the id of the gift-wrap event.
|
||||||
|
|
||||||
|
#### Why a simplified NIP-17/NIP-59 pattern?
|
||||||
|
|
||||||
|
The standard implementation of NIP-17 and NIP-59 is complex and designed for private direct messages that are meant to persist in relays. Therefore, the standard uses an extra layer of encryption to prevent leakage of the original message if an attacker decrypts it. This involves a 'rumor' - an unsigned event embedded in a 'seal' event. The 'rumor' represents the original message, and because it lacks a signature, it cannot be leaked to relays as it is an invalid Nostr event. The 'seal' serves as the signature for the 'rumor'.
|
||||||
|
|
||||||
|
In contrast, ContextVM uses ephemeral events that are not intended to be stored in relays, so the 'rumor' and 'seal' events are unnecessary, but still leveraging the metadata leakage protection of NIP-59 gift wrapping.
|
||||||
|
|
||||||
|
## Notifications
|
||||||
|
|
||||||
|
All notifications in ContextVM follow the standard MCP notification format and conventions, using the unified kind 25910 event type. This includes notifications for payment requests, progress updates, and all other server-to-client or client-to-server communications.
|
||||||
|
|
||||||
|
Notifications are constructed according to the MCP notification template. The direction is determined by the `p` tag: client-to-server notifications are signed by the client's pubkey and use the server's pubkey as the `p` tag; server-to-client notifications are signed by the server's provider pubkey and use the client's pubkey as the `p` tag.
|
||||||
|
|
||||||
|
### Payment Required Notification Example
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 25910,
|
||||||
|
"pubkey": "<provider-pubkey>",
|
||||||
|
"content": {
|
||||||
|
"method": "notifications/payment_required",
|
||||||
|
"params": {
|
||||||
|
"amount": 1000,
|
||||||
|
"currency": "sats",
|
||||||
|
"invoice": "lnbc...",
|
||||||
|
"description": "Payment for tool execution"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
["p", "<client-pubkey>"],
|
||||||
|
["e", "<request-event-id>"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For long-running jobs, servers should send progress notifications frequently to indicate the job is still processing and to prevent client timeout.
|
||||||
|
|
||||||
|
## Complete Protocol Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Client
|
||||||
|
participant Relay
|
||||||
|
participant Server
|
||||||
|
|
||||||
|
opt Public Server Discovery (Catalog)
|
||||||
|
Client->>Relay: Subscribe to kinds 11316-11320
|
||||||
|
Relay-->>Client: Server Announcements and Capabilities
|
||||||
|
end
|
||||||
|
|
||||||
|
Client->>Relay: Publishes kind 25910 (method: initialize)
|
||||||
|
Relay-->>Server: Forwards initialize request
|
||||||
|
|
||||||
|
Server->>Relay: Publishes kind 25910 (initialize response)
|
||||||
|
Relay-->>Client: Forwards initialize response
|
||||||
|
|
||||||
|
Client->>Relay: Publishes kind 25910 (notification: initialized)
|
||||||
|
Relay-->>Server: Forwards initialized notification
|
||||||
|
|
||||||
|
Note over Client,Server: Capability Interaction (e.g., tools/list)
|
||||||
|
|
||||||
|
Client->>Relay: Publishes kind 25910 (method: tools/list)
|
||||||
|
Relay-->>Server: Forwards request
|
||||||
|
Server->>Relay: Publishes kind 25910 (tools/list response)
|
||||||
|
Relay-->>Client: Forwards tools/list response
|
||||||
|
|
||||||
|
Note over Client,Server: Capability Interaction (e.g., tools/call)
|
||||||
|
|
||||||
|
Client->>Relay: Publishes kind 25910 (method: tools/call)
|
||||||
|
|
||||||
|
opt Optional Payment Flow
|
||||||
|
Server->>Relay: Publishes kind 25910 (notification: payment_required)
|
||||||
|
Relay-->>Client: Forwards payment_required
|
||||||
|
Client->>Client: User Pays Invoice
|
||||||
|
end
|
||||||
|
|
||||||
|
Server->>Relay: Publishes kind 25910 (tools/call response)
|
||||||
|
Relay-->>Client: Forwards tools/call response
|
||||||
|
```
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
title: Example Guide
|
|
||||||
description: A guide in my new Starlight docs site.
|
|
||||||
---
|
|
||||||
|
|
||||||
Guides lead a user through a specific task they want to accomplish, often with a sequence of steps.
|
|
||||||
Writing a good guide requires thinking about what your users are trying to do.
|
|
||||||
|
|
||||||
## Further reading
|
|
||||||
|
|
||||||
- Read [about how-to guides](https://diataxis.fr/how-to-guides/) in the Diátaxis framework
|
|
||||||
83
src/content/docs/guides/gateway/overview.md
Normal file
83
src/content/docs/guides/gateway/overview.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
---
|
||||||
|
title: Gateway Overview
|
||||||
|
description: Understanding the NostrMCPGateway component for bridging MCP and Nostr
|
||||||
|
---
|
||||||
|
|
||||||
|
# Gateway
|
||||||
|
|
||||||
|
The `NostrMCPGateway` is a server-side bridging component that exposes a traditional MCP server to the Nostr network. It acts as a gateway, translating communication between Nostr-based clients and a standard MCP server.
|
||||||
|
|
||||||
|
## Purpose and Capabilities
|
||||||
|
|
||||||
|
The gateway manages two transports simultaneously:
|
||||||
|
|
||||||
|
1. **Nostr Server Transport**: A [`NostrServerTransport`](../transports/nostr-server-transport.md) that listens for incoming connections from clients on the Nostr network.
|
||||||
|
2. **MCP Server Transport**: A standard MCP client transport (like `StdioClientTransport`) that connects to a local or remote MCP server.
|
||||||
|
|
||||||
|
The gateway's role is to forward requests from Nostr clients to the MCP server and relay the server's responses back to the appropriate client on Nostr.
|
||||||
|
|
||||||
|
## Integration Scenarios
|
||||||
|
|
||||||
|
The `NostrMCPGateway` is ideal for:
|
||||||
|
|
||||||
|
- **Exposing Existing Servers**: If you have an existing MCP server, you can use the gateway to make it accessible to Nostr clients without modifying the server's core logic. The server continues to operate with its standard transport, while the gateway handles all Nostr-related communication.
|
||||||
|
- **Decoupling Services**: You can run your core MCP server in a secure environment and use the gateway as a public-facing entry point on the Nostr network. The gateway can be configured with its own security policies (like `allowedPublicKeys`).
|
||||||
|
- **Adding Nostr Capabilities**: It allows you to add features like public server announcements and decentralized discovery to a conventional MCP server.
|
||||||
|
|
||||||
|
## `NostrMCPGatewayOptions`
|
||||||
|
|
||||||
|
To create a `NostrMCPGateway`, you need to provide a configuration object that implements the `NostrMCPGatewayOptions` interface:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface NostrMCPGatewayOptions {
|
||||||
|
mcpServerTransport: Transport;
|
||||||
|
nostrTransportOptions: NostrServerTransportOptions;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`mcpServerTransport`**: An instance of a client-side MCP transport that the gateway will use to connect to your existing MCP server. For example, `new StdioClientTransport(...)`.
|
||||||
|
- **`nostrTransportOptions`**: The full configuration object required by the `NostrServerTransport`. This includes the `signer`, `relayHandler`, and options like `isPublicServer`.
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
This example shows how to create a gateway that connects to a local MCP server (running in a separate process) and exposes it to the Nostr network.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/stdio';
|
||||||
|
import { NostrMCPGateway } from '@ctxvm/sdk/gateway';
|
||||||
|
import { PrivateKeySigner } from '@ctxvm/sdk/signer';
|
||||||
|
import { SimpleRelayPool } from '@ctxvm/sdk/relay';
|
||||||
|
|
||||||
|
// 1. Configure the signer and relay handler for the Nostr transport
|
||||||
|
const signer = new PrivateKeySigner('your-gateway-private-key');
|
||||||
|
const relayPool = new SimpleRelayPool(['wss://relay.damus.io']);
|
||||||
|
|
||||||
|
// 2. Configure the transport to connect to your existing MCP server
|
||||||
|
const serverTransport = new StdioClientTransport({
|
||||||
|
command: 'bun',
|
||||||
|
args: ['run', 'path/to/your/mcp-server.ts'],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Create the gateway instance
|
||||||
|
const gateway = new NostrMCPGateway({
|
||||||
|
mcpServerTransport: serverTransport,
|
||||||
|
nostrTransportOptions: {
|
||||||
|
signer,
|
||||||
|
relayHandler: relayPool,
|
||||||
|
isPublicServer: true, // Announce this gateway on Nostr
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Start the gateway
|
||||||
|
await gateway.start();
|
||||||
|
|
||||||
|
console.log('Gateway is running, exposing the MCP server to Nostr.');
|
||||||
|
|
||||||
|
// To stop the gateway: await gateway.stop();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
This concludes the core components of the SDK. The final section provides practical examples of how to use these components together.
|
||||||
|
|
||||||
|
- **[Tutorials](../tutorials/client-server-communication.md)**
|
||||||
70
src/content/docs/guides/getting-started/quick-overview.md
Normal file
70
src/content/docs/guides/getting-started/quick-overview.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
title: Quick Overview
|
||||||
|
description: An overview of the @contextvm/sdk, including its modules and core concepts.
|
||||||
|
---
|
||||||
|
|
||||||
|
# SDK Quick Overview
|
||||||
|
|
||||||
|
This overview introduces the essential modules and core concepts of the `@contextvm/sdk`. Understanding these fundamentals will help you leverage the full power of the ContextVM protocol.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
`@contextvm/sdk` is distributed as an NPM package, making it easy to integrate into your project.
|
||||||
|
|
||||||
|
## Install the SDK
|
||||||
|
|
||||||
|
Run the following command in your terminal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @contextvm/sdk
|
||||||
|
```
|
||||||
|
|
||||||
|
This will install the SDK and its dependencies into your project.
|
||||||
|
|
||||||
|
**Note:** If you are using a different package manager than NPM, just replace `npm` with the appropriate command for your package manager.
|
||||||
|
|
||||||
|
## Modules Introduction
|
||||||
|
|
||||||
|
The SDK is organized into several modules, each providing a specific set of functionalities:
|
||||||
|
|
||||||
|
- **[Core](../core/interfaces.md)**: Contains fundamental definitions, constants, interfaces, and utilities (e.g., encryption, serialization). It forms the foundation of the SDK.
|
||||||
|
- **[Transports](../transports/base-nostr-transport.md)**: Critical for communication, this module provides `NostrClientTransport` and `NostrServerTransport` implementations for enabling MCP over Nostr.
|
||||||
|
- **[Signer](../signer/nostr-signer-interface.md)**: Provides cryptographic signing capabilities required for Nostr events
|
||||||
|
- **[Relay](../relay/relay-handler-interface.md)**: Manages Nostr relay connections, abstracting the complexity of relay interactions.
|
||||||
|
- **[Proxy](../proxy/overview.md)**: A client-side MCP server that connects to other servers through Nostr, exposing their capabilities locally, specially useful for clients that don't natively support Nostr transport.
|
||||||
|
- **[Gateway](../gateway/overview.md)**: An MCP server transport that binds to another MCP server, exposing its capabilities to the Nostr network, specially useful for servers that don't natively support Nostr transport.
|
||||||
|
|
||||||
|
## Core Concepts
|
||||||
|
|
||||||
|
The `@contextvm/sdk` is built around a few core concepts that enable the bridging of MCP and Nostr.
|
||||||
|
|
||||||
|
### Signers and Relay Handlers
|
||||||
|
|
||||||
|
At the heart of the SDK are two key interfaces:
|
||||||
|
|
||||||
|
- **`NostrSigner`**: An interface for signing Nostr events. The SDK includes a default `PrivateKeySigner`, but you can create a custom implementation to integrate with other signing mechanisms (e.g., Window.nostr for web, remote signers, etc).
|
||||||
|
- **`RelayHandler`**: An interface for managing connections to Nostr relays. The default `SimpleRelayPool` provides basic relay management, but you can implement your own logic for more sophisticated relay selection and management.
|
||||||
|
|
||||||
|
These components are fundamental for creating and broadcasting Nostr events, which are the backbone of ContextVM communication.
|
||||||
|
|
||||||
|
### Nostr Transports
|
||||||
|
|
||||||
|
The SDK provides two specialized transports to send and receive MCP messages over the Nostr network:
|
||||||
|
|
||||||
|
- [`NostrClientTransport`](../transports/nostr-client-transport.md): Used by MCP clients to connect to remote MCP servers exposed via Nostr.
|
||||||
|
- [`NostrServerTransport`](../transports/nostr-server-transport.md): Used by MCP servers to expose their capabilities through Nostr.
|
||||||
|
|
||||||
|
These transports handle the serialization of MCP messages into Nostr events and manage the communication flow.
|
||||||
|
|
||||||
|
### Bridging Components: Proxy and Gateway
|
||||||
|
|
||||||
|
To simplify integration with existing MCP applications, the SDK provides two high-level bridging components:
|
||||||
|
|
||||||
|
- [`NostrMCPProxy`](../proxy/overview.md): A client-side bridge that allows an MCP client to communicate with a remote MCP server over Nostr without requiring native Nostr support in the client.
|
||||||
|
- [`NostrMCPGateway`](../gateway/overview.md): A server-side bridge that exposes an existing MCP server to the Nostr network, allowing it to be discovered and used by Nostr-native clients.
|
||||||
|
|
||||||
|
These components abstract away the underlying transport complexities, making it easy to connect conventional MCP setups with the decentralized Nostr ecosystem.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Now that you have a basic understanding of the SDK's modules and concepts, you are ready to dive deeper. Explore the **Core Modules** section to learn about the fundamental interfaces and data structures.
|
||||||
45
src/content/docs/guides/index.md
Normal file
45
src/content/docs/guides/index.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
title: ContextVM SDK Documentation
|
||||||
|
description: A comprehensive guide to the ContextVM SDK
|
||||||
|
---
|
||||||
|
|
||||||
|
# @contextvm/sdk: The Official SDK for the ContextVM Protocol
|
||||||
|
|
||||||
|
Welcome to the official documentation for the **@contextvm/sdk**, a JavaScript/TypeScript library for the Context Vending Machine (ContextVM) Protocol. This SDK provides the tools to bridge Nostr and the Model Context Protocol (MCP), enabling decentralized discovery, access and exposure of computational services.
|
||||||
|
|
||||||
|
## What is ContextVM?
|
||||||
|
|
||||||
|
The Context Vending Machine (ContextVM) protocol defines how [Nostr](https://nostr.com/) and Model Context Protocol can be used to expose MCP server capabilities. It enables standardized usage of these resources through a decentralized, cryptographically secure messaging system. By integrating MCP with Nostr, ContextVM offers:
|
||||||
|
|
||||||
|
- **Discoverability**: MCP servers can be discovered through the Nostr network without centralized registries.
|
||||||
|
- **Verifiability**: All messages are cryptographically signed using Nostr's public keys.
|
||||||
|
- **Authorization**: No complex authorization logic required, just cryptography.
|
||||||
|
- **Decentralization**: No single point of failure for service discovery or communication.
|
||||||
|
- **Protocol Interoperability**: Both MCP and ContextVMs utilize JSON-RPC patterns, enabling seamless communication.
|
||||||
|
|
||||||
|
This documentation serves as the primary entry point for developers and individuals interested in learning more about ContextVM and its SDK.
|
||||||
|
|
||||||
|
## SDK Overview
|
||||||
|
|
||||||
|
The `@ctxvm/sdk` provides the necessary components to interact with the CTXVM Protocol:
|
||||||
|
|
||||||
|
- **Core Module**: Contains fundamental definitions, constants, interfaces, and utilities (e.g., encryption, serialization).
|
||||||
|
- **Transports**: Critical for communication, providing `NostrClientTransport` and `NostrServerTransport` implementations for enabling MCP over Nostr.
|
||||||
|
- **Proxy**: A client-side MCP server that connects to other servers through Nostr, exposing server capabilities locally. Particularly useful for clients that don't natively support Nostr transport.
|
||||||
|
- **Gateway**: Implements Nostr server transport, binding to another MCP server and exposing its capabilities through the Nostr network.
|
||||||
|
- **Relay**: Functionality for managing Nostr relays, abstracting relay interactions.
|
||||||
|
- **Signer**: Provides cryptographic signing capabilities required for Nostr events.
|
||||||
|
|
||||||
|
Both the Proxy and Gateway leverage Nostr transports, allowing existing MCP servers to maintain their conventional transports while gaining Nostr interoperability.
|
||||||
|
|
||||||
|
## How to Use These Docs
|
||||||
|
|
||||||
|
This documentation is structured to guide you from initial setup to advanced implementation. We recommend starting with the "Getting Started" section and then exploring the modules most relevant to your use case.
|
||||||
|
|
||||||
|
- **Getting Started**: Covers installation and a high-level overview of the SDK.
|
||||||
|
- **Core Modules**: Details the fundamental interfaces, encryption methods, and constants.
|
||||||
|
- **Transports, Signer, Relay**: Deep dives into the key components for communication and security.
|
||||||
|
- **Proxy & Gateway**: Explains how to use the bridging components.
|
||||||
|
- **Tutorials**: Provides practical, step-by-step examples.
|
||||||
|
|
||||||
|
Let's begin by setting up your environment in the [Installation Guide](./getting-started/installation.md).
|
||||||
84
src/content/docs/guides/proxy/overview.md
Normal file
84
src/content/docs/guides/proxy/overview.md
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
title: Proxy Overview
|
||||||
|
description: A client-side bridging component for the @contextvm/sdk.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Proxy
|
||||||
|
|
||||||
|
The `NostrMCPProxy` is a powerful, client-side bridging component in the `@contextvm/sdk`. Its primary function is to act as a local proxy that translates communication between a standard MCP client and a remote, Nostr-based MCP server.
|
||||||
|
|
||||||
|
## Functionality Overview
|
||||||
|
|
||||||
|
The proxy manages two transports simultaneously:
|
||||||
|
|
||||||
|
1. **MCP Host Transport**: This is a standard MCP transport (like `StdioServerTransport`) that communicates with a local MCP client application.
|
||||||
|
2. **Nostr Client Transport**: This is a [`NostrClientTransport`](../transports/nostr-client-transport.md) that communicates with the remote MCP server over the Nostr network.
|
||||||
|
|
||||||
|
The proxy sits in the middle, seamlessly forwarding messages between these two transports. When the local client sends a request, the proxy forwards it over Nostr. When the remote server sends a response, the proxy relays it back to the local client.
|
||||||
|
|
||||||
|
## Use Cases and Capabilities
|
||||||
|
|
||||||
|
The `NostrMCPProxy` is particularly useful in the following scenarios:
|
||||||
|
|
||||||
|
- **Integrating with Existing Clients**: If you have an existing MCP client that does not have native Nostr support, you can use the proxy to enable it to communicate with Nostr-based MCP servers without modifying the client's code. The client simply connects to the proxy's local transport.
|
||||||
|
- **Simplifying Client-Side Logic**: The proxy abstracts away all the complexities of Nostr communication (signing, relay management, encryption), allowing your main client application to remain simple and focused on its core tasks.
|
||||||
|
- **Local Development and Testing**: The proxy can be a valuable tool for local development, allowing you to easily test a client against a remote Nostr server.
|
||||||
|
|
||||||
|
## `NostrMCPProxyOptions`
|
||||||
|
|
||||||
|
To create a `NostrMCPProxy`, you need to provide a configuration object that implements the `NostrMCPProxyOptions` interface:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface NostrMCPProxyOptions {
|
||||||
|
mcpHostTransport: Transport;
|
||||||
|
nostrTransportOptions: NostrTransportOptions;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`mcpHostTransport`**: An instance of a server-side MCP transport that the local client will connect to. For example, `new StdioServerTransport()`.
|
||||||
|
- **`nostrTransportOptions`**: The full configuration object required by the `NostrClientTransport`. This includes the `signer`, `relayHandler`, and the remote `serverPubkey`.
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
This example demonstrates how to create a proxy that listens for a local client over standard I/O and connects to a remote server over Nostr.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/stdio';
|
||||||
|
import { NostrMCPProxy } from '@ctxvm/sdk/proxy';
|
||||||
|
import { PrivateKeySigner } from '@ctxvm/sdk/signer';
|
||||||
|
import { SimpleRelayPool } from '@ctxvm/sdk/relay';
|
||||||
|
|
||||||
|
// 1. Configure the signer and relay handler for the Nostr connection
|
||||||
|
const signer = new PrivateKeySigner('your-private-key');
|
||||||
|
const relayPool = new SimpleRelayPool(['wss://relay.damus.io']);
|
||||||
|
const REMOTE_SERVER_PUBKEY = 'remote-server-public-key';
|
||||||
|
|
||||||
|
// 2. Configure the transport for the local client
|
||||||
|
// In this case, a stdio transport that the local client can connect to
|
||||||
|
const hostTransport = new StdioServerTransport();
|
||||||
|
|
||||||
|
// 3. Create the proxy instance
|
||||||
|
const proxy = new NostrMCPProxy({
|
||||||
|
mcpHostTransport: hostTransport,
|
||||||
|
nostrTransportOptions: {
|
||||||
|
signer,
|
||||||
|
relayHandler: relayPool,
|
||||||
|
serverPubkey: REMOTE_SERVER_PUBKEY,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Start the proxy
|
||||||
|
await proxy.start();
|
||||||
|
|
||||||
|
console.log('Proxy is running. Connect your local MCP client.');
|
||||||
|
|
||||||
|
// To stop the proxy: await proxy.stop();
|
||||||
|
```
|
||||||
|
|
||||||
|
In this setup, a separate MCP client process could connect to this proxy's `StdioServerTransport` and it would be transparently communicating with the remote server on Nostr.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Next, we'll look at the server-side equivalent of the proxy:
|
||||||
|
|
||||||
|
- **[Gateway](../gateway/overview.md)**
|
||||||
92
src/content/docs/guides/relay/custom-relay-handler.md
Normal file
92
src/content/docs/guides/relay/custom-relay-handler.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
---
|
||||||
|
title: Custom Relay Handler Development
|
||||||
|
description: Learn how to create a custom relay handler for the @contextvm/sdk.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Custom Relay Handler Development
|
||||||
|
|
||||||
|
The `@contextvm/sdk`'s-pluggable architecture, centered around the [`RelayHandler`](./relay-handler-interface.md) interface, allows developers to implement custom logic for managing Nostr-relay connections. This is particularly useful for advanced use cases that require more sophisticated behavior than what the default [`SimpleRelayPool`](./simple-relay-pool.md) provides.
|
||||||
|
|
||||||
|
## Why Create a Custom Relay Handler?
|
||||||
|
|
||||||
|
You might want to create a custom `RelayHandler` for several reasons:
|
||||||
|
|
||||||
|
- **Intelligent Relay Selection**: To dynamically select relays based on performance, reliability, or the specific type of data being requested. For example, you might use a different set of relays for fetching user metadata versus broadcasting messages.
|
||||||
|
- **Auth Relays**: To integrate with auth relays that require authentication or specific connection logic.
|
||||||
|
- **Dynamic Relay Discovery**: To discover and connect to new relays at runtime, rather than using a static list.
|
||||||
|
- **Custom Caching**: To implement a custom caching layer to reduce redundant requests to relays.
|
||||||
|
- **Resiliency and-failover**: To build more robust-failover logic, such as automatically retrying failed connections or switching to backup relays.
|
||||||
|
|
||||||
|
## Implementing the `RelayHandler` Interface
|
||||||
|
|
||||||
|
To create a custom relay handler, you need to create a class that implements the `RelayHandler` interface. This involves implementing five methods: `connect`, `disconnect`, `publish`, `subscribe`, and `unsubscribe`.
|
||||||
|
|
||||||
|
### Example: A Handler with logging
|
||||||
|
|
||||||
|
Here is a simple example of a custom `RelayHandler` that wraps the default `SimpleRelayPool` and adds logging to each operation. This illustrates how you can extend or compose existing handlers.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { RelayHandler } from '@ctxvm/sdk/core';
|
||||||
|
import { SimpleRelayPool } from '@ctxvm/sdk/relay';
|
||||||
|
import { Filter, NostrEvent } from 'nostr-tools';
|
||||||
|
|
||||||
|
class LoggingRelayHandler implements RelayHandler {
|
||||||
|
private readonly innerHandler: RelayHandler;
|
||||||
|
|
||||||
|
constructor(relayUrls: string[]) {
|
||||||
|
this.innerHandler = new SimpleRelayPool(relayUrls);
|
||||||
|
console.log(`[LoggingRelayHandler] Initialized with relays: ${relayUrls.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect(): Promise<void> {
|
||||||
|
console.log('[LoggingRelayHandler] Attempting to connect...');
|
||||||
|
await this.innerHandler.connect();
|
||||||
|
console.log('[LoggingRelayHandler] Connected successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
async disconnect(): Promise<void> {
|
||||||
|
console.log('[LoggingRelayHandler] Disconnecting...');
|
||||||
|
await this.innerHandler.disconnect();
|
||||||
|
console.log('[LoggingRelayHandler] Disconnected.');
|
||||||
|
}
|
||||||
|
|
||||||
|
publish(event: NostrEvent): void {
|
||||||
|
console.log(`[LoggingRelayHandler] Publishing event kind ${event.kind}...`);
|
||||||
|
this.innerHandler.publish(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(filters: Filter[], onEvent: (event: NostrEvent) => void): void {
|
||||||
|
console.log(`[LoggingRelayHandler] Subscribing with filters:`, filters);
|
||||||
|
this.innerHandler.subscribe(filters, (event) => {
|
||||||
|
console.log(`[LoggingRelayHandler] Received event kind ${event.kind}`);
|
||||||
|
onEvent(event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe(): void {
|
||||||
|
console.log('[LoggingRelayHandler] Unsubscribing from all.');
|
||||||
|
this.innerHandler.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
const loggingHandler = new LoggingRelayHandler(['wss://relay.damus.io']);
|
||||||
|
|
||||||
|
const transport = new NostrClientTransport({
|
||||||
|
relayHandler: loggingHandler,
|
||||||
|
// ... other options
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
This example demonstrates the composition pattern. For a more advanced handler, you might use a different underlying relay management library or implement the connection logic from scratch using WebSockets.
|
||||||
|
|
||||||
|
## Using Your Custom Relay Handler
|
||||||
|
|
||||||
|
Once your custom handler class is created, you can instantiate it and pass it to any component that requires a `RelayHandler`, such as the `NostrClientTransport` or `NostrServerTransport`. The SDK will then use your custom logic for all relay interactions.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
With the `Relay` component covered, we will now look at the high-level bridging components provided by the SDK.
|
||||||
|
|
||||||
|
- **[Proxy](./proxy/overview.md)**
|
||||||
|
- **[Gateway](./gateway/overview.md)**
|
||||||
51
src/content/docs/guides/relay/relay-handler-interface.md
Normal file
51
src/content/docs/guides/relay/relay-handler-interface.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
title: RelayHandler Interface
|
||||||
|
description: An interface for managing Nostr relay connections.
|
||||||
|
---
|
||||||
|
|
||||||
|
# `RelayHandler` Interface
|
||||||
|
|
||||||
|
The `RelayHandler` interface is another crucial abstraction in the `@contextvm/sdk`. It defines the standard for managing connections to Nostr relays, which are the backbone of the Nostr network responsible for message propagation.
|
||||||
|
|
||||||
|
## Purpose and Design
|
||||||
|
|
||||||
|
The `RelayHandler`'s purpose is to abstract the complexities of relay management, including:
|
||||||
|
|
||||||
|
- Connecting and disconnecting from a set of relays.
|
||||||
|
- Subscribing to events with specific filters.
|
||||||
|
- Publishing events to the network.
|
||||||
|
- Handling relay-specific logic, such as connection retries, timeouts, and relay selection.
|
||||||
|
|
||||||
|
By depending on this interface, the SDK's transports can remain agnostic about the specific relay management strategy being used. This allows developers to plug in different relay handlers to suit their needs.
|
||||||
|
|
||||||
|
## Interface Definition
|
||||||
|
|
||||||
|
The `RelayHandler` interface is defined in [`core/interfaces.ts`](../core/interfaces.md#relayhandler) and includes several key methods:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface RelayHandler {
|
||||||
|
connect(): Promise<void>;
|
||||||
|
disconnect(): Promise<void>;
|
||||||
|
subscribe(filters: Filter[], onEvent: (event: NostrEvent) => void): void;
|
||||||
|
unsubscribe(): void;
|
||||||
|
publish(event: NostrEvent): void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `connect()`: Establishes connections to the configured relays.
|
||||||
|
- `disconnect()`: Closes connections to all relays.
|
||||||
|
- `subscribe(filters, onEvent)`: Creates a subscription on the connected relays, listening for events that match the provided filters and passing them to the `onEvent` callback.
|
||||||
|
- `unsubscribe()`: Closes all active subscriptions.
|
||||||
|
- `publish(event)`: Publishes a Nostr event to the connected relays.
|
||||||
|
|
||||||
|
## Implementations
|
||||||
|
|
||||||
|
The SDK provides a default implementation for common use cases and allows for custom implementations for advanced scenarios.
|
||||||
|
|
||||||
|
- **[SimpleRelayPool](./simple-relay-pool.md)**: The default implementation, which manages a simple pool of relays.
|
||||||
|
- **[Custom Relay Handler](./custom-relay-handler.md)**: A guide to creating your own relay handler by implementing the `RelayHandler` interface.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- Learn about the default implementation: **[SimpleRelayPool](./simple-relay-pool.md)**
|
||||||
|
- Learn how to create your own: **[Custom Relay Handler](./custom-relay-handler.md)**
|
||||||
68
src/content/docs/guides/relay/simple-relay-pool.md
Normal file
68
src/content/docs/guides/relay/simple-relay-pool.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
title: SimpleRelayPool
|
||||||
|
description: A default relay handler implementation for the @contextvm/sdk.
|
||||||
|
---
|
||||||
|
|
||||||
|
# `SimpleRelayPool`
|
||||||
|
|
||||||
|
The `SimpleRelayPool` is the default implementation of the [`RelayHandler`](./relay-handler-interface.md) interface provided by the `@contextvm/sdk`. It uses the `SimplePool` from the `nostr-tools` library to manage connections to a list of specified relays.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The `SimpleRelayPool` provides a straightforward way to manage relay connections for most common use cases. Its responsibilities include:
|
||||||
|
|
||||||
|
- Connecting to a predefined list of Nostr relays.
|
||||||
|
- Publishing events to all relays in the pool.
|
||||||
|
- Subscribing to events from all relays in the pool.
|
||||||
|
- Managing the lifecycle of connections and subscriptions.
|
||||||
|
|
||||||
|
It is a simple but effective solution for applications that need to interact with a static set of relays.
|
||||||
|
|
||||||
|
## `constructor(relayUrls: string[])`
|
||||||
|
|
||||||
|
The constructor takes a single argument:
|
||||||
|
|
||||||
|
- **`relayUrls`**: An array of strings, where each string is the URL of a Nostr relay (e.g., `wss://relay.damus.io`).
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { SimpleRelayPool } from '@ctxvm/sdk/relay';
|
||||||
|
import { NostrClientTransport } from '@ctxvm/sdk/transport';
|
||||||
|
|
||||||
|
// 1. Define the list of relays you want to connect to
|
||||||
|
const myRelays = [
|
||||||
|
'wss://relay.damus.io',
|
||||||
|
'wss://relay.primal.net',
|
||||||
|
'wss://nos.lol',
|
||||||
|
];
|
||||||
|
|
||||||
|
// 2. Create an instance of the SimpleRelayPool
|
||||||
|
const relayPool = new SimpleRelayPool(myRelays);
|
||||||
|
|
||||||
|
// 3. Pass the instance to a transport
|
||||||
|
const transport = new NostrClientTransport({
|
||||||
|
relayHandler: relayPool,
|
||||||
|
// ... other options
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
The `SimpleRelayPool` wraps the `SimplePool` from `nostr-tools` and implements the methods of the `RelayHandler` interface:
|
||||||
|
|
||||||
|
- **`connect()`**: Iterates through the provided `relayUrls` and calls `pool.ensureRelay()` for each one, which establishes a connection if one doesn't already exist.
|
||||||
|
- **`disconnect()`**: Closes the connections to the specified relays.
|
||||||
|
- **`publish(event)`**: Publishes the given event to all relays in the pool by calling `pool.publish()`.
|
||||||
|
- **`subscribe(filters, onEvent)`**: Creates a subscription on all relays in the pool using `pool.subscribeMany()`. It tracks all active subscriptions so they can be closed later.
|
||||||
|
- **`unsubscribe()`**: Closes all active subscriptions that were created through the `subscribe` method.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
The `SimpleRelayPool` is designed for simplicity. It connects to all provided relays and does not include advanced features.
|
||||||
|
|
||||||
|
For applications that require more sophisticated relay management, you may want to create a [Custom Relay Handler](./custom-relay-handler.md).
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- Learn how to build a custom relay handler: **[Custom Relay Handler](./custom-relay-handler.md)**
|
||||||
93
src/content/docs/guides/signer/custom-signer-development.md
Normal file
93
src/content/docs/guides/signer/custom-signer-development.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
---
|
||||||
|
title: Custom Signer Development
|
||||||
|
description: Learn how to create a custom signer for the @contextvm/sdk.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Custom Signer Development
|
||||||
|
|
||||||
|
One of the key design features of the `@contextvm/sdk` is its modularity, which is exemplified by the [`NostrSigner`](./nostr-signer-interface.md) interface. By creating your own implementation of this interface, you can integrate the SDK with any key management system, such as a hardware wallet, a remote signing service (like an HSM), or a browser extension.
|
||||||
|
|
||||||
|
## Why Create a Custom Signer?
|
||||||
|
|
||||||
|
While the [`PrivateKeySigner`](./private-key-signer.md) is suitable for many development and server-side scenarios, a custom signer is often necessary when:
|
||||||
|
|
||||||
|
- **Security is paramount**: You need to keep private keys isolated from the main application logic, for example, in a hardware security module (HSM) or a secure enclave.
|
||||||
|
- **Interacting with external wallets**: Your application needs to request signatures from a user's wallet, such as a browser extension (e.g., Alby, Noster) or a mobile wallet.
|
||||||
|
- **Complex key management**: Your application uses a more complex key management architecture that doesn't involve direct access to raw private keys.
|
||||||
|
|
||||||
|
## Implementing the `NostrSigner` Interface
|
||||||
|
|
||||||
|
To create a custom signer, you need to create a class that implements the `NostrSigner` interface. This involves implementing two main methods: `getPublicKey()` and `signEvent()`, as well as an optional `nip44` object for encryption.
|
||||||
|
|
||||||
|
### Example: A NIP-07 Browser Signer (window.nostr)
|
||||||
|
|
||||||
|
A common use case for a custom signer is in a web application that needs to interact with a Nostr browser extension (like Alby, nos2x, or Blockcore) that exposes the `window.nostr` object according to [NIP-07](https://github.com/nostr-protocol/nips/blob/master/07.md). This allows the application to request signatures and encryption from the user's wallet without ever handling private keys directly.
|
||||||
|
|
||||||
|
Here is how you could implement a `NostrSigner` that wraps the `window.nostr` object:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { NostrSigner } from '@ctxvm/sdk/core';
|
||||||
|
import { UnsignedEvent, NostrEvent } from 'nostr-tools';
|
||||||
|
|
||||||
|
// Define the NIP-07 window.nostr interface for type-safety
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
nostr?: {
|
||||||
|
getPublicKey(): Promise<string>;
|
||||||
|
signEvent(event: UnsignedEvent): Promise<NostrEvent>;
|
||||||
|
nip44?: {
|
||||||
|
encrypt(pubkey: string, plaintext: string): Promise<string>;
|
||||||
|
decrypt(pubkey: string, ciphertext: string): Promise<string>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Nip07Signer implements NostrSigner {
|
||||||
|
constructor() {
|
||||||
|
if (!window.nostr) {
|
||||||
|
throw new Error('NIP-07 compatible browser extension not found.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPublicKey(): Promise<string> {
|
||||||
|
if (!window.nostr) throw new Error('window.nostr not found.');
|
||||||
|
return await window.nostr.getPublicKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
async signEvent(event: UnsignedEvent): Promise<NostrEvent> {
|
||||||
|
if (!window.nostr) throw new Error('window.nostr not found.');
|
||||||
|
return await window.nostr.signEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
nip44 = {
|
||||||
|
encrypt: async (pubkey: string, plaintext: string): Promise<string> => {
|
||||||
|
if (!window.nostr?.nip44) {
|
||||||
|
throw new Error('The extension does not support NIP-44 encryption.');
|
||||||
|
}
|
||||||
|
return await window.nostr.nip44.encrypt(pubkey, plaintext);
|
||||||
|
},
|
||||||
|
|
||||||
|
decrypt: async (pubkey: string, ciphertext: string): Promise<string> => {
|
||||||
|
if (!window.nostr?.nip44) {
|
||||||
|
throw new Error('The extension does not support NIP-44 decryption.');
|
||||||
|
}
|
||||||
|
return await window.nostr.nip44.decrypt(pubkey, ciphertext);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementing `nip44` for Decryption
|
||||||
|
|
||||||
|
When using a NIP-07 signer, the `nip44` implementation is straightforward, as you can see in the example above. You simply delegate the calls to the `window.nostr.nip44` object.
|
||||||
|
|
||||||
|
It's important to include checks to ensure that the user's browser extension supports `nip44`, as it is an optional part of the NIP-07 specification. If the extension does not support it, you should throw an error to prevent unexpected behavior.
|
||||||
|
|
||||||
|
## Using Your Custom Signer
|
||||||
|
|
||||||
|
Once your custom signer class is created, you can instantiate it and pass it to any component that requires a `NostrSigner`, such as the `NostrClientTransport` or `NostrServerTransport`. The rest of the SDK will use your custom implementation seamlessly.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
With the `Signer` component covered, let's move on to the **[Relay](./relay-handler-interface.md)**, which handles another critical aspect of Nostr communication: managing connections to relays.
|
||||||
60
src/content/docs/guides/signer/nostr-signer-interface.md
Normal file
60
src/content/docs/guides/signer/nostr-signer-interface.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
title: NostrSigner Interface
|
||||||
|
description: An interface for signing Nostr events.
|
||||||
|
---
|
||||||
|
|
||||||
|
# `NostrSigner` Interface
|
||||||
|
|
||||||
|
The `NostrSigner` interface is a central component of the `@contextvm/sdk`, defining the standard for cryptographic signing operations. Every Nostr event must be signed by a private key to be considered valid, and this interface provides a consistent, pluggable way to handle this requirement.
|
||||||
|
|
||||||
|
## Purpose and Design
|
||||||
|
|
||||||
|
The primary purpose of the `NostrSigner` is to abstract the process of event signing. By depending on this interface rather than a concrete implementation, the SDK's transports and other components can remain agnostic about how and where private keys are stored and used.
|
||||||
|
|
||||||
|
This design offers several key benefits:
|
||||||
|
|
||||||
|
- **Security**: Private keys can be managed in secure environments (e.g., web extensions, hardware wallets, dedicated signing services) without exposing them to the application logic.
|
||||||
|
- **Flexibility**: Developers can easily swap out the default signer with a custom implementation that meets their specific needs.
|
||||||
|
- **Modularity**: The signing logic is decoupled from the communication logic, leading to a cleaner, more maintainable codebase.
|
||||||
|
|
||||||
|
## Interface Definition
|
||||||
|
|
||||||
|
The `NostrSigner` interface is defined in [`core/interfaces.ts`](../core/interfaces.md#nostrsigner).
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface NostrSigner {
|
||||||
|
getPublicKey(): Promise<string>;
|
||||||
|
signEvent(event: EventTemplate): Promise<NostrEvent>;
|
||||||
|
|
||||||
|
// Optional NIP-04 encryption support (deprecated)
|
||||||
|
nip04?: {
|
||||||
|
encrypt: (pubkey: string, plaintext: string) => Promise<string>;
|
||||||
|
decrypt: (pubkey: string, ciphertext: string) => Promise<string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Optional NIP-44 encryption support
|
||||||
|
nip44?: {
|
||||||
|
encrypt: (pubkey: string, plaintext: string) => Promise<string>;
|
||||||
|
decrypt: (pubkey: string, ciphertext: string) => Promise<string>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `getPublicKey()`: Asynchronously returns the public key corresponding to the signer's private key.
|
||||||
|
- `signEvent(event)`: Takes an unsigned Nostr event, signs it, and returns the signature.
|
||||||
|
- `nip04`: (Deprecated) Provides NIP-04 encryption support.
|
||||||
|
- `nip44`: Provides NIP-44 encryption support.
|
||||||
|
|
||||||
|
Any class that implements this interface can be used as a signer throughout the SDK.
|
||||||
|
|
||||||
|
## Implementations
|
||||||
|
|
||||||
|
The SDK provides a default implementation for common use cases and allows for custom implementations for advanced scenarios.
|
||||||
|
|
||||||
|
- **[PrivateKeySigner](./private-key-signer.md)**: The default implementation, which takes a raw private key string and performs signing operations locally.
|
||||||
|
- **[Custom Signer Development](./custom-signer-development.md)**: A guide to creating your own signer by implementing the `NostrSigner` interface.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- Learn about the default implementation: **[PrivateKeySigner](./private-key-signer.md)**
|
||||||
|
- Learn how to create your own: **[Custom Signer Development](./custom-signer-development.md)**
|
||||||
70
src/content/docs/guides/signer/private-key-signer.md
Normal file
70
src/content/docs/guides/signer/private-key-signer.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
title: PrivateKeySigner
|
||||||
|
description: A default signer implementation for the @contextvm/sdk.
|
||||||
|
---
|
||||||
|
|
||||||
|
# `PrivateKeySigner`
|
||||||
|
|
||||||
|
The `PrivateKeySigner` is the default implementation of the [`NostrSigner`](./nostr-signer-interface.md) interface provided by the `@contextvm/sdk`. It is a straightforward and easy-to-use signer that operates directly on a raw private key provided as a hexadecimal string.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The `PrivateKeySigner` is designed for scenarios where the private key is readily available in the application's environment. It handles all the necessary cryptographic operations locally, including:
|
||||||
|
|
||||||
|
- Deriving the corresponding public key.
|
||||||
|
- Signing Nostr events.
|
||||||
|
- Encrypting and decrypting messages using NIP-44.
|
||||||
|
|
||||||
|
## `constructor(privateKey: string)`
|
||||||
|
|
||||||
|
The constructor takes a single argument:
|
||||||
|
|
||||||
|
- **`privateKey`**: A hexadecimal string representing the 32-byte Nostr private key.
|
||||||
|
|
||||||
|
When instantiated, the `PrivateKeySigner` will immediately convert the hex string into a `Uint8Array` and derive the public key, which is then cached for future calls to `getPublicKey()`.
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { PrivateKeySigner } from '@ctxvm/sdk/signer';
|
||||||
|
|
||||||
|
// Replace with a securely stored private key
|
||||||
|
const privateKeyHex = 'your-32-byte-private-key-in-hex';
|
||||||
|
|
||||||
|
const signer = new PrivateKeySigner(privateKeyHex);
|
||||||
|
|
||||||
|
// You can now pass this signer instance to a transport
|
||||||
|
const transportOptions = {
|
||||||
|
signer: signer,
|
||||||
|
// ... other options
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Methods
|
||||||
|
|
||||||
|
The `PrivateKeySigner` implements all the methods required by the `NostrSigner` interface.
|
||||||
|
|
||||||
|
### `async getPublicKey(): Promise<string>`
|
||||||
|
|
||||||
|
Returns a promise that resolves with the public key corresponding to the private key provided in the constructor.
|
||||||
|
|
||||||
|
### `async signEvent(event: UnsignedEvent): Promise<NostrEvent>`
|
||||||
|
|
||||||
|
Takes an unsigned Nostr event, signs it using the private key, and returns a promise that resolves with the finalized, signed `NostrEvent`.
|
||||||
|
|
||||||
|
### `nip44`
|
||||||
|
|
||||||
|
The `PrivateKeySigner` also provides a `nip44` object that implements NIP-44 encryption and decryption. This is used internally by the transports when encryption is enabled, and it allows the `decryptMessage` function to work seamlessly with this signer.
|
||||||
|
|
||||||
|
- `encrypt(pubkey, plaintext)`: Encrypts a plaintext message for a given recipient public key.
|
||||||
|
- `decrypt(pubkey, ciphertext)`: Decrypts a ciphertext message received from a given sender public key.
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
While the `PrivateKeySigner` is convenient, it requires you to handle a raw private key directly in your application code. **It is crucial to manage this key securely.** Avoid hard-coding private keys in your source code. Instead, use environment variables or a secure secret management system to load the key at runtime.
|
||||||
|
|
||||||
|
For applications requiring a higher level of security, consider creating a custom signer that interacts with a hardware wallet or a remote signing service.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- Learn how to build a custom signer: **[Custom Signer Development](./custom-signer-development.md)**
|
||||||
56
src/content/docs/guides/transports/base-nostr-transport.md
Normal file
56
src/content/docs/guides/transports/base-nostr-transport.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
title: Base Nostr Transport
|
||||||
|
description: An abstract class that provides the core functionality for all Nostr-based transports in the @contextvm/sdk.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Base Nostr Transport
|
||||||
|
|
||||||
|
The `BaseNostrTransport` is an abstract class that provides the core functionality for all Nostr-based transports in the `@contextvm/sdk`. It serves as the foundation for the [`NostrClientTransport`](./nostr-client-transport.md) and [`NostrServerTransport`](./nostr-server-transport.md), handling the common logic for connecting to relays, managing subscriptions, and converting messages between the MCP and Nostr formats.
|
||||||
|
|
||||||
|
## Core Responsibilities
|
||||||
|
|
||||||
|
The `BaseNostrTransport` is responsible for:
|
||||||
|
|
||||||
|
- **Connection Management**: Establishing and terminating connections to the Nostr relay network via a `RelayHandler`.
|
||||||
|
- **Event Serialization**: Converting MCP JSON-RPC messages into Nostr events and vice-versa.
|
||||||
|
- **Cryptographic Operations**: Signing Nostr events using a `NostrSigner`.
|
||||||
|
- **Message Publishing**: Publishing events to the Nostr network.
|
||||||
|
- **Subscription Management**: Creating and managing subscriptions to listen for relevant events.
|
||||||
|
- **Encryption Handling**: Managing the encryption and decryption of messages based on the configured `EncryptionMode`.
|
||||||
|
|
||||||
|
## `BaseNostrTransportOptions`
|
||||||
|
|
||||||
|
When creating a transport that extends `BaseNostrTransport`, you must provide a configuration object that implements the `BaseNostrTransportOptions` interface:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface BaseNostrTransportOptions {
|
||||||
|
signer: NostrSigner;
|
||||||
|
relayHandler: RelayHandler;
|
||||||
|
encryptionMode?: EncryptionMode;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`signer`**: An instance of a `NostrSigner` for signing events. This is a required parameter.
|
||||||
|
- **`relayHandler`**: An instance of a `RelayHandler` for managing relay connections. This is a required parameter.
|
||||||
|
- **`encryptionMode`**: An optional `EncryptionMode` enum that determines the encryption policy for the transport. Defaults to `OPTIONAL`.
|
||||||
|
|
||||||
|
## Key Methods
|
||||||
|
|
||||||
|
The `BaseNostrTransport` provides several protected methods that are used by its subclasses to implement their specific logic:
|
||||||
|
|
||||||
|
- `connect()`: Connects to the configured Nostr relays.
|
||||||
|
- `disconnect()`: Disconnects from the relays and clears subscriptions.
|
||||||
|
- `subscribe(filters, onEvent)`: Subscribes to Nostr events that match the given filters.
|
||||||
|
- `sendMcpMessage(...)`: Converts an MCP message to a Nostr event, signs it, optionally encrypts it, and publishes it to the network.
|
||||||
|
- `createSubscriptionFilters(...)`: A helper method to create standard filters for listening to messages directed at a specific public key.
|
||||||
|
|
||||||
|
## How It Fits Together
|
||||||
|
|
||||||
|
The `BaseNostrTransport` encapsulates the shared logic of Nostr communication, allowing the `NostrClientTransport` and `NostrServerTransport` to focus on their specific roles in the client-server interaction model. By building on this common base, the SDK ensures consistent behavior and a unified approach to handling MCP over Nostr.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Now that you understand the foundation of the Nostr transports, let's explore the concrete implementations:
|
||||||
|
|
||||||
|
- **[Nostr Client Transport](./nostr-client-transport.md)**: For building MCP clients that communicate over Nostr.
|
||||||
|
- **[Nostr Server Transport](./nostr-server-transport.md)**: For exposing MCP servers to the Nostr network.
|
||||||
88
src/content/docs/guides/transports/nostr-client-transport.md
Normal file
88
src/content/docs/guides/transports/nostr-client-transport.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
---
|
||||||
|
title: Nostr Client Transport
|
||||||
|
description: A client-side component for communicating with MCP servers over Nostr.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Nostr Client Transport
|
||||||
|
|
||||||
|
The `NostrClientTransport` is a key component of the `@contextvm/sdk`, enabling MCP clients to communicate with remote MCP servers over the Nostr network. It implements the `Transport` interface from the `@modelcontextprotocol/sdk`, making it a plug-and-play solution for any MCP client.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The `NostrClientTransport` handles all the complexities of Nostr-based communication, including:
|
||||||
|
|
||||||
|
- Connecting to Nostr relays.
|
||||||
|
- Subscribing to events from a specific server.
|
||||||
|
- Sending MCP requests as Nostr events.
|
||||||
|
- Receiving and processing responses and notifications.
|
||||||
|
- Handling encryption and decryption of messages.
|
||||||
|
|
||||||
|
By using this transport, an MCP client can interact with a Nostr-enabled MCP server without needing to implement any Nostr-specific logic itself.
|
||||||
|
|
||||||
|
## `NostrTransportOptions`
|
||||||
|
|
||||||
|
To create an instance of `NostrClientTransport`, you must provide a configuration object that implements the `NostrTransportOptions` interface:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface NostrTransportOptions extends BaseNostrTransportOptions {
|
||||||
|
serverPubkey: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`serverPubkey`**: The public key of the target MCP server. The transport will only listen for events from this public key.
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
Here's how you can use the `NostrClientTransport` with an MCP client from the `@modelcontextprotocol/sdk`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Client } from '@modelcontextprotocol/sdk/client';
|
||||||
|
import { NostrClientTransport } from '@ctxvm/sdk/transport';
|
||||||
|
import { EncryptionMode } from '@ctxvm/sdk/core';
|
||||||
|
import { PrivateKeySigner } from '@ctxvm/sdk/signer';
|
||||||
|
import { SimpleRelayPool } from '@ctxvm/sdk/relay';
|
||||||
|
|
||||||
|
// 1. Configure the signer and relay handler
|
||||||
|
const signer = new PrivateKeySigner('your-private-key'); // Replace with your actual private key
|
||||||
|
const relayPool = new SimpleRelayPool(['wss://relay.damus.io']);
|
||||||
|
|
||||||
|
// 2. Set the public key of the target server
|
||||||
|
const REMOTE_SERVER_PUBKEY = 'remote-server-public-key';
|
||||||
|
|
||||||
|
// 3. Create the transport instance
|
||||||
|
const clientNostrTransport = new NostrClientTransport({
|
||||||
|
signer,
|
||||||
|
relayHandler: relayPool,
|
||||||
|
serverPubkey: REMOTE_SERVER_PUBKEY,
|
||||||
|
encryptionMode: EncryptionMode.OPTIONAL,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Create and connect the MCP client
|
||||||
|
const mcpClient = new Client();
|
||||||
|
await mcpClient.connect(clientNostrTransport);
|
||||||
|
|
||||||
|
// 5. Use the client to interact with the server
|
||||||
|
const tools = await mcpClient.listTools();
|
||||||
|
console.log('Available tools:', tools);
|
||||||
|
|
||||||
|
// 6. Close the connection when done
|
||||||
|
// await mcpClient.close();
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. **`start()`**: When `mcpClient.connect()` is called, it internally calls the transport's `start()` method. This method connects to the relays and subscribes to events targeting the client's public key.
|
||||||
|
2. **`send(message)`**: When you call an MCP method like `mcpClient.listTools()`, the client creates a JSON-RPC request and passes it to the transport's `send()` method. The transport then:
|
||||||
|
- Wraps the message in a Nostr event.
|
||||||
|
- Signs the event.
|
||||||
|
- Optionally encrypts it.
|
||||||
|
- Publishes it to the relays, targeting the `serverPubkey`.
|
||||||
|
3. **Event Processing**: The transport listens for incoming events. When an event is received, it is decrypted (if necessary) and converted back into a JSON-RPC message.
|
||||||
|
- If the message is a **response** (correlated by the original event ID), it is passed to the MCP client to resolve the pending request.
|
||||||
|
- If the message is a **notification**, it is emitted through the `onmessage` handler.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Next, we will look at the server-side counterpart to this transport:
|
||||||
|
|
||||||
|
- **[Nostr Server Transport](./nostr-server-transport.md)**: For exposing MCP servers to the Nostr network.
|
||||||
98
src/content/docs/guides/transports/nostr-server-transport.md
Normal file
98
src/content/docs/guides/transports/nostr-server-transport.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
---
|
||||||
|
title: Nostr Server Transport
|
||||||
|
description: A server-side component for exposing MCP servers over Nostr.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Nostr Server Transport
|
||||||
|
|
||||||
|
The `NostrServerTransport` is the server-side counterpart to the [`NostrClientTransport`](./nostr-client-transport.md). It allows an MCP server to expose its capabilities to the Nostr network, making them discoverable and usable by any Nostr-enabled client. Like the client transport, it implements the `Transport` interface from the `@modelcontextprotocol/sdk`.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The `NostrServerTransport` is responsible for:
|
||||||
|
|
||||||
|
- Listening for incoming MCP requests from Nostr clients.
|
||||||
|
- Managing individual client sessions and their state (e.g., initialization, encryption).
|
||||||
|
- Handling request/response correlation to ensure responses are sent to the correct client.
|
||||||
|
- Sending responses and notifications back to clients over Nostr.
|
||||||
|
- Optionally announcing the server and its capabilities to the network for public discovery.
|
||||||
|
|
||||||
|
## `NostrServerTransportOptions`
|
||||||
|
|
||||||
|
The transport is configured via the `NostrServerTransportOptions` interface:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface NostrServerTransportOptions extends BaseNostrTransportOptions {
|
||||||
|
serverInfo?: ServerInfo;
|
||||||
|
isPublicServer?: boolean;
|
||||||
|
allowedPublicKeys?: string[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`serverInfo`**: (Optional) Information about the server (`name`, `picture`, `website`) to be used in public announcements.
|
||||||
|
- **`isPublicServer`**: (Optional) If `true`, the transport will automatically announce the server's capabilities on the Nostr network. Defaults to `false`.
|
||||||
|
- **`allowedPublicKeys`**: (Optional) A list of client public keys that are allowed to connect. If not provided, any client can connect.
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
Here's how to use the `NostrServerTransport` with an `McpServer` from the `@modelcontextprotocol/sdk`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { McpServer } from '@modelcontextprotocol/sdk/server';
|
||||||
|
import { NostrServerTransport } from '@ctxvm/sdk/transport';
|
||||||
|
import { PrivateKeySigner } from '@ctxvm/sdk/signer';
|
||||||
|
import { SimpleRelayPool } from '@ctxvm/sdk/relay';
|
||||||
|
|
||||||
|
// 1. Configure the signer and relay pool
|
||||||
|
const signer = new PrivateKeySigner('your-server-private-key');
|
||||||
|
const relayPool = new SimpleRelayPool(['wss://relay.damus.io']);
|
||||||
|
|
||||||
|
// 2. Create the McpServer instance
|
||||||
|
const mcpServer = new McpServer({
|
||||||
|
name: 'demo-server',
|
||||||
|
version: '1.0.0',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register your server's tools, resources, etc.
|
||||||
|
// mcpServer.tool(...);
|
||||||
|
|
||||||
|
// 3. Create the NostrServerTransport instance
|
||||||
|
const serverNostrTransport = new NostrServerTransport({
|
||||||
|
signer: signer,
|
||||||
|
relayHandler: relayPool,
|
||||||
|
isPublicServer: true, // Announce the server publicly
|
||||||
|
serverInfo: {
|
||||||
|
name: 'My Awesome MCP Server',
|
||||||
|
website: 'https://example.com',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Connect the server
|
||||||
|
await mcpServer.connect(serverNostrTransport);
|
||||||
|
|
||||||
|
console.log('MCP server is running and available on Nostr.');
|
||||||
|
|
||||||
|
// Keep the process running...
|
||||||
|
// To shut down: await mcpServer.close();
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. **`start()`**: When `mcpServer.connect()` is called, the transport connects to the relays and subscribes to events targeting the server's public key. If `isPublicServer` is `true`, it also initiates the announcement process.
|
||||||
|
2. **Incoming Events**: The transport listens for events from clients. For each client, it maintains a `ClientSession`.
|
||||||
|
3. **Request Handling**: When a valid request is received from an authorized client, the transport forwards it to the `McpServer`'s internal logic via the `onmessage` handler. It replaces the request's original ID with the unique Nostr event ID to prevent ID collisions between different clients.
|
||||||
|
4. **Response Handling**: When the `McpServer` sends a response, the transport's `send()` method is called. The transport looks up the original request details from the client's session, restores the original request ID, and sends the response back to the correct client, referencing the original event ID.
|
||||||
|
5. **Announcements**: If `isPublicServer` is true, the transport sends requests to its own `McpServer` for `initialize`, `tools/list`, etc. It then formats the responses into the appropriate replaceable Nostr events (kinds 11316-11320) and publishes them.
|
||||||
|
|
||||||
|
## Session Management
|
||||||
|
|
||||||
|
The `NostrServerTransport` manages a session for each unique client public key. Each session tracks:
|
||||||
|
|
||||||
|
- If the client has completed the MCP initialization handshake.
|
||||||
|
- Whether the session is encrypted.
|
||||||
|
- A map of pending requests to correlate responses.
|
||||||
|
- The timestamp of the last activity, used for cleaning up inactive sessions.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Now that you understand how the transports work, let's dive into the **[Signer](../signer/nostr-signer-interface.md)**, the component responsible for cryptographic signatures.
|
||||||
215
src/content/docs/guides/tutorials/client-server-communication.md
Normal file
215
src/content/docs/guides/tutorials/client-server-communication.md
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
---
|
||||||
|
title: Tutorial Client-Server Communication
|
||||||
|
description: A step-by-step guide to setting up a basic MCP client and server that communicate directly over the Nostr network using the @contextvm/sdk.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Tutorial: Client-Server Communication
|
||||||
|
|
||||||
|
This tutorial provides a complete, step-by-step guide to setting up a basic MCP client and server that communicate directly over the Nostr network using the `@contextvm/sdk`.
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
We will build two separate scripts:
|
||||||
|
|
||||||
|
1. `server.ts`: An MCP server that exposes a simple "echo" tool.
|
||||||
|
2. `client.ts`: An MCP client that connects to the server, lists the available tools, and calls the "echo" tool.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- You have completed the [Installation Guide](../getting-started/installation.md).
|
||||||
|
- You have two Nostr private keys (one for the server, one for the client). You can generate new keys using various tools, or by running `nostr-tools` commands.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. The Server (`server.ts`)
|
||||||
|
|
||||||
|
First, let's create the MCP server. This server will use the `NostrServerTransport` to listen for requests on the Nostr network.
|
||||||
|
|
||||||
|
Create a new file named `server.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { McpServer, Tool } from '@modelcontextprotocol/sdk/server';
|
||||||
|
import { NostrServerTransport } from '@ctxvm/sdk/transport';
|
||||||
|
import { PrivateKeySigner } from '@ctxvm/sdk/signer';
|
||||||
|
import { SimpleRelayPool } from '@ctxvm/sdk/relay';
|
||||||
|
import { generateSecretKey, getPublicKey } from 'nostr-tools/pure';
|
||||||
|
|
||||||
|
// --- Configuration ---
|
||||||
|
// IMPORTANT: Replace with your own private key
|
||||||
|
const SERVER_PRIVATE_KEY_HEX = process.env.SERVER_PRIVATE_KEY || 'your-32-byte-server-private-key-in-hex';
|
||||||
|
const RELAYS = ['wss://relay.damus.io', 'wss://nos.lol'];
|
||||||
|
|
||||||
|
// --- Main Server Logic ---
|
||||||
|
async function main() {
|
||||||
|
// 1. Setup Signer and Relay Pool
|
||||||
|
const signer = new PrivateKeySigner(SERVER_PRIVATE_KEY_HEX);
|
||||||
|
const relayPool = new SimpleRelayPool(RELAYS);
|
||||||
|
const serverPubkey = await signer.getPublicKey();
|
||||||
|
|
||||||
|
console.log(`Server Public Key: ${serverPubkey}`);
|
||||||
|
console.log('Connecting to relays...');
|
||||||
|
|
||||||
|
// 2. Create and Configure the MCP Server
|
||||||
|
const mcpServer = new McpServer({
|
||||||
|
name: 'nostr-echo-server',
|
||||||
|
version: '1.0.0',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Define a simple "echo" tool
|
||||||
|
const echoTool = new Tool({
|
||||||
|
name: 'echo',
|
||||||
|
description: 'Replies with the input it received.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
message: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['message'],
|
||||||
|
},
|
||||||
|
execute: async (input) => {
|
||||||
|
return {
|
||||||
|
content: `You said: ${input.message}`
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
mcpServer.tools.add(echoTool);
|
||||||
|
|
||||||
|
// 4. Configure the Nostr Server Transport
|
||||||
|
const serverTransport = new NostrServerTransport({
|
||||||
|
signer,
|
||||||
|
relayHandler: relayPool,
|
||||||
|
isPublicServer: true, // Announce this server on the Nostr network
|
||||||
|
serverInfo: {
|
||||||
|
name: 'CTXVM Echo Server',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5. Connect the server
|
||||||
|
await mcpServer.connect(serverTransport);
|
||||||
|
|
||||||
|
console.log('Server is running and listening for requests on Nostr...');
|
||||||
|
console.log('Press Ctrl+C to exit.');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error('Failed to start server:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running the Server
|
||||||
|
|
||||||
|
To run the server, execute the following command in your terminal. Be sure to replace the placeholder private key or set the `SERVER_PRIVATE_KEY` environment variable.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run server.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
The server will start, print its public key, and wait for incoming client connections.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. The Client (`client.ts`)
|
||||||
|
|
||||||
|
Next, let's create the client that will connect to our server.
|
||||||
|
|
||||||
|
Create a new file named `client.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Client } from '@modelcontextprotocol/sdk/client';
|
||||||
|
import { NostrClientTransport } from '@ctxvm/sdk/transport';
|
||||||
|
import { PrivateKeySigner } from '@ctxvm/sdk/signer';
|
||||||
|
import { SimpleRelayPool } from '@ctxvm/sdk/relay';
|
||||||
|
|
||||||
|
// --- Configuration ---
|
||||||
|
// IMPORTANT: Replace with the server's public key from the server output
|
||||||
|
const SERVER_PUBKEY = 'the-public-key-printed-by-server.ts';
|
||||||
|
|
||||||
|
// IMPORTANT: Replace with your own private key
|
||||||
|
const CLIENT_PRIVATE_KEY_HEX = process.env.CLIENT_PRIVATE_KEY || 'your-32-byte-client-private-key-in-hex';
|
||||||
|
const RELAYS = ['wss://relay.damus.io', 'wss://nos.lol'];
|
||||||
|
|
||||||
|
|
||||||
|
// --- Main Client Logic ---
|
||||||
|
async function main() {
|
||||||
|
if (SERVER_PUBKEY.startsWith('the-public-key')) {
|
||||||
|
console.error('Please replace SERVER_PUBKEY with the actual public key from the server.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 1. Setup Signer and Relay Pool
|
||||||
|
const signer = new PrivateKeySigner(CLIENT_PRIVATE_KEY_HEX);
|
||||||
|
const relayPool = new SimpleRelayPool(RELAYS);
|
||||||
|
|
||||||
|
console.log('Connecting to relays...');
|
||||||
|
|
||||||
|
// 2. Configure the Nostr Client Transport
|
||||||
|
const clientTransport = new NostrClientTransport({
|
||||||
|
signer,
|
||||||
|
relayHandler: relayPool,
|
||||||
|
serverPubkey: SERVER_PUBKEY,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Create and connect the MCP Client
|
||||||
|
const mcpClient = new Client();
|
||||||
|
await mcpClient.connect(clientTransport);
|
||||||
|
|
||||||
|
console.log('Connected to server!');
|
||||||
|
|
||||||
|
// 4. List the available tools
|
||||||
|
console.log('\nListing available tools...');
|
||||||
|
const tools = await mcpClient.listTools();
|
||||||
|
console.log('Tools:', tools);
|
||||||
|
|
||||||
|
// 5. Call the "echo" tool
|
||||||
|
console.log('\nCalling the "echo" tool...');
|
||||||
|
const echoResult = await mcpClient.callTool('echo', { message: 'Hello, Nostr!' });
|
||||||
|
console.log('Echo result:', echoResult.content);
|
||||||
|
|
||||||
|
// 6. Close the connection
|
||||||
|
await mcpClient.close();
|
||||||
|
console.log('\nConnection closed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error('Client failed:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running the Client
|
||||||
|
|
||||||
|
Open a **new terminal window** (leave the server running in the first one). Before running the client, make sure to update the `SERVER_PUBKEY` variable with the public key that your `server.ts` script printed to the console.
|
||||||
|
|
||||||
|
Then, run the client:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run client.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected Output
|
||||||
|
|
||||||
|
If everything is configured correctly, you should see the following output in the client's terminal:
|
||||||
|
|
||||||
|
```
|
||||||
|
Connecting to relays...
|
||||||
|
Connected to server!
|
||||||
|
|
||||||
|
Listing available tools...
|
||||||
|
Tools: {
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
name: 'echo',
|
||||||
|
description: 'Replies with the input it received.',
|
||||||
|
inputSchema: { ... }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Calling the "echo" tool...
|
||||||
|
Echo result: You said: Hello, Nostr!
|
||||||
|
|
||||||
|
Connection closed.
|
||||||
|
```
|
||||||
|
And that's it! You've successfully created an MCP client and server that communicate securely and decentrally over the Nostr network.
|
||||||
@@ -1,36 +1,48 @@
|
|||||||
---
|
---
|
||||||
title: Welcome to Starlight
|
title: ContextVM Documentation
|
||||||
description: Get started building your docs site with Starlight.
|
description: A virtual machine for context-controlled computation using Nostr protocol
|
||||||
template: splash
|
template: splash
|
||||||
hero:
|
hero:
|
||||||
tagline: Congrats on setting up a new Starlight project!
|
tagline: ContextVM - The context-controlled virtual machine built on Nostr
|
||||||
image:
|
image:
|
||||||
file: ../../assets/houston.webp
|
file: ../../assets/contextvm-logo.svg
|
||||||
actions:
|
actions:
|
||||||
- text: Example Guide
|
- text: Get Started
|
||||||
link: /guides/example/
|
link: /guides/getting-started/quick-overview/
|
||||||
icon: right-arrow
|
icon: right-arrow
|
||||||
- text: Read the Starlight docs
|
- text: View on GitHub
|
||||||
link: https://starlight.astro.build
|
link: https://github.com/contextvm/sdk
|
||||||
icon: external
|
icon: external
|
||||||
variant: minimal
|
variant: minimal
|
||||||
---
|
---
|
||||||
|
|
||||||
import { Card, CardGrid } from '@astrojs/starlight/components';
|
import { Card, CardGrid } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
## Next steps
|
## Core Concepts
|
||||||
|
|
||||||
<CardGrid stagger>
|
<CardGrid stagger>
|
||||||
<Card title="Update content" icon="pencil">
|
<Card title="Architecture" icon="document">
|
||||||
Edit `src/content/docs/index.mdx` to see this page change.
|
Learn the foundational concepts and architecture of ContextVM
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="Add new content" icon="add-document">
|
<Card title="SDK Documentation" icon="code">
|
||||||
Add Markdown or MDX files to `src/content/docs` to create new pages.
|
Explore our comprehensive API documentation and implementation examples
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="Configure your site" icon="setting">
|
<Card title="Tutorials" icon="open-book">
|
||||||
Edit your `sidebar` and other config in `astro.config.mjs`.
|
Follow step-by-step guides to build context-controlled applications
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="Read the docs" icon="open-book">
|
<Card title="Integrations" icon="puzzle">
|
||||||
Learn more in [the Starlight Docs](https://starlight.astro.build/).
|
Discover how ContextVM integrates with Nostr and other protocols
|
||||||
</Card>
|
</Card>
|
||||||
</CardGrid>
|
</CardGrid>
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
ContextVM provides a powerful framework for context-controlled computation on the Nostr network. Our SDK enables developers to:
|
||||||
|
|
||||||
|
- **Create secure contexts** for identity and application management
|
||||||
|
- **Build distributed applications** with trustless architecture
|
||||||
|
- **Integrate with Nostr** seamlessly through our transport layers
|
||||||
|
- **Manage cryptographic signing** with extensible signer interfaces
|
||||||
|
- **Handle relay operations** through our relay handler implementations
|
||||||
|
|
||||||
|
Ready to start building? Check out our [Quick Overview](/guides/getting-started/quick-overview/) to get up and running.
|
||||||
|
|||||||
Reference in New Issue
Block a user