
nope-mcp — Open Educational Resources search
org.edufeedv0.4.0更新於 Oct 3, 2026
Search German open educational resources (OER) via AMB/schema.org metadata, open licenses only.
概覽
透過 AMB/schema.org 中介資料搜尋德語開放教育資源與相關開放內容,並可選擇提供有依據的段落檢索。
- 功能
- 提供跨教育資源的排序全文搜尋,涵蓋長篇文章、維基、transferkiosk 專案與措施以及科學出版品,並可依出版者、創作者、學科、資源類型與教育階段篩選。其他工具可依識別碼取得單一內容、將作者姓名解析為公鑰、瀏覽學科與教育階段、查詢 SKOS 詞彙表以及搜尋行事曆事件。可選的擷取工具可將公開網頁 URL 轉為 AMB 表單預填中介資料。簽章與發布工具存在,但僅在本地傳輸方式下提供。
- 適用情境
- 適合讓助理尋找、引用或推薦以德語為主的開放授權學習素材,或根據該語料回答問題並附上引用。也適用於瀏覽受控詞彙與教育類行事曆事件。
- 執行需求
- 提供遠端 streamable-HTTP 端點,唯讀使用不需本地執行環境。唯讀工具可匿名使用,擷取工具需要具備 mcp:extract 範圍的 OAuth 權杖。自架需要 Bun 或 Node.js,並設定 AMB_RELAYS、INDEXER_ENDPOINTS、INDEXER_API_TOKEN 等。
安裝
在 SourceWeft 中
- 開啟 儀表板中的 nope-mcp — Open Educational Resources search,將其新增到工作區。
- 為需要使用其工具的對話啟用該服務。
Web executable,透過 Streamable HTTP。 遠端服務在工作區中設定後即可從網頁執行環境執行。
其他 MCP 客戶端
把它新增到你客戶端的 mcpServers 設定中。
{
"mcpServers": {
"nope-mcp": {
"type": "http",
"url": "https://mcp.edufeed.org/mcp"
}
}
}README
nope-mcp (formerly amb-mcp)
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:
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; withANTHROPIC_API_KEYset, 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
Configuration
Copy .env.example to .env and configure:
Environment variables
Usage
Option 1: Add to Claude Code (Recommended)
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:
Connect from another machine:
Option 3: Run standalone with Nostr transport
Option 4: Run with Streamable HTTP transport
For web-based MCP clients (Claude.ai connectors, MCP Inspector, custom browser apps):
Authentication model: the HTTP transport is an OAuth 2.0 resource server.
- A request without an
Authorizationheader gets an anonymous read-only session (mcp:read): search, get, browse, resolve, SKOS lookups. - A request with a valid JWT (issued by
OAUTH_ISSUERfor one of theOAUTH_AUDIENCEaudiences) is granted the token's scopes:mcp:readand/ormcp:extract(the budget-spendingextract_metadatatool). 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 /andPOST /mcp— JSON-RPC requests (initialize, tool calls, etc.), same session mapGET /andGET /mcp— server-push SSE stream for the current sessionGET /(plain browser request — noMcp-Session-Id, noAccept: text/event-stream) — a small JSON info document (name,version,mcp,docs,transport) instead of the MCP 404;GET /mcpalways behaves as an MCP session requestDELETE /andDELETE /mcp— terminate the current sessionGET /.well-known/oauth-protected-resource/GET /.well-known/oauth-protected-resource/mcp— RFC 9728 protected-resource metadata for/and/mcprespectively, host-awareGET /healthz— unauthenticated liveness probe
Example handshake with curl:
Smoke-test with MCP Inspector:
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:
(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:
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:
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 themcp:extractscope. - 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:
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:
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:
The subjectLabel / resourceTypeLabel / educationalLevelLabel filters are
exact label matches (case-insensitive), not free text — a phrase that
isn't a stored label returns 0. Put topic words in query; use browse_*
to discover valid labels. publisherName / creatorName are exact too, but
suggest near spellings (actorCandidates) on a zero-match.
get_resource
Retrieve a single piece of content by naddr, d-tag identifier, or event ID.
An naddr from any search_content result resolves — resources, articles,
wikis, projects, measures, and publications alike; non-resource kinds come
back in the same shape as their search_content result ({ type, kind, title, ... }), not the full AMB resource shape. A bare identifier/eventId
lookup (no naddr) always resolves the full educational-resource metadata
(kind 30142), including creator/publisher and educational properties.
Parameters:
Response shape (search_resources and get_resource)
Each returned resource includes the standard AMB fields plus:
nostr.naddr— NIP-19 addressable identifier (kind=30142, pubkey, d-tag). Useful for any Nostr client.url— direct link to the edufeed-app page for this resource. Only present whenEDUFEED_APP_BASE_URLis configured. LLM clients should cite this as a markdown link ([name](url)) when recommending the resource so users can open it.
search_content, search_resources, and search_calendar_events add
relaysIncomplete + a warning when a selected relay timed out or was
unreachable — under that warning an empty result is not proof the corpus is
empty, so tell the user a source was unreachable and offer to retry. When
every relay answers, neither field appears.
resolve_author
Resolve an organisation or person name to candidate pubkeys using the relay's
kind-0 author-profile index (NIP-50 search). This is the entry point for
name-driven questions ("recent articles by Jörg Lohrer"): resolve the name, pick
the best candidate, then pass its pubkey to search_content({ authors: [pubkey] })
or search_calendar_events. Returns up to limit candidates ranked by relevance.
Parameters:
list_known_authors
List known educational-resource authors loaded from the configured follow sets
(NIP-51 kind 30000, see AMB_AUTHOR_SETS). Returns names, pubkeys, and NIP-05
identifiers. Distinct from resolve_author, which searches the relay-wide
profile index rather than hand-curated sets.
browse_subjects
List available subjects/topics with resource counts.
browse_resource_types
List available learning resource types (Video, Course, Worksheet, etc.).
browse_educational_levels
List available educational levels (Primary, Secondary, Higher Education, etc.).
relay_stats
Get relay information including name, description, and supported NIPs.
list_relays
List all AMB relays configured for the current session, split into
defaultRelays (searched on every query) and extraRelays (selectable per
call). A relays parameter (on search_content, search_resources,
get_resource, search_passages) names relays from either group by full
URL or short name — the hostname or first label (oersi, sodix,
amb-relay), case-insensitive; an ambiguous label shared by two relays
resolves only by its longer forms. defaultRelaysSource says whose choice the default set was —
server-config, or connector-url when the connection URL named it via
?relays=. The write profile
additionally exposes add_relay / remove_relay to adjust the session's relay
set at runtime (per-session over HTTP; process-wide on stdio).
relay_list_get
Fetch a user's NIP-65 relay list (kind 10002). See NIP-65 Outbox Model below for the response shape.
SKOS vocabulary tools
Read tools for controlled vocabularies hosted as Nostr events or referenced by URI:
skos_list_vocabularies— list vocabularies known to the serverskos_get_vocabulary/skos_get_vocabulary_status— fetch a scheme with its concepts / check availabilityskos_get_concept— fetch a single concept with labels and relationsskos_search— search concepts across vocabularies by label
The write profile adds a vocabulary builder suite (skos_create_vocabulary,
skos_add_concept, skos_update_concept, skos_remove_concept,
skos_set_relationship, skos_add_mapping, skos_import_turtle,
skos_export_turtle, skos_delete_vocabulary) for authoring SKOS vocabularies
and publishing them as Nostr events.
extract_metadata
Fetch one or more public web pages (or PDFs) and produce an AMB/EKW form-prefill
payload. Returns OpenGraph/JSON-LD 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. Also available as a library
export: import { extractMetadata } from 'nope-mcp/lib'.
Parameters:
Fetching is SSRF-aware (private/loopback ranges are blocked).
search_calendar_events
Search for NIP-52 calendar events (date-based 31922, time-based 31923). Supports temporal filters, geohash location filtering, and hashtag filtering.
Parameters:
Each event carries naddr (NIP-19 addressable identifier) and, when
EDUFEED_APP_BASE_URL is set, url (the edufeed-app viewer at <base>/<naddr>).
Prefer citing url over sourcePage, since the viewer shows fuller event details.
list_calendar_authors
List known calendar event authors loaded from configured follow sets (NIP-51 kind 30000).
Returns author names, pubkeys, and NIP-05 identifiers. Use the returned pubkeys with
search_calendar_events(authors: [...]) to filter events by author.
Signing and Publishing
The MCP server supports signing and publishing Nostr events via NIP-46 remote signing (bunker).
Connecting a Signer
Option 1: QR Code Flow (Recommended)
- Call
signer_initto generate a nostrconnect:// URL and QR code - Scan the QR code with your bunker app (Amber, nsecBunker, etc.)
- Call
signer_awaitto wait for the connection to complete
Option 2: Bunker URL
If you have a bunker:// URL from your signer app, use signer_connect directly.
Option 3: Private Key (Development Only)
For testing, use signer_connect with an nsec and allowInsecure=true. Never use this in production.
Signer Tools
signer_init
Generate a QR code for connecting a signer app.
Parameters:
Returns:
sessionId- Session ID forsigner_awaitnostrconnectUrl- The nostrconnect:// URLqrCode- ASCII QR code for terminal display
signer_await
Wait for a bunker app to connect after scanning the QR code.
Parameters:
signer_connect
Connect directly using a bunker URL or private key.
Parameters:
signer_disconnect
Disconnect the current signer session.
signer_status
Check the current signer connection status.
Returns:
connected- Whether a signer is connecteduserPubkey- The connected user's public keyconnectedAt- ISO timestamp of connection time
Publishing Tools
sign_event
Sign an unsigned Nostr event using the connected signer.
Parameters:
publish_event
Publish a pre-signed Nostr event to relays.
Parameters:
create_and_publish_metadata
Build, sign, and publish a kind 0 profile metadata event.
Parameters:
create_and_publish_resource
Build, sign, and publish a kind 30142 AMB educational resource event.
Parameters:
NIP-65 Outbox Model
Publishing tools use the NIP-65 outbox model by default for intelligent relay selection:
- Author's write relays - Fetched from kind 10002 events
- Tagged users' read relays - For p-tagged mentions, fetches their read relays
- Default relays - Falls back to configured AMB relays
This ensures events are delivered to relays where both the author publishes and where tagged users expect to receive events.
relay_list_get
Fetch a user's NIP-65 relay list (kind 10002).
Parameters:
Returns:
pubkey- The queried public keyreadRelays- Array of relay URLs marked as readwriteRelays- Array of relay URLs marked as writetotalRead- Count of read relaystotalWrite- Count of write relays
Available Resources
Deployment
The server has three entry points; pick the one that matches your client:
src/index.ts(defaultCMD) — Nostr/ContextVM transport. No HTTP port. Clients reach it by addressing its pubkey on the configured ContextVMRELAYS. RequiresSERVER_PRIVATE_KEY.src/stdio.ts— stdio transport forcvmi serveand Claude Code as a local subprocess.src/http.ts— Streamable HTTP transport onHTTP_PORT(default3000). Use for web-based MCP clients. See Option 4 above for env vars and the handshake.
Prerequisites on the host
- Node ≥ 20 (or Bun ≥ 1.1) for runtime.
- Outbound WebSocket access to the configured AMB and ContextVM relays.
- Outbound HTTPS for the
extract_metadatatool (target pages and, optionally, the Anthropic API). - Network access to
git.edufeed.orgduring install — theamb-nostr-converterdependency is fetched as a published tarball from the edufeed npm registry (seepackage.json).
Build & run
For development without a build step: bun run src/index.ts.
Identity & secrets
SERVER_PRIVATE_KEYis the server's persistent Nostr identity. Losing or rotating it changes the pubkey clients use to address the server, so treat it as long-lived state. Mint one withnak key generate(or any Nostr keygen) and store it via your secret manager — never commit it.- The matching pubkey is what users pass to
cvmi use <pubkey>. Print it once after first start so operators can record it. ANTHROPIC_API_KEY, if used, should be scoped to this service; theextract_metadatatool will spend tokens on every call where SKOS grounding is requested.
State & persistence
The server itself is stateless on disk — all state lives on the configured relays. The only thing that needs to persist across restarts is the env file containing SERVER_PRIVATE_KEY. No volume is required for the MCP container.
Discovery
On startup with Nostr transport, the server publishes a ContextVM announcement to RELAYS. To remove an old announcement (e.g. after rotating the key or decommissioning), use scripts/unpublish-server.ts.
Operational notes
- Logging: plain stdout/stderr. Capture via your process supervisor (systemd journal, Docker logs, etc.).
- Healthcheck: the Nostr and stdio entry points have no HTTP healthcheck — liveness ≈ "process is up and the relay subscription has not errored", integrate at the supervisor level. The HTTP entry point exposes
GET /healthz(unauthenticated) for probes. extract_metadataegress: the tool fetches arbitrary URLs supplied by callers. Fetching is SSRF-aware (private/loopback ranges blocked) but you should still consider running it behind an egress proxy if your homelab restricts outbound traffic.- Resource footprint: small — a single Node process with a handful of WebSocket connections. No database, no cache directory.
Development
Run tests
Test against local relay
docker-compose.yml in this repo starts a local AMB relay (with Typesense) on
ws://localhost:3337 — it expects a sibling checkout of amb-relay at
../amb-relay for the image build:
Scripts
scripts/ask.ts— pose a natural-language question tosearch_contentand print the structured result an LLM client would receivescripts/test-client.ts— quick relay-client smoke test (relay info, queries, transforms)scripts/smoke-search-content.ts/scripts/smoke-extract-metadata.ts— live smoke tests against the dev relayscripts/unpublish-server.ts— remove server from public ContextVM discoveryscripts/delete-announcements.ts— attempt to delete announcement events
Architecture
License
This is free and unencumbered software released into the public domain. See UNLICENSE for details.
來源:README.md,提交 96bda95
工具
0版本歷史
1- v0.4.0最新Oct 3, 2026


