obsidian-tc 1.0.1 → 1.0.2

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/dist/cli.js +99 -31
  2. package/dist/index.js +94 -28
  3. package/package.json +3 -3
package/dist/cli.js CHANGED
@@ -30672,7 +30672,7 @@ var MUTATING_FAMILIES = ["write", "delete", "bulk", "execute"];
30672
30672
  function isMutatingScope(scope) {
30673
30673
  return MUTATING_FAMILIES.includes(parseScope(scope).family);
30674
30674
  }
30675
- var SCOPE_CLASS_PRECEDENCE = ["bulk", "execute", "admin", "write", "read"];
30675
+ var SCOPE_CLASS_PRECEDENCE = ["bulk", "execute", "admin", "delete", "write", "read"];
30676
30676
  function scopeClassOf(requiredScopes) {
30677
30677
  const families = new Set(requiredScopes.map((s) => parseScope(s).family));
30678
30678
  for (const cls of SCOPE_CLASS_PRECEDENCE)
@@ -34653,6 +34653,29 @@ var coerce = {
34653
34653
  date: (arg) => ZodDate.create({ ...arg, coerce: true })
34654
34654
  };
34655
34655
  var NEVER = INVALID;
34656
+ // ../shared/src/net-host.ts
34657
+ function normalizeHostForBind(host) {
34658
+ return host.trim().toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
34659
+ }
34660
+ function isStrictIpv4(h) {
34661
+ const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(h);
34662
+ if (!m)
34663
+ return false;
34664
+ return m.slice(1).every((octet) => Number(octet) <= 255);
34665
+ }
34666
+ function isLoopbackHost(host) {
34667
+ const h = normalizeHostForBind(host);
34668
+ if (h === "localhost" || h === "::1")
34669
+ return true;
34670
+ if (isStrictIpv4(h))
34671
+ return h.startsWith("127.");
34672
+ if (h.startsWith("::ffff:")) {
34673
+ const v4 = h.slice("::ffff:".length);
34674
+ return isStrictIpv4(v4) && v4.startsWith("127.");
34675
+ }
34676
+ return false;
34677
+ }
34678
+
34656
34679
  // ../shared/src/config.schema.ts
34657
34680
  var VaultBridgesConfigSchema = exports_external.object({
34658
34681
  timeoutMs: exports_external.number().int().positive().default(5000),
@@ -34688,7 +34711,7 @@ var VaultConfigSchema = exports_external.object({
34688
34711
  workspace: VaultWorkspaceConfigSchema.optional()
34689
34712
  });
34690
34713
  var AuthConfigSchema = exports_external.object({
34691
- mode: exports_external.enum(["none", "jwt", "oauth"]).default("none"),
34714
+ mode: exports_external.enum(["none", "jwt"]).default("none"),
34692
34715
  jwtSecret: exports_external.string().min(32).optional(),
34693
34716
  tokenTtlSeconds: exports_external.number().int().positive().default(86400)
34694
34717
  }).refine((c) => c.mode !== "jwt" || !!c.jwtSecret, {
@@ -34770,7 +34793,7 @@ var PlurConfigSchema = exports_external.object({
34770
34793
  apiPrefix: exports_external.string().default(""),
34771
34794
  timeoutMs: exports_external.number().int().positive().default(5000)
34772
34795
  });
34773
- var ServerConfigSchema = exports_external.object({
34796
+ var ServerConfigObject = exports_external.object({
34774
34797
  cacheDir: exports_external.string().default(".obsidian-tc"),
34775
34798
  vaults: exports_external.array(VaultConfigSchema).min(1),
34776
34799
  plur: PlurConfigSchema.optional(),
@@ -34784,6 +34807,16 @@ var ServerConfigSchema = exports_external.object({
34784
34807
  idempotencyTtlSeconds: exports_external.number().int().positive().default(86400),
34785
34808
  elicitTtlSeconds: exports_external.number().int().positive().default(300)
34786
34809
  });
34810
+ var ServerConfigSchema = ServerConfigObject.superRefine((cfg, ctx) => {
34811
+ const http = cfg.transports.http;
34812
+ if (http.enabled && cfg.auth.mode === "none" && !isLoopbackHost(http.host)) {
34813
+ ctx.addIssue({
34814
+ code: exports_external.ZodIssueCode.custom,
34815
+ path: ["transports", "http", "host"],
34816
+ message: `refusing to expose an unauthenticated server: transports.http.enabled is true with host "${http.host}" (non-loopback) while auth.mode is "none". Set auth.mode to "jwt" (with jwtSecret) or bind transports.http.host to a loopback address (127.0.0.1, ::1, localhost).`
34817
+ });
34818
+ }
34819
+ });
34787
34820
  // ../shared/src/morgiana.schema.ts
34788
34821
  var MORGIANA_EVENT_TYPES = [
34789
34822
  "tc.tool.call.completed",
@@ -35241,10 +35274,12 @@ function jsBm25Score(tf, docLen, avgDocLen, docFreq, docCount) {
35241
35274
  const denom = tf + k1 * (1 - b + b * (docLen / Math.max(avgDocLen, 1)));
35242
35275
  return idf * (tf * (k1 + 1)) / denom;
35243
35276
  }
35244
- function loadNative() {
35245
- const pkg = ["@the-40-thieves", "obsidian-tc-native"].join("/");
35277
+ var NATIVE_PKG = ["@the-40-thieves", "obsidian-tc-native"].join("/");
35278
+ function loadNative(env = process.env, requireFn = createRequire2(import.meta.url)) {
35279
+ if (env.OBSIDIAN_TC_FORCE_JS_FALLBACK === "1")
35280
+ return null;
35246
35281
  try {
35247
- const mod = createRequire2(import.meta.url)(pkg);
35282
+ const mod = requireFn(NATIVE_PKG);
35248
35283
  if (typeof mod.cosineSimilarity === "function" && typeof mod.tokenize === "function" && typeof mod.bm25Score === "function") {
35249
35284
  return mod;
35250
35285
  }
@@ -35387,6 +35422,7 @@ class TokenBucket {
35387
35422
  var DEFAULT_THROTTLE_TIERS = {
35388
35423
  read: { perMinute: 600, burst: 100 },
35389
35424
  write: { perMinute: 60, burst: 20 },
35425
+ delete: { perMinute: 60, burst: 20 },
35390
35426
  bulk: { perMinute: 10, burst: 3 },
35391
35427
  execute: { perMinute: 5, burst: 1 },
35392
35428
  admin: { perMinute: 5, burst: 1 }
@@ -35397,8 +35433,15 @@ class RateLimiter {
35397
35433
  tiers;
35398
35434
  buckets = new Map;
35399
35435
  hits = new Map;
35400
- constructor(tiers = DEFAULT_THROTTLE_TIERS) {
35436
+ idleTtlMs;
35437
+ maxBuckets;
35438
+ sweepIntervalMs;
35439
+ lastSweepMs = null;
35440
+ constructor(tiers = DEFAULT_THROTTLE_TIERS, opts = {}) {
35401
35441
  this.tiers = tiers;
35442
+ this.idleTtlMs = opts.idleTtlMs ?? 600000;
35443
+ this.maxBuckets = opts.maxBuckets ?? 1e4;
35444
+ this.sweepIntervalMs = opts.sweepIntervalMs ?? 60000;
35402
35445
  }
35403
35446
  check(callerHashValue, scopeClass, vaultId, nowMs, n = 1) {
35404
35447
  const tier = this.tiers[scopeClass];
@@ -35406,20 +35449,26 @@ class RateLimiter {
35406
35449
  return { ok: true, scopeClass, retryAfterSeconds: 0, currentBurst: -1, currentRate: -1 };
35407
35450
  }
35408
35451
  const key = `${callerHashValue}|${scopeClass}|${vaultId}`;
35409
- let bucket = this.buckets.get(key);
35410
- if (!bucket) {
35411
- bucket = new TokenBucket({
35412
- capacity: tier.burst,
35413
- refillTokens: tier.perMinute,
35414
- intervalMs: INTERVAL_MS
35415
- });
35416
- this.buckets.set(key, bucket);
35452
+ let entry = this.buckets.get(key);
35453
+ if (!entry) {
35454
+ entry = {
35455
+ bucket: new TokenBucket({
35456
+ capacity: tier.burst,
35457
+ refillTokens: tier.perMinute,
35458
+ intervalMs: INTERVAL_MS
35459
+ }),
35460
+ fullRefillMs: tier.perMinute > 0 ? Math.ceil(tier.burst * INTERVAL_MS / tier.perMinute) : 0,
35461
+ lastSeenMs: nowMs
35462
+ };
35463
+ this.buckets.set(key, entry);
35417
35464
  }
35418
- const res = bucket.tryRemove(n, nowMs);
35465
+ entry.lastSeenMs = nowMs;
35466
+ const res = entry.bucket.tryRemove(n, nowMs);
35419
35467
  if (!res.ok) {
35420
35468
  const hk = `${vaultId}|${scopeClass}`;
35421
35469
  this.hits.set(hk, (this.hits.get(hk) ?? 0) + 1);
35422
35470
  }
35471
+ this.sweep(nowMs);
35423
35472
  return {
35424
35473
  ok: res.ok,
35425
35474
  scopeClass,
@@ -35428,6 +35477,26 @@ class RateLimiter {
35428
35477
  currentRate: tier.perMinute
35429
35478
  };
35430
35479
  }
35480
+ sweep(nowMs) {
35481
+ if (this.lastSweepMs !== null && nowMs - this.lastSweepMs < this.sweepIntervalMs)
35482
+ return;
35483
+ this.lastSweepMs = nowMs;
35484
+ for (const [key, e] of this.buckets) {
35485
+ if (nowMs - e.lastSeenMs >= this.idleTtlMs)
35486
+ this.buckets.delete(key);
35487
+ }
35488
+ if (this.buckets.size <= this.maxBuckets)
35489
+ return;
35490
+ const evictable = [...this.buckets.entries()].filter(([, e]) => nowMs - e.lastSeenMs >= e.fullRefillMs).sort((a, b) => a[1].lastSeenMs - b[1].lastSeenMs);
35491
+ for (const [key] of evictable) {
35492
+ if (this.buckets.size <= this.maxBuckets)
35493
+ break;
35494
+ this.buckets.delete(key);
35495
+ }
35496
+ }
35497
+ get bucketCount() {
35498
+ return this.buckets.size;
35499
+ }
35431
35500
  snapshot() {
35432
35501
  return [...this.hits.entries()].map(([k, hits]) => {
35433
35502
  const sep = k.indexOf("|");
@@ -35444,6 +35513,7 @@ function callStatusForError(code) {
35444
35513
  switch (code) {
35445
35514
  case "unauthorized":
35446
35515
  case "forbidden":
35516
+ case "acl_denied":
35447
35517
  case "elicit_required":
35448
35518
  case "throttled":
35449
35519
  return "denied";
@@ -35525,6 +35595,7 @@ class ToolRegistry {
35525
35595
  }
35526
35596
  switch (result2.error.code) {
35527
35597
  case "forbidden":
35598
+ case "acl_denied":
35528
35599
  this.relay(ctx.vaultId, "tc.acl.denied", data);
35529
35600
  break;
35530
35601
  case "overflow":
@@ -35672,7 +35743,7 @@ class ToolRegistry {
35672
35743
  const duration = Math.max(0, now() - start);
35673
35744
  audit("error", duration, 0, error.code);
35674
35745
  this.meter((m) => {
35675
- if (error.code === "forbidden")
35746
+ if (error.code === "forbidden" || error.code === "acl_denied")
35676
35747
  m.incAclDenied(ctx.vaultId, scopeClass, error.code);
35677
35748
  m.observeToolCall(ctx.vaultId, name, callStatusForError(error.code), duration / 1000, 0);
35678
35749
  });
@@ -56891,20 +56962,17 @@ async function resolveAuth(header, auth) {
56891
56962
  if (auth.mode === "none") {
56892
56963
  return { ok: true, caller: "http-local", scopes: new Set(["*"]) };
56893
56964
  }
56894
- if (auth.mode === "jwt") {
56895
- const token = bearer2(header);
56896
- if (!token)
56897
- return { ok: false, status: 401, reason: "missing bearer token" };
56898
- if (!auth.jwtSecret)
56899
- return { ok: false, status: 500, reason: "jwt mode misconfigured: no secret" };
56900
- try {
56901
- const id = await verifyJwt(token, auth.jwtSecret);
56902
- return { ok: true, caller: id.caller, scopes: id.scopes };
56903
- } catch {
56904
- return { ok: false, status: 401, reason: "invalid or expired token" };
56905
- }
56965
+ const token = bearer2(header);
56966
+ if (!token)
56967
+ return { ok: false, status: 401, reason: "missing bearer token" };
56968
+ if (!auth.jwtSecret)
56969
+ return { ok: false, status: 500, reason: "jwt mode misconfigured: no secret" };
56970
+ try {
56971
+ const id = await verifyJwt(token, auth.jwtSecret);
56972
+ return { ok: true, caller: id.caller, scopes: id.scopes };
56973
+ } catch {
56974
+ return { ok: false, status: 401, reason: "invalid or expired token" };
56906
56975
  }
56907
- return { ok: false, status: 501, reason: `auth mode '${auth.mode}' is not implemented` };
56908
56976
  }
56909
56977
  function createHttpApp(opts) {
56910
56978
  const app = new Hono2;
@@ -56956,7 +57024,7 @@ function createHttpApp(opts) {
56956
57024
  function startHttp(opts) {
56957
57025
  const app = createHttpApp(opts);
56958
57026
  return new Promise((resolve2) => {
56959
- const server = serve({ fetch: app.fetch, hostname: opts.host, port: opts.port }, (info) => {
57027
+ const server = serve({ fetch: app.fetch, hostname: normalizeHostForBind(opts.host), port: opts.port }, (info) => {
56960
57028
  resolve2({
56961
57029
  port: info.port,
56962
57030
  close: () => new Promise((done) => {
package/dist/index.js CHANGED
@@ -20730,7 +20730,7 @@ var MUTATING_FAMILIES = ["write", "delete", "bulk", "execute"];
20730
20730
  function isMutatingScope(scope) {
20731
20731
  return MUTATING_FAMILIES.includes(parseScope(scope).family);
20732
20732
  }
20733
- var SCOPE_CLASS_PRECEDENCE = ["bulk", "execute", "admin", "write", "read"];
20733
+ var SCOPE_CLASS_PRECEDENCE = ["bulk", "execute", "admin", "delete", "write", "read"];
20734
20734
  function scopeClassOf(requiredScopes) {
20735
20735
  const families = new Set(requiredScopes.map((s) => parseScope(s).family));
20736
20736
  for (const cls of SCOPE_CLASS_PRECEDENCE)
@@ -24711,6 +24711,29 @@ var coerce = {
24711
24711
  date: (arg) => ZodDate.create({ ...arg, coerce: true })
24712
24712
  };
24713
24713
  var NEVER = INVALID;
24714
+ // ../shared/src/net-host.ts
24715
+ function normalizeHostForBind(host) {
24716
+ return host.trim().toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
24717
+ }
24718
+ function isStrictIpv4(h) {
24719
+ const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(h);
24720
+ if (!m)
24721
+ return false;
24722
+ return m.slice(1).every((octet) => Number(octet) <= 255);
24723
+ }
24724
+ function isLoopbackHost(host) {
24725
+ const h = normalizeHostForBind(host);
24726
+ if (h === "localhost" || h === "::1")
24727
+ return true;
24728
+ if (isStrictIpv4(h))
24729
+ return h.startsWith("127.");
24730
+ if (h.startsWith("::ffff:")) {
24731
+ const v4 = h.slice("::ffff:".length);
24732
+ return isStrictIpv4(v4) && v4.startsWith("127.");
24733
+ }
24734
+ return false;
24735
+ }
24736
+
24714
24737
  // ../shared/src/config.schema.ts
24715
24738
  var VaultBridgesConfigSchema = exports_external.object({
24716
24739
  timeoutMs: exports_external.number().int().positive().default(5000),
@@ -24746,7 +24769,7 @@ var VaultConfigSchema = exports_external.object({
24746
24769
  workspace: VaultWorkspaceConfigSchema.optional()
24747
24770
  });
24748
24771
  var AuthConfigSchema = exports_external.object({
24749
- mode: exports_external.enum(["none", "jwt", "oauth"]).default("none"),
24772
+ mode: exports_external.enum(["none", "jwt"]).default("none"),
24750
24773
  jwtSecret: exports_external.string().min(32).optional(),
24751
24774
  tokenTtlSeconds: exports_external.number().int().positive().default(86400)
24752
24775
  }).refine((c) => c.mode !== "jwt" || !!c.jwtSecret, {
@@ -24828,7 +24851,7 @@ var PlurConfigSchema = exports_external.object({
24828
24851
  apiPrefix: exports_external.string().default(""),
24829
24852
  timeoutMs: exports_external.number().int().positive().default(5000)
24830
24853
  });
24831
- var ServerConfigSchema = exports_external.object({
24854
+ var ServerConfigObject = exports_external.object({
24832
24855
  cacheDir: exports_external.string().default(".obsidian-tc"),
24833
24856
  vaults: exports_external.array(VaultConfigSchema).min(1),
24834
24857
  plur: PlurConfigSchema.optional(),
@@ -24842,6 +24865,16 @@ var ServerConfigSchema = exports_external.object({
24842
24865
  idempotencyTtlSeconds: exports_external.number().int().positive().default(86400),
24843
24866
  elicitTtlSeconds: exports_external.number().int().positive().default(300)
24844
24867
  });
24868
+ var ServerConfigSchema = ServerConfigObject.superRefine((cfg, ctx) => {
24869
+ const http = cfg.transports.http;
24870
+ if (http.enabled && cfg.auth.mode === "none" && !isLoopbackHost(http.host)) {
24871
+ ctx.addIssue({
24872
+ code: exports_external.ZodIssueCode.custom,
24873
+ path: ["transports", "http", "host"],
24874
+ message: `refusing to expose an unauthenticated server: transports.http.enabled is true with host "${http.host}" (non-loopback) while auth.mode is "none". Set auth.mode to "jwt" (with jwtSecret) or bind transports.http.host to a loopback address (127.0.0.1, ::1, localhost).`
24875
+ });
24876
+ }
24877
+ });
24845
24878
  // ../shared/src/morgiana.schema.ts
24846
24879
  var MORGIANA_EVENT_TYPES = [
24847
24880
  "tc.tool.call.completed",
@@ -25124,6 +25157,7 @@ class TokenBucket {
25124
25157
  var DEFAULT_THROTTLE_TIERS = {
25125
25158
  read: { perMinute: 600, burst: 100 },
25126
25159
  write: { perMinute: 60, burst: 20 },
25160
+ delete: { perMinute: 60, burst: 20 },
25127
25161
  bulk: { perMinute: 10, burst: 3 },
25128
25162
  execute: { perMinute: 5, burst: 1 },
25129
25163
  admin: { perMinute: 5, burst: 1 }
@@ -25134,8 +25168,15 @@ class RateLimiter {
25134
25168
  tiers;
25135
25169
  buckets = new Map;
25136
25170
  hits = new Map;
25137
- constructor(tiers = DEFAULT_THROTTLE_TIERS) {
25171
+ idleTtlMs;
25172
+ maxBuckets;
25173
+ sweepIntervalMs;
25174
+ lastSweepMs = null;
25175
+ constructor(tiers = DEFAULT_THROTTLE_TIERS, opts = {}) {
25138
25176
  this.tiers = tiers;
25177
+ this.idleTtlMs = opts.idleTtlMs ?? 600000;
25178
+ this.maxBuckets = opts.maxBuckets ?? 1e4;
25179
+ this.sweepIntervalMs = opts.sweepIntervalMs ?? 60000;
25139
25180
  }
25140
25181
  check(callerHashValue, scopeClass, vaultId, nowMs, n = 1) {
25141
25182
  const tier = this.tiers[scopeClass];
@@ -25143,20 +25184,26 @@ class RateLimiter {
25143
25184
  return { ok: true, scopeClass, retryAfterSeconds: 0, currentBurst: -1, currentRate: -1 };
25144
25185
  }
25145
25186
  const key = `${callerHashValue}|${scopeClass}|${vaultId}`;
25146
- let bucket = this.buckets.get(key);
25147
- if (!bucket) {
25148
- bucket = new TokenBucket({
25149
- capacity: tier.burst,
25150
- refillTokens: tier.perMinute,
25151
- intervalMs: INTERVAL_MS
25152
- });
25153
- this.buckets.set(key, bucket);
25187
+ let entry = this.buckets.get(key);
25188
+ if (!entry) {
25189
+ entry = {
25190
+ bucket: new TokenBucket({
25191
+ capacity: tier.burst,
25192
+ refillTokens: tier.perMinute,
25193
+ intervalMs: INTERVAL_MS
25194
+ }),
25195
+ fullRefillMs: tier.perMinute > 0 ? Math.ceil(tier.burst * INTERVAL_MS / tier.perMinute) : 0,
25196
+ lastSeenMs: nowMs
25197
+ };
25198
+ this.buckets.set(key, entry);
25154
25199
  }
25155
- const res = bucket.tryRemove(n, nowMs);
25200
+ entry.lastSeenMs = nowMs;
25201
+ const res = entry.bucket.tryRemove(n, nowMs);
25156
25202
  if (!res.ok) {
25157
25203
  const hk = `${vaultId}|${scopeClass}`;
25158
25204
  this.hits.set(hk, (this.hits.get(hk) ?? 0) + 1);
25159
25205
  }
25206
+ this.sweep(nowMs);
25160
25207
  return {
25161
25208
  ok: res.ok,
25162
25209
  scopeClass,
@@ -25165,6 +25212,26 @@ class RateLimiter {
25165
25212
  currentRate: tier.perMinute
25166
25213
  };
25167
25214
  }
25215
+ sweep(nowMs) {
25216
+ if (this.lastSweepMs !== null && nowMs - this.lastSweepMs < this.sweepIntervalMs)
25217
+ return;
25218
+ this.lastSweepMs = nowMs;
25219
+ for (const [key, e] of this.buckets) {
25220
+ if (nowMs - e.lastSeenMs >= this.idleTtlMs)
25221
+ this.buckets.delete(key);
25222
+ }
25223
+ if (this.buckets.size <= this.maxBuckets)
25224
+ return;
25225
+ const evictable = [...this.buckets.entries()].filter(([, e]) => nowMs - e.lastSeenMs >= e.fullRefillMs).sort((a, b) => a[1].lastSeenMs - b[1].lastSeenMs);
25226
+ for (const [key] of evictable) {
25227
+ if (this.buckets.size <= this.maxBuckets)
25228
+ break;
25229
+ this.buckets.delete(key);
25230
+ }
25231
+ }
25232
+ get bucketCount() {
25233
+ return this.buckets.size;
25234
+ }
25168
25235
  snapshot() {
25169
25236
  return [...this.hits.entries()].map(([k, hits]) => {
25170
25237
  const sep = k.indexOf("|");
@@ -25181,6 +25248,7 @@ function callStatusForError(code) {
25181
25248
  switch (code) {
25182
25249
  case "unauthorized":
25183
25250
  case "forbidden":
25251
+ case "acl_denied":
25184
25252
  case "elicit_required":
25185
25253
  case "throttled":
25186
25254
  return "denied";
@@ -25262,6 +25330,7 @@ class ToolRegistry {
25262
25330
  }
25263
25331
  switch (result2.error.code) {
25264
25332
  case "forbidden":
25333
+ case "acl_denied":
25265
25334
  this.relay(ctx.vaultId, "tc.acl.denied", data);
25266
25335
  break;
25267
25336
  case "overflow":
@@ -25409,7 +25478,7 @@ class ToolRegistry {
25409
25478
  const duration = Math.max(0, now() - start);
25410
25479
  audit("error", duration, 0, error.code);
25411
25480
  this.meter((m) => {
25412
- if (error.code === "forbidden")
25481
+ if (error.code === "forbidden" || error.code === "acl_denied")
25413
25482
  m.incAclDenied(ctx.vaultId, scopeClass, error.code);
25414
25483
  m.observeToolCall(ctx.vaultId, name, callStatusForError(error.code), duration / 1000, 0);
25415
25484
  });
@@ -39712,20 +39781,17 @@ async function resolveAuth(header, auth) {
39712
39781
  if (auth.mode === "none") {
39713
39782
  return { ok: true, caller: "http-local", scopes: new Set(["*"]) };
39714
39783
  }
39715
- if (auth.mode === "jwt") {
39716
- const token = bearer(header);
39717
- if (!token)
39718
- return { ok: false, status: 401, reason: "missing bearer token" };
39719
- if (!auth.jwtSecret)
39720
- return { ok: false, status: 500, reason: "jwt mode misconfigured: no secret" };
39721
- try {
39722
- const id = await verifyJwt(token, auth.jwtSecret);
39723
- return { ok: true, caller: id.caller, scopes: id.scopes };
39724
- } catch {
39725
- return { ok: false, status: 401, reason: "invalid or expired token" };
39726
- }
39784
+ const token = bearer(header);
39785
+ if (!token)
39786
+ return { ok: false, status: 401, reason: "missing bearer token" };
39787
+ if (!auth.jwtSecret)
39788
+ return { ok: false, status: 500, reason: "jwt mode misconfigured: no secret" };
39789
+ try {
39790
+ const id = await verifyJwt(token, auth.jwtSecret);
39791
+ return { ok: true, caller: id.caller, scopes: id.scopes };
39792
+ } catch {
39793
+ return { ok: false, status: 401, reason: "invalid or expired token" };
39727
39794
  }
39728
- return { ok: false, status: 501, reason: `auth mode '${auth.mode}' is not implemented` };
39729
39795
  }
39730
39796
  function createHttpApp(opts) {
39731
39797
  const app = new Hono2;
@@ -39777,7 +39843,7 @@ function createHttpApp(opts) {
39777
39843
  function startHttp(opts) {
39778
39844
  const app = createHttpApp(opts);
39779
39845
  return new Promise((resolve) => {
39780
- const server = serve({ fetch: app.fetch, hostname: opts.host, port: opts.port }, (info) => {
39846
+ const server = serve({ fetch: app.fetch, hostname: normalizeHostForBind(opts.host), port: opts.port }, (info) => {
39781
39847
  resolve({
39782
39848
  port: info.port,
39783
39849
  close: () => new Promise((done) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "obsidian-tc",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "MCP server for Obsidian — comprehensive tool surface for humans and autonomous agents",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -19,8 +19,8 @@
19
19
  "dependencies": {
20
20
  "@hono/node-server": "^2.0.5",
21
21
  "@modelcontextprotocol/sdk": "^1.0.0",
22
- "@the-40-thieves/obsidian-tc-native": "1.0.1",
23
- "@the-40-thieves/obsidian-tc-shared": "1.0.1",
22
+ "@the-40-thieves/obsidian-tc-native": "1.0.2",
23
+ "@the-40-thieves/obsidian-tc-shared": "1.0.2",
24
24
  "@opentelemetry/api": "^1.9.1",
25
25
  "@opentelemetry/exporter-trace-otlp-http": "^0.219.0",
26
26
  "@opentelemetry/resources": "^2.8.0",