@holdyourvoice/hyv 3.6.1 → 4.0.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.
Files changed (87) hide show
  1. package/Readme.md +91 -96
  2. package/dist/ai-editor-rules.js +1 -0
  3. package/dist/ai-editor.js +19 -13
  4. package/dist/analysis.js +33 -0
  5. package/dist/cli/agents.js +15 -35
  6. package/dist/cli/io.js +41 -37
  7. package/dist/copy-spec.js +6 -6
  8. package/dist/editorial-packs.js +4 -3
  9. package/dist/fact-linter.js +45 -27
  10. package/dist/hidden-text.js +6 -6
  11. package/dist/hold-your-voice.mcpb +0 -0
  12. package/dist/internal.js +8 -0
  13. package/dist/judgment-task.js +10 -14
  14. package/dist/learning.js +66 -67
  15. package/dist/lifecycle-adapter.js +2 -5
  16. package/dist/local-eval.js +16 -31
  17. package/dist/mcp-server.js +272 -0
  18. package/dist/mcp-tools.js +2 -2
  19. package/dist/mcp.js +2 -261
  20. package/dist/pipeline.js +9 -85
  21. package/dist/profile-compose.js +18 -11
  22. package/dist/profile-score.js +6 -6
  23. package/dist/profile.js +17 -19
  24. package/dist/provenance-status.js +1 -3
  25. package/dist/rebuild-task.js +14 -25
  26. package/dist/recomposition.js +18 -13
  27. package/dist/rewrite-prompt.js +82 -0
  28. package/dist/rewrite-task.js +66 -65
  29. package/dist/rule-allowances.js +6 -10
  30. package/dist/semantic-review.js +71 -47
  31. package/dist/stage1-evaluation.js +29 -14
  32. package/dist/strict-quality.js +10 -18
  33. package/dist/version.js +1 -1
  34. package/dist/voice-dna.js +34 -21
  35. package/dist/writing-examples.js +2 -7
  36. package/package.json +6 -3
  37. package/skills/hyv-prepare-judgment/SKILL.md +4 -0
  38. package/dist/agents/catalog.test.js +0 -60
  39. package/dist/agents/emit.test.js +0 -64
  40. package/dist/agents/load.test.js +0 -149
  41. package/dist/ai-editor.test.js +0 -265
  42. package/dist/approval-capability.test.js +0 -52
  43. package/dist/approval-context.test.js +0 -38
  44. package/dist/backtest.test.js +0 -20
  45. package/dist/benchmark.test.js +0 -328
  46. package/dist/canonical-json.test.js +0 -24
  47. package/dist/cli/context.test.js +0 -55
  48. package/dist/cli.test.js +0 -731
  49. package/dist/editorial-packs.test.js +0 -94
  50. package/dist/fact-linter.test.js +0 -85
  51. package/dist/hidden-text.test.js +0 -26
  52. package/dist/hygiene.test.js +0 -83
  53. package/dist/judgment-task.test.js +0 -162
  54. package/dist/learning.test.js +0 -325
  55. package/dist/lifecycle-adapter.test.js +0 -56
  56. package/dist/local-eval.test.js +0 -20
  57. package/dist/logic-linter-corpus.test.js +0 -22
  58. package/dist/logic-linter.test.js +0 -39
  59. package/dist/mcp-tools.test.js +0 -286
  60. package/dist/mcp.test.js +0 -312
  61. package/dist/mirror-refs.test.js +0 -63
  62. package/dist/pipeline.test.js +0 -247
  63. package/dist/preservation.test.js +0 -22
  64. package/dist/production-gates.test.js +0 -34
  65. package/dist/profile-compose.test.js +0 -32
  66. package/dist/profile-score.test.js +0 -22
  67. package/dist/profile-watch.test.js +0 -23
  68. package/dist/profile.test.js +0 -141
  69. package/dist/provenance-status.test.js +0 -22
  70. package/dist/rebuild-task.test.js +0 -206
  71. package/dist/recomposition.test.js +0 -34
  72. package/dist/release-audit.test.js +0 -292
  73. package/dist/rewrite-task.test.js +0 -166
  74. package/dist/rule-allowances.test.js +0 -17
  75. package/dist/rule-reconciliation.test.js +0 -50
  76. package/dist/sample-ingest.test.js +0 -52
  77. package/dist/semantic-review.test.js +0 -101
  78. package/dist/stage1-dry-run.test.js +0 -39
  79. package/dist/stage1-evaluation.test.js +0 -184
  80. package/dist/stage1-human-packet.test.js +0 -102
  81. package/dist/stage1-schema-contract.test.js +0 -95
  82. package/dist/stage2-human-packet.test.js +0 -81
  83. package/dist/strict-quality.test.js +0 -62
  84. package/dist/text-provenance.feature.test.js +0 -45
  85. package/dist/text.test.js +0 -16
  86. package/dist/voice-dna.test.js +0 -121
  87. package/dist/writing-examples.test.js +0 -35
@@ -0,0 +1,272 @@
1
+ import * as handlers from './mcp-tools.js';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { z } from 'zod';
4
+ import { HYV_VERSION } from './version.js';
5
+ import { loadApprovalContext } from './approval-context.js';
6
+ const localRead = { readOnlyHint: true, destructiveHint: false, openWorldHint: false };
7
+ const localWrite = { readOnlyHint: false, destructiveHint: false, openWorldHint: false };
8
+ const localDelete = { readOnlyHint: false, destructiveHint: true, openWorldHint: false };
9
+ const readOnly = { readOnlyHint: true };
10
+ const writing = z.string().min(1).max(100_000);
11
+ const hygieneText = z.string().max(100_000);
12
+ const profileJson = z.string().min(1).max(50_000);
13
+ const copySpecJson = z.string().min(1).max(250_000);
14
+ const writingBriefJson = z.string().min(1).max(50_000);
15
+ const samples = z.array(writing).min(2).max(20);
16
+ const heldoutSamples = z.array(writing).min(3).max(20);
17
+ const evalParagraphs = z.array(z.object({ paragraph_id: z.string().min(1).max(160), text: writing })).min(2).max(50);
18
+ const writingExamples = z.array(z.object({ basename: z.string().min(1).max(160), text: writing })).min(1).max(64);
19
+ const avoid = z.array(z.string().min(1).max(200)).max(50).optional();
20
+ const lifecycleJson = z.string().min(1).max(1_048_576);
21
+ const approvedLearningText = z.string().min(1).max(1_048_576);
22
+ const evaluatorId = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/);
23
+ const semanticViolation = z.enum(['action_change', 'dropped_object', 'unsupported_claim', 'constraint_weakened', 'clarity_regression']);
24
+ const factSourcesJson = z.string().min(2).max(250_000);
25
+ const learningOptions = {
26
+ mutation_id: z.string().min(1).max(200).optional(),
27
+ authority: z.enum(['founder', 'team', 'system']).optional(),
28
+ provenance: z.string().min(1).max(500).optional(),
29
+ weight: z.number().positive().finite().optional(),
30
+ compatibility: z.enum(['same-or-newer', 'exact']).optional(),
31
+ };
32
+ function learningArgs(value) {
33
+ return { mutationId: value.mutation_id, authority: value.authority, provenance: value.provenance, weight: value.weight, compatibility: value.compatibility };
34
+ }
35
+ function json(value) {
36
+ return { content: [{ type: 'text', text: JSON.stringify(value, null, 2) }] };
37
+ }
38
+ function failure(error) {
39
+ return { content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }], isError: true };
40
+ }
41
+ function jsonHandler(run, publicError) {
42
+ return async (args) => {
43
+ try {
44
+ return json(run(args));
45
+ }
46
+ catch (error) {
47
+ return failure(publicError ?? error);
48
+ }
49
+ };
50
+ }
51
+ function lifecycleValue(result) {
52
+ return result.ok ? result.artifact : { error: result.error };
53
+ }
54
+ function registerWritingTools(server, redactsSensitiveInputs) {
55
+ server.registerTool('hyv_build_profile', {
56
+ description: 'Build a portable VoiceDNA profile from at least two writing samples. The samples stay in memory and are not saved.',
57
+ inputSchema: { samples, avoid },
58
+ annotations: readOnly,
59
+ }, jsonHandler(({ samples: writingSamples, avoid: phrases }) => handlers.buildProfileForMcp(writingSamples, phrases)));
60
+ server.registerTool('hyv_profile_assess', {
61
+ description: 'Assess local sample readiness before building a VoiceDNA profile. It returns aggregate counts and one-way sample digests, never stored sample text or an authorship verdict.',
62
+ inputSchema: { samples }, annotations: readOnly,
63
+ }, async ({ samples: writingSamples }) => json(handlers.assessProfileForMcp(writingSamples)));
64
+ server.registerTool('hyv_analyze', {
65
+ description: 'Run separate VoiceDNA and AI Editor checks plus a non-scoring Unicode hygiene inspection against a draft using a portable profile JSON string.',
66
+ inputSchema: { draft: writing, profile_json: profileJson, writing_brief_json: writingBriefJson.optional() },
67
+ annotations: readOnly,
68
+ }, jsonHandler(({ draft, profile_json, writing_brief_json }) => handlers.analyzeForMcp(draft, profile_json, writing_brief_json)));
69
+ server.registerTool('hyv_strict_check', {
70
+ description: 'Run the calibrated local strict-quality gate. It requires a Profile v3 and local writing samples; it returns strict-ready, needs-human-review, or blocked without changing text or learning state.',
71
+ inputSchema: { draft: writing, profile_json: profileJson, samples, writing_brief_json: writingBriefJson.optional() },
72
+ annotations: readOnly,
73
+ }, jsonHandler(({ draft, profile_json, samples: localSamples, writing_brief_json }) => handlers.strictCheckForMcp(draft, profile_json, localSamples, writing_brief_json)));
74
+ server.registerTool('hyv_score', {
75
+ description: 'Score a draft against explicit held-out local samples. It reports a VoiceDNA component vector and the writer’s own similarity band, or abstains when channel or language evidence is inadequate. It is not an authorship score.',
76
+ inputSchema: { draft: writing, profile_json: profileJson, samples: heldoutSamples, channel: z.enum(['general', 'email', 'chat', 'long-form', 'social', 'docs']).optional() },
77
+ annotations: readOnly,
78
+ }, jsonHandler(({ draft, profile_json, samples: localSamples, channel }) => handlers.scoreHeldoutForMcp(draft, profile_json, localSamples, channel)));
79
+ server.registerTool('hyv_backtest', {
80
+ description: 'Score a caller-supplied reconstruction against a held-out target without generating text. Returns separate preservation, AI Editor, and held-out-band reports without returning prose.',
81
+ inputSchema: { context: writing, target: writing, candidate: writing, profile_json: profileJson, samples: heldoutSamples },
82
+ annotations: readOnly,
83
+ }, jsonHandler(({ context, target, candidate, profile_json, samples: localSamples }) => handlers.backtestForMcp(context, target, candidate, profile_json, localSamples)));
84
+ server.registerTool('hyv_evaluate_local', {
85
+ description: 'Run a deterministic optional local evaluation composite: train-only TF-IDF logistic proxy, content F1, AI-tell change, and stylometric cosine. It groups paragraph IDs before splitting and is not an authorship verdict.',
86
+ inputSchema: { input: writing, candidate: writing, user: evalParagraphs, ai_shadow: evalParagraphs },
87
+ annotations: readOnly,
88
+ }, jsonHandler(({ input, candidate, user, ai_shadow }) => handlers.evaluateLocalForMcp(input, candidate, user.map((item) => ({ paragraphId: item.paragraph_id, text: item.text })), ai_shadow.map((item) => ({ paragraphId: item.paragraph_id, text: item.text })))));
89
+ server.registerTool('hyv_hygiene', {
90
+ description: 'Inspect text for zero-width characters, bidirectional controls, Unicode tag characters, and unusual spaces without changing it or requiring a voice profile.',
91
+ inputSchema: { draft: hygieneText },
92
+ annotations: readOnly,
93
+ }, async ({ draft }) => json(handlers.inspectHygieneForMcp(draft)));
94
+ server.registerTool('hyv_inspect_hidden_text', {
95
+ description: 'Inspect hidden text controls with a non-mutating policy report. Findings are not watermark verdicts.',
96
+ inputSchema: { text: hygieneText, policy_json: lifecycleJson.optional() }, annotations: readOnly,
97
+ }, async ({ text, policy_json }) => json(handlers.inspectHiddenTextForMcp(text, policy_json)));
98
+ server.registerTool('hyv_apply_hidden_text_policy', {
99
+ description: 'Apply only explicitly approved minimal hidden-text removals and return hashes, exact changes, and remaining review findings.',
100
+ inputSchema: { text: hygieneText, policy_json: lifecycleJson }, annotations: localWrite,
101
+ }, async ({ text, policy_json }) => json(handlers.applyHiddenTextPolicyForMcp(text, policy_json)));
102
+ server.registerTool('hyv_final_check', {
103
+ description: 'Gate exact user-facing text from any model, tool, or interface. Returns output only when clean or after removing a leading byte-order mark; unresolved hidden characters withhold output.',
104
+ inputSchema: { text: hygieneText },
105
+ annotations: readOnly,
106
+ }, async ({ text }) => json(handlers.finalOutputCheckForMcp(text)));
107
+ server.registerTool('hyv_delivery_check', {
108
+ description: 'Run an opt-in local delivery-integrity check for placeholders, likely secrets, local links, and supplied citation IDs. It never fetches URLs and is separate from final-check.',
109
+ inputSchema: { text: hygieneText, policy_json: lifecycleJson.optional() }, annotations: readOnly,
110
+ }, jsonHandler(({ text, policy_json }) => handlers.deliveryCheckForMcp(text, policy_json)));
111
+ server.registerTool('hyv_fact_lint', {
112
+ description: 'Compare a draft against explicitly supplied local evidence text. It is a source-consistency check, not a truth service, and does not make network requests.',
113
+ inputSchema: { draft: writing, sources_json: factSourcesJson, metadata_json: lifecycleJson.optional() }, annotations: readOnly,
114
+ }, jsonHandler(({ draft, sources_json, metadata_json }) => handlers.factLintForMcp(draft, sources_json, metadata_json)));
115
+ server.registerTool('hyv_mcp_capabilities', {
116
+ description: 'Return the stable core MCP tool names and the compatibility-preserved advanced surface.', inputSchema: {}, annotations: readOnly,
117
+ }, async () => json({ version: '1', serverVersion: HYV_VERSION, core: ['hyv_analyze', 'hyv_verify', 'hyv_fact_lint', 'hyv_final_check'], advanced: ['hyv_logic_lint', 'hyv_delivery_check', 'hyv_profile_assess'], sensitiveInputRedaction: redactsSensitiveInputs }));
118
+ server.registerTool('hyv_logic_lint', {
119
+ description: 'Run the deterministic document-coherence gate. It detects configured topic drift, unanchored inference, and direct internal contradictions; it does not verify facts or approve publication.',
120
+ inputSchema: { draft: writing, writing_brief_json: writingBriefJson.optional() },
121
+ annotations: readOnly,
122
+ }, jsonHandler(({ draft, writing_brief_json }) => handlers.logicLintForMcp(draft, writing_brief_json)));
123
+ server.registerTool('hyv_rewrite_prompt', {
124
+ description: 'Create a strict constrained editing brief. Every active AI Editor finding is a required repair; explicit local examples remain advisory cadence evidence. It does not rewrite the draft or call a model.',
125
+ inputSchema: { draft: writing, profile_json: profileJson, writing_brief_json: writingBriefJson.optional(), examples: writingExamples.optional() },
126
+ annotations: readOnly,
127
+ }, jsonHandler(({ draft, profile_json, writing_brief_json, examples }) => handlers.rewritePromptForMcp(draft, profile_json, {}, writing_brief_json, examples)));
128
+ server.registerTool('hyv_find_writing_examples', {
129
+ description: 'Find up to three redacted excerpts from explicit in-memory local samples. Returns basenames only and never writes an index.',
130
+ inputSchema: { query: writing, examples: writingExamples },
131
+ annotations: readOnly,
132
+ }, jsonHandler(({ query, examples }) => handlers.findWritingExamplesForMcp(query, examples)));
133
+ server.registerTool('hyv_prepare_rewrite', {
134
+ description: 'Prepare a strict local, versioned rewrite task. Every active AI Editor finding is eligible for source-faithful repair; the caller may forward the task to a provider only by explicit choice.',
135
+ inputSchema: { draft: writing, profile_json: profileJson, copy_spec_json: copySpecJson.optional(), writing_brief_json: writingBriefJson.optional() },
136
+ annotations: readOnly,
137
+ }, jsonHandler(({ draft, profile_json, copy_spec_json, writing_brief_json }) => handlers.prepareRewriteForMcp(draft, profile_json, copy_spec_json, writing_brief_json)));
138
+ server.registerTool('hyv_apply_rewrite', {
139
+ description: 'Validate a model response to a prepared task, then reject any candidate with an unresolved active AI Editor finding. It never calls a provider or stores source or candidate text.',
140
+ inputSchema: { task_json: z.string().min(1).max(250_000), response_json: z.string().min(1).max(100_000), profile_json: profileJson },
141
+ annotations: readOnly,
142
+ }, jsonHandler(({ task_json, response_json, profile_json }) => handlers.applyRewriteForMcp(task_json, response_json, profile_json)));
143
+ server.registerTool('hyv_prepare_judgment', {
144
+ description: 'Prepare a versioned pre-edit or post-candidate judgment task. It does not call a model.',
145
+ inputSchema: {
146
+ stage: z.enum(['pre-edit', 'post-candidate']),
147
+ kind: z.enum(['triage', 'argument', 'form', 'polarity', 'flatness', 'semantic']),
148
+ draft: writing,
149
+ profile_json: profileJson,
150
+ candidate: writing.optional(),
151
+ },
152
+ annotations: readOnly,
153
+ }, jsonHandler(({ stage, kind, draft, profile_json, candidate }) => handlers.prepareJudgmentForMcp(stage, kind, draft, profile_json, candidate)));
154
+ server.registerTool('hyv_reduce_judgment', {
155
+ description: 'Reduce bound judgment envelopes into SHIP, EDIT, REBUILD, CLEAR, or ESCALATE. It does not call a model.',
156
+ inputSchema: { envelopes_json: z.string().min(1).max(250_000) },
157
+ annotations: readOnly,
158
+ }, jsonHandler(({ envelopes_json }) => handlers.reduceJudgmentForMcp(envelopes_json)));
159
+ server.registerTool('hyv_verify', {
160
+ description: 'Verify a revised candidate against an original draft and portable profile. Every active AI Editor finding fails default verification; learning state remains unchanged.',
161
+ inputSchema: { original: writing, candidate: writing, profile_json: profileJson, writing_brief_json: writingBriefJson.optional() },
162
+ annotations: localRead,
163
+ }, jsonHandler(({ original, candidate, profile_json, writing_brief_json }) => handlers.verifyForMcp(original, candidate, profile_json, writing_brief_json)));
164
+ server.registerTool('hyv_verify_copy_spec', {
165
+ description: 'Verify a candidate against strict default voice gates and a local CopySpec. Every active AI Editor finding fails verification; immutable claims remain verbatim unless atoms are supplied, and prohibited claims fail closed.',
166
+ inputSchema: { original: writing, candidate: writing, profile_json: profileJson, copy_spec_json: copySpecJson, writing_brief_json: writingBriefJson.optional() },
167
+ annotations: localRead,
168
+ }, jsonHandler(({ original, candidate, profile_json, copy_spec_json, writing_brief_json }) => handlers.verifyCopySpecForMcp(original, candidate, profile_json, copy_spec_json, writing_brief_json)));
169
+ server.registerTool('hyv_batch_analyze', {
170
+ description: 'Inspect two to one hundred drafts for repeated opening and closing sentences. It returns advisory batch findings and does not store the drafts.',
171
+ inputSchema: { drafts: z.array(writing).min(2).max(100) },
172
+ annotations: readOnly,
173
+ }, jsonHandler(({ drafts }) => handlers.analyzeBatchForMcp(drafts)));
174
+ server.registerTool('hyv_patterns', {
175
+ description: 'List the exact AI Editor rules that run in this extension.',
176
+ inputSchema: {},
177
+ annotations: readOnly,
178
+ }, async () => json(handlers.patternsForMcp()));
179
+ }
180
+ function registerLearningTools(server) {
181
+ server.registerTool('hyv_learning_inspect', {
182
+ description: 'Inspect profile-scoped learning receipts without returning stored instruction or draft text.',
183
+ inputSchema: { profile_json: profileJson }, annotations: readOnly,
184
+ }, jsonHandler(({ profile_json }) => handlers.inspectLearningForMcp(profile_json)));
185
+ server.registerTool('hyv_learning_record', {
186
+ description: 'Record an explicit profile-scoped learning instruction with authority and provenance metadata.',
187
+ inputSchema: { profile_json: profileJson, instruction: z.string().min(1).max(240), ...learningOptions }, annotations: localWrite,
188
+ }, jsonHandler((args) => handlers.recordLearningForMcp(args.profile_json, args.instruction, learningArgs(args))));
189
+ server.registerTool('hyv_learning_ratify', {
190
+ description: 'Ratify a learning event for a Profile v3 revision.',
191
+ inputSchema: { profile_json: profileJson, event_id: z.string().min(1).max(200), ...learningOptions }, annotations: localWrite,
192
+ }, jsonHandler((args) => handlers.ratifyLearningForMcp(args.profile_json, args.event_id, learningArgs(args))));
193
+ server.registerTool('hyv_learning_supersede', {
194
+ description: 'Supersede a learning event for a Profile v3 revision.',
195
+ inputSchema: { profile_json: profileJson, event_id: z.string().min(1).max(200), ...learningOptions }, annotations: localDelete,
196
+ }, jsonHandler((args) => handlers.supersedeLearningForMcp(args.profile_json, args.event_id, learningArgs(args))));
197
+ server.registerTool('hyv_learning_migrate', {
198
+ description: 'Migrate Profile v2 learning into a Profile v3 identity.',
199
+ inputSchema: { source_profile_json: profileJson, target_profile_json: profileJson, ...learningOptions }, annotations: localWrite,
200
+ }, jsonHandler((args) => handlers.migrateLearningForMcp(args.source_profile_json, args.target_profile_json, learningArgs(args))));
201
+ server.registerTool('hyv_learning_clear', {
202
+ description: 'Delete all local learning state for a profile.',
203
+ inputSchema: { profile_json: profileJson }, annotations: localDelete,
204
+ }, jsonHandler(({ profile_json }) => handlers.clearLearningForMcp(profile_json)));
205
+ }
206
+ function registerLifecycleTools(server, redactsSensitiveInputs) {
207
+ server.registerTool('hyv_lifecycle_prepare_semantic', {
208
+ description: 'Prepare a normal semantic-review task and its initial immutable lifecycle artifact.',
209
+ inputSchema: { deterministic_json: lifecycleJson, binding_json: lifecycleJson, receipt_json: lifecycleJson, policy: z.literal('normal'), allowed_violations: z.array(semanticViolation).max(5) },
210
+ annotations: localRead,
211
+ }, jsonHandler((args) => handlers.prepareLifecycleForMcp(args.deterministic_json, args.binding_json, args.receipt_json, args.policy, args.allowed_violations)));
212
+ server.registerTool('hyv_lifecycle_submit_verdict', {
213
+ description: 'Submit one normal-policy semantic verdict using the server-installed evaluator authorization context.',
214
+ inputSchema: { artifact_json: lifecycleJson, task_json: lifecycleJson, evaluator_id: evaluatorId, verdict_json: lifecycleJson },
215
+ annotations: localRead,
216
+ }, jsonHandler((args) => lifecycleValue(handlers.submitSemanticVerdictForMcp(args.artifact_json, args.task_json, args.evaluator_id, args.verdict_json, loadApprovalContext()))));
217
+ server.registerTool('hyv_lifecycle_inspect', {
218
+ description: 'Validate and inspect an immutable lifecycle artifact without exposing bound source or candidate hashes.',
219
+ inputSchema: { artifact_json: lifecycleJson }, annotations: localRead,
220
+ }, jsonHandler(({ artifact_json }) => handlers.inspectLifecycleForMcp(artifact_json)));
221
+ if (redactsSensitiveInputs) {
222
+ server.registerTool('hyv_lifecycle_finalize', {
223
+ description: 'Finalize an authorized human approval or rejection. Capability input requires host-guaranteed sensitive-input redaction.',
224
+ inputSchema: { artifact_json: lifecycleJson, decision_json: lifecycleJson, capability_json: lifecycleJson.optional() },
225
+ annotations: localRead,
226
+ }, jsonHandler((args) => lifecycleValue(handlers.finalizeLifecycleForMcp(args.artifact_json, args.decision_json, loadApprovalContext(), args.capability_json)), 'Lifecycle finalization failed.'));
227
+ server.registerTool('hyv_lifecycle_validate_final_approval', {
228
+ description: 'Validate a final-approval capability against the server-installed trust context.',
229
+ inputSchema: { artifact_json: lifecycleJson, capability_json: lifecycleJson }, annotations: localRead,
230
+ }, jsonHandler((args) => handlers.validateFinalApprovalForMcp(args.artifact_json, args.capability_json, loadApprovalContext()), 'Capability validation failed.'));
231
+ server.registerTool('hyv_learning_record_approved', {
232
+ description: 'Record one approval-revalidated, deterministic, text-free learning event.',
233
+ inputSchema: { ready_json: lifecycleJson, approved_json: lifecycleJson, original: approvedLearningText, candidate: approvedLearningText, profile_json: profileJson, decision_json: lifecycleJson, capability_json: lifecycleJson, copy_spec_json: copySpecJson.optional(), writing_brief_json: writingBriefJson.optional() },
234
+ annotations: localWrite,
235
+ }, jsonHandler((args) => ({ status: handlers.recordApprovedLearningForMcp({ readyJson: args.ready_json, approvedJson: args.approved_json, source: args.original, candidate: args.candidate, profileJson: args.profile_json, decisionJson: args.decision_json, capabilityJson: args.capability_json, context: loadApprovalContext(), copySpecJson: args.copy_spec_json, writingBriefJson: args.writing_brief_json }) }), 'Approved learning was not authorized.'));
236
+ server.registerTool('hyv_prepare_rebuild', {
237
+ description: 'Prepare a rebuild task only after an upstream REBUILD recommendation, CopySpec, and signed rebuild-authorization capability. Capability input requires host-guaranteed sensitive-input redaction.',
238
+ inputSchema: {
239
+ draft: writing,
240
+ profile_json: profileJson,
241
+ reduction_json: lifecycleJson,
242
+ copy_spec_json: copySpecJson,
243
+ capability_json: lifecycleJson,
244
+ writing_brief_json: writingBriefJson.optional(),
245
+ recomposition_policy_json: lifecycleJson.optional(),
246
+ },
247
+ annotations: localRead,
248
+ }, jsonHandler((args) => handlers.prepareRebuildForMcp(args.draft, args.profile_json, args.reduction_json, args.copy_spec_json, args.capability_json, loadApprovalContext(), args.writing_brief_json, args.recomposition_policy_json), 'Rebuild preparation failed.'));
249
+ server.registerTool('hyv_apply_rebuild', {
250
+ description: 'Validate a whole-document rebuild response against a prepared authorized rebuild task and reject unresolved active AI Editor findings. Capability input requires host-guaranteed sensitive-input redaction. It never calls a provider.',
251
+ inputSchema: { task_json: lifecycleJson, response_json: z.string().min(1).max(100_000), profile_json: profileJson, capability_json: lifecycleJson },
252
+ annotations: localRead,
253
+ }, jsonHandler((args) => handlers.applyRebuildForMcp(args.task_json, args.response_json, args.profile_json, args.capability_json, loadApprovalContext()), 'Rebuild application failed.'));
254
+ server.registerTool('hyv_rebuild_writer_request', {
255
+ description: 'Create the writer-only payload for a prepared rebuild. It excludes source draft, capability, profile body, and validation evidence.',
256
+ inputSchema: { task_json: lifecycleJson }, annotations: localRead,
257
+ }, jsonHandler(({ task_json }) => handlers.rebuildWriterRequestForMcp(task_json), 'Writer request could not be prepared.'));
258
+ }
259
+ else {
260
+ server.registerTool('hyv_lifecycle_finalize', {
261
+ description: 'Record an authorized human rejection. Approval is unavailable because this host does not guarantee sensitive-input redaction.',
262
+ inputSchema: { artifact_json: lifecycleJson, decision_json: lifecycleJson }, annotations: localRead,
263
+ }, jsonHandler((args) => lifecycleValue(handlers.finalizeRejectionForMcp(args.artifact_json, args.decision_json, loadApprovalContext())), 'Only rejection is available without sensitive-input redaction.'));
264
+ }
265
+ }
266
+ export function createMcpServer(redactsSensitiveInputs = process.env.HYV_MCP_SENSITIVE_INPUT_REDACTION === '1') {
267
+ const server = new McpServer({ name: 'hold-your-voice', version: HYV_VERSION });
268
+ registerWritingTools(server, redactsSensitiveInputs);
269
+ registerLearningTools(server);
270
+ registerLifecycleTools(server, redactsSensitiveInputs);
271
+ return server;
272
+ }
package/dist/mcp-tools.js CHANGED
@@ -119,9 +119,9 @@ export function logicLintForMcp(draft, writingBriefJson) {
119
119
  }
120
120
  export function factLintForMcp(draft, sourcesJson, metadataJson) {
121
121
  const sources = parsed(sourcesJson, 'Fact sources');
122
- if (!Array.isArray(sources) || !sources.every((source) => source && typeof source === 'object' && typeof source.id === 'string' && typeof source.text === 'string'))
122
+ if (!Array.isArray(sources) || !sources.every((source) => source && typeof source === 'object' && 'id' in source && typeof source.id === 'string' && 'text' in source && typeof source.text === 'string'))
123
123
  throw new Error('Fact sources are not valid.');
124
- return lintFacts({ draft, sources: sources, metadata: metadataJson ? parsed(metadataJson, 'Fact metadata') : undefined });
124
+ return lintFacts({ draft, sources, metadata: metadataJson ? parsed(metadataJson, 'Fact metadata') : undefined });
125
125
  }
126
126
  export function deliveryCheckForMcp(text, policyJson) {
127
127
  return inspectDeliveryIntegrity(text, policyJson ? parseDeliveryIntegrityPolicy(parsed(policyJson, 'Delivery policy')) : undefined);
package/dist/mcp.js CHANGED
@@ -1,262 +1,3 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
1
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
- import { z } from 'zod';
4
- import { analyzeBatchForMcp, analyzeForMcp, applyHiddenTextPolicyForMcp, applyRebuildForMcp, applyRewriteForMcp, assessProfileForMcp, buildProfileForMcp, clearLearningForMcp, deliveryCheckForMcp, factLintForMcp, finalOutputCheckForMcp, finalizeLifecycleForMcp, finalizeRejectionForMcp, inspectHiddenTextForMcp, inspectHygieneForMcp, inspectLearningForMcp, inspectLifecycleForMcp, logicLintForMcp, migrateLearningForMcp, patternsForMcp, prepareJudgmentForMcp, prepareLifecycleForMcp, prepareRebuildForMcp, prepareRewriteForMcp, ratifyLearningForMcp, rebuildWriterRequestForMcp, recordApprovedLearningForMcp, recordLearningForMcp, reduceJudgmentForMcp, rewritePromptForMcp, scoreHeldoutForMcp, strictCheckForMcp, submitSemanticVerdictForMcp, supersedeLearningForMcp, validateFinalApprovalForMcp, verifyCopySpecForMcp, verifyForMcp } from './mcp-tools.js';
5
- import { HYV_VERSION } from './version.js';
6
- import { loadApprovalContext } from './approval-context.js';
7
- import { findWritingExamplesForMcp } from './mcp-tools.js';
8
- import { backtestForMcp, evaluateLocalForMcp } from './mcp-tools.js';
9
- const writing = z.string().min(1).max(100_000);
10
- const hygieneText = z.string().max(100_000);
11
- const profileJson = z.string().min(1).max(50_000);
12
- const copySpecJson = z.string().min(1).max(250_000);
13
- const writingBriefJson = z.string().min(1).max(50_000);
14
- const samples = z.array(writing).min(2).max(20);
15
- const heldoutSamples = z.array(writing).min(3).max(20);
16
- const evalParagraphs = z.array(z.object({ paragraph_id: z.string().min(1).max(160), text: writing })).min(2).max(50);
17
- const writingExamples = z.array(z.object({ basename: z.string().min(1).max(160), text: writing })).min(1).max(64);
18
- const avoid = z.array(z.string().min(1).max(200)).max(50).optional();
19
- const lifecycleJson = z.string().min(1).max(1_048_576);
20
- const approvedLearningText = z.string().min(1).max(1_048_576);
21
- const evaluatorId = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/);
22
- const semanticViolation = z.enum(['action_change', 'dropped_object', 'unsupported_claim', 'constraint_weakened', 'clarity_regression']);
23
- const factSourcesJson = z.string().min(2).max(250_000);
24
- const redactsSensitiveInputs = process.env.HYV_MCP_SENSITIVE_INPUT_REDACTION === '1';
25
- const learningOptions = {
26
- mutation_id: z.string().min(1).max(200).optional(),
27
- authority: z.enum(['founder', 'team', 'system']).optional(),
28
- provenance: z.string().min(1).max(500).optional(),
29
- weight: z.number().positive().finite().optional(),
30
- compatibility: z.enum(['same-or-newer', 'exact']).optional(),
31
- };
32
- function learningArgs(value) {
33
- return { mutationId: value.mutation_id, authority: value.authority, provenance: value.provenance, weight: value.weight, compatibility: value.compatibility };
34
- }
35
- function json(value) {
36
- return { content: [{ type: 'text', text: JSON.stringify(value, null, 2) }] };
37
- }
38
- function failure(error) {
39
- return { content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }], isError: true };
40
- }
41
- function guardedJson(run, publicError) {
42
- try {
43
- return json(run());
44
- }
45
- catch (error) {
46
- return failure(publicError ? new Error(publicError) : error);
47
- }
48
- }
49
- function guardedLifecycle(run, publicError) {
50
- return guardedJson(() => {
51
- const result = run();
52
- return result.ok ? result.artifact : { error: result.error };
53
- }, publicError);
54
- }
55
- const server = new McpServer({ name: 'hold-your-voice', version: HYV_VERSION });
56
- server.registerTool('hyv_build_profile', {
57
- description: 'Build a portable VoiceDNA profile from at least two writing samples. The samples stay in memory and are not saved.',
58
- inputSchema: { samples, avoid },
59
- annotations: { readOnlyHint: true },
60
- }, async ({ samples: writingSamples, avoid: phrases }) => guardedJson(() => buildProfileForMcp(writingSamples, phrases)));
61
- server.registerTool('hyv_profile_assess', {
62
- description: 'Assess local sample readiness before building a VoiceDNA profile. It returns aggregate counts and one-way sample digests, never stored sample text or an authorship verdict.',
63
- inputSchema: { samples }, annotations: { readOnlyHint: true },
64
- }, async ({ samples: writingSamples }) => json(assessProfileForMcp(writingSamples)));
65
- server.registerTool('hyv_analyze', {
66
- description: 'Run separate VoiceDNA and AI Editor checks plus a non-scoring Unicode hygiene inspection against a draft using a portable profile JSON string.',
67
- inputSchema: { draft: writing, profile_json: profileJson, writing_brief_json: writingBriefJson.optional() },
68
- annotations: { readOnlyHint: true },
69
- }, async ({ draft, profile_json, writing_brief_json }) => guardedJson(() => analyzeForMcp(draft, profile_json, writing_brief_json)));
70
- server.registerTool('hyv_strict_check', {
71
- description: 'Run the calibrated local strict-quality gate. It requires a Profile v3 and local writing samples; it returns strict-ready, needs-human-review, or blocked without changing text or learning state.',
72
- inputSchema: { draft: writing, profile_json: profileJson, samples, writing_brief_json: writingBriefJson.optional() },
73
- annotations: { readOnlyHint: true },
74
- }, async ({ draft, profile_json, samples: localSamples, writing_brief_json }) => guardedJson(() => strictCheckForMcp(draft, profile_json, localSamples, writing_brief_json)));
75
- server.registerTool('hyv_score', {
76
- description: 'Score a draft against explicit held-out local samples. It reports a VoiceDNA component vector and the writer’s own similarity band, or abstains when channel or language evidence is inadequate. It is not an authorship score.',
77
- inputSchema: { draft: writing, profile_json: profileJson, samples: heldoutSamples, channel: z.enum(['general', 'email', 'chat', 'long-form', 'social', 'docs']).optional() },
78
- annotations: { readOnlyHint: true },
79
- }, async ({ draft, profile_json, samples: localSamples, channel }) => guardedJson(() => scoreHeldoutForMcp(draft, profile_json, localSamples, channel)));
80
- server.registerTool('hyv_backtest', {
81
- description: 'Score a caller-supplied reconstruction against a held-out target without generating text. Returns separate preservation, AI Editor, and held-out-band reports without returning prose.',
82
- inputSchema: { context: writing, target: writing, candidate: writing, profile_json: profileJson, samples: heldoutSamples },
83
- annotations: { readOnlyHint: true },
84
- }, async ({ context, target, candidate, profile_json, samples: localSamples }) => guardedJson(() => backtestForMcp(context, target, candidate, profile_json, localSamples)));
85
- server.registerTool('hyv_evaluate_local', {
86
- description: 'Run a deterministic optional local evaluation composite: train-only TF-IDF logistic proxy, content F1, AI-tell change, and stylometric cosine. It groups paragraph IDs before splitting and is not an authorship verdict.',
87
- inputSchema: { input: writing, candidate: writing, user: evalParagraphs, ai_shadow: evalParagraphs },
88
- annotations: { readOnlyHint: true },
89
- }, async ({ input, candidate, user, ai_shadow }) => guardedJson(() => evaluateLocalForMcp(input, candidate, user.map((item) => ({ paragraphId: item.paragraph_id, text: item.text })), ai_shadow.map((item) => ({ paragraphId: item.paragraph_id, text: item.text })))));
90
- server.registerTool('hyv_hygiene', {
91
- description: 'Inspect text for zero-width characters, bidirectional controls, Unicode tag characters, and unusual spaces without changing it or requiring a voice profile.',
92
- inputSchema: { draft: hygieneText },
93
- annotations: { readOnlyHint: true },
94
- }, async ({ draft }) => json(inspectHygieneForMcp(draft)));
95
- server.registerTool('hyv_inspect_hidden_text', {
96
- description: 'Inspect hidden text controls with a non-mutating policy report. Findings are not watermark verdicts.',
97
- inputSchema: { text: hygieneText, policy_json: lifecycleJson.optional() }, annotations: { readOnlyHint: true },
98
- }, async ({ text, policy_json }) => json(inspectHiddenTextForMcp(text, policy_json)));
99
- server.registerTool('hyv_apply_hidden_text_policy', {
100
- description: 'Apply only explicitly approved minimal hidden-text removals and return hashes, exact changes, and remaining review findings.',
101
- inputSchema: { text: hygieneText, policy_json: lifecycleJson }, annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
102
- }, async ({ text, policy_json }) => json(applyHiddenTextPolicyForMcp(text, policy_json)));
103
- server.registerTool('hyv_final_check', {
104
- description: 'Gate exact user-facing text from any model, tool, or interface. Returns output only when clean or after removing a leading byte-order mark; unresolved hidden characters withhold output.',
105
- inputSchema: { text: hygieneText },
106
- annotations: { readOnlyHint: true },
107
- }, async ({ text }) => json(finalOutputCheckForMcp(text)));
108
- server.registerTool('hyv_delivery_check', {
109
- description: 'Run an opt-in local delivery-integrity check for placeholders, likely secrets, local links, and supplied citation IDs. It never fetches URLs and is separate from final-check.',
110
- inputSchema: { text: hygieneText, policy_json: lifecycleJson.optional() }, annotations: { readOnlyHint: true },
111
- }, async ({ text, policy_json }) => guardedJson(() => deliveryCheckForMcp(text, policy_json)));
112
- server.registerTool('hyv_fact_lint', {
113
- description: 'Compare a draft against explicitly supplied local evidence text. It is a source-consistency check, not a truth service, and does not make network requests.',
114
- inputSchema: { draft: writing, sources_json: factSourcesJson, metadata_json: lifecycleJson.optional() }, annotations: { readOnlyHint: true },
115
- }, async ({ draft, sources_json, metadata_json }) => guardedJson(() => factLintForMcp(draft, sources_json, metadata_json)));
116
- server.registerTool('hyv_mcp_capabilities', {
117
- description: 'Return the stable core MCP tool names and the compatibility-preserved advanced surface.', inputSchema: {}, annotations: { readOnlyHint: true },
118
- }, async () => json({ version: '1', serverVersion: HYV_VERSION, core: ['hyv_analyze', 'hyv_verify', 'hyv_fact_lint', 'hyv_final_check'], advanced: ['hyv_logic_lint', 'hyv_delivery_check', 'hyv_profile_assess'], sensitiveInputRedaction: redactsSensitiveInputs }));
119
- server.registerTool('hyv_logic_lint', {
120
- description: 'Run the deterministic document-coherence gate. It detects configured topic drift, unanchored inference, and direct internal contradictions; it does not verify facts or approve publication.',
121
- inputSchema: { draft: writing, writing_brief_json: writingBriefJson.optional() },
122
- annotations: { readOnlyHint: true },
123
- }, async ({ draft, writing_brief_json }) => guardedJson(() => logicLintForMcp(draft, writing_brief_json)));
124
- server.registerTool('hyv_rewrite_prompt', {
125
- description: 'Create a strict constrained editing brief. Every active AI Editor finding is a required repair; explicit local examples remain advisory cadence evidence. It does not rewrite the draft or call a model.',
126
- inputSchema: { draft: writing, profile_json: profileJson, writing_brief_json: writingBriefJson.optional(), examples: writingExamples.optional() },
127
- annotations: { readOnlyHint: true },
128
- }, async ({ draft, profile_json, writing_brief_json, examples }) => guardedJson(() => rewritePromptForMcp(draft, profile_json, {}, writing_brief_json, examples)));
129
- server.registerTool('hyv_find_writing_examples', {
130
- description: 'Find up to three redacted excerpts from explicit in-memory local samples. Returns basenames only and never writes an index.',
131
- inputSchema: { query: writing, examples: writingExamples },
132
- annotations: { readOnlyHint: true },
133
- }, async ({ query, examples }) => guardedJson(() => findWritingExamplesForMcp(query, examples)));
134
- server.registerTool('hyv_prepare_rewrite', {
135
- description: 'Prepare a strict local, versioned rewrite task. Every active AI Editor finding is eligible for source-faithful repair; the caller may forward the task to a provider only by explicit choice.',
136
- inputSchema: { draft: writing, profile_json: profileJson, copy_spec_json: copySpecJson.optional(), writing_brief_json: writingBriefJson.optional() },
137
- annotations: { readOnlyHint: true },
138
- }, async ({ draft, profile_json, copy_spec_json, writing_brief_json }) => guardedJson(() => prepareRewriteForMcp(draft, profile_json, copy_spec_json, writing_brief_json)));
139
- server.registerTool('hyv_apply_rewrite', {
140
- description: 'Validate a model response to a prepared task, then reject any candidate with an unresolved active AI Editor finding. It never calls a provider or stores source or candidate text.',
141
- inputSchema: { task_json: z.string().min(1).max(250_000), response_json: z.string().min(1).max(100_000), profile_json: profileJson },
142
- annotations: { readOnlyHint: true },
143
- }, async ({ task_json, response_json, profile_json }) => guardedJson(() => applyRewriteForMcp(task_json, response_json, profile_json)));
144
- server.registerTool('hyv_prepare_judgment', {
145
- description: 'Prepare a versioned pre-edit or post-candidate judgment task. It does not call a model.',
146
- inputSchema: {
147
- stage: z.enum(['pre-edit', 'post-candidate']),
148
- kind: z.enum(['triage', 'argument', 'form', 'polarity', 'flatness', 'semantic']),
149
- draft: writing,
150
- profile_json: profileJson,
151
- candidate: writing.optional(),
152
- },
153
- annotations: { readOnlyHint: true },
154
- }, async ({ stage, kind, draft, profile_json, candidate }) => guardedJson(() => prepareJudgmentForMcp(stage, kind, draft, profile_json, candidate)));
155
- server.registerTool('hyv_reduce_judgment', {
156
- description: 'Reduce bound judgment envelopes into SHIP, EDIT, REBUILD, CLEAR, or ESCALATE. It does not call a model.',
157
- inputSchema: { envelopes_json: z.string().min(1).max(250_000) },
158
- annotations: { readOnlyHint: true },
159
- }, async ({ envelopes_json }) => guardedJson(() => reduceJudgmentForMcp(envelopes_json)));
160
- server.registerTool('hyv_verify', {
161
- description: 'Verify a revised candidate against an original draft and portable profile. Every active AI Editor finding fails default verification; learning state remains unchanged.',
162
- inputSchema: { original: writing, candidate: writing, profile_json: profileJson, writing_brief_json: writingBriefJson.optional() },
163
- annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
164
- }, async ({ original, candidate, profile_json, writing_brief_json }) => guardedJson(() => verifyForMcp(original, candidate, profile_json, writing_brief_json)));
165
- server.registerTool('hyv_verify_copy_spec', {
166
- description: 'Verify a candidate against strict default voice gates and a local CopySpec. Every active AI Editor finding fails verification; immutable claims remain verbatim unless atoms are supplied, and prohibited claims fail closed.',
167
- inputSchema: { original: writing, candidate: writing, profile_json: profileJson, copy_spec_json: copySpecJson, writing_brief_json: writingBriefJson.optional() },
168
- annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
169
- }, async ({ original, candidate, profile_json, copy_spec_json, writing_brief_json }) => guardedJson(() => verifyCopySpecForMcp(original, candidate, profile_json, copy_spec_json, writing_brief_json)));
170
- server.registerTool('hyv_batch_analyze', {
171
- description: 'Inspect two to one hundred drafts for repeated opening and closing sentences. It returns advisory batch findings and does not store the drafts.',
172
- inputSchema: { drafts: z.array(writing).min(2).max(100) },
173
- annotations: { readOnlyHint: true },
174
- }, async ({ drafts }) => guardedJson(() => analyzeBatchForMcp(drafts)));
175
- server.registerTool('hyv_patterns', {
176
- description: 'List the exact AI Editor rules that run in this extension.',
177
- inputSchema: {},
178
- annotations: { readOnlyHint: true },
179
- }, async () => json(patternsForMcp()));
180
- server.registerTool('hyv_learning_inspect', {
181
- description: 'Inspect profile-scoped learning receipts without returning stored instruction or draft text.',
182
- inputSchema: { profile_json: profileJson }, annotations: { readOnlyHint: true },
183
- }, async ({ profile_json }) => guardedJson(() => inspectLearningForMcp(profile_json)));
184
- server.registerTool('hyv_learning_record', {
185
- description: 'Record an explicit profile-scoped learning instruction with authority and provenance metadata.',
186
- inputSchema: { profile_json: profileJson, instruction: z.string().min(1).max(240), ...learningOptions }, annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
187
- }, async (args) => guardedJson(() => recordLearningForMcp(args.profile_json, args.instruction, learningArgs(args))));
188
- server.registerTool('hyv_learning_ratify', {
189
- description: 'Ratify a learning event for a Profile v3 revision.',
190
- inputSchema: { profile_json: profileJson, event_id: z.string().min(1).max(200), ...learningOptions }, annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
191
- }, async (args) => guardedJson(() => ratifyLearningForMcp(args.profile_json, args.event_id, learningArgs(args))));
192
- server.registerTool('hyv_learning_supersede', {
193
- description: 'Supersede a learning event for a Profile v3 revision.',
194
- inputSchema: { profile_json: profileJson, event_id: z.string().min(1).max(200), ...learningOptions }, annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
195
- }, async (args) => guardedJson(() => supersedeLearningForMcp(args.profile_json, args.event_id, learningArgs(args))));
196
- server.registerTool('hyv_learning_migrate', {
197
- description: 'Migrate Profile v2 learning into a Profile v3 identity.',
198
- inputSchema: { source_profile_json: profileJson, target_profile_json: profileJson, ...learningOptions }, annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
199
- }, async (args) => guardedJson(() => migrateLearningForMcp(args.source_profile_json, args.target_profile_json, learningArgs(args))));
200
- server.registerTool('hyv_learning_clear', {
201
- description: 'Delete all local learning state for a profile.',
202
- inputSchema: { profile_json: profileJson }, annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
203
- }, async ({ profile_json }) => guardedJson(() => clearLearningForMcp(profile_json)));
204
- server.registerTool('hyv_lifecycle_prepare_semantic', {
205
- description: 'Prepare a normal semantic-review task and its initial immutable lifecycle artifact.',
206
- inputSchema: { deterministic_json: lifecycleJson, binding_json: lifecycleJson, receipt_json: lifecycleJson, policy: z.literal('normal'), allowed_violations: z.array(semanticViolation).max(5) },
207
- annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
208
- }, async (args) => guardedJson(() => prepareLifecycleForMcp(args.deterministic_json, args.binding_json, args.receipt_json, args.policy, args.allowed_violations)));
209
- server.registerTool('hyv_lifecycle_submit_verdict', {
210
- description: 'Submit one normal-policy semantic verdict using the server-installed evaluator authorization context.',
211
- inputSchema: { artifact_json: lifecycleJson, task_json: lifecycleJson, evaluator_id: evaluatorId, verdict_json: lifecycleJson },
212
- annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
213
- }, async (args) => guardedLifecycle(() => submitSemanticVerdictForMcp(args.artifact_json, args.task_json, args.evaluator_id, args.verdict_json, loadApprovalContext())));
214
- server.registerTool('hyv_lifecycle_inspect', {
215
- description: 'Validate and inspect an immutable lifecycle artifact without exposing bound source or candidate hashes.',
216
- inputSchema: { artifact_json: lifecycleJson }, annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
217
- }, async ({ artifact_json }) => guardedJson(() => inspectLifecycleForMcp(artifact_json)));
218
- if (redactsSensitiveInputs) {
219
- server.registerTool('hyv_lifecycle_finalize', {
220
- description: 'Finalize an authorized human approval or rejection. Capability input requires host-guaranteed sensitive-input redaction.',
221
- inputSchema: { artifact_json: lifecycleJson, decision_json: lifecycleJson, capability_json: lifecycleJson.optional() },
222
- annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
223
- }, async (args) => guardedLifecycle(() => finalizeLifecycleForMcp(args.artifact_json, args.decision_json, loadApprovalContext(), args.capability_json), 'Lifecycle finalization failed.'));
224
- server.registerTool('hyv_lifecycle_validate_final_approval', {
225
- description: 'Validate a final-approval capability against the server-installed trust context.',
226
- inputSchema: { artifact_json: lifecycleJson, capability_json: lifecycleJson }, annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
227
- }, async (args) => guardedJson(() => validateFinalApprovalForMcp(args.artifact_json, args.capability_json, loadApprovalContext()), 'Capability validation failed.'));
228
- server.registerTool('hyv_learning_record_approved', {
229
- description: 'Record one approval-revalidated, deterministic, text-free learning event.',
230
- inputSchema: { ready_json: lifecycleJson, approved_json: lifecycleJson, original: approvedLearningText, candidate: approvedLearningText, profile_json: profileJson, decision_json: lifecycleJson, capability_json: lifecycleJson, copy_spec_json: copySpecJson.optional(), writing_brief_json: writingBriefJson.optional() },
231
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
232
- }, async (args) => guardedJson(() => ({ status: recordApprovedLearningForMcp({ readyJson: args.ready_json, approvedJson: args.approved_json, source: args.original, candidate: args.candidate, profileJson: args.profile_json, decisionJson: args.decision_json, capabilityJson: args.capability_json, context: loadApprovalContext(), copySpecJson: args.copy_spec_json, writingBriefJson: args.writing_brief_json }) }), 'Approved learning was not authorized.'));
233
- server.registerTool('hyv_prepare_rebuild', {
234
- description: 'Prepare a rebuild task only after an upstream REBUILD recommendation, CopySpec, and signed rebuild-authorization capability. Capability input requires host-guaranteed sensitive-input redaction.',
235
- inputSchema: {
236
- draft: writing,
237
- profile_json: profileJson,
238
- reduction_json: lifecycleJson,
239
- copy_spec_json: copySpecJson,
240
- capability_json: lifecycleJson,
241
- writing_brief_json: writingBriefJson.optional(),
242
- recomposition_policy_json: lifecycleJson.optional(),
243
- },
244
- annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
245
- }, async (args) => guardedJson(() => prepareRebuildForMcp(args.draft, args.profile_json, args.reduction_json, args.copy_spec_json, args.capability_json, loadApprovalContext(), args.writing_brief_json, args.recomposition_policy_json), 'Rebuild preparation failed.'));
246
- server.registerTool('hyv_apply_rebuild', {
247
- description: 'Validate a whole-document rebuild response against a prepared authorized rebuild task and reject unresolved active AI Editor findings. Capability input requires host-guaranteed sensitive-input redaction. It never calls a provider.',
248
- inputSchema: { task_json: lifecycleJson, response_json: z.string().min(1).max(100_000), profile_json: profileJson, capability_json: lifecycleJson },
249
- annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
250
- }, async (args) => guardedJson(() => applyRebuildForMcp(args.task_json, args.response_json, args.profile_json, args.capability_json, loadApprovalContext()), 'Rebuild application failed.'));
251
- server.registerTool('hyv_rebuild_writer_request', {
252
- description: 'Create the writer-only payload for a prepared rebuild. It excludes source draft, capability, profile body, and validation evidence.',
253
- inputSchema: { task_json: lifecycleJson }, annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
254
- }, async ({ task_json }) => guardedJson(() => rebuildWriterRequestForMcp(task_json), 'Writer request could not be prepared.'));
255
- }
256
- else {
257
- server.registerTool('hyv_lifecycle_finalize', {
258
- description: 'Record an authorized human rejection. Approval is unavailable because this host does not guarantee sensitive-input redaction.',
259
- inputSchema: { artifact_json: lifecycleJson, decision_json: lifecycleJson }, annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
260
- }, async (args) => guardedLifecycle(() => finalizeRejectionForMcp(args.artifact_json, args.decision_json, loadApprovalContext()), 'Only rejection is available without sensitive-input redaction.'));
261
- }
262
- await server.connect(new StdioServerTransport());
2
+ import { createMcpServer } from './mcp-server.js';
3
+ await createMcpServer().connect(new StdioServerTransport());