idiolect-mcp 0.5.2 → 0.6.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.
@@ -81,5 +81,16 @@ export function publicMcpState(state, message, next, details = {}) {
81
81
  delete safeDetails.state;
82
82
  delete safeDetails.message;
83
83
  delete safeDetails.next;
84
- return { state, message, ...safeDetails, next: { action: next.action, tool: next.tool } };
84
+ return {
85
+ state,
86
+ message,
87
+ ...safeDetails,
88
+ next: {
89
+ action: next.action,
90
+ tool: next.tool,
91
+ ...(next.action === "retry" && typeof next.retryAfterSeconds === "number"
92
+ ? { retryAfterSeconds: next.retryAfterSeconds }
93
+ : {}),
94
+ },
95
+ };
85
96
  }
package/server.mjs CHANGED
@@ -47,7 +47,16 @@ export const accountRuntimeCompatibilityToolNames = Object.freeze([
47
47
  "feedback",
48
48
  "refine",
49
49
  "profile",
50
- "manage_profile",
50
+ "list_writing_samples",
51
+ "create_writing_profile",
52
+ "add_writing_samples",
53
+ "rename_writing_profile",
54
+ "set_current_writing_profile",
55
+ "set_default_writing_template",
56
+ "add_writing_rule",
57
+ "remove_writing_rule",
58
+ "remove_writing_sample",
59
+ "delete_writing_profile",
51
60
  "learn_writing_style",
52
61
  "get_my_voice",
53
62
  "create_my_voice",
@@ -142,195 +151,14 @@ export function formatLegacyScoreVoiceResult(result) {
142
151
  return `${lines}\n\n(${provenance} — higher = reads more like you; not a forensic identity claim.)`;
143
152
  }
144
153
 
145
- const CREDIT_STATES = new Set([
146
- "free_beta",
147
- "balance_available",
148
- "low_balance",
149
- "zero_balance",
150
- "legacy_unlimited",
151
- "accounting_unavailable",
152
- ]);
153
- const CREDIT_MODES = new Set(["free", "credits"]);
154
-
155
154
  function plainObject(value) {
156
155
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
157
156
  }
158
157
 
159
- function safeManageUrl(value) {
160
- if (value === null) return null;
161
- if (typeof value !== "string") return undefined;
162
- try {
163
- const parsed = new URL(value);
164
- const accountOrigin = new URL(ACCOUNT_BASE).origin;
165
- const firstParty = parsed.hostname === "idiolect.app" || parsed.hostname.endsWith(".idiolect.app");
166
- if (
167
- !["https:", "http:"].includes(parsed.protocol)
168
- || (parsed.origin !== accountOrigin && !firstParty)
169
- || parsed.pathname !== "/settings"
170
- || parsed.username
171
- || parsed.password
172
- || parsed.search !== "?section=credits"
173
- || parsed.hash
174
- ) return undefined;
175
- return `${parsed.origin}/settings?section=credits`;
176
- } catch {
177
- return undefined;
178
- }
179
- }
180
-
181
- function creditMessage(credits) {
182
- const { state, balance, charged, manageUrl, mode } = credits;
183
- if (state === "free_beta") {
184
- return charged === 0
185
- ? "Free during beta. No credits are being used."
186
- : "Completed free during beta. No credit was used.";
187
- }
188
- if (state === "legacy_unlimited") return "Unlimited generation access is active. Credits are not used.";
189
- if (state === "accounting_unavailable") {
190
- return mode === "free"
191
- ? "Free during beta. The credit balance is temporarily unavailable; generation remains available."
192
- : "The credit balance is temporarily unavailable. Generation is paused; no credit was used.";
193
- }
194
- if (state === "zero_balance") {
195
- return charged === 1
196
- ? "This request completed with the last credit."
197
- : `The credit balance is 0. A draft or rewrite needs 1 credit. Credit management: ${manageUrl}.`;
198
- }
199
- if (charged === 1) return `1 credit used. ${balance} ${balance === 1 ? "remains" : "remain"}.`;
200
- if (state === "low_balance") return `${balance} credit${balance === 1 ? "" : "s"} remain. Credit management: ${manageUrl}.`;
201
- return `${balance} credits available. Each draft or rewrite uses 1 credit.`;
202
- }
203
-
204
- function validCreditInvariant(credits) {
205
- const {
206
- state,
207
- mode,
208
- balance,
209
- balancePreview,
210
- unitCost,
211
- charged,
212
- unlimited,
213
- accountingAvailable,
214
- generationAvailable,
215
- purchaseRequired,
216
- manageUrl,
217
- } = credits;
218
- if (state === "free_beta") {
219
- return mode === "free" && balance !== null && balancePreview && unitCost === 0 && charged === 0
220
- && !unlimited && accountingAvailable && generationAvailable && !purchaseRequired && manageUrl === null;
221
- }
222
- if (state === "legacy_unlimited") {
223
- return mode === "credits" && balance === null && !balancePreview && unitCost === 0 && charged === 0
224
- && unlimited && accountingAvailable && generationAvailable && !purchaseRequired && manageUrl === null;
225
- }
226
- if (state === "accounting_unavailable") {
227
- return balance === null && !balancePreview && charged === 0 && !unlimited && !accountingAvailable
228
- && !purchaseRequired && manageUrl === null && unitCost === (mode === "free" ? 0 : 1)
229
- && generationAvailable === (mode === "free");
230
- }
231
- if (mode !== "credits" || balance === null || balancePreview || unitCost !== 1 || unlimited || !accountingAvailable) {
232
- return false;
233
- }
234
- if (state === "balance_available") {
235
- return balance > 5 && generationAvailable && !purchaseRequired && manageUrl === null;
236
- }
237
- if (state === "low_balance") {
238
- return balance >= 1 && balance <= 5 && generationAvailable && !purchaseRequired && manageUrl !== null;
239
- }
240
- return state === "zero_balance" && balance === 0 && !generationAvailable && purchaseRequired && manageUrl !== null;
241
- }
242
-
243
- /**
244
- * Rebuild the public credit envelope from known primitives only. Never copy an
245
- * upstream message or unknown field into an MCP response.
246
- */
247
- export function safeCreditPresentation(value) {
248
- if (!plainObject(value)) return null;
249
- const balance = value.balance;
250
- const manageUrl = safeManageUrl(value.manageUrl);
251
- if (
252
- !CREDIT_STATES.has(value.state)
253
- || !CREDIT_MODES.has(value.mode)
254
- || !(balance === null || (Number.isSafeInteger(balance) && balance >= 0))
255
- || typeof value.balancePreview !== "boolean"
256
- || ![0, 1].includes(value.unitCost)
257
- || ![0, 1].includes(value.charged)
258
- || typeof value.unlimited !== "boolean"
259
- || typeof value.accountingAvailable !== "boolean"
260
- || typeof value.generationAvailable !== "boolean"
261
- || typeof value.purchaseRequired !== "boolean"
262
- || manageUrl === undefined
263
- || typeof value.replayed !== "boolean"
264
- ) return null;
265
-
266
- const credits = {
267
- state: value.state,
268
- mode: value.mode,
269
- balance,
270
- balancePreview: value.balancePreview,
271
- unitCost: value.unitCost,
272
- charged: value.charged,
273
- unlimited: value.unlimited,
274
- accountingAvailable: value.accountingAvailable,
275
- generationAvailable: value.generationAvailable,
276
- purchaseRequired: value.purchaseRequired,
277
- manageUrl,
278
- replayed: value.replayed,
279
- };
280
- if (!validCreditInvariant(credits)) return null;
281
- return { ...credits, message: creditMessage(credits) };
282
- }
283
-
284
- function safeCreditToolErrorPayload(value) {
285
- if (!plainObject(value) || !plainObject(value.error) || !plainObject(value.retry)) return null;
286
- const credits = safeCreditPresentation(value.credits);
287
- if (!credits) return null;
288
- const purchaseRequired = value.status === "purchase_required"
289
- && value.error.code === "insufficient_credits"
290
- && credits.state === "zero_balance"
291
- && credits.mode === "credits"
292
- && credits.balance === 0
293
- && credits.charged === 0
294
- && value.retry.after === "balance_updated";
295
- const accountingUnavailable = value.status === "accounting_unavailable"
296
- && value.error.code === "credit_accounting_unavailable"
297
- && credits.state === "accounting_unavailable"
298
- && credits.mode === "credits"
299
- && credits.charged === 0
300
- && value.retry.after === "accounting_available";
301
- if (
302
- (!purchaseRequired && !accountingUnavailable)
303
- || value.error.retryable !== true
304
- || value.retry.safe !== true
305
- || value.retry.idempotent !== true
306
- || value.retry.scope !== "pre_generation"
307
- ) return null;
308
-
309
- const message = purchaseRequired
310
- ? `This request needs 1 credit; the balance is 0. Nothing was charged. Credit management: ${credits.manageUrl}. The same request remains safe to retry after the balance changes.`
311
- : "The credit balance is temporarily unavailable. No generation started and no credit was used. The same request remains safe to retry after accounting recovers.";
312
- return {
313
- status: value.status,
314
- message,
315
- error: { code: value.error.code, retryable: true },
316
- credits: { ...credits, message },
317
- retry: {
318
- safe: true,
319
- idempotent: true,
320
- scope: "pre_generation",
321
- after: value.retry.after,
322
- },
323
- };
324
- }
325
-
326
158
  async function accountRequestError(res, toolName) {
327
159
  if ((res.headers.get("content-type") || "").includes("application/json")) {
328
160
  try {
329
161
  const payload = await res.json();
330
- const creditContent = safeCreditToolErrorPayload(payload);
331
- if (creditContent) {
332
- return new AccountRequestError(creditContent.message, res.status, creditContent);
333
- }
334
162
  const creationContent = toolName === "create_my_voice" ? safeCreationFailurePayload(payload) : null;
335
163
  if (creationContent) {
336
164
  return new AccountRequestError(creationContent.message, res.status, creationContent);
@@ -608,72 +436,6 @@ const PROVENANCE_OUTPUT_PROPERTIES = {
608
436
  voice: SELECTED_VOICE_SCHEMA,
609
437
  evidence: EVIDENCE_OUTPUT_SCHEMA,
610
438
  };
611
- const CREDIT_OUTPUT_SCHEMA = {
612
- type: "object",
613
- additionalProperties: false,
614
- properties: {
615
- state: {
616
- type: "string",
617
- enum: ["free_beta", "balance_available", "low_balance", "zero_balance", "legacy_unlimited", "accounting_unavailable"],
618
- },
619
- mode: { type: "string", enum: ["free", "credits"] },
620
- balance: { type: ["integer", "null"], minimum: 0 },
621
- balancePreview: { type: "boolean" },
622
- unitCost: { type: "number", enum: [0, 1] },
623
- charged: { type: "number", enum: [0, 1] },
624
- unlimited: { type: "boolean" },
625
- accountingAvailable: { type: "boolean" },
626
- generationAvailable: { type: "boolean" },
627
- purchaseRequired: { type: "boolean" },
628
- manageUrl: { type: ["string", "null"] },
629
- replayed: { type: "boolean" },
630
- message: { type: "string" },
631
- },
632
- required: [
633
- "state",
634
- "mode",
635
- "balance",
636
- "balancePreview",
637
- "unitCost",
638
- "charged",
639
- "unlimited",
640
- "accountingAvailable",
641
- "generationAvailable",
642
- "purchaseRequired",
643
- "manageUrl",
644
- "replayed",
645
- "message",
646
- ],
647
- };
648
- const CREDIT_ERROR_SCHEMA = {
649
- type: "object",
650
- additionalProperties: false,
651
- properties: {
652
- code: { type: "string", enum: ["insufficient_credits", "credit_accounting_unavailable"] },
653
- retryable: { type: "boolean", enum: [true] },
654
- },
655
- required: ["code", "retryable"],
656
- };
657
- const CREDIT_RETRY_SCHEMA = {
658
- type: "object",
659
- additionalProperties: false,
660
- properties: {
661
- safe: { type: "boolean", enum: [true] },
662
- idempotent: { type: "boolean", enum: [true] },
663
- scope: { type: "string", enum: ["pre_generation"] },
664
- after: { type: "string", enum: ["balance_updated", "accounting_available"] },
665
- },
666
- required: ["safe", "idempotent", "scope", "after"],
667
- };
668
- // Carried by a credit-gate refusal only; a completed draft or rewrite no longer reports
669
- // credits (13 constants in free mode) or a status beside `state` (IDI-381).
670
- const CREDIT_GENERATION_OUTPUT_PROPERTIES = {
671
- status: { type: "string", enum: ["purchase_required", "accounting_unavailable"] },
672
- message: { type: "string" },
673
- credits: CREDIT_OUTPUT_SCHEMA,
674
- error: CREDIT_ERROR_SCHEMA,
675
- retry: CREDIT_RETRY_SCHEMA,
676
- };
677
439
  const NEXT_ACTION_SCHEMA = {
678
440
  type: "object",
679
441
  additionalProperties: false,
@@ -735,7 +497,6 @@ const REWRITE_OUTPUT_SCHEMA = {
735
497
  reason: { type: ["string", "null"], description: "Present only when scoring is enabled and the engine did not measure; says why." },
736
498
  voice: SELECTED_VOICE_SCHEMA,
737
499
  evidence: EVIDENCE_OUTPUT_SCHEMA,
738
- ...CREDIT_GENERATION_OUTPUT_PROPERTIES,
739
500
  ...CAVEATS_OUTPUT_PROPERTY,
740
501
  },
741
502
  };
@@ -747,7 +508,6 @@ const DRAFT_OUTPUT_SCHEMA = {
747
508
  reason: { type: ["string", "null"], description: "Present only when scoring is enabled and the engine did not measure; says why." },
748
509
  voice: SELECTED_VOICE_SCHEMA,
749
510
  evidence: EVIDENCE_OUTPUT_SCHEMA,
750
- ...CREDIT_GENERATION_OUTPUT_PROPERTIES,
751
511
  ...CAVEATS_OUTPUT_PROPERTY,
752
512
  },
753
513
  };
@@ -1012,20 +772,6 @@ const accountToolDefinitions = [
1012
772
  ]),
1013
773
  annotations: { readOnlyHint: false, openWorldHint: false, destructiveHint: true, title: "Delete Writing Profile" },
1014
774
  },
1015
- {
1016
- name: "get_credit_status",
1017
- description:
1018
- "Check the account's current generation-credit state. During the free beta, this confirms that no credits are being used.",
1019
- inputSchema: { type: "object", properties: {}, additionalProperties: false },
1020
- outputSchema: CREDIT_OUTPUT_SCHEMA,
1021
- annotations: {
1022
- readOnlyHint: true,
1023
- idempotentHint: true,
1024
- openWorldHint: false,
1025
- destructiveHint: false,
1026
- title: "Check Credit Status",
1027
- },
1028
- },
1029
775
  {
1030
776
  name: "score_voice",
1031
777
  description:
@@ -1480,18 +1226,7 @@ export async function handleAccountTool(name, args = {}) {
1480
1226
  }
1481
1227
  }
1482
1228
  if (name === "get_credit_status") {
1483
- try {
1484
- const credits = safeCreditPresentation(await accountRequest("/api/v1/credits", {
1485
- toolName: "get_credit_status",
1486
- }));
1487
- if (!credits) return toolError("get_credit_status: Idiolect AI returned an invalid credit status.");
1488
- return {
1489
- content: [{ type: "text", text: credits.message }],
1490
- structuredContent: credits,
1491
- };
1492
- } catch (error) {
1493
- return accountToolError(error);
1494
- }
1229
+ return toolError("This tool is no longer available. Refresh the Idiolect AI tool catalogue.");
1495
1230
  }
1496
1231
 
1497
1232
  if (name === "get_my_voice") {