stdio.js 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. import process from 'node:process';
  2. import { ReadBuffer, serializeMessage } from '../shared/stdio.js';
  3. /**
  4. * Server transport for stdio: this communicates with an MCP client by reading from the current process' stdin and writing to stdout.
  5. *
  6. * This transport is only available in Node.js environments.
  7. */
  8. export class StdioServerTransport {
  9. constructor(_stdin = process.stdin, _stdout = process.stdout) {
  10. this._stdin = _stdin;
  11. this._stdout = _stdout;
  12. this._readBuffer = new ReadBuffer();
  13. this._started = false;
  14. // Arrow functions to bind `this` properly, while maintaining function identity.
  15. this._ondata = (chunk) => {
  16. this._readBuffer.append(chunk);
  17. this.processReadBuffer();
  18. };
  19. this._onerror = (error) => {
  20. this.onerror?.(error);
  21. };
  22. }
  23. /**
  24. * Starts listening for messages on stdin.
  25. */
  26. async start() {
  27. if (this._started) {
  28. throw new Error('StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.');
  29. }
  30. this._started = true;
  31. this._stdin.on('data', this._ondata);
  32. this._stdin.on('error', this._onerror);
  33. }
  34. processReadBuffer() {
  35. while (true) {
  36. try {
  37. const message = this._readBuffer.readMessage();
  38. if (message === null) {
  39. break;
  40. }
  41. this.onmessage?.(message);
  42. }
  43. catch (error) {
  44. this.onerror?.(error);
  45. }
  46. }
  47. }
  48. async close() {
  49. // Remove our event listeners first
  50. this._stdin.off('data', this._ondata);
  51. this._stdin.off('error', this._onerror);
  52. // Check if we were the only data listener
  53. const remainingDataListeners = this._stdin.listenerCount('data');
  54. if (remainingDataListeners === 0) {
  55. // Only pause stdin if we were the only listener
  56. // This prevents interfering with other parts of the application that might be using stdin
  57. this._stdin.pause();
  58. }
  59. // Clear the buffer and notify closure
  60. this._readBuffer.clear();
  61. this.onclose?.();
  62. }
  63. send(message) {
  64. return new Promise(resolve => {
  65. const json = serializeMessage(message);
  66. if (this._stdout.write(json)) {
  67. resolve();
  68. }
  69. else {
  70. this._stdout.once('drain', resolve);
  71. }
  72. });
  73. }
  74. }
  75. //# sourceMappingURL=stdio.js.map