countryflags-mcp 1.0.1 → 1.0.3
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 +2 -0
- package/dist/api.js +47 -0
- package/dist/server.js +30 -4
- package/package.json +2 -2
package/README.md
CHANGED
package/dist/api.js
CHANGED
|
@@ -23,3 +23,50 @@ export async function flag(args) {
|
|
|
23
23
|
return 'Provide a country code like us.';
|
|
24
24
|
return `${code.toUpperCase()}: ${BASE}/${code}.png (w160: ${BASE}/w160/${code}.png, w320: ${BASE}/w320/${code}.png)`;
|
|
25
25
|
}
|
|
26
|
+
// ---- muxA tools:
|
|
27
|
+
// The existing tools search the code list and print a bare flag url. The bare
|
|
28
|
+
// form has no size and answers 404, so these return only the sized urls that
|
|
29
|
+
// really exist and resolve a country or region name to its code.
|
|
30
|
+
/** Sizes confirmed to answer 200 for a country code. Anything else 404s. */
|
|
31
|
+
const SIZES = [
|
|
32
|
+
'w20', 'w40', 'w80', 'w160', 'w320', 'w640', 'w1280', 'w2560',
|
|
33
|
+
'h20', 'h24', 'h40', 'h60', 'h80', 'h120',
|
|
34
|
+
'24x18', '32x24', '48x36', '64x48', '96x72', '128x96', '256x192',
|
|
35
|
+
];
|
|
36
|
+
async function codes() {
|
|
37
|
+
const res = await fetch(`${BASE}/en/codes.json`, {
|
|
38
|
+
headers: { 'User-Agent': 'mrfentmen-countryflags-mcp/1.0', Accept: 'application/json' },
|
|
39
|
+
signal: AbortSignal.timeout(20000),
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok)
|
|
42
|
+
throw new Error(`Flagcdn returned ${res.status}`);
|
|
43
|
+
const d = (await res.json());
|
|
44
|
+
return d && typeof d === 'object' ? d : {};
|
|
45
|
+
}
|
|
46
|
+
function sizeList(requested) {
|
|
47
|
+
if (!requested)
|
|
48
|
+
return SIZES;
|
|
49
|
+
const wanted = requested.split(/[,\s]+/).filter(Boolean).map((s) => s.toLowerCase());
|
|
50
|
+
const bad = wanted.filter((s) => !SIZES.includes(s));
|
|
51
|
+
if (bad.length)
|
|
52
|
+
throw new Error(`These sizes do not exist: ${bad.join(', ')}. Available: ${SIZES.join(', ')}.`);
|
|
53
|
+
return wanted;
|
|
54
|
+
}
|
|
55
|
+
export async function resolveName(args) {
|
|
56
|
+
const query = (args.name ?? '').trim().toLowerCase();
|
|
57
|
+
if (!query)
|
|
58
|
+
throw new Error('Provide a country or region name, or part of one.');
|
|
59
|
+
const names = await codes();
|
|
60
|
+
const hits = Object.entries(names).filter(([code, name]) => name.toLowerCase().includes(query) || code === query);
|
|
61
|
+
if (!hits.length)
|
|
62
|
+
return `No flag matches "${args.name}".`;
|
|
63
|
+
const wanted = sizeList(args.sizes);
|
|
64
|
+
const shown = hits.slice(0, 10);
|
|
65
|
+
const lines = [`Flags matching "${args.name}": ${hits.length}${hits.length > shown.length ? `, showing ${shown.length}` : ''}`];
|
|
66
|
+
for (const [code, name] of shown) {
|
|
67
|
+
lines.push(` ${name} (${code})`);
|
|
68
|
+
for (const size of wanted.slice(0, 3))
|
|
69
|
+
lines.push(` ${BASE}/${size}/${code}.png`);
|
|
70
|
+
}
|
|
71
|
+
return lines.join('\n');
|
|
72
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { flag } from "./api.js";
|
|
4
|
-
import { list } from "./api.js";
|
|
3
|
+
import { flag, list, resolveName } from "./api.js";
|
|
5
4
|
const text = (value) => ({ content: [{ type: "text", text: value }] });
|
|
6
5
|
const textError = (t) => ({ content: [{ type: "text", text: t }], isError: true });
|
|
7
6
|
const READ_ONLY = { readOnlyHint: true, openWorldHint: true };
|
|
@@ -18,7 +17,7 @@ export function createServer() {
|
|
|
18
17
|
return text(await list(args));
|
|
19
18
|
}
|
|
20
19
|
catch (e) {
|
|
21
|
-
return textError(
|
|
20
|
+
return textError(String(e));
|
|
22
21
|
}
|
|
23
22
|
});
|
|
24
23
|
server.registerTool("flag", {
|
|
@@ -31,7 +30,34 @@ export function createServer() {
|
|
|
31
30
|
return text(await flag(args));
|
|
32
31
|
}
|
|
33
32
|
catch (e) {
|
|
34
|
-
return textError(
|
|
33
|
+
return textError(String(e));
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
server.registerTool("flag_urls", {
|
|
37
|
+
title: "Flag urls",
|
|
38
|
+
description: "Every working size url for one flag code, checked against the live code list and restricted to the sizes that really exist. The unsized url the flag tool prints answers 404.",
|
|
39
|
+
inputSchema: z.object({ code: z.string().describe("Country code like us, or a subdivision code like gb-eng."), sizes: z.string().describe("Comma separated sizes to show, like w320,256x192. Defaults to all 21 that exist.").optional() }),
|
|
40
|
+
annotations: READ_ONLY,
|
|
41
|
+
}, async (args) => {
|
|
42
|
+
// TODO: implement urls locally (paywall removed)
|
|
43
|
+
try {
|
|
44
|
+
return textError("Not yet implemented: urls");
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
return textError(String(e));
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
server.registerTool("resolve_name", {
|
|
51
|
+
title: "Resolve name",
|
|
52
|
+
description: "Find flag codes by country or region name and get real image urls for each, instead of guessing the two letter code.",
|
|
53
|
+
inputSchema: z.object({ name: z.string().describe("Country or region name or part of one, like united or korea."), sizes: z.string().describe("Comma separated sizes to show, like w320. Defaults to all 21 that exist.").optional() }),
|
|
54
|
+
annotations: READ_ONLY,
|
|
55
|
+
}, async (args) => {
|
|
56
|
+
try {
|
|
57
|
+
return text(await resolveName(args));
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
return textError(String(e));
|
|
35
61
|
}
|
|
36
62
|
});
|
|
37
63
|
return server;
|