nope-mcp (formerly amb-mcp)

MCP serverSearch

Search German open educational resources (OER) via AMB/schema.org metadata, open licenses only.

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use nope-mcp (formerly amb-mcp) to search resources

Install nope-mcp (formerly amb-mcp)

The server’s own address, for the clients that take one directly. Or connect ahel once and every client you use reads it from one address, with the account kept on ahel rather than in each client’s config.

  • Claude Code

    claude mcp add --transport http --scope user nope-mcp-formerly-amb-mcp 'https://mcp.edufeed.org/mcp'

    Run it once in your project, then open /mcp to approve any sign-in the server asks for.

  • Claude Desktop

    https://mcp.edufeed.org/mcp

    Add a custom connector in Settings, paste this address, and approve the sign-in.

  • Cursor

    cursor://anysphere.cursor-deeplink/mcp/install?name=nope-mcp-formerly-amb-mcp&config=eyJ1cmwiOiJodHRwczovL21jcC5lZHVmZWVkLm9yZy9tY3AifQ==

    Open the link and Cursor adds the server at that address.

  • ChatGPT

    https://mcp.edufeed.org/mcp

    In Settings, enable Developer mode, create an MCP app, and paste this address. Your plan and workspace must allow custom apps.

  • Codex

    codex mcp add nope-mcp-formerly-amb-mcp --url 'https://mcp.edufeed.org/mcp'

    Run it once, then sign in with codex mcp login nope-mcp-formerly-amb-mcp if the server asks for an account.

From the project's README

As published by edufeed-org/nope-mcp in README.md.

An MCP (Model Context Protocol) server for querying educational resources from AMB (Allgemeines Metadatenprofil für Bildungsressourcen) Nostr relays.

Repository

The canonical repository lives on Nostr (NIP-34) — browse it on gitworkshop.dev, or clone with the ngit remote helper:

git clone nostr://laoc.xyz/relay.ngit.dev/amb-mcp

Mirrors: git.edufeed.org/edufeed/nope-mcp · github.com/edufeed-org/nope-mcp. Issues and PRs are welcome on any of the three — Nostr PRs arrive as pr/* branches.

Releases are tagged (v0.1.0, …) and listed in CHANGELOG.md.

MCP Registry

server.json is the manifest nope-mcp publishes to the official MCP Registry as org.edufeed/nope-mcp, pointing at the https://mcp.edufeed.org/mcp remote. Its version always matches package.json (enforced by test/server-json.test.ts).

Publishing is done with the registry's mcp-publisher CLI after DNS-based domain verification of edufeed.org (the registry proves ownership of the org.edufeed namespace via a TXT record, not a secret the CLI holds) — there is no key to manage and no command embeds one.

Features

Query & Browse

  • Cross-content full-text search (search_content) across educational resources, long-form articles, wikis, transferkiosk projects/measures, and NKBIP-01 scientific publications in one ranked call
  • Grounded passage retrieval (search_passages) — answers a question from the corpus's fulltext with citations, scoped by a grimoire spell (kind 777) or inline scope; requires the indexer
  • Semantic snippet passages from chunk re-ranking surfaced per result when the relay's re-ranking is active
  • Full-text search with NIP-50
  • Filter by publisher, creator, subject, resource type, educational level
  • Browse available subjects, resource types, and educational levels
  • Resolve author/organisation names to pubkeys (resolve_author) for author-scoped queries
  • NIP-52 calendar event search with temporal, geohash, and hashtag filters
  • SKOS controlled-vocabulary lookup and search (skos_*)
  • Get individual resources by identifier
  • Relay statistics and info

URL → Form-Prefill Metadata

  • extract_metadata(url, variant, skosSchemes?) — fetch a public web page and produce a complete AMB/EKW form-prefill payload. Returns OpenGraph fallback by default; with ANTHROPIC_API_KEY set, an LLM grounded in the configured SKOS vocabularies fills SKOS-typed fields with concept IDs and per-field evidence quotes.
  • Library export: import { extractMetadata } from 'nope-mcp/lib' for direct in-process use (e.g. SvelteKit server routes).

Signing & Publishing

  • NIP-46 remote signing (bunker) with QR code connection flow
  • Sign and publish arbitrary Nostr events
  • Create and publish kind 0 (profile/metadata) events
  • Create and publish kind 30142 (AMB educational resource) events
  • NIP-42 relay authentication support
  • Multi-user session isolation

Installation

bun install

Configuration

Copy .env.example to .env and configure:

cp .env.example .env

Environment variables

NameUsed byDefaultDescription
AMB_RELAYSall transportswss://relay.edufeed.orgComma-separated AMB relay URLs. Queried by every content tool (search_content, search_resources, get_resource, browse_*, resolve_author, relay_stats) and used as default publish targets for the signing tools.
AMB_EXTRA_RELAYSall transports(empty)Comma-separated AMB relay URLs that are selectable but not searched by default. search_content, search_resources, and get_resource accept a relays parameter naming relays from AMB_RELAYS ∪ AMB_EXTRA_RELAYS (anything else is rejected); list_relays advertises both groups as defaultRelays/extraRelays. Use this to expose alternative corpora (e.g. the OERSI and SODIX aggregation relays, wss://oersi.edufeed.org,wss://sodix.edufeed.org) on request without merging them into every search. Over HTTP this list also feeds the ?relays= connector parameter — see Choosing a connector's default relays.
AMB_AUTHOR_SETSall transports(empty)Comma-separated naddr identifiers of NIP-51 follow sets (kind 30000). Loaded once at startup into the author directory served by list_known_authors; the returned pubkeys can then be passed to search_* tools as authors.
CALENDAR_RELAYSall transportswss://relay.edufeed.orgComma-separated NIP-52 calendar relay URLs for search_calendar_events. The AMB relay serves calendar events itself, so the default is the same relay; set this only for split deployments with a dedicated calendar relay.
CALENDAR_AUTHOR_SETSall transports(empty)Same as AMB_AUTHOR_SETS, but for the calendar author directory served by list_calendar_authors.
INDEXER_ENDPOINTSsearch_passages(unset — tool disabled)Comma-separated wss://relay=https://indexer pairs mapping each AMB relay to its amb-indexer base URL. Enables search_passages.
INDEXER_API_TOKENsearch_passages(unset)Bearer token for the indexer's /search_chunks (shared default for all endpoints).
INDEXER_API_TOKENSsearch_passages(unset)Per-relay token overrides, comma-separated wss://relay=token pairs — each deployed indexer instance has its own token. Every INDEXER_ENDPOINTS entry must be covered by this or INDEXER_API_TOKEN; a partially tokened config fails at startup.
OPEN_LICENSES_ONLYall transportstrueSearch results (search_resources, search_content, search_passages) hold only openly licensed learning resources: CC0, Public Domain Mark, CC BY, CC BY-SA. Other content kinds carry no license and are always included; get_resource is never filtered but reports openLicense. false (or 0/no/off) returns every resource.
SPELL_RELAYSsearch_passageswss://relay.edufeed.orgRelays to fetch kind-777 spells (and kind-3 contact lists) from.
LOG_LEVELall transportsinfoPer-tool-call logging on stderr (stdout belongs to the stdio transport). Every call emits one JSON line: {"ts":"2026-09-14T12:00:00.000Z","tool":"search_passages","ms":412,"ok":true,"session":"<id>","args":{"question":"…","kinds":[30142]}} — ok is false for isError results, thrown errors, and text results whose JSON body carries an error code (typed failures such as relay_unreachable) — error then holds the message or code; strings in args are cut at 120 chars, arrays longer than five collapse to {length, head}. info (default) logs every call, warn/error log failures only, silent disables the lines.
EDUFEED_APP_BASE_URLall transports(unset)Frontend base URL (no trailing slash, e.g. https://app.edufeed.org). When set, results from search_content, search_resources, get_resource, and search_calendar_events include a url field pointing at the edufeed-app viewer page (<base>/<naddr>) so LLM clients can render direct links. Unset means no url field.
SERVER_PRIVATE_KEYsrc/index.ts (+ discovery scripts)required for Nostr transportNostr private key (nsec or hex) that is the server's own ContextVM identity. The derived pubkey is what clients connect to via cvmi use <pubkey>. Not read by the stdio or HTTP transports.
RELAYSsrc/index.ts (+ discovery scripts)wss://relay.contextvm.org, wss://cvm.otherstuff.aiComma-separated relay URLs for ContextVM transport announcements and request/response traffic. Not read by the stdio or HTTP transports.
ANTHROPIC_API_KEYextract_metadata(unset)Enables LLM-grounded SKOS field extraction. When unset the tool degrades gracefully to OpenGraph/JSON-LD-only output.
ANTHROPIC_MODELextract_metadataclaude-sonnet-4-6Override the Anthropic model used for extraction.
SKOS_SCHEMESextract_metadata(unset)JSON map { "<form-field>": "<scheme-uri>" } of default vocabularies used when the caller does not pass skosSchemes explicitly.
VOCAB_RELAYSextract_metadatafalls back to AMB_RELAYSRelays used to resolve naddr1… SKOS scheme identifiers to relay-hosted vocabularies.
SCHEME_NADDR_*extract_metadata(unset)Per-vocabulary naddr overrides (e.g. SCHEME_NADDR_HCRT, SCHEME_NADDR_SCHULFAECHER) mapping well-known scheme URIs to relay-hosted SKOS vocabularies. See .env.example for the full list.
HTTP_*, OAUTH_*src/http.ts onlysee belowHTTP bind and OAuth resource-server settings — documented under Option 4.
EMBED_TOKENdocker-compose.yml only(unset)Token for the embedding service used by the bundled local test relay. Not read by the server itself.

Usage

Option 1: Add to Claude Code (Recommended)

claude mcp add nope-mcp -- bun run /path/to/nope-mcp/src/stdio.ts

This uses the default public relay (wss://relay.edufeed.org). To point at another relay — e.g. the local docker relay from Development — add -e AMB_RELAYS=ws://localhost:3337.

Option 2: Run with cvmi (ContextVM)

Start the server:

cvmi serve -- bun run src/stdio.ts

Connect from another machine:

cvmi use <server-pubkey>

Option 3: Run standalone with Nostr transport

bun run src/index.ts

Option 4: Run with Streamable HTTP transport

For web-based MCP clients (Claude.ai connectors, MCP Inspector, custom browser apps):

bun run src/http.ts   # dev
node dist/http.js     # production (after `npm run build`)
VariableDefaultDescription
HTTP_PORT3000Port to bind.
HTTP_HOST0.0.0.0Bind host. Use 127.0.0.1 to limit to a local proxy.
HTTP_ALLOWED_HOSTS(unset)Comma-separated Host allow-list. Enables DNS-rebinding protection when set.
HTTP_ALLOWED_ORIGINS(unset)Comma-separated Origin allow-list.
OAUTH_ISSUERhttps://auth.edufeed.org/realms/edufeedOIDC issuer whose tokens are accepted.
OAUTH_AUDIENCEnope-mcp,amb-mcpComma-separated accepted audiences; a token whose aud contains any of them is valid.
OAUTH_JWKS_URI<issuer>/protocol/openid-connect/certsJWKS endpoint for token signature verification.
OAUTH_RESOURCE_URLhttps://mcp.amb.edufeed.org/mcpFallback PRM resource for a host not in HTTP_ALLOWED_HOSTS. Otherwise the resource is derived per request (RFC 9728): /.well-known/oauth-protected-resource → https://<host>/, /.well-known/oauth-protected-resource/mcp → https://<host>/mcp.

Authentication model: the HTTP transport is an OAuth 2.0 resource server.

  • A request without an Authorization header gets an anonymous read-only session (mcp:read): search, get, browse, resolve, SKOS lookups.
  • A request with a valid JWT (issued by OAUTH_ISSUER for one of the OAUTH_AUDIENCE audiences) is granted the token's scopes: mcp:read and/or mcp:extract (the budget-spending extract_metadata tool). An invalid token is rejected with 401.
  • Write/signing tools are never exposed over HTTP — they are only available on the stdio and Nostr transports. Insufficient scope means the tool is simply absent from tools/list.

The server exposes:

  • POST / and POST /mcp — JSON-RPC requests (initialize, tool calls, etc.), same session map
  • GET / and GET /mcp — server-push SSE stream for the current session
  • GET / (plain browser request — no Mcp-Session-Id, no Accept: text/event-stream) — a small JSON info document (name, version, mcp, docs, transport) instead of the MCP 404; GET /mcp always behaves as an MCP session request
  • DELETE / and DELETE /mcp — terminate the current session
  • GET /.well-known/oauth-protected-resource / GET /.well-known/oauth-protected-resource/mcp — RFC 9728 protected-resource metadata for / and /mcp respectively, host-aware
  • GET /healthz — unauthenticated liveness probe

Example handshake with curl:

# 1. initialize (anonymous = read-only session), capture the Mcp-Session-Id response header
curl -i http://localhost:3000/mcp -X POST \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'

# 2. reuse the session id for tools/list, tools/call, etc.
curl http://localhost:3000/mcp -X POST \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'Mcp-Session-Id: <id-from-step-1>' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

# To use extract_metadata, add an OAuth token to the *initialize* request:
#   -H "Authorization: Bearer $ACCESS_TOKEN"
# (scopes are fixed at session init; a token on later requests does not upgrade the session)

Smoke-test with MCP Inspector:

npx @modelcontextprotocol/inspector http://localhost:3000/mcp
Choosing a connector's default relays

Most connector UIs (Claude.ai's "Add custom connector", for one) give you two fields: a name and a URL. So the URL is where a connection states which relays it wants searched by default:

https://mcp.edufeed.org/mcp?relays=sodix
https://mcp.edufeed.org/mcp?relays=amb-relay,sodix,oersi

(The previous host, https://mcp.amb.edufeed.org/mcp, keeps working unchanged — both accept the same ?relays= syntax.)

The relays named in ?relays= become that session's default set — searched on every search_content / search_resources call. Every other relay the deployment serves stays in the extra set: list_relays still advertises it, and a tool call can still reach it through its own relays parameter. Without the parameter, the session uses the server's configured default set, exactly as before.

Names resolve against the relays the deployment already serves (AMB_RELAYS ∪ AMB_EXTRA_RELAYS). Each relay answers to three forms:

FormExample
Full URLwss://sodix.edufeed.org
Hostnamesodix.edufeed.org
First hostname labelsodix

A short label claimed by two relays is dropped rather than guessed at — use the hostname for those. The names are per deployment: list_relays on a plain session shows exactly which relays a server offers, and a rejected ?relays= lists every name it accepts. A name the deployment does not serve fails the initialize request with HTTP 400 and a JSON-RPC error listing the names it does accept; the connection is never silently pointed at the server default. Arbitrary relay URLs are not accepted, so a public endpoint cannot be used to make the server open WebSocket connections to hosts of the caller's choosing.

list_relays reports defaultRelaysSource: "connector-url" on such a session, so a model can tell a deliberately narrowed corpus from the deployment's standard one.

Public deployment

A managed instance is hosted at:

https://mcp.edufeed.org/mcp

The previous URL, https://mcp.amb.edufeed.org/mcp, remains valid and serves the same deployment.

It serves three relays — amb-relay (wss://amb-relay.edufeed.org, the default), plus oersi and sodix as per-call extras — so …/mcp?relays=sodix gives a connector that searches the SODIX corpus by default. See Choosing a connector's default relays.

It speaks the same streamable-HTTP protocol as the local server. Read tools are public — a request with no Authorization header gets a read-only session (search/get/browse/resolve). The budget-spending extract_metadata tool requires a valid OAuth token carrying the mcp:extract scope; tokens are issued by the Keycloak realm out-of-band — ask the operator. The handshake is otherwise identical to the curl example above; just substitute the URL and drop the Authorization header for read-only use.

The public endpoint is rate-limited per source IP at the edge (Traefik) — search_passages in particular embeds each query on the in-stack CPU service, so bursts are throttled. Normal interactive use is unaffected.

Available Tools

Tools are grouped into three profiles:

  • Read — search, get, browse, resolve, SKOS lookups, calendar. Available on all transports; served anonymously over HTTP.
  • Extract — extract_metadata. Over HTTP requires an OAuth token with the mcp:extract scope.
  • Write — signer, publish, relay management, and SKOS vocabulary builder tools. Only exposed on the stdio and Nostr transports, never over HTTP.

search_content

Topic search across all content types in one ranked call — educational resources (30142), long-form articles (30023), wikis (30818), transferkiosk projects (30143), transferkiosk measures (30144), and NKBIP-01 scientific publications (30040 indices + 30041 sections — academic articles, books). Results are interleaved and ranked by semantic passage match; each carries the matched passage (snippet) when the relay's chunk re-ranking is active.

Discovery vs. question intent. When search_passages is available (the indexer is configured), the two tools split by intent: search_content is for discovery — the user wants materials to browse ("finde/empfiehl Materialien zu X"), and you present the items as links. For a question the user wants answered from the sources ("wie/warum/was hilft bei X?"), use search_passages instead. When the indexer is not configured, search_content is the default entry point for natural-language questions.

Parameters:

NameTypeDescription
querystringFree-text topic
typesstring[]Subset of ["resource","article","wiki","project","measure","publication"] (default: all)
languagestringLabel language (default de)
since / untilnumberUnix timestamp bounds
authorsstring[]Author pubkeys (hex)
limitnumberMax results, 1-250 (default 20)
communitystringReturn content shared into this community (hex pubkey or npub)
relaysstring[]Restrict to specific relays — by full URL or short name (oersi, sodix). See list_relays.

Each result: { type, kind, title, url?, naddr?, snippet?, score?, ...type-specific }. For upcoming events on the same topic, follow up with search_calendar_events.

If a selected relay times out or is unreachable, the response adds relaysIncomplete (the affected relays) and a warning — an empty or short result under that warning is not proof the corpus is empty. Absent those fields, every relay answered.

search_passages

Grounded RAG: retrieves the best-matching fulltext passages for a question and returns them with citations (source resource, page, heading, source URL) — answer the user from the passages and cite each source. This is the default tool for question-shaped queries; use search_content for discovery. Requires the indexer (INDEXER_ENDPOINTS); absent that config the tool is not registered.

Scope is required (the tool never runs an unscoped search) but usually trivial — with no source restriction, pass the content kinds. A restriction routes into scope: a metadata publisher (resolve_publisher finds the exact spelling) into search as a quoted field filter (publisher.name:"LEHRE LADEN"); a Nostr signer (resolve_author) into authors. Scope can also come from a published grimoire spell (kind 777) passed by nevent/event id; every response echoes the canonical spell for the scope so it can be published and reused. Spells may use $me/$contacts, resolved to the caller (pass me when the transport is anonymous).

Ranking. The indexer runs a Typesense hybrid search; search_passages requests vector weight alpha: 0.7 (the indexer's own default is the keyword-leaning 0.3, kept for the relay's rerank path), so the semantic rank leads and boilerplate keyword matches ("GRUNDSCHULE", "Kinder") stop winning. It over-fetches min(limit × 3, 100) chunks, then keeps at most two passages per document (event_coord), drops hits below 10 % of the top score, and returns the first limit. Phrase question as a topical statement naming subject and target group ("Friedenserziehung in der Grundschule: Einstieg in das Thema Frieden mit Kindern") rather than the user's literal sentence ("Wie kann ich …?"). A passage with only a snippet and no text is either license-gated or has no fulltext indexed yet.

Parameters:

NameTypeDescription
questionstringThe question/topic to find grounding passages for
spellstringPublished spell: nevent, note id, or 64-hex event id (alternative to inline scope)
authorsstring[]Inline scope: signer pubkeys (hex/npub/$me/$contacts)
kindsnumber[]Inline scope: content kinds (e.g. [30142])
tagobjectInline scope: one tag filter, { letter, values }
searchstringInline scope: NIP-50 term selecting the events in scope (quote multi-word field values)
since / untilstringInline scope: absolute Unix seconds or relative (7d, 1mo, now)
mestringWho $me refers to (npub/hex); defaults to the calling identity
relaysstring[]Relay selection (full URL or short name); first mapped relay is used
limitnumberPassages to return, 1-25 (default 10)

Failure is explicit, never a silent unscoped search: empty_scope (the relay answered but nothing matched), relay_unreachable (the content relay did not answer — retry), spell_not_found, no_indexer, indexer_error.

Facet-in-query syntax: relay-side facets ride inside query as NIP-50 field filters rather than as separate parameters — append them to the free-text term and the relay resolves them server-side. Examples: type:academic (publication display type), doi:10.1234/abcd.5678 (bare DOI, no doi: prefix in the stored value), keywords:<term> (topic words), or partOf:30143:<pubkey>:<d> (publications/measures that belong to a given project coord). These can be combined with a topic term, e.g. query: "seminardidaktik partOf:30143:<pubkey>:<d>".

search_resources

Search for educational resources using full-text search and metadata filters.

Parameters:

NameTypeDescription
querystringFree-text search query
publisherNamestringFilter by publisher name
creatorNamestringFilter by creator/author name
subjectLabelstringFilter by subject label (e.g., "Mathematik")
resourceTypeLabelstringFilter by resource type (e.g., "Video", "Kurs")
educationalLevelLabelstringFilter by educational level
languagestringLanguage for labels (default: "de")
authorsstring[]Filter by author pubkeys (hex) — e.g. from resolve_author or list_known_authors
since / untilnumberUnix timestamp bounds on resource creation time
limitnumberMax results, 1-250 (default: 20)
relaysstring[]Restrict to specific relays — by full URL or short name (oersi, sodix)

Shortened here. Read the whole README on GitHub.

Tools it offers (18)

What this server listed when ahel dialed its public endpoint in Oct 2026, with no key and no account of yours. The names are the server’s own.

  • search_resources
  • search_content
  • get_resource
  • browse_subjects
  • browse_resource_types
  • browse_educational_levels
  • relay_stats
  • list_relays
  • relay_list_get
  • skos_get_vocabulary
  • skos_get_concept
  • skos_search
  • list_known_authors
  • resolve_author
  • resolve_publisher
  • search_calendar_events
  • list_calendar_authors
  • search_passages

Signals

GitHub stars
1
Last commit
Oct 2026
Advanced
Delivery
nope-mcp MCP server → your ahel connector (mcp.ahel.ai) → your AI.
Item type
mcp-server
Key
org-edufeed-nope-mcp
Source
github.com/edufeed-org/nope-mcp
Hosted endpoint
https://mcp.edufeed.org/mcp