@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/index.cjs CHANGED
@@ -522,7 +522,7 @@ function outcomeCode(result) {
522
522
  // package.json
523
523
  var package_default = {
524
524
  name: "@tinycloud/mcp",
525
- version: "0.8.3-beta.14",
525
+ version: "0.8.3-beta.16",
526
526
  description: "TinyCloud delegated operations MCP server",
527
527
  keywords: [
528
528
  "tinycloud",
@@ -593,9 +593,9 @@ var package_default = {
593
593
  dependencies: {
594
594
  "@modelcontextprotocol/node": "2.0.0-beta.5",
595
595
  "@modelcontextprotocol/server": "2.0.0-beta.5",
596
- "@tinycloud/node-sdk": "3.8.0-beta.14",
596
+ "@tinycloud/node-sdk": "3.8.0-beta.15",
597
597
  "@tinycloud/node-sdk-wasm": "1.7.8",
598
- "@tinycloud/operations": "0.7.7-beta.13",
598
+ "@tinycloud/operations": "0.7.7-beta.14",
599
599
  jose: "6.2.4"
600
600
  },
601
601
  devDependencies: {
@@ -1795,6 +1795,8 @@ function samePendingRequest(pending, expected) {
1795
1795
 
1796
1796
  // src/http.ts
1797
1797
  var REQUIRED_SCOPE = "tinycloud:mcp";
1798
+ var LOCAL_CLI_PACKAGE = ["@tinycloud", "cli"].join("/");
1799
+ var LOCAL_MCP_PACKAGE = ["@tinycloud", "mcp"].join("/");
1798
1800
  function createHostedMcpApp(config) {
1799
1801
  const resourceUrl = canonicalResourceUrl(config.publicUrl);
1800
1802
  const pluginUrl = new URL("./chatgpt/mcp", resourceUrl);
@@ -1874,14 +1876,173 @@ function createHostedSurface(config, resourceUrl, surface, tokenStatus) {
1874
1876
  const allowedHosts = [resourceUrl.hostname];
1875
1877
  return {
1876
1878
  async fetch(request) {
1877
- const metadata = (0, import_server5.oauthMetadataResponse)(request, metadataOptions);
1878
- if (metadata !== void 0) return metadata;
1879
1879
  const url = new URL(request.url);
1880
+ if (url.pathname !== "/healthz") {
1881
+ const hostFailure = (0, import_server5.hostHeaderValidationResponse)(request, allowedHosts);
1882
+ if (hostFailure !== void 0) return hostFailure;
1883
+ }
1884
+ if (url.pathname === "/.well-known/oauth-protected-resource" && surface === "hosted") {
1885
+ const metadataUrl = new URL((0, import_server5.getOAuthProtectedResourceMetadataUrl)(resourceUrl));
1886
+ const metadataRequest = new Request(metadataUrl.toString(), {
1887
+ method: request.method,
1888
+ headers: request.headers
1889
+ });
1890
+ const response = (0, import_server5.oauthMetadataResponse)(metadataRequest, metadataOptions);
1891
+ if (response !== void 0) return withBearerMethodsSupported(response);
1892
+ }
1893
+ const metadata = (0, import_server5.oauthMetadataResponse)(request, metadataOptions);
1894
+ if (metadata !== void 0) {
1895
+ return surface === "hosted" ? withBearerMethodsSupported(metadata) : metadata;
1896
+ }
1897
+ if (url.pathname === "/.well-known/ai-catalog.json" && surface === "hosted") {
1898
+ if (request.method === "OPTIONS") {
1899
+ return publicMetadataCors(new Response(null, { status: 204 }), request);
1900
+ }
1901
+ if (request.method !== "GET") return methodNotAllowed("GET");
1902
+ return publicMetadataCors(Response.json({
1903
+ specVersion: "1.0",
1904
+ host: {
1905
+ displayName: "TinyCloud, Inc.",
1906
+ identifier: "https://mcp.tinycloud.xyz",
1907
+ documentationUrl: "https://docs.tinycloud.xyz/cli/mcp"
1908
+ },
1909
+ entries: [{
1910
+ identifier: "urn:air:tinycloud.xyz:mcp:tinycloud-mcp",
1911
+ displayName: "TinyCloud MCP",
1912
+ type: "application/json",
1913
+ description: "Hosted Streamable HTTP MCP server for delegated TinyCloud operations.",
1914
+ url: new URL("/.well-known/mcp/server-card.json", resourceUrl).toString(),
1915
+ representativeQueries: [
1916
+ "How do I connect to TinyCloud's hosted MCP server?",
1917
+ "Which endpoint provides TinyCloud's delegated MCP operations?",
1918
+ "How can I connect to TinyCloud MCP using local stdio?"
1919
+ ]
1920
+ }]
1921
+ }, {
1922
+ headers: {
1923
+ "cache-control": "public, max-age=300",
1924
+ "content-type": "application/json; charset=utf-8"
1925
+ }
1926
+ }), request);
1927
+ }
1928
+ if (url.pathname === "/.well-known/poppy.json" && surface === "hosted") {
1929
+ if (request.method === "OPTIONS") {
1930
+ return publicMetadataCors(new Response(null, { status: 204 }), request);
1931
+ }
1932
+ if (request.method !== "GET") return methodNotAllowed("GET");
1933
+ return publicMetadataCors(Response.json({
1934
+ protocol_version: "0.1",
1935
+ organization: {
1936
+ name: "TinyCloud, Inc.",
1937
+ domain: resourceUrl.hostname
1938
+ },
1939
+ web: {}
1940
+ }, {
1941
+ headers: {
1942
+ "cache-control": "public, max-age=300",
1943
+ "content-type": "application/json; charset=utf-8"
1944
+ }
1945
+ }), request);
1946
+ }
1947
+ if (url.pathname === "/auth.md" && surface === "hosted") {
1948
+ if (request.method === "OPTIONS") {
1949
+ return publicMetadataCors(new Response(null, { status: 204 }), request);
1950
+ }
1951
+ if (request.method !== "GET") return methodNotAllowed("GET");
1952
+ const issuerUrl = new URL(issuer);
1953
+ const authorizationServerMetadataUrl = new URL(
1954
+ `/.well-known/oauth-authorization-server${issuerUrl.pathname}`,
1955
+ issuerUrl.origin
1956
+ );
1957
+ const registrationEndpoint = config.oauthMetadata.registration_endpoint;
1958
+ 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.";
1959
+ const authDoc = [
1960
+ "# TinyCloud MCP auth.md",
1961
+ "",
1962
+ `Protected resource: ${resourceUrl}.`,
1963
+ "",
1964
+ "Send an OAuth access token in the `Authorization: Bearer` header.",
1965
+ "",
1966
+ `Protected Resource Metadata: ${new URL("/.well-known/oauth-protected-resource", resourceUrl)}.`,
1967
+ `Authorization server issuer: ${issuer}.`,
1968
+ `Authorization server metadata: ${authorizationServerMetadataUrl}.`,
1969
+ `Scope: \`${REQUIRED_SCOPE}\`.`,
1970
+ "",
1971
+ "Hosted access uses OpenKey OAuth.",
1972
+ registrationNote,
1973
+ "TinyCloud does not host an Auth.md agent-enrollment or user-registration endpoint for this service.",
1974
+ "No `agent_auth` registration block is published because no agent-registration endpoint is available.",
1975
+ "",
1976
+ `Local stdio is a separate option configured with \`${LOCAL_CLI_PACKAGE}\` and \`${LOCAL_MCP_PACKAGE}\`.`,
1977
+ "",
1978
+ "Connection steps: https://docs.tinycloud.xyz/cli/mcp"
1979
+ ].join("\n") + "\n";
1980
+ return publicMetadataCors(publicText(authDoc, "text/markdown; charset=utf-8"), request);
1981
+ }
1982
+ if (url.pathname === "/robots.txt" && surface === "hosted") {
1983
+ return request.method === "GET" ? publicText(`User-agent: *
1984
+ Allow: /
1985
+ Sitemap: ${new URL("/sitemap.xml", resourceUrl)}
1986
+ Agentmap: ${new URL("/.well-known/ai-catalog.json", resourceUrl)}
1987
+ `, "text/plain; charset=utf-8") : methodNotAllowed("GET");
1988
+ }
1989
+ if ((url.pathname === "/mcp/server-card" || url.pathname === "/.well-known/mcp/server-card.json") && surface === "hosted") {
1990
+ if (request.method === "OPTIONS") {
1991
+ return publicMetadataCors(new Response(null, { status: 204 }), request);
1992
+ }
1993
+ if (request.method !== "GET") return methodNotAllowed("GET");
1994
+ return publicMetadataCors(Response.json({
1995
+ $schema: "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json",
1996
+ name: `xyz.tinycloud/${MCP_SERVER_NAME}`,
1997
+ title: "TinyCloud MCP",
1998
+ version: MCP_VERSION,
1999
+ description: "MCP server for delegated TinyCloud operations.",
2000
+ websiteUrl: "https://docs.tinycloud.xyz/cli/mcp",
2001
+ repository: { source: "github", url: "https://github.com/TinyCloudLabs/js-sdk", subfolder: "packages/mcp" },
2002
+ remotes: [{ type: "streamable-http", url: resourceUrl.toString() }]
2003
+ }, { headers: { "cache-control": "public, max-age=300" } }), request);
2004
+ }
2005
+ if (url.pathname === "/llms.txt" && surface === "hosted") {
2006
+ return request.method === "GET" ? publicText(`# TinyCloud MCP
2007
+
2008
+ Hosted Streamable HTTP endpoint: https://mcp.tinycloud.xyz/mcp (OpenKey OAuth).
2009
+
2010
+ Connection documentation: https://docs.tinycloud.xyz/cli/mcp
2011
+
2012
+ Local alternative: install ${LOCAL_CLI_PACKAGE} and ${LOCAL_MCP_PACKAGE}, then set up tinycloud-mcp as a stdio server in your MCP client.
2013
+ `, "text/plain; charset=utf-8") : methodNotAllowed("GET");
2014
+ }
2015
+ if (url.pathname === "/sitemap.xml" && surface === "hosted") {
2016
+ 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>`;
2017
+ return request.method === "GET" ? publicText(sitemap, "application/xml; charset=utf-8") : methodNotAllowed("GET");
2018
+ }
2019
+ if (url.pathname === "/" && surface === "hosted") {
2020
+ if (request.method !== "GET" && request.method !== "HEAD") return methodNotAllowed("GET, HEAD");
2021
+ const markdown = request.headers.get("accept")?.includes("text/markdown") ?? false;
2022
+ const content = markdown ? `# TinyCloud MCP
2023
+
2024
+ Hosted endpoint: <https://mcp.tinycloud.xyz/mcp> (Streamable HTTP, OpenKey OAuth).
2025
+
2026
+ [Connection documentation](https://docs.tinycloud.xyz/cli/mcp).
2027
+
2028
+ Local alternative: install \`${LOCAL_CLI_PACKAGE}\` and \`${LOCAL_MCP_PACKAGE}\` and connect the \`tinycloud-mcp\` stdio server.
2029
+ ` : `<!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>`;
2030
+ const headers = new Headers({
2031
+ "content-type": markdown ? "text/markdown; charset=utf-8" : "text/html; charset=utf-8",
2032
+ "cache-control": "public, max-age=300",
2033
+ "content-security-policy": "default-src 'none'; base-uri 'none'; frame-ancestors 'none'",
2034
+ "x-content-type-options": "nosniff",
2035
+ vary: "Accept"
2036
+ });
2037
+ headers.set(
2038
+ "link",
2039
+ `<${new URL("/mcp/server-card", resourceUrl)}>; rel="describedby"; type="application/json", <https://docs.tinycloud.xyz/cli/mcp>; rel="service-doc"`
2040
+ );
2041
+ return new Response(request.method === "HEAD" ? null : content, { headers });
2042
+ }
1880
2043
  if (url.pathname === "/healthz") {
1881
2044
  return request.method === "GET" ? json({ ok: true }) : methodNotAllowed("GET");
1882
2045
  }
1883
- const hostFailure = (0, import_server5.hostHeaderValidationResponse)(request, allowedHosts);
1884
- if (hostFailure !== void 0) return hostFailure;
1885
2046
  if (url.pathname === connectPath) {
1886
2047
  if (request.method !== "GET") return methodNotAllowed("GET");
1887
2048
  try {
@@ -2051,6 +2212,41 @@ function callbackCors(response, openkeyHost) {
2051
2212
  function json(value, status = 200) {
2052
2213
  return Response.json(value, { status, headers: { "cache-control": "no-store" } });
2053
2214
  }
2215
+ async function withBearerMethodsSupported(response) {
2216
+ if (response.status === 204 || response.body === null) return response;
2217
+ const metadata = await response.json();
2218
+ const headers = new Headers(response.headers);
2219
+ headers.delete("content-length");
2220
+ headers.delete("content-encoding");
2221
+ return new Response(JSON.stringify({ ...metadata, bearer_methods_supported: ["header"] }), {
2222
+ status: response.status,
2223
+ statusText: response.statusText,
2224
+ headers
2225
+ });
2226
+ }
2227
+ function publicMetadataCors(response, request) {
2228
+ const headers = new Headers(response.headers);
2229
+ headers.set("access-control-allow-origin", "*");
2230
+ headers.set("access-control-allow-methods", "GET, OPTIONS");
2231
+ const requestedHeaders = request.method === "OPTIONS" ? request.headers.get("access-control-request-headers") : null;
2232
+ if (requestedHeaders !== null) {
2233
+ headers.set("access-control-allow-headers", requestedHeaders);
2234
+ }
2235
+ headers.set(
2236
+ "vary",
2237
+ requestedHeaders === null ? "Origin" : "Origin, Access-Control-Request-Headers"
2238
+ );
2239
+ return new Response(response.body, { status: response.status, headers });
2240
+ }
2241
+ function publicText(text, contentType) {
2242
+ return new Response(text, {
2243
+ headers: {
2244
+ "content-type": contentType,
2245
+ "cache-control": "public, max-age=300",
2246
+ "x-content-type-options": "nosniff"
2247
+ }
2248
+ });
2249
+ }
2054
2250
  function methodNotAllowed(allow) {
2055
2251
  return new Response("Method not allowed", { status: 405, headers: { allow } });
2056
2252
  }