Skip to main content
Glama
epicweb-dev

Advanced MCP Features

by epicweb-dev
tools.ts14.9 kB
import { invariant } from '@epic-web/invariant' import { type CallToolResult } from '@modelcontextprotocol/sdk/types.js' import { z } from 'zod' import { createEntryInputSchema, createTagInputSchema, entryIdSchema, entrySchema, entryTagIdSchema, entryTagSchema, entryWithTagsSchema, tagIdSchema, tagSchema, updateEntryInputSchema, updateTagInputSchema, } from './db/schema.ts' import { type EpicMeMCP } from './index.ts' import { suggestTagsSampling } from './sampling.ts' import { createWrappedVideo } from './video.ts' export async function initializeTools(agent: EpicMeMCP) { agent.server.registerTool( 'create_entry', { title: 'Create Entry', description: 'Create a new journal entry', annotations: { destructiveHint: false, openWorldHint: false, } satisfies ToolAnnotations, inputSchema: createEntryInputSchema, outputSchema: { entry: entryWithTagsSchema }, }, async (entry) => { const createdEntry = await agent.db.createEntry(entry) if (entry.tags) { for (const tagId of entry.tags) { await agent.db.addTagToEntry({ entryId: createdEntry.id, tagId, }) } } void suggestTagsSampling(agent, createdEntry.id) const structuredContent = { entry: createdEntry } return { structuredContent, content: [ createText( `Entry "${createdEntry.title}" created successfully with ID "${createdEntry.id}"`, ), createEntryResourceLink(createdEntry), createText(structuredContent), ], } }, ) const getEntryTool = agent.server.registerTool( 'get_entry', { title: 'Get Entry', description: 'Get a journal entry by ID', annotations: { readOnlyHint: true, openWorldHint: false, } satisfies ToolAnnotations, inputSchema: entryIdSchema, outputSchema: { entry: entryWithTagsSchema }, }, async ({ id }) => { const entry = await agent.db.getEntry(id) invariant(entry, `Entry with ID "${id}" not found`) const structuredContent = { entry } return { structuredContent, content: [ createEntryResourceLink(entry), createText(structuredContent), ], } }, ) const listEntriesTool = agent.server.registerTool( 'list_entries', { title: 'List Entries', description: 'List all journal entries', annotations: { readOnlyHint: true, openWorldHint: false, } satisfies ToolAnnotations, outputSchema: { entries: z.array(entrySchema) }, }, async () => { const entries = await agent.db.getEntries() const entryLinks = entries.map(createEntryResourceLink) const structuredContent = { entries } return { structuredContent, content: [ createText(`Found ${entries.length} entries.`), ...entryLinks, createText(structuredContent), ], } }, ) const updateEntryTool = agent.server.registerTool( 'update_entry', { title: 'Update Entry', description: 'Update a journal entry. Fields that are not provided (or set to undefined) will not be updated. Fields that are set to null or any other value will be updated.', annotations: { destructiveHint: false, idempotentHint: true, openWorldHint: false, } satisfies ToolAnnotations, inputSchema: updateEntryInputSchema, outputSchema: { entry: entryWithTagsSchema }, }, async ({ id, ...updates }) => { const existingEntry = await agent.db.getEntry(id) invariant(existingEntry, `Entry with ID "${id}" not found`) const updatedEntry = await agent.db.updateEntry(id, updates) const structuredContent = { entry: updatedEntry } return { structuredContent, content: [ createText( `Entry "${updatedEntry.title}" (ID: ${id}) updated successfully`, ), createEntryResourceLink(updatedEntry), createText(structuredContent), ], } }, ) const deleteEntryTool = agent.server.registerTool( 'delete_entry', { title: 'Delete Entry', description: 'Delete a journal entry', annotations: { openWorldHint: false, } satisfies ToolAnnotations, inputSchema: entryIdSchema, outputSchema: { success: z.boolean(), entry: entryWithTagsSchema }, }, async ({ id }) => { const existingEntry = await agent.db.getEntry(id) invariant(existingEntry, `Entry with ID "${id}" not found`) const confirmed = await elicitConfirmation( agent, `Are you sure you want to delete entry "${existingEntry.title}" (ID: ${id})?`, ) if (!confirmed) { const structuredContent = { success: false, entry: existingEntry, } return { structuredContent, content: [ createText( `Deleting entry "${existingEntry.title}" (ID: ${id}) rejected by the user.`, ), createText(structuredContent), ], } } await agent.db.deleteEntry(id) const structuredContent = { success: true, entry: existingEntry } return { structuredContent, content: [ createText( `Entry "${existingEntry.title}" (ID: ${id}) deleted successfully`, ), createEntryResourceLink(existingEntry), createText(structuredContent), ], } }, ) agent.server.registerTool( 'create_tag', { title: 'Create Tag', description: 'Create a new tag', annotations: { destructiveHint: false, openWorldHint: false, } satisfies ToolAnnotations, inputSchema: createTagInputSchema, outputSchema: { tag: tagSchema }, }, async (tag) => { const createdTag = await agent.db.createTag(tag) const structuredContent = { tag: createdTag } return { structuredContent, content: [ createText( `Tag "${createdTag.name}" created successfully with ID "${createdTag.id}"`, ), createTagResourceLink(createdTag), createText(structuredContent), ], } }, ) const getTagTool = agent.server.registerTool( 'get_tag', { title: 'Get Tag', description: 'Get a tag by ID', annotations: { readOnlyHint: true, openWorldHint: false, } satisfies ToolAnnotations, inputSchema: tagIdSchema, outputSchema: { tag: tagSchema }, }, async ({ id }) => { const tag = await agent.db.getTag(id) invariant(tag, `Tag ID "${id}" not found`) const structuredContent = { tag } return { structuredContent, content: [createTagResourceLink(tag), createText(structuredContent)], } }, ) const listTagsTool = agent.server.registerTool( 'list_tags', { title: 'List Tags', description: 'List all tags', annotations: { readOnlyHint: true, openWorldHint: false, } satisfies ToolAnnotations, outputSchema: { tags: z.array(tagSchema) }, }, async () => { const tags = await agent.db.getTags() const tagLinks = tags.map(createTagResourceLink) const structuredContent = { tags } return { structuredContent, content: [ createText(`Found ${tags.length} tags.`), ...tagLinks, createText(structuredContent), ], } }, ) const updateTagTool = agent.server.registerTool( 'update_tag', { title: 'Update Tag', description: 'Update a tag', annotations: { destructiveHint: false, idempotentHint: true, openWorldHint: false, } satisfies ToolAnnotations, inputSchema: updateTagInputSchema, outputSchema: { tag: tagSchema }, }, async ({ id, ...updates }) => { const updatedTag = await agent.db.updateTag(id, updates) const structuredContent = { tag: updatedTag } return { structuredContent, content: [ createText( `Tag "${updatedTag.name}" (ID: ${id}) updated successfully`, ), createTagResourceLink(updatedTag), createText(structuredContent), ], } }, ) const deleteTagTool = agent.server.registerTool( 'delete_tag', { title: 'Delete Tag', description: 'Delete a tag', annotations: { openWorldHint: false, } satisfies ToolAnnotations, inputSchema: tagIdSchema, outputSchema: { success: z.boolean(), tag: tagSchema }, }, async ({ id }) => { const existingTag = await agent.db.getTag(id) invariant(existingTag, `Tag ID "${id}" not found`) const confirmed = await elicitConfirmation( agent, `Are you sure you want to delete tag "${existingTag.name}" (ID: ${id})?`, ) if (!confirmed) { const structuredContent = { success: false, tag: existingTag } return { structuredContent, content: [ createText( `Deleting tag "${existingTag.name}" (ID: ${id}) rejected by the user.`, ), createTagResourceLink(existingTag), createText(structuredContent), ], } } await agent.db.deleteTag(id) const structuredContent = { success: true, tag: existingTag } return { structuredContent, content: [ createText( `Tag "${existingTag.name}" (ID: ${id}) deleted successfully`, ), createTagResourceLink(existingTag), createText(structuredContent), ], } }, ) const addTagToEntryTool = agent.server.registerTool( 'add_tag_to_entry', { title: 'Add Tag to Entry', description: 'Add a tag to an entry', annotations: { destructiveHint: false, idempotentHint: true, openWorldHint: false, } satisfies ToolAnnotations, inputSchema: entryTagIdSchema, outputSchema: { success: z.boolean(), entryTag: entryTagSchema }, }, async ({ entryId, tagId }) => { const tag = await agent.db.getTag(tagId) const entry = await agent.db.getEntry(entryId) invariant(tag, `Tag ${tagId} not found`) invariant(entry, `Entry with ID "${entryId}" not found`) const entryTag = await agent.db.addTagToEntry({ entryId, tagId, }) const structuredContent = { success: true, entryTag } return { structuredContent, content: [ createText( `Tag "${tag.name}" (ID: ${entryTag.tagId}) added to entry "${entry.title}" (ID: ${entryTag.entryId}) successfully`, ), createTagResourceLink(tag), createEntryResourceLink(entry), createText(structuredContent), ], } }, ) agent.server.registerTool( 'create_wrapped_video', { title: 'Create Wrapped Video', description: 'Create a "wrapped" video highlighting stats of your journaling this year', annotations: { destructiveHint: false, openWorldHint: false, } satisfies ToolAnnotations, inputSchema: { year: z .number() .default(new Date().getFullYear()) .describe( 'The year to create a wrapped video for (defaults to current year)', ), mockTime: z .number() .optional() .describe( 'If set to > 0, use mock mode and this is the mock wait time in milliseconds', ), }, outputSchema: { videoUri: z.string().describe('The URI of the video') }, }, async ( { year = new Date().getFullYear(), mockTime }, { sendNotification, _meta, signal }, ) => { const entries = await agent.db.getEntries() const filteredEntries = entries.filter( (entry) => new Date(entry.createdAt * 1000).getFullYear() === year, ) const tags = await agent.db.getTags() const filteredTags = tags.filter( (tag) => new Date(tag.createdAt * 1000).getFullYear() === year, ) const videoUri = await createWrappedVideo({ entries: filteredEntries, tags: filteredTags, year, mockTime, signal, onProgress: (progress) => { const { progressToken } = _meta ?? {} if (!progressToken) return void sendNotification({ method: 'notifications/progress', params: { progressToken, progress, total: 1, message: 'Creating video...', }, }) }, }) const structuredContent = { videoUri } return { structuredContent, content: [ createText('Video created successfully'), { type: 'resource_link', uri: videoUri, name: `wrapped-${year}.mp4`, description: `Wrapped Video for ${year}`, mimeType: 'video/mp4', }, createText(structuredContent), ], } }, ) async function updateTools() { const entries = await agent.db.getEntries() if (entries.length > 0) { if (!deleteEntryTool.enabled) deleteEntryTool.enable() if (!updateEntryTool.enabled) updateEntryTool.enable() if (!listEntriesTool.enabled) listEntriesTool.enable() if (!getEntryTool.enabled) getEntryTool.enable() } else { if (deleteEntryTool.enabled) deleteEntryTool.disable() if (updateEntryTool.enabled) updateEntryTool.disable() if (listEntriesTool.enabled) listEntriesTool.disable() if (getEntryTool.enabled) getEntryTool.disable() } const tags = await agent.db.getTags() if (tags.length > 0) { if (!deleteTagTool.enabled) deleteTagTool.enable() if (!updateTagTool.enabled) updateTagTool.enable() if (!listTagsTool.enabled) listTagsTool.enable() if (!getTagTool.enabled) getTagTool.enable() } else { if (deleteTagTool.enabled) deleteTagTool.disable() if (updateTagTool.enabled) updateTagTool.disable() if (listTagsTool.enabled) listTagsTool.disable() if (getTagTool.enabled) getTagTool.disable() } if (entries.length > 0 && tags.length > 0) { if (!addTagToEntryTool.enabled) addTagToEntryTool.enable() } else { if (addTagToEntryTool.enabled) addTagToEntryTool.disable() } } agent.db.subscribe(updateTools) await updateTools() } type ToolAnnotations = { // defaults to true, so only allow false openWorldHint?: false } & ( | { // when readOnlyHint is true, none of the other annotations can be changed readOnlyHint: true } | { destructiveHint?: false // Only allow false (default is true) idempotentHint?: true // Only allow true (default is false) } ) function createText(text: unknown): CallToolResult['content'][number] { if (typeof text === 'string') { return { type: 'text', text } } else { return { type: 'text', text: JSON.stringify(text) } } } type ResourceLinkContent = Extract< CallToolResult['content'][number], { type: 'resource_link' } > function createEntryResourceLink(entry: { id: number title: string }): ResourceLinkContent { return { type: 'resource_link', uri: `epicme://entries/${entry.id}`, name: entry.title, description: `Journal Entry: "${entry.title}"`, mimeType: 'application/json', } } function createTagResourceLink(tag: { id: number name: string }): ResourceLinkContent { return { type: 'resource_link', uri: `epicme://tags/${tag.id}`, name: tag.name, description: `Tag: "${tag.name}"`, mimeType: 'application/json', } } async function elicitConfirmation(agent: EpicMeMCP, message: string) { const capabilities = agent.server.server.getClientCapabilities() if (!capabilities?.elicitation) { return true } const result = await agent.server.server.elicitInput({ message, requestedSchema: { type: 'object', properties: { confirmed: { type: 'boolean', description: 'Whether to confirm the action', }, }, }, }) return result.action === 'accept' && result.content?.confirmed === true }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/epicweb-dev/advanced-mcp-features'

If you have feedback or need assistance with the MCP directory API, please join our Discord server