SortJar MCP server

SortJar speaks the Model Context Protocol, so an AI assistant such as Claude, ChatGPT or Cursor can search your inventory and file new things away on your behalf. It connects as you, over OAuth, and only ever sees your own Jars and items.

Server URL

https://your-sortjar-site/mcp

Transport: MCP Streamable HTTP. Auth: OAuth 2.1 with dynamic client registration.

Connecting a client

Most clients only need the URL. Add it as a custom connector / remote MCP server and approve the sign-in prompt that follows.

{
  "mcpServers": {
    "sortjar": {
      "url": "https://your-sortjar-site/mcp"
    }
  }
}

A client that cannot do OAuth in-app will print an authorisation link instead — open it in a browser and the connection completes once you approve.

How the sign-in works

There is no API key to copy or paste anywhere. SortJar is an OAuth 2.1 resource server; your account provider issues the tokens.

  1. The client fetches /.well-known/oauth-protected-resource from this site to discover the authorisation server.
  2. It registers itself automatically (dynamic client registration) — no manual client ID or secret.
  3. You are sent to SortJar to sign in, then to a consent screen naming the client that is asking. Approve it and you land back in the client.
  4. The client receives a short-lived access token and sends it as Authorization: Bearer … on every call. It is refreshed silently.
  5. Every tool runs as you: database row-level security is keyed to your user, so a token can never read another person's inventory. Calls without a valid token are rejected with 401.

To revoke access, disconnect the connector in the client. Tokens are scoped to that client only.

The five tools

Arguments below are the JSON that goes in a tools/call request. Ids are UUIDs returned by the read tools — ask for a Jar list first, then use the id you get back.

search_items

Find where something is stored. This is the tool an assistant reaches for first.

Arguments: query (string, required) · tags (string[], max 5) · sort (relevance | name | jar | quantity) · order (asc | desc) · limit (1-200, default 50) · offset

Returns: hits — items with quantity, tags, the Jar they live in and the full location path — plus a page object.

{
  "name": "search_items",
  "arguments": {
    "query": "ski jacket",
    "tags": ["winter"],
    "sort": "name",
    "limit": 25,
    "offset": 0
  }
}

list_jars

Browse your containers. Omit parent_id for every Jar, or pass one to list its children.

Arguments: parent_id (uuid) · search (name contains) · sort (name | created_at | updated_at) · order (asc | desc) · limit (1-200, default 50) · offset

Returns: jars with id, name, parent_id, kind, tags, notes and timestamps, plus a page object.

{
  "name": "list_jars",
  "arguments": {
    "parent_id": "6f1c9e2a-6a3b-4a63-9c1e-2b0d5b9f7a11",
    "search": "box",
    "sort": "updated_at",
    "order": "desc",
    "limit": 50
  }
}

get_jar_contents

Open one Jar: its details, the Jars nested inside it and the items it holds.

Arguments: jar_id (uuid, required) · sort (favorite | name | quantity | created_at) · order (asc | desc) · limit (1-200, default 50) · offset

Returns: jar, path (breadcrumb from the top), jars (children), items (favourites first by default) and items_page.

{
  "name": "get_jar_contents",
  "arguments": {
    "jar_id": "6f1c9e2a-6a3b-4a63-9c1e-2b0d5b9f7a11",
    "sort": "name",
    "limit": 100,
    "offset": 0
  }
}

create_jar

Create a room, shelf, box or any other container, optionally nested inside another Jar.

Arguments: name (string, required) · parent_id (uuid, optional) · tags (string[], max 5) · notes (string)

Returns: The new Jar, including its id — pass that to add_item.

{
  "name": "create_jar",
  "arguments": {
    "name": "Attic box 3",
    "parent_id": "6f1c9e2a-6a3b-4a63-9c1e-2b0d5b9f7a11",
    "tags": ["box", "attic"],
    "notes": "Winter clothing overflow"
  }
}

add_item

Record an item as stored inside one of your Jars.

Arguments: jar_id (uuid, required) · name (string, required) · quantity (int, default 1) · tags (string[], max 5) · description (string)

Returns: The saved item row.

{
  "name": "add_item",
  "arguments": {
    "jar_id": "6f1c9e2a-6a3b-4a63-9c1e-2b0d5b9f7a11",
    "name": "Down ski jacket",
    "quantity": 1,
    "tags": ["winter", "clothing"],
    "description": "Navy, size M"
  }
}

update_jar

Edit an existing Jar: rename it, change its notes, replace its tags, set its kind or move it inside another Jar.

Arguments: jar_id (uuid, required) · name (string) · notes (string | null) · tags (string[], max 5, replaces existing) · kind (room | cupboard | shelf | wardrobe | drawer | box | bag | crate | suitcase | garage) · parent_id (uuid | null to move to the top level)

Returns: The updated Jar row. Only the fields you send are changed. Row-level security means a jar_id that isn't yours simply isn't found, and a move that would nest a Jar inside its own subtree is rejected.

{
  "name": "update_jar",
  "arguments": {
    "jar_id": "6f1c9e2a-6a3b-4a63-9c1e-2b0d5b9f7a11",
    "name": "Attic box 3 (winter)",
    "tags": ["box", "attic", "winter"],
    "parent_id": null
  }
}

Paging and sorting

The browse tools — search_items, list_jars and get_jar_contents — return one page at a time so a large inventory never floods the assistant's context.

Each response carries a page summary (named items_page in get_jar_contents). Keep calling while has_more is true, feeding next_offset straight back in.

{
  "page": {
    "limit": 50,
    "offset": 0,
    "returned": 50,
    "total": 214,
    "has_more": true,
    "next_offset": 50
  }
}

Calling it directly

If you are testing by hand, the endpoint is plain JSON-RPC over HTTP POST. Both Accept types are required by the MCP spec.

curl -X POST https://your-sortjar-site/mcp \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "search_items",
      "arguments": { "query": "ski jacket" }
    }
  }'

Use "method": "tools/list" to see the live tool schemas.

If something goes wrong

Back to SortJar