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
|
# Finder (MacOS) folder config
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# Data
|
||||||
|
data
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"prepare": "bun .husky/install.ts"
|
"prepare": "bun .husky/install.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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 { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
|
||||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
||||||
|
import { registerDictionariesFunctionality } from "@/resources/dictionaries"
|
||||||
|
|
||||||
const server = new McpServer({
|
const server = new McpServer({
|
||||||
name: "writer-helpers",
|
name: "writer-helpers",
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
registerDictionariesFunctionality(server)
|
||||||
|
|
||||||
const transport = new StdioServerTransport()
|
const transport = new StdioServerTransport()
|
||||||
await server.connect(transport)
|
await server.connect(transport)
|
||||||
|
|||||||
Reference in New Issue
Block a user