@setluca/mcp 0.1.1 → 0.2.1
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 +110 -0
- package/README.md +178 -80
- package/dist/annotations.js +45 -0
- package/dist/completions.js +110 -0
- package/dist/config.js +55 -29
- package/dist/errors.js +56 -15
- package/dist/generated/openapi-inputs.js +4324 -1429
- package/dist/generated/openapi-outputs.js +21992 -8516
- package/dist/generated/route-catalog.js +1480 -0
- package/dist/http.js +97 -103
- package/dist/index.js +16 -10
- package/dist/oauth-resolver.js +59 -37
- package/dist/observability.js +12 -4
- package/dist/openapi-zod.js +138 -72
- package/dist/operations/catalog.js +50 -0
- package/dist/operations/groups/analytics.js +122 -0
- package/dist/operations/groups/bookings.js +189 -0
- package/dist/operations/groups/broadcasts.js +106 -0
- package/dist/operations/groups/cadences.js +14 -0
- package/dist/operations/groups/callEvents.js +131 -0
- package/dist/operations/groups/campaigns.js +132 -0
- package/dist/operations/groups/capabilities.js +8 -0
- package/dist/operations/groups/coach.js +43 -0
- package/dist/operations/groups/conversations.js +48 -0
- package/dist/operations/groups/insights.js +33 -0
- package/dist/operations/groups/integrations.js +80 -0
- package/dist/operations/groups/knowledge.js +34 -0
- package/dist/operations/groups/leads.js +101 -0
- package/dist/operations/groups/learning.js +24 -0
- package/dist/operations/groups/memory.js +27 -0
- package/dist/operations/groups/reports.js +14 -0
- package/dist/operations/groups/reviewQueue.js +57 -0
- package/dist/operations/groups/safety.js +8 -0
- package/dist/operations/groups/usage.js +13 -0
- package/dist/operations/groups/voice.js +14 -0
- package/dist/operations/groups/webhooks.js +76 -0
- package/dist/operations/manifest.js +40 -0
- package/dist/operations/registry.js +331 -0
- package/dist/operations.js +11 -780
- package/dist/optional-field.js +12 -0
- package/dist/page-shape.js +62 -0
- package/dist/pagination.js +165 -0
- package/dist/prompts.js +89 -8
- package/dist/provenance.js +18 -2
- package/dist/remote.js +80 -49
- package/dist/resilience.js +127 -0
- package/dist/resources.js +17 -2
- package/dist/scopes.js +154 -0
- package/dist/serialization.js +36 -0
- package/dist/server.js +258 -316
- package/dist/task-tools/analytics.js +168 -0
- package/dist/task-tools/bookings.js +157 -0
- package/dist/task-tools/cadences.js +65 -0
- package/dist/task-tools/calls.js +202 -0
- package/dist/task-tools/inbox.js +120 -0
- package/dist/task-tools/leads.js +123 -0
- package/dist/task-tools/runtime.js +92 -0
- package/dist/task-tools.js +21 -0
- package/dist/tool-cache.js +39 -0
- package/dist/tool.js +228 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/version.js +14 -0
- package/package.json +38 -34
package/dist/config.js
CHANGED
|
@@ -1,47 +1,73 @@
|
|
|
1
|
-
import { Context, Effect, Layer } from "effect";
|
|
1
|
+
import { Context, Effect, Layer, Match, Option } from "effect";
|
|
2
|
+
import * as z from "zod/v4";
|
|
2
3
|
import { LucaConfigError } from "./errors.js";
|
|
4
|
+
import { optionalField } from "./optional-field.js";
|
|
3
5
|
const TRAILING_SLASHES = /\/+$/;
|
|
6
|
+
/** How long one HTTP attempt may run before it is cancelled and retried. */
|
|
7
|
+
export const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
4
8
|
export const LucaConfig = Context.GenericTag("@luca/mcp/LucaConfig");
|
|
5
9
|
function nonEmpty(value) {
|
|
6
|
-
|
|
7
|
-
|
|
10
|
+
// An all-whitespace variable is the same as an unset one, so it collapses to
|
|
11
|
+
// undefined and lets every caller's `??` fallback take over.
|
|
12
|
+
return value?.trim() || undefined;
|
|
8
13
|
}
|
|
9
14
|
function normalizeBaseUrl(value) {
|
|
10
15
|
const raw = nonEmpty(value) ?? "https://api.setluca.com";
|
|
11
16
|
return raw.replace(TRAILING_SLASHES, "");
|
|
12
17
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Every spelling of LUCA_AUTH_HEADER we accept, mapped to the header the client
|
|
20
|
+
* actually sends. "bearer" is here because it is what people write when they
|
|
21
|
+
* mean the Authorization header, and refusing it would be a papercut.
|
|
22
|
+
*/
|
|
23
|
+
const AUTH_HEADER_ALIASES = new Map([
|
|
24
|
+
["x-api-key", "x-api-key"],
|
|
25
|
+
["authorization", "authorization"],
|
|
26
|
+
["bearer", "authorization"],
|
|
27
|
+
]);
|
|
28
|
+
/**
|
|
29
|
+
* The per-attempt timeout a variable asks for, absent when it is unset or is
|
|
30
|
+
* not a positive whole number of milliseconds. Absent means the caller's
|
|
31
|
+
* default applies, so a typo degrades to the default rather than to no timeout
|
|
32
|
+
* at all.
|
|
33
|
+
*/
|
|
34
|
+
export function parseTimeoutMs(value) {
|
|
35
|
+
const parsed = z.coerce
|
|
36
|
+
.number()
|
|
37
|
+
.int()
|
|
38
|
+
.positive()
|
|
39
|
+
.safeParse(nonEmpty(value) ?? undefined);
|
|
40
|
+
return parsed.success ? parsed.data : undefined;
|
|
24
41
|
}
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
42
|
+
function parseAuthHeader(value) {
|
|
43
|
+
const alias = nonEmpty(value)?.toLowerCase() ?? "x-api-key";
|
|
44
|
+
return Match.value(AUTH_HEADER_ALIASES.get(alias)).pipe(Match.when(Match.undefined, () => {
|
|
28
45
|
throw new LucaConfigError({
|
|
29
|
-
message: "
|
|
46
|
+
message: "LUCA_AUTH_HEADER must be x-api-key or authorization",
|
|
30
47
|
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
48
|
+
}), Match.orElse((header) => header));
|
|
49
|
+
}
|
|
50
|
+
export function loadConfig(env = process.env) {
|
|
51
|
+
const apiKey = Option.fromNullable(nonEmpty(env.LUCA_API_KEY) ?? nonEmpty(env.LUCA_API_TOKEN)).pipe(Option.getOrThrowWith(() => new LucaConfigError({
|
|
52
|
+
message: "Set LUCA_API_KEY to a Luca developer API key.",
|
|
53
|
+
})));
|
|
34
54
|
return {
|
|
35
55
|
apiBaseUrl: normalizeBaseUrl(env.LUCA_API_BASE_URL),
|
|
36
56
|
apiKey,
|
|
37
57
|
authHeader: parseAuthHeader(env.LUCA_AUTH_HEADER),
|
|
38
|
-
...(workspaceId
|
|
39
|
-
...(workspaceSlug
|
|
58
|
+
...optionalField("workspaceId", nonEmpty(env.LUCA_WORKSPACE_ID)),
|
|
59
|
+
...optionalField("workspaceSlug", nonEmpty(env.LUCA_WORKSPACE_SLUG)),
|
|
60
|
+
...optionalField("requestTimeoutMs", parseTimeoutMs(env.LUCA_REQUEST_TIMEOUT_MS)),
|
|
40
61
|
};
|
|
41
62
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Every way `loadConfig` can fail has to reach the caller as a `LucaConfigError`
|
|
65
|
+
* so the layer's error channel stays one type. A config error passes through
|
|
66
|
+
* untouched. Anything else is wrapped rather than escaping as an untyped
|
|
67
|
+
* defect, whether that is a getter on the environment that throws or a future
|
|
68
|
+
* caller of this helper raising something unexpected.
|
|
69
|
+
*/
|
|
70
|
+
export function toConfigError(cause) {
|
|
71
|
+
return Match.value(cause).pipe(Match.when(Match.instanceOf(LucaConfigError), (error) => error), Match.orElse((other) => new LucaConfigError({ message: String(other) })));
|
|
72
|
+
}
|
|
73
|
+
export const LucaConfigLive = Layer.effect(LucaConfig, Effect.try({ try: () => loadConfig(), catch: toConfigError }));
|
package/dist/errors.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { Data } from "effect";
|
|
1
|
+
import { Data, Match, Option } from "effect";
|
|
2
|
+
import * as z from "zod/v4";
|
|
3
|
+
import { toJsonPayload } from "./serialization.js";
|
|
2
4
|
export class LucaConfigError extends Data.TaggedError("LucaConfigError") {
|
|
3
5
|
}
|
|
4
6
|
export class LucaNetworkError extends Data.TaggedError("LucaNetworkError") {
|
|
@@ -7,19 +9,58 @@ export class LucaDecodeError extends Data.TaggedError("LucaDecodeError") {
|
|
|
7
9
|
}
|
|
8
10
|
export class LucaHttpError extends Data.TaggedError("LucaHttpError") {
|
|
9
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* One attempt ran past the request timeout and was cancelled. It is its own
|
|
14
|
+
* error rather than a network error because a caller can retry it safely under
|
|
15
|
+
* the same rules but cannot tell from a network failure whether the request
|
|
16
|
+
* reached the API.
|
|
17
|
+
*/
|
|
18
|
+
export class LucaTimeoutError extends Data.TaggedError("LucaTimeoutError") {
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A tool was called with arguments that are not a JSON object. The MCP SDK
|
|
22
|
+
* validates against the tool's own input schema before the handler runs, so a
|
|
23
|
+
* real client cannot reach this. It exists so no code ahead of `runPromise` can
|
|
24
|
+
* throw where the error channel cannot see it.
|
|
25
|
+
*/
|
|
26
|
+
export class LucaToolInputError extends Data.TaggedError("LucaToolInputError") {
|
|
27
|
+
}
|
|
28
|
+
const DECODE_BODY_EXCERPT = 500;
|
|
29
|
+
function formatDecodeError(error) {
|
|
30
|
+
return `${error.message}: ${error.body.slice(0, DECODE_BODY_EXCERPT)}`;
|
|
31
|
+
}
|
|
32
|
+
function formatHttpError(error) {
|
|
33
|
+
const request = Option.fromNullable(error.requestId).pipe(Option.match({
|
|
34
|
+
onNone: () => "",
|
|
35
|
+
onSome: (requestId) => ` requestId=${requestId}`,
|
|
36
|
+
}));
|
|
37
|
+
return `Luca API returned ${error.status} ${error.statusText}${request}: ${toJsonPayload(error.body)}`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A value that carries no message of its own, rendered for a human. A string is
|
|
41
|
+
* handed back as it was thrown, since quoting it would put the reader a step
|
|
42
|
+
* further from the value than the throw site was. Everything else comes back as
|
|
43
|
+
* JSON rather than "[object Object]". Numbers and booleans need no case of
|
|
44
|
+
* their own: JSON renders each of them exactly as `String` would.
|
|
45
|
+
*/
|
|
46
|
+
function formatJsonValue(error) {
|
|
47
|
+
const text = z.string().safeParse(error);
|
|
48
|
+
return text.success ? text.data : toJsonPayload(error);
|
|
49
|
+
}
|
|
50
|
+
/** Any failure this server can hand a client, as one line of text. */
|
|
10
51
|
export function formatError(error) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return error
|
|
52
|
+
return error instanceof Error ? formatThrown(error) : formatJsonValue(error);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A thrown value rendered for a human. The two structured errors get their own
|
|
56
|
+
* rendering; every other tagged error carries a `message`, and one branch
|
|
57
|
+
* covers all of them rather than a case per tag that would all say the same
|
|
58
|
+
* thing.
|
|
59
|
+
*
|
|
60
|
+
* The match is on the class, not on `_tag`. This takes any `Error`, including
|
|
61
|
+
* one thrown by a dependency that carries no tag at all, so there is no tagged
|
|
62
|
+
* union here for `Match.tag` to narrow.
|
|
63
|
+
*/
|
|
64
|
+
function formatThrown(error) {
|
|
65
|
+
return Match.value(error).pipe(Match.when(Match.instanceOf(LucaDecodeError), formatDecodeError), Match.when(Match.instanceOf(LucaHttpError), formatHttpError), Match.orElse((thrown) => thrown.message));
|
|
25
66
|
}
|