-
-
Notifications
You must be signed in to change notification settings - Fork 0
feat(docs): document the MCP server and list GitHub releases #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| <script setup lang="ts"> | ||
| const { data: releases, status, error } = await useFetch('/api/releases') | ||
|
|
||
| const formatter = new Intl.DateTimeFormat('en', { dateStyle: 'long' }) | ||
| </script> | ||
|
|
||
| <template> | ||
| <div> | ||
| <div | ||
| v-if="status === 'pending'" | ||
| class="flex flex-col gap-4" | ||
| > | ||
| <USkeleton | ||
| v-for="index in 3" | ||
| :key="index" | ||
| class="h-32 w-full" | ||
| /> | ||
| </div> | ||
|
|
||
| <UAlert | ||
| v-else-if="error || !releases" | ||
| color="warning" | ||
| variant="subtle" | ||
| icon="i-lucide-triangle-alert" | ||
| title="Releases unavailable" | ||
| description="GitHub did not answer. The full list is always on the releases page of the repository." | ||
| :actions="[{ | ||
| label: 'Open on GitHub', | ||
| color: 'neutral', | ||
| variant: 'outline', | ||
| to: 'https://github.com/thoda-dev/shhh/releases', | ||
| target: '_blank', | ||
| }]" | ||
| /> | ||
|
|
||
| <div | ||
| v-else | ||
| class="flex flex-col gap-12" | ||
| > | ||
| <section | ||
| v-for="release in releases" | ||
| :key="release.tag" | ||
| > | ||
| <div class="flex flex-wrap items-center gap-2 border-b border-default pb-3"> | ||
| <h2 | ||
| :id="release.tag" | ||
| class="scroll-mt-20 text-xl font-bold text-highlighted" | ||
| > | ||
| {{ release.title }} | ||
| </h2> | ||
|
|
||
| <UBadge | ||
| v-if="release.prerelease" | ||
| color="warning" | ||
| variant="subtle" | ||
| size="sm" | ||
| > | ||
| Pre-release | ||
| </UBadge> | ||
|
|
||
| <span class="ms-auto flex items-center gap-3 text-sm text-muted"> | ||
| <time :datetime="release.publishedAt">{{ formatter.format(new Date(release.publishedAt)) }}</time> | ||
|
|
||
| <ULink | ||
| :to="release.url" | ||
| target="_blank" | ||
| class="inline-flex items-center gap-1 hover:text-default" | ||
| > | ||
| <UIcon name="i-simple-icons-github" class="size-4" /> | ||
| <span class="sr-only">{{ release.tag }} on GitHub</span> | ||
| </ULink> | ||
| </span> | ||
| </div> | ||
|
|
||
| <MDCRenderer | ||
| v-if="release.body" | ||
| :body="release.body" | ||
| class="mt-4" | ||
| /> | ||
| </section> | ||
| </div> | ||
| </div> | ||
| </template> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| <script setup lang="ts"> | ||
| const runtimeConfig = useRuntimeConfig() | ||
| const toast = useToast() | ||
|
|
||
| // Kept in step with `mcp.name` in nuxt.config.ts, which the module does not expose to the client. | ||
| const serverName = 'shhh docs' | ||
|
|
||
| const route = (runtimeConfig.public.mcp as { route?: string } | undefined)?.route || '/mcp' | ||
| const serverUrl = `${useRequestURL().origin}${route}` | ||
| const copied = ref(false) | ||
|
|
||
| // Cursor and VS Code go through the module's own deeplink route; claude.ai only pre-fills its | ||
| // dialog, so the user still confirms the URL there — by design, the link comes from outside. | ||
| const claudeUrl = `https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=${encodeURIComponent(serverName)}&connectorUrl=${encodeURIComponent(serverUrl)}` | ||
|
|
||
| async function copyUrl() { | ||
| try { | ||
| await navigator.clipboard.writeText(serverUrl) | ||
| } catch { | ||
| // There is no clipboard outside a secure context, which a docs instance on plain HTTP is not. | ||
| toast.add({ | ||
| title: 'Could not copy', | ||
| description: 'Select the URL above and copy it by hand.', | ||
| icon: 'i-lucide-triangle-alert', | ||
| color: 'warning', | ||
| }) | ||
| return | ||
| } | ||
|
|
||
| copied.value = true | ||
| setTimeout(() => copied.value = false, 2000) | ||
| toast.add({ title: 'Server URL copied', icon: 'i-lucide-check-circle', color: 'success' }) | ||
| } | ||
| </script> | ||
|
|
||
| <template> | ||
| <div class="my-5 flex flex-col gap-4 rounded-lg border border-default bg-elevated/50 p-4"> | ||
| <div class="flex items-center gap-2"> | ||
| <code class="min-w-0 flex-1 truncate rounded-md bg-default px-3 py-2 font-mono text-sm text-highlighted ring ring-accented">{{ serverUrl }}</code> | ||
|
|
||
| <UButton | ||
| :icon="copied ? 'i-lucide-check' : 'i-lucide-copy'" | ||
| color="neutral" | ||
| variant="subtle" | ||
| aria-label="Copy the MCP server URL" | ||
| @click="copyUrl" | ||
| /> | ||
| </div> | ||
|
|
||
| <div class="flex flex-wrap gap-2"> | ||
| <UButton | ||
| icon="i-simple-icons-claude" | ||
| color="neutral" | ||
| variant="outline" | ||
| :to="claudeUrl" | ||
| external | ||
| target="_blank" | ||
| > | ||
| Add to Claude | ||
| </UButton> | ||
|
|
||
| <UButton | ||
| icon="i-simple-icons-cursor" | ||
| color="neutral" | ||
| variant="outline" | ||
| :to="`${route}/deeplink?ide=cursor`" | ||
| external | ||
| target="_blank" | ||
| > | ||
| Add to Cursor | ||
| </UButton> | ||
|
|
||
| <UButton | ||
| icon="i-vscode-icons-file-type-vscode" | ||
| color="neutral" | ||
| variant="outline" | ||
| :to="`${route}/deeplink?ide=vscode`" | ||
| external | ||
| target="_blank" | ||
| > | ||
| Add to VS Code | ||
| </UButton> | ||
| </div> | ||
| </div> | ||
| </template> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| --- | ||
| title: Doc MCP server | ||
| description: Plug this documentation into Cursor, VS Code, Claude or any other MCP client. | ||
| navigation: | ||
| icon: i-lucide-plug | ||
| --- | ||
|
|
||
| This documentation is also served as a [Model Context Protocol](https://modelcontextprotocol.io) | ||
| server. Point an AI assistant at it and it answers from the pages you are reading right now, instead | ||
| of whatever it remembers about a self-hosted pastebin it saw once during training. | ||
|
|
||
| The endpoint is one URL, over HTTP, with no account and no token: | ||
|
|
||
| ::mcp-install | ||
| :: | ||
|
|
||
| ## What it exposes | ||
|
|
||
| | Tool | What it does | | ||
| |---|---| | ||
| | `list-pages` | Lists every documentation page with its title, description and path. | | ||
| | `get-page` | Returns the full markdown of one page, given its path. | | ||
|
|
||
| Both are read-only. The server has no access to any shhh instance, no access to your pastes, and no | ||
| way to create anything — it reads this documentation and nothing else. | ||
|
|
||
| ## Adding it by hand | ||
|
|
||
| The buttons above cover Claude, Cursor and VS Code. Everywhere else, add the URL yourself. | ||
|
|
||
| ::code-group | ||
|
|
||
| ```bash [Claude Code] | ||
| claude mcp add --transport http shhh-docs https://shhh-docs.thoda.dev/mcp | ||
| ``` | ||
|
|
||
| ```json [.vscode/mcp.json] | ||
| { | ||
| "servers": { | ||
| "shhh-docs": { | ||
| "type": "http", | ||
| "url": "https://shhh-docs.thoda.dev/mcp" | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ```json [~/.cursor/mcp.json] | ||
| { | ||
| "mcpServers": { | ||
| "shhh-docs": { | ||
| "type": "http", | ||
| "url": "https://shhh-docs.thoda.dev/mcp" | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| :: | ||
|
|
||
| **ChatGPT** has no install link of its own. Custom connectors sit behind *Developer mode*, in | ||
| **Settings → Apps & Connectors → Advanced**; turn it on, then add the URL as a connector. It is | ||
| web-only and needs a paid plan — OpenAI moves those menu labels around, so follow | ||
| [their help page](https://help.openai.com/en/articles/12584461) if they have shifted again. | ||
|
|
||
| Other clients — Zed, Windsurf, Continue, and anything built on an MCP SDK — take the same URL as a | ||
| *streamable HTTP* server; check their own documentation for where the config file lives. | ||
|
|
||
| ::note | ||
| Restart the client after editing its config. Most of them only read MCP servers at startup. | ||
| :: | ||
|
|
||
| ::warning | ||
| Claude and ChatGPT reach the server from their own infrastructure, so the URL has to be publicly | ||
| reachable over HTTPS. Cursor, VS Code and Claude Code connect from your own machine instead, so they | ||
| also work against an instance on your network, or on a `localhost` port while you run the docs | ||
| yourself. | ||
| :: | ||
|
|
||
| ## Running it from your own instance | ||
|
|
||
| The MCP server is part of the documentation site, not of shhh itself. If you host the docs yourself | ||
| with `docker/docs.Dockerfile`, the endpoint is `/mcp` on your own origin — `https://docs.example.com/mcp` — | ||
| and the install buttons on this page already point at whichever host served it to you. | ||
|
|
||
| ::warning | ||
| shhh itself has **no public API and no MCP server**. An agent cannot create or read pastes on your | ||
| behalf; it can only read this documentation. Both are on the | ||
| [roadmap](https://github.com/thoda-dev/shhh/blob/master/ROADMAP.md) and neither is built yet. | ||
| :: | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| --- | ||
| title: Releases | ||
| description: Every published version of shhh, pulled straight from GitHub. | ||
| navigation: | ||
| icon: i-lucide-tag | ||
| --- | ||
|
|
||
| Release notes are generated from the commit history at tag time. This page reads the | ||
| [GitHub releases](https://github.com/thoda-dev/shhh/releases) of the repository directly, so it never | ||
| lags behind a publication. | ||
|
|
||
| ::note | ||
| Version numbers follow [semantic versioning](https://semver.org). Upgrading is a `docker compose pull` | ||
| away — migrations run on boot, so there is no separate step. Read the notes first when a major | ||
| version lands. | ||
| :: | ||
|
|
||
| ::github-releases | ||
| :: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| interface GitHubRelease { | ||
| name: string | null | ||
| tag_name: string | ||
| html_url: string | ||
| published_at: string | null | ||
| created_at: string | ||
| draft: boolean | ||
| prerelease: boolean | ||
| body: string | null | ||
| } | ||
|
|
||
| interface MarkdownNode { | ||
| props?: Record<string, unknown> | ||
| children?: MarkdownNode[] | ||
| } | ||
|
|
||
| // Every release repeats the same `### 🚀 Enhancements` headings, and they all land on one page. | ||
| function prefixIds(node: MarkdownNode, prefix: string) { | ||
| if (typeof node.props?.id === 'string') { | ||
| node.props.id = `${prefix}-${node.props.id}` | ||
| } | ||
| node.children?.forEach(child => prefixIds(child, prefix)) | ||
| } | ||
|
|
||
| async function renderNotes(body: string, tag: string) { | ||
| // changelogen opens each body with the version as a heading; the card already shows it. GitHub | ||
| // hands the notes back with CRLF endings, which `.` in a JS regex refuses to cross. | ||
| const notes = body.replace(/\r\n/g, '\n').trim().replace(/^##\s+.*\n+/, '') | ||
| const parsed = await parseMarkdown(notes || '_No release notes._') | ||
| prefixIds(parsed.body as MarkdownNode, tag) | ||
| return parsed.body | ||
| } | ||
|
|
||
| // Unauthenticated GitHub allows 60 calls an hour per IP, shared by every visitor of the instance. | ||
| // Caching the whole list keeps a busy day at 48 calls and rides out a GitHub outage for half an hour. | ||
| export default defineCachedEventHandler(async () => { | ||
| const releases = await $fetch<GitHubRelease[]>('https://api.github.com/repos/thoda-dev/shhh/releases', { | ||
| query: { per_page: 30 }, | ||
| headers: { | ||
| 'accept': 'application/vnd.github+json', | ||
| 'user-agent': 'shhh-docs', | ||
| 'x-github-api-version': '2022-11-28', | ||
| }, | ||
| }) | ||
|
|
||
| return Promise.all(releases | ||
| .filter(release => !release.draft) | ||
| .map(async release => ({ | ||
| title: release.name || release.tag_name, | ||
| tag: release.tag_name, | ||
| url: release.html_url, | ||
| publishedAt: release.published_at || release.created_at, | ||
| prerelease: release.prerelease, | ||
| // Parsed here rather than in the browser so the page ships an AST the docs prose components | ||
| // render, instead of raw HTML that would need its own styling. | ||
| body: await renderNotes(release.body || '', release.tag_name), | ||
| }))) | ||
| }, { | ||
| name: 'github-releases', | ||
| maxAge: 60 * 30, | ||
| swr: true, | ||
| getKey: () => 'thoda-dev/shhh', | ||
| }) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.