@tangle-network/hub-sdk 1.0.3 → 1.0.5

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
@@ -420,3 +420,28 @@ const result = await broker.invoke(grant.grantId, connectionId, "github.issues.l
420
420
  ```
421
421
 
422
422
  `invoke` mints a fresh one-use, 60-second broker token and checks its selected scope before execution. The host application must recheck its own current customer enrollment on every call and revoke the app grant when access ends. Do not return broker tokens or application secrets to a browser or persist them in agent profiles.
423
+
424
+ ## Embedded Node MCP hosts
425
+
426
+ `@tangle-network/hub-sdk/mcp` owns `HubMcpScope`, connection resolution,
427
+ `createHubMcpRoutes`, the HTTP bridge and `lowerProfileConnections`. The root
428
+ client export remains browser/Worker-safe.
429
+
430
+ Create one scope for the host's lifetime with its existing canonical hash and
431
+ credential-renewal function. Mount `createHubMcpRoutes(scope)` on the host's
432
+ existing listener. `scope.resolveConnectionsToMcp(client, refs, serverForGrant)`
433
+ validates the connections, provider and tool allowlists, rolls back partial
434
+ allocation, and gives the host its native MCP representation. Only a pure
435
+ attach-all request can return an explicitly degraded empty result; required
436
+ connections remain errors. The host surfaces degradation and recovery.
437
+
438
+ Revoke each resolution's `grantTokens` when its run settles. `scope.close()`
439
+ revokes the remaining grants of that instance and prevents new allocation.
440
+ Other scopes and their borrowed clients remain independent.
441
+
442
+ The published `@tangle-network/hub-sdk/mcp/stdio` entry reads only
443
+ `TANGLE_HUB_MCP_ENDPOINT`. Launch it through Node with the opaque callback
444
+ endpoint; do not forward the Hub credential. `lowerProfileConnections` keeps
445
+ the authored adapter profile separate from the ephemeral workspace profile.
446
+ Grant endpoints belong to runtime materialization, never persisted authored
447
+ profiles or trace metadata.
@@ -0,0 +1,81 @@
1
+ import http from "node:http";
2
+ import https from "node:https";
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
5
+ //#region src/mcp/bridge.ts
6
+ var HttpHubMcpBridgeClient = class {
7
+ endpoint;
8
+ constructor(endpoint) {
9
+ if (!endpoint || endpoint.trim().length === 0) throw new Error("TANGLE_HUB_MCP_ENDPOINT is required");
10
+ this.endpoint = endpoint.replace(/\/+$/, "");
11
+ }
12
+ async listTools() {
13
+ return (await requestJson("GET", `${this.endpoint}/tools`)).tools;
14
+ }
15
+ async callTool(name, args) {
16
+ return requestJson("POST", `${this.endpoint}/call`, {
17
+ name,
18
+ arguments: args ?? {}
19
+ });
20
+ }
21
+ };
22
+ function createHubMcpBridgeServer(client) {
23
+ const server = new Server({
24
+ name: "tangle-hub",
25
+ version: "0.1.0"
26
+ }, { capabilities: { tools: {} } });
27
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
28
+ return { tools: (await client.listTools()).map((tool) => ({
29
+ name: tool.name,
30
+ description: tool.description,
31
+ inputSchema: tool.inputSchema
32
+ })) };
33
+ });
34
+ server.setRequestHandler(CallToolRequestSchema, async (request) => client.callTool(request.params.name, request.params.arguments ?? {}));
35
+ return server;
36
+ }
37
+ function requestJson(method, url, body) {
38
+ return new Promise((resolve, reject) => {
39
+ const parsed = new URL(url);
40
+ const payload = body ? JSON.stringify(body) : void 0;
41
+ const request = (parsed.protocol === "https:" ? https : http).request({
42
+ protocol: parsed.protocol,
43
+ hostname: parsed.hostname,
44
+ port: parsed.port,
45
+ path: `${parsed.pathname}${parsed.search}`,
46
+ method,
47
+ rejectUnauthorized: parsed.hostname !== "127.0.0.1",
48
+ headers: {
49
+ Accept: "application/json",
50
+ ...payload ? {
51
+ "Content-Type": "application/json",
52
+ "Content-Length": Buffer.byteLength(payload).toString()
53
+ } : {}
54
+ }
55
+ }, (response) => {
56
+ const chunks = [];
57
+ response.on("data", (chunk) => {
58
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
59
+ });
60
+ response.on("end", () => {
61
+ const text = Buffer.concat(chunks).toString("utf8");
62
+ if ((response.statusCode ?? 500) >= 400) {
63
+ reject(new Error(text || `Hub MCP request failed: ${response.statusCode}`));
64
+ return;
65
+ }
66
+ try {
67
+ resolve(JSON.parse(text));
68
+ } catch (error) {
69
+ reject(error);
70
+ }
71
+ });
72
+ });
73
+ request.on("error", reject);
74
+ if (payload) request.write(payload);
75
+ request.end();
76
+ });
77
+ }
78
+ //#endregion
79
+ export { createHubMcpBridgeServer as n, HttpHubMcpBridgeClient as t };
80
+
81
+ //# sourceMappingURL=bridge-D8_lAKaW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-D8_lAKaW.js","names":[],"sources":["../src/mcp/bridge.ts"],"sourcesContent":["import http from \"node:http\";\nimport https from \"node:https\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type { HubMcpToolDescriptor, HubMcpToolResult } from \"./tools.js\";\n\ntype JsonObject = Record<string, unknown>;\n\ninterface HubMcpBridgeClient {\n listTools(): Promise<HubMcpToolDescriptor[]>;\n callTool(name: string, args: unknown): Promise<HubMcpToolResult>;\n}\n\nexport class HttpHubMcpBridgeClient implements HubMcpBridgeClient {\n private readonly endpoint: string;\n\n constructor(endpoint: string) {\n if (!endpoint || endpoint.trim().length === 0) {\n throw new Error(\"TANGLE_HUB_MCP_ENDPOINT is required\");\n }\n this.endpoint = endpoint.replace(/\\/+$/, \"\");\n }\n\n async listTools(): Promise<HubMcpToolDescriptor[]> {\n const response = await requestJson<{ tools: HubMcpToolDescriptor[] }>(\n \"GET\",\n `${this.endpoint}/tools`,\n );\n return response.tools;\n }\n\n async callTool(name: string, args: unknown): Promise<HubMcpToolResult> {\n return requestJson<HubMcpToolResult>(\"POST\", `${this.endpoint}/call`, {\n name,\n arguments: args ?? {},\n });\n }\n}\n\nexport function createHubMcpBridgeServer(client: HubMcpBridgeClient): Server {\n const server = new Server(\n { name: \"tangle-hub\", version: \"0.1.0\" },\n { capabilities: { tools: {} } },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n const tools = await client.listTools();\n return {\n tools: tools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n })),\n };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request) =>\n client.callTool(request.params.name, request.params.arguments ?? {}),\n );\n\n return server;\n}\n\nfunction requestJson<T>(\n method: \"GET\" | \"POST\",\n url: string,\n body?: JsonObject,\n) {\n return new Promise<T>((resolve, reject) => {\n const parsed = new URL(url);\n const payload = body ? JSON.stringify(body) : undefined;\n const transport = parsed.protocol === \"https:\" ? https : http;\n const request = transport.request(\n {\n protocol: parsed.protocol,\n hostname: parsed.hostname,\n port: parsed.port,\n path: `${parsed.pathname}${parsed.search}`,\n method,\n rejectUnauthorized: parsed.hostname !== \"127.0.0.1\",\n headers: {\n Accept: \"application/json\",\n ...(payload\n ? {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(payload).toString(),\n }\n : {}),\n },\n },\n (response) => {\n const chunks: Buffer[] = [];\n response.on(\"data\", (chunk) => {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n });\n response.on(\"end\", () => {\n const text = Buffer.concat(chunks).toString(\"utf8\");\n if ((response.statusCode ?? 500) >= 400) {\n reject(\n new Error(\n text || `Hub MCP request failed: ${response.statusCode}`,\n ),\n );\n return;\n }\n try {\n resolve(JSON.parse(text) as T);\n } catch (error) {\n reject(error);\n }\n });\n },\n );\n request.on(\"error\", reject);\n if (payload) request.write(payload);\n request.end();\n });\n}\n"],"mappings":";;;;;AAgBA,IAAa,yBAAb,MAAkE;CAChE;CAEA,YAAY,UAAkB;AAC5B,MAAI,CAAC,YAAY,SAAS,MAAM,CAAC,WAAW,EAC1C,OAAM,IAAI,MAAM,sCAAsC;AAExD,OAAK,WAAW,SAAS,QAAQ,QAAQ,GAAG;;CAG9C,MAAM,YAA6C;AAKjD,UAAO,MAJgB,YACrB,OACA,GAAG,KAAK,SAAS,QAClB,EACe;;CAGlB,MAAM,SAAS,MAAc,MAA0C;AACrE,SAAO,YAA8B,QAAQ,GAAG,KAAK,SAAS,QAAQ;GACpE;GACA,WAAW,QAAQ,EAAE;GACtB,CAAC;;;AAIN,SAAgB,yBAAyB,QAAoC;CAC3E,MAAM,SAAS,IAAI,OACjB;EAAE,MAAM;EAAc,SAAS;EAAS,EACxC,EAAE,cAAc,EAAE,OAAO,EAAE,EAAE,EAAE,CAChC;AAED,QAAO,kBAAkB,wBAAwB,YAAY;AAE3D,SAAO,EACL,QAAO,MAFW,OAAO,WAAW,EAEvB,KAAK,UAAU;GAC1B,MAAM,KAAK;GACX,aAAa,KAAK;GAClB,aAAa,KAAK;GACnB,EAAE,EACJ;GACD;AAEF,QAAO,kBAAkB,uBAAuB,OAAO,YACrD,OAAO,SAAS,QAAQ,OAAO,MAAM,QAAQ,OAAO,aAAa,EAAE,CAAC,CACrE;AAED,QAAO;;AAGT,SAAS,YACP,QACA,KACA,MACA;AACA,QAAO,IAAI,SAAY,SAAS,WAAW;EACzC,MAAM,SAAS,IAAI,IAAI,IAAI;EAC3B,MAAM,UAAU,OAAO,KAAK,UAAU,KAAK,GAAG,KAAA;EAE9C,MAAM,WADY,OAAO,aAAa,WAAW,QAAQ,MAC/B,QACxB;GACE,UAAU,OAAO;GACjB,UAAU,OAAO;GACjB,MAAM,OAAO;GACb,MAAM,GAAG,OAAO,WAAW,OAAO;GAClC;GACA,oBAAoB,OAAO,aAAa;GACxC,SAAS;IACP,QAAQ;IACR,GAAI,UACA;KACE,gBAAgB;KAChB,kBAAkB,OAAO,WAAW,QAAQ,CAAC,UAAU;KACxD,GACD,EAAE;IACP;GACF,GACA,aAAa;GACZ,MAAM,SAAmB,EAAE;AAC3B,YAAS,GAAG,SAAS,UAAU;AAC7B,WAAO,KAAK,OAAO,SAAS,MAAM,GAAG,QAAQ,OAAO,KAAK,MAAM,CAAC;KAChE;AACF,YAAS,GAAG,aAAa;IACvB,MAAM,OAAO,OAAO,OAAO,OAAO,CAAC,SAAS,OAAO;AACnD,SAAK,SAAS,cAAc,QAAQ,KAAK;AACvC,YACE,IAAI,MACF,QAAQ,2BAA2B,SAAS,aAC7C,CACF;AACD;;AAEF,QAAI;AACF,aAAQ,KAAK,MAAM,KAAK,CAAM;aACvB,OAAO;AACd,YAAO,MAAM;;KAEf;IAEL;AACD,UAAQ,GAAG,SAAS,OAAO;AAC3B,MAAI,QAAS,SAAQ,MAAM,QAAQ;AACnC,UAAQ,KAAK;GACb"}