MCP server
Elyra Sjá can run as an MCP server, so your own AI assistant — Claude Desktop, Zed, Cursor, or anything else that speaks MCP — can call the analysis engine while you work.
This inverts the app's usual direction. Normally you drive Sjá and it can call a model for you (Run with AI). Here a model drives Sjá: you ask your assistant to look at a page, and it comes back with the same evidence the app would show you.
elyra-sja mcp
The server speaks JSON-RPC 2.0 over stdio. It is read-only and stateless: no file writes, no AI keys spent, no database, nothing written to disk.
Contents
Setting it up
The server is the same binary as the app, so there is nothing extra to install. Its path inside the bundle is:
/Applications/Elyra Sjá.app/Contents/MacOS/elyra-sja
Note the non-ASCII
áand the space. Most clients handle it, but if yours struggles, symlink it somewhere plain:sudo ln -sf "/Applications/Elyra Sjá.app/Contents/MacOS/elyra-sja" /usr/local/bin/elyra-sjaand use
/usr/local/bin/elyra-sjain the configuration below.
Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"elyra-sja": {
"command": "/Applications/Elyra Sjá.app/Contents/MacOS/elyra-sja",
"args": ["mcp"],
"env": {
"ELYRA_SJA_LICENSE": "SJA-…"
}
}
}
}
Restart Claude Desktop. The tools appear under the plug icon in the composer.
Zed
In ~/.config/zed/settings.json:
{
"context_servers": {
"elyra-sja": {
"command": {
"path": "/usr/local/bin/elyra-sja",
"args": ["mcp"],
"env": { "ELYRA_SJA_LICENSE": "SJA-…" }
}
}
}
}
Cursor
In ~/.cursor/mcp.json (or .cursor/mcp.json in a project, to scope it):
{
"mcpServers": {
"elyra-sja": {
"command": "/usr/local/bin/elyra-sja",
"args": ["mcp"],
"env": { "ELYRA_SJA_LICENSE": "SJA-…" }
}
}
}
Any other client
Anything that launches a command and speaks MCP over stdio will work. The command
is elyra-sja mcp; there are no flags, no ports and no config file.
To check the server by hand, without a client:
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | elyra-sja mcp
You should get one line of JSON on stdout and a ready on stdio line on stderr.
Licence
The MCP server needs a valid licence, like headless mode. It looks in two places, in order:
ELYRA_SJA_LICENSE— set it in the client'senvblock, as above.- The Keychain — if the app is activated on this machine, the stored code is
used automatically and you can leave
envout entirely.
A missing licence does not stop the server from starting. The client connects, the tools are listed, and the first call returns an explanation:
Elyra Sjá's MCP server needs a licence. Set ELYRA_SJA_LICENSE to a signed code,
or activate the app on this machine so the code is in the Keychain.
That is deliberate: exiting at startup shows up in an assistant's UI as an unexplained crash, with nothing to diagnose.
Tools
analyze_page
Analyze one page for SEO and GEO readiness. Fetches the page and nothing else.
| Parameter | Type | Required | Notes |
|---|---|---|---|
url |
string | yes | Page to analyze |
keyword |
string | no | Enables keyword-placement checks |
recipe |
string | no | audit (default), optimize, indexed, monitor, ai_visibility, research — biases the scoring |
locale |
string | no | BCP 47 market locale (e.g. de-DE), sent as Accept-Language. Omit to analyze the page exactly as served |
robots_check
Whether a specific crawler may fetch a URL, per the site's robots.txt. Resolves
the most specific user-agent group, so a site that allows * and disallows
GPTBot is correctly reported as blocking it.
| Parameter | Type | Required | Notes |
|---|---|---|---|
url |
string | yes | URL to test (the path matters — rules are per path) |
agent |
string | no | Crawler token; default GPTBot. E.g. OAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-User, PerplexityBot, Googlebot, CCBot, Bytespider |
The answer also says what that crawler is for, because the cost of blocking it depends entirely on that: an answers crawler blocked means absence from AI answers today, while a training crawler blocked costs no visibility at all.
Worked examples
Asking for an audit
You: Have a look at https://acme.test/pricing — I care about AI visibility, the keyword is "widgets".
The assistant calls analyze_page with
{"url": "https://acme.test/pricing", "keyword": "widgets", "recipe": "ai_visibility"}
and receives:
https://acme.test/pricing — score 74/100 (C)
HTTP 200 · 12 pass / 5 warn / 2 fail
Categories:
meta 100
structure 71
social 50
geo 56
indexing 78
Needs attention:
[FAIL] Content depth (structure) — 41 words — too thin to rank or be cited.
[WARN] OpenGraph tags (social) — Missing: og:description, og:image.
[WARN] Twitter Card (social) — No twitter:card meta tag.
[FAIL] Structured data (JSON-LD) (geo) — No JSON-LD — AI engines can't parse entities reliably.
[WARN] Citable schema type (geo) — Add FAQPage / Article / HowTo schema for AI citations.
[WARN] Canonical URL (indexing) — Canonical points elsewhere: http://127.0.0.1/pricing
[WARN] Internal linking (indexing) — Only 2 internal links — add contextual links.
2 question-style headings with answers on the page.
Because the output names the category and the evidence, the assistant can act on it: write the missing OpenGraph tags, or draft FAQ schema from the two question headings the page already has.
Checking a crawler
You: Can GPTBot read that page?
robots_check with {"url": "https://acme.test/pricing", "agent": "GPTBot"}:
GPTBot is NOT allowed to fetch https://acme.test/pricing
Governed by the group for: gptbot
1 disallow rule(s), 0 allow rule(s)
GPTBot is operated by OpenAI and is used for training future models — blocking it
costs no visibility today.
And the crawler that actually matters for answers:
OAI-SearchBot is allowed to fetch https://acme.test/pricing
Governed by the group for: *
1 disallow rule(s), 0 allow rule(s)
Crawl-delay: 2.0s
OAI-SearchBot is operated by OpenAI and is used for answering user questions now —
blocking it means absence from those answers.
Note how the two answers come from different groups in the same file. That
distinction is the whole point: a tool that only reads User-agent: * would have
called GPTBot allowed.
Prompts that work well
- "Audit these five URLs and tell me which one to fix first, and why."
- "Is this page blocked for any of the AI crawlers that answer questions?"
- "Compare the German and English versions of this page — use locale de-DE for one."
- "This page scores badly on GEO. Look at it, then rewrite the headings so the answers are self-contained."
The last one is where this gets genuinely useful: the assistant has the finding and your file open.
Protocol details
- Revision:
2024-11-05 - Transport: stdio, one JSON object per line
- Methods:
initialize,tools/list,tools/call,ping - Notifications (no
id) receive no response, per the JSON-RPC spec
initialize response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": { "tools": {} },
"serverInfo": { "name": "elyra-sja", "version": "0.5.0" },
"instructions": "Read-only SEO/GEO analysis. analyze_page scores a page and lists what fails; robots_check says whether a given AI crawler is allowed to fetch a URL."
}
}
Error semantics
A tool failure — a page that won't load, a missing licence, a rate limit — comes
back as a successful RPC with isError:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [{ "type": "text", "text": "Could not analyze https://…: Request failed: …" }],
"isError": true
}
}
The model then sees the reason and adapts. A JSON-RPC error object is reserved
for actual protocol faults, so an assistant doesn't report "the server broke" when
a page simply 404s:
| Code | Meaning |
|---|---|
-32700 |
Invalid JSON on the wire |
-32600 |
Missing or wrong jsonrpc version |
-32601 |
Unknown method |
-32602 |
Missing params, or an unknown tool name |
Limits and guarantees
Read-only, by design. Only analyze_page and robots_check are exposed. The
file tools that Run with AI uses are not reachable through MCP, and neither is
anything that spends an AI API key or touches the database. An assistant in a loop
must not be able to edit your checkout through a side door. A test pins the tool
list to exactly those two names, so adding a third has to be a deliberate act.
Rate limited to 30 calls per rolling minute. Sjá fetches real pages from real
sites; an assistant will happily iterate, and the caller has no limiter of its own.
Beyond the limit you get an isError result explaining the wait.
Nothing on disk. No database is opened, no app data directory is created, no
temporary files are left. A test asserts this by pointing HOME at an empty
directory and checking it stays empty.
stdout is the protocol. Every log line, warning and diagnostic goes to stderr. One stray line on stdout would corrupt the stream — which is why the server prints nothing there but responses.
Requests are not cached. Two identical calls fetch the page twice. That is honest — a page can change between calls — but worth knowing if you ask an assistant to iterate on the same URL.
Troubleshooting
The server doesn't appear in the client. Run the command by hand first:
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | \
"/Applications/Elyra Sjá.app/Contents/MacOS/elyra-sja" mcp
A JSON line on stdout means the server is fine and the problem is in the client's configuration — usually the path. Try the symlink from Setting it up.
Every tool call says it needs a licence.
Either ELYRA_SJA_LICENSE isn't reaching the process (client env blocks are not
your shell environment — a login shell's exports are usually not inherited), or
the code has expired. Codes from before 0.3.0 (ELYRA-…) no longer validate; a
current one looks like SJA-….
Calls fail after a burst. That's the rate limit. Wait a minute, and ask the assistant to work through URLs in smaller batches.
The assistant says the server crashed. Check the client's MCP log — the server writes diagnostics to stderr, which clients capture. A genuine crash is a bug: please report it with that log at elyracode.com/sja.
Analysis is slower than in the app. The app can reuse a warm HTTP connection pool across a session; a freshly launched server pays for the first TLS handshake. Subsequent calls in the same session are faster.
See also
- Features → Headless mode — the same engine as a CI gate with exit codes
- Features → AI crawlers — proving which crawlers actually visit, from your server log
- Security & privacy — what leaves the machine, and what never does