mcp-sdk.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. (function () {
  2. const jsonRpcVersion = "2.0";
  3. const protocolVersion = "2026-01-26";
  4. const queuedHandlerNames = [
  5. "ontoolinput",
  6. "ontoolinputpartial",
  7. "ontoolresult",
  8. "ontoolcancelled",
  9. "onhostcontextchanged",
  10. ];
  11. const errorCodes = {
  12. parseError: -32700,
  13. invalidRequest: -32600,
  14. methodNotFound: -32601,
  15. invalidParams: -32602,
  16. internalError: -32603,
  17. };
  18. let nextRequestId = 0;
  19. const pendingRequests = new Map();
  20. const handlers = {};
  21. const queuedNotifications = queuedHandlerNames.reduce(function (
  22. queue,
  23. name,
  24. ) {
  25. queue[name] = [];
  26. return queue;
  27. }, {});
  28. const state = {
  29. hostContext: null,
  30. hostInfo: null,
  31. hostCapabilities: null,
  32. };
  33. let resizeObserver = null;
  34. function disconnectResizeObserver() {
  35. if (resizeObserver) {
  36. resizeObserver.disconnect();
  37. resizeObserver = null;
  38. }
  39. }
  40. const notificationHandlers = {
  41. "ui/notifications/host-context-changed": applyHostContext,
  42. "ui/notifications/tool-input": function (params) {
  43. emit("ontoolinput", params ?? {});
  44. },
  45. "ui/notifications/tool-input-partial": function (params) {
  46. emit("ontoolinputpartial", params ?? {});
  47. },
  48. "ui/notifications/tool-result": function (params) {
  49. emit("ontoolresult", params ?? {});
  50. },
  51. "ui/notifications/tool-cancelled": function (params) {
  52. emit("ontoolcancelled", params ?? {});
  53. },
  54. };
  55. function send(message) {
  56. message.jsonrpc = jsonRpcVersion;
  57. window.parent.postMessage(message, "*");
  58. }
  59. function request(method, params) {
  60. return new Promise(function (resolve, reject) {
  61. const id = ++nextRequestId;
  62. pendingRequests.set(id, { resolve, reject });
  63. send({ id, method, params });
  64. });
  65. }
  66. function notify(method, params) {
  67. const message = { method };
  68. if (params !== undefined) {
  69. message.params = params;
  70. }
  71. send(message);
  72. }
  73. function respond(id, result) {
  74. send({ id, result });
  75. }
  76. function respondWithError(id, code, message) {
  77. send({ id, error: { code, message } });
  78. }
  79. function parseMessage(data) {
  80. if (typeof data === "string") {
  81. try {
  82. return JSON.parse(data);
  83. } catch (error) {
  84. return null;
  85. }
  86. }
  87. if (data && typeof data === "object") {
  88. return data;
  89. }
  90. return null;
  91. }
  92. function isObject(value) {
  93. return (
  94. value !== null && typeof value === "object" && !Array.isArray(value)
  95. );
  96. }
  97. function normalizeParams(value, key) {
  98. return isObject(value) ? value : { [key]: value };
  99. }
  100. function mergeObjects(original, toMerge) {
  101. return Object.assign({}, original || {}, toMerge || {});
  102. }
  103. function mergeHostContext(update) {
  104. if (!update) {
  105. return state.hostContext;
  106. }
  107. const current = state.hostContext || {};
  108. const next = mergeObjects(current, update);
  109. if (current.styles || update.styles) {
  110. const currentStyles = current.styles || {};
  111. const nextStyles = update.styles || {};
  112. next.styles = mergeObjects(currentStyles, nextStyles);
  113. next.styles.variables = mergeObjects(
  114. currentStyles.variables,
  115. nextStyles.variables,
  116. );
  117. next.styles.css = mergeObjects(currentStyles.css, nextStyles.css);
  118. }
  119. state.hostContext = next;
  120. return next;
  121. }
  122. function flushQueuedNotifications(name) {
  123. const callback = handlers[name];
  124. const queue = queuedNotifications[name];
  125. if (!callback || !queue || queue.length === 0) {
  126. return;
  127. }
  128. while (queue.length > 0) {
  129. callback(queue.shift());
  130. }
  131. }
  132. function emit(name, payload) {
  133. const callback = handlers[name];
  134. if (callback) {
  135. callback(payload);
  136. return;
  137. }
  138. if (queuedNotifications[name]) {
  139. queuedNotifications[name].push(payload);
  140. }
  141. }
  142. function setHandler(name, callback) {
  143. handlers[name] = callback;
  144. flushQueuedNotifications(name);
  145. }
  146. function applyTheme(theme) {
  147. if (!theme) {
  148. return;
  149. }
  150. document.documentElement.setAttribute("data-theme", theme);
  151. document.documentElement.style.colorScheme = theme;
  152. }
  153. function applyStyleVariables(variables) {
  154. if (!variables) {
  155. return;
  156. }
  157. Object.keys(variables)
  158. .filter((key) => variables[key] !== undefined)
  159. .forEach(function (key) {
  160. document.documentElement.style.setProperty(key, variables[key]);
  161. });
  162. }
  163. function applyFonts(fontCss) {
  164. if (!fontCss) {
  165. return;
  166. }
  167. let style = document.getElementById("__mcp-host-fonts");
  168. if (!style) {
  169. style = document.createElement("style");
  170. style.id = "__mcp-host-fonts";
  171. document.head.appendChild(style);
  172. }
  173. style.textContent = fontCss;
  174. }
  175. function applyHostContext(update) {
  176. const hostContext = mergeHostContext(update);
  177. if (!hostContext) {
  178. return;
  179. }
  180. applyTheme(hostContext.theme);
  181. applyStyleVariables(hostContext.styles?.variables);
  182. applyFonts(hostContext.styles?.css?.fonts);
  183. emit("onhostcontextchanged", hostContext);
  184. }
  185. function currentSize() {
  186. return {
  187. width: document.documentElement.scrollWidth,
  188. height: document.documentElement.scrollHeight,
  189. };
  190. }
  191. function notifySizeChanged() {
  192. notify("ui/notifications/size-changed", currentSize());
  193. }
  194. function autoResize() {
  195. if (typeof ResizeObserver === "undefined" || !document.body) {
  196. return;
  197. }
  198. disconnectResizeObserver();
  199. resizeObserver = new ResizeObserver(notifySizeChanged);
  200. resizeObserver.observe(document.body);
  201. return disconnectResizeObserver;
  202. }
  203. async function handleTeardown(id) {
  204. try {
  205. disconnectResizeObserver();
  206. respond(id, await (handlers.onteardown?.() ?? {}));
  207. } catch (error) {
  208. respondWithError(
  209. id,
  210. errorCodes.internalError,
  211. error instanceof Error
  212. ? error.message
  213. : "Unknown teardown error",
  214. );
  215. }
  216. }
  217. async function handleCallTool(id, params) {
  218. if (!handlers.oncalltool) {
  219. respondWithError(
  220. id,
  221. errorCodes.methodNotFound,
  222. "No tool handler registered.",
  223. );
  224. return;
  225. }
  226. try {
  227. respond(id, await handlers.oncalltool(params));
  228. } catch (error) {
  229. respondWithError(
  230. id,
  231. errorCodes.internalError,
  232. error instanceof Error ? error.message : "Unknown tool error",
  233. );
  234. }
  235. }
  236. async function handleListTools(id, params) {
  237. try {
  238. respond(
  239. id,
  240. await (handlers.onlisttools?.(params) ?? { tools: [] }),
  241. );
  242. } catch (error) {
  243. respondWithError(
  244. id,
  245. errorCodes.internalError,
  246. error instanceof Error
  247. ? error.message
  248. : "Unknown list tools error",
  249. );
  250. }
  251. }
  252. function handlePendingResponse(message) {
  253. if (message.id === undefined || !pendingRequests.has(message.id)) {
  254. return false;
  255. }
  256. const pending = pendingRequests.get(message.id);
  257. pendingRequests.delete(message.id);
  258. if (message.error) {
  259. pending.reject(new Error(message.error.message));
  260. } else {
  261. pending.resolve(message.result);
  262. }
  263. return true;
  264. }
  265. function handleNotification(message) {
  266. const handler = notificationHandlers[message.method];
  267. if (handler) {
  268. handler(message.params);
  269. }
  270. }
  271. const requestHandlers = {
  272. "ui/resource-teardown": function (message) {
  273. handleTeardown(message.id);
  274. },
  275. "tools/call": function (message) {
  276. handleCallTool(message.id, message.params);
  277. },
  278. "tools/list": function (message) {
  279. handleListTools(message.id, message.params);
  280. },
  281. };
  282. function handleIncomingRequest(message) {
  283. const handler = requestHandlers[message.method];
  284. if (handler) {
  285. handler(message);
  286. } else {
  287. respondWithError(
  288. message.id,
  289. errorCodes.methodNotFound,
  290. "Method not found: " + message.method,
  291. );
  292. }
  293. }
  294. window.addEventListener("message", function (event) {
  295. if (event.source !== window.parent) {
  296. return;
  297. }
  298. const message = parseMessage(event.data);
  299. if (!message || message.jsonrpc !== jsonRpcVersion) {
  300. return;
  301. }
  302. if (handlePendingResponse(message)) {
  303. return;
  304. }
  305. if (message.id === undefined) {
  306. handleNotification(message);
  307. return;
  308. }
  309. handleIncomingRequest(message);
  310. });
  311. window.createMcpApp = async function createMcpApp(setup) {
  312. const initializeResult = await request("ui/initialize", {
  313. protocolVersion: protocolVersion,
  314. appInfo: {
  315. name: document.title || "MCP App",
  316. version: "1.0.0",
  317. },
  318. appCapabilities: {},
  319. });
  320. state.hostInfo = initializeResult?.hostInfo ?? null;
  321. state.hostCapabilities = initializeResult?.hostCapabilities ?? null;
  322. applyHostContext(initializeResult?.hostContext ?? null);
  323. notify("ui/notifications/initialized");
  324. function callServerTool(nameOrParams, args) {
  325. const params = isObject(nameOrParams)
  326. ? {
  327. name: nameOrParams.name,
  328. arguments: nameOrParams.arguments || {},
  329. }
  330. : {
  331. name: nameOrParams,
  332. arguments: args || {},
  333. };
  334. return request("tools/call", params);
  335. }
  336. function listResources(cursorOrParams) {
  337. return request(
  338. "resources/list",
  339. cursorOrParams
  340. ? normalizeParams(cursorOrParams, "cursor")
  341. : undefined,
  342. );
  343. }
  344. function readResource(uriOrParams) {
  345. return request("resources/read", normalizeParams(uriOrParams, "uri"));
  346. }
  347. function sendMessage(messageOrContent, role) {
  348. const params =
  349. isObject(messageOrContent) &&
  350. ("content" in messageOrContent || "role" in messageOrContent)
  351. ? {
  352. role: messageOrContent.role || "user",
  353. content: messageOrContent.content,
  354. }
  355. : {
  356. role: role || "user",
  357. content: messageOrContent,
  358. };
  359. return request("ui/message", params);
  360. }
  361. function openLink(urlOrParams) {
  362. return request("ui/open-link", normalizeParams(urlOrParams, "url"));
  363. }
  364. function downloadFile(contentsOrParams) {
  365. const params =
  366. isObject(contentsOrParams) && "contents" in contentsOrParams
  367. ? contentsOrParams
  368. : { contents: contentsOrParams };
  369. return request("ui/download-file", params);
  370. }
  371. function requestDisplayMode(modeOrParams) {
  372. return request(
  373. "ui/request-display-mode",
  374. normalizeParams(modeOrParams, "mode"),
  375. );
  376. }
  377. function updateModelContext(params) {
  378. return request("ui/update-model-context", params || {});
  379. }
  380. function requestTeardown() {
  381. notify("ui/notifications/request-teardown");
  382. }
  383. function sendLog(levelOrParams, data, logger) {
  384. const params = isObject(levelOrParams)
  385. ? levelOrParams
  386. : {
  387. level: levelOrParams,
  388. data: data,
  389. };
  390. if (!isObject(levelOrParams) && logger !== undefined) {
  391. params.logger = logger;
  392. }
  393. notify("notifications/message", params);
  394. return Promise.resolve();
  395. }
  396. await setup({
  397. getHostContext: function () {
  398. return state.hostContext;
  399. },
  400. getHostInfo: function () {
  401. return state.hostInfo;
  402. },
  403. getHostCapabilities: function () {
  404. return state.hostCapabilities;
  405. },
  406. callServerTool: callServerTool,
  407. listResources: listResources,
  408. readResource: readResource,
  409. sendMessage: sendMessage,
  410. openLink: openLink,
  411. downloadFile: downloadFile,
  412. requestDisplayMode: requestDisplayMode,
  413. updateModelContext: updateModelContext,
  414. requestTeardown: requestTeardown,
  415. sendLog: sendLog,
  416. resize: notifySizeChanged,
  417. autoResize: autoResize,
  418. onTeardown: function (callback) {
  419. handlers.onteardown = callback;
  420. },
  421. onCallTool: function (callback) {
  422. handlers.oncalltool = callback;
  423. },
  424. onListTools: function (callback) {
  425. handlers.onlisttools = callback;
  426. },
  427. onToolInput: function (callback) {
  428. setHandler("ontoolinput", callback);
  429. },
  430. onToolInputPartial: function (callback) {
  431. setHandler("ontoolinputpartial", callback);
  432. },
  433. onToolResult: function (callback) {
  434. setHandler("ontoolresult", callback);
  435. },
  436. onToolCancelled: function (callback) {
  437. setHandler("ontoolcancelled", callback);
  438. },
  439. onHostContextChanged: function (callback) {
  440. setHandler("onhostcontextchanged", callback);
  441. },
  442. });
  443. };
  444. })();