@setluca/mcp 0.5.1 → 0.6.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
@@ -10,6 +10,75 @@ input schema, or changing what a tool does to a workspace.
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.6.0] - 2026-10-24
14
+
15
+ ### Breaking
16
+
17
+ - `luca_knowledge_approve_suggestion` is gone, which leaves 223 tools. Luca
18
+ made approving a profile suggestion session-only, because approval writes
19
+ the coach profile that every later draft reads and needs a person in the
20
+ loop. Since that release, every call from an API key or OAuth token got 403,
21
+ so no call that worked is lost. Coaches approve suggestions in the app;
22
+ `luca_knowledge_suggestions` lists them and
23
+ `luca_knowledge_reject_suggestion` still turns one down.
24
+
25
+ ### Changed
26
+
27
+ - Contracts are pinned to Luca `621b92a`, the v0.4.0 production release.
28
+ `luca_bookings_availability` and `luca_bookings_managed_availability`
29
+ document that `start` defaults to now and `end` to seven days after `start`.
30
+
31
+ ## [0.5.2] - 2026-10-23
32
+
33
+ ### Fixed
34
+
35
+ - Connections keep working past their first hour. The 401 challenge now asks
36
+ for `offline_access` along with the luca scopes, and the authorization server
37
+ issues a refresh token only when the grant carries it. Claude and ChatGPT ask
38
+ for exactly what the challenge names, so until now neither got a refresh
39
+ token and each connection lapsed with its one-hour access token.
40
+ - claude.ai can connect. `serverInfo` no longer carries `icons`, which makes
41
+ claude.ai abandon the session after `initialize`
42
+ ([anthropics/claude-ai-mcp#474](https://github.com/anthropics/claude-ai-mcp/issues/474)).
43
+ The server card at `/.well-known/mcp/server-card.json` still lists them.
44
+ - `luca_conversations_media_retry` and `luca_review_queue_media_retry` set
45
+ `openWorldHint: true`. A retried transcription sends the voice note to
46
+ OpenRouter's speech-to-text. Neither reaches a lead, so neither needs
47
+ `confirm: true`.
48
+ - `luca_analytics_rollup` returns per-campaign analytics. It looked for
49
+ campaigns under `items`, but the API lists them under `campaigns`, so its
50
+ `campaigns` section had always come back empty.
51
+ - `luca_conversations_send_status`, `luca_bookings_managed_availability`, and
52
+ `luca_webhooks_events_list` require `query`. Each route has a required query
53
+ parameter, so a call without `query` already failed with the API's 400; the
54
+ tool schema now says so. No call that worked before is rejected.
55
+ - `luca_leads_draft` and `luca_draft_reply` set `destructiveHint: true` and
56
+ `openWorldHint: true`. Drafting again replaces the lead's earlier unapproved
57
+ draft from these tools, and the drafter sends the conversation to
58
+ OpenRouter's language model, the same reach that makes a transcription
59
+ retry open-world. Neither sends to the lead, so neither needs
60
+ `confirm: true`.
61
+ - Descriptions that said more than the API does:
62
+ - `luca_campaigns_generate` builds its plan with fixed rules, not an AI
63
+ model;
64
+ - `luca_leads_update` cannot set a score;
65
+ - `luca_reschedule_call`, `luca_bookings_update`, and `luca_bookings_cancel`
66
+ say that a Google Calendar booking's calendar emails the lead, and Cal.com
67
+ may too, where the reschedule tool had said no message is sent.
68
+
69
+ ### Changed
70
+
71
+ - The 401 challenge asks for every scope instead of `luca:read luca:draft`.
72
+ Claude requests what the challenge names, and the consent screen offers only
73
+ what was requested, so a coach could not let Claude send or book. The consent
74
+ screen still starts at read and draft, with every higher scope unticked.
75
+ - Built on Effect 4.0.6, up from 4.0.0. Its JSON Schema output flattens a
76
+ nested `anyOf`, which reshapes `luca_close_call_loop`'s output schema without
77
+ changing what it accepts. No other tool's schema changed.
78
+ - `typecheck` and `build` run the Effect language service
79
+ ([`@effect/tsgo`](https://github.com/Effect-TS/tsgo)) and fail on any of its
80
+ errors, warnings, or suggestions.
81
+
13
82
  ## [0.5.1] - 2026-10-22
14
83
 
15
84
  ### Fixed
@@ -1,9 +1,8 @@
1
1
  import * as Arr from "effect/Array";
2
2
  import * as Effect from "effect/Effect";
3
- import * as Option from "effect/Option";
4
3
  import * as R from "effect/Record";
5
- import * as Schema from "effect/Schema";
6
4
  import { LucaApi } from "./http.js";
5
+ import { recordIdsOf } from "./pagination.js";
7
6
  /**
8
7
  * Argument completion for the two ids a coach types by hand. MCP completes
9
8
  * prompt arguments and resource-template variables; there is no tool-argument
@@ -28,27 +27,6 @@ const COMPLETION_SOURCES = {
28
27
  * array it gets back, so a source with more ids than this page cannot say so.
29
28
  */
30
29
  const COMPLETION_PAGE_SIZE = 100;
31
- /**
32
- * Reads a list page down to the ids in one array field, ignoring every other
33
- * field on the page and every other field on a record. A page that arrives in
34
- * any other shape decodes to nothing, which the caller offers as no
35
- * suggestions.
36
- */
37
- function idsDecoder(itemsKey) {
38
- const decode = Schema.decodeUnknownOption(Schema.Struct({
39
- [itemsKey]: Schema.Array(Schema.Struct({ id: Schema.String })),
40
- }));
41
- return (page) => Option.match(decode(page), {
42
- onNone: () => [],
43
- // Read through the values rather than the key: the struct declares
44
- // exactly one field, so a page that decoded carries that field's records
45
- // and nothing else. Indexing by `itemsKey` would say the same thing while
46
- // reading as though the key might be missing.
47
- onSome: (decoded) => R.values(decoded)
48
- .flat()
49
- .map((record) => record.id),
50
- });
51
- }
52
30
  /**
53
31
  * The completion callbacks for one server, closed over the layer that serves
54
32
  * the reads.
@@ -62,14 +40,9 @@ export function completionCallbacks(operationById, lucaLayer) {
62
40
  const operation = operationById(operationId);
63
41
  // A completion source has to be a list route: the ids come out of the
64
42
  // page's own array field, and a route that does not paginate carries none.
65
- // Naming one that cannot serve them is a wiring mistake, so it is refused
66
- // while the server is being built rather than on a keystroke.
67
- const itemsKey = operation.pageContract?.itemsKey;
68
- if (itemsKey === undefined) {
69
- // oxlint-disable-next-line effect/avoid-untagged-errors -- raw failure on an invariant or interop path; not a caller-recoverable domain failure
70
- throw new Error(`Completion source ${operationId} carries no list of records to read ids from`);
71
- }
72
- const idsOn = idsDecoder(itemsKey);
43
+ // `recordIdsOf` refuses any other while the server is being built rather
44
+ // than on a keystroke.
45
+ const idsOn = recordIdsOf(operation);
73
46
  return (value) => Effect.runPromise(Effect.gen(function* () {
74
47
  const api = yield* LucaApi;
75
48
  const page = yield* api.request({
@@ -81,7 +54,7 @@ export function completionCallbacks(operationById, lucaLayer) {
81
54
  // Ids are UUIDs, which print in either case, and a client may type
82
55
  // the capitals it copied from elsewhere.
83
56
  const typed = (value ?? "").toLowerCase();
84
- return idsOn(page).filter((id) => id.toLowerCase().startsWith(typed));
57
+ return idsOn(page, COMPLETION_PAGE_SIZE).filter((id) => id.toLowerCase().startsWith(typed));
85
58
  }).pipe(Effect.provide(lucaLayer),
86
59
  // A failed read answers with no suggestions. A defect is left alone:
87
60
  // that one is our own bug, and an empty list is exactly how it would
package/dist/errors.js CHANGED
@@ -18,7 +18,7 @@ export class LucaDecodeError extends Schema.TaggedError()("LucaDecodeError", {
18
18
  }) {
19
19
  }
20
20
  export class LucaHttpError extends Schema.TaggedError()("LucaHttpError", {
21
- status: Schema.Number,
21
+ status: Schema.Int,
22
22
  statusText: Schema.String,
23
23
  body: Schema.Json,
24
24
  requestId: Schema.optionalKey(Schema.String),
@@ -26,7 +26,7 @@ export class LucaHttpError extends Schema.TaggedError()("LucaHttpError", {
26
26
  * How long the server asked us to wait, from its `Retry-After` header. Absent
27
27
  * when the header was missing or unreadable.
28
28
  */
29
- retryAfterMs: Schema.optionalKey(Schema.Number),
29
+ retryAfterMs: Schema.optionalKey(Schema.Finite),
30
30
  }) {
31
31
  }
32
32
  /**