Skip to content

feat!: consolidate MCP tool surface (27 → 15) behind backward-compatible aliases#62

Merged
jack-arturo merged 11 commits into
mainfrom
feat/tool-surface-consolidation
Jul 7, 2026
Merged

feat!: consolidate MCP tool surface (27 → 15) behind backward-compatible aliases#62
jack-arturo merged 11 commits into
mainfrom
feat/tool-surface-consolidation

Conversation

@jack-arturo

Copy link
Copy Markdown
Member

Consolidates the MCP tool surface from 27 tools to 15 with zero capability
loss, behind a non-breaking alias shim. Fewer tools means less context loaded on
every turn and more reliable tool selection (27 was ~2× the 10–15 comfort ceiling).

Folds into the standing 2.0.0 release (already breaking on the Node 20.16 bump).

Changes (one commit per bucket)

Bucket Change Default surface
alias infra resolveToolAlias + TOOL_ALIASES + EVERNOTE_LEGACY_TOOLS (no behavior change) 27
resources 5→2 get_resource({guid, as: text|binary|recognition|metadata}); drop list_note_resources 24
polling 4→1 polling({action: start|stop|poll|status}) 21
connection 4→1 connection({action: status|user|reconnect|revoke}) (folds get_user_info) 18
notebooks/tags list_notebooks/list_tags absorb get_notebook/get_tag via optional name/guid 16
notes 6→5 patch_note folds into update_note patch mode (replacements[]) 15

Only safe consolidation mechanisms were used — projection, list-absorbs-get, and
admin action-unions. No action-unions on note/notebook/tag write verbs.

Backward compatibility

All 14 retired tool names stay callable: a TOOL_ALIASES map (src/tool-aliases.ts)
rewrites each to its canonical tool + args before validation, logging a one-time
deprecation notice. Retired names are hidden from ListTools by default; set
EVERNOTE_LEGACY_TOOLS=true to re-advertise them during a migration window.
Full old→new mapping in MIGRATION.md.

Verification

  • 273 unit tests pass, lint clean, build clean.
  • New __tests__/unit/tool-aliases.test.ts: proves the resolution mechanism and
    enforces two invariants over every alias (canonical exists; mapArgs output
    validates against the canonical Zod schema).
  • Live end-to-end against real Evernote (built branch over MCP stdio) for
    every bucket: get_resource as projections + OCR, alias routing, polling
    and connection (canonical output == alias output), notebook/tag list vs
    single-lookup, and a self-cleaning create→patch→verify→delete for update_note
    patch mode (via both the canonical tool and the patch_note alias).
  • Surface flips 15 ⇄ 29 with EVERNOTE_LEGACY_TOOLS; every retired call logs
    exactly one deprecation notice.

BREAKING CHANGES

  • Default tool surface reduced from 27 to 15. The 14 retired names are removed
    from ListTools (still callable as deprecated aliases; EVERNOTE_LEGACY_TOOLS=true
    re-lists them).
  • evernote_get_resource now defaults to text extraction, not binary
    download. Pass as:"binary" (or the deprecated includeData:true) for base64.

🤖 Generated with Claude Code

jack-arturo and others added 7 commits July 7, 2026 13:20
Adds src/tool-aliases.ts (resolveToolAlias + empty TOOL_ALIASES map) and
wires it into the CallTool dispatcher ahead of Zod validation, plus an
EVERNOTE_LEGACY_TOOLS ListTools branch (off by default) and a warn-once
deprecation notice. Empty map means every call resolves to itself, so there
is no behavior change. Groundwork for the 27->15 tool-surface consolidation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rojection

Replace get_resource_text, get_resource_recognition, and list_note_resources
with a projected get_resource({guid, as: text|binary|recognition|metadata})
plus the unchanged add_resource_to_note. Default as:"text".

- text/recognition/binary reached through the `as` discriminator; metadata adds
  hasRecognition + fileName (getResource gains an optional withAttributes flag,
  default false so text-extraction paths are unchanged).
- Retired names stay callable via TOOL_ALIASES (get_resource_text->as:text,
  get_resource_recognition->as:recognition, list_note_resources->get_note
  resources[]); re-advertised only under EVERNOTE_LEGACY_TOOLS.
- Dead code removed: EvernoteAPI.listNoteResources + ResourceInfo import.

Default advertised surface 27->24. Verified live against Evernote.

BREAKING CHANGE: get_resource now defaults to text extraction, not binary
download; pass as:"binary" (or deprecated includeData:true) for base64.
evernote_get_resource_text, evernote_get_resource_recognition, and
evernote_list_note_resources are removed from the default surface (still
callable as deprecated aliases; EVERNOTE_LEGACY_TOOLS=true re-lists them).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…{action})

Collapse start_polling/stop_polling/poll_now/polling_status into
evernote_polling({ action: "start"|"stop"|"poll"|"status" }). Handler logic and
response text are unchanged; the four retired names route through TOOL_ALIASES
and re-list only under EVERNOTE_LEGACY_TOOLS.

Default advertised surface 24->21. Verified live (canonical == alias output).

BREAKING CHANGE: evernote_start_polling, evernote_stop_polling,
evernote_poll_now, and evernote_polling_status are removed from the default
tool surface; use evernote_polling({action}). Old names remain callable as
deprecated aliases; EVERNOTE_LEGACY_TOOLS=true re-lists them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ction({action})

Fold health_check, get_user_info, reconnect, and revoke_auth into
evernote_connection({ action: "status"|"user"|"reconnect"|"revoke", verbose? }).
Handler logic and response shapes are unchanged: revoke/reconnect run pre-API,
status/user keep the old ensureAPI gating. Retired names route via TOOL_ALIASES
and re-list only under EVERNOTE_LEGACY_TOOLS. Removes the now-dead
HealthCheckSchema (health_check is aliased away).

Default advertised surface 21->18. Verified live (canonical == alias output).

BREAKING CHANGE: evernote_health_check, evernote_get_user_info,
evernote_reconnect, and evernote_revoke_auth are removed from the default tool
surface; use evernote_connection({action}). Old names remain callable as
deprecated aliases; EVERNOTE_LEGACY_TOOLS=true re-lists them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
list_notebooks and list_tags now return a single entity (fresh, full detail)
when given a name or guid, or the full cached list otherwise — absorbing the
retired get_notebook/get_tag. Retired names route via TOOL_ALIASES and re-list
only under EVERNOTE_LEGACY_TOOLS. Removes the now-dead GetNotebookSchema/
GetTagSchema.

Default advertised surface 18->16. Verified live (alias == single-lookup).

BREAKING CHANGE: evernote_get_notebook and evernote_get_tag are removed from
the default tool surface; call evernote_list_notebooks / evernote_list_tags
with a name or guid instead. Old names remain callable as deprecated aliases;
EVERNOTE_LEGACY_TOOLS=true re-lists them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ements[])

update_note gains an optional replacements[] input: when present it applies the
targeted find-and-replace patch (preserving title/tags/notebook/attachments) and
is mutually exclusive with title/content/tags/notebookName (enforced by
superRefine). Absent, it behaves as the existing full-field update. patch_note is
retired to a TOOL_ALIASES entry (re-listed only under EVERNOTE_LEGACY_TOOLS);
PatchNoteSchema is removed.

Completes the consolidation: default advertised surface is now 15 tools (was 27).

BREAKING CHANGE: evernote_patch_note is removed from the default tool surface;
call evernote_update_note with replacements[] instead. The old name remains
callable as a deprecated alias; EVERNOTE_LEGACY_TOOLS=true re-lists it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rewrite the MCP Tools section (27 -> 15): document the get_resource `as`
projection, list-absorbs-get for notebooks/tags, update_note patch mode, and the
polling/connection action tools. Add MIGRATION.md mapping every retired tool to
its canonical form + args, and note the EVERNOTE_LEGACY_TOOLS grace window.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 60fedd3eb4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tool-schemas.ts
Comment thread src/tool-aliases.ts Outdated
Comment thread src/index.ts
…re-auth

- validateToolArgs coerces omitted `arguments` to {} so the zero-arg list-all
  form of list_notebooks/list_tags works (object schemas no longer reject
  undefined); required-arg tools still error properly.
- evernote_connection({action:"status"}) (and the health_check alias) now run
  before ensureAPI(), so the diagnostic reports needs_auth/needs_setup for an
  unauthenticated server instead of surfacing a generic auth error.

The Codex finding on the list_note_resources alias was verified false live: the
get_note path returns resources[] metadata even with withResourcesData=false.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jack-arturo

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2bb9cd3347

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tool-aliases.ts Outdated
Comment thread src/tool-aliases.ts
Comment thread src/index.ts
…/forceUpdate

- list_note_resources is no longer aliased to get_note (which changed the
  response from a top-level array to a nested object and dropped
  hash/hasRecognition). It keeps a dedicated hidden handler returning the exact
  original array [{guid,filename,mimeType,size,hash,hasRecognition}], still off
  the default surface (EVERNOTE_LEGACY_TOOLS re-lists it).
- update_note rejects replacements combined with forceUpdate (patch mode never
  reaches the edit-lock fallback) instead of silently ignoring the request.

Verified live; MIGRATION.md updated; tests added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jack-arturo

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d6ed445225

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/index.ts
Comment thread src/tool-aliases.ts
…get_tag guard

- server.json (MCP registry manifest) now advertises the 15 canonical tools
  (dropped retired get_user_info; added connection/polling/get_resource/
  add_resource_to_note/update_notebook/update_tag) so registry clients discover
  the consolidated surface.
- get_notebook/get_tag aliases carry requireOneOf:["name","guid"]; the
  dispatcher rejects an empty legacy lookup (preserving the old "requires one
  of" error) while list_notebooks/list_tags still accept {} as list-all.

Verified live; tests added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jack-arturo

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cb6b456475

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tool-aliases.ts
Comment thread src/index.ts
…ookups

- patch_note alias carries requireOneOf:["replacements"] so a legacy call
  without replacements errors instead of falling through to a no-op full update.
- list_notebooks/list_tags require non-empty name/guid when present, so an
  empty-string lookup errors instead of silently listing all (list-all stays {}).

Verified live; tests added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jack-arturo

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: 43da89ea26

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@jack-arturo jack-arturo merged commit d5a04fa into main Jul 7, 2026
14 checks passed
@jack-arturo jack-arturo deleted the feat/tool-surface-consolidation branch July 7, 2026 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant