@yamo/mcp-server 1.3.9 → 1.3.11

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 (2) hide show
  1. package/dist/index.js +288 -243
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -47,14 +47,55 @@ const crypto_1 = __importDefault(require("crypto"));
47
47
  const path_1 = __importDefault(require("path"));
48
48
  const pkg = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '../package.json'), 'utf8'));
49
49
  dotenv.config();
50
- // Validation helper for bytes32 hashes (Part 3: Security Fixes)
50
+ // Constants
51
+ const GENESIS_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000";
52
+ const TOOL_NAMES = {
53
+ SUBMIT_BLOCK: "yamo_submit_block",
54
+ GET_BLOCK: "yamo_get_block",
55
+ GET_LATEST_BLOCK: "yamo_get_latest_block",
56
+ AUDIT_BLOCK: "yamo_audit_block",
57
+ VERIFY_BLOCK: "yamo_verify_block",
58
+ };
59
+ const LOG_PREFIX = {
60
+ DEBUG: "[DEBUG]",
61
+ INFO: "[INFO]",
62
+ ERROR: "[ERROR]",
63
+ };
64
+ const VALIDATION_RULES = {
65
+ BYTES32_PATTERN: /^0x[a-fA-F0-9]{64}$/,
66
+ ETH_ADDRESS_PATTERN: /^0x[a-fA-F0-9]{40}$/,
67
+ };
68
+ // Validation helpers
51
69
  function validateBytes32(value, fieldName) {
52
- if (!value.match(/^0x[a-fA-F0-9]{64}$/)) {
70
+ if (!value.match(VALIDATION_RULES.BYTES32_PATTERN)) {
53
71
  throw new Error(`${fieldName} must be a valid bytes32 hash (0x + 64 hex chars). ` +
54
72
  `Received: ${value.substring(0, 20)}...` +
55
73
  `\nDo NOT include algorithm prefixes like "sha256:"`);
56
74
  }
57
75
  }
76
+ function validateEthereumAddress(address, fieldName) {
77
+ if (!address || !address.match(VALIDATION_RULES.ETH_ADDRESS_PATTERN)) {
78
+ throw new Error(`${fieldName} must be a valid Ethereum address (0x + 40 hex characters)`);
79
+ }
80
+ }
81
+ function validateBlockId(blockId) {
82
+ if (!blockId)
83
+ throw new Error("blockId is required");
84
+ const parts = blockId.split('_');
85
+ if (parts.length < 2) {
86
+ throw new Error(`blockId must follow format {origin}_{workflow} (e.g., 'claude_chain'). Received: ${blockId}`);
87
+ }
88
+ }
89
+ function validateEnvironment() {
90
+ const requiredEnvVars = ['CONTRACT_ADDRESS', 'RPC_URL', 'PRIVATE_KEY'];
91
+ const missing = requiredEnvVars.filter(v => !process.env[v]);
92
+ if (missing.length > 0) {
93
+ throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
94
+ }
95
+ // Validate contract address format
96
+ const contractAddress = process.env.CONTRACT_ADDRESS;
97
+ validateEthereumAddress(contractAddress, 'CONTRACT_ADDRESS');
98
+ }
58
99
  const SUBMIT_BLOCK_TOOL = {
59
100
  name: "yamo_submit_block",
60
101
  description: `Submits a YAMO block to the YAMORegistry smart contract.
@@ -253,11 +294,245 @@ class YamoMcpServer {
253
294
  // Cache for chain continuation: latest submitted block's contentHash
254
295
  latestContentHash = null;
255
296
  constructor() {
297
+ // Validate environment variables at startup
298
+ validateEnvironment();
256
299
  this.server = new index_js_1.Server({ name: "yamo", version: pkg.version }, { capabilities: { tools: {} } });
257
300
  this.ipfs = new core_1.IpfsManager();
258
301
  this.chain = new core_1.YamoChainClient();
259
302
  this.setupHandlers();
260
303
  }
304
+ // Response formatting helpers
305
+ createSuccessResponse(data) {
306
+ return {
307
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
308
+ };
309
+ }
310
+ createErrorResponse(error, isError = true) {
311
+ return {
312
+ content: [{ type: "text", text: JSON.stringify(error, null, 2) }],
313
+ isError,
314
+ };
315
+ }
316
+ createTextResponse(text) {
317
+ return { content: [{ type: "text", text }] };
318
+ }
319
+ // Logging helper
320
+ log(level, message) {
321
+ const prefix = LOG_PREFIX[level];
322
+ console.error(`${prefix} ${message}`);
323
+ }
324
+ // Cache update helper
325
+ updateLatestBlockCache(contentHash) {
326
+ this.latestContentHash = contentHash;
327
+ this.log('INFO', `Updated latestContentHash cache: ${contentHash}`);
328
+ }
329
+ // Block formatting helper
330
+ formatBlockResponse(block) {
331
+ return {
332
+ blockId: block.blockId,
333
+ previousBlock: block.previousBlock,
334
+ agentAddress: block.agentAddress,
335
+ contentHash: block.contentHash,
336
+ timestamp: block.timestamp,
337
+ timestampISO: new Date(block.timestamp * 1000).toISOString(),
338
+ consensusType: block.consensusType,
339
+ ledger: block.ledger,
340
+ ipfsCID: block.ipfsCID || null
341
+ };
342
+ }
343
+ // File security validation
344
+ validateFileSecurity(filePath) {
345
+ const realPath = fs_1.default.realpathSync(filePath);
346
+ const allowedDir = fs_1.default.realpathSync(process.cwd());
347
+ const stats = fs_1.default.lstatSync(filePath);
348
+ if (stats.isSymbolicLink()) {
349
+ throw new Error(`Symbolic links are not allowed: ${filePath}`);
350
+ }
351
+ if (!realPath.startsWith(allowedDir)) {
352
+ throw new Error(`File path outside allowed directory: ${filePath}`);
353
+ }
354
+ }
355
+ // File processing helper
356
+ async processSingleFile(file) {
357
+ if (typeof file.content === 'string' && fs_1.default.existsSync(file.content)) {
358
+ this.validateFileSecurity(file.content);
359
+ this.log('DEBUG', `Auto-reading file from path: ${file.content}`);
360
+ const content = await fs_1.default.promises.readFile(fs_1.default.realpathSync(file.content), 'utf8');
361
+ return { name: file.name, content };
362
+ }
363
+ return file;
364
+ }
365
+ // Previous block resolution helper
366
+ async resolvePreviousBlock(previousBlock) {
367
+ if (previousBlock)
368
+ return previousBlock;
369
+ this.log('INFO', 'No previousBlock provided, fetching latest block from chain...');
370
+ if (this.latestContentHash) {
371
+ this.log('INFO', `Using cached latest block's contentHash: ${this.latestContentHash}`);
372
+ return this.latestContentHash;
373
+ }
374
+ const latestHash = await this.chain.getLatestBlockHash();
375
+ if (latestHash && latestHash !== GENESIS_HASH) {
376
+ this.latestContentHash = latestHash;
377
+ this.log('INFO', `Using latest block's contentHash from contract: ${latestHash}`);
378
+ return latestHash;
379
+ }
380
+ this.log('INFO', 'No existing blocks found, using genesis');
381
+ return GENESIS_HASH;
382
+ }
383
+ async handleSubmitBlock(args) {
384
+ const { blockId, previousBlock, contentHash, consensusType, ledger, content, files, encryptionKey } = args;
385
+ // Validate inputs
386
+ validateBlockId(blockId);
387
+ validateBytes32(contentHash, "contentHash");
388
+ if (previousBlock) {
389
+ validateBytes32(previousBlock, "previousBlock");
390
+ }
391
+ // Process files with security validation
392
+ const processedFiles = files && Array.isArray(files)
393
+ ? await Promise.all(files.map(f => this.processSingleFile(f)))
394
+ : files;
395
+ // Resolve previous block hash
396
+ const resolvedPreviousBlock = await this.resolvePreviousBlock(previousBlock);
397
+ // Upload to IPFS if content provided
398
+ let ipfsCID = undefined;
399
+ if (content) {
400
+ ipfsCID = await this.ipfs.upload({
401
+ content,
402
+ files: processedFiles,
403
+ encryptionKey
404
+ });
405
+ }
406
+ // Submit to blockchain
407
+ const tx = await this.chain.submitBlock(blockId, resolvedPreviousBlock, contentHash, consensusType, ledger, ipfsCID);
408
+ const receipt = await tx.wait();
409
+ // Update cache
410
+ this.updateLatestBlockCache(contentHash);
411
+ return this.createSuccessResponse({
412
+ success: true,
413
+ blockId,
414
+ transactionHash: tx.hash,
415
+ blockNumber: receipt.blockNumber,
416
+ gasUsed: receipt.gasUsed.toString(),
417
+ effectiveGasPrice: receipt.effectiveGasPrice?.toString(),
418
+ ipfsCID: ipfsCID || null,
419
+ previousBlock: resolvedPreviousBlock,
420
+ contractAddress: this.chain.getContractAddress(),
421
+ timestamp: new Date().toISOString()
422
+ });
423
+ }
424
+ async handleGetBlock(args) {
425
+ const { blockId } = args;
426
+ const block = await this.chain.getBlock(blockId);
427
+ if (!block) {
428
+ return this.createErrorResponse({
429
+ success: false,
430
+ error: "Block not found on-chain",
431
+ blockId,
432
+ hint: "Verify the blockId or check if the block was submitted"
433
+ });
434
+ }
435
+ return this.createSuccessResponse({
436
+ success: true,
437
+ block: this.formatBlockResponse(block)
438
+ });
439
+ }
440
+ async handleGetLatestBlock() {
441
+ const latestBlock = await this.chain.getLatestBlock();
442
+ if (!latestBlock) {
443
+ return this.createErrorResponse({
444
+ success: false,
445
+ error: "No blocks found on-chain",
446
+ hint: "The chain may be empty. Try submitting a genesis block first."
447
+ });
448
+ }
449
+ return this.createSuccessResponse({
450
+ success: true,
451
+ block: this.formatBlockResponse(latestBlock)
452
+ });
453
+ }
454
+ async handleAuditBlock(args) {
455
+ const { blockId, encryptionKey } = args;
456
+ const block = await this.chain.getBlock(blockId);
457
+ if (!block) {
458
+ return this.createErrorResponse({
459
+ verified: false,
460
+ error: "Block not found on-chain",
461
+ blockId,
462
+ hint: "Cannot audit non-existent block"
463
+ });
464
+ }
465
+ if (!block.ipfsCID) {
466
+ return this.createSuccessResponse({
467
+ verified: null,
468
+ onChainHash: block.contentHash,
469
+ ipfsCID: null,
470
+ note: "V1 block with no IPFS CID - cannot audit actual content",
471
+ blockId,
472
+ agentAddress: block.agentAddress,
473
+ timestamp: block.timestamp
474
+ });
475
+ }
476
+ try {
477
+ const bundle = await this.ipfs.downloadBundle(block.ipfsCID, encryptionKey);
478
+ const computedHash = "0x" + crypto_1.default.createHash("sha256").update(bundle.block).digest("hex");
479
+ const verified = computedHash === block.contentHash;
480
+ return this.createSuccessResponse({
481
+ verified,
482
+ blockId,
483
+ onChainHash: block.contentHash,
484
+ computedHash,
485
+ ipfsCID: block.ipfsCID,
486
+ agentAddress: block.agentAddress,
487
+ timestamp: block.timestamp,
488
+ timestampISO: new Date(block.timestamp * 1000).toISOString(),
489
+ consensusType: block.consensusType,
490
+ ledger: block.ledger,
491
+ contentPreview: bundle.block.substring(0, 500) + (bundle.block.length > 500 ? "..." : ""),
492
+ contentLength: bundle.block.length,
493
+ artifactFiles: Object.keys(bundle.files),
494
+ wasEncrypted: !!encryptionKey
495
+ });
496
+ }
497
+ catch (error) {
498
+ let errorType = "unknown";
499
+ let hint = "";
500
+ if (error.message.includes("encrypted") && !encryptionKey) {
501
+ errorType = "missing_key";
502
+ hint = "This bundle is encrypted. Provide encryptionKey to audit.";
503
+ }
504
+ else if (error.message.includes("Decryption failed") || error.message.includes("decrypt")) {
505
+ errorType = "decryption_failed";
506
+ hint = "The provided encryption key may be incorrect.";
507
+ }
508
+ else if (error.message.includes("not found on-chain")) {
509
+ errorType = "block_not_found";
510
+ hint = "Verify the blockId was submitted correctly.";
511
+ }
512
+ return this.createErrorResponse({
513
+ verified: false,
514
+ error: error.message,
515
+ errorType,
516
+ hint,
517
+ blockId
518
+ });
519
+ }
520
+ }
521
+ async handleVerifyBlock(args) {
522
+ const { blockId, contentHash } = args;
523
+ const contract = this.chain.getContract(false);
524
+ const hashBytes = contentHash.startsWith("0x") ? contentHash : `0x${contentHash}`;
525
+ const isValid = await contract.verifyBlock(blockId, hashBytes);
526
+ return this.createTextResponse(isValid ? "VERIFIED" : "FAILED");
527
+ }
528
+ // Tool handler registry
529
+ toolHandlers = {
530
+ [TOOL_NAMES.SUBMIT_BLOCK]: (args) => this.handleSubmitBlock(args),
531
+ [TOOL_NAMES.GET_BLOCK]: (args) => this.handleGetBlock(args),
532
+ [TOOL_NAMES.GET_LATEST_BLOCK]: () => this.handleGetLatestBlock(),
533
+ [TOOL_NAMES.AUDIT_BLOCK]: (args) => this.handleAuditBlock(args),
534
+ [TOOL_NAMES.VERIFY_BLOCK]: (args) => this.handleVerifyBlock(args),
535
+ };
261
536
  setupHandlers() {
262
537
  this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
263
538
  tools: [SUBMIT_BLOCK_TOOL, GET_BLOCK_TOOL, GET_LATEST_BLOCK_TOOL, AUDIT_BLOCK_TOOL, VERIFY_BLOCK_TOOL],
@@ -265,256 +540,26 @@ class YamoMcpServer {
265
540
  this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
266
541
  const { name, arguments: args } = request.params;
267
542
  try {
268
- if (name === "yamo_submit_block") {
269
- const { blockId, previousBlock, contentHash, consensusType, ledger, content, files, encryptionKey } = args;
270
- // Process files - auto-read if they're file paths (with security fix)
271
- let processedFiles = files;
272
- if (files && Array.isArray(files)) {
273
- processedFiles = files.map((file) => {
274
- // Check if content is a file path that exists
275
- if (typeof file.content === 'string' && fs_1.default.existsSync(file.content)) {
276
- // Security: Resolve to absolute path and restrict to cwd (Part 3: Security Fixes)
277
- const filePath = path_1.default.resolve(file.content);
278
- const allowedDir = process.cwd();
279
- if (!filePath.startsWith(allowedDir)) {
280
- throw new Error(`File path outside allowed directory: ${file.content}`);
281
- }
282
- console.error(`[DEBUG] Auto-reading file from path: ${file.content}`);
283
- return {
284
- name: file.name,
285
- content: fs_1.default.readFileSync(filePath, 'utf8')
286
- };
287
- }
288
- // Otherwise use content as-is
289
- return file;
290
- });
291
- }
292
- // Input validation (Part 3: Security Fixes)
293
- validateBytes32(contentHash, "contentHash");
294
- // Auto-fetch previousBlock if not provided
295
- let resolvedPreviousBlock = previousBlock;
296
- if (!resolvedPreviousBlock) {
297
- console.error(`[INFO] No previousBlock provided, fetching latest block from chain...`);
298
- // First, try the cache (most reliable for chain continuation)
299
- if (this.latestContentHash) {
300
- resolvedPreviousBlock = this.latestContentHash;
301
- console.error(`[INFO] Using cached latest block's contentHash: ${resolvedPreviousBlock}`);
302
- }
303
- else {
304
- // Fallback to direct contract state read (reliable)
305
- const latestHash = await this.chain.getLatestBlockHash();
306
- if (latestHash && latestHash !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
307
- resolvedPreviousBlock = latestHash;
308
- this.latestContentHash = latestHash; // Update cache
309
- console.error(`[INFO] Using latest block's contentHash from contract: ${resolvedPreviousBlock}`);
310
- }
311
- else {
312
- // No blocks exist yet, use genesis
313
- resolvedPreviousBlock = "0x0000000000000000000000000000000000000000000000000000000000000000";
314
- console.error(`[INFO] No existing blocks found, using genesis`);
315
- }
316
- }
317
- }
318
- else {
319
- validateBytes32(previousBlock, "previousBlock");
320
- }
321
- let ipfsCID = undefined;
322
- if (content) {
323
- ipfsCID = await this.ipfs.upload({
324
- content,
325
- files: processedFiles,
326
- encryptionKey
327
- });
328
- }
329
- const tx = await this.chain.submitBlock(blockId, resolvedPreviousBlock, contentHash, consensusType, ledger, ipfsCID);
330
- const receipt = await tx.wait();
331
- // Update cache with the new block's contentHash for chain continuation
332
- this.latestContentHash = contentHash;
333
- console.error(`[INFO] Updated latestContentHash cache: ${contentHash}`);
334
- return {
335
- content: [{ type: "text", text: JSON.stringify({
336
- success: true,
337
- blockId,
338
- transactionHash: tx.hash,
339
- blockNumber: receipt.blockNumber,
340
- gasUsed: receipt.gasUsed.toString(),
341
- effectiveGasPrice: receipt.effectiveGasPrice?.toString(),
342
- ipfsCID: ipfsCID || null,
343
- previousBlock: resolvedPreviousBlock,
344
- contractAddress: this.chain.getContractAddress(),
345
- timestamp: new Date().toISOString()
346
- }, null, 2) }],
347
- };
348
- }
349
- if (name === "yamo_get_block") {
350
- const { blockId } = args;
351
- const block = await this.chain.getBlock(blockId);
352
- if (!block) {
353
- return {
354
- content: [{ type: "text", text: JSON.stringify({
355
- success: false,
356
- error: "Block not found on-chain",
357
- blockId,
358
- hint: "Verify the blockId or check if the block was submitted"
359
- }, null, 2) }],
360
- isError: false,
361
- };
362
- }
363
- return {
364
- content: [{ type: "text", text: JSON.stringify({
365
- success: true,
366
- block: {
367
- blockId: block.blockId,
368
- previousBlock: block.previousBlock,
369
- agentAddress: block.agentAddress,
370
- contentHash: block.contentHash,
371
- timestamp: block.timestamp,
372
- timestampISO: new Date(block.timestamp * 1000).toISOString(),
373
- consensusType: block.consensusType,
374
- ledger: block.ledger,
375
- ipfsCID: block.ipfsCID || null
376
- }
377
- }, null, 2) }],
378
- };
379
- }
380
- if (name === "yamo_get_latest_block") {
381
- const latestBlock = await this.chain.getLatestBlock();
382
- if (!latestBlock) {
383
- return {
384
- content: [{ type: "text", text: JSON.stringify({
385
- success: false,
386
- error: "No blocks found on-chain",
387
- hint: "The chain may be empty. Try submitting a genesis block first."
388
- }, null, 2) }],
389
- isError: false,
390
- };
391
- }
392
- return {
393
- content: [{ type: "text", text: JSON.stringify({
394
- success: true,
395
- block: {
396
- blockId: latestBlock.blockId,
397
- previousBlock: latestBlock.previousBlock,
398
- agentAddress: latestBlock.agentAddress,
399
- contentHash: latestBlock.contentHash,
400
- timestamp: latestBlock.timestamp,
401
- timestampISO: new Date(latestBlock.timestamp * 1000).toISOString(),
402
- consensusType: latestBlock.consensusType,
403
- ledger: latestBlock.ledger,
404
- ipfsCID: latestBlock.ipfsCID || null
405
- }
406
- }, null, 2) }],
407
- };
408
- }
409
- if (name === "yamo_audit_block") {
410
- const { blockId, encryptionKey } = args;
411
- // Get block from chain
412
- const block = await this.chain.getBlock(blockId);
413
- if (!block) {
414
- return {
415
- content: [{ type: "text", text: JSON.stringify({
416
- verified: false,
417
- error: "Block not found on-chain",
418
- blockId,
419
- hint: "Cannot audit non-existent block"
420
- }, null, 2) }],
421
- isError: false,
422
- };
423
- }
424
- // If no IPFS CID, can't audit content
425
- if (!block.ipfsCID) {
426
- return {
427
- content: [{ type: "text", text: JSON.stringify({
428
- verified: null, // Cannot verify without IPFS
429
- onChainHash: block.contentHash,
430
- ipfsCID: null,
431
- note: "V1 block with no IPFS CID - cannot audit actual content",
432
- blockId,
433
- agentAddress: block.agentAddress,
434
- timestamp: block.timestamp
435
- }, null, 2) }],
436
- };
437
- }
438
- // Download and verify from IPFS
439
- try {
440
- const bundle = await this.ipfs.downloadBundle(block.ipfsCID, encryptionKey);
441
- const computedHash = "0x" + crypto_1.default.createHash("sha256").update(bundle.block).digest("hex");
442
- const verified = computedHash === block.contentHash;
443
- return {
444
- content: [{ type: "text", text: JSON.stringify({
445
- verified,
446
- blockId,
447
- onChainHash: block.contentHash,
448
- computedHash,
449
- ipfsCID: block.ipfsCID,
450
- agentAddress: block.agentAddress,
451
- timestamp: block.timestamp,
452
- timestampISO: new Date(block.timestamp * 1000).toISOString(),
453
- consensusType: block.consensusType,
454
- ledger: block.ledger,
455
- contentPreview: bundle.block.substring(0, 500) + (bundle.block.length > 500 ? "..." : ""),
456
- contentLength: bundle.block.length,
457
- artifactFiles: Object.keys(bundle.files),
458
- wasEncrypted: !!encryptionKey
459
- }, null, 2) }],
460
- };
461
- }
462
- catch (error) {
463
- // Enhanced error messages
464
- let errorType = "unknown";
465
- let hint = "";
466
- if (error.message.includes("encrypted") && !encryptionKey) {
467
- errorType = "missing_key";
468
- hint = "This bundle is encrypted. Provide encryptionKey to audit.";
469
- }
470
- else if (error.message.includes("Decryption failed") || error.message.includes("decrypt")) {
471
- errorType = "decryption_failed";
472
- hint = "The provided encryption key may be incorrect.";
473
- }
474
- else if (error.message.includes("not found on-chain")) {
475
- errorType = "block_not_found";
476
- hint = "Verify the blockId was submitted correctly.";
477
- }
478
- return {
479
- content: [{ type: "text", text: JSON.stringify({
480
- verified: false,
481
- error: error.message,
482
- errorType,
483
- hint,
484
- blockId
485
- }, null, 2) }],
486
- isError: true,
487
- };
488
- }
489
- }
490
- if (name === "yamo_verify_block") {
491
- const { blockId, contentHash } = args;
492
- const contract = this.chain.getContract(false);
493
- const hashBytes = contentHash.startsWith("0x") ? contentHash : `0x${contentHash}`;
494
- const isValid = await contract.verifyBlock(blockId, hashBytes);
495
- return {
496
- content: [{ type: "text", text: isValid ? "VERIFIED" : "FAILED" }],
497
- };
543
+ const handler = this.toolHandlers[name];
544
+ if (!handler) {
545
+ throw new Error(`Unknown tool: ${name}`);
498
546
  }
499
- throw new Error(`Unknown tool: ${name}`);
547
+ return await handler(args);
500
548
  }
501
549
  catch (error) {
502
- return {
503
- content: [{ type: "text", text: JSON.stringify({
504
- success: false,
505
- error: error.message,
506
- tool: name,
507
- timestamp: new Date().toISOString()
508
- }, null, 2) }],
509
- isError: true,
510
- };
550
+ return this.createErrorResponse({
551
+ success: false,
552
+ error: error.message,
553
+ tool: name,
554
+ timestamp: new Date().toISOString()
555
+ });
511
556
  }
512
557
  });
513
558
  }
514
559
  async run() {
515
560
  const transport = new stdio_js_1.StdioServerTransport();
516
561
  await this.server.connect(transport);
517
- console.error(`YAMO MCP Server v${pkg.version} running on stdio`);
562
+ this.log('INFO', `YAMO MCP Server v${pkg.version} running on stdio`);
518
563
  }
519
564
  }
520
565
  const server = new YamoMcpServer();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yamo/mcp-server",
3
- "version": "1.3.9",
3
+ "version": "1.3.11",
4
4
  "description": "YAMO Protocol v0.4 - Model Context Protocol server for AI agents",
5
5
  "main": "dist/index.js",
6
6
  "type": "commonjs",