@allourthings/mcp-server 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +7 -1
  2. package/dist/index.js +94 -22
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -6,7 +6,7 @@ An [MCP server](https://modelcontextprotocol.io) that gives your AI assistant na
6
6
 
7
7
  ## Requirements
8
8
 
9
- **Desktop only** — macOS, Windows, or Linux with [Claude Desktop](https://claude.ai/download) or another MCP-compatible client. The MCP server runs as a local process; mobile AI assistant access is not currently supported. An iOS app for add/browse on mobile is in development.
9
+ **Desktop only** — macOS, Windows, or Linux with [Claude Desktop](https://claude.ai/download) or another MCP-compatible client. The MCP server runs as a local process.
10
10
 
11
11
  ## Setup
12
12
 
@@ -45,6 +45,8 @@ Once connected, ask your AI assistant things like:
45
45
  - *"What's in the kitchen?"*
46
46
  - *"When does my TV warranty expire?"*
47
47
  - *"Search for anything Samsung"*
48
+ - *"Find and attach the manual for my Dyson V15"*
49
+ - *"Attach this receipt to my MacBook"*
48
50
 
49
51
  ## Tools
50
52
 
@@ -56,6 +58,10 @@ Once connected, ask your AI assistant things like:
56
58
  | `update_item` | Update fields on an existing item |
57
59
  | `delete_item` | Delete an item by ID |
58
60
  | `search_items` | Full-text search across all item fields |
61
+ | `add_attachment` | Attach a file (manual, receipt, photo, warranty) to an item |
62
+ | `attach_from_url` | Download a file from a URL and attach it to an item |
63
+ | `get_attachment` | Retrieve an attachment's contents |
64
+ | `delete_attachment` | Remove an attachment from an item |
59
65
 
60
66
  ## Vault structure
61
67
 
package/dist/index.js CHANGED
@@ -11169,13 +11169,22 @@ class NativeBackend {
11169
11169
  return this.store.searchItems(query);
11170
11170
  }
11171
11171
  async addAttachment(itemId, filename, kind, data, label) {
11172
- return this.store.addAttachment(itemId, filename, kind, data, label ?? null);
11172
+ const result = this.store.addAttachment(itemId, filename, kind, data, label ?? null);
11173
+ if (result instanceof Error)
11174
+ throw result;
11175
+ return result;
11173
11176
  }
11174
11177
  async getAttachment(itemId, filename) {
11175
- return this.store.getAttachment(itemId, filename);
11178
+ const result = this.store.getAttachment(itemId, filename);
11179
+ if (result instanceof Error)
11180
+ throw result;
11181
+ return result;
11176
11182
  }
11177
11183
  async deleteAttachment(itemId, filename) {
11178
- return this.store.deleteAttachment(itemId, filename);
11184
+ const result = this.store.deleteAttachment(itemId, filename);
11185
+ if (result instanceof Error)
11186
+ throw result;
11187
+ return result;
11179
11188
  }
11180
11189
  }
11181
11190
 
@@ -17800,20 +17809,19 @@ class Server extends Protocol {
17800
17809
  // src/tools/add-item.ts
17801
17810
  var addItemInputSchema = exports_external.object({
17802
17811
  name: exports_external.string().describe("Name of the item"),
17803
- category: exports_external.string().optional().describe("Category (e.g. appliance, furniture, subscription)"),
17804
- brand: exports_external.string().optional(),
17805
- model: exports_external.string().optional(),
17806
- purchase_date: exports_external.string().optional().describe("ISO date string"),
17807
- purchase_price: exports_external.number().optional(),
17808
- currency: exports_external.string().optional().describe("e.g. USD, GBP"),
17809
- warranty_expires: exports_external.string().optional().describe("ISO date string"),
17810
- retailer: exports_external.string().optional(),
17811
- location: exports_external.string().optional().describe("Where the item is kept"),
17812
- features: exports_external.array(exports_external.string()).optional(),
17813
- notes: exports_external.string().optional(),
17814
- tags: exports_external.array(exports_external.string()).optional(),
17815
- manual_ref: exports_external.string().optional().describe("URL or filename of the manual"),
17816
- images: exports_external.array(exports_external.string()).optional().describe("Image file paths or URLs")
17812
+ category: exports_external.string().optional().describe("Category (e.g. appliance, electronics, furniture, subscription)"),
17813
+ brand: exports_external.string().optional().describe("Manufacturer or brand name"),
17814
+ model: exports_external.string().optional().describe("Model name or number"),
17815
+ purchase_date: exports_external.string().optional().describe("Date of purchase (ISO date, e.g. 2024-01-15)"),
17816
+ purchase_price: exports_external.number().optional().describe("Price paid for the item"),
17817
+ currency: exports_external.string().optional().describe("Currency code (e.g. GBP, USD, EUR)"),
17818
+ warranty_expires: exports_external.string().optional().describe("Warranty expiry date (ISO date, e.g. 2026-01-15)"),
17819
+ retailer: exports_external.string().optional().describe("Where the item was purchased"),
17820
+ location: exports_external.string().optional().describe("Where the item is kept (e.g. kitchen, garage, office)"),
17821
+ features: exports_external.array(exports_external.string()).optional().describe("Key features or specifications"),
17822
+ notes: exports_external.string().optional().describe("Any additional notes about the item"),
17823
+ tags: exports_external.array(exports_external.string()).optional().describe("Tags for grouping or searching (e.g. ['white goods', 'laundry'])"),
17824
+ serial_number: exports_external.string().optional().describe("Serial number of the item")
17817
17825
  }).passthrough();
17818
17826
  async function addItem(backend, input) {
17819
17827
  const item = await backend.addItem(input);
@@ -17829,7 +17837,7 @@ async function addItem(backend, input) {
17829
17837
 
17830
17838
  // src/tools/get-item.ts
17831
17839
  var getItemInputSchema = exports_external.object({
17832
- id_or_name: exports_external.string().describe("Item ID or name (fuzzy match)")
17840
+ id_or_name: exports_external.string().describe("Item ID or name. Tries exact ID match first, then exact name match, then substring match.")
17833
17841
  });
17834
17842
  async function getItem(backend, input) {
17835
17843
  const item = await backend.getItem(input.id_or_name);
@@ -17869,8 +17877,8 @@ ${summary}` }]
17869
17877
 
17870
17878
  // src/tools/update-item.ts
17871
17879
  var updateItemInputSchema = exports_external.object({
17872
- id: exports_external.string().regex(/^[0-9a-f]{8}$/).describe("Item ID to update"),
17873
- updates: exports_external.record(exports_external.unknown()).describe("Fields to update")
17880
+ id: exports_external.string().regex(/^[0-9a-f]{8}$/).describe("Item ID to update (8-character hex, from the id field on the item)"),
17881
+ updates: exports_external.record(exports_external.unknown()).describe("Fields to update. Any item field can be updated: name, category, brand, model, purchase_date, purchase_price, currency, warranty_expires, retailer, location, features, notes, tags, serial_number. Custom fields are also accepted.")
17874
17882
  });
17875
17883
  async function updateItem(backend, input) {
17876
17884
  const item = await backend.updateItem(input.id, input.updates);
@@ -17880,7 +17888,7 @@ async function updateItem(backend, input) {
17880
17888
  };
17881
17889
  }
17882
17890
  return {
17883
- content: [{ type: "text", text: `Updated: ${item.name}` }]
17891
+ content: [{ type: "text", text: JSON.stringify(item, null, 2) }]
17884
17892
  };
17885
17893
  }
17886
17894
 
@@ -17977,6 +17985,63 @@ async function deleteAttachment(backend, input) {
17977
17985
  };
17978
17986
  }
17979
17987
 
17988
+ // src/tools/attach-from-url.ts
17989
+ import { basename } from "path";
17990
+ var TIMEOUT_MS = 30000;
17991
+ var MAX_BYTES = 50 * 1024 * 1024;
17992
+ var attachFromUrlInputSchema = exports_external.object({
17993
+ item_id: exports_external.string().describe("ID of the item to attach the file to"),
17994
+ url: exports_external.string().url().describe("URL of the file to download and attach"),
17995
+ kind: exports_external.enum(["manual", "receipt", "photo", "warranty", "other"]).describe("Type of attachment"),
17996
+ filename: exports_external.string().optional().describe("Filename to store the attachment as — inferred from URL if omitted"),
17997
+ label: exports_external.string().optional().describe("Human-readable label (e.g. 'User Manual')")
17998
+ });
17999
+ function errorResult(message) {
18000
+ return {
18001
+ content: [{ type: "text", text: message }],
18002
+ isError: true
18003
+ };
18004
+ }
18005
+ async function attachFromUrl(backend, input) {
18006
+ const abort = new AbortController;
18007
+ const timer = setTimeout(() => abort.abort(), TIMEOUT_MS);
18008
+ let response;
18009
+ try {
18010
+ response = await fetch(input.url, { signal: abort.signal });
18011
+ } catch (err) {
18012
+ clearTimeout(timer);
18013
+ const isTimeout = err instanceof Error && err.name === "AbortError";
18014
+ return errorResult(isTimeout ? `Download timed out after ${TIMEOUT_MS / 1000}s. The server may be blocking automated requests or the file is too slow to respond. Try a different direct download URL.` : `Network error fetching URL: ${err instanceof Error ? err.message : String(err)}. Check the URL is correct and publicly accessible.`);
18015
+ }
18016
+ clearTimeout(timer);
18017
+ if (!response.ok) {
18018
+ const hint = response.status === 403 || response.status === 401 ? " The server is blocking automated downloads. Try finding a direct PDF link from a different source." : response.status === 404 ? " The file was not found at this URL. It may have moved — try searching for an updated link." : "";
18019
+ return errorResult(`Download failed: HTTP ${response.status} ${response.statusText}.${hint}`);
18020
+ }
18021
+ const contentType = response.headers.get("content-type") ?? "";
18022
+ const contentLength = Number(response.headers.get("content-length") ?? 0);
18023
+ if (contentLength > MAX_BYTES) {
18024
+ return errorResult(`File is too large to attach (${Math.round(contentLength / 1024 / 1024)} MB — limit is ${MAX_BYTES / 1024 / 1024} MB). Try finding a compressed or alternative version.`);
18025
+ }
18026
+ if (contentType.startsWith("text/html")) {
18027
+ return errorResult("The URL returned an HTML page rather than a file. This is likely a product page or login wall, not a direct download link. Try right-clicking the download button on the page and copying the direct PDF link.");
18028
+ }
18029
+ const buffer = Buffer.from(await response.arrayBuffer());
18030
+ if (buffer.byteLength > MAX_BYTES) {
18031
+ return errorResult(`File is too large to attach (${Math.round(buffer.byteLength / 1024 / 1024)} MB — limit is ${MAX_BYTES / 1024 / 1024} MB).`);
18032
+ }
18033
+ const filename = input.filename ?? (basename(new URL(input.url).pathname) || "attachment");
18034
+ const item = await backend.addAttachment(input.item_id, filename, input.kind, buffer, input.label);
18035
+ return {
18036
+ content: [
18037
+ {
18038
+ type: "text",
18039
+ text: JSON.stringify(item, null, 2)
18040
+ }
18041
+ ]
18042
+ };
18043
+ }
18044
+
17980
18045
  // src/server.ts
17981
18046
  function createServer(backend) {
17982
18047
  const server = new Server({ name: "allourthings", version: "0.1.0" }, { capabilities: { tools: {} } });
@@ -18019,13 +18084,18 @@ function createServer(backend) {
18019
18084
  },
18020
18085
  {
18021
18086
  name: "get_attachment",
18022
- description: "Retrieve an attachment's contents as a base64-encoded string",
18087
+ description: "Retrieve an attachment's contents as a base64-encoded string. Useful for checking an attachment exists. Note: binary files like PDFs cannot be read or displayed directly.",
18023
18088
  inputSchema: zodToJsonSchema(getAttachmentInputSchema)
18024
18089
  },
18025
18090
  {
18026
18091
  name: "delete_attachment",
18027
18092
  description: "Delete an attachment from an item",
18028
18093
  inputSchema: zodToJsonSchema(deleteAttachmentInputSchema)
18094
+ },
18095
+ {
18096
+ name: "attach_from_url",
18097
+ description: "Download a file from a URL and attach it to an item (e.g. a PDF manual or warranty document)",
18098
+ inputSchema: zodToJsonSchema(attachFromUrlInputSchema)
18029
18099
  }
18030
18100
  ]
18031
18101
  }));
@@ -18050,6 +18120,8 @@ function createServer(backend) {
18050
18120
  return getAttachment(backend, getAttachmentInputSchema.parse(args));
18051
18121
  case "delete_attachment":
18052
18122
  return deleteAttachment(backend, deleteAttachmentInputSchema.parse(args));
18123
+ case "attach_from_url":
18124
+ return attachFromUrl(backend, attachFromUrlInputSchema.parse(args));
18053
18125
  default:
18054
18126
  throw new Error(`Unknown tool: ${name}`);
18055
18127
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@allourthings/mcp-server",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "mcpName": "io.allourthings/mcp-server",
5
- "description": "MCP server for All Our Things — AI-powered household inventory",
5
+ "description": "AllOurThings — Your things, understood by AI.",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",