@mrfentmen/datamuse-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,29 @@
1
+ # Datamuse
2
+
3
+ Use this MCP server to wordplay. Rhymes, related words, spell checks, and word suggestions for any phrase.
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ npm install
9
+ npm run build
10
+ node dist/index.js
11
+ ```
12
+
13
+ The server uses stdio, so it can be connected to Claude Desktop, Cursor, VS Code, MCP Inspector, or another compatible MCP client.
14
+
15
+ ## Tools at a glance
16
+
17
+ - `rhymes_with`: Find words that rhyme with a word.
18
+ - `means_like`: Find words and phrases with a similar meaning, with definitions.
19
+ - `related_to`: Find words commonly associated with a topic.
20
+ - `spell_check`: Check a word
21
+ - `word_suggestions`: Suggest words starting with a prefix.
22
+
23
+ ## Limits and privacy
24
+
25
+ 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.
26
+
27
+ ## Try it
28
+
29
+ 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 `rhymes_with`.
package/dist/api.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ export declare class DatamuseError extends Error {
2
+ }
3
+ export interface WordHit {
4
+ word: string;
5
+ score?: number;
6
+ numSyllables?: number;
7
+ defs?: string[];
8
+ tags?: string[];
9
+ }
10
+ export declare function rhymesWith(word: string, limit?: number): Promise<WordHit[]>;
11
+ export declare function meansLike(word: string, limit?: number): Promise<WordHit[]>;
12
+ export declare function relatedTo(word: string, limit?: number): Promise<WordHit[]>;
13
+ export declare function spellCheck(word: string): Promise<WordHit[]>;
14
+ export declare function suggest(prefix: string, limit?: number): Promise<WordHit[]>;
15
+ export declare function formatHits(hits: WordHit[]): string;
16
+ export declare function formatWithDefs(hits: WordHit[]): string;
package/dist/api.js ADDED
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Datamuse client. A word engine that knows how words sound and relate.
3
+ * Keyless, no signup. Great for rhymes, thesaurus lookups, and spelling.
4
+ */
5
+ const BASE = "https://api.datamuse.com";
6
+ export class DatamuseError extends Error {
7
+ }
8
+ async function words(params) {
9
+ const qs = new URLSearchParams(params).toString();
10
+ const res = await fetch(`${BASE}/words?${qs}`, { signal: AbortSignal.timeout(15000) });
11
+ if (!res.ok)
12
+ throw new DatamuseError(`Datamuse error ${res.status}`);
13
+ return (await res.json());
14
+ }
15
+ export function rhymesWith(word, limit = 10) {
16
+ return words({ rel_rhy: word, max: String(limit) });
17
+ }
18
+ export function meansLike(word, limit = 10) {
19
+ return words({ ml: word, max: String(limit) });
20
+ }
21
+ export function relatedTo(word, limit = 10) {
22
+ return words({ rel_trg: word, max: String(limit) });
23
+ }
24
+ export async function spellCheck(word) {
25
+ return words({ sp: word, max: "5" });
26
+ }
27
+ export async function suggest(prefix, limit = 10) {
28
+ const qs = new URLSearchParams({ s: prefix, max: String(limit) }).toString();
29
+ const res = await fetch(`${BASE}/sug?${qs}`, { signal: AbortSignal.timeout(15000) });
30
+ if (!res.ok)
31
+ throw new DatamuseError(`Datamuse error ${res.status}`);
32
+ return (await res.json());
33
+ }
34
+ export function formatHits(hits) {
35
+ return hits
36
+ .map((h, i) => `${i + 1}. ${h.word}${h.numSyllables != null ? ` (${h.numSyllables} syllables)` : ""}${h.score != null ? `, score ${h.score}` : ""}`)
37
+ .join("\n");
38
+ }
39
+ export function formatWithDefs(hits) {
40
+ return hits
41
+ .map((h, i) => {
42
+ const lines = [`${i + 1}. ${h.word}`];
43
+ for (const d of h.defs ?? [])
44
+ lines.push(` ${d}`);
45
+ return lines.join("\n");
46
+ })
47
+ .join("\n");
48
+ }
@@ -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("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,92 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { DatamuseError, formatHits, formatWithDefs, meansLike, relatedTo, rhymesWith, spellCheck, suggest, } 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: "datamuse-mcp",
10
+ version: "1.0.0",
11
+ });
12
+ server.registerTool("rhymes_with", {
13
+ title: "Rhymes with",
14
+ description: "Find words that rhyme with a word.",
15
+ inputSchema: z.object({ word: z.string().describe("The word to rhyme"), limit: z.number().int().min(1).max(50).default(10) }),
16
+ annotations: READ_ONLY,
17
+ }, async ({ word, limit }) => {
18
+ try {
19
+ const hits = await rhymesWith(word, limit);
20
+ return text(hits.length ? `Words that rhyme with "${word}":\n${formatHits(hits)}` : `No rhymes for "${word}".`);
21
+ }
22
+ catch (e) {
23
+ return textError(errorMessage(e));
24
+ }
25
+ });
26
+ server.registerTool("means_like", {
27
+ title: "Means like",
28
+ description: "Find words and phrases with a similar meaning, with definitions.",
29
+ inputSchema: z.object({ word: z.string().describe("The meaning to match, e.g. 'retro gaming'"), limit: z.number().int().min(1).max(50).default(10) }),
30
+ annotations: READ_ONLY,
31
+ }, async ({ word, limit }) => {
32
+ try {
33
+ const hits = await meansLike(word, limit);
34
+ return text(hits.length ? `Words meaning like "${word}":\n${formatWithDefs(hits)}` : `Nothing meaning like "${word}".`);
35
+ }
36
+ catch (e) {
37
+ return textError(errorMessage(e));
38
+ }
39
+ });
40
+ server.registerTool("related_to", {
41
+ title: "Related to",
42
+ description: "Find words commonly associated with a topic.",
43
+ inputSchema: z.object({ word: z.string().describe("The topic, e.g. 'cave'"), limit: z.number().int().min(1).max(50).default(10) }),
44
+ annotations: READ_ONLY,
45
+ }, async ({ word, limit }) => {
46
+ try {
47
+ const hits = await relatedTo(word, limit);
48
+ return text(hits.length ? `Words related to "${word}":\n${formatHits(hits)}` : `Nothing related to "${word}".`);
49
+ }
50
+ catch (e) {
51
+ return textError(errorMessage(e));
52
+ }
53
+ });
54
+ server.registerTool("spell_check", {
55
+ title: "Spell check",
56
+ description: "Check a word's spelling and get corrections.",
57
+ inputSchema: z.object({ word: z.string().describe("The word to check") }),
58
+ annotations: READ_ONLY,
59
+ }, async ({ word }) => {
60
+ try {
61
+ const hits = await spellCheck(word);
62
+ const correct = hits.some((h) => h.word.toLowerCase() === word.toLowerCase());
63
+ const body = hits.length ? `Closest matches:\n${formatHits(hits)}` : "No close matches.";
64
+ return text(correct ? `"${word}" looks correctly spelled. ${body}` : `"${word}" may be misspelled. ${body}`);
65
+ }
66
+ catch (e) {
67
+ return textError(errorMessage(e));
68
+ }
69
+ });
70
+ server.registerTool("word_suggestions", {
71
+ title: "Word suggestions",
72
+ description: "Suggest words starting with a prefix.",
73
+ inputSchema: z.object({ prefix: z.string().describe("Word start, e.g. 'comp'"), limit: z.number().int().min(1).max(50).default(10) }),
74
+ annotations: READ_ONLY,
75
+ }, async ({ prefix, limit }) => {
76
+ try {
77
+ const hits = await suggest(prefix, limit);
78
+ return text(hits.length ? `Words starting with "${prefix}":\n${formatHits(hits)}` : `Nothing starts with "${prefix}".`);
79
+ }
80
+ catch (e) {
81
+ return textError(errorMessage(e));
82
+ }
83
+ });
84
+ return server;
85
+ }
86
+ function errorMessage(e) {
87
+ if (e instanceof DatamuseError)
88
+ return `Error: ${e.message}`;
89
+ if (e instanceof Error)
90
+ return `Error: ${e.message}`;
91
+ return `Error: ${String(e)}`;
92
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@mrfentmen/datamuse-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Use this MCP server to wordplay. Rhymes, related words, spell checks, and word suggestions for any phrase",
5
+ "type": "module",
6
+ "mcpName": "io.github.mrfentmen/datamuse-mcp",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/mrfentmen/datamuse-mcp.git"
10
+ },
11
+ "bin": {
12
+ "datamuse-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
+ "datamuse",
27
+ "api"
28
+ ],
29
+ "license": "MIT",
30
+ "dependencies": {
31
+ "@modelcontextprotocol/sdk": "^1.0.4",
32
+ "zod": "^3.23.8"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^22.0.0",
36
+ "typescript": "^5.6.0"
37
+ },
38
+ "engines": {
39
+ "node": ">=20"
40
+ }
41
+ }