@tinycloud/mcp 0.8.3-beta.14 → 0.8.3-beta.16

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/dist/http-cli.js CHANGED
@@ -1550,7 +1550,7 @@ function outcomeCode(result) {
1550
1550
  // package.json
1551
1551
  var package_default = {
1552
1552
  name: "@tinycloud/mcp",
1553
- version: "0.8.3-beta.14",
1553
+ version: "0.8.3-beta.16",
1554
1554
  description: "TinyCloud delegated operations MCP server",
1555
1555
  keywords: [
1556
1556
  "tinycloud",
@@ -1621,9 +1621,9 @@ var package_default = {
1621
1621
  dependencies: {
1622
1622
  "@modelcontextprotocol/node": "2.0.0-beta.5",
1623
1623
  "@modelcontextprotocol/server": "2.0.0-beta.5",
1624
- "@tinycloud/node-sdk": "3.8.0-beta.14",
1624
+ "@tinycloud/node-sdk": "3.8.0-beta.15",
1625
1625
  "@tinycloud/node-sdk-wasm": "1.7.8",
1626
- "@tinycloud/operations": "0.7.7-beta.13",
1626
+ "@tinycloud/operations": "0.7.7-beta.14",
1627
1627
  jose: "6.2.4"
1628
1628
  },
1629
1629
  devDependencies: {
@@ -1647,6 +1647,8 @@ var MCP_SERVER_NAME = "tinycloud-mcp";
1647
1647
 
1648
1648
  // src/http.ts
1649
1649
  var REQUIRED_SCOPE = "tinycloud:mcp";
1650
+ var LOCAL_CLI_PACKAGE = ["@tinycloud", "cli"].join("/");
1651
+ var LOCAL_MCP_PACKAGE = ["@tinycloud", "mcp"].join("/");
1650
1652
  function createHostedMcpApp(config) {
1651
1653
  const resourceUrl = canonicalResourceUrl(config.publicUrl);
1652
1654
  const pluginUrl = new URL("./chatgpt/mcp", resourceUrl);
@@ -1726,14 +1728,173 @@ function createHostedSurface(config, resourceUrl, surface, tokenStatus) {
1726
1728
  const allowedHosts = [resourceUrl.hostname];
1727
1729
  return {
1728
1730
  async fetch(request) {
1729
- const metadata = oauthMetadataResponse(request, metadataOptions);
1730
- if (metadata !== void 0) return metadata;
1731
1731
  const url = new URL(request.url);
1732
+ if (url.pathname !== "/healthz") {
1733
+ const hostFailure = hostHeaderValidationResponse(request, allowedHosts);
1734
+ if (hostFailure !== void 0) return hostFailure;
1735
+ }
1736
+ if (url.pathname === "/.well-known/oauth-protected-resource" && surface === "hosted") {
1737
+ const metadataUrl = new URL(getOAuthProtectedResourceMetadataUrl(resourceUrl));
1738
+ const metadataRequest = new Request(metadataUrl.toString(), {
1739
+ method: request.method,
1740
+ headers: request.headers
1741
+ });
1742
+ const response = oauthMetadataResponse(metadataRequest, metadataOptions);
1743
+ if (response !== void 0) return withBearerMethodsSupported(response);
1744
+ }
1745
+ const metadata = oauthMetadataResponse(request, metadataOptions);
1746
+ if (metadata !== void 0) {
1747
+ return surface === "hosted" ? withBearerMethodsSupported(metadata) : metadata;
1748
+ }
1749
+ if (url.pathname === "/.well-known/ai-catalog.json" && surface === "hosted") {
1750
+ if (request.method === "OPTIONS") {
1751
+ return publicMetadataCors(new Response(null, { status: 204 }), request);
1752
+ }
1753
+ if (request.method !== "GET") return methodNotAllowed("GET");
1754
+ return publicMetadataCors(Response.json({
1755
+ specVersion: "1.0",
1756
+ host: {
1757
+ displayName: "TinyCloud, Inc.",
1758
+ identifier: "https://mcp.tinycloud.xyz",
1759
+ documentationUrl: "https://docs.tinycloud.xyz/cli/mcp"
1760
+ },
1761
+ entries: [{
1762
+ identifier: "urn:air:tinycloud.xyz:mcp:tinycloud-mcp",
1763
+ displayName: "TinyCloud MCP",
1764
+ type: "application/json",
1765
+ description: "Hosted Streamable HTTP MCP server for delegated TinyCloud operations.",
1766
+ url: new URL("/.well-known/mcp/server-card.json", resourceUrl).toString(),
1767
+ representativeQueries: [
1768
+ "How do I connect to TinyCloud's hosted MCP server?",
1769
+ "Which endpoint provides TinyCloud's delegated MCP operations?",
1770
+ "How can I connect to TinyCloud MCP using local stdio?"
1771
+ ]
1772
+ }]
1773
+ }, {
1774
+ headers: {
1775
+ "cache-control": "public, max-age=300",
1776
+ "content-type": "application/json; charset=utf-8"
1777
+ }
1778
+ }), request);
1779
+ }
1780
+ if (url.pathname === "/.well-known/poppy.json" && surface === "hosted") {
1781
+ if (request.method === "OPTIONS") {
1782
+ return publicMetadataCors(new Response(null, { status: 204 }), request);
1783
+ }
1784
+ if (request.method !== "GET") return methodNotAllowed("GET");
1785
+ return publicMetadataCors(Response.json({
1786
+ protocol_version: "0.1",
1787
+ organization: {
1788
+ name: "TinyCloud, Inc.",
1789
+ domain: resourceUrl.hostname
1790
+ },
1791
+ web: {}
1792
+ }, {
1793
+ headers: {
1794
+ "cache-control": "public, max-age=300",
1795
+ "content-type": "application/json; charset=utf-8"
1796
+ }
1797
+ }), request);
1798
+ }
1799
+ if (url.pathname === "/auth.md" && surface === "hosted") {
1800
+ if (request.method === "OPTIONS") {
1801
+ return publicMetadataCors(new Response(null, { status: 204 }), request);
1802
+ }
1803
+ if (request.method !== "GET") return methodNotAllowed("GET");
1804
+ const issuerUrl = new URL(issuer);
1805
+ const authorizationServerMetadataUrl = new URL(
1806
+ `/.well-known/oauth-authorization-server${issuerUrl.pathname}`,
1807
+ issuerUrl.origin
1808
+ );
1809
+ const registrationEndpoint = config.oauthMetadata.registration_endpoint;
1810
+ const registrationNote = typeof registrationEndpoint === "string" ? `OpenKey's OAuth \`registration_endpoint\` is ${registrationEndpoint}; it registers OAuth clients, not TinyCloud users or agents.` : "The authorization server does not advertise an OAuth client registration endpoint.";
1811
+ const authDoc = [
1812
+ "# TinyCloud MCP auth.md",
1813
+ "",
1814
+ `Protected resource: ${resourceUrl}.`,
1815
+ "",
1816
+ "Send an OAuth access token in the `Authorization: Bearer` header.",
1817
+ "",
1818
+ `Protected Resource Metadata: ${new URL("/.well-known/oauth-protected-resource", resourceUrl)}.`,
1819
+ `Authorization server issuer: ${issuer}.`,
1820
+ `Authorization server metadata: ${authorizationServerMetadataUrl}.`,
1821
+ `Scope: \`${REQUIRED_SCOPE}\`.`,
1822
+ "",
1823
+ "Hosted access uses OpenKey OAuth.",
1824
+ registrationNote,
1825
+ "TinyCloud does not host an Auth.md agent-enrollment or user-registration endpoint for this service.",
1826
+ "No `agent_auth` registration block is published because no agent-registration endpoint is available.",
1827
+ "",
1828
+ `Local stdio is a separate option configured with \`${LOCAL_CLI_PACKAGE}\` and \`${LOCAL_MCP_PACKAGE}\`.`,
1829
+ "",
1830
+ "Connection steps: https://docs.tinycloud.xyz/cli/mcp"
1831
+ ].join("\n") + "\n";
1832
+ return publicMetadataCors(publicText(authDoc, "text/markdown; charset=utf-8"), request);
1833
+ }
1834
+ if (url.pathname === "/robots.txt" && surface === "hosted") {
1835
+ return request.method === "GET" ? publicText(`User-agent: *
1836
+ Allow: /
1837
+ Sitemap: ${new URL("/sitemap.xml", resourceUrl)}
1838
+ Agentmap: ${new URL("/.well-known/ai-catalog.json", resourceUrl)}
1839
+ `, "text/plain; charset=utf-8") : methodNotAllowed("GET");
1840
+ }
1841
+ if ((url.pathname === "/mcp/server-card" || url.pathname === "/.well-known/mcp/server-card.json") && surface === "hosted") {
1842
+ if (request.method === "OPTIONS") {
1843
+ return publicMetadataCors(new Response(null, { status: 204 }), request);
1844
+ }
1845
+ if (request.method !== "GET") return methodNotAllowed("GET");
1846
+ return publicMetadataCors(Response.json({
1847
+ $schema: "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json",
1848
+ name: `xyz.tinycloud/${MCP_SERVER_NAME}`,
1849
+ title: "TinyCloud MCP",
1850
+ version: MCP_VERSION,
1851
+ description: "MCP server for delegated TinyCloud operations.",
1852
+ websiteUrl: "https://docs.tinycloud.xyz/cli/mcp",
1853
+ repository: { source: "github", url: "https://github.com/TinyCloudLabs/js-sdk", subfolder: "packages/mcp" },
1854
+ remotes: [{ type: "streamable-http", url: resourceUrl.toString() }]
1855
+ }, { headers: { "cache-control": "public, max-age=300" } }), request);
1856
+ }
1857
+ if (url.pathname === "/llms.txt" && surface === "hosted") {
1858
+ return request.method === "GET" ? publicText(`# TinyCloud MCP
1859
+
1860
+ Hosted Streamable HTTP endpoint: https://mcp.tinycloud.xyz/mcp (OpenKey OAuth).
1861
+
1862
+ Connection documentation: https://docs.tinycloud.xyz/cli/mcp
1863
+
1864
+ Local alternative: install ${LOCAL_CLI_PACKAGE} and ${LOCAL_MCP_PACKAGE}, then set up tinycloud-mcp as a stdio server in your MCP client.
1865
+ `, "text/plain; charset=utf-8") : methodNotAllowed("GET");
1866
+ }
1867
+ if (url.pathname === "/sitemap.xml" && surface === "hosted") {
1868
+ const sitemap = `<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><url><loc>${new URL("/", resourceUrl)}</loc></url></urlset>`;
1869
+ return request.method === "GET" ? publicText(sitemap, "application/xml; charset=utf-8") : methodNotAllowed("GET");
1870
+ }
1871
+ if (url.pathname === "/" && surface === "hosted") {
1872
+ if (request.method !== "GET" && request.method !== "HEAD") return methodNotAllowed("GET, HEAD");
1873
+ const markdown = request.headers.get("accept")?.includes("text/markdown") ?? false;
1874
+ const content = markdown ? `# TinyCloud MCP
1875
+
1876
+ Hosted endpoint: <https://mcp.tinycloud.xyz/mcp> (Streamable HTTP, OpenKey OAuth).
1877
+
1878
+ [Connection documentation](https://docs.tinycloud.xyz/cli/mcp).
1879
+
1880
+ Local alternative: install \`${LOCAL_CLI_PACKAGE}\` and \`${LOCAL_MCP_PACKAGE}\` and connect the \`tinycloud-mcp\` stdio server.
1881
+ ` : `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="ai-catalog" href="/.well-known/ai-catalog.json"><title>TinyCloud MCP</title></head><main><h1>TinyCloud MCP</h1><p>Hosted MCP endpoint: <code>https://mcp.tinycloud.xyz/mcp</code> (Streamable HTTP, OpenKey OAuth).</p><p><a href="https://docs.tinycloud.xyz/cli/mcp">Connection documentation</a></p><p>Local alternative: install <code>${LOCAL_CLI_PACKAGE}</code> and <code>${LOCAL_MCP_PACKAGE}</code> and connect the <code>tinycloud-mcp</code> stdio server.</p></main></html>`;
1882
+ const headers = new Headers({
1883
+ "content-type": markdown ? "text/markdown; charset=utf-8" : "text/html; charset=utf-8",
1884
+ "cache-control": "public, max-age=300",
1885
+ "content-security-policy": "default-src 'none'; base-uri 'none'; frame-ancestors 'none'",
1886
+ "x-content-type-options": "nosniff",
1887
+ vary: "Accept"
1888
+ });
1889
+ headers.set(
1890
+ "link",
1891
+ `<${new URL("/mcp/server-card", resourceUrl)}>; rel="describedby"; type="application/json", <https://docs.tinycloud.xyz/cli/mcp>; rel="service-doc"`
1892
+ );
1893
+ return new Response(request.method === "HEAD" ? null : content, { headers });
1894
+ }
1732
1895
  if (url.pathname === "/healthz") {
1733
1896
  return request.method === "GET" ? json({ ok: true }) : methodNotAllowed("GET");
1734
1897
  }
1735
- const hostFailure = hostHeaderValidationResponse(request, allowedHosts);
1736
- if (hostFailure !== void 0) return hostFailure;
1737
1898
  if (url.pathname === connectPath) {
1738
1899
  if (request.method !== "GET") return methodNotAllowed("GET");
1739
1900
  try {
@@ -1903,6 +2064,41 @@ function callbackCors(response, openkeyHost) {
1903
2064
  function json(value, status = 200) {
1904
2065
  return Response.json(value, { status, headers: { "cache-control": "no-store" } });
1905
2066
  }
2067
+ async function withBearerMethodsSupported(response) {
2068
+ if (response.status === 204 || response.body === null) return response;
2069
+ const metadata = await response.json();
2070
+ const headers = new Headers(response.headers);
2071
+ headers.delete("content-length");
2072
+ headers.delete("content-encoding");
2073
+ return new Response(JSON.stringify({ ...metadata, bearer_methods_supported: ["header"] }), {
2074
+ status: response.status,
2075
+ statusText: response.statusText,
2076
+ headers
2077
+ });
2078
+ }
2079
+ function publicMetadataCors(response, request) {
2080
+ const headers = new Headers(response.headers);
2081
+ headers.set("access-control-allow-origin", "*");
2082
+ headers.set("access-control-allow-methods", "GET, OPTIONS");
2083
+ const requestedHeaders = request.method === "OPTIONS" ? request.headers.get("access-control-request-headers") : null;
2084
+ if (requestedHeaders !== null) {
2085
+ headers.set("access-control-allow-headers", requestedHeaders);
2086
+ }
2087
+ headers.set(
2088
+ "vary",
2089
+ requestedHeaders === null ? "Origin" : "Origin, Access-Control-Request-Headers"
2090
+ );
2091
+ return new Response(response.body, { status: response.status, headers });
2092
+ }
2093
+ function publicText(text, contentType) {
2094
+ return new Response(text, {
2095
+ headers: {
2096
+ "content-type": contentType,
2097
+ "cache-control": "public, max-age=300",
2098
+ "x-content-type-options": "nosniff"
2099
+ }
2100
+ });
2101
+ }
1906
2102
  function methodNotAllowed(allow) {
1907
2103
  return new Response("Method not allowed", { status: 405, headers: { allow } });
1908
2104
  }