RISE & AI Sweden — Public MCP
A public MCP server that indexes AI-relevant publications from RISE and AI Sweden — Sweden's two government-funded AI organizations. Provided in good faith to support the growth of the agentic AI ecosystem.
Add it to your AI assistant
In most modern MCP-compatible tools, this URL is all you need. No login, no API key, no authentication.
Where to add it
These menus change often — if something looks different, search the app's settings for “MCP”.
mcpServers with the URL above.mcpServers config pointing at the URL.How to use it in a chat
Once added, just mention the name in your prompt. For example:
“Use rise-ai-sweden to give me the most important content related to Helsingborg.”
JSON config (for tools that need it)
{
"mcpServers": {
"rise-ai-sweden": {
"transport": { "type": "http", "url": "https://rise-ai-sweden.jardenberg.org/api/mcp" }
}
}
}Rate limit: 60 requests / 5 minutes per IP.
Tools exposed
Six tools, each returning JSON. Most accept a page_type filter (event · news · project · page). Examples below show the actual shape your assistant receives.
search_swedish_ai
Hybrid (semantic + lexical) search across all indexed RISE and AI Sweden content. Vector arm for topical queries; lexical arm (Swedish/English stemming + trigram fallback) for proper nouns and short keyword queries. Reciprocal-rank-fusion merge; lexical weighted 2x when query is ≤2 tokens.
{
query: string, // 2–500 chars
source?: "rise" | "ai_sweden",
lang?: "en" | "sv",
page_type?: "event" | "news" | "project" | "page",
limit?: number // 1–50, default 20
}{
"query": "Helsingborg",
"count": 2,
"hybrid": { "rrfK": 60, "weights": { "vector": 1, "lexical": 2 }, "tokenCount": 1 },
"results": [
{
"url": "https://www.ai.se/en/sector-initiatives-projects/.../national-city-lab",
"title": "National City Lab",
"source": "ai_sweden",
"sourceName": "AI Sweden",
"lang": "en",
"pageType": "project",
"score": 0.0312,
"arms": { "vector": 0.4912, "lexical": 0.0821 },
"snippet": "The National City Lab in Helsingborg...",
"fetchedAt": "2026-06-10T08:14:22Z"
},
{ "...": "..." }
]
}find_mentions
Lexical 'everything that mentions X' primitive. No embeddings — stemmed tsquery + exact-substring fallback. Includes a total field that mirrors a direct count(DISTINCT id) WHERE raw_markdown ILIKE '%term%' SQL query.
{
term: string, // 1–200 chars
source?: "rise" | "ai_sweden",
lang?: "en" | "sv",
page_type?: "event" | "news" | "project" | "page",
limit?: number // 1–100, default 25
}{
"term": "Helsingborg",
"total": 39,
"totalNote": "Exact-substring document count (ILIKE) matching the same filters. Reconciles with raw SQL.",
"returned": 25,
"results": [
{
"url": "https://www.ai.se/en/news/helsingborg-opens-national-city-lab",
"title": "Helsingborg opens the National City Lab",
"source": "ai_sweden",
"sourceName": "AI Sweden",
"lang": "en",
"pageType": "news",
"matchMode": "exact",
"rank": 0.0912,
"snippet": "Helsingborg opens the National City Lab...",
"fetchedAt": "2026-06-09T07:32:10Z"
},
{ "...": "..." }
]
}list_latest
Cheap 'what's new' view. Without arguments, returns the newest documents across both sources. Pass source / page_type to slice. No snippets — lightweight metadata only.
{
source?: "rise" | "ai_sweden",
lang?: "en" | "sv",
page_type?: "event" | "news" | "project" | "page",
limit?: number // 1–50, default 20
}{
"count": 2,
"results": [
{
"url": "https://www.ai.se/en/news/...",
"title": "New language model release",
"source": "ai_sweden",
"sourceName": "AI Sweden",
"lang": "en",
"pageType": "news",
"publishedAt": "2026-06-09T00:00:00Z",
"publishedAtSource": "meta",
"sitemapLastmod": "2026-06-09T00:00:00Z",
"fetchedAt": "2026-06-10T08:14:22Z"
},
{ "...": "..." }
]
}find_similar
Given a URL already in the index, return semantically nearest other documents. Uses the seed document's centroid embedding (average of all its chunks) so navigation chrome doesn't dominate matches. Great for 'more like this' after picking a hit. URLs are canonicalized on input, so bare-host, trailing-slash, and tracking-param variants all resolve.
{
url: string, // a URL from search_swedish_ai / list_latest
source?: "rise" | "ai_sweden",
lang?: "en" | "sv",
limit?: number // 1–25, default 10
}{
"seedUrl": "https://www.ri.se/en/what-we-do/projects/edge-ai-...",
"count": 2,
"results": [
{
"url": "https://www.ri.se/en/what-we-do/projects/tinyml-...",
"title": "TinyML for industrial sensors",
"source": "rise",
"sourceName": "RISE",
"lang": "en",
"score": 0.7912,
"snippet": "Running compact ML models on microcontrollers...",
"fetchedAt": "2026-06-07T11:02:08Z"
},
{ "...": "..." }
]
}get_document
Fetch full cleaned markdown for a single indexed URL. Use after search_swedish_ai to load complete context on the best hit. Body is capped at 80 000 chars — check `truncated` before assuming you have the whole document. `contentNote` / `extractionMethod` surface provenance (e.g. Firecrawl OCR for image-only PDFs, manually replaced stored copies).
{ url: string }{
"url": "https://www.ai.se/en/project/...",
"title": "Project title",
"lang": "en",
"pageType": "project",
"contentType": "html",
"source": "ai_sweden",
"sourceName": "AI Sweden",
"publishedAt": "2026-05-14T00:00:00Z",
"publishedAtSource": "meta",
"fetchedAt": "2026-06-08T09:01:44Z",
"bytesReplacedAt": null,
"extractionMethod": null,
"contentNote": null,
"content": "# Project title\n\nFull markdown body...",
"truncated": false
}list_sources
Discover scope and freshness: which sources are indexed, document counts, language breakdown, and the most recent fetch per source.
{}{
"sources": [
{
"slug": "rise",
"name": "RISE",
"rootUrl": "https://www.ri.se",
"documents": 412,
"languages": { "en": 280, "sv": 132 },
"lastUpdated": "2026-06-10T08:14:22Z"
},
{
"slug": "ai_sweden",
"name": "AI Sweden",
"rootUrl": "https://www.ai.se",
"documents": 388,
"languages": { "en": 250, "sv": 138 },
"lastUpdated": "2026-06-10T07:55:01Z"
}
]
}server_info
Server identity and scope: name, origin, verifiable-response spec id, build version, dataset statistics (documents per source, total chunks), and last-updated.
{}{
"name": "rise-ai-sweden",
"server": "rise-ai-sweden.jardenberg.org",
"origin": "https://rise-ai-sweden.jardenberg.org",
"endpoint": "https://rise-ai-sweden.jardenberg.org/api/mcp",
"serverOperator": "Studio Jardenberg",
"version": "v202608211730",
"spec": "org.jardenberg/verifiable-mcp",
"specVersion": "0.2.1",
"authentication": "none",
"rateLimit": "60 requests / 5 minutes per IP",
"stats": {
"documents": 2266,
"chunks": 41230,
"sources": [
{ "source": "rise", "documents": 650, "lastUpdated": "2026-08-21T08:03:34Z" },
{ "source": "ai_sweden", "documents": 1616, "lastUpdated": "2026-08-21T07:58:10Z" }
]
},
"lastUpdated": "2026-08-21T08:03:34Z"
}Trust & verification
Every tools/call response is cryptographically signed and carries a provenance block (spec org.jardenberg/verifiable-mcp 0.2.1). This is a working pilot of verifiable MCP responses — in the spirit of C2PA content credentials, but for tool output. No MCP client verifies these signatures automatically yet; the point is that you can, today, with any standard JOSE library.
What is signed
- Scope: a wrapper object
{ iat, payload, payload_digest, content_digest, provenance }—payloadis the tool result data exactly as returned (no provenance mirrored inside it). Both digests are inside the signature. - Envelope location: the JSON-RPC result's
_meta["org.jardenberg/verifiable-mcp"]={ spec, alg, kid, signed: "wrapper", jws }. Nothing security-bearing lives outside the JWS — a verifier trusts only values recovered from the verified wrapper. - Algorithm: EdDSA (Ed25519), compact JWS, protected header
typ: "verifiable-mcp+jws"(RFC 8725 explicit typing). Canonicalization: RFC 8785 (JCS) — normative for the JWS payload and every digest. - Content binding by digest:
content_digestissha256:<hex>over the exact served bytes ofcontent[0].text(canonical JSON on this server). Hash the rendered text and compare it against the signed digest;payload_digestcovers the RFC 8785 canonical payload. - Key discovery: /.well-known/mcp.json (server card with a
jwkskey set), or the dedicated JWK at /.well-known/rise-ai-sweden-mcp-public-key.json (kid= RFC 7638 thumbprint). The server-card path follows SEP-2127, which superseded SEP-1649 and settled /.well-known/mcp.json — both the card and the dedicated key file are served. - JSON-RPC error frames carry the envelope at
error.data["org.jardenberg/verifiable-mcp"](strict SDKs drop unknown members oferror), with{ id, error }— including the request id, envelope removed — as the wrapper payload. Tool-levelisErrorresults are signed like any result, wrapper payload{ isError: true, message }. - Provenance inside the wrapper:
server_operator,server,legal_basis,dataset_versionandlast_updated— pluscontent_publisherand a stringcanonical_originfor single-source responses, or apublishers[]array (and nocanonical_origin) when a response spans both sources. Nocontent_hash*keys — the two digests above are the binding. Content is published by RISE and AI Sweden and remains © its publisher: indexed under the EU TDM exception (DSM directive, art. 4), excerpts served with source attribution. This server claims no licence over it. structuredContentis byte-identical (RFC 8785) to the signedwrapper.payload— nothing is injected into it. The deprecated v0.1result.signatureandresult.provenancemirror are emitted as top-levelresultsiblings (removed in v0.3).
Verify it yourself — three steps
- Fetch
/.well-known/mcp.jsonand pick the JWK fromjwks.keyswhosekidmatches the envelope. - Verify the compact JWS (
header.payload.signature) with EdDSA —jose(JS) orpython-jose/PyJWT(Python). The verified payload is the JCS-canonical wrapper. - Hash the served
content[0].textbytes and compare with the signedwrapper.content_digest; checkwrapper.payload_digestagainst the SHA-256 of the RFC 8785 canonicalwrapper.payload.
// Node — npm i jose canonicalize
import { compactVerify, importJWK } from "jose";
import canon from "canonicalize"; // RFC 8785 (JCS)
const card = await (await fetch("https://rise-ai-sweden.jardenberg.org/.well-known/mcp.json")).json();
const jwk = card.jwks.keys[0];
const key = await importJWK({ kty: jwk.kty, crv: jwk.crv, x: jwk.x }, "EdDSA");
const { result } = mcpResponse; // one tools/call result
const env = result._meta["org.jardenberg/verifiable-mcp"];
const { payload } = await compactVerify(env.jws, key);
const wrapper = JSON.parse(new TextDecoder().decode(payload));
const sha = async (s) => "sha256:" + [...new Uint8Array(await crypto.subtle.digest(
"SHA-256", new TextEncoder().encode(s)))]
.map((b) => b.toString(16).padStart(2, "0")).join("");
// digests come from the VERIFIED wrapper, never from _meta
console.log(await sha(result.content[0].text) === wrapper.content_digest); // content binding
console.log(await sha(canon(wrapper.payload)) === wrapper.payload_digest);If the signing key is ever unavailable, responses are served unsigned — signing never blocks a query. Data fields are unchanged by this layer; the envelope and provenance are purely additive.
FAQ
- Are responses signed?
- Yes — every
tools/callresponse carries an Ed25519 (EdDSA) compact JWS over a{ iat, payload, provenance }wrapper, RFC 8785 (JCS) canonical, inresult._meta["org.jardenberg/verifiable-mcp"]. The deprecated v0.1result.signatureis still emitted. See Trust & verification above. Purely additive — no existing data field changed. - Is this open source?
- Yes — code lives at github.com/jardenberg/swedish-ai-unlocked. Issues and PRs welcome.
- Do I need an API key?
- No. Public endpoint, no auth. Rate limit: 60 requests / 5 minutes per IP.
- What's indexed?
- Public web pages and PDFs from ri.se and ai.se. No paywalled or internal content. Use
list_sourcesfor current counts and the most recent fetch per source. - How fresh is it?
- Sitemaps are re-checked automatically every day at 04:15 UTC. New and changed documents are scraped, cleaned, chunked, and embedded in the same pass. Last completed refresh: 2026-09-11 04:15 UTC.
list_sourcesalways reports the live per-source last-fetch timestamps. - Why does find_mentions return fewer rows than `total`?
totalis a reconciliation count — distinct documents whose markdown contains the exact substring, matching a direct SQLILIKEquery.returnedis what the tool hands back, capped bylimit(max 100).- Why doesn't my AI Sweden event URL show up?
- Many ai.se event pages 301-redirect to third-party sites (Invajo, Meetup, etc.). Those are deliberately skipped — the index only stores content served from ri.se and ai.se themselves.
- How short-query search works
- For queries of ≤2 tokens, the lexical arm is weighted 2× over the vector arm. Good for proper nouns and organization names; if a one-word topical query feels too literal, add a second word.
- Can I trust the dates?
publishedAtcomes from page metadata or PDF path when available, falling back to sitemap last-modified.publishedAtSourcetells you which (meta·path·sitemap).- Why does a PDF look OCR'd?
- Image-only PDFs and oversized files (~>20 MB) are routed through Firecrawl OCR.
get_documentsurfaces this viaextractionMethod: "firecrawl"and a human-readablecontentNote. - What does page_type mean?
- It's URL-derived, not editorial.
projectmeans the URL lives under a/projects/path, not that someone curated it as a project.