imsg-mcp 1.18.0 → 1.19.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and follows [Semantic Versioning](https://semver.org/).
5
5
 
6
+ # [1.19.0](https://github.com/george43g/imsg-mcp/compare/v1.18.0...v1.19.0) (2026-07-25)
7
+
8
+
9
+ ### Features
10
+
11
+ * **contacts:** attach a normalized identity block to get_contact + resolve_handle ([99dfc72](https://github.com/george43g/imsg-mcp/commit/99dfc723e2a5bf8508f1b3940a63a0aeb684d120))
12
+
6
13
  # [1.18.0](https://github.com/george43g/imsg-mcp/compare/v1.17.0...v1.18.0) (2026-07-25)
7
14
 
8
15
 
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import { createInterface } from "node:readline";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import { Command } from "commander";
6
6
  import { d as checkLocalAccess, f as formatAccessReport, I as IMPLEMENTED_TYPES, A as ANALYTIC_INFO, i as installShutdownHandlers, r as registerCleanup, l as looksLikeThreadSlug } from "./shutdown-0TCv4XJB.js";
7
- import { A as APP_VERSION, t as toYaml } from "./meta-CZmUZHTh.js";
7
+ import { A as APP_VERSION, t as toYaml } from "./meta-CD2uvSHu.js";
8
8
  import { spawn } from "node:child_process";
9
9
  import { join, dirname } from "node:path";
10
10
  function distRoot() {
package/dist/index.js CHANGED
@@ -14,11 +14,11 @@ import { ensureAttachmentDownloaded } from "./attachment-sync-DMvlT8Zk.js";
14
14
  import { n as normalizedPhoneVariants, h as hasNativeModule, I as IMessageDB, r as rankFuzzy } from "./imessage-db-CHbdUXk3.js";
15
15
  import { parseUserDate } from "./date-parse-DJXMfq3a.js";
16
16
  import { streamExport, ExportInterpretGuardError } from "./exportStream-BFxilSlL.js";
17
- import { r as renderAnalyticText, A as APP_VERSION, a as APP_NAME } from "./meta-CZmUZHTh.js";
17
+ import { n as normalizePhoneToE164, m as minMessageId, a as resolveRecipient, d as defaultCountryFromEnv, i as installWatchdog, b as noteActivity, r as readWatchdogState } from "./watchdog-D54vuRzh.js";
18
+ import { r as renderAnalyticText, A as APP_VERSION, a as APP_NAME } from "./meta-CD2uvSHu.js";
18
19
  import { randomUUID } from "node:crypto";
19
20
  import { z } from "zod";
20
21
  import { a as applyInlineInterpretations, i as imageBlockFromFile, v as videoPosterFrame, m as mediaMetadata, b as refForAttachment, T as TRANSCRIBE_MAX_BYTES, g as getInterpretRuntime, t as transcriptSourceEnum, d as detectTranscriber } from "./media-intel-runtime-BkxG4bmW.js";
21
- import { m as minMessageId, a as resolveRecipient, d as defaultCountryFromEnv, i as installWatchdog, n as noteActivity, r as readWatchdogState } from "./watchdog-CW2SEAmu.js";
22
22
  const DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
23
23
  let dbPath = join(homedir(), ".imsg-mcp", "analytics-cache.db");
24
24
  let db = null;
@@ -323,6 +323,37 @@ function humansHintText(hint) {
323
323
  _Relationship file(s): ${list}_
324
324
  _${hint.guidance}_`;
325
325
  }
326
+ function dedupe(items) {
327
+ const seen = /* @__PURE__ */ new Set();
328
+ const out = [];
329
+ for (const item of items) {
330
+ if (item && !seen.has(item)) {
331
+ seen.add(item);
332
+ out.push(item);
333
+ }
334
+ }
335
+ return out;
336
+ }
337
+ function normalizeEmail(email) {
338
+ return email.trim().toLowerCase();
339
+ }
340
+ function e164OrOriginal(phone, country) {
341
+ return normalizePhoneToE164(phone, country) ?? phone.trim();
342
+ }
343
+ function buildIdentity(canonicalName, phoneNumbers, emails, country) {
344
+ const phones = dedupe(phoneNumbers.map((p) => e164OrOriginal(p, country)));
345
+ const mails = dedupe(emails.map(normalizeEmail));
346
+ return {
347
+ canonicalName,
348
+ phones,
349
+ emails: mails,
350
+ handles: dedupe([...phones, ...mails])
351
+ };
352
+ }
353
+ function buildIdentityFromHandle(handle, name, country) {
354
+ const isEmail = handle.includes("@");
355
+ return buildIdentity(name ?? handle, isEmail ? [] : [handle], isEmail ? [handle] : [], country);
356
+ }
326
357
  const INSTRUCTIONS_UUID = randomUUID();
327
358
  function wrapUntrusted(text) {
328
359
  if (text == null || text === "") return "";
@@ -2001,8 +2032,16 @@ const GetContactSchema = z.object({
2001
2032
  }).refine((v) => v.handle !== void 0 || v.id !== void 0, {
2002
2033
  message: "Provide either `handle` or `id`."
2003
2034
  });
2035
+ const IdentitySchema = z.object({
2036
+ canonicalName: z.string().describe("Best display name for this person (or the handle itself)."),
2037
+ phones: z.array(z.string()).describe("Phone handles, E.164-normalized where derivable."),
2038
+ emails: z.array(z.string()).describe("Email handles, trimmed + lowercased."),
2039
+ handles: z.array(z.string()).describe("Every reachable handle, deduped — phones first, then emails.")
2040
+ });
2004
2041
  const GetContactOutputSchema = z.object({
2005
2042
  contact: ContactSchema.nullable(),
2043
+ /** Normalized cross-tool handle view (E.164 phones, lowercased emails). */
2044
+ identity: IdentitySchema.optional(),
2006
2045
  /** Per-handle conversation mapping: which thread slug each handle chats under. */
2007
2046
  threads: z.array(
2008
2047
  z.object({
@@ -2041,7 +2080,9 @@ const ResolveHandleOutputSchema = z.object({
2041
2080
  displayName: z.string(),
2042
2081
  contactId: z.number().int().nullable(),
2043
2082
  label: z.string().nullable(),
2044
- resolved: z.boolean()
2083
+ resolved: z.boolean(),
2084
+ /** Normalized cross-tool handle view — populated even when unresolved. */
2085
+ identity: IdentitySchema.optional()
2045
2086
  });
2046
2087
  const CheckImessageAvailabilitySchema = z.object({
2047
2088
  handle: nonEmptyString("Phone number or email to preflight-check for reachability.")
@@ -2439,7 +2480,7 @@ const TOOLS = [
2439
2480
  },
2440
2481
  {
2441
2482
  name: "get_contact",
2442
- description: "Fetch a single contact by handle (phone/email) or by numeric id, including each handle's thread slug (for send_message/get_messages). Returns null if not found. Includes `humansFile` — the path to this person's humans/v1 relationship file if one exists (read it for relationship context; init_human scaffolds one otherwise).",
2483
+ description: "Fetch a single contact by handle (phone/email) or by numeric id, including each handle's thread slug (for send_message/get_messages) and an `identity` block (canonicalName + E.164-normalized phones + lowercased emails + deduped handles). Returns null if not found. Includes `humansFile` — the path to this person's humans/v1 relationship file if one exists (read it for relationship context; init_human scaffolds one otherwise).",
2443
2484
  annotations: annotations.read,
2444
2485
  inputSchema: {
2445
2486
  type: "object",
@@ -2469,7 +2510,7 @@ const TOOLS = [
2469
2510
  },
2470
2511
  {
2471
2512
  name: "resolve_handle",
2472
- description: "Resolve a phone number or email to its contact display name. Pass-through if unknown.",
2513
+ description: "Resolve a phone number or email to its contact display name. Pass-through if unknown. Always returns an `identity` block (canonicalName + E.164-normalized phones + lowercased emails + deduped handles) — even for an unknown handle, so you get it in canonical form.",
2473
2514
  annotations: annotations.read,
2474
2515
  inputSchema: {
2475
2516
  type: "object",
@@ -3688,10 +3729,17 @@ ${withThreads.map((t) => ` ${t.handle} → ${t.threadSlug}`).join("\n")}` : "";
3688
3729
  const humansLine = humansHint ? humansHintText(humansHint) : `
3689
3730
 
3690
3731
  _${HUMANS_INIT_HINT}_`;
3732
+ const identity = buildIdentity(
3733
+ contact.displayName,
3734
+ contact.phoneNumbers,
3735
+ contact.emails,
3736
+ defaultCountryFromEnv()
3737
+ );
3691
3738
  return toolText(
3692
3739
  `${sanitizeUserText(contact.displayName)} (id ${contact.id})${phones}${emails}${org}${threadLines}${humansLine}`,
3693
3740
  {
3694
3741
  contact,
3742
+ identity,
3695
3743
  threads,
3696
3744
  humansFile,
3697
3745
  humansGuidance: humansHint?.guidance ?? HUMANS_INIT_HINT
@@ -3749,13 +3797,17 @@ ${lines}${hint}`,
3749
3797
  const selectorHit = resolveContactSelector(handle);
3750
3798
  const effectiveHandle = selectorHit?.handle ?? handle;
3751
3799
  const lookup = this.db.contacts.lookupContact(effectiveHandle);
3800
+ const country = defaultCountryFromEnv();
3752
3801
  if (lookup) {
3802
+ const contact = this.db.contacts.getContact(lookup.contactId);
3803
+ const identity = contact ? buildIdentity(contact.displayName, contact.phoneNumbers, contact.emails, country) : buildIdentityFromHandle(effectiveHandle, lookup.displayName, country);
3753
3804
  return toolText(`${handle} → ${sanitizeUserText(lookup.displayName)}`, {
3754
3805
  handle,
3755
3806
  displayName: lookup.displayName,
3756
3807
  contactId: lookup.contactId,
3757
3808
  label: lookup.label ?? null,
3758
- resolved: true
3809
+ resolved: true,
3810
+ identity
3759
3811
  });
3760
3812
  }
3761
3813
  return toolText(`No contact for ${handle}.`, {
@@ -3763,7 +3815,8 @@ ${lines}${hint}`,
3763
3815
  displayName: handle,
3764
3816
  contactId: null,
3765
3817
  label: null,
3766
- resolved: false
3818
+ resolved: false,
3819
+ identity: buildIdentityFromHandle(effectiveHandle, null, country)
3767
3820
  });
3768
3821
  }
3769
3822
  async handleCheckImessageAvailability(args) {