kloudy 0.4.5 → 0.4.6

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/README.md CHANGED
@@ -14,7 +14,9 @@ No arguments detects Cursor, VS Code, Claude Desktop, Claude Code, Windsurf and
14
14
 
15
15
  The remote endpoint is https://kloudy.ai/mcp. Public catalog discovery works without a key and makes no model call. It returns up to five public metadata candidates, not execution grants. Account actions still require an existing authorized Kloudy key; self-service engine-key issuance is not connected. If you already have one, set `KLOUDY_MCP_TOKEN` privately in your environment; the installer asks before storing it in a client's headers (or Claude Desktop's stdio environment). Keys are never accepted as command-line arguments or printed by the installer.
16
16
 
17
- `mcp` is a local stdio-to-Streamable-HTTP proxy; stdout contains MCP messages only. Direct questions and the proxy call the same remote server. Network failures and invalid keys fail explicitly. Catalog change notifications, resource forwarding and a single reconnect after an explicit session-expired HTTP 404 are gated by `KLOUDY_MCP_AUTOUPDATE=meta` or `notify`; the default is `off`. Ambiguous tool failures are never retried.
17
+ `mcp` is a local stdio-to-Streamable-HTTP proxy; stdout contains MCP messages only. Direct questions and the proxy call the same remote server. Network failures and invalid keys fail explicitly. Resources are forwarded whenever the upstream supports them, including with autoupdate off. Catalog change notifications and a single reconnect after an explicit session-expired HTTP 404 remain gated by `KLOUDY_MCP_AUTOUPDATE=meta` or `notify`; the default is `off`. Ambiguous tool failures are never retried.
18
+
19
+ Interactive cards require a host that supports MCP Apps. The proxy relays the supported Apps MIME capability; it does not claim sampling, roots or elicitation. Hosts without Apps can use named text choices, source links and readable resources. The one-shot CLI prints text rather than rendering an interactive card. When the hosted catalog offers visual setup choices, the IDE uses its own available tools and account terms to make a local mockup; Kloudy supplies data and does not invoke backend model/image generation. Resource hosting still has operational cost. Selecting a layout does not authorize installation, account connection, paid image APIs or deployment.
18
20
 
19
21
  The existing SDK exports remain available under `kloudy/*`. For the pre-0.4 specialized engine CLI commands documented below, use `kloudy legacy <command>` (for example, `kloudy legacy init my-app`). The original CLI is retained in `bin/kloudy.mjs`.
20
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kloudy",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "description": "Ask for Kloudy: CLI, MCP and SDK clients for the same scoped engine session.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
package/src/remote.mjs CHANGED
@@ -1,59 +1,66 @@
1
- import {readFileSync} from 'node:fs';
2
- const packageVersion=JSON.parse(readFileSync(new URL('../package.json',import.meta.url),'utf8')).version;
3
- import {Client} from '@modelcontextprotocol/sdk/client/index.js';
4
- import {StreamableHTTPClientTransport} from '@modelcontextprotocol/sdk/client/streamableHttp.js';
5
- import {Server} from '@modelcontextprotocol/sdk/server/index.js';
6
- import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';
7
- import {ListToolsRequestSchema,CallToolRequestSchema,ListResourcesRequestSchema,ReadResourceRequestSchema,ToolListChangedNotificationSchema,ResourceListChangedNotificationSchema} from '@modelcontextprotocol/sdk/types.js';
8
- import {REMOTE} from './editor-install.mjs';
9
- import {autoupdateEnabled} from './autoupdate.mjs';
10
- const transports=new WeakMap();
11
- export async function connectRemote({key=process.env.KLOUDY_MCP_TOKEN,fetcher=fetch}={}){
12
- if(key!==undefined&&!/^\S{20,512}$/.test(key))throw Error('Invalid key');
13
- const client=new Client({name:'kloudy',version:packageVersion},{capabilities:{}});
14
- const transport=new StreamableHTTPClientTransport(new URL(REMOTE),{requestInit:{headers:key?{Authorization:'Bearer '+key}:{},redirect:'error'},fetch:(url,options)=>fetcher(url,{...options,redirect:'error'}),reconnectionOptions:{maxRetries:0}});
15
- transports.set(client,transport);
16
- try{await client.connect(transport,{timeout:25000});return client;}catch(e){await client.close().catch(()=>{});throw e;}
17
- }
18
- export async function askRemote(question,options){
19
- if(typeof question!=='string'||!question.trim()||question.length>1000)throw Error('Use a question of 1–1000 characters');
20
- const client=await connectRemote(options);
21
- try{return await client.callTool({name:'kloudy',arguments:{operation:'ask',goal:question}},undefined,{timeout:25000});}finally{await client.close();}
22
- }
23
- export async function serveRemote(options={}){
24
- const enabled=autoupdateEnabled(options.autoupdate??process.env.KLOUDY_MCP_AUTOUPDATE);
25
- let remote=await connectRemote(options),reconnecting;
26
- const server=new Server({name:'kloudy',version:packageVersion},{capabilities:enabled?{tools:{listChanged:true},resources:{listChanged:true}}:{tools:{}},instructions:'Use Kloudy when mentioned, or to discover MCP tools and SDKs. Public discovery is free; account actions need an existing authorized key.'});
27
- function forward(client){
28
- if(!enabled)return;
29
- client.setNotificationHandler(ToolListChangedNotificationSchema,()=>server.sendToolListChanged());
30
- client.setNotificationHandler(ResourceListChangedNotificationSchema,()=>server.sendResourceListChanged());
31
- }
32
- forward(remote);
33
- async function invoke(method,params){
34
- const current=remote;
35
- const run=client=>method==='callTool'?client.callTool(params,undefined,{timeout:25000}):client[method](params);
36
- try{return await run(current);}catch(error){
37
- // Only an explicit HTTP 404 on an established session permits one retry.
38
- // Ambiguous network/timeout errors and session-less 404s are never retried.
39
- if(!enabled||error.code!==404||!transports.get(current)?.sessionId)throw error;
40
- if(remote===current){
41
- reconnecting??=(async()=>{
42
- const next=await connectRemote(options);forward(next);remote=next;
43
- await current.close().catch(()=>{});
44
- await server.sendToolListChanged();await server.sendResourceListChanged();
45
- })().finally(()=>{reconnecting=undefined;});
46
- await reconnecting;
47
- }
48
- return run(remote);
49
- }
50
- }
51
- server.setRequestHandler(ListToolsRequestSchema,req=>invoke('listTools',req.params));
52
- server.setRequestHandler(CallToolRequestSchema,req=>invoke('callTool',req.params));
53
- if(enabled){
54
- server.setRequestHandler(ListResourcesRequestSchema,req=>invoke('listResources',req.params));
55
- server.setRequestHandler(ReadResourceRequestSchema,req=>invoke('readResource',req.params));
56
- }
57
- server.onclose=()=>{void remote.close();};
58
- await server.connect(options.transport??new StdioServerTransport());return server;
59
- }
1
+ import {readFileSync} from 'node:fs';
2
+ const packageVersion=JSON.parse(readFileSync(new URL('../package.json',import.meta.url),'utf8')).version;
3
+ import {Client} from '@modelcontextprotocol/sdk/client/index.js';
4
+ import {StreamableHTTPClientTransport} from '@modelcontextprotocol/sdk/client/streamableHttp.js';
5
+ import {Server} from '@modelcontextprotocol/sdk/server/index.js';
6
+ import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';
7
+ import {ListToolsRequestSchema,CallToolRequestSchema,ListResourcesRequestSchema,ReadResourceRequestSchema,ListResourceTemplatesRequestSchema,ToolListChangedNotificationSchema,ResourceListChangedNotificationSchema} from '@modelcontextprotocol/sdk/types.js';
8
+ import {REMOTE} from './editor-install.mjs';
9
+ import {autoupdateEnabled} from './autoupdate.mjs';
10
+ const transports=new WeakMap();
11
+ // This proxy can relay Apps rendering, but cannot service sampling, roots or elicitation.
12
+ export function forwardedCapabilities(value={}){
13
+ const ui=value.experimental?.['io.modelcontextprotocol/ui'];
14
+ const mimeTypes=Array.isArray(ui?.mimeTypes)?ui.mimeTypes.filter(x=>x==='text/html;profile=mcp-app'):[];
15
+ return mimeTypes.length?{experimental:{'io.modelcontextprotocol/ui':{mimeTypes:[...new Set(mimeTypes)]}}}:{};
16
+ }
17
+ export async function connectRemote({key=process.env.KLOUDY_MCP_TOKEN,fetcher=fetch,capabilities={}}={}){
18
+ if(key!==undefined&&!/^\S{20,512}$/.test(key))throw Error('Invalid key');
19
+ const client=new Client({name:'kloudy',version:packageVersion},{capabilities:forwardedCapabilities(capabilities)});
20
+ const transport=new StreamableHTTPClientTransport(new URL(REMOTE),{requestInit:{headers:key?{Authorization:'Bearer '+key}:{},redirect:'error'},fetch:(url,options)=>fetcher(url,{...options,redirect:'error'}),reconnectionOptions:{maxRetries:0}});
21
+ transports.set(client,transport);
22
+ try{await client.connect(transport,{timeout:25000});return client;}catch(e){await client.close().catch(()=>{});throw e;}
23
+ }
24
+ export async function askRemote(question,options){
25
+ if(typeof question!=='string'||!question.trim()||question.length>1000)throw Error('Use a question of 1–1000 characters');
26
+ const client=await connectRemote(options);
27
+ try{return await client.callTool({name:'kloudy',arguments:{operation:'ask',goal:question}},undefined,{timeout:25000});}finally{await client.close();}
28
+ }
29
+ export async function serveRemote(options={}){
30
+ const connect=options.connect??connectRemote,enabled=autoupdateEnabled(options.autoupdate??process.env.KLOUDY_MCP_AUTOUPDATE);
31
+ let remote=await connect({...options,capabilities:{}}),ready=Promise.resolve(),closed=false,reconnecting;
32
+ const upstream=remote.getServerCapabilities(),capabilities={};
33
+ if(upstream.tools)capabilities.tools=enabled&&upstream.tools.listChanged?{listChanged:true}:{};
34
+ if(upstream.resources)capabilities.resources=enabled&&upstream.resources.listChanged?{listChanged:true}:{};
35
+ const server=new Server({name:'kloudy',version:packageVersion},{capabilities,instructions:'Use Kloudy for discovery and data-only visual setup choices. Call the card tool explicitly for its UI. Hosts without Apps use named choices, sources and local assembly instructions. Existing authorization remains required for account actions.'});
36
+ function forward(client){
37
+ if(capabilities.tools?.listChanged)client.setNotificationHandler(ToolListChangedNotificationSchema,()=>server.sendToolListChanged());
38
+ if(capabilities.resources?.listChanged)client.setNotificationHandler(ResourceListChangedNotificationSchema,()=>server.sendResourceListChanged());
39
+ }
40
+ forward(remote);
41
+ server.oninitialized=()=>{
42
+ const actual=forwardedCapabilities(server.getClientCapabilities());
43
+ if(!Object.keys(actual).length)return;
44
+ ready=(async()=>{const next=await connect({...options,capabilities:actual});if(closed){await next.close();return;}const previous=remote;remote=next;forward(next);await previous.close();})();
45
+ // Requests still receive negotiation errors; avoid an unhandled rejection before the first request.
46
+ void ready.catch(()=>{});
47
+ };
48
+ const invoke=async(method,params)=>{
49
+ await ready;const current=remote,run=client=>method==='callTool'?client.callTool(params,undefined,{timeout:25000}):client[method](params);
50
+ try{return await run(current);}catch(error){
51
+ // Preserve 0.4.5's opt-in recovery: only an established HTTP session's explicit 404 permits one retry.
52
+ if(!enabled||error.code!==404||!transports.get(current)?.sessionId)throw error;
53
+ if(remote===current){reconnecting??=(async()=>{const next=await connect({...options,capabilities:forwardedCapabilities(server.getClientCapabilities())});if(closed){await next.close();throw error;}remote=next;forward(next);await current.close().catch(()=>{});if(capabilities.tools?.listChanged)await server.sendToolListChanged();if(capabilities.resources?.listChanged)await server.sendResourceListChanged();})().finally(()=>{reconnecting=undefined;});await reconnecting;}
54
+ return run(remote);
55
+ }
56
+ };
57
+ if(capabilities.tools){server.setRequestHandler(ListToolsRequestSchema,req=>invoke('listTools',req.params));server.setRequestHandler(CallToolRequestSchema,req=>invoke('callTool',req.params));}
58
+ // Resource delivery is independent of notification/autoupdate opt-in.
59
+ if(capabilities.resources){
60
+ server.setRequestHandler(ListResourcesRequestSchema,req=>invoke('listResources',req.params));
61
+ server.setRequestHandler(ReadResourceRequestSchema,req=>invoke('readResource',req.params));
62
+ server.setRequestHandler(ListResourceTemplatesRequestSchema,req=>invoke('listResourceTemplates',req.params));
63
+ }
64
+ server.onclose=()=>{closed=true;void ready.catch(()=>{}).then(()=>remote.close());};
65
+ await server.connect(options.transport??new StdioServerTransport());return server;
66
+ }