altalandi-mcp 0.7.10 → 0.8.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/README.md +11 -0
- package/api-client.mjs +12 -0
- package/package.json +1 -1
- package/report.mjs +20 -1
- package/server.mjs +35 -2
package/README.md
CHANGED
|
@@ -62,6 +62,17 @@ what changed and what it cost in the terminal or the editor, not only on the web
|
|
|
62
62
|
preserve meaning, the tool returns the **original unchanged** and flags it,
|
|
63
63
|
never a silent change.
|
|
64
64
|
|
|
65
|
+
## UI strings: the Hnappasafn
|
|
66
|
+
|
|
67
|
+
`lookup_icelandic_ui_strings({ strings, minConfidence? })` looks up English buttons,
|
|
68
|
+
menus and labels (up to 200, 80 characters each) in the
|
|
69
|
+
[Hnappasafn](https://altalandi.is/hnappasafn): the Icelandic that Icelandic websites
|
|
70
|
+
and open-source software (Firefox, Thunderbird, LibreOffice, Android) actually use,
|
|
71
|
+
served only where the sources agree. No model runs, so it is instant and the same
|
|
72
|
+
string gets the same Icelandic across a project. **1 kr per string found**, from the
|
|
73
|
+
free daily characters first; a string not found costs nothing. Translate the misses
|
|
74
|
+
with `refine_icelandic`.
|
|
75
|
+
|
|
65
76
|
## Proofreading
|
|
66
77
|
|
|
67
78
|
`proofread_icelandic({ text, suggestions?, minPrecision?, noStore? })` fixes only
|
package/api-client.mjs
CHANGED
|
@@ -83,3 +83,15 @@ export async function requestSuggestionDecisions(baseUrl, apiKey, body, { fetchI
|
|
|
83
83
|
return { ok: false, error: error.message };
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
+
|
|
87
|
+
/** Look up English UI strings in the Hnappasafn (no model; 1 kr per string
|
|
88
|
+
* found, misses free). Returns {response, data}; a network failure throws. */
|
|
89
|
+
export async function requestHnappasafn(baseUrl, apiKey, body, { fetchImpl = globalThis.fetch } = {}) {
|
|
90
|
+
const response = await fetchImpl(`${baseUrl}/v1/hnappasafn`, {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
|
|
93
|
+
body: JSON.stringify(body),
|
|
94
|
+
signal: AbortSignal.timeout(30_000),
|
|
95
|
+
});
|
|
96
|
+
return { response, data: await response.json().catch(() => ({})) };
|
|
97
|
+
}
|
package/package.json
CHANGED
package/report.mjs
CHANGED
|
@@ -89,6 +89,7 @@ export function reportMarkdown(input, data, options = {}) {
|
|
|
89
89
|
`| Innfæddni | ${q.nativenessScore ?? "n/a"}/100 |`,
|
|
90
90
|
`| Ritill | ${tier.writer || "standard"} / ${tier.scoring || "?"} |`,
|
|
91
91
|
`| Auðkenni | ${data?.runUrl ? `[${data.id}](${data.runUrl})` : data?.id || "?"} |`,
|
|
92
|
+
...(data?.jobUrl ? [`| Verkið | ${data.jobUrl} |`] : []),
|
|
92
93
|
"",
|
|
93
94
|
"## Fyrir", "", fence(input), "",
|
|
94
95
|
"## Eftir", "", fence(data?.output), "",
|
|
@@ -335,7 +336,9 @@ export function reviewMarkdown(input, data, { before = null, after = null, maxCh
|
|
|
335
336
|
|
|
336
337
|
out.push("**Kostnaður**", ...costLines(data, before, after));
|
|
337
338
|
// The run's own page: the text sent, the result, the checks and the review.
|
|
338
|
-
|
|
339
|
+
// A call sent as part of a job (--job) links the job first, then this part.
|
|
340
|
+
if (data?.jobUrl) out.push(`- Verkið: ${data.jobUrl}`);
|
|
341
|
+
if (data?.runUrl) out.push(`- ${data?.jobUrl ? "Hlutinn" : "Verkið"}: ${data.runUrl}`);
|
|
339
342
|
else if (data?.id) out.push(`- Auðkenni: \`${data.id}\``);
|
|
340
343
|
return out.join("\n") + "\n";
|
|
341
344
|
}
|
|
@@ -362,6 +365,18 @@ const count = (n) => formatInt(n || 0);
|
|
|
362
365
|
/** The one summary of a folder or site run: every file, every changed passage
|
|
363
366
|
* (before and after, with the meaning verdict) and, under their own heading,
|
|
364
367
|
* the passages that keep the original text because a check did not pass. */
|
|
368
|
+
/** A job's classification (POST /v1/jobs) as one line: "Vefsíða · Bókhald",
|
|
369
|
+
* the kind and the field names. null when it is not known: uncertain, a
|
|
370
|
+
* fallback, or nothing named. */
|
|
371
|
+
export function jobTypeText(classification) {
|
|
372
|
+
const c = classification;
|
|
373
|
+
if (!c || typeof c !== "object" || c.uncertain || c.source === "fallback") return null;
|
|
374
|
+
const name = (x) => String(x?.name || x?.label || x?.code || "").replace(/\s+/g, " ").trim();
|
|
375
|
+
const kind = c.kind ? name(c.kind) : "";
|
|
376
|
+
const fields = (Array.isArray(c.fields) ? c.fields : []).map(name).filter(Boolean).join(", ");
|
|
377
|
+
return [kind, fields].filter(Boolean).join(" · ") || null;
|
|
378
|
+
}
|
|
379
|
+
|
|
365
380
|
export function batchReportMarkdown(summary) {
|
|
366
381
|
const t = summary?.totals || {};
|
|
367
382
|
const files = Array.isArray(summary?.files) ? summary.files : [];
|
|
@@ -386,8 +401,12 @@ export function batchReportMarkdown(summary) {
|
|
|
386
401
|
`| Stafir sendir | ${count(t.characters)} |`,
|
|
387
402
|
...(Number.isFinite(t.billedCharacters)
|
|
388
403
|
? [`| Stafir rukkaðir | ${t.billedCharactersApprox ? "um " : ""}${count(t.billedCharacters)} |`] : []),
|
|
404
|
+
// The job's kind and fields, as the engine classified them (when it knows).
|
|
405
|
+
...(jobTypeText(summary?.classification) ? [`| Tegund verks | ${cell(jobTypeText(summary.classification))} |`] : []),
|
|
389
406
|
`| Tegund texta | ${cell(summary?.domain || "general")} |`,
|
|
390
407
|
`| Málsnið | ${cell(summary?.register || "neutral")} |`,
|
|
408
|
+
// The run's one job page (an engine that knows jobs); never one link per call.
|
|
409
|
+
...(summary?.jobUrl ? [`| Verkið | ${summary.jobUrl} |`] : []),
|
|
391
410
|
];
|
|
392
411
|
|
|
393
412
|
if (summary?.stopped) {
|
package/server.mjs
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
15
15
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
16
16
|
import { z } from "zod";
|
|
17
|
-
import { requestMe, requestRefine, requestSuggestionDecisions } from "./api-client.mjs";
|
|
17
|
+
import { requestHnappasafn, requestMe, requestRefine, requestSuggestionDecisions } from "./api-client.mjs";
|
|
18
18
|
import { CARD_HTML, RENDER_META, UI_META, UI_MIME, UI_URI, UI_URI_ALIASES, connectCard, failureCard, proofreadCard, refineCard } from "./card.mjs";
|
|
19
19
|
import { beginLogin } from "./device.mjs";
|
|
20
20
|
import { formatProofreadResponse, outcomeOf } from "./format-response.mjs";
|
|
@@ -22,7 +22,7 @@ import { resolveKey } from "./key.mjs";
|
|
|
22
22
|
import { formatIsk, freeCharsText, reportEnabled, reviewMarkdown, writeReport } from "./report.mjs";
|
|
23
23
|
|
|
24
24
|
const BASE = (process.env.ALTALANDI_BASE_URL || "https://altalandi.is/api").replace(/\/$/, "");
|
|
25
|
-
const server = new McpServer({ name: "altalandi", version: "0.
|
|
25
|
+
const server = new McpServer({ name: "altalandi", version: "0.8.0" });
|
|
26
26
|
|
|
27
27
|
const CARD_NOTE = " When the host shows Altalandi's card, the card already shows this: do not repeat it, add at most a short sentence.";
|
|
28
28
|
|
|
@@ -185,6 +185,39 @@ server.registerTool(
|
|
|
185
185
|
},
|
|
186
186
|
);
|
|
187
187
|
|
|
188
|
+
server.registerTool(
|
|
189
|
+
"lookup_icelandic_ui_strings",
|
|
190
|
+
{
|
|
191
|
+
title: "Look up Icelandic UI strings",
|
|
192
|
+
description: "Look up English user-interface strings (buttons, menus, labels, short messages) in the Hnappasafn: the Icelandic that Icelandic websites and software (Firefox, LibreOffice, Android) actually use, e.g. 'Search' -> 'Leita', 'Save changes' -> 'Vista breytingar'. Only strings the sources agree on are returned, each with its confidence and alternatives. No model runs, so it is instant and consistent across a project. Costs 1 kr per string found, from the account's free allowance first; strings not found cost nothing: translate those with refine_icelandic. Use it first for app and website strings, up to 200 at a time. Never purchases credit.",
|
|
193
|
+
inputSchema: {
|
|
194
|
+
strings: z.array(z.string().min(1).max(80)).min(1).max(200).describe("English UI strings, exactly as they appear in the interface."),
|
|
195
|
+
minConfidence: z.enum(["high", "medium"]).optional().describe("'high' returns only strings the most sources agree on. Default medium."),
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
async ({ strings, minConfidence }) => {
|
|
199
|
+
const { key: KEY } = resolveKey();
|
|
200
|
+
if (!KEY) return connectFirst();
|
|
201
|
+
let r;
|
|
202
|
+
try {
|
|
203
|
+
r = await requestHnappasafn(BASE, KEY, { strings, ...(minConfidence ? { minConfidence } : {}) });
|
|
204
|
+
} catch (e) {
|
|
205
|
+
return requestFailed(e);
|
|
206
|
+
}
|
|
207
|
+
if (!r.response.ok) return httpError(r.response, r.data);
|
|
208
|
+
const results = r.data.results ?? [];
|
|
209
|
+
const found = results.filter((x) => x.found);
|
|
210
|
+
const missing = results.filter((x) => !x.found).map((x) => x.en);
|
|
211
|
+
const lines = found.map((x) => `- ${x.en} → **${x.is}**` + (x.alternatives?.length ? ` (eða: ${x.alternatives.join(", ")})` : "")
|
|
212
|
+
+ (x.confidence === "medium" ? " · _medium_" : ""));
|
|
213
|
+
let text = lines.length ? `**Hnappasafn:** ${found.length} of ${results.length} found` +
|
|
214
|
+
(typeof r.data.chargedIsk === "number" ? ` · ${formatIsk(r.data.chargedIsk)}` : "") + "\n\n" + lines.join("\n")
|
|
215
|
+
: "None of these strings are in the Hnappasafn.";
|
|
216
|
+
if (missing.length) text += `\n\nNot in the library (translate these with refine_icelandic): ${missing.join("; ")}`;
|
|
217
|
+
return { content: [{ type: "text", text }], structuredContent: r.data };
|
|
218
|
+
},
|
|
219
|
+
);
|
|
220
|
+
|
|
188
221
|
server.tool(
|
|
189
222
|
"altalandi_status",
|
|
190
223
|
"The connected Altalandi account: balance, today's free characters left, and where to add credit. Use when the person asks about their balance or credit, or before a large batch.",
|