feat: add dictionary management functionality

This commit is contained in:
2026-02-20 20:24:11 -06:00
parent 5f3229dc0f
commit a51842c48f
5 changed files with 198 additions and 1 deletions

3
.gitignore vendored
View File

@@ -32,3 +32,6 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Finder (MacOS) folder config
.DS_Store
# Data
data

View File

@@ -18,6 +18,7 @@
"prepare": "bun .husky/install.ts"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.26.0"
"@modelcontextprotocol/sdk": "^1.26.0",
"zod": "^4.3.6"
}
}

130
resources/dictionaries.ts Normal file
View File

@@ -0,0 +1,130 @@
import { type McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"
import { UriTemplate } from "@modelcontextprotocol/sdk/shared/uriTemplate.js"
import type { ListResourcesResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"
import z from "zod"
import {
createDictionary,
deleteDictionary,
listDictionaries,
readDictionary,
updateDictionary
} from "@/services/dictionaries"
export const registerDictionariesFunctionality = (server: McpServer) => {
server.registerResource(
"dictionaries",
new ResourceTemplate(new UriTemplate(`dictionary://{name}`), {
list: async () => {
try {
const dictionaries = await listDictionaries()
return {
resources: dictionaries.map((dictionary) => ({
name: dictionary,
uri: `dictionary://${dictionary}`,
mimeType: "text/plain"
}))
} satisfies ListResourcesResult
} catch (_) {
return {
resources: []
}
}
}
}),
{ mimeType: "text/plain" },
async (uri: URL) => {
try {
const dictionary = await readDictionary(uri.href.replace("dictionary://", ""))
return { contents: [{ uri: uri.toString(), text: dictionary }] } satisfies ReadResourceResult
} catch (error) {
if (error instanceof Error) {
return {
isError: true,
contents: [{ uri: uri.toString(), text: error.message }]
} satisfies ReadResourceResult
}
console.error(error)
return {
isError: true,
contents: [{ uri: uri.toString(), text: "An unknown error occurred." }]
} satisfies ReadResourceResult
}
}
)
server.registerTool(
"add_dictionary",
{
title: "Add Dictionary",
description: "Add a dictionary. Dictionaries are text files containing words or phrases separated by newlines.",
inputSchema: z.object({
name: z.string(),
content: z.string()
})
},
async ({ name, content }) => {
try {
const dictionaryName = await createDictionary(name, content)
return {
content: [
{ type: "text", text: `Dictionary ${name} created` },
{ type: "resource_link", uri: `dictionary://${dictionaryName}`, name: name }
]
}
} catch (error) {
if (error instanceof Error) {
return { isError: true, content: [{ type: "text", text: `An error occurred: ${error.message}` }] }
}
console.error(error)
return { isError: true, content: [{ type: "text", text: "An unknown error occurred." }] }
}
}
)
server.registerTool(
"delete_dictionary",
{
title: "Delete Dictionary",
description: "Delete a dictionary.",
inputSchema: z.object({
name: z.string()
})
},
async ({ name }) => {
try {
await deleteDictionary(name)
return { content: [{ type: "text", text: "Dictionary deleted" }] }
} catch (error) {
if (error instanceof Error) {
return { isError: true, content: [{ type: "text", text: `An error occurred: ${error.message}` }] }
}
console.error(error)
return { isError: true, content: [{ type: "text", text: "An unknown error occurred." }] }
}
}
)
server.registerTool(
"update_dictionary",
{
title: "Update Dictionary",
description: "Update a dictionary, overwriting the existing content.",
inputSchema: z.object({
name: z.string(),
content: z.string()
})
},
async ({ name, content }) => {
try {
await updateDictionary(name, content)
return { content: [{ type: "text", text: "Dictionary updated" }] }
} catch (error) {
if (error instanceof Error) {
return { isError: true, content: [{ type: "text", text: `An error occurred: ${error.message}` }] }
}
console.error(error)
return { isError: true, content: [{ type: "text", text: "An unknown error occurred." }] }
}
}
)
}

60
services/dictionaries.ts Normal file
View File

@@ -0,0 +1,60 @@
import { readdir } from "node:fs/promises"
import { join } from "node:path"
const DICTIONARIES_PATH = join(process.cwd(), "data", "dictionaries")
export const sanitizeDictionaryName = (name: string) => {
return name.replace(/[^a-zA-Z0-9_]/g, "_")
}
export const createDictionary = async (name: string, content: string): Promise<string> => {
const dictionaryName = sanitizeDictionaryName(name)
const path = join(DICTIONARIES_PATH, `${dictionaryName}.txt`)
const file = Bun.file(path)
if (await file.exists()) {
throw new Error(`Dictionary "${dictionaryName}" already exists`)
}
await file.write(content)
return dictionaryName
}
export const readDictionary = async (name: string): Promise<string> => {
const path = join(DICTIONARIES_PATH, `${sanitizeDictionaryName(name)}.txt`)
const file = Bun.file(path)
if (!(await file.exists())) {
throw new Error(`Dictionary "${name === "" || name === undefined ? "undefined" : name}" does not exist`)
}
return Bun.file(path).text()
}
export const listDictionaries = async (): Promise<string[]> => {
const files = await readdir(DICTIONARIES_PATH, { recursive: true })
return files.map((file) => file.replace(".txt", ""))
}
export const updateDictionary = async (name: string, content: string): Promise<void> => {
const path = join(DICTIONARIES_PATH, `${sanitizeDictionaryName(name)}.txt`)
const file = Bun.file(path)
if (!(await file.exists())) {
throw new Error(`Dictionary "${name}" does not exist`)
}
await file.write(content)
}
export const deleteDictionary = async (name: string): Promise<void> => {
const path = join(DICTIONARIES_PATH, `${sanitizeDictionaryName(name)}.txt`)
const file = Bun.file(path)
if (!(await file.exists())) {
throw new Error(`Dictionary "${name}" does not exist`)
}
await file.delete()
}

View File

@@ -1,10 +1,13 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import { registerDictionariesFunctionality } from "@/resources/dictionaries"
const server = new McpServer({
name: "writer-helpers",
version: "0.0.1"
})
registerDictionariesFunctionality(server)
const transport = new StdioServerTransport()
await server.connect(transport)