@morphism-systems/agentic-math 0.1.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/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # @morphism-systems/agentic-math
2
+
3
+ Category theory MCP server for AI agents. Provides morphism composition, functor mapping, natural transformations, entropy computation, and convergence checking.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npx @morphism-systems/agentic-math
9
+ ```
10
+
11
+ ## MCP Config
12
+
13
+ Add to your `.claude/mcp.json` or Cursor MCP settings:
14
+
15
+ ```json
16
+ {
17
+ "mcpServers": {
18
+ "morphism-math": {
19
+ "command": "npx",
20
+ "args": ["@morphism-systems/agentic-math"]
21
+ }
22
+ }
23
+ }
24
+ ```
25
+
26
+ ## Tools
27
+
28
+ | Tool | Description |
29
+ |------|-------------|
30
+ | `morphism_compose` | Compose two morphisms f . g |
31
+ | `functor_map_object` | Apply a functor to an object |
32
+ | `functor_map_morphism` | Apply a functor to a morphism |
33
+ | `natural_transform_check` | Check naturality condition |
34
+ | `natural_transform_compose` | Vertical composition of natural transformations |
35
+ | `entropy_compute` | Shannon entropy H(s) = -sum(p_i ln(p_i)) |
36
+ | `convergence_check` | Check if governance converges (kappa < 1) |
37
+ | `drift_check` | Compute drift delta between states |
38
+ | `analyze_scores` | Full governance score analysis |
39
+
40
+ ## Why Morphism
41
+
42
+ Morphism provides mathematically-grounded governance for AI agent fleets. Instead of heuristics, it uses category theory and the Banach contraction principle to guarantee convergence. [Learn more](https://morphism.systems).
43
+
44
+ ## License
45
+
46
+ MIT
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,480 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+
6
+ // src/server.ts
7
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
+
9
+ // src/tools/compose.ts
10
+ import { z as z2 } from "zod";
11
+
12
+ // src/engine/morphism.ts
13
+ import { z } from "zod";
14
+ var MorphismSchema = z.object({
15
+ name: z.string(),
16
+ source: z.string(),
17
+ target: z.string(),
18
+ metadata: z.record(z.unknown()).default({})
19
+ });
20
+ function compose(f, g) {
21
+ if (f.source !== g.target) {
22
+ throw new TypeError(
23
+ `Cannot compose: ${f.name}.source (${f.source}) != ${g.name}.target (${g.target})`
24
+ );
25
+ }
26
+ return {
27
+ name: `${f.name} . ${g.name}`,
28
+ source: g.source,
29
+ target: f.target,
30
+ metadata: { composed_from: [f.name, g.name] }
31
+ };
32
+ }
33
+
34
+ // src/tools/compose.ts
35
+ var ComposeInput = z2.object({
36
+ f: MorphismSchema.describe("First morphism (applied second)"),
37
+ g: MorphismSchema.describe("Second morphism (applied first)")
38
+ });
39
+ function morphismCompose(input) {
40
+ return compose(input.f, input.g);
41
+ }
42
+
43
+ // src/tools/functor-map.ts
44
+ import { z as z4 } from "zod";
45
+
46
+ // src/engine/functor.ts
47
+ import { z as z3 } from "zod";
48
+ var FunctorSchema = z3.object({
49
+ name: z3.string(),
50
+ source_category: z3.string(),
51
+ target_category: z3.string(),
52
+ object_map: z3.record(z3.string()).default({}),
53
+ metadata: z3.record(z3.unknown()).default({})
54
+ });
55
+ function mapObject(functor, obj) {
56
+ const mapped = functor.object_map[obj];
57
+ if (mapped === void 0) {
58
+ throw new Error(`Object '${obj}' not in functor ${functor.name}'s object map`);
59
+ }
60
+ return mapped;
61
+ }
62
+ function mapMorphism(functor, morphism) {
63
+ return {
64
+ name: `${functor.name}(${morphism.name})`,
65
+ source: mapObject(functor, morphism.source),
66
+ target: mapObject(functor, morphism.target),
67
+ metadata: { functor: functor.name, original: morphism.name }
68
+ };
69
+ }
70
+
71
+ // src/tools/functor-map.ts
72
+ var FunctorMapObjectInput = z4.object({
73
+ functor: FunctorSchema,
74
+ object: z4.string().describe("Object name to map")
75
+ });
76
+ var FunctorMapMorphismInput = z4.object({
77
+ functor: FunctorSchema,
78
+ morphism: MorphismSchema
79
+ });
80
+ function functorMapObject(input) {
81
+ return { mapped: mapObject(input.functor, input.object) };
82
+ }
83
+ function functorMapMorphism(input) {
84
+ return mapMorphism(input.functor, input.morphism);
85
+ }
86
+
87
+ // src/tools/nat-transform.ts
88
+ import { z as z6 } from "zod";
89
+
90
+ // src/engine/natural-transformation.ts
91
+ import { z as z5 } from "zod";
92
+ var NaturalTransformationSchema = z5.object({
93
+ name: z5.string(),
94
+ source_functor: FunctorSchema,
95
+ target_functor: FunctorSchema,
96
+ components: z5.record(MorphismSchema).default({}),
97
+ metadata: z5.record(z5.unknown()).default({})
98
+ });
99
+ function componentAt(nt, obj) {
100
+ const component = nt.components[obj];
101
+ if (!component) {
102
+ throw new Error(`No component defined at object '${obj}' for ${nt.name}`);
103
+ }
104
+ return component;
105
+ }
106
+ function checkNaturality(nt, morphism) {
107
+ const x = morphism.source;
108
+ const y = morphism.target;
109
+ const alphaX = componentAt(nt, x);
110
+ const gF = mapMorphism(nt.target_functor, morphism);
111
+ const left = compose(gF, alphaX);
112
+ const alphaY = componentAt(nt, y);
113
+ const fF = mapMorphism(nt.source_functor, morphism);
114
+ const right = compose(alphaY, fF);
115
+ return left.source === right.source && left.target === right.target;
116
+ }
117
+ function composeVertical(beta, alpha) {
118
+ if (beta.source_functor.name !== alpha.target_functor.name) {
119
+ throw new TypeError(
120
+ `Cannot compose: ${beta.name}.source_functor (${beta.source_functor.name}) != ${alpha.name}.target_functor (${alpha.target_functor.name})`
121
+ );
122
+ }
123
+ const allObjects = Object.keys(beta.components).filter(
124
+ (obj) => obj in alpha.components
125
+ );
126
+ const composed = {};
127
+ for (const obj of allObjects) {
128
+ composed[obj] = compose(componentAt(beta, obj), componentAt(alpha, obj));
129
+ }
130
+ return {
131
+ name: `${beta.name} . ${alpha.name}`,
132
+ source_functor: alpha.source_functor,
133
+ target_functor: beta.target_functor,
134
+ components: composed,
135
+ metadata: { composed_from: [beta.name, alpha.name] }
136
+ };
137
+ }
138
+
139
+ // src/tools/nat-transform.ts
140
+ var CheckNaturalityInput = z6.object({
141
+ transformation: NaturalTransformationSchema,
142
+ morphism: MorphismSchema.describe("Morphism f: X -> Y to check naturality against")
143
+ });
144
+ var ComposeVerticalInput = z6.object({
145
+ beta: NaturalTransformationSchema.describe("Top transformation (applied second)"),
146
+ alpha: NaturalTransformationSchema.describe("Bottom transformation (applied first)")
147
+ });
148
+ function natCheckNaturality(input) {
149
+ return { commutes: checkNaturality(input.transformation, input.morphism) };
150
+ }
151
+ function natComposeVertical(input) {
152
+ return composeVertical(input.beta, input.alpha);
153
+ }
154
+
155
+ // src/tools/entropy.ts
156
+ import { z as z7 } from "zod";
157
+
158
+ // src/engine/convergence.ts
159
+ function convergenceConstant(scoreSequence, tolerance = 1e-6) {
160
+ if (scoreSequence.length < 3) return 0;
161
+ const deltas = [];
162
+ for (let i = 1; i < scoreSequence.length; i++) {
163
+ deltas.push(Math.abs(scoreSequence[i] - scoreSequence[i - 1]));
164
+ }
165
+ if (deltas.length === 0 || deltas[deltas.length - 1] <= tolerance) return 0;
166
+ const ratios = [];
167
+ for (let i = 1; i < deltas.length; i++) {
168
+ if (deltas[i - 1] > tolerance) {
169
+ ratios.push(deltas[i] / deltas[i - 1]);
170
+ }
171
+ }
172
+ if (ratios.length === 0) return 0;
173
+ return ratios.reduce((a, b) => a + b, 0) / ratios.length;
174
+ }
175
+ function robustnessDelta(baseline, current) {
176
+ if (typeof baseline === "number" && typeof current === "number") {
177
+ return Math.abs(current - baseline);
178
+ }
179
+ if (Array.isArray(baseline) && Array.isArray(current)) {
180
+ if (baseline.length !== current.length) return Infinity;
181
+ if (baseline.length === 0) return 0;
182
+ const sum = baseline.reduce((acc, b, i) => acc + Math.abs(current[i] - b), 0);
183
+ return sum / baseline.length;
184
+ }
185
+ return 0;
186
+ }
187
+ function entropy(probabilities) {
188
+ if (probabilities.length === 0) return 0;
189
+ let h = 0;
190
+ for (const p of probabilities) {
191
+ if (p > 0) {
192
+ h -= p * Math.log(p);
193
+ }
194
+ }
195
+ return h;
196
+ }
197
+ function analyzeFromScores(scores) {
198
+ const kappa = convergenceConstant(scores);
199
+ const current = scores.length > 0 ? scores[scores.length - 1] : 0;
200
+ const baseline = scores.length > 0 ? scores[0] : 0;
201
+ const delta = robustnessDelta(baseline, current);
202
+ return { kappa, delta, current, baseline };
203
+ }
204
+
205
+ // src/tools/entropy.ts
206
+ var EntropyInput = z7.object({
207
+ probabilities: z7.array(z7.number()).describe("Probability distribution (should sum to ~1)")
208
+ });
209
+ function entropyCompute(input) {
210
+ const h = entropy(input.probabilities);
211
+ return { entropy: h };
212
+ }
213
+
214
+ // src/tools/convergence.ts
215
+ import { z as z8 } from "zod";
216
+ var ConvergenceCheckInput = z8.object({
217
+ scores: z8.array(z8.number()).describe("Sequence of governance scores over time"),
218
+ tolerance: z8.number().optional().default(1e-6)
219
+ });
220
+ var DriftCheckInput = z8.object({
221
+ baseline: z8.union([z8.number(), z8.array(z8.number())]).describe("Baseline state"),
222
+ current: z8.union([z8.number(), z8.array(z8.number())]).describe("Current state")
223
+ });
224
+ function convergenceCheck(input) {
225
+ const kappa = convergenceConstant(input.scores, input.tolerance);
226
+ return {
227
+ kappa,
228
+ converges: kappa < 1,
229
+ interpretation: kappa === 0 ? "Already converged or insufficient data" : kappa < 1 ? `Converging (\u03BA=${kappa.toFixed(4)})` : `Diverging (\u03BA=${kappa.toFixed(4)}) \u2014 governance not contracting`
230
+ };
231
+ }
232
+ function driftCheck(input) {
233
+ const delta = robustnessDelta(input.baseline, input.current);
234
+ return {
235
+ delta,
236
+ drifted: delta > 0
237
+ };
238
+ }
239
+ function analyzeScores(input) {
240
+ return analyzeFromScores(input.scores);
241
+ }
242
+
243
+ // src/tools/governance-validate.ts
244
+ import { z as z9 } from "zod";
245
+ import { execSync } from "child_process";
246
+ var GovernanceValidateInput = z9.object({
247
+ project_path: z9.string().describe("Absolute path to the project root"),
248
+ write_proof: z9.boolean().optional().default(true).describe(
249
+ "Write proof artifact to .morphism/proofs/"
250
+ ),
251
+ include_sheaf: z9.boolean().optional().default(true).describe(
252
+ "Run sheaf-theoretic drift analysis (H\xB9 cohomology)"
253
+ )
254
+ });
255
+ function governanceCategoricalValidate(input) {
256
+ const { project_path, write_proof, include_sheaf } = input;
257
+ const script = [
258
+ `import json, sys`,
259
+ `sys.path.insert(0, '${project_path}/src')`,
260
+ `from pathlib import Path`,
261
+ `from morphism.governance_category import full_categorical_validation, compute_runtime_evidence, compute_governance_vector, compute_vector_kappa`,
262
+ `root = Path('${project_path}')`,
263
+ `result = full_categorical_validation(root)`,
264
+ // Proof artifact generation
265
+ ...write_proof ? [
266
+ `from morphism.proof.artifact import generate_proof_artifact, write_proof_artifact`,
267
+ `gov_vec = result['governance_vector']`,
268
+ `kappa = result['kappa']`,
269
+ `proof = generate_proof_artifact(`,
270
+ ` root=root,`,
271
+ ` naturality_result=result['naturality'],`,
272
+ ` chain_morphisms=[v['morphism'] for v in result['naturality'].get('verdicts', [])],`,
273
+ ` chain_source='Policy', chain_target='Runbook',`,
274
+ ` convergence_data={'kappa': kappa, 'governance_vector': gov_vec, 'vector_labels': result['vector_labels'], 'l_inf_distance': kappa, 'kappa_history': [kappa]},`,
275
+ ` sheaf_radius=result['sheaf']['consistency_radius'],`,
276
+ ` drift_types=result['sheaf']['drift_types'],`,
277
+ ` governance_score=sum(gov_vec)/len(gov_vec) if gov_vec else 0.0,`,
278
+ `)`,
279
+ `p = write_proof_artifact(proof, root)`,
280
+ `result['proof_artifact_path'] = str(p)`,
281
+ `result['proof_sha256'] = proof.sha256`
282
+ ] : [],
283
+ `print(json.dumps(result, default=str))`
284
+ ].join("\n");
285
+ try {
286
+ const out = execSync(
287
+ `python3 -c "${script.replace(/\n/g, "\n").replace(/"/g, '\\"')}"`,
288
+ { cwd: project_path, encoding: "utf-8", timeout: 6e4 }
289
+ );
290
+ const parsed = JSON.parse(out.trim());
291
+ return {
292
+ success: true,
293
+ all_valid: parsed.all_valid,
294
+ kappa: parsed.kappa,
295
+ governance_vector: parsed.governance_vector,
296
+ vector_labels: parsed.vector_labels,
297
+ naturality: {
298
+ all_natural: parsed.naturality?.all_natural,
299
+ summary: parsed.naturality?.summary,
300
+ violations: (parsed.naturality?.verdicts ?? []).filter((v) => !v.natural).map((v) => v.violation).filter(Boolean)
301
+ },
302
+ sheaf: parsed.sheaf,
303
+ categorical_errors: parsed.categorical_errors,
304
+ proof_artifact_path: parsed.proof_artifact_path,
305
+ proof_sha256: parsed.proof_sha256
306
+ };
307
+ } catch (err) {
308
+ return {
309
+ success: false,
310
+ error: err.stderr || err.message || "Unknown error",
311
+ stdout: err.stdout || ""
312
+ };
313
+ }
314
+ }
315
+ var NaturalityCheckInput = z9.object({
316
+ project_path: z9.string().describe("Path to project root"),
317
+ morphism_name: z9.string().optional().describe(
318
+ 'Check naturality for a specific morphism only (e.g. "enforce_hook")'
319
+ )
320
+ });
321
+ function checkGovernanceNaturality(input) {
322
+ const script = [
323
+ `import json, sys`,
324
+ `sys.path.insert(0, '${input.project_path}/src')`,
325
+ `from pathlib import Path`,
326
+ `from morphism.governance_category import compute_runtime_evidence`,
327
+ `from morphism.governance_category import GOVERNANCE_OBJECTS`,
328
+ `from morphism.engine.naturality_bridge import check_governance_naturality`,
329
+ `ev, _ = compute_runtime_evidence(Path('${input.project_path}'))`,
330
+ `result = check_governance_naturality(ev, GOVERNANCE_OBJECTS)`,
331
+ ...input.morphism_name ? [
332
+ `result['verdicts'] = [v for v in result['verdicts'] if v['morphism'] == '${input.morphism_name}']`
333
+ ] : [],
334
+ `print(json.dumps(result))`
335
+ ].join("\n");
336
+ try {
337
+ const out = execSync(
338
+ `python3 -c "${script.replace(/\n/g, "\n").replace(/"/g, '\\"')}"`,
339
+ { cwd: input.project_path, encoding: "utf-8", timeout: 3e4 }
340
+ );
341
+ return { success: true, ...JSON.parse(out.trim()) };
342
+ } catch (err) {
343
+ return { success: false, error: err.stderr || err.message };
344
+ }
345
+ }
346
+ var SheafDriftInput = z9.object({
347
+ project_path: z9.string().describe("Path to project root")
348
+ });
349
+ function checkSheafDrift(input) {
350
+ const script = [
351
+ `import json, sys`,
352
+ `sys.path.insert(0, '${input.project_path}/src')`,
353
+ `from pathlib import Path`,
354
+ `from morphism.governance_category import compute_runtime_evidence`,
355
+ `from morphism.sheaf.cech_complex import build_governance_sheaf, analyze_governance_drift`,
356
+ `ev, dim_scores = compute_runtime_evidence(Path('${input.project_path}'))`,
357
+ `sheaf = build_governance_sheaf(ev, dim_scores)`,
358
+ `report = analyze_governance_drift(sheaf)`,
359
+ `print(json.dumps({'consistency_radius': report.consistency_radius, 'h1_non_trivial': report.h1_non_trivial, 'obstructions': report.obstructions, 'drift_types': report.drift_types, 'severity': report.severity, 'recommendation': report.recommendation}))`
360
+ ].join("\n");
361
+ try {
362
+ const out = execSync(
363
+ `python3 -c "${script.replace(/\n/g, "\n").replace(/"/g, '\\"')}"`,
364
+ { cwd: input.project_path, encoding: "utf-8", timeout: 3e4 }
365
+ );
366
+ return { success: true, ...JSON.parse(out.trim()) };
367
+ } catch (err) {
368
+ return { success: false, error: err.stderr || err.message };
369
+ }
370
+ }
371
+
372
+ // src/server.ts
373
+ function createServer() {
374
+ const server2 = new McpServer({
375
+ name: "morphism-agentic-math",
376
+ version: "0.1.0"
377
+ });
378
+ server2.tool(
379
+ "morphism_compose",
380
+ "Compose two morphisms f . g (category theory). Requires f.source == g.target.",
381
+ ComposeInput.shape,
382
+ async (input) => ({
383
+ content: [{ type: "text", text: JSON.stringify(morphismCompose(input), null, 2) }]
384
+ })
385
+ );
386
+ server2.tool(
387
+ "functor_map_object",
388
+ "Apply a functor to an object, mapping it between categories.",
389
+ FunctorMapObjectInput.shape,
390
+ async (input) => ({
391
+ content: [{ type: "text", text: JSON.stringify(functorMapObject(input), null, 2) }]
392
+ })
393
+ );
394
+ server2.tool(
395
+ "functor_map_morphism",
396
+ "Apply a functor to a morphism, preserving structure between categories.",
397
+ FunctorMapMorphismInput.shape,
398
+ async (input) => ({
399
+ content: [{ type: "text", text: JSON.stringify(functorMapMorphism(input), null, 2) }]
400
+ })
401
+ );
402
+ server2.tool(
403
+ "natural_transform_check",
404
+ "Check if a natural transformation satisfies the naturality condition for a morphism.",
405
+ CheckNaturalityInput.shape,
406
+ async (input) => ({
407
+ content: [{ type: "text", text: JSON.stringify(natCheckNaturality(input), null, 2) }]
408
+ })
409
+ );
410
+ server2.tool(
411
+ "natural_transform_compose",
412
+ "Vertical composition of natural transformations: beta . alpha.",
413
+ ComposeVerticalInput.shape,
414
+ async (input) => ({
415
+ content: [{ type: "text", text: JSON.stringify(natComposeVertical(input), null, 2) }]
416
+ })
417
+ );
418
+ server2.tool(
419
+ "entropy_compute",
420
+ "Compute Shannon entropy H(s) = -sum(p_i * ln(p_i)) for a probability distribution.",
421
+ EntropyInput.shape,
422
+ async (input) => ({
423
+ content: [{ type: "text", text: JSON.stringify(entropyCompute(input), null, 2) }]
424
+ })
425
+ );
426
+ server2.tool(
427
+ "convergence_check",
428
+ "Check if governance iteration converges (kappa < 1, Banach contraction).",
429
+ ConvergenceCheckInput.shape,
430
+ async (input) => ({
431
+ content: [{ type: "text", text: JSON.stringify(convergenceCheck(input), null, 2) }]
432
+ })
433
+ );
434
+ server2.tool(
435
+ "drift_check",
436
+ "Compute drift delta between baseline and current governance state.",
437
+ DriftCheckInput.shape,
438
+ async (input) => ({
439
+ content: [{ type: "text", text: JSON.stringify(driftCheck(input), null, 2) }]
440
+ })
441
+ );
442
+ server2.tool(
443
+ "analyze_scores",
444
+ "Full analysis of governance score sequence: kappa, delta, current, baseline.",
445
+ ConvergenceCheckInput.shape,
446
+ async (input) => ({
447
+ content: [{ type: "text", text: JSON.stringify(analyzeScores(input), null, 2) }]
448
+ })
449
+ );
450
+ server2.tool(
451
+ "governance_categorical_validate",
452
+ "Full categorical governance validation: naturality check, sheaf drift (H\uFFFD cohomology), vector kappa, and proof artifact generation.",
453
+ GovernanceValidateInput.shape,
454
+ async (input) => ({
455
+ content: [{ type: "text", text: JSON.stringify(governanceCategoricalValidate(input), null, 2) }]
456
+ })
457
+ );
458
+ server2.tool(
459
+ "governance_naturality_check",
460
+ "Check that governance policy enforcement satisfies the naturality condition (commuting diagrams). Violations indicate input-order-dependent enforcement.",
461
+ NaturalityCheckInput.shape,
462
+ async (input) => ({
463
+ content: [{ type: "text", text: JSON.stringify(checkGovernanceNaturality(input), null, 2) }]
464
+ })
465
+ );
466
+ server2.tool(
467
+ "governance_sheaf_drift",
468
+ "Sheaf-theoretic drift detection: compute H\uFFFD cohomology obstructions. Non-trivial H\uFFFD means globally inconsistent governance that local auditing cannot detect.",
469
+ SheafDriftInput.shape,
470
+ async (input) => ({
471
+ content: [{ type: "text", text: JSON.stringify(checkSheafDrift(input), null, 2) }]
472
+ })
473
+ );
474
+ return server2;
475
+ }
476
+
477
+ // src/index.ts
478
+ var server = createServer();
479
+ var transport = new StdioServerTransport();
480
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@morphism-systems/agentic-math",
3
+ "version": "0.1.0",
4
+ "description": "Category theory MCP server — morphisms, functors, natural transformations, convergence",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "agentic-math": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsup src/index.ts --format esm --dts --clean",
12
+ "typecheck": "tsc --noEmit",
13
+ "test": "vitest run",
14
+ "lint": "echo 'no lint configured'"
15
+ },
16
+ "keywords": [
17
+ "mcp",
18
+ "model-context-protocol",
19
+ "category-theory",
20
+ "morphism",
21
+ "functor",
22
+ "natural-transformation",
23
+ "ai-governance"
24
+ ],
25
+ "license": "BUSL-1.1",
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "dependencies": {
30
+ "@modelcontextprotocol/sdk": "^1.12.1",
31
+ "zod": "^3.24.0"
32
+ },
33
+ "devDependencies": {
34
+ "tsup": "^8.4.0",
35
+ "typescript": "^5.8.3",
36
+ "vitest": "^3.0.0"
37
+ }
38
+ }