ask-refined-element-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Refined Element, LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,235 @@
1
+ <!-- mcp-name: io.github.refined-element/ask-refined-element -->
2
+
3
+ Part of [Refined Element](https://refinedelement.com) — an independent consultancy for Xperience by Kentico upgrades, AI-driven development, and agent-first marketing (GEO).
4
+
5
+ # Ask Refined Element MCP Server
6
+
7
+ An open-source MCP (Model Context Protocol) server that lets AI agents query Refined Element's knowledge pack: field-tested guidance on Xperience by Kentico upgrades, GEO (generative engine optimization), AI-driven development, Sentinel for Xperience by Kentico, and agent-ready CMS architecture.
8
+
9
+ The knowledge pack is **authored once in Xperience by Kentico and served headless** — to humans on the website, to AI crawlers via `llms.txt`, and to agents through this MCP server. This server is one of those delivery surfaces, and a working example of the agent-ready CMS pattern the pack itself describes.
10
+
11
+ Most of the pack is free — including priority consultation requests. Two knowledge items are premium, gated behind a single **L402 (Lightning) payment of 100 sats** — a demonstration of metered, pay-per-access content on the same content model.
12
+
13
+ ## What It Does
14
+
15
+ Give your AI agent this server and it can:
16
+
17
+ - **Search the knowledge pack** — ranked, answer-first summaries it can cite directly, across blog articles, FAQs, case studies, services, checklists, and premium playbooks on Kentico upgrades, GEO, AI development, Sentinel, and CMS architecture.
18
+ - **Fetch a full checklist** — complete, ordered, actionable steps by slug (e.g. the GEO readiness checklist).
19
+ - **Read the blog** — list recent Refined Element articles and read any one in full, with its HTML converted to clean text (headings, lists, and links preserved).
20
+ - **Request a consultation** — send a message straight to Mike Rahel; set `priority: true` to flag it as urgent (free — no payment).
21
+ - **Buy a premium playbook** — retrieve an L402-gated item: the tool returns the Lightning invoice on the first call, then the full content once the agent has paid.
22
+
23
+ ## Tools
24
+
25
+ | Tool | Paid | What you get |
26
+ |------|------|--------------|
27
+ | `search_refined_element_knowledge` | Free | Ranked matches with answer-first summaries across articles, FAQs, case studies, services, checklists, and playbooks. Article hits point at `get_article`; premium hits are flagged with their price and the slug to buy them. |
28
+ | `get_checklist` | Free | One full free checklist by slug — title, summary, topic, audience, and every ordered step. |
29
+ | `list_articles` | Free | Recent blog articles, newest first — title, publish date, one-line summary, url, and the slug to read each in full. |
30
+ | `get_article` | Free | One full blog article by slug — HTML converted to readable text (headings, lists, and links preserved), plus its canonical URL. |
31
+ | `request_consultation` | Free | Send a consultation request to Mike Rahel. Set `priority: true` to flag it as urgent — still free, no payment. |
32
+ | `get_paid_playbook` | 100 sats (L402) | One premium item by slug. First call returns a Lightning invoice + macaroon; a second call with the paid credential returns the full content. |
33
+
34
+ ### `search_refined_element_knowledge`
35
+
36
+ Full-text search over the pack. Returns ranked matches, each with an answer-first summary you can cite directly or use to decide which item to fetch in full. Premium items return a summary and slug only — get their body with `get_paid_playbook`. Free checklists can be pulled in full with `get_checklist`.
37
+
38
+ ```json
39
+ {
40
+ "query": "kentico upgrade risk",
41
+ "topic": "kentico-upgrades", // optional filter
42
+ "audience": "decision-maker", // optional filter
43
+ "limit": 5 // optional, default 5, max 20
44
+ }
45
+ ```
46
+
47
+ `topic` and `audience` accept any string and are matched exactly against each result — the taxonomy is owned by the CMS, so new values keep working without a package upgrade. Known `topic` values today (examples): `kentico-upgrades`, `geo`, `ai-development`, `sentinel`, `cms-architecture`. Known `audience` values today (examples): `developer`, `marketer`, `decision-maker`, `agent`. Only `query` is required.
48
+
49
+ ### `get_checklist`
50
+
51
+ Fetch one full free checklist by slug, e.g. `geo-readiness-checklist-basic`. Returns the complete ordered step list — each step with its one-line action and an explanatory sentence — ready to follow, quote, or hand to a user. If the slug is a premium checklist, the response points you to `get_paid_playbook`. Unknown slugs return a not-found result.
52
+
53
+ ```json
54
+ { "slug": "geo-readiness-checklist-basic" }
55
+ ```
56
+
57
+ ### `list_articles`
58
+
59
+ List the most recent posts from the Refined Element blog, newest first. Each entry has its title, publish date, a one-line summary, the canonical URL, and the slug to read it in full with `get_article`.
60
+
61
+ ```json
62
+ { "limit": 10 } // optional, default 10, max 50
63
+ ```
64
+
65
+ ### `get_article`
66
+
67
+ Read one full blog article by slug (discover slugs with `list_articles` or `search_refined_element_knowledge`). Returns the title, publish date, summary, the complete body converted from HTML to readable text — headings, lists, and links preserved — and the canonical URL. Unknown slugs return a not-found result.
68
+
69
+ ```json
70
+ { "slug": "agent-ready-cms-xperience" }
71
+ ```
72
+
73
+ ### `request_consultation`
74
+
75
+ Send a consultation request straight to Mike Rahel — for Xperience by Kentico upgrades, AI-driven development, or agent-first (GEO) work. The email is shape-checked locally (a quick `@`/length sanity check) and fully validated by the API.
76
+
77
+ ```json
78
+ {
79
+ "name": "Ada Lovelace",
80
+ "email": "ada@example.com",
81
+ "message": "We need help upgrading a KX13 site to Xperience by Kentico.",
82
+ "company": "Analytical Engines Ltd", // optional
83
+ "priority": false // optional; true = flag as urgent (free)
84
+ }
85
+ ```
86
+
87
+ Mike reads every request personally, so include enough context for him to act on it. Set `priority: true` to flag the request as urgent so Mike sees the urgency — priority is free, no payment required.
88
+
89
+ ### `get_paid_playbook` (L402-gated)
90
+
91
+ Retrieve one premium item by slug, gated at 100 sats via L402. Discover the current premium items and their slugs with `search_refined_element_knowledge` — they are flagged `[PREMIUM]`. (Examples at the time of writing: `agent-ready-cms-checklist-full`, `kentico-upgrade-risk-triage` — the CMS owns the catalog, so this list can grow without a package release.)
92
+
93
+ ```json
94
+ {
95
+ "slug": "kentico-upgrade-risk-triage",
96
+ "macaroon": "…", // omit on the first call
97
+ "preimage": "…" // omit on the first call
98
+ }
99
+ ```
100
+
101
+ See [The L402 payment flow](#the-l402-payment-flow) below.
102
+
103
+ ## Requirements
104
+
105
+ - **Node.js ≥ 22.**
106
+ - **The Ask Refined Element API (`/api/ask/*`) must be live at the configured base URL.** The default is the production site, `https://refinedelement.com`. If the API is not reachable there — for example while it is still being deployed, or when you run the site locally — point the server at the right host with the `ASK_RE_BASE_URL` environment variable (see [Development](#development)). When the base URL answers with an HTML page instead of API JSON, the tools report that the API is not deployed/reachable there rather than pretending content does not exist.
107
+
108
+ ## Quick Install
109
+
110
+ Run it with no install:
111
+
112
+ ```bash
113
+ npx -y ask-refined-element-mcp
114
+ ```
115
+
116
+ ## Claude Code
117
+
118
+ ```bash
119
+ claude mcp add ask-refined-element -- npx -y ask-refined-element-mcp
120
+ ```
121
+
122
+ Or add it to `.mcp.json` in your project:
123
+
124
+ ```json
125
+ {
126
+ "mcpServers": {
127
+ "ask-refined-element": {
128
+ "command": "npx",
129
+ "args": ["-y", "ask-refined-element-mcp"]
130
+ }
131
+ }
132
+ }
133
+ ```
134
+
135
+ ## Claude Desktop
136
+
137
+ Add to your `claude_desktop_config.json`:
138
+
139
+ ```json
140
+ {
141
+ "mcpServers": {
142
+ "ask-refined-element": {
143
+ "command": "npx",
144
+ "args": ["-y", "ask-refined-element-mcp"]
145
+ }
146
+ }
147
+ }
148
+ ```
149
+
150
+ Config file locations:
151
+ - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
152
+ - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
153
+ - **Linux:** `~/.config/claude/claude_desktop_config.json`
154
+
155
+ No API key, wallet, or secret is needed to install or run this server — it calls only public endpoints. A Lightning wallet is needed only to unlock the two premium items (see below).
156
+
157
+ ## The L402 Payment Flow
158
+
159
+ Two knowledge items are premium and gated with **L402** — the HTTP 402 "Payment Required" standard for pay-per-access over Lightning. `get_paid_playbook` handles both halves of the exchange:
160
+
161
+ 1. **Ask for the item.** Call `get_paid_playbook` with only the `slug`. The endpoint answers `402 Payment Required` with a Lightning **invoice** (100 sats) and a **macaroon**. The tool returns these as structured payment instructions — it does not error.
162
+ 2. **Pay the invoice.** Pay the Lightning invoice with any wallet that returns a payment **preimage**. The easiest path for an agent is the free [**Lightning Enable MCP server**](https://github.com/refined-element/lightning-enable-mcp) and its `pay_invoice` tool — connect a wallet via NWC or Strike ([docs.lightningenable.com](https://docs.lightningenable.com)) and it pays, tracks, and budgets automatically.
163
+ 3. **Unlock the content.** Call `get_paid_playbook` again with the **same slug** plus the `macaroon` from step 1 and the `preimage` from step 2. The tool sends `Authorization: L402 <macaroon>:<preimage>` and returns the full item.
164
+
165
+ If a credential fails to verify, the tool returns a fresh challenge with the same guidance, so the agent can retry cleanly. Run both the Lightning Enable MCP and this server together and an agent can discover, pay for, and read a premium playbook end to end without a human in the loop.
166
+
167
+ ## Development
168
+
169
+ Clone, install, build, and run the smoke tests:
170
+
171
+ ```bash
172
+ git clone https://github.com/refined-element/ask-refined-element.git
173
+ cd ask-refined-element
174
+ npm install
175
+ npm run build # tsc → dist/
176
+ npm test # vitest — mocks fetch, asserts each tool + the 402 flow
177
+ ```
178
+
179
+ Run against a local Ask Refined Element API instead of production with the `ASK_RE_BASE_URL` environment variable:
180
+
181
+ ```bash
182
+ # Default is https://refinedelement.com
183
+ ASK_RE_BASE_URL=http://localhost:23918 npx -y ask-refined-element-mcp
184
+
185
+ # The local re-xbk app may serve the API on :23918 or :23917
186
+ ASK_RE_BASE_URL=http://localhost:23917 node dist/index.js
187
+ ```
188
+
189
+ In a client config, set it under `env`:
190
+
191
+ ```json
192
+ {
193
+ "mcpServers": {
194
+ "ask-refined-element": {
195
+ "command": "node",
196
+ "args": ["/absolute/path/to/ask-refined-element/dist/index.js"],
197
+ "env": { "ASK_RE_BASE_URL": "http://localhost:23918" }
198
+ }
199
+ }
200
+ }
201
+ ```
202
+
203
+ `npm run dev` runs the TypeScript entry point directly via `tsx` without a build step.
204
+
205
+ ### Project structure
206
+
207
+ ```
208
+ ask-refined-element/
209
+ ├── src/
210
+ │ ├── index.ts # stdio entry point (bin)
211
+ │ ├── server.ts # MCP server + tool registration (zod schemas)
212
+ │ ├── tools.ts # tool logic + response formatting
213
+ │ ├── api.ts # public HTTP client (10s timeout, clean error shaping)
214
+ │ └── types.ts # API response shapes
215
+ ├── test/
216
+ │ └── tools.test.ts
217
+ ├── package.json
218
+ ├── tsconfig.json
219
+ └── LICENSE
220
+ ```
221
+
222
+ ## Related Projects
223
+
224
+ - [Lightning Enable MCP](https://github.com/refined-element/lightning-enable-mcp) — give your agent a Lightning wallet; pays L402 challenges (including this server's premium items) automatically.
225
+ - [Lightning Enable](https://lightningenable.com) — infrastructure for agent commerce over Lightning.
226
+ - [Lightning Enable Docs](https://docs.lightningenable.com) — wallet setup (NWC, Strike) and the full L402 guide.
227
+
228
+ ## Links
229
+
230
+ - [Refined Element](https://refinedelement.com) — the consultancy, and the live knowledge pack.
231
+ - [Sentinel for Xperience by Kentico](https://github.com/refined-element/xperience-community-sentinel) — the free, open-source Kentico health scanner referenced throughout the pack.
232
+
233
+ ## License
234
+
235
+ MIT © Refined Element, LLC — see [LICENSE](LICENSE).
package/dist/api.d.ts ADDED
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Thin HTTP client for the public Ask Refined Element API.
3
+ *
4
+ * The knowledge pack is authored in Xperience by Kentico and served headless;
5
+ * this client is how the MCP tools read it. Every method targets a PUBLIC
6
+ * endpoint — there are no secrets, credentials, or API keys anywhere in this
7
+ * server. The only auth this client ever sends is an L402 credential the agent
8
+ * itself supplies (macaroon + payment preimage) for the paid playbook endpoint.
9
+ *
10
+ * All methods resolve to a discriminated {@link HttpOutcome} and never throw:
11
+ * network failures and timeouts come back as `network-error` so tools can turn
12
+ * them into helpful text instead of a stack trace.
13
+ */
14
+ import type { ContactRequest } from "./types.js";
15
+ /**
16
+ * How long any single request — including reading the response body — is
17
+ * allowed to run before it is aborted.
18
+ */
19
+ export declare const DEFAULT_TIMEOUT_MS = 10000;
20
+ /** Default base URL — the production Refined Element site. */
21
+ export declare const DEFAULT_BASE_URL = "https://refinedelement.com";
22
+ /** Result of a single HTTP request. Never thrown — always returned. */
23
+ export type HttpOutcome = {
24
+ kind: "ok";
25
+ status: number;
26
+ json: unknown;
27
+ headers: Headers;
28
+ } | {
29
+ kind: "http-error";
30
+ status: number;
31
+ json: unknown | null;
32
+ text: string | null;
33
+ headers: Headers;
34
+ }
35
+ /**
36
+ * The host answered, but with an HTML page instead of API JSON — the
37
+ * `/api/ask` routes are not served at this base URL (e.g. the site's generic
38
+ * 404 page). Distinct from `http-error` so tools never translate it into
39
+ * "no item with slug X".
40
+ */
41
+ | {
42
+ kind: "api-unavailable";
43
+ status: number;
44
+ message: string;
45
+ } | {
46
+ kind: "network-error";
47
+ message: string;
48
+ };
49
+ export interface AskReClientOptions {
50
+ /** Base URL of the Ask Refined Element API. Defaults to {@link DEFAULT_BASE_URL}. */
51
+ baseUrl?: string;
52
+ /** Per-request timeout in milliseconds. Defaults to {@link DEFAULT_TIMEOUT_MS}. */
53
+ timeoutMs?: number;
54
+ /** Injectable fetch, primarily for tests. Defaults to the global `fetch`. */
55
+ fetchImpl?: typeof fetch;
56
+ }
57
+ export declare class AskReClient {
58
+ readonly baseUrl: string;
59
+ private readonly timeoutMs;
60
+ private readonly fetchImpl;
61
+ constructor(options?: AskReClientOptions);
62
+ /** GET /api/ask/knowledge?q={query} */
63
+ searchKnowledge(query: string): Promise<HttpOutcome>;
64
+ /** GET /api/ask/checklists/{slug} */
65
+ getChecklist(slug: string): Promise<HttpOutcome>;
66
+ /**
67
+ * GET /api/ask/playbooks/{slug}
68
+ *
69
+ * Without a credential the endpoint answers 402 with an L402 challenge.
70
+ * With `Authorization: L402 <macaroon>:<preimage>` it returns the full item.
71
+ */
72
+ getPlaybook(slug: string, credential?: {
73
+ macaroon: string;
74
+ preimage: string;
75
+ }): Promise<HttpOutcome>;
76
+ /** GET /api/ask/articles?limit={limit} */
77
+ listArticles(limit: number): Promise<HttpOutcome>;
78
+ /** GET /api/ask/articles/{slug} */
79
+ getArticle(slug: string): Promise<HttpOutcome>;
80
+ /** POST /api/ask/contact — free-lane consultation request. */
81
+ submitContact(body: ContactRequest): Promise<HttpOutcome>;
82
+ /**
83
+ * POST /api/ask/contact/priority — free priority-flagged consultation.
84
+ *
85
+ * Same free delivery as {@link submitContact}, but the API tags the inquiry
86
+ * with a priority subject prefix so the flagged urgency is visible. Returns
87
+ * `{ status: "received", priority: true }`.
88
+ */
89
+ submitPriorityContact(body: ContactRequest): Promise<HttpOutcome>;
90
+ /** GET wrapper over {@link request}. */
91
+ private get;
92
+ /** POST wrapper over {@link request}: sends `body` as JSON. */
93
+ private post;
94
+ /**
95
+ * Core request helper: applies the timeout to BOTH the headers phase and the
96
+ * body read, parses the body, never throws. Shared by GET and POST so both
97
+ * verbs inherit the same timeout-covers-body, HTML-detection, null-body, and
98
+ * clean-error-shaping behavior.
99
+ */
100
+ private request;
101
+ }
package/dist/api.js ADDED
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Thin HTTP client for the public Ask Refined Element API.
3
+ *
4
+ * The knowledge pack is authored in Xperience by Kentico and served headless;
5
+ * this client is how the MCP tools read it. Every method targets a PUBLIC
6
+ * endpoint — there are no secrets, credentials, or API keys anywhere in this
7
+ * server. The only auth this client ever sends is an L402 credential the agent
8
+ * itself supplies (macaroon + payment preimage) for the paid playbook endpoint.
9
+ *
10
+ * All methods resolve to a discriminated {@link HttpOutcome} and never throw:
11
+ * network failures and timeouts come back as `network-error` so tools can turn
12
+ * them into helpful text instead of a stack trace.
13
+ */
14
+ /**
15
+ * How long any single request — including reading the response body — is
16
+ * allowed to run before it is aborted.
17
+ */
18
+ export const DEFAULT_TIMEOUT_MS = 10_000;
19
+ /** Default base URL — the production Refined Element site. */
20
+ export const DEFAULT_BASE_URL = "https://refinedelement.com";
21
+ export class AskReClient {
22
+ baseUrl;
23
+ timeoutMs;
24
+ fetchImpl;
25
+ constructor(options = {}) {
26
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
27
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
28
+ this.fetchImpl = options.fetchImpl ?? fetch;
29
+ }
30
+ /** GET /api/ask/knowledge?q={query} */
31
+ searchKnowledge(query) {
32
+ const path = `/api/ask/knowledge?q=${encodeURIComponent(query)}`;
33
+ return this.get(path);
34
+ }
35
+ /** GET /api/ask/checklists/{slug} */
36
+ getChecklist(slug) {
37
+ return this.get(`/api/ask/checklists/${encodeURIComponent(slug)}`);
38
+ }
39
+ /**
40
+ * GET /api/ask/playbooks/{slug}
41
+ *
42
+ * Without a credential the endpoint answers 402 with an L402 challenge.
43
+ * With `Authorization: L402 <macaroon>:<preimage>` it returns the full item.
44
+ */
45
+ getPlaybook(slug, credential) {
46
+ const headers = {};
47
+ if (credential) {
48
+ headers.Authorization = `L402 ${credential.macaroon}:${credential.preimage}`;
49
+ }
50
+ return this.get(`/api/ask/playbooks/${encodeURIComponent(slug)}`, headers);
51
+ }
52
+ /** GET /api/ask/articles?limit={limit} */
53
+ listArticles(limit) {
54
+ return this.get(`/api/ask/articles?limit=${encodeURIComponent(String(limit))}`);
55
+ }
56
+ /** GET /api/ask/articles/{slug} */
57
+ getArticle(slug) {
58
+ return this.get(`/api/ask/articles/${encodeURIComponent(slug)}`);
59
+ }
60
+ /** POST /api/ask/contact — free-lane consultation request. */
61
+ submitContact(body) {
62
+ return this.post("/api/ask/contact", body);
63
+ }
64
+ /**
65
+ * POST /api/ask/contact/priority — free priority-flagged consultation.
66
+ *
67
+ * Same free delivery as {@link submitContact}, but the API tags the inquiry
68
+ * with a priority subject prefix so the flagged urgency is visible. Returns
69
+ * `{ status: "received", priority: true }`.
70
+ */
71
+ submitPriorityContact(body) {
72
+ return this.post("/api/ask/contact/priority", body);
73
+ }
74
+ /** GET wrapper over {@link request}. */
75
+ get(path, headers = {}) {
76
+ return this.request("GET", path, { headers });
77
+ }
78
+ /** POST wrapper over {@link request}: sends `body` as JSON. */
79
+ post(path, body, headers = {}) {
80
+ return this.request("POST", path, { headers, body });
81
+ }
82
+ /**
83
+ * Core request helper: applies the timeout to BOTH the headers phase and the
84
+ * body read, parses the body, never throws. Shared by GET and POST so both
85
+ * verbs inherit the same timeout-covers-body, HTML-detection, null-body, and
86
+ * clean-error-shaping behavior.
87
+ */
88
+ async request(method, path, options = {}) {
89
+ const url = `${this.baseUrl}${path}`;
90
+ const controller = new AbortController();
91
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
92
+ try {
93
+ let res;
94
+ try {
95
+ const init = {
96
+ method,
97
+ headers: {
98
+ Accept: "application/json",
99
+ ...(options.body !== undefined ? { "Content-Type": "application/json" } : {}),
100
+ ...(options.headers ?? {}),
101
+ },
102
+ signal: controller.signal,
103
+ };
104
+ if (options.body !== undefined) {
105
+ init.body = JSON.stringify(options.body);
106
+ }
107
+ res = await this.fetchImpl(url, init);
108
+ }
109
+ catch (err) {
110
+ if (isAborted(controller, err)) {
111
+ return {
112
+ kind: "network-error",
113
+ message: `Timed out after ${this.timeoutMs / 1000}s reaching ${this.baseUrl}. The API may be slow or unreachable — try again shortly.`,
114
+ };
115
+ }
116
+ const detail = err instanceof Error ? err.message : String(err);
117
+ return {
118
+ kind: "network-error",
119
+ message: `Could not reach the Ask Refined Element API at ${this.baseUrl} (${detail}).`,
120
+ };
121
+ }
122
+ // The body read stays under the same timeout: a server that sends
123
+ // headers and then stalls (or trickles) the body must not hang the tool
124
+ // call forever. Racing against the abort signal also covers bodies that
125
+ // are not tied to the fetch signal (e.g. injected test responses).
126
+ let parsed;
127
+ const bodyRead = readBody(res);
128
+ // If the abort wins the race, handle the late body-read rejection so it
129
+ // never surfaces as an unhandled promise rejection.
130
+ bodyRead.catch(() => { });
131
+ try {
132
+ parsed = await Promise.race([bodyRead, rejectOnAbort(controller.signal)]);
133
+ }
134
+ catch (err) {
135
+ if (isAborted(controller, err)) {
136
+ return {
137
+ kind: "network-error",
138
+ message: `Timed out after ${this.timeoutMs / 1000}s reading the response from ${this.baseUrl}. The API may be slow or unreachable — try again shortly.`,
139
+ };
140
+ }
141
+ parsed = { json: null, text: null };
142
+ }
143
+ // An HTML page where API JSON belongs means the /api/ask routes are not
144
+ // served at this base URL at all (e.g. the site's generic 404 page, or a
145
+ // fallback page on 200) — NOT that a specific item is missing.
146
+ if (parsed.json === null && isHtmlBody(res, parsed.text) && (res.status === 404 || res.ok)) {
147
+ return {
148
+ kind: "api-unavailable",
149
+ status: res.status,
150
+ message: `The Ask Refined Element API does not appear to be deployed or reachable at ${this.baseUrl} — ` +
151
+ `the server answered HTTP ${res.status} with an HTML page instead of API JSON. ` +
152
+ `If you are running the API locally, set the ASK_RE_BASE_URL environment variable to its base URL ` +
153
+ `(e.g. http://localhost:23918).`,
154
+ };
155
+ }
156
+ if (res.ok) {
157
+ return { kind: "ok", status: res.status, json: parsed.json, headers: res.headers };
158
+ }
159
+ return {
160
+ kind: "http-error",
161
+ status: res.status,
162
+ json: parsed.json,
163
+ text: parsed.text,
164
+ headers: res.headers,
165
+ };
166
+ }
167
+ finally {
168
+ clearTimeout(timer);
169
+ }
170
+ }
171
+ }
172
+ /**
173
+ * Reads a response body once, returning parsed JSON when possible plus raw
174
+ * text. May reject (e.g. when the read is aborted mid-stream) — the caller
175
+ * shapes that into a clean outcome.
176
+ */
177
+ async function readBody(res) {
178
+ const raw = await res.text();
179
+ if (!raw) {
180
+ return { json: null, text: null };
181
+ }
182
+ try {
183
+ return { json: JSON.parse(raw), text: raw };
184
+ }
185
+ catch {
186
+ return { json: null, text: raw };
187
+ }
188
+ }
189
+ /** A promise that rejects with an AbortError as soon as the signal aborts. */
190
+ function rejectOnAbort(signal) {
191
+ return new Promise((_resolve, reject) => {
192
+ const abort = () => reject(new DOMException("The response body read was aborted", "AbortError"));
193
+ if (signal.aborted) {
194
+ abort();
195
+ return;
196
+ }
197
+ signal.addEventListener("abort", abort, { once: true });
198
+ });
199
+ }
200
+ /** True when the error (or the request's own controller) indicates our timeout abort. */
201
+ function isAborted(controller, err) {
202
+ return controller.signal.aborted || (err instanceof Error && err.name === "AbortError");
203
+ }
204
+ /** True when the response body is an HTML document rather than API JSON. */
205
+ function isHtmlBody(res, text) {
206
+ const contentType = res.headers.get("content-type")?.toLowerCase() ?? "";
207
+ if (contentType.includes("text/html")) {
208
+ return true;
209
+ }
210
+ return Boolean(text && text.trimStart().startsWith("<"));
211
+ }
212
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ask-refined-element-mcp — stdio entry point.
4
+ *
5
+ * Boots the MCP server over stdio. The knowledge pack is authored in Xperience
6
+ * by Kentico and served headless; this process is one of its delivery surfaces.
7
+ *
8
+ * Configuration (all optional, all non-secret):
9
+ * ASK_RE_BASE_URL Base URL of the Ask Refined Element API.
10
+ * Default: https://refinedelement.com
11
+ * Local dev: http://localhost:23918 (or :23917)
12
+ */
13
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ask-refined-element-mcp — stdio entry point.
4
+ *
5
+ * Boots the MCP server over stdio. The knowledge pack is authored in Xperience
6
+ * by Kentico and served headless; this process is one of its delivery surfaces.
7
+ *
8
+ * Configuration (all optional, all non-secret):
9
+ * ASK_RE_BASE_URL Base URL of the Ask Refined Element API.
10
+ * Default: https://refinedelement.com
11
+ * Local dev: http://localhost:23918 (or :23917)
12
+ */
13
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14
+ import { createServer, SERVER_NAME, SERVER_VERSION } from "./server.js";
15
+ async function main() {
16
+ const baseUrl = process.env.ASK_RE_BASE_URL?.trim() || undefined;
17
+ const server = createServer({ baseUrl });
18
+ const transport = new StdioServerTransport();
19
+ await server.connect(transport);
20
+ // stderr only — stdout is reserved for the MCP protocol stream.
21
+ console.error(`${SERVER_NAME} v${SERVER_VERSION} ready (base URL: ${baseUrl ?? "https://refinedelement.com"}).`);
22
+ }
23
+ main().catch((err) => {
24
+ const message = err instanceof Error ? err.message : String(err);
25
+ console.error(`Fatal: ${message}`);
26
+ process.exit(1);
27
+ });
28
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAExE,KAAK,UAAU,IAAI;IACjB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,EAAE,IAAI,SAAS,CAAC;IAEjE,MAAM,MAAM,GAAG,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;IACzC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,gEAAgE;IAChE,OAAO,CAAC,KAAK,CACX,GAAG,WAAW,KAAK,cAAc,qBAAqB,OAAO,IAAI,4BAA4B,IAAI,CAClG,CAAC;AACJ,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,OAAO,CAAC,KAAK,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC;IACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * MCP server wiring for Ask Refined Element.
3
+ *
4
+ * Registers six tools over a shared {@link AskReClient}. Tool descriptions
5
+ * are written for agent consumption (what you get, when to use it) and mirror
6
+ * the product's Agent Endpoints spec. The heavy lifting lives in tools.ts.
7
+ */
8
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
+ import { type AskReClientOptions } from "./api.js";
10
+ export declare const SERVER_NAME = "ask-refined-element";
11
+ export declare const SERVER_VERSION = "0.1.0";
12
+ export declare function createServer(clientOptions?: AskReClientOptions): McpServer;