Skip to content

feat(mcp-server): tags & tax-category lookup tools (MCP Prompt 14)#4015

Open
gilgardosh wants to merge 2 commits into
claude/mcp-prompt-13-charges-toolfrom
claude/mcp-prompt-14-lookup-tools
Open

feat(mcp-server): tags & tax-category lookup tools (MCP Prompt 14)#4015
gilgardosh wants to merge 2 commits into
claude/mcp-prompt-13-charges-toolfrom
claude/mcp-prompt-14-lookup-tools

Conversation

@gilgardosh

Copy link
Copy Markdown
Collaborator

Summary

Implements Prompt 14 – Tool 2: tags and tax-category lookup (read-only) (see
docs/mcp/spec.md §8.2). Two reference-data
lookup tools.

Stacked PR: targets claude/mcp-prompt-13-charges-tool (Prompt 13, #4014).
Review/merge Prompts 09→13 first; this diff shows only the Prompt 14 changes.

Changes

  • src/tools/lookups.ts
    • accounter_list_tags (allTags) → { id, name, namePath }.
    • accounter_list_tax_categories (taxCategories) → { id, name, irsCode, isActive }, with an activeOnly filter.
    • Minimal input (nameContains, limit); output is deterministically sorted (name case-insensitive, then id) and size-capped at 500 with a truncated flag. Response fields are limited to the lookup use case.
  • src/tools/registry-instance.ts — both tools registered.
  • Tests — sorted lookups, name filter, active-only filter, cap/truncation, business-scope enforcement, and unknown-field rejection. 160 tests total.

Validation

  • yarn workspace @accounter/mcp-server test → 160 passed
  • yarn workspace @accounter/mcp-server lint / typecheck / build → pass

Notes / open decisions

  • Scope gate on reference data. allTags / taxCategories are org-wide (not business-filtered upstream), but I set requiresBusinessScope: true so a caller with no business membership is denied — that's the "scope enforcement" the prompt asks tests to cover, and it's a sensible gate (you browse tags because you belong to a business). The scope itself isn't used to filter the query. Easy to relax to false if reference lookups should be open to any authenticated user.
  • Client-side filter/sort/cap. allTags/taxCategories take no arguments upstream, so nameContains/limit/sort are applied in the tool. Fine at reference-data sizes; if these grow large, a server-side filtered query would be better.

🤖 Generated with Claude Code


Generated by Claude Code

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces two new read-only lookup tools, accounter_list_tags and accounter_list_tax_categories, along with their corresponding unit tests. The feedback focuses on improving type safety in the test suite, utilizing localeCompare for more robust sorting, simplifying redundant object mapping, and polishing user-facing pluralization strings.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread packages/mcp-server/src/tools/__tests__/lookups.test.ts Outdated
Comment thread packages/mcp-server/src/tools/__tests__/lookups.test.ts Outdated
Comment on lines +35 to +41
function byNameThenId(a: { name: string; id: string }, b: { name: string; id: string }): number {
const an = a.name.toLowerCase();
const bn = b.name.toLowerCase();
if (an < bn) return -1;
if (an > bn) return 1;
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The byNameThenId comparison logic can be simplified and made more robust, especially for international characters, by using String.prototype.localeCompare.

function byNameThenId(a: { name: string; id: string }, b: { name: string; id: string }): number {
  return (
    a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }) || a.id.localeCompare(b.id)
  );
}

Comment thread packages/mcp-server/src/tools/lookups.ts Outdated
Comment thread packages/mcp-server/src/tools/lookups.ts Outdated
Comment thread packages/mcp-server/src/tools/lookups.ts Outdated
@gilgardosh
gilgardosh force-pushed the claude/mcp-prompt-13-charges-tool branch from b165a5a to 0e6e0f0 Compare July 21, 2026 16:16
claude added 2 commits July 21, 2026 16:16
Add two read-only reference lookups (docs/mcp/spec.md §8.2).

- src/tools/lookups.ts: accounter_list_tags (allTags) and
  accounter_list_tax_categories (taxCategories) with minimal input
  (nameContains, limit, activeOnly), deterministic sort (name then id), and a
  500-row output cap with a `truncated` flag; response fields limited to the
  lookup use case
- both gated on business membership (scope enforcement) and registered in the
  process-wide registry
- integration tests: sorted lookups, name filter, active filter, cap/truncation,
  scope enforcement, and unknown-field rejection

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo
Address review:
- byNameThenId uses localeCompare (locale-aware, robust for i18n names)
- tax-category handler returns the already-shaped rows directly (drop redundant
  identity map)
- natural-language pluralization for tag/tax-category summaries
- lookup tests: union type for the runTool helper; assert the full rows array

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPMszz9gdZFhNjobRm6Ndo
@gilgardosh
gilgardosh force-pushed the claude/mcp-prompt-14-lookup-tools branch from 0929ded to 57e3d25 Compare July 21, 2026 16:18
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 21, 2026 16:18 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack July 21, 2026 16:18 — with GitHub Actions Inactive
@gilgardosh gilgardosh self-assigned this Jul 22, 2026
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.

2 participants