npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aiphotostudio/mcp

v0.6.5

Published

MCP server for Self Care (Photo Studio): products, free previews, material tuning, shader node graphs, model, texture and background uploads, paid silo shots (Blender only with no AI at all, or with the AI pass and four AI variants), product, room and roo

Readme

@aiphotostudio/mcp

An MCP server over Self Care's user API (/api/public/v1). It gives an agent the Photo Studio tools — products, free previews, material tuning, model and texture uploads, model collections, lifestyle backgrounds, paid renders (product, drawn room, room photo), scene AI, albums — and, as its standing instructions, a short map of this server's silo and free-check tools followed by the briefing the API itself serves. Every public route is a tool unless it is browser-only (see Routes without a tool).

Silo shots: submit_silo_render (the Blender render only — no AI at all: it always sends aiPass: false) or submit_silo_render_with_ai (the render with the platform's automatic AI pass + 4 AI variants from your prompt and optional reference image). Both spend credits: the render at the same silo3d price, and with AI also one sceneAiCandidate per variant, taken when each variant is styled after the render is done (prices in get_credits). For the AI render, get_render_job gives the AI-enhanced image and get_render_original the raw Blender one; for the Blender-only render the two are the same image. A free look at the Blender render is exposure_preview (16 samples, 1K). Where the render platform cannot render without AI yet, submit_silo_render is refused with 503 ai_pass_opt_out_unavailable and nothing is charged (needs the Self Care API with aiPass; MCP 0.6.1).

Shader node graphs: set_material_node_graph takes inline node groups (groups, each used by a ShaderNodeGroup and wired by interface socket name), frames (NodeFrame, and parent on any node), the newer node types (Combine / Separate XYZ, Tangent, Vector Rotate, Vector Transform, Float Curve, legacy MixRGB) and Attribute nodes reading the four values the renderer provides per board — a connected piece of a part's mesh (board_coord, board_seed, board_variation_a, board_variation_b), a Tangent uv_map of board_fiber_uv (the per-board UV map), attributeBindings that map a shader's own attribute names onto those (with scale and offset) and a display label on any node. Limits: 384 nodes and 512 links across the graph and each inline group definition, 64 inputs / 16 outputs per group, 32 groups, nesting depth 3, a 1 MiB body (413 node_graph_too_large). A refusal's issues name the group they sit in; read_guide has every setting and issue code (MCP 0.6.4).

Node templates: instead of building a whole shader, pin a vetted one. Every ref names its scope and an exact version: public:[email protected] (written by the integration's admins, usable by every brand) or brand:[email protected] (the material's own brand). Find one with list_node_templates. Read it with get_public_node_template / get_brand_node_template: inputs with defaults and recommended ranges, presets such as oak (presetValues), the read-only definition, and exampleGraph / presetGraphs ready for set_material_node_graph. Then render it with your inputs for free: preview_public_node_template → get_public_node_template_preview, on a sphere (budgeted per credential: 429 preview_budget_exhausted). Nothing upgrades a pinned ref, and a template's nodes do not count against the graph's limits. A brand's users can also write its templates (create_brand_node_template, fork_node_template_into_brand, …). That is free but brand-wide: every colleague sees the result, so tell the user first (MCP 0.6.5).

Moving materials between template versions: the brand bulk tools (check_brand_node_template_compatibility, create_brand_node_template_from_material, adopt_brand_node_template, upgrade_brand_node_template_materials, get_brand_node_template_operation) only ever touch the brand's own materials. They are free but brand-wide, so tell the user first. With templateScope: "public" they use the public template instead, which is how a brand moves its materials from public:[email protected] to public:[email protected]. Adopt and upgrade send dryRun: true unless you pass dryRun: false. Show the plan and run it only after the user says yes. An operation is done when complete is true, and a failed material keeps its previous render.

Before any render that spends credits, check the look for free, in this order — the server's standing instructions carry the same list, and every paid tool's description points at it right after its price:

  1. Framing: framing_preview (seconds; keep arrangementApplied), saved and reused with create_frame / list_frames.
  2. Product and configuration: get_product_capabilities (the choices and their materialIds), preview_product_configuration (a 256 px still of one configuration; a 422 lists the bad selections).
  3. Materials: render_material_preview → get_material_preview (the material on a cloth ball), exposure_preview → get_exposure_preview (a 16-sample 1K Blender render of the actual shot, whole frame or a region), compare_material_to_reference / list_reference_comparisons (measured against a reference photo or the configurator image); fix it with update_material (reversible via get_material_history) or the node-graph tools (set_material_node_graph → get_node_graph_build, verify_node_graph_build), or a vetted node template (list_node_templates, get_public_node_template, preview_public_node_template → get_public_node_template_preview).
  4. Then ask the user to confirm, and only then call the paid tool.

Published on npm as @aiphotostudio/mcp. It needs Node 18 or newer and nothing else — no install step, no dependencies:

npx -y --prefer-online @aiphotostudio/mcp@latest

Keep --prefer-online: without it npx reuses the copy it cached the first time, so after a new release it would silently keep starting the old version. The version the server reports in its MCP handshake tells you which one is running.

Run it

{
  "mcpServers": {
    "self-care": {
      "command": "npx",
      "args": ["-y", "--prefer-online", "@aiphotostudio/mcp@latest"],
      "env": { "SELFCARE_API_BASE_URL": "https://api.ai-photo.studio/api/public/v1" }
    }
  }
}

There is no token in that configuration. The server looks for one in this order:

| | Where | | |---|---|---| | 1 | SELFCARE_TOKEN | the value itself | | 2 | SELFCARE_TOKEN_FILE | a path to a file holding it | | 3 | ~/.config/selfcare/token | the default, and what a pairing link writes |

The user gets a pairing link from Photo Studio → Agent access and pastes it into the agent; the page it serves carries a one-line command that writes the token into that file without the agent ever reading it. See bff/app/agent_pairing.py for the reasoning.

Why npx and not uvx

The criterion is what the user's MCP client already has on the machine, not what this repository is written in.

  • Every MCP client we target documents npx as the way to run a server, and their own example configurations use it. uvx needs uv installed first — which is precisely the "go and install this thing" step a non-technical user is supposed to be spared.
  • Node 18+ ships fetch, so this package has no dependencies and needs no build: npx fetches it and runs it. A Python package doing the same would still need uv on the box.
  • The repository already builds Node (the frontend), so a JS package here is not a new toolchain for us either.

The cost, stated plainly: this package is outside ./run-lint's Python gates. Its invariants are held by mcp/test/ (node --test) and by bff/tests/test_mcp_server_catalogue.py, which reads these sources from the pytest suite so a tool that stops declaring its price fails the BFF gate.

What it does not do

It adds no rules. Every tool is a method and a path; budgets, brand scope, credit gates, validation and refusals live in the API and are returned verbatim. The one thing decided here is that every tool description opens with FREE — or COSTS CREDITS —, because that is the sentence a model reads when it picks a tool; a paid one follows it with its price sentence and the pointer to the free checks.

Two tools take named arguments instead of a pass-through body, because one route does two jobs an agent must not mix up (with or without the four AI variants): submit_silo_render and submit_silo_render_with_ai both call POST /render-jobs with type: "silo". Each declares the body fields it may send; the pure one's list has no aiVariants, so it can never send one, and the AI one always does. A body outside a tool's declaration is refused before it leaves (the BFF test suite checks each list against the route's own model).

Uploading files

Nine tools send files, and submit_silo_render_with_ai sends one inline. The server runs on the agent's machine, so each file part is the path of a local file — absolute, or starting with ~/ — named <part>Path (filePath, sceneGlbPath, photoPath, …), read there and uploaded as multipart/form-data.

| Tool | Part | Accepts | Up to | |---|---|---|---| | upload_asset | file | .glb; .fbx; .zip or a folder with the model (FBX, OBJ, glTF) and its textures | 100 MB; 250 MB; 250 MB packed, 250 MB unpacked | | upload_material_texture | file | .png, .jpg, .jpeg | 64 MB | | compare_material_to_reference | reference (optional) | .png, .jpg, .jpeg, .webp | 25 MB | | upload_background | background, foreground (optional) | .png, .jpg, .jpeg | 100 MB | | upload_background | sceneGlb | .glb | 100 MB | | submit_room_render (paid) | sceneGlb | .glb (the room surfaces) | 120 MB | | submit_room_photo_render (paid) | sceneGlb | .glb (the composite scene) | 120 MB | | scene_ai_enhance (paid) | screenshot, referenceImage (optional) | .png, .jpg, .jpeg, .webp | 100 MB | | scene_ai_decompose | composedImage | .png, .jpg, .jpeg, .webp | 100 MB | | upload_room_photo (paid) | photo | .jpg, .jpeg, .png, .webp | 100 MB | | submit_silo_render_with_ai (paid) | aiReferenceImagePath (optional), sent as aiVariants.referenceImage | .png, .jpg, .jpeg, .webp | 12 MB |

The style reference of submit_silo_render_with_ai goes in the JSON body as a base64 data: URL. Its type is the one its bytes say (PNG, JPEG or WebP magic numbers), as the API decides it — a .png that is really a JPEG goes as image/jpeg, and a file that is none of the three is refused here.

A form field the route requires (name on upload_background, cameraConfig and payload on submit_room_render, prompt on scene_ai_enhance, width and height on submit_room_photo_render) is checked before any file is read. A field that carries JSON (cameraConfig, payload, aiVariants, scene, a crop) is given as an object and sent as its JSON text.

A folder is packed into a ZIP by this server (Node's own zlib, no archiver needed): exactly one model file (.fbx, .obj, .gltf, .glb) and the files a model refers to (.mtl, .bin, images) at their relative paths. Hidden files, other extensions and symlinks that lead out of the folder are left out; a folder with no model file or with several is refused.

The path is checked before a byte is read: an extension the route does not take (on the path or on what a symlink points at), a relative path, anything that is not a regular file or a model folder, or a file over the route's cap is refused as a tool error and nothing is sent. The limits are the API's own, and so are the content types sent where a route checks them (the BFF test suite fails when either drifts). What the file is is still decided by the API from its bytes, and its refusals — invalid_glb, unsupported_file_type, model_archive_*, invalid_texture, invalid_file, unsupported_content_type, file_too_large — reach the agent word for word.

Upload the model as it is — no need to convert it to GLB yourself. A GLB is usable on arrival. An FBX, OBJ or ZIP is converted: upload_asset answers processing, and an FBX/OBJ reports awaiting_review when the conversion is done; it can be previewed for free (framing_preview, exposure_preview) but renders only after approve_asset — check its measuredDimensionsM against the real product first and correct it with adjust_asset (approval is final). Textures the upload did not carry do not fail it: the model is needs_fix, its issues name the material, the missing files and the parts, and set_part_materials with an active library material on every listed part makes it ready. A failed conversion says why in statusReason.

A lifestyle render (exposure_preview, submit_render) takes its lifestyle.backgroundId from list_backgrounds or list_my_backgrounds; any other id is 404 background_not_found, and a background whose scene files are gone is 409 scene_files_missing. A scene render that fails names its cause in error.code: scene_files_missing, product_model_missing (detail items[i]), product_out_of_frame or render_black_frame.

Admin tools

With an admin token configured, the server also offers 15 tools over Self Care's admin API (/api/admin/v1). They manage the public node templates, the ones every brand can pin. Without an admin token they are not offered at all. Each description opens with ADMIN:, because each tool changes what every brand of the integration can pick. None of them spends credits. The lifecycle, the naming and versioning rules, seeding WOOD, and the dry-run-first procedure for moving materials are in docs/ADMIN-AGENT.md in this repository.

| Variable | | |---|---| | SELFCARE_ADMIN_TOKEN_FILE | a path to a file holding the token (preferred: the value stays out of the client's configuration) | | SELFCARE_ADMIN_TOKEN | the token itself | | SELFCARE_ADMIN_API_BASE_URL | the admin API base; default: SELFCARE_API_BASE_URL with its /api/public/v1 replaced by /api/admin/v1 |

The token must be an admin personal access token (scadm_…), minted in Self Care Admin → Access tokens. If a user token is put there, the server refuses to start rather than send it to the admin API. The two credentials are never mixed: the admin token goes only to the admin base URL, and the user token never goes there. Neither is ever logged.

| Tool | What it does | |---|---| | list_public_node_template_versions | every public version, every status; filter by status, name | | get_public_node_template_version | one version: definition, docs, presetValues, manifest, preview, lineage. Poll it after a draft preview | | create_public_node_template | a DRAFT from a JSON definition: inline definition, or definitionPath, a local .json file of up to 1 MiB | | update_public_node_template | replace a DRAFT (definition or definitionPath again); resets its preview | | update_public_node_template_docs | description, category, tags, inputDocs, presets, at any status | | preview_public_node_template_draft | dry build + verify of the version's exampleGraph on the sphere (202), then poll | | publish_public_node_template | publish a draft whose preview is passed and current; moves no material | | deprecate_public_node_template | graphs that pin it keep rendering; note says what to use instead | | delete_public_node_template_draft | destructive: a draft only | | fork_node_template_into_public | copy a version into a new public DRAFT (from, name?, version; lineage.forkedFrom) |

Admin bulk tools: moving materials

| Tool | What it does | |---|---| | check_public_node_template_compatibility | a static check (no Blender) of every brand's materials on public:name@from against this version | | create_public_node_template_from_material | a DRAFT from a material's stored graph: expose (1..64 inputs, optional default), convertSource applied at publish; 422 invalid_expose lists error.problems | | adopt_public_node_template | move structurally identical materials onto a published template made from a material; 409 node_template_not_adoptable | | upgrade_public_node_template_materials | move materials between two public versions; 409 node_template_upgrade_breaking (error.breaking), 422 operation_too_large (over 200) | | get_public_node_template_operation | an upgrade / adopt / convert operation: poll until complete is true |

adopt_public_node_template and upgrade_public_node_template_materials send dryRun: true unless you pass dryRun: false explicitly; the API requires the field. The plan comes back (per material: action, and the skip reason) and nothing changes. Show the plan to the user, and run it for real only after they confirm. A run refuses what it cannot do rather than skipping it silently. An adopted material must render the same as before: a mean difference of at most 0.5/255 AND a 99th percentile of at most 4/255. Otherwise it fails with adopt_render_mismatch and its graph is restored. publish_public_node_template answers a publishReport (compatibility against the previous version, and convertSource).

Tools

148 tools. The admin tools come on top, only with an admin token. Every description opens with its price, and the paid ones also say so in their first sentence and name their row in get_credits. The HTTP route each one calls is at the end of its description.

Paid (spend the user's credits): submit_silo_render, submit_silo_render_with_ai (plus four sceneAiCandidate charges when the variants are styled), submit_render, submit_silo_batch, retry_render, submit_room_render, submit_room_photo_render, upload_room_photo, choose_room_removals (only when it removes something), scene_ai_enhance, request_render_variants and refine_render_variant (their charges land when each variant is styled). Every other tool is free.

| Area | Tools | |---|---| | Account | whoami, list_organizations, list_brands, get_features, get_credits, read_guide | | Products | list_products, get_product, get_product_capabilities, get_product_configuration, preview_product_configuration, get_configurator_image | | Materials | list_materials, get_material, create_material, create_material_from_choice, update_material, copy_material, delete_material (destructive), get_material_history, set_material_delegate, clear_material_delegate | | Material textures | get_material_textures, upload_material_texture, clear_material_texture (destructive), generate_material_maps | | Material look | render_material_preview, get_material_preview, compare_material_to_reference, list_reference_comparisons | | Shader node graphs | set_material_node_graph, get_material_node_graph, get_node_graph_build, verify_node_graph_build | | Node templates (read, preview) | list_node_templates, get_public_node_template_versions, get_public_node_template, preview_public_node_template, get_public_node_template_preview, get_brand_node_template_versions, get_brand_node_template, preview_brand_node_template, get_brand_node_template_preview | | Brand node templates (brand-wide writes, free) | list_brand_node_template_versions, create_brand_node_template, update_brand_node_template, update_brand_node_template_docs, preview_brand_node_template_draft, publish_brand_node_template, deprecate_brand_node_template, delete_brand_node_template_draft (destructive, drafts only), fork_node_template_into_brand | | Brand node templates, bulk (brand-wide, dry run first) | check_brand_node_template_compatibility, create_brand_node_template_from_material, adopt_brand_node_template, upgrade_brand_node_template_materials, get_brand_node_template_operation | | Uploaded models | upload_asset, list_assets, get_asset, adjust_asset, approve_asset, rename_asset, set_asset_sharing, delete_asset (destructive), list_asset_parts, get_asset_part_label_images, infer_part_labels, set_part_label, get_part_materials, set_part_materials, clear_part_material | | Model collections | list_asset_collections, create_asset_collection, get_asset_collection, rename_asset_collection, delete_asset_collection (destructive), add_assets_to_collection, remove_assets_from_collection | | Backgrounds | list_backgrounds, list_my_backgrounds, upload_background, get_background_scene, delete_background (destructive), get_render_samples_lifestyle | | Previews (free) | framing_preview, exposure_preview, get_exposure_preview, get_render_samples_silo | | Silo shots | submit_silo_render (paid, no AI variants), submit_silo_render_with_ai (paid, + 4 AI variants) | | Renders | submit_render (paid, lifestyle and raw pass-through), submit_silo_batch (paid, no AI variants), retry_render (paid), submit_room_render (paid), get_render_job, list_results, rename_render, delete_render (destructive, reversible), restore_render, get_render_original, get_render_input | | AI on a render | request_render_variants (paid), list_render_variants, refine_render_variant (paid), approve_render_variant, submit_enhancement, get_enhancement, cancel_enhancement | | Scene AI | scene_ai_enhance (paid), get_scene_ai_enhance, scene_ai_decompose, get_scene_ai_decompose | | Room photo | list_room_photos, list_room_scenes, upload_room_photo (paid), get_room_photo, choose_room_removals (paid), reopen_room_selection, step_room_cleaning, approve_room_cleanup, reclean_room, restore_previous_room, get_room_scene, save_room_scene, set_room_scene_sharing, submit_room_photo_render (paid) | | Albums | list_albums, create_album, get_album, update_album, delete_album (destructive, renders kept), add_to_album, remove_from_album, set_album_item_file_name, get_album_files | | Saved framings, prompts, rooms | list_frames, create_frame, update_frame, delete_frame (destructive), list_prompts, create_prompt, update_prompt, delete_prompt (destructive), list_rooms, create_room, rename_room, delete_room (destructive) |

Each tool takes the query parameters its route reads, no more and no fewer — and, where the route reads a form, exactly its form fields and file parts (the BFF test suite fails when they drift). The one to know about is brandId on list_materials: a token that carries several brands must name one (whoami lists them), or the API refuses with a 422 whose brandIds names them.

The server's serverInfo.version in the MCP handshake is read from this package's package.json, so it always names the release that is running.

Routes without a tool

A public route has no tool only when an agent could not use it through one. The BFF test suite holds this list (SKIPPED_ROUTES in bff/tests/test_mcp_server_catalogue.py) and fails on any public route that is neither a tool nor on it.

| Route | Why | |---|---| | GET /guide, /guide.md, /guide/summary | served by read_guide, the selfcare://guide resources and the standing instructions | | GET/POST /pair/{code} | the pairing that produces this server's token — it runs before there is one | | GET /me | the tenant's integration check; whoami and list_brands answer for the caller | | GET /products/{id}/viewer-bootstrap | identifiers for the browser's 360 configurator embed | | GET /assets/{id}/glb, GET /albums/{id}/download, GET /albums/{id}/items/{jobId}/download, GET /render-jobs/{id}/variants/{vid}/s/{token}/image | binary streams for the browser; the tools return the same files' URLs | | GET /reconstruction/room-photo/{id}/original-photo, …/segmentation, …/planes, …/foreground, …/depth, …/depth-meta, …/cleaned-photo | same-origin copies for the browser canvas; get_room_photo returns each file's signed URL | | POST/GET /sales-leads | the "Talk to sales" form — an enquiry to a person, which the user sends themselves |

Publishing

Published by the publish_mcp CI job, for maintainers:

  1. Bump version in package.json in a merge request, and merge it.
  2. Tag the merged commit mcp-vX.Y.Z, matching that version exactly. The job runs this package's tests, refuses a tag that disagrees with package.json, then runs npm publish --access public.

An npm version cannot be reused or, after 72 hours, unpublished — so the tag is the release decision, not a formality.