@setluca/mcp 0.2.0 → 0.3.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 +426 -0
- package/LICENSE +21 -0
- package/README.md +19 -258
- package/dist/annotations.js +35 -20
- package/dist/client-address.js +64 -0
- package/dist/completions.js +44 -27
- package/dist/config.js +89 -38
- package/dist/errors.js +99 -22
- package/dist/fields.js +68 -0
- package/dist/fixed-window.js +46 -0
- package/dist/generated/openapi-inputs.js +216 -5820
- package/dist/generated/openapi-outputs.js +19014 -14001
- package/dist/generated/route-catalog.js +80 -0
- package/dist/http.js +108 -44
- package/dist/index.js +4 -1
- package/dist/oauth-challenge.js +74 -0
- package/dist/oauth-resolver.js +58 -61
- package/dist/observability.js +31 -16
- package/dist/openapi-schema.js +175 -0
- package/dist/operations/catalog.js +17 -0
- package/dist/operations/groups/analytics.js +20 -10
- package/dist/operations/groups/bookings.js +43 -15
- package/dist/operations/groups/broadcasts.js +29 -58
- package/dist/operations/groups/cadences.js +3 -1
- package/dist/operations/groups/callEvents.js +19 -72
- package/dist/operations/groups/campaigns.js +28 -32
- package/dist/operations/groups/channels.js +23 -0
- package/dist/operations/groups/conversations.js +14 -25
- package/dist/operations/groups/insights.js +4 -0
- package/dist/operations/groups/integrations.js +15 -17
- package/dist/operations/groups/knowledge.js +5 -1
- package/dist/operations/groups/leads.js +22 -23
- package/dist/operations/groups/learning.js +13 -1
- package/dist/operations/groups/memory.js +4 -6
- package/dist/operations/groups/reports.js +1 -1
- package/dist/operations/groups/reviewQueue.js +17 -14
- package/dist/operations/groups/safety.js +1 -1
- package/dist/operations/groups/voice.js +0 -1
- package/dist/operations/groups/webhooks.js +16 -4
- package/dist/operations/manifest.js +9 -8
- package/dist/operations/registry.js +110 -116
- package/dist/operations.js +6 -5
- package/dist/optional-field.js +15 -3
- package/dist/page-contract.js +74 -0
- package/dist/pagination.js +149 -68
- package/dist/prompts.js +139 -88
- package/dist/provenance.js +2 -2
- package/dist/rate-limit.js +40 -0
- package/dist/remote.js +145 -65
- package/dist/resilience.js +120 -55
- package/dist/resolvers.js +125 -0
- package/dist/resources.js +17 -2
- package/dist/scopes.js +207 -0
- package/dist/serialization.js +32 -22
- package/dist/server.js +174 -191
- package/dist/standard-schema.js +215 -0
- package/dist/task-tools/analytics.js +145 -86
- package/dist/task-tools/bookings.js +176 -86
- package/dist/task-tools/cadences.js +30 -45
- package/dist/task-tools/calls.js +132 -125
- package/dist/task-tools/inbox.js +89 -53
- package/dist/task-tools/leads.js +49 -77
- package/dist/task-tools/runtime.js +80 -20
- package/dist/task-tools.js +1 -1
- package/dist/token-verification.js +42 -0
- package/dist/tool-cache.js +3 -2
- package/dist/tool.js +155 -100
- package/dist/version.js +1 -1
- package/package.json +40 -18
- package/dist/openapi-zod.js +0 -333
- package/dist/page-shape.js +0 -47
- package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/tool.js
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as
|
|
3
|
-
import
|
|
4
|
-
import
|
|
1
|
+
import * as Cause from "effect/Cause";
|
|
2
|
+
import * as Duration from "effect/Duration";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Exit from "effect/Exit";
|
|
5
|
+
import * as Option from "effect/Option";
|
|
6
|
+
import * as Schema from "effect/Schema";
|
|
7
|
+
import { operationAnnotationSource, toolAnnotations } from "./annotations.js";
|
|
8
|
+
import { formatError, LucaConfirmationRequired, LucaToolInputError, } from "./errors.js";
|
|
9
|
+
import { confirmInput } from "./fields.js";
|
|
5
10
|
import { LucaApi } from "./http.js";
|
|
11
|
+
import { authChallengeMeta } from "./oauth-challenge.js";
|
|
6
12
|
import { logAgentSurfaceEvent } from "./observability.js";
|
|
13
|
+
import { openApiToolOutputFields } from "./openapi-schema.js";
|
|
14
|
+
import { needsConfirmation, operationById, } from "./operations.js";
|
|
7
15
|
import { optionalField } from "./optional-field.js";
|
|
8
16
|
import { paginatedProgram } from "./pagination.js";
|
|
9
17
|
import { provenanceFields, UNTRUSTED_CONTENT_NOTE } from "./provenance.js";
|
|
10
|
-
import {
|
|
18
|
+
import { oauthScopeForOperation, oauthScopeForTool, } from "./scopes.js";
|
|
19
|
+
import { JsonObject, toJsonText } from "./serialization.js";
|
|
20
|
+
import { toolInputSchema, toolSchema } from "./standard-schema.js";
|
|
21
|
+
import { taskRequest } from "./task-tools/runtime.js";
|
|
11
22
|
import { buildCache } from "./tool-cache.js";
|
|
12
23
|
/**
|
|
13
24
|
* Each derivation below is cached on the identity of the catalog entry it comes
|
|
@@ -17,75 +28,99 @@ import { buildCache } from "./tool-cache.js";
|
|
|
17
28
|
*/
|
|
18
29
|
const operationTools = buildCache();
|
|
19
30
|
const taskTools = buildCache();
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
const inputSchemas = buildCache();
|
|
32
|
+
const outputSchemas = buildCache();
|
|
33
|
+
/**
|
|
34
|
+
* A tool's fields as one Standard Schema object, built once per fields object.
|
|
35
|
+
* Building one renders its JSON Schema, which is the expensive part. Inputs and
|
|
36
|
+
* outputs are cached apart because only an input refuses undeclared keys.
|
|
37
|
+
*/
|
|
38
|
+
function inputObjectSchema(fields) {
|
|
39
|
+
return inputSchemas.get(fields, () => toolInputSchema(fields));
|
|
40
|
+
}
|
|
41
|
+
function outputObjectSchema(fields) {
|
|
42
|
+
return outputSchemas.get(fields, () => toolSchema(fields));
|
|
24
43
|
}
|
|
25
44
|
export function fromOperation(operation) {
|
|
26
45
|
return operationTools.get(operation, () => buildFromOperation(operation));
|
|
27
46
|
}
|
|
28
47
|
function buildFromOperation(operation) {
|
|
29
48
|
return {
|
|
49
|
+
...operationAnnotationSource(operation),
|
|
30
50
|
name: operation.toolName,
|
|
31
|
-
title: operation.title,
|
|
32
51
|
description: operationDescription(operation),
|
|
33
52
|
inputSchema: operation.inputSchema,
|
|
34
53
|
outputSchema: operation.outputSchema,
|
|
35
|
-
readOnly: operation.method === "GET",
|
|
36
|
-
mutatesExisting: mutatesExisting(operation.method),
|
|
37
|
-
confirmRequired: operation.confirmRequired,
|
|
38
|
-
idempotencyRequired: operation.idempotencyRequired,
|
|
39
54
|
untrustedContent: operation.untrustedContent,
|
|
55
|
+
oauthScope: oauthScopeForOperation(operation),
|
|
40
56
|
run: (input) => operationProgram(operation, input),
|
|
41
57
|
};
|
|
42
58
|
}
|
|
59
|
+
export function fromTaskTool(tool) {
|
|
60
|
+
return taskTools.get(tool, () => buildFromTaskTool(tool));
|
|
61
|
+
}
|
|
62
|
+
function confirmFor(tool, composed) {
|
|
63
|
+
if (composed.some((operation) => operation.confirm === "always")) {
|
|
64
|
+
return "always";
|
|
65
|
+
}
|
|
66
|
+
const conditional = composed.some((operation) => operation.confirm !== undefined);
|
|
67
|
+
return conditional ? (tool.confirm ?? "always") : tool.confirm;
|
|
68
|
+
}
|
|
43
69
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* between this module and the one that registers with it.
|
|
70
|
+
* Every safety flag on a task tool comes from the operations it composes, so a
|
|
71
|
+
* tool cannot claim to be safer than the routes it calls.
|
|
47
72
|
*/
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
73
|
+
function buildFromTaskTool(tool) {
|
|
74
|
+
const composed = tool.composes.map(operationById);
|
|
75
|
+
const untrustedContent = tool.untrustedContent === true ||
|
|
76
|
+
composed.some((operation) => operation.untrustedContent);
|
|
77
|
+
// One approval covers the whole call. A composed write gated for every call
|
|
78
|
+
// gates the task tool every time. A composed write gated only for some inputs
|
|
79
|
+
// cannot be evaluated here, because the tool's own input is not the input
|
|
80
|
+
// that write receives. The tool then has to name its own narrower gate, and
|
|
81
|
+
// without one it is gated every time.
|
|
82
|
+
const confirm = confirmFor(tool, composed);
|
|
52
83
|
return {
|
|
53
84
|
name: tool.name,
|
|
54
85
|
title: tool.title,
|
|
55
86
|
description: tool.description,
|
|
56
|
-
inputSchema:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// A task tool
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
87
|
+
inputSchema: confirm === undefined
|
|
88
|
+
? tool.inputSchema
|
|
89
|
+
: { ...tool.inputSchema, ...confirmInput },
|
|
90
|
+
outputSchema: {
|
|
91
|
+
...openApiToolOutputFields({ body: null, untrustedContent }),
|
|
92
|
+
result: tool.outputSchema,
|
|
93
|
+
},
|
|
94
|
+
readOnly: composed.every((operation) => operation.method === "GET"),
|
|
95
|
+
// A task tool overwrites or removes existing state when any write it
|
|
96
|
+
// composes does.
|
|
97
|
+
mutatesExisting: composed.some((operation) => operation.mutatesExisting),
|
|
98
|
+
...optionalField("confirm", confirm),
|
|
99
|
+
untrustedContent,
|
|
100
|
+
// A task tool reaches as far as the furthest operation it composes.
|
|
101
|
+
openWorld: composed.some((operation) => operation.openWorld),
|
|
102
|
+
oauthScope: oauthScopeForTool(composed),
|
|
103
|
+
run: (input) => tool.run((id, parts) => Effect.flatMap(LucaApi, (api) => api.request(taskRequest(operationById(id), input, parts))), input),
|
|
72
104
|
};
|
|
73
105
|
}
|
|
74
|
-
|
|
75
|
-
* `toolset` arrives as a parameter rather than a field on the tool: the same
|
|
76
|
-
* `LucaOperation` serves both surfaces, so a tool has no business claiming to
|
|
77
|
-
* know which one registered it.
|
|
78
|
-
*/
|
|
79
|
-
export function registerTool(server, tool, lucaLayer, toolset) {
|
|
106
|
+
export function registerTool(server, tool, lucaLayer, surface) {
|
|
80
107
|
server.registerTool(tool.name, {
|
|
81
108
|
title: tool.title,
|
|
82
109
|
description: tool.description,
|
|
83
|
-
inputSchema:
|
|
84
|
-
|
|
85
|
-
? undefined
|
|
86
|
-
: objectSchema(tool.outputSchema)),
|
|
110
|
+
inputSchema: inputObjectSchema(tool.inputSchema),
|
|
111
|
+
outputSchema: outputObjectSchema(tool.outputSchema),
|
|
87
112
|
annotations: toolAnnotations(tool),
|
|
88
|
-
|
|
113
|
+
// ChatGPT reads this to know a tool needs a linked account, and which
|
|
114
|
+
// scope to ask for when it does.
|
|
115
|
+
_meta: {
|
|
116
|
+
securitySchemes: [{ type: "oauth2", scopes: [tool.oauthScope] }],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
// The client's cancellation interrupts the program, which aborts the
|
|
120
|
+
// request it has in flight.
|
|
121
|
+
(input, ctx) => Effect.runPromise(toolResult(tool, lucaLayer, surface, input), {
|
|
122
|
+
signal: ctx.mcpReq.signal,
|
|
123
|
+
}));
|
|
89
124
|
}
|
|
90
125
|
/**
|
|
91
126
|
* The call's arguments as a JSON object, failing into the error channel rather
|
|
@@ -93,59 +128,76 @@ export function registerTool(server, tool, lucaLayer, toolset) {
|
|
|
93
128
|
* the handler runs, so a real client never lands here; the point is that no
|
|
94
129
|
* code ahead of `runPromise` can throw where the error channel cannot see it.
|
|
95
130
|
*/
|
|
131
|
+
const decodeToolInput = Schema.decodeUnknownEffect(JsonObject);
|
|
132
|
+
// oxlint-disable-next-line anti-slop/no-unknown-parameters -- the SDK hands over the arguments its own schema decoded, typed loosely; this is where they become a JSON object.
|
|
96
133
|
function parseToolInput(input) {
|
|
97
|
-
return Effect.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
message: "Tool arguments must be a JSON object",
|
|
101
|
-
}),
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
function ended(outcome, result) {
|
|
105
|
-
return { outcome, result };
|
|
134
|
+
return decodeToolInput(input).pipe(Effect.mapError(() => new LucaToolInputError({
|
|
135
|
+
message: "Tool arguments must be a JSON object",
|
|
136
|
+
})));
|
|
106
137
|
}
|
|
107
138
|
/**
|
|
108
|
-
* A tool call's result, confirmation gate included. Every way the call can end
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* nothing reaches the transport as an unhandled rejection.
|
|
112
|
-
*
|
|
113
|
-
* The log line sits after the defect handler, so a call produces exactly one
|
|
114
|
-
* line whichever way it ended.
|
|
139
|
+
* A tool call's result, confirmation gate included. Every way the call can end
|
|
140
|
+
* leaves as a tool result, so nothing reaches the transport as an unhandled
|
|
141
|
+
* rejection, and every way logs exactly one line.
|
|
115
142
|
*/
|
|
116
|
-
function toolResult(tool, lucaLayer,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
143
|
+
function toolResult(tool, lucaLayer, surface,
|
|
144
|
+
// oxlint-disable-next-line anti-slop/no-unknown-parameters -- input is the same raw MCP JSON-RPC tool-call arguments registerTool's server.registerTool callback hands in; parseToolInput below is what actually validates it.
|
|
145
|
+
input) {
|
|
146
|
+
const logCall = (outcome, elapsed) =>
|
|
147
|
+
// A defect's own message never lands here: it can carry arbitrary thrown
|
|
148
|
+
// text derived from the call's input. The client still sees it in the
|
|
149
|
+
// tool result.
|
|
150
|
+
logAgentSurfaceEvent("mcp.tool.called", {
|
|
151
|
+
toolName: tool.name,
|
|
152
|
+
toolset: surface.toolset,
|
|
153
|
+
outcome,
|
|
154
|
+
durationMs: elapsed === undefined
|
|
155
|
+
? undefined
|
|
156
|
+
: Math.round(Duration.toMillis(elapsed)),
|
|
157
|
+
});
|
|
158
|
+
return parseToolInput(input).pipe(Effect.flatMap((parsed) => needsConfirmation(tool.confirm, parsed) && parsed.confirm !== true
|
|
159
|
+
? Effect.fail(new LucaConfirmationRequired({ toolName: tool.name }))
|
|
160
|
+
: tool.run(parsed)), Effect.provide(lucaLayer), Effect.exit, Effect.timed, Effect.map(([elapsed, exit]) => {
|
|
161
|
+
const [outcome, result] = settle(tool, surface, exit);
|
|
162
|
+
logCall(outcome, elapsed);
|
|
163
|
+
return result;
|
|
164
|
+
}),
|
|
165
|
+
// A cancellation from the client stops the program before the exit above
|
|
166
|
+
// is read, so it is logged on the way out instead.
|
|
167
|
+
Effect.onInterrupt(() => Effect.sync(() => logCall("interrupted"))));
|
|
135
168
|
}
|
|
136
|
-
/**
|
|
137
|
-
function
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
169
|
+
/** A finished call as the outcome it logs and the result the client gets. */
|
|
170
|
+
function settle(tool, surface, exit) {
|
|
171
|
+
if (Exit.isSuccess(exit)) {
|
|
172
|
+
return ["success", asSuccessResult(tool, exit.value)];
|
|
173
|
+
}
|
|
174
|
+
const { cause } = exit;
|
|
175
|
+
const error = Cause.findErrorOption(cause);
|
|
176
|
+
if (Option.isSome(error)) {
|
|
177
|
+
const failure = error.value;
|
|
178
|
+
if (failure instanceof LucaConfirmationRequired) {
|
|
179
|
+
logAgentSurfaceEvent("mcp.write_gate.rejected", { toolName: tool.name });
|
|
180
|
+
return ["error", confirmationError(tool.name)];
|
|
181
|
+
}
|
|
182
|
+
return [
|
|
183
|
+
"error",
|
|
184
|
+
asErrorResult(failure, authChallengeMeta(failure, tool.oauthScope, surface.resourceMetadataUrl)),
|
|
185
|
+
];
|
|
186
|
+
}
|
|
187
|
+
const defect = Cause.squash(cause);
|
|
188
|
+
return [
|
|
189
|
+
"defect",
|
|
190
|
+
asErrorResult(
|
|
191
|
+
// oxlint-disable-next-line effect/avoid-untagged-errors -- boundary narrows an unknown thrown value that carries no tag
|
|
192
|
+
defect instanceof Error ? defect : new Error(String(defect))),
|
|
193
|
+
];
|
|
142
194
|
}
|
|
143
195
|
/** The Effect program that runs an operation, auto-paginating list tools. */
|
|
144
196
|
export function operationProgram(operation, toolInput) {
|
|
145
197
|
const baseRequest = operation.buildRequest(toolInput);
|
|
146
|
-
const {
|
|
147
|
-
if (
|
|
148
|
-
return paginatedProgram(operation,
|
|
198
|
+
const { pageContract } = operation;
|
|
199
|
+
if (pageContract) {
|
|
200
|
+
return paginatedProgram(operation, pageContract, baseRequest, toolInput.maxPages);
|
|
149
201
|
}
|
|
150
202
|
// An operation with no page shape has no pagination to follow, so it is one
|
|
151
203
|
// request, sent as-is.
|
|
@@ -156,8 +208,8 @@ export function operationProgram(operation, toolInput) {
|
|
|
156
208
|
}
|
|
157
209
|
/**
|
|
158
210
|
* A tool's successful result. A response that can carry external-user text is
|
|
159
|
-
* flagged untrusted on both halves
|
|
160
|
-
* field
|
|
211
|
+
* flagged untrusted on both halves, the structured `provenance.untrusted`
|
|
212
|
+
* field that a lead cannot spoof and a human-facing preamble on the text, so a
|
|
161
213
|
* downstream agent treats embedded text as data, not instructions. What the
|
|
162
214
|
* structured half attaches is `provenanceFields`, shared with the resource
|
|
163
215
|
* reads in `server.ts`.
|
|
@@ -179,7 +231,7 @@ function asSuccessResult(tool, result) {
|
|
|
179
231
|
},
|
|
180
232
|
};
|
|
181
233
|
}
|
|
182
|
-
function asErrorResult(error) {
|
|
234
|
+
function asErrorResult(error, meta) {
|
|
183
235
|
return {
|
|
184
236
|
isError: true,
|
|
185
237
|
content: [
|
|
@@ -188,6 +240,7 @@ function asErrorResult(error) {
|
|
|
188
240
|
text: formatError(error),
|
|
189
241
|
},
|
|
190
242
|
],
|
|
243
|
+
...optionalField("_meta", meta),
|
|
191
244
|
};
|
|
192
245
|
}
|
|
193
246
|
function operationDescription(operation) {
|
|
@@ -196,25 +249,27 @@ function operationDescription(operation) {
|
|
|
196
249
|
"",
|
|
197
250
|
`Luca operation: ${operation.method} ${operation.path}`,
|
|
198
251
|
`Required scopes: ${operation.scopes.join(", ")}`,
|
|
199
|
-
`Required
|
|
252
|
+
`Required OAuth scope: ${oauthScopeForOperation(operation)}; data sensitivity: ${operation.requiredScope.dataSensitivity}`,
|
|
200
253
|
operation.idempotencyRequired
|
|
201
254
|
? "Requires Idempotency-Key. Provide idempotencyKey for safe retries, or the server will generate one."
|
|
202
255
|
: "Does not require idempotency.",
|
|
203
256
|
];
|
|
204
|
-
if (operation.
|
|
205
|
-
|
|
257
|
+
if (operation.confirm !== undefined) {
|
|
258
|
+
const when = operation.confirm === "always"
|
|
259
|
+
? ""
|
|
260
|
+
: ` when ${operation.confirm.description}`;
|
|
261
|
+
lines.push(`Destructive${when}: has a real-world side effect. You must pass confirm: true; the call is rejected without it.`);
|
|
206
262
|
}
|
|
207
263
|
lines.push("Use workspaceId or workspaceSlug to override the default workspace configured by environment.");
|
|
208
264
|
return lines.join("\n");
|
|
209
265
|
}
|
|
210
266
|
function confirmationError(toolName) {
|
|
211
|
-
logAgentSurfaceEvent("mcp.write_gate.rejected", { toolName });
|
|
212
267
|
return {
|
|
213
268
|
isError: true,
|
|
214
269
|
content: [
|
|
215
270
|
{
|
|
216
271
|
type: "text",
|
|
217
|
-
text: `${toolName}: This tool requires confirm: true because it has a real-world side effect
|
|
272
|
+
text: `${toolName}: This tool requires confirm: true because it has a real-world side effect. Re-call the tool with confirm: true to proceed.`,
|
|
218
273
|
},
|
|
219
274
|
],
|
|
220
275
|
structuredContent: {
|
package/dist/version.js
CHANGED
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Bump this in the same commit as package.json and server.json.
|
|
11
11
|
*/
|
|
12
|
-
export const LUCA_MCP_VERSION = "0.
|
|
12
|
+
export const LUCA_MCP_VERSION = "0.3.0";
|
|
13
13
|
/** The user-agent every outbound Luca API call identifies itself with. */
|
|
14
14
|
export const LUCA_MCP_USER_AGENT = `luca-mcp/${LUCA_MCP_VERSION}`;
|
package/package.json
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@setluca/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Let your AI assistant help with Luca conversations, leads, replies, campaigns, and bookings.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"ai",
|
|
8
|
-
"crm",
|
|
9
8
|
"luca",
|
|
10
9
|
"mcp",
|
|
11
10
|
"model-context-protocol"
|
|
12
11
|
],
|
|
13
12
|
"license": "Apache-2.0",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/setluca/luca-mcp.git"
|
|
16
|
+
},
|
|
14
17
|
"bin": {
|
|
15
18
|
"luca-mcp": "dist/index.js"
|
|
16
19
|
},
|
|
17
20
|
"files": [
|
|
18
21
|
"dist",
|
|
19
|
-
"README.md"
|
|
22
|
+
"README.md",
|
|
23
|
+
"CHANGELOG.md"
|
|
20
24
|
],
|
|
21
25
|
"type": "module",
|
|
22
26
|
"exports": {
|
|
@@ -34,45 +38,63 @@
|
|
|
34
38
|
"test": "TZ=UTC vitest run --exclude test/stdio-smoke.test.ts",
|
|
35
39
|
"test:mutation": "TZ=UTC stryker run",
|
|
36
40
|
"test:stdio": "vitest run test/stdio-smoke.test.ts",
|
|
41
|
+
"lint": "oxlint . --deny-warnings",
|
|
42
|
+
"lint:anti-slop": "oxlint . --deny-warnings",
|
|
43
|
+
"format": "oxfmt --write . --disable-nested-config",
|
|
44
|
+
"format:check": "oxfmt --check . --disable-nested-config",
|
|
45
|
+
"knip": "knip",
|
|
46
|
+
"fallow:dead-code": "fallow dead-code --fail-on-issues",
|
|
47
|
+
"fallow:dupes": "fallow dupes --fail-on-issues --summary",
|
|
48
|
+
"fallow:security": "fallow security --fail-on-issues --summary",
|
|
49
|
+
"fallow:health:gate": "fallow health --min-score 85 --summary",
|
|
50
|
+
"quality": "bun run format:check && bun run lint && bun run knip && bun run fallow:dead-code && bun run fallow:dupes && bun run fallow:security && bun run fallow:health:gate",
|
|
37
51
|
"dev:remote": "wrangler dev",
|
|
38
52
|
"deploy": "wrangler deploy --env production",
|
|
53
|
+
"deploy:dry-run": "wrangler deploy --env production --dry-run",
|
|
39
54
|
"test:integration": "vitest run test/integration",
|
|
40
55
|
"coverage": "bun run test -- --coverage",
|
|
41
56
|
"typecheck": "tsc --noEmit",
|
|
42
|
-
"verify": "bun run docs:check && bun run catalog:check && bun run openapi:inputs:check && bun run openapi:outputs:check && bun run openapi:check && bun run registry:check && bun run
|
|
57
|
+
"verify": "bun run quality && bun run docs:check && bun run catalog:check && bun run openapi:inputs:check && bun run openapi:outputs:check && bun run openapi:check && bun run registry:check && bun run typecheck && bun run coverage && bun run build && bun run test:stdio && bun run contracts:deployed:check",
|
|
43
58
|
"openapi:generate": "bun run catalog:generate && bun scripts/generate-openapi-inputs.ts --write && bun scripts/generate-openapi-outputs.ts --write",
|
|
59
|
+
"contracts:sync": "bun scripts/contract-sync.ts",
|
|
60
|
+
"contracts:deployed:check": "bun scripts/check-deployed-luca-contracts.ts",
|
|
44
61
|
"catalog:generate": "bun scripts/generate-route-catalog.ts --write",
|
|
45
62
|
"catalog:check": "bun scripts/generate-route-catalog.ts --check",
|
|
46
63
|
"openapi:inputs:check": "bun scripts/generate-openapi-inputs.ts --check",
|
|
47
64
|
"openapi:outputs:check": "bun scripts/generate-openapi-outputs.ts --check",
|
|
48
65
|
"openapi:check": "bun scripts/check-openapi.ts",
|
|
49
66
|
"registry:check": "bun scripts/check-registry.ts",
|
|
50
|
-
"redaction:check": "bun scripts/check-redaction.ts",
|
|
51
67
|
"docs:generate": "bun scripts/generate-docs.ts --write",
|
|
52
68
|
"docs:check": "bun scripts/generate-docs.ts --check",
|
|
53
|
-
"run-all": "bun run openapi:generate && bun run docs:generate && bun run typecheck && bun run test",
|
|
54
69
|
"clean": "rm -rf .turbo dist coverage node_modules"
|
|
55
70
|
},
|
|
56
71
|
"dependencies": {
|
|
57
72
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
58
|
-
"effect": "
|
|
59
|
-
"zod": "^4.4.3"
|
|
73
|
+
"effect": "4.0.0"
|
|
60
74
|
},
|
|
61
75
|
"devDependencies": {
|
|
62
|
-
"@
|
|
63
|
-
"@
|
|
76
|
+
"@effect/platform-bun": "4.0.0",
|
|
77
|
+
"@effect/vitest": "4.0.0",
|
|
64
78
|
"@modelcontextprotocol/client": "^2.0.0",
|
|
65
|
-
"@modelcontextprotocol/inspector": "^2.
|
|
66
|
-
"@
|
|
67
|
-
"@
|
|
68
|
-
"
|
|
79
|
+
"@modelcontextprotocol/inspector": "^2.5.0",
|
|
80
|
+
"@mpsuesser/oxlint-plugin-effect": "0.6.0",
|
|
81
|
+
"@oxlint/plugins": "1.82.0",
|
|
82
|
+
"@stryker-mutator/core": "10.0.0",
|
|
83
|
+
"@stryker-mutator/vitest-runner": "10.0.0",
|
|
84
|
+
"@types/node": "^26.5.0",
|
|
85
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
86
|
+
"fallow": "3.23.0",
|
|
87
|
+
"knip": "6.34.0",
|
|
88
|
+
"oxfmt": "^0.67.0",
|
|
89
|
+
"oxlint": "^1.82.0",
|
|
69
90
|
"typescript": "^7.0.2",
|
|
70
|
-
"
|
|
71
|
-
"
|
|
91
|
+
"ultracite": "7.11.0",
|
|
92
|
+
"vitest": "^5.0.0",
|
|
93
|
+
"wrangler": "4.113.0"
|
|
72
94
|
},
|
|
73
95
|
"engines": {
|
|
74
96
|
"node": ">=20"
|
|
75
97
|
},
|
|
76
|
-
"//publish": "Publish name is @setluca/mcp (the @setluca npm org). To publish:
|
|
98
|
+
"//publish": "Publish name is @setluca/mcp (the @setluca npm org). To publish: tag mcp-v*. The workflow authenticates to npm with OIDC Trusted Publishing, so there is no NPM_TOKEN. See docs/release.md.",
|
|
77
99
|
"mcpName": "io.github.setluca/luca-mcp"
|
|
78
100
|
}
|