Picsha AI MCP Server
Overview
Picsha AI ships an official Model Context Protocol (MCP) server. This allows your custom AI agents, assistants, and LLM workflows to directly interface with your Picsha instance.
By connecting to the Picsha MCP Server, your AI agents can instantly search, retrieve, and process your organization's digital assets without you having to build custom API wrappers or prompt definitions.
Connection Details
Connect through the published @picsha-ai/mcp-server package. It runs locally over stdio transport (the model every MCP client supports natively — Claude Desktop, Cursor, custom agents), authenticates with your standard Picsha API key, and talks to the Picsha REST API under the hood.
PICSHA_API_KEY="<YOUR_PICSHA_API_KEY>" npx -y @picsha-ai/mcp-server
Don't have an API key? Generate one from your Picsha Admin Dashboard.
Hosted Remote MCP (Streamable HTTP)
Web and cloud agents that cannot spawn a local subprocess — hosted assistants, cloud-deployed agent frameworks, workflow platforms — can connect directly to Picsha's hosted MCP endpoint instead:
- Endpoint:
https://api.picsha.ai/v1/mcp(MCP Streamable HTTP transport) - Authentication:
Authorization: Bearer <YOUR_PICSHA_API_KEY>— the same API key as the REST API - Multi-tenancy: send an
x-external-user-id: <end-user-id>header to scope the session to a single end user (same semantics asPICSHA_EXTERNAL_USER_IDbelow) - Rate limits: requests are limited per API key (default 120 requests/minute)
The endpoint is stateless — every JSON-RPC message is an individual POST — so it needs no sticky sessions and works with any Streamable HTTP MCP client. For example, connecting Claude Code:
claude mcp add --transport http picsha-ai https://api.picsha.ai/v1/mcp --header "Authorization: Bearer <YOUR_PICSHA_API_KEY>"
Or a remote server entry in Cursor's .cursor/mcp.json:
{
"mcpServers": {
"picsha-ai": {
"url": "https://api.picsha.ai/v1/mcp",
"headers": {
"Authorization": "Bearer <YOUR_PICSHA_API_KEY>"
}
}
}
}
[!NOTE] The hosted endpoint exposes the same core toolset as the local package with two transport-appropriate substitutions — see Available Tools. Use the local
stdiopackage when your agent should upload files from the local filesystem or render image previews inline in a chat UI.
Multi-Tenancy & User Isolation (For B2B2C Apps)
If you are building an AI agent that serves multiple users (for example, a Slack bot or a customer-facing SaaS application), you must ensure the agent only accesses assets belonging to the specific user making the request.
Picsha AI natively supports strict multi-tenant isolation at the transport layer. You do not need to build complex mapping logic or modify your LLM prompts. Instead, you simply start the MCP Server session with the PICSHA_EXTERNAL_USER_ID environment variable.
PICSHA_API_KEY="..." PICSHA_EXTERNAL_USER_ID="user_123" npx @picsha-ai/mcp-server
(Under the hood, the MCP server forwards this value as the x-external-user-id header on every Picsha API request it makes. If you connect to the hosted Streamable HTTP endpoint instead of the local package, send the x-external-user-id header directly on your MCP requests — the isolation semantics are identical.)
When this value is present:
- Isolated Sandboxing: Every request carries the user identity, and the Picsha backend applies OpenSearch and Postgres filters so the AI agent only searches, views, or modifies files owned by that specific user.
- Invisible Enforcement: The LLM tools functionally operate the same — the scoping happens at the transport layer, not in your prompts.
If PICSHA_EXTERNAL_USER_ID is omitted, the session operates with the full organization scope of the API key. That is appropriate for internal tools and org-wide automations — but for customer-facing multi-user deployments, always set the external user ID so one user's agent can never touch another user's assets.
Integrating with AI Agents
Using the MCP TypeScript / Python SDKs
If you are building your own AI agent (for example, using LangChain, defining custom LLM tool chains, or a custom Node/Python application), you can connect to the Picsha MCP server using the official MCP client SDKs. The client spawns the server as a subprocess over stdio:
TypeScript Example:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
const transport = new StdioClientTransport({
command: "npx",
args: ["-y", "@picsha-ai/mcp-server"],
env: {
PICSHA_API_KEY: process.env.PICSHA_API_KEY
}
});
const mcpClient = new Client(
{ name: "my-ai-agent", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
await mcpClient.connect(transport);
// View available Picsha tools for your LLM
const tools = await mcpClient.request({ method: "tools/list" });
console.log(tools);
Agent Framework Recipes
Every major agent framework can now consume MCP servers natively, so there is no Picsha-specific package to install — you point the framework's MCP adapter at @picsha-ai/mcp-server and the full toolset appears as regular framework tools. The recipes below cover the most common Python frameworks; multi-tenancy works identically in all of them (add PICSHA_EXTERNAL_USER_ID to the env block).
LangChain / LangGraph
# pip install langchain-mcp-adapters langgraph "langchain[anthropic]"
import asyncio, os
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
async def main():
client = MultiServerMCPClient({
"picsha": {
"transport": "stdio",
"command": "npx",
"args": ["-y", "@picsha-ai/mcp-server"],
"env": {"PICSHA_API_KEY": os.environ["PICSHA_API_KEY"]},
}
})
tools = await client.get_tools()
agent = create_react_agent("anthropic:claude-sonnet-5", tools)
result = await agent.ainvoke({
"messages": "Find photos of people wearing red hats and tag the top result 'campaign-hats'"
})
print(result["messages"][-1].content)
asyncio.run(main())
Building in TypeScript? The equivalent adapter is @langchain/mcp-adapters with the same server config.
LlamaIndex
# pip install llama-index llama-index-tools-mcp llama-index-llms-anthropic
import os
from llama_index.tools.mcp import BasicMCPClient, McpToolSpec
from llama_index.core.agent.workflow import FunctionAgent
from llama_index.llms.anthropic import Anthropic
mcp_client = BasicMCPClient(
"npx",
args=["-y", "@picsha-ai/mcp-server"],
env={"PICSHA_API_KEY": os.environ["PICSHA_API_KEY"]},
)
tools = await McpToolSpec(client=mcp_client).to_tool_list_async()
agent = FunctionAgent(tools=tools, llm=Anthropic(model="claude-sonnet-5"))
response = await agent.run("Summarize the most recently uploaded document")
CrewAI
# pip install crewai "crewai-tools[mcp]"
import os
from crewai import Agent
from crewai_tools import MCPServerAdapter
from mcp import StdioServerParameters
server_params = StdioServerParameters(
command="npx",
args=["-y", "@picsha-ai/mcp-server"],
env={"PICSHA_API_KEY": os.environ["PICSHA_API_KEY"]},
)
with MCPServerAdapter(server_params) as picsha_tools:
media_specialist = Agent(
role="Media Specialist",
goal="Search, organize, and transform the team's digital assets",
backstory="Expert curator of the organization's Picsha AI asset library",
tools=picsha_tools,
)
# Add media_specialist to any Crew alongside your other agents
Claude Desktop Integration
Claude Desktop runs local MCP servers over stdio transport — exactly how @picsha-ai/mcp-server works, so no bridge or extra configuration is needed.
- Open Claude Desktop.
- Go to Settings > Developer and click Edit Config.
- Add the following entry to your
claude_desktop_config.jsonfile:
{
"mcpServers": {
"picsha-ai": {
"command": "npx",
"args": ["-y", "@picsha-ai/mcp-server"],
"env": {
"PICSHA_API_KEY": "<YOUR_PICSHA_API_KEY>"
}
}
}
}
Don't have an API key? Generate one from your Picsha Admin Dashboard.
- Save the file and restart Claude Desktop. You should now see the Picsha hammer icon 🔨 in your prompt bar, indicating the tools are successfully connected!
The Agentic Workflow (How to talk to Claude)
Now that Claude is connected, you can use natural language to search, generate, and organize your files.
Recommended Context Prompt To get the best results from Claude, start a new chat with the following context prompt. This ensures Claude knows exactly how to utilize the Picsha tools you've provided.
"You are my Picsha Media Assistant. You have direct access to my organization's Digital Asset Management library via MCP tools. When I ask for images, use
search_assetswithmode: \"ai\"to find them semantically. When I ask for alterations (like background removal or MIMI edits), userender_asset_previewto perform the transformation and show me the resulting image preview inline while also providing the finalcdnUrl. Keep my workspace organized by usingcreate_dam_groupandlink_assetsfor variations."
Example Prompts to Try
1. Semantic Search & Discovery
- You: "Find me photos of people wearing red hats enjoying the outdoors, then summarize what's happening in the top 3 results."
- What Claude Does: Calls
search_assets(query: "people wearing red hats outdoors", mode: "ai"), reads the rich AI descriptions attached to the results, and intelligently summarizes them.
2. On-the-fly Image Manipulation
- You: "Grab that sunset photo we just uploaded, remove the background, and crop it to a 16:9 aspect ratio."
- What Claude Does: First searches to find the sunset photo ID. Then calls
render_asset_preview(id: "550e8400-e29b-41d4-a716-446655440000", params: "bg_rem=true&ar=16:9")to dynamically generate the asset. Claude visually returns the finished image directly into the chat interface for your review, and provides the finalized 4K delivery CDN link.
3. Intelligent Organization
- You: "Gather all photos related to the Q3 Marketing Campaign and put them into a new folder."
- What Claude Does: Executes a semantic search for related campaign assets. Calls
create_dam_group(name: "Q3 Campaign"), adds the retrieved IDs to the group, and confirms the organization structure.
4. Re-analysis & Summarization
- You: "Re-run the AI analysis on that blurry document, then give me a summary of it."
- What Claude Does: Calls
reanalyze_asseton the document to re-trigger the ingestion pipeline, checks back withget_assetfor the refreshed analysis, and usessummarize_assetto produce an on-demand text summary.
5. Safe Cleanup with the Trash
- You: "Clean up the duplicate screenshots from last week's testing."
- What Claude Does: Searches for the assets and calls
delete_asseton each — which moves them to the Trash rather than destroying them. They stay recoverable for 30 days (viarestore_assetor the dashboard), so a misunderstood instruction never causes permanent data loss.
Cursor Integration
Add the server to .cursor/mcp.json in your project root (or ~/.cursor/mcp.json to make it available in every project):
{
"mcpServers": {
"picsha-ai": {
"command": "npx",
"args": ["-y", "@picsha-ai/mcp-server"],
"env": {
"PICSHA_API_KEY": "<YOUR_PICSHA_API_KEY>"
}
}
}
}
Cursor lists the Picsha tools under Settings → MCP once the file is saved. In Agent mode, just ask in natural language — "find our hero images with transparent backgrounds and crop them to 1:1" — and Cursor will call the tools directly.
VS Code Integration
VS Code (1.99+) supports MCP servers in agent mode via .vscode/mcp.json. The inputs block below prompts for your API key the first time the server starts and stores it securely, so the key never lives in a file you might commit:
{
"inputs": [
{
"type": "promptString",
"id": "picsha-api-key",
"description": "Picsha AI API Key",
"password": true
}
],
"servers": {
"picsha-ai": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@picsha-ai/mcp-server"],
"env": {
"PICSHA_API_KEY": "${input:picsha-api-key}"
}
}
}
}
Open Copilot Chat in Agent mode and the Picsha tools appear in the tools picker.
Available Tools
The published @picsha-ai/mcp-server package (v2.2.1+) exposes the following tools:
[!NOTE] The hosted Streamable HTTP endpoint (
https://api.picsha.ai/v1/mcp— see Connection Details) exposes the same core toolset with two substitutions for transport reasons: instead ofupload_assetit offersget_presigned_upload_url(filename, contentType)(returning a short-lived S3 PUT URL, since a remote server cannot read your local files), instead ofget_rendered_asset_urlit offersgenerate_render_url(id, transformations), and it omits the chat-UI preview tools (render_asset_preview,poll_render).
1. search_assets
Search for assets in the Picsha AI platform. Natively supports semantic/vector search for natural language queries.
query(string, required): The search query to find assets.mode(string, optional): The search mode to use. Set to"ai"to enable hybrid semantic vector search using natural language (e.g., "people wearing red hats"). Defaults to"standard"for simple tag/text matching.threshold(number, optional): Minimum relevance score threshold (default:0.6).
2. get_asset
Retrieve detailed metadata and AI analysis results for a specific asset.
id(string, required): The unique ID of the asset to fetch metadata for.
3. list_recent_assets
Returns the most recently uploaded assets in chronological order.
limit(number, default:10): Number of assets to retrieve.
4. reanalyze_asset
Re-runs the Picsha AI processing pipeline on an existing asset (face/object detection, tagging, summaries, embeddings).
id(string, required): The unique ID of the asset.
5. upload_asset
Uploads a local file directly to the Picsha platform. The tool fetches a pre-signed S3 URL and executes the PUT automatically, then triggers the asynchronous ingestion pipeline — so the asset is initially pending; use get_asset a few seconds later for the processed result.
filePath(string, required): Absolute path to the local file.filename(string, optional): Original filename to associate; defaults to the file's basename.
6. trigger_url_ingest
Ingests a file from a public web URL. The Picsha ingest worker downloads the file to S3 and processes it automatically.
url(string, required): Public URL for the worker to download the asset from.filename(string, optional): Explicit filename to save.config(object, optional): Processing configuration (e.g.{ auto_summarize: true }).
7. get_rendered_asset_url
Generates a delivery URL for an asset with applied transformations or AI overrides — immediate access to Picsha's visual manipulation engine without duplicating the underlying asset.
id(string, required): The unique ID of the asset.params(string, optional): A query string of transformation parameters. Supports standard parameters (e.g.w,h,ar,fmt, forced downloads viadownload=true) and generative AI parameters (e.g.bg_rem=true,mimi=remove person and add a sunset scene,mimi_mode=lite,upscale=4k,mimi_bg=misty alpine lake).- When
paramsincludes generative parameters, the returned URL is automatically minted with a delivery signature (?sig=) so it is fetchable without authentication — anonymous unsigned generative URLs are rejected with401. Always obtain generative URLs from this tool (orsign-delivery) rather than hand-constructing them.
8. update_asset
Allows agents to act as automated curators by updating asset metadata and tags.
id(string, required): The unique ID of the asset.tags(array, optional): Array of strings to append as tags. To remove tags, prefix the string with a hyphen (e.g.["-old_tag", "new_tag"]).metadata(object, optional): Custom key-value dictionary to attach to the asset.
9. delete_asset
Moves an asset into a 30-day Trash state (soft delete). The asset immediately disappears from search results and delivery endpoints (cached CDN copies are evicted), but remains restorable via restore_asset or the dashboard until its purge date. This fail-safe protects against prompt injection or misunderstood instructions causing permanent data loss.
id(string, required): The unique ID of the asset.force(boolean, optional): Iftrue, skips the Trash and permanently deletes immediately (database, search indexes, and storage). Agents should pass this only when the user explicitly asks for permanent deletion.
10. restore_asset
Restores a trashed asset to its previous state, making it deliverable and searchable again. No re-processing is needed — the asset returns exactly as it was.
id(string, required): The unique ID of the trashed asset.
11. moderate_asset
Allows specialized AI agents to handle moderation workflows by approving or rejecting flagged assets. Approval sets the asset's status to active; rejection sets it to rejected (blocking delivery).
id(string, required): The unique ID of the asset.action(string, required): The moderation action. Must be"approve"or"reject".
12. create_dam_group
Creates a collection or folder to structurally organize assets.
name(string, required): The name of the collection/folder.description(string, optional): A description for the group.
13. link_assets
Explicitly defines a relationship between two assets, useful for linking AI-generated variants to their originals.
sourceId(string, required): The asset ID of the parent or source asset.targetId(string, required): The asset ID of the variation, derived, or correlated asset.relationshipType(string, required): Description of the link (e.g.,"REPLACES","VARIATION","CONVERTED").
14. summarize_asset
Generates an on-demand AI text summary of a document asset.
id(string, required): The unique ID of the asset.
15. escalate_to_support
Use this tool ONLY when you need to log a feature request, report a documentation gap, or escalate an issue to the engineering team. This will actually send an email to support@picsha.ai.
subject(string, required): The subject of the escalation email.headline(string, required): A short, punchy headline for the email.message(string, required): The full summary of the request, formatted nicely.
16. render_asset_preview
Generates a dynamic preview of an asset with applied transformations or AI overrides, returning the visual image bytes natively to the agent. Non-blocking: if a cold generation takes too long, it returns pending with a jobId for poll_render.
id(string, required): The unique ID of the asset.params(string, optional): A query string of transformation parameters (e.g.bg_rem=true&mimi=sunset, ormimi=sunset&mimi_mode=litefor a faster 1K preview render).maxDim(number, optional): The maximum dimension in pixels to cap the preview at (default:768).timeoutMs(number, optional): Max time to block waiting for cold generation.
17. poll_render
Retrieves the result of an asynchronous generation started by render_asset_preview when the initial request timed out and returned pending.
jobId(string, required): The async job ID returned by a pending render response.timeoutMs(number, optional): Bounded wait to check for completion.