feat: add dictionary management functionality
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -32,3 +32,6 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
|
||||
# Data
|
||||
data
|
||||
|
||||
@@ -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
130
resources/dictionaries.ts
Normal 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
60
services/dictionaries.ts
Normal 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()
|
||||
}
|
||||
3
stdio.ts
3
stdio.ts
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user