visual-asset-management-system / VamsMCP
CLAUDE.md146 starsChanged 41 days agoApache-2.0View on GitHub
Steering document for Claude Code / Kiro when working in the VAMS MCP server. Auto-loaded when the working context is within tools/VamsMCP/. VamsMCP is a Model Context Protocol server that exposes the VAMS REST API as agent-callable tools. It is built with the mcp SDK (FastMCP) and reuses the vamscli package for API access and authentication. - Entry point: vams_mcp/server.py (main() -> mcp.run(), stdio transport) - Stores no credentials: authenticates via the user's existing vamscli profile (vamscli setup + vamscli…
- Reads credentials
- Installs packages
In this repository12 other files it gives its agents.
- backend/CLAUDE.mdCLAUDE.md
- backendPipelines/CLAUDE.mdCLAUDE.md
- backend/tests/CLAUDE.mdCLAUDE.md
- CLAUDE.mdCLAUDE.md
- documentation/CLAUDE.mdCLAUDE.md
- infra/CLAUDE.mdCLAUDE.md
- infra/lib/nestedStacks/pipelines/CLAUDE.mdCLAUDE.md
- tools/VamsCLI/CLAUDE.mdCLAUDE.md
- web/CLAUDE.mdCLAUDE.md
- web/e2e/CLAUDE.mdCLAUDE.md
- web/src/visualizerPlugin/CLAUDE.mdCLAUDE.md
- vams-agentSkill
The file
We split this CLAUDE.md into the rules it gives an agent
12 rules. About 0 are common, 1 is distinctive, and 11 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
11
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 100
No credential storage: Do not add API-key or password env vars that get persisted.
- Very specific: line 103
Reuse
APIClientmethods: Prefer existing vamscli APIClient methods over hand-rolled requests so retries/backoff/typed-errors are … - Very specific: line 174
Every APIClient method in that domain returns the handler's raw {"message": ...} body, because _pwe_body() leaves the envelope intact and …
- Very specific: line 200
Use _paginate_with_page_metadata() instead of CLIENT.paginate() for any list endpoint that can report a warnings array or echo its applied …
- Very specific: line 252
Add an @mcp.tool() + @tool_result function in server.py.
- Very specific: line 254
Place it in the correct section: read (top), write (enable_writes), or destructive (enable_destructive).
- Very specific: line 260
Check the request payload against the backend Pydantic model in backend/backend/models/ — required fields, minimum lengths, and exact key …
- Very specific: line 265
Add the tool to the README's tool list, and — if it is a safe read — to the autoApprove array of the sample MCP host config in that same …
- Very specific: line 270
Add a unit test in tests/ mocking CLIENT.
- Very specific: line 273
When the server's contract with the CLI changes, roll the version in both pyproject.toml and vams_mcp/__init__.py, alongside …
- Very specific: line 314
Always work in this server's own virtual environment.
- Very specific: line 337
Do not mark the source-layout guards in tests/test_server_tools.py.
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CLAUDE.md - VamsMCP (tools/VamsMCP/)
Steering document for Claude Code / Kiro when working in the VAMS MCP server. Auto-loaded when the working context is within
tools/VamsMCP/.
Project Overview
VamsMCP is a Model Context Protocol server
that exposes the VAMS REST API as agent-callable tools. It is built with the
mcp SDK (FastMCP) and reuses the vamscli package for API access and
authentication.
- Entry point:
vams_mcp/server.py(main()->mcp.run(), stdio transport) - Stores no credentials: authenticates via the user's existing
vamscliprofile (vamscli setup+vamscli auth login). - Reused dependency:
vamscli.utils.api_client.APIClientandvamscli.utils.profile.ProfileManager.
Architecture
Directory Structure
Maintenance note: Update this tree when adding modules or tool groups.
tools/VamsMCP/
pyproject.toml # package + deps (mcp, vamscli, requests)
.env.example # reference list of every supported env var (NOT a dotenv template -
# nothing loads a .env; set them in the MCP host's `env` block)
vams_mcp/
__init__.py # __version__ (roll with pyproject.toml)
config.py # env-based config (profile, feature gates, pagination)
client.py # VamsClient: wraps vamscli APIClient + ProfileManager
server.py # FastMCP app, response-shape helpers, tool definitions, logging guard
tests/
conftest.py # clears VAMS_ENABLE_* / VAMS_PROFILE / pagination vars at import,
# BEFORE vams_mcp.server reads them at module level
test_config.py # env parsing + the writes/destructive gate interaction
test_client_helpers.py # paginate(), unwrap_message(), trim_search_results()
test_server_tools.py # tool behavior + the source-layout assertions of Mandatory Rule 4
test_gated_tools.py # the write/destructive tool bodies, via a module reload with both gates on
test_comment_subscription_tools.py
# the comment / subscription / metadata-schema / API-key tools: the three
# response shapes they span, and their docstring contractsNo dotenv. config.py reads os.environ only, and there is no python-dotenv dependency. That
is the right mechanism for this transport — a host spawns the vams-mcp console script from an
arbitrary working directory, so a cwd-upward .env search would not reliably find this one — but it
means .env.example is a reference list, not a template to copy. Never write documentation that
tells a reader to copy it: a .env beside the server is silently ignored, the gates stay off with
nothing reporting why, and the usual next step is to delete the if CONFIG.enable_writes: guard in
source. Document a new variable in .env.example and in the README's env sample.
Layers
config.py— reads env vars into a frozenConfig. No secrets/URLs.client.py—VamsClientresolves the vamscli profile, discovers the API Gateway URL, verifies auth, and builds anAPIClient. Addspaginate(),get_json()/post_json(), andtrim_search_results().server.py— defines@mcp.tool()functions that callCLIENT. Reads are always registered; writes/destructive tools are registered conditionally.
client.py helpers worth knowing: paginate() (follows NextToken, normalizes
any list field onto Items, flags truncated), unwrap_message() (strips the
legacy message envelope), and trim_search_results() (compacts an OpenSearch
response to total / returned / results).
server.py adds two response-shape helpers of its own, deliberately kept out of
VamsClient because they describe how the tool surface presents a response
rather than the client's contract with the CLI (whose message-envelope
asymmetry other callers rely on):
_unwrap_message_with_warnings(page)— unwraps the envelope while keeping a sibling top-levelwarningsarray. Used bycreate_pipeline/update_pipeline(andunarchive_pipeline, which routes through the update), whose response model has no warnings field, so that array is the only copy._paginate_with_page_metadata(fetch_page, passthrough_keys=..., ...)— paginates while collecting each page'swarnings(deduplicated, in order) and named echo fields onto the result, marking ittruncatedwhen any page withheld rows.list_executionsandlist_workflow_executionsboth use it to carrywarningsplus the appliedfilterStartDate/filterEndDatewindow._bounded_message_list(page, max_items, page_size, noun)— lifts a bare array nested undermessageontoItems, and flags the bound the route applied. Used by the two asset-scoped comment listings (Mandatory Rule 7).
Mandatory Rules
- stdout is sacred. MCP stdio transport uses stdout for JSON-RPC. Never
print()to stdout and never let logging reach stdout.server.pycalls_force_logging_to_stderr()at startup — keep it, and route any new logs to stderr. - No credential storage. Do not add API-key or password env vars that get persisted. Auth must come from the vamscli profile. This server is distributed publicly for bring-your-own-account use.
- Reuse
APIClientmethods. Prefer existingvamscliAPIClientmethods over hand-rolled requests so retries/backoff/typed-errors are inherited. UseVamsClient.get_json/post_jsononly for endpoints without a dedicated method. Gate mutations. New create/update tools go inside the
if CONFIG.enable_writes:block. Destructive tools (delete/archive) go insideif CONFIG.enable_destructive:and must never be inautoApprove.enable_destructiveis AND-ed withenable_writesinConfig.from_env(), soVAMS_ENABLE_DESTRUCTIVE=truealone registers nothing.execute_workflow,rerun_executionandabort_executionare write-tier, not destructive — they remove no stored data — but all three act on real AWS compute (the first two start it,abort_executionirreversibly stops it and fans out across a group), so keep all three out ofautoApprove. The tier is about stored data; that list is about compute. Three places state this and must agree: this rule, the README's caution paragraph, and the tool's own docstring. The same three-place rule holds for the tools that RETURN a credential:create_api_keyandcreate_user_api_key(write tier) hand back the one-time API key value, a bearer token with the acting user's permissions, so both stay out ofautoApproveand their docstrings say where the value ends up.update_*_api_keywithis_active=Falseis the reversible revoke and is what the delete tools point at first. An API-key tool takesis_activeas a bool and sends the model's"true"/"false"string — the request model rejects a raw boolean.A required-true request field is not a confirmation to re-expose.
delete_assetsendsconfirmPermanentDelete=TruebecauseDeleteAssetRequestModeldeclares analways=Truevalidator that rejects any other value — it is part of the request contract, not an optional interlock, so surfacing it as a tool parameter would give the agent a boolean whose only non-erroring value isTrue. What actually controls these tools is the destructive gate (whichConfig.from_env()additionally requiresenable_writesfor), the tool name, the docstring, and keeping them out ofautoApprove. Because the tools are module-leveldefs, misplacement fails silently: a duplicate name shadows the earlier definition, and adefafter theif __name__entrypoint or outside its gate block never executes, so the tool is simply absent with no import error.tests/test_server_tools.pyasserts the source layout (uniqueness, position relative to each gate and the entrypoint, and that everyCLIENT.api.*call resolves onAPIClient) — keep it passing.- Return data, not exceptions. Wrap tool bodies with
@tool_resultso failures return{"error": ..., "error_type": ...}. - API Gateway URL only. The server rejects CloudFront URLs implicitly (the profile stores the API Gateway URL). Never point at the CloudFront web URL.
Match the endpoint's list field.
paginate()normalizes results ontoItems, but it reads the source list fromitems_key— which differs per endpoint (Itemsfor databases/assets/workflows,itemsforlistFiles,versionsforgetVersions,metadatafor the metadata APIs). It also unwraps the legacymessageenvelope used by tags, tag types, workflows, and workflow executions. Verify both against the handler's response model before adding a list tool; a mismatch silently returns zero items.A
messageenvelope can hold a BARE ARRAY, and then neither helper finds the rows.unwrap_message()returns the page untouched whenmessageis not a dict, andpaginate()readsitems_keyoff whatever it gets — so for{"message": [row, ...]}both hand back zero rows on a successful call, which reads as an empty thread rather than a wiring defect. The asset-scoped comment listings are that shape; use_bounded_message_list(). Three variants now exist across the API, so check which one a route returns before choosing a helper: a bare array undermessage, anItems/NextTokenpage undermessage(subscriptions), and no envelope at all (metadata schemas, API keys).A route can accept the pagination parameters and discard the token. The comment listings apply
maxItems/pageSizeand return noNextToken— the service returnsresponse["Items"]and drops it — so rows past the bound are unreachable through the API. Do not paper over it with a client-side walk, and do not offer astarting_tokenthat could never be filled. Report the bound instead: it is knowable without a token, because the handler takesmaxItemsas given, falls back topageSize, and otherwise appliesCOMMENT_LIST_DEFAULT_BOUND, so a result that reached the effective bound can be flagged even when the caller narrowed nothing.Unwrap the envelope on non-paginated pipeline/workflow/execution tools. Every
APIClientmethod in that domain returns the handler's raw{"message": ...}body, because_pwe_body()leaves the envelope intact and lets callers decide. The asset and database methods return unwrapped data, so a tool that forwards the envelope hands agents a nesting level no other tool has. Wrap single-object and write calls inCLIENT.unwrap_message(...);paginate()already does it.Repeat a filter-pinned endpoint's filters on every page. Some continuation tokens are only valid alongside the filters they were issued with — the paged execution-detail metadata read (
page_execution_detail_metadata) pins its token to thecollectionandpipelineIdof the request that produced it, and the handler answers a mismatch with a 400.paginate()passes onlypageSizeandstartingToken, so merge the filters into the params inside thefetch_pagecallable rather than sending them on the first request alone.Never let a bounded response read as a complete one. A VAMS handler can answer successfully while withholding rows, and it reports that out of band: a top-level
warningsarray (a page that hit its distinct-asset permission-check cap, or spent its per-request work budget before filling the page — the array can carry more than one entry, so collect them all rather than the first), atruncatedCollectionslist (a bounded execution-detail collection), or an echoed filter window (filterStartDateon the executions list). Becausepaginate()rebuilds its result from the accumulated items alone, every one of those is dropped by default, and the agent reports an understated count or concludes an object does not exist.Use
_paginate_with_page_metadata()instead ofCLIENT.paginate()for any list endpoint that can report awarningsarray or echo its applied filters, and_unwrap_message_with_warnings()for any single-call endpoint that returnswarningsas a sibling ofmessage. Then say in the docstring what the flag means for the agent's conclusion, not just that the field exists — a tool description is the only place an agent learns not to trust a short list.paginate()'s own bound is one of those, and every paginated tool docstring must state it. The walk stops atmax_itemsor atmax_pages, whichever comes first, and setstruncatedplus anotenaming which fired — including the case where a single page returned more rows thanmax_itemsand carried no token at all, which a token-only check misses. It returns the outstandingNextToken, and every paginated read tool takes astarting_tokenthat forwards it as the first page'sstartingToken; without that pair the ceiling is a wall and rows past it are unreachable through this server.max_pagesis a per-call work bound on the server process, so a largermax_itemsdeliberately does NOT raise it — the resumption token is the way past it.Forward every narrowing parameter the endpoint supports. A tool that omits one silently pins the agent to the server default:
get_execution_logswithoutlimit/next_tokencaps a container's output at 100 events with no way past the first page, andlist_workflowswithoutinclude_archivedmakes an archived workflow's id — the argumentunarchive_workflowrequires — undiscoverable. Read the matchingvamsclicommand intools/VamsCLI/vamscli/commands/for the full parameter set the endpoint accepts, and forward a parameter only in the mode that acts on it (the log paging parameters are sent infullmode only, since truncated mode returns one joined blob and no continuation token).The converse is equally a defect: never expose a parameter the endpoint does not act on. A knob that silently does nothing is worse than its absence, because the agent draws a conclusion from having set it. Two shapes to refuse: a parameter the handler forwards and the service ignores (
showDeletedon the asset-scoped comment routes), and one whose only valid value can never be obtained (starting_tokenon a route that returns no token). Say in the docstring that the capability is absent, so the agent stops looking for it — and pin the absence with a test, or the next reader adds it back as a fix.- Support both
mcpmajor versions.mcp1.x exposesFastMCPfrommcp.server.fastmcp;mcp2.x renamed it toMCPServerinmcp.server.mcpserverand removed the old module.server.pyimports it through atry/except ImportErroralias (McpServer) — keep that shim and themcp>=1.2.0,<3.0.0pin, and test against both before widening it.
Adding a New Tool
- Confirm the underlying operation exists on
vamscliAPIClient(tools/VamsCLI/vamscli/utils/api_client.py); prefer it. Read the method's real signature — several take required positional arguments the endpoint demands (for exampleexecute_workflowrequiresworkflow_database_id). - Add an
@mcp.tool()+@tool_resultfunction inserver.py. Use type hints and a clear docstring (both feed the tool's MCP schema/description). - Place it in the correct section: read (top), write (
enable_writes), or destructive (enable_destructive). - For list endpoints, use
CLIENT.paginate(...)with the correctitems_key(see Mandatory Rule 7), or_paginate_with_page_metadata(...)when the endpoint reportswarningsor echoes its applied filters (Rule 8). For search, useCLIENT.trim_search_results(...). - Check the request payload against the backend Pydantic model in
backend/backend/models/— required fields, minimum lengths, and exact key names (for examplecreateFoldertakesrelativeKeyand it must end in/). - Forward the endpoint's full narrowing/paging parameter set (Rule 9), and state in the docstring what a bound or a warning means for the agent's conclusion.
- Add the tool to the README's tool list, and — if it is a safe read — to the
autoApprovearray of the sample MCP host config in that same README. Any tool whose parameters or response fields change also needs its README paragraph updated: the tool list is the only place the parameter set is documented outside the docstring. - Add a unit test in
tests/mockingCLIENT. Assert the params that reach theAPIClient, not just that the call happened: a dropped optional parameter is a silent server-default, which no assertion on the return value catches. - When the server's contract with the CLI changes, roll the version in both
pyproject.tomlandvams_mcp/__init__.py, alongsidetools/VamsCLI/vamscli/version.py(rootCLAUDE.mdPattern 7 rule 6).
Upstream Dependency on the CLI
This server is downstream of tools/VamsCLI — it calls APIClient methods
directly rather than the REST API. Any change to a vamscli APIClient method
signature or response handling can break a tool here without an error at import
time. Root CLAUDE.md Pattern 7 defines the propagation chain; when a CLI change
reveals a missing or wrong APIClient method, fix it in the CLI instead of
hand-rolling raw requests here.
Keeping Steering Documents in Sync
VAMS maintains two parallel steering families: CLAUDE.md files for Claude Code
and .kiro/steering/ workflow documents for Kiro. Synchronization is
bidirectional (root CLAUDE.md Rule 11) — a rule authored in either family
must be carried into the other in the same change, or one agent scaffolds
outdated code.
This file's Kiro counterpart is the MCP propagation section of
.kiro/steering/CLI_DEVELOPMENT_WORKFLOW.md — the MCP server shares that
workflow document with the CLI because it is downstream of it. A change to a rule
in this file, or to the propagation chain itself, must land in:
CLAUDE.md(root) — Pattern 7, the canonical chaintools/VamsCLI/CLAUDE.md— the MCP step in "Adding a New Command"tools/VamsMCP/CLAUDE.md— this file.kiro/steering/CLI_DEVELOPMENT_WORKFLOW.md— the Step 9 MCP checklist
Also update documentation/docusaurus-site/docs/developer/agentic-development.md
when the user-facing description of the MCP server or the propagation chain
changes.
Testing
Always work in this server's own virtual environment. The mcp SDK requires
Pydantic v2, while the VAMS backend pins Pydantic 1.10.13 (root
CLAUDE.md Rule 1) — installing this server into a shared interpreter upgrades
Pydantic and breaks the whole backend suite at collection time, with the failure
surfacing in backend/ far from its cause. vamscli is not published to PyPI, so
install it from the local path first or the install fails resolving it.
python -m venv .venv
source .venv/bin/activate # Windows: .venv/Scripts/activate
pip install -e ../VamsCLI # must come first
pip install -e '.[dev]'
pytestTests mock vams_mcp.server.CLIENT so no live VAMS deployment is required.
Mark a Temporary Test with @pytest.mark.temporary
A test written to prove one specific change landed — a removed tool, a renamed call site — carries
@pytest.mark.temporary (registered in pyproject.toml) plus a line saying what it pins, so release
cleanup can find it with pytest -m temporary --collect-only.
Do not mark the source-layout guards in tests/test_server_tools.py. They are durable: a duplicate
def silently shadows an earlier tool, a def placed outside its gate block never executes, and a read
tool calling a mutating APIClient method is a permission defect — each stays writable, so each guard
can still fire. Full criterion: root CLAUDE.md Rule 13.
Gold Standard References
- Reused client:
tools/VamsCLI/vamscli/utils/api_client.py - Profile/auth:
tools/VamsCLI/vamscli/utils/profile.py - Tool patterns:
vams_mcp/server.py
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

