Files
edgeever/apps/api/src/mcp-tools.ts
T

688 lines
22 KiB
TypeScript

const MCP_TOOL_DEFINITIONS = [
{
name: "get_current_user",
description:
"Identify the EdgeEver user and personal workspace authorized by the current session or API token. Use this before imports when the destination account must be confirmed.",
inputSchema: {
type: "object",
additionalProperties: false,
properties: {},
},
},
{
name: "search_memos",
description: "Search active EdgeEver memos by text, tag, notebook, time range, pin state, or resource presence.",
inputSchema: {
type: "object",
additionalProperties: false,
properties: {
query: { type: "string" },
notebookId: { type: "string" },
tags: { type: "array", items: { type: "string" } },
createdAfter: { type: "string", format: "date-time" },
createdBefore: { type: "string", format: "date-time" },
updatedAfter: { type: "string", format: "date-time" },
updatedBefore: { type: "string", format: "date-time" },
isPinned: { type: "boolean" },
hasResources: { type: "boolean" },
limit: { type: "integer", minimum: 1, maximum: 50 },
},
},
},
{
name: "list_memos",
description: "List EdgeEver memos with pagination. Use includeContent when full Markdown is needed.",
inputSchema: {
type: "object",
additionalProperties: false,
properties: {
notebookId: { type: "string" },
limit: { type: "integer", minimum: 1, maximum: 100 },
offset: { type: "integer", minimum: 0 },
includeContent: { type: "boolean" },
includeDeleted: { type: "boolean" },
},
},
},
{
name: "get_memo",
description: "Read a memo with Markdown content.",
inputSchema: {
type: "object",
required: ["memoId"],
additionalProperties: false,
properties: {
memoId: { type: "string" },
includeDeleted: { type: "boolean" },
},
},
},
{
name: "create_memo",
description: "Create a memo in a notebook.",
inputSchema: {
type: "object",
required: ["notebookId"],
additionalProperties: false,
properties: {
notebookId: { type: "string" },
title: { type: "string" },
contentMarkdown: { type: "string" },
tags: { type: "array", items: { type: "string" } },
createdAt: { type: "string", format: "date-time" },
updatedAt: { type: "string", format: "date-time" },
},
},
},
{
name: "import_memos",
description:
"Import up to 25 memos from an external service with database-backed idempotency. Reusing the same source and externalId returns skipped instead of creating a duplicate. Results are reported per item.",
inputSchema: {
type: "object",
required: ["source", "notebookId", "items"],
additionalProperties: false,
properties: {
source: {
type: "string",
minLength: 1,
maxLength: 80,
pattern: "^[A-Za-z0-9._-]+$",
description: "Stable source identifier such as flomo, notion, memos, or evernote.",
},
notebookId: { type: "string", description: "Destination notebook for every item in this batch." },
dryRun: { type: "boolean", description: "Validate and report existing items without creating memos." },
items: {
type: "array",
minItems: 1,
maxItems: 25,
items: {
type: "object",
required: ["externalId"],
additionalProperties: false,
properties: {
externalId: {
type: "string",
minLength: 1,
maxLength: 512,
description: "Stable ID from the source system. It is the idempotency key within source and workspace.",
},
title: { type: "string", maxLength: 160 },
contentMarkdown: { type: "string" },
tags: { type: "array", maxItems: 100, items: { type: "string" } },
createdAt: { type: "string", format: "date-time" },
updatedAt: { type: "string", format: "date-time" },
},
},
},
},
},
},
{
name: "update_memo",
description: "Update memo title, Markdown, tags, notebook, or pinned state.",
inputSchema: {
type: "object",
required: ["memoId"],
additionalProperties: false,
properties: {
memoId: { type: "string" },
title: { type: "string" },
isPinned: { type: "boolean" },
contentMarkdown: { type: "string" },
tags: { type: "array", items: { type: "string" } },
notebookId: { type: "string" },
expectedRevision: { type: "integer", minimum: 0 },
createdAt: { type: "string", format: "date-time" },
updatedAt: { type: "string", format: "date-time" },
},
},
},
{
name: "trash_memos",
description: "Move one or more active memos to trash. Use dryRun to preview affected memos.",
inputSchema: {
type: "object",
required: ["memoIds"],
additionalProperties: false,
properties: {
memoIds: { type: "array", minItems: 1, maxItems: 100, items: { type: "string" } },
dryRun: { type: "boolean" },
},
},
},
{
name: "restore_memos",
description: "Restore one or more trashed memos. If the original notebook is gone, memos are restored to the default inbox.",
inputSchema: {
type: "object",
required: ["memoIds"],
additionalProperties: false,
properties: {
memoIds: { type: "array", minItems: 1, maxItems: 100, items: { type: "string" } },
dryRun: { type: "boolean" },
},
},
},
{
name: "move_memos",
description: "Move one or more active memos to another notebook. Use dryRun to preview affected memos.",
inputSchema: {
type: "object",
required: ["memoIds", "notebookId"],
additionalProperties: false,
properties: {
memoIds: { type: "array", minItems: 1, maxItems: 100, items: { type: "string" } },
notebookId: { type: "string" },
dryRun: { type: "boolean" },
},
},
},
{
name: "add_tags_to_memos",
description: "Add tags to one or more active memos. Use dryRun to preview changed tags.",
inputSchema: {
type: "object",
required: ["memoIds", "tags"],
additionalProperties: false,
properties: {
memoIds: { type: "array", minItems: 1, maxItems: 100, items: { type: "string" } },
tags: { type: "array", minItems: 1, maxItems: 20, items: { type: "string" } },
dryRun: { type: "boolean" },
},
},
},
{
name: "remove_tags_from_memos",
description: "Remove tags from one or more active memos. Use dryRun to preview changed tags.",
inputSchema: {
type: "object",
required: ["memoIds", "tags"],
additionalProperties: false,
properties: {
memoIds: { type: "array", minItems: 1, maxItems: 100, items: { type: "string" } },
tags: { type: "array", minItems: 1, maxItems: 20, items: { type: "string" } },
dryRun: { type: "boolean" },
},
},
},
{
name: "rename_tag",
description: "Rename a tag across all active memos. This merges into an existing tag with the same normalized name.",
inputSchema: {
type: "object",
required: ["from", "to"],
additionalProperties: false,
properties: {
from: { type: "string" },
to: { type: "string" },
dryRun: { type: "boolean" },
},
},
},
{
name: "delete_tag",
description: "Remove a tag from all active memos.",
inputSchema: {
type: "object",
required: ["tag"],
additionalProperties: false,
properties: {
tag: { type: "string" },
dryRun: { type: "boolean" },
},
},
},
{
name: "merge_memos",
description: "Merge multiple active memos into a new memo and soft-delete the sources.",
inputSchema: {
type: "object",
required: ["memoIds"],
additionalProperties: false,
properties: {
memoIds: { type: "array", minItems: 2, maxItems: 50, items: { type: "string" } },
notebookId: { type: "string" },
title: { type: "string" },
},
},
},
{
name: "upload_memo_image",
description:
"Upload a base64-encoded image resource to a memo and return Markdown that can be inserted into memo content. Images are stored as provided; server-side compression is disabled to avoid Cloudflare Images quota usage.",
inputSchema: {
type: "object",
required: ["memoId", "mimeType", "dataBase64"],
additionalProperties: false,
properties: {
memoId: { type: "string" },
filename: { type: "string" },
mimeType: { type: "string", enum: ["image/png", "image/jpeg", "image/gif", "image/webp", "image/avif"] },
dataBase64: { type: "string" },
alt: { type: "string" },
},
},
},
{
name: "upload_memo_attachment",
description: "Upload a base64-encoded attachment resource to a memo and return Markdown link text that can be inserted into memo content.",
inputSchema: {
type: "object",
required: ["memoId", "filename", "mimeType", "dataBase64"],
additionalProperties: false,
properties: {
memoId: { type: "string" },
filename: { type: "string" },
mimeType: { type: "string" },
dataBase64: { type: "string" },
label: { type: "string" },
},
},
},
{
name: "list_memo_resources",
description: "List active resources attached to a memo.",
inputSchema: {
type: "object",
required: ["memoId"],
additionalProperties: false,
properties: {
memoId: { type: "string" },
},
},
},
{
name: "list_resources",
description: "List active workspace resources with storage summary.",
inputSchema: {
type: "object",
additionalProperties: false,
properties: {
limit: { type: "integer", minimum: 1, maximum: 500 },
},
},
},
{
name: "list_memo_revisions",
description: "List revision history for a memo.",
inputSchema: {
type: "object",
required: ["memoId"],
additionalProperties: false,
properties: {
memoId: { type: "string" },
limit: { type: "integer", minimum: 1, maximum: 100 },
},
},
},
{
name: "restore_memo_revision",
description: "Restore a memo to a previous revision. Use dryRun to preview the target revision.",
inputSchema: {
type: "object",
required: ["memoId", "revisionId"],
additionalProperties: false,
properties: {
memoId: { type: "string" },
revisionId: { type: "string" },
dryRun: { type: "boolean" },
},
},
},
{
name: "move_notebook",
description: "Move a notebook under another notebook or root and update its sort order.",
inputSchema: {
type: "object",
required: ["notebookId"],
additionalProperties: false,
properties: {
notebookId: { type: "string" },
parentId: { type: ["string", "null"] },
sortOrder: { type: "integer" },
},
},
},
{
name: "create_notebook",
description: "Create a notebook at the root or under another notebook.",
inputSchema: {
type: "object",
required: ["name"],
additionalProperties: false,
properties: {
name: { type: "string", minLength: 1, maxLength: 80 },
parentId: { type: ["string", "null"] },
sortOrder: { type: "integer" },
},
},
},
{
name: "rename_notebook",
description: "Rename an active notebook in the authenticated user's workspace.",
inputSchema: {
type: "object",
required: ["notebookId", "name"],
additionalProperties: false,
properties: {
notebookId: { type: "string", description: "The exact EdgeEver notebook ID." },
name: { type: "string", minLength: 1, maxLength: 80 },
},
},
},
{
name: "get_notebook",
description: "Get one active notebook by ID from the authenticated user's workspace.",
inputSchema: {
type: "object",
required: ["notebookId"],
additionalProperties: false,
properties: {
notebookId: { type: "string", description: "The exact EdgeEver notebook ID." },
},
},
},
{
name: "find_notebooks",
description: "Find active notebooks by name, optionally restricted to a parent notebook or the workspace root.",
inputSchema: {
type: "object",
required: ["name"],
additionalProperties: false,
properties: {
name: { type: "string", minLength: 1, description: "Full or partial notebook name, matched case-insensitively." },
parentId: {
type: ["string", "null"],
description: "Parent notebook ID. Pass null to search only root notebooks; omit to search all levels.",
},
exact: { type: "boolean", description: "Require an exact name match. Defaults to false." },
limit: { type: "integer", minimum: 1, maximum: 50 },
},
},
},
{
name: "resolve_notebook_path",
description:
"Resolve a slash-separated notebook path such as 'Imports/Flomo' to one exact notebook. Returns a diagnostic result instead of guessing when a segment is missing or ambiguous.",
inputSchema: {
type: "object",
required: ["path"],
additionalProperties: false,
properties: {
path: { type: "string", minLength: 1, description: "Slash-separated notebook names from the workspace root." },
},
},
},
{
name: "list_notebooks",
description:
"List active notebooks in the authenticated user's workspace. Every returned notebook is owned by that workspace; notebooks from other users are never returned.",
inputSchema: {
type: "object",
additionalProperties: false,
properties: {},
},
},
{
name: "list_tags",
description: "List tags and memo counts.",
inputSchema: {
type: "object",
additionalProperties: false,
properties: {},
},
},
{
name: "get_workspace_stats",
description: "Get notebook, memo, tag, and resource counts for workspace diagnostics.",
inputSchema: {
type: "object",
additionalProperties: false,
properties: {},
},
},
{
name: "list_note_templates",
description: "List reusable note templates in the authenticated user's workspace, including their Markdown content and tags.",
inputSchema: {
type: "object",
additionalProperties: false,
properties: {},
},
},
{
name: "get_note_template",
description: "Get one reusable note template by its exact EdgeEver template ID.",
inputSchema: {
type: "object",
required: ["templateId"],
additionalProperties: false,
properties: {
templateId: { type: "string", minLength: 1 },
},
},
},
{
name: "create_note_template",
description: "Create a reusable note template from supplied Markdown or an existing memo in the authenticated user's workspace.",
inputSchema: {
type: "object",
required: ["name"],
additionalProperties: false,
anyOf: [{ required: ["memoId"] }, { required: ["contentMarkdown"] }],
properties: {
name: { type: "string", minLength: 1, maxLength: 160 },
description: { type: "string", maxLength: 500 },
memoId: { type: "string", minLength: 1, description: "Existing memo to copy into the template." },
title: { type: ["string", "null"], maxLength: 160 },
contentMarkdown: { type: "string" },
tags: { type: "array", items: { type: "string" } },
},
},
},
{
name: "update_note_template",
description: "Update the name, description, title, Markdown content, or tags of a reusable note template.",
inputSchema: {
type: "object",
required: ["templateId"],
additionalProperties: false,
anyOf: [
{ required: ["name"] },
{ required: ["description"] },
{ required: ["title"] },
{ required: ["contentMarkdown"] },
{ required: ["tags"] },
],
properties: {
templateId: { type: "string", minLength: 1 },
name: { type: "string", minLength: 1, maxLength: 160 },
description: { type: ["string", "null"], maxLength: 500 },
title: { type: ["string", "null"], maxLength: 160 },
contentMarkdown: { type: "string" },
tags: { type: "array", items: { type: "string" } },
},
},
},
{
name: "delete_note_template",
description: "Permanently delete a reusable note template from the authenticated user's workspace.",
inputSchema: {
type: "object",
required: ["templateId"],
additionalProperties: false,
properties: {
templateId: { type: "string", minLength: 1 },
},
},
},
{
name: "use_note_template",
description: "Create a new memo from a reusable note template in the selected notebook.",
inputSchema: {
type: "object",
required: ["templateId", "notebookId"],
additionalProperties: false,
properties: {
templateId: { type: "string", minLength: 1 },
notebookId: { type: "string", minLength: 1 },
},
},
},
{
name: "list_ai_instructions",
description: "List reusable AI instructions in the authenticated user's workspace, including built-in and custom entries.",
inputSchema: {
type: "object",
additionalProperties: false,
properties: {
locale: { type: "string", description: "Optional locale such as en-US or zh-CN for unmodified built-in instructions." },
},
},
},
{
name: "get_ai_instruction",
description: "Get one reusable AI instruction by its exact EdgeEver instruction ID.",
inputSchema: {
type: "object",
required: ["instructionId"],
additionalProperties: false,
properties: {
instructionId: { type: "string", minLength: 1 },
locale: { type: "string" },
},
},
},
{
name: "create_ai_instruction",
description: "Create a custom reusable AI instruction in the authenticated user's workspace.",
inputSchema: {
type: "object",
required: ["name", "instruction"],
additionalProperties: false,
properties: {
name: { type: "string", minLength: 1, maxLength: 80 },
description: { type: "string", maxLength: 200 },
instruction: { type: "string", minLength: 1, maxLength: 2000 },
parameterKind: { type: "string", enum: ["none", "target-language", "tone"], default: "none" },
resultMode: { type: "string", enum: ["append", "replace", "both"], default: "both" },
locale: { type: "string" },
},
},
},
{
name: "update_ai_instruction",
description: "Update the text or execution behavior of a built-in or custom reusable AI instruction.",
inputSchema: {
type: "object",
required: ["instructionId"],
additionalProperties: false,
anyOf: [
{ required: ["name"] },
{ required: ["description"] },
{ required: ["instruction"] },
{ required: ["parameterKind"] },
{ required: ["resultMode"] },
],
properties: {
instructionId: { type: "string", minLength: 1 },
name: { type: "string", minLength: 1, maxLength: 80 },
description: { type: ["string", "null"], maxLength: 200 },
instruction: { type: "string", minLength: 1, maxLength: 2000 },
parameterKind: { type: "string", enum: ["none", "target-language", "tone"] },
resultMode: { type: "string", enum: ["append", "replace", "both"] },
locale: { type: "string" },
},
},
},
{
name: "delete_ai_instruction",
description: "Delete a reusable AI instruction. Deleted built-in instructions can be restored later.",
inputSchema: {
type: "object",
required: ["instructionId"],
additionalProperties: false,
properties: {
instructionId: { type: "string", minLength: 1 },
},
},
},
{
name: "restore_default_ai_instructions",
description: "Restore missing built-in AI instructions without overwriting edited instructions or custom entries.",
inputSchema: {
type: "object",
additionalProperties: false,
properties: {
locale: { type: "string" },
},
},
},
];
const READ_ONLY_MCP_TOOLS = new Set([
"get_current_user",
"search_memos",
"list_memos",
"get_memo",
"list_memo_resources",
"list_resources",
"list_memo_revisions",
"get_notebook",
"find_notebooks",
"resolve_notebook_path",
"list_notebooks",
"list_tags",
"get_workspace_stats",
"list_note_templates",
"get_note_template",
"list_ai_instructions",
"get_ai_instruction",
]);
const NON_DESTRUCTIVE_MCP_TOOLS = new Set([
"create_memo",
"import_memos",
"restore_memos",
"move_memos",
"add_tags_to_memos",
"upload_memo_image",
"upload_memo_attachment",
"move_notebook",
"create_notebook",
"rename_notebook",
"create_note_template",
"update_note_template",
"use_note_template",
"create_ai_instruction",
"update_ai_instruction",
"restore_default_ai_instructions",
]);
const IDEMPOTENT_MCP_TOOLS = new Set([
"restore_memos",
"move_memos",
"add_tags_to_memos",
"remove_tags_from_memos",
"import_memos",
"move_notebook",
"rename_notebook",
"update_note_template",
"update_ai_instruction",
"restore_default_ai_instructions",
]);
export const MCP_TOOLS = MCP_TOOL_DEFINITIONS.map((tool) => {
const readOnly = READ_ONLY_MCP_TOOLS.has(tool.name);
return {
...tool,
title: tool.name.split("_").map((part) => part[0]?.toUpperCase() + part.slice(1)).join(" "),
outputSchema: { type: "object" },
annotations: {
readOnlyHint: readOnly,
destructiveHint: readOnly ? false : !NON_DESTRUCTIVE_MCP_TOOLS.has(tool.name),
idempotentHint: readOnly || IDEMPOTENT_MCP_TOOLS.has(tool.name),
openWorldHint: false,
},
};
});