obsidian-mcp-server
Version:
MCP server for Obsidian vaults — read, write, search, and surgically edit notes, tags, and frontmatter via the Local REST API plugin. STDIO or Streamable HTTP.
237 lines • 10.7 kB
TypeScript
/**
* @fileoverview obsidian_search_notes — text/jsonlogic/omnisearch search with
* MCP-spec cursor pagination. The `omnisearch` mode is added conditionally by
* the entry point only when the Omnisearch plugin's HTTP server is reachable
* at startup. Text-mode hits additionally clip per file via `maxMatchesPerHit`
* so a single match-heavy note can't blow the response budget — clipped hits
* carry `truncated: true` and `totalMatches`.
* @module mcp-server/tools/definitions/obsidian-search-notes.tool
*/
import { z } from '@cyanheads/mcp-ts-core';
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
/**
* Build the `obsidian_search_notes` tool. The `omnisearch` mode is included
* in the input/output schemas only when `omnisearchReachable` is true so the
* LLM never sees it as an option on a deployment where it can't run. Re-probe
* requires a server restart.
*/
export declare function buildSearchNotesTool({ omnisearchReachable }: {
omnisearchReachable: boolean;
}): import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
mode: z.ZodEnum<{
text: "text";
jsonlogic: "jsonlogic";
omnisearch: "omnisearch";
}>;
query: z.ZodOptional<z.ZodString>;
logic: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
contextLength: z.ZodDefault<z.ZodNumber>;
pathPrefix: z.ZodOptional<z.ZodString>;
maxMatchesPerHit: z.ZodDefault<z.ZodNumber>;
cursor: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, z.ZodObject<{
result: z.ZodDiscriminatedUnion<[z.ZodObject<{
mode: z.ZodLiteral<"text">;
hits: z.ZodArray<z.ZodObject<{
filename: z.ZodString;
matches: z.ZodArray<z.ZodObject<{
context: z.ZodString;
match: z.ZodObject<{
start: z.ZodNumber;
end: z.ZodNumber;
}, z.core.$strip>;
}, z.core.$strip>>;
totalMatches: z.ZodOptional<z.ZodNumber>;
truncated: z.ZodOptional<z.ZodBoolean>;
}, z.core.$strip>>;
totalCount: z.ZodNumber;
nextCursor: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, z.ZodObject<{
mode: z.ZodLiteral<"jsonlogic">;
hits: z.ZodArray<z.ZodObject<{
filename: z.ZodString;
result: z.ZodUnknown;
}, z.core.$strip>>;
totalCount: z.ZodNumber;
nextCursor: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, z.ZodObject<{
mode: z.ZodLiteral<"omnisearch">;
hits: z.ZodArray<z.ZodObject<{
filename: z.ZodString;
basename: z.ZodString;
score: z.ZodNumber;
foundWords: z.ZodArray<z.ZodString>;
matches: z.ZodArray<z.ZodObject<{
match: z.ZodString;
offset: z.ZodNumber;
}, z.core.$strip>>;
excerpt: z.ZodString;
}, z.core.$strip>>;
totalCount: z.ZodNumber;
nextCursor: z.ZodOptional<z.ZodString>;
truncated: z.ZodBoolean;
}, z.core.$strip>] | [z.ZodObject<{
mode: z.ZodLiteral<"text">;
hits: z.ZodArray<z.ZodObject<{
filename: z.ZodString;
matches: z.ZodArray<z.ZodObject<{
context: z.ZodString;
match: z.ZodObject<{
start: z.ZodNumber;
end: z.ZodNumber;
}, z.core.$strip>;
}, z.core.$strip>>;
totalMatches: z.ZodOptional<z.ZodNumber>;
truncated: z.ZodOptional<z.ZodBoolean>;
}, z.core.$strip>>;
totalCount: z.ZodNumber;
nextCursor: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, z.ZodObject<{
mode: z.ZodLiteral<"jsonlogic">;
hits: z.ZodArray<z.ZodObject<{
filename: z.ZodString;
result: z.ZodUnknown;
}, z.core.$strip>>;
totalCount: z.ZodNumber;
nextCursor: z.ZodOptional<z.ZodString>;
}, z.core.$strip>], "mode">;
}, z.core.$strip>, readonly [{
readonly reason: "path_prefix_invalid_mode";
readonly code: JsonRpcErrorCode.ValidationError;
readonly when: "`pathPrefix` was provided in a non-text mode (only `text` supports prefix filtering).";
readonly recovery: "Drop pathPrefix or switch mode to text for prefix filtering.";
}, {
readonly reason: "query_required";
readonly code: JsonRpcErrorCode.ValidationError;
readonly when: "`query` is missing for `text` or `omnisearch` mode (required for both).";
readonly recovery: "Pass `query` — substring for text mode, or BM25 query syntax (quoted phrases, `-exclusion`, `path:` / `ext:` filters) for omnisearch.";
}, {
readonly reason: "logic_required";
readonly code: JsonRpcErrorCode.ValidationError;
readonly when: "`logic` is missing for `jsonlogic` mode.";
readonly recovery: "Pass a JSONLogic tree as `logic`, e.g. `{\"glob\": [{\"var\": \"path\"}, \"Projects/*.md\"]}`.";
}, {
readonly reason: "omnisearch_unreachable";
readonly code: JsonRpcErrorCode.ServiceUnavailable;
readonly when: "Omnisearch was reachable at startup but is now unreachable (Obsidian quit, plugin disabled, or mobile session).";
readonly retryable: true;
readonly recovery: "Restart Obsidian with the Omnisearch plugin enabled, then restart this MCP server so it re-probes the plugin URL.";
}], {
readonly effectiveQuery: z.ZodOptional<z.ZodString>;
readonly notice: z.ZodOptional<z.ZodString>;
}>;
/**
* Static specimen for the MCP definition linter (which duck-types tool
* exports out of each `.tool.ts` file) and for existing tests that import
* the tool directly. Defaults to `omnisearchReachable: false` — the safe
* baseline that doesn't assume the optional plugin is installed. The entry
* point (`src/index.ts`) builds the live tool via `buildSearchNotesTool`
* with the actual probe result; this export is not the registered tool.
* The omnisearch-enabled variant is exercised by tests rather than the
* linter (two exports under the same tool name would collide on
* `name-unique`).
*/
export declare const obsidianSearchNotes: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
mode: z.ZodEnum<{
text: "text";
jsonlogic: "jsonlogic";
omnisearch: "omnisearch";
}>;
query: z.ZodOptional<z.ZodString>;
logic: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
contextLength: z.ZodDefault<z.ZodNumber>;
pathPrefix: z.ZodOptional<z.ZodString>;
maxMatchesPerHit: z.ZodDefault<z.ZodNumber>;
cursor: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, z.ZodObject<{
result: z.ZodDiscriminatedUnion<[z.ZodObject<{
mode: z.ZodLiteral<"text">;
hits: z.ZodArray<z.ZodObject<{
filename: z.ZodString;
matches: z.ZodArray<z.ZodObject<{
context: z.ZodString;
match: z.ZodObject<{
start: z.ZodNumber;
end: z.ZodNumber;
}, z.core.$strip>;
}, z.core.$strip>>;
totalMatches: z.ZodOptional<z.ZodNumber>;
truncated: z.ZodOptional<z.ZodBoolean>;
}, z.core.$strip>>;
totalCount: z.ZodNumber;
nextCursor: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, z.ZodObject<{
mode: z.ZodLiteral<"jsonlogic">;
hits: z.ZodArray<z.ZodObject<{
filename: z.ZodString;
result: z.ZodUnknown;
}, z.core.$strip>>;
totalCount: z.ZodNumber;
nextCursor: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, z.ZodObject<{
mode: z.ZodLiteral<"omnisearch">;
hits: z.ZodArray<z.ZodObject<{
filename: z.ZodString;
basename: z.ZodString;
score: z.ZodNumber;
foundWords: z.ZodArray<z.ZodString>;
matches: z.ZodArray<z.ZodObject<{
match: z.ZodString;
offset: z.ZodNumber;
}, z.core.$strip>>;
excerpt: z.ZodString;
}, z.core.$strip>>;
totalCount: z.ZodNumber;
nextCursor: z.ZodOptional<z.ZodString>;
truncated: z.ZodBoolean;
}, z.core.$strip>] | [z.ZodObject<{
mode: z.ZodLiteral<"text">;
hits: z.ZodArray<z.ZodObject<{
filename: z.ZodString;
matches: z.ZodArray<z.ZodObject<{
context: z.ZodString;
match: z.ZodObject<{
start: z.ZodNumber;
end: z.ZodNumber;
}, z.core.$strip>;
}, z.core.$strip>>;
totalMatches: z.ZodOptional<z.ZodNumber>;
truncated: z.ZodOptional<z.ZodBoolean>;
}, z.core.$strip>>;
totalCount: z.ZodNumber;
nextCursor: z.ZodOptional<z.ZodString>;
}, z.core.$strip>, z.ZodObject<{
mode: z.ZodLiteral<"jsonlogic">;
hits: z.ZodArray<z.ZodObject<{
filename: z.ZodString;
result: z.ZodUnknown;
}, z.core.$strip>>;
totalCount: z.ZodNumber;
nextCursor: z.ZodOptional<z.ZodString>;
}, z.core.$strip>], "mode">;
}, z.core.$strip>, readonly [{
readonly reason: "path_prefix_invalid_mode";
readonly code: JsonRpcErrorCode.ValidationError;
readonly when: "`pathPrefix` was provided in a non-text mode (only `text` supports prefix filtering).";
readonly recovery: "Drop pathPrefix or switch mode to text for prefix filtering.";
}, {
readonly reason: "query_required";
readonly code: JsonRpcErrorCode.ValidationError;
readonly when: "`query` is missing for `text` or `omnisearch` mode (required for both).";
readonly recovery: "Pass `query` — substring for text mode, or BM25 query syntax (quoted phrases, `-exclusion`, `path:` / `ext:` filters) for omnisearch.";
}, {
readonly reason: "logic_required";
readonly code: JsonRpcErrorCode.ValidationError;
readonly when: "`logic` is missing for `jsonlogic` mode.";
readonly recovery: "Pass a JSONLogic tree as `logic`, e.g. `{\"glob\": [{\"var\": \"path\"}, \"Projects/*.md\"]}`.";
}, {
readonly reason: "omnisearch_unreachable";
readonly code: JsonRpcErrorCode.ServiceUnavailable;
readonly when: "Omnisearch was reachable at startup but is now unreachable (Obsidian quit, plugin disabled, or mobile session).";
readonly retryable: true;
readonly recovery: "Restart Obsidian with the Omnisearch plugin enabled, then restart this MCP server so it re-probes the plugin URL.";
}], {
readonly effectiveQuery: z.ZodOptional<z.ZodString>;
readonly notice: z.ZodOptional<z.ZodString>;
}>;
//# sourceMappingURL=obsidian-search-notes.tool.d.ts.map