jisho-mcp 1.0.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 ADDED
@@ -0,0 +1,47 @@
1
+ # jisho mcp
2
+
3
+ The [Jisho.org](https://jisho.org) Japanese English dictionary. Words,
4
+ readings, senses, JLPT levels. No key.
5
+
6
+ ## Tools
7
+
8
+ - `search_words`, Japanese text, romaji, or English keywords
9
+ - `search_by_tag`, feature searches: `#common`, `jlpt-n1`…`jlpt-n5`,
10
+ `wanikani10`, English meanings
11
+
12
+ ## Run
13
+
14
+ ```bash
15
+ npm install && npm run build && node dist/index.js
16
+ ```
17
+
18
+ ## Example
19
+
20
+ > "What does 大丈夫 mean?"
21
+ > `search_words("daijoubu")` → 大丈夫 (だいじょうぶ), safe; sound; problem free [Na adj] ⭐common, JLPT N5
22
+
23
+ > "Give me common JLPT N5 words"
24
+ > `search_by_tag("jlpt-n5")`
25
+
26
+ ## Quick start
27
+
28
+ ```bash
29
+ npm install
30
+ npm run build
31
+ node dist/index.js
32
+ ```
33
+
34
+ The server uses stdio, so it can be connected to Claude Desktop, Cursor, VS Code, MCP Inspector, or another compatible MCP client.
35
+
36
+ ## Tools at a glance
37
+
38
+ - `search_words`: Search the Jisho Japanese-English dictionary. Accepts Japanese text,
39
+ - `search_by_tag`: Search Jisho by feature or tag:
40
+
41
+ ## Limits and privacy
42
+
43
+ This project is intentionally narrow. It should be treated as a practical helper, not a complete certification or security audit. Check the implementation and the returned data before using it with sensitive material. No credentials are required unless the project explicitly says otherwise.
44
+
45
+ ## Try it
46
+
47
+ After building, connect the server through your MCP client. The repository root also contains `smoke-test.mjs` for projects covered by the shared harness. A typical tool call starts with `search_words`.
package/dist/api.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Jisho.org API v1 client — Japanese-English dictionary.
3
+ * Docs: https://jisho.org/forum/54fefc1f6e71740b1f000000 (unofficial)
4
+ * Endpoints: /api/v1/search/words?keyword=... and /api/v1/search/sentences?keyword=...
5
+ */
6
+ export declare class JishoError extends Error {
7
+ }
8
+ export interface WordResult {
9
+ slug: string;
10
+ isCommon: boolean;
11
+ jlpt: string[];
12
+ tags: string[];
13
+ japanese: {
14
+ word?: string;
15
+ reading?: string;
16
+ }[];
17
+ senses: {
18
+ englishDefinitions: string[];
19
+ partsOfSpeech: string[];
20
+ tags: string[];
21
+ seeAlso: string[];
22
+ }[];
23
+ }
24
+ export declare function searchWords(keyword: string, limit?: number): Promise<WordResult[]>;
25
+ /**
26
+ * Tag/feature searches the words endpoint supports: "#common", "jlpt-n5",
27
+ * "wanikani10", English meanings, etc.
28
+ */
29
+ export declare function searchByTag(keyword: string, limit?: number): Promise<WordResult[]>;
30
+ export declare function formatWord(w: WordResult, index?: number): string;
package/dist/api.js ADDED
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Jisho.org API v1 client — Japanese-English dictionary.
3
+ * Docs: https://jisho.org/forum/54fefc1f6e71740b1f000000 (unofficial)
4
+ * Endpoints: /api/v1/search/words?keyword=... and /api/v1/search/sentences?keyword=...
5
+ */
6
+ const BASE = "https://jisho.org/api/v1/search";
7
+ export class JishoError extends Error {
8
+ }
9
+ async function search(kind, keyword) {
10
+ const res = await fetch(`${BASE}/${kind}?keyword=${encodeURIComponent(keyword)}`, {
11
+ headers: { Accept: "application/json", "User-Agent": "jisho-mcp/1.0" },
12
+ });
13
+ if (!res.ok)
14
+ throw new JishoError(`Jisho API error ${res.status}: ${res.statusText}`);
15
+ const data = (await res.json());
16
+ return data.data ?? [];
17
+ }
18
+ export async function searchWords(keyword, limit = 10) {
19
+ const results = await search("words", keyword);
20
+ return results.slice(0, limit).map((r) => ({
21
+ slug: r.slug,
22
+ isCommon: r.is_common,
23
+ jlpt: r.jlpt ?? [],
24
+ tags: r.tags ?? [],
25
+ japanese: r.japanese ?? [],
26
+ senses: (r.senses ?? []).map((s) => ({
27
+ englishDefinitions: s.english_definitions ?? [],
28
+ partsOfSpeech: s.parts_of_speech ?? [],
29
+ tags: s.tags ?? [],
30
+ seeAlso: s.see_also ?? [],
31
+ })),
32
+ }));
33
+ }
34
+ /**
35
+ * Tag/feature searches the words endpoint supports: "#common", "jlpt-n5",
36
+ * "wanikani10", English meanings, etc.
37
+ */
38
+ export async function searchByTag(keyword, limit = 10) {
39
+ const results = await search("words", keyword);
40
+ return results.slice(0, limit).map((r) => ({
41
+ slug: r.slug,
42
+ isCommon: r.is_common,
43
+ jlpt: r.jlpt ?? [],
44
+ tags: r.tags ?? [],
45
+ japanese: r.japanese ?? [],
46
+ senses: (r.senses ?? []).map((s) => ({
47
+ englishDefinitions: s.english_definitions ?? [],
48
+ partsOfSpeech: s.parts_of_speech ?? [],
49
+ tags: s.tags ?? [],
50
+ seeAlso: s.see_also ?? [],
51
+ })),
52
+ }));
53
+ }
54
+ // ---------------------------------------------------------------------------
55
+ // Formatting
56
+ // ---------------------------------------------------------------------------
57
+ export function formatWord(w, index = 0) {
58
+ const head = w.japanese
59
+ .map((j) => `${j.word ?? ""}${j.reading ? ` (${j.reading})` : ""}`.trim())
60
+ .filter(Boolean)
61
+ .join(" / ");
62
+ const lines = [`${index > 0 ? `${index}. ` : ""}${head || w.slug}`];
63
+ const firstSense = w.senses[0];
64
+ if (firstSense) {
65
+ const pos = firstSense.partsOfSpeech.length ? ` [${firstSense.partsOfSpeech.join(", ")}]` : "";
66
+ const defs = firstSense.englishDefinitions.slice(0, 4).join("; ");
67
+ lines.push(` ${defs}${pos}`);
68
+ }
69
+ if (w.isCommon)
70
+ lines[lines.length - 1] += " ⭐common";
71
+ if (w.jlpt.length)
72
+ lines.push(` JLPT: ${w.jlpt.join(", ")}`);
73
+ if (w.senses.length > 1) {
74
+ lines.push(` (+${w.senses.length - 1} more senses)`);
75
+ }
76
+ return lines.join("\n");
77
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
+ import { createServer } from "./server.js";
3
+ async function main() {
4
+ const server = createServer();
5
+ const transport = new StdioServerTransport();
6
+ await server.connect(transport);
7
+ console.error("Jisho MCP server running on stdio");
8
+ }
9
+ main().catch((err) => {
10
+ console.error("Fatal error:", err);
11
+ process.exit(1);
12
+ });
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function createServer(): McpServer;
package/dist/server.js ADDED
@@ -0,0 +1,63 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { JishoError, formatWord, searchByTag, searchWords, } from "./api.js";
4
+ const text = (t) => ({ content: [{ type: "text", text: t }] });
5
+ const textError = (t) => ({ content: [{ type: "text", text: t }], isError: true });
6
+ const READ_ONLY = { readOnlyHint: true, openWorldHint: true };
7
+ export function createServer() {
8
+ const server = new McpServer({
9
+ name: "jisho-mcp",
10
+ version: "1.0.0",
11
+ });
12
+ server.registerTool("search_words", {
13
+ title: "Search words",
14
+ description: "Search the Jisho Japanese-English dictionary. Accepts Japanese text, " +
15
+ "romaji, or English keywords.",
16
+ inputSchema: z.object({
17
+ keyword: z.string().describe("e.g. 'daijoubu', '大丈夫', or 'friendship'"),
18
+ limit: z.number().int().min(1).max(15).default(10).describe("Max results"),
19
+ }),
20
+ annotations: READ_ONLY,
21
+ }, async ({ keyword, limit }) => {
22
+ try {
23
+ const words = await searchWords(keyword, limit);
24
+ if (words.length === 0)
25
+ return text(`No results for "${keyword}".`);
26
+ return text(`Jisho results for "${keyword}":\n` +
27
+ words.map((w, i) => formatWord(w, i + 1)).join("\n"));
28
+ }
29
+ catch (e) {
30
+ return textError(errorMessage(e));
31
+ }
32
+ });
33
+ server.registerTool("search_by_tag", {
34
+ title: "Search by tag",
35
+ description: "Search Jisho by feature or tag: '#common' for common words, " +
36
+ "'jlpt-n5' through 'jlpt-n1' for JLPT levels, 'wanikani5' etc., " +
37
+ "or any English meaning.",
38
+ inputSchema: z.object({
39
+ keyword: z.string().describe("e.g. '#common', 'jlpt-n4', 'wanikani10', or 'friendship'"),
40
+ limit: z.number().int().min(1).max(15).default(10).describe("Max results"),
41
+ }),
42
+ annotations: READ_ONLY,
43
+ }, async ({ keyword, limit }) => {
44
+ try {
45
+ const words = await searchByTag(keyword, limit);
46
+ if (words.length === 0)
47
+ return text(`No results for "${keyword}".`);
48
+ return text(`Jisho results for "${keyword}":\n` +
49
+ words.map((w, i) => formatWord(w, i + 1)).join("\n"));
50
+ }
51
+ catch (e) {
52
+ return textError(errorMessage(e));
53
+ }
54
+ });
55
+ return server;
56
+ }
57
+ function errorMessage(e) {
58
+ if (e instanceof JishoError)
59
+ return `Error: ${e.message}`;
60
+ if (e instanceof Error)
61
+ return `Error: ${e.message}`;
62
+ return `Error: ${String(e)}`;
63
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "jisho-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Use this MCP server to Jisho, the Japanese English dictionary. Words, kanji, readings, and example sentences",
5
+ "type": "module",
6
+ "mcpName": "io.github.mrfentmen/jisho-mcp",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/mrfentmen/jisho-mcp.git"
10
+ },
11
+ "bin": {
12
+ "jisho-mcp": "./dist/index.js"
13
+ },
14
+ "main": "./dist/index.js",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json",
20
+ "start": "node dist/index.js",
21
+ "dev": "rm -rf dist && tsc -p tsconfig.json && node dist/index.js",
22
+ "inspect": "npx @modelcontextprotocol/inspector node dist/index.js"
23
+ },
24
+ "keywords": [
25
+ "mcp",
26
+ "jisho",
27
+ "japanese",
28
+ "dictionary",
29
+ "kanji",
30
+ "language",
31
+ "weeb"
32
+ ],
33
+ "license": "MIT",
34
+ "dependencies": {
35
+ "@modelcontextprotocol/sdk": "^1.0.4",
36
+ "zod": "^3.23.8"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^22.0.0",
40
+ "typescript": "^5.6.0"
41
+ },
42
+ "engines": {
43
+ "node": ">=20"
44
+ }
45
+ }