@gemmein/mcp 0.3.0 → 0.5.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/dist/index.js +1 -1
- package/dist/relays.js +43 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -291,7 +291,7 @@ const TOOLS = [
|
|
|
291
291
|
},
|
|
292
292
|
{
|
|
293
293
|
name: "explain_relay",
|
|
294
|
-
description: "Call while WRITING or FIXING gemmein/relays/<name>.json — before `gemmein sync` carries it to the cloud. A relay is one trigger (receiver: a provider's webhook; schedule: a clock; data_change: a record changing) and up to ten actions in Gemmein's own verbs (write_record, grant_access, revoke_access, email_person, call_url) — route, map, authorise, never compute. Pass the definition JSON; the answer is the English sentence the dashboard shows (\"When gocardless-paid receives an event where event_type is confirmed → grant Pro, email the person, call https://…\") or the ONE refusal sentence the cloud would answer, naming the field and the fix. Offline and read-only: nothing is created. Two checks run only in the cloud and are stated in the answer (the API's own hosts; the address's resolved network at call time).",
|
|
294
|
+
description: "Call while WRITING or FIXING gemmein/relays/<name>.json — before `gemmein sync` carries it to the cloud. A relay is one trigger (receiver: a provider's webhook; schedule: a clock; data_change: a record changing) and up to ten actions in Gemmein's own verbs (write_record, grant_access, revoke_access, grant_credits, email_person, call_url) — route, map, authorise, never compute. Pass the definition JSON; the answer is the English sentence the dashboard shows (\"When gocardless-paid receives an event where event_type is confirmed → grant Pro, email the person, call https://…\") or the ONE refusal sentence the cloud would answer, naming the field and the fix. Offline and read-only: nothing is created. Two checks run only in the cloud and are stated in the answer (the API's own hosts; the address's resolved network at call time).",
|
|
295
295
|
annotations: { title: "Explain relay (validate offline)", readOnlyHint: true },
|
|
296
296
|
inputSchema: {
|
|
297
297
|
type: "object",
|
package/dist/relays.js
CHANGED
|
@@ -34,9 +34,13 @@ const DURATION_RE = /^(\d{1,4})([hdw])$/;
|
|
|
34
34
|
const MAX_MAP_ENTRIES = 20;
|
|
35
35
|
const MAX_WHEN_ENTRIES = 20;
|
|
36
36
|
const RECORD_KINDS = ["created", "updated", "deleted"];
|
|
37
|
-
const ACTION_TYPES = ["write_record", "grant_access", "revoke_access", "email_person", "call_url"];
|
|
37
|
+
const ACTION_TYPES = ["write_record", "grant_access", "revoke_access", "email_person", "call_url", "grant_credits", "fulfil_product", "refund_product"];
|
|
38
|
+
/** W9.5 — mirrors apps/api/src/relays/schema.ts. */
|
|
39
|
+
const PRODUCT_NAME_RE = /^[a-z0-9][a-z0-9 _.-]{0,39}$/i;
|
|
40
|
+
// W9.3: a relay may grant a person up to 10,000 credits per action.
|
|
41
|
+
const MAX_RELAY_CREDITS = 10000;
|
|
38
42
|
const SCHEMES = ["hmac_sha256_header", "stripe", "svix", "shared_token"];
|
|
39
|
-
const PERSON_ACTIONS = new Set(["grant_access", "revoke_access", "email_person"]);
|
|
43
|
+
const PERSON_ACTIONS = new Set(["grant_access", "grant_credits", "revoke_access", "email_person", "fulfil_product", "refund_product"]);
|
|
40
44
|
const ENTITLEMENT_KEY = /^access:[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
41
45
|
const RESERVED_FIELD_NAMES = new Set([
|
|
42
46
|
"id", "appId", "app_id", "environmentId", "environment_id", "userId", "user_id", "ownerId", "ownerUserId", "owner_user_id",
|
|
@@ -296,6 +300,40 @@ function validateAction(raw, index, triggerKind) {
|
|
|
296
300
|
const reason = optionalString(a, "reason", what, 200);
|
|
297
301
|
return { type: "grant_access", entitlement, ...(expiresAt !== undefined ? { expiresAt } : {}), ...(reason !== undefined ? { reason } : {}) };
|
|
298
302
|
}
|
|
303
|
+
case "grant_credits": {
|
|
304
|
+
// W9.3 — mirrors apps/api/src/relays/schema.ts (the lockstep test
|
|
305
|
+
// holds them equal): a whole number of credits, 1..10,000, and an
|
|
306
|
+
// optional reason the owner reads in the ledger.
|
|
307
|
+
rejectUnknownKeys(a, ["type", "amount", "reason"], what);
|
|
308
|
+
const amount = a.amount;
|
|
309
|
+
if (typeof amount !== "number" || !Number.isInteger(amount) || amount < 1 || amount > MAX_RELAY_CREDITS) {
|
|
310
|
+
refuse(`${what}.amount must be a whole number from 1 to ${MAX_RELAY_CREDITS.toLocaleString("en-GB")} credits`);
|
|
311
|
+
}
|
|
312
|
+
const reason = optionalString(a, "reason", what, 200);
|
|
313
|
+
return { type: "grant_credits", amount, ...(reason !== undefined ? { reason } : {}) };
|
|
314
|
+
}
|
|
315
|
+
case "fulfil_product":
|
|
316
|
+
case "refund_product": {
|
|
317
|
+
// W9.5 — mirrors apps/api/src/relays/schema.ts (the lockstep test holds
|
|
318
|
+
// them equal): the product is a NAME, never a template; the payment
|
|
319
|
+
// reference may be a template, because it comes out of the event.
|
|
320
|
+
rejectUnknownKeys(a, ["type", "product", "ref"], what);
|
|
321
|
+
const product = a.product;
|
|
322
|
+
if (typeof product !== "string" || !PRODUCT_NAME_RE.test(product.trim())) {
|
|
323
|
+
refuse(`${what}.product must be a product name (1-40 letters, numbers, spaces, or -_.)`);
|
|
324
|
+
}
|
|
325
|
+
const rawRef = a.ref;
|
|
326
|
+
// A written ref that is empty is not the absent ref: absent means "make
|
|
327
|
+
// one from the event id", and a blank string would silently take that
|
|
328
|
+
// road instead of the one the founder wrote. Refused here.
|
|
329
|
+
if (rawRef !== undefined &&
|
|
330
|
+
rawRef !== null &&
|
|
331
|
+
(typeof rawRef !== "string" || rawRef.trim() === "" || rawRef.length > 200)) {
|
|
332
|
+
refuse(`${what}.ref must be text of at most 200 characters (templates allowed)`);
|
|
333
|
+
}
|
|
334
|
+
const ref = typeof rawRef === "string" ? rawRef : undefined;
|
|
335
|
+
return { type: a.type, product: product.trim(), ...(ref !== undefined ? { ref } : {}) };
|
|
336
|
+
}
|
|
299
337
|
case "revoke_access": {
|
|
300
338
|
rejectUnknownKeys(a, ["type", "entitlement"], what);
|
|
301
339
|
return { type: "revoke_access", entitlement: validateEntitlementInput(a.entitlement, what) };
|
|
@@ -385,6 +423,9 @@ export function describeRelay(def) {
|
|
|
385
423
|
switch (a.type) {
|
|
386
424
|
case "write_record": return `write a ${a.collection} record${a.to === "person" ? " for the person" : ""}`;
|
|
387
425
|
case "grant_access": return `grant ${entitlementWords(a.entitlement)}`;
|
|
426
|
+
case "grant_credits": return `grant ${a.amount} credit${a.amount === 1 ? "" : "s"}`;
|
|
427
|
+
case "fulfil_product": return `fulfil ${a.product}`;
|
|
428
|
+
case "refund_product": return `refund ${a.product}`;
|
|
388
429
|
case "revoke_access": return `revoke ${entitlementWords(a.entitlement)}`;
|
|
389
430
|
case "email_person": return "email the person";
|
|
390
431
|
case "call_url": return `call ${a.url}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gemmein/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"mcpName": "com.gemmein/mcp",
|
|
5
5
|
"description": "Gemmein MCP server \u2014 gives coding agents the Gemmein guide, API reference, rule/error explainers, and a live integration check (reaffirm) as tools. Read-only: it never creates, edits, or deletes anything.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"prepublishOnly": "npm run build"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@gemmein/sdk": "^0.
|
|
37
|
+
"@gemmein/sdk": "^0.8.0",
|
|
38
38
|
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
39
39
|
},
|
|
40
40
|
"author": "Gemmein Limited",
|