@honua/mcp-server 0.2.3 → 0.2.4

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.
@@ -0,0 +1,58 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
4
+ import type { DevOpsExtension } from "./devops-runtime.js";
5
+ import type { AgentConnection, AgentRuntime } from "./local-runtime.js";
6
+ export type UpstreamFactory = (connection: AgentConnection, view?: string) => Promise<Client>;
7
+ /** A gateway owns initialization; it does not impersonate the downstream client. */
8
+ export declare function connectAgentUpstream(connection: AgentConnection, view?: string, fetchImpl?: typeof fetch): Promise<Client>;
9
+ /** One configured installation per process. Target changes require an explicit host restart. */
10
+ export declare class HonuaAgentGateway {
11
+ #private;
12
+ private readonly runtime;
13
+ private readonly upstream;
14
+ private readonly devops?;
15
+ private readonly options;
16
+ readonly server: Server<{
17
+ method: string;
18
+ params?: {
19
+ [x: string]: unknown;
20
+ _meta?: {
21
+ [x: string]: unknown;
22
+ progressToken?: string | number | undefined;
23
+ "io.modelcontextprotocol/related-task"?: {
24
+ taskId: string;
25
+ } | undefined;
26
+ } | undefined;
27
+ } | undefined;
28
+ }, {
29
+ method: string;
30
+ params?: {
31
+ [x: string]: unknown;
32
+ _meta?: {
33
+ [x: string]: unknown;
34
+ progressToken?: string | number | undefined;
35
+ "io.modelcontextprotocol/related-task"?: {
36
+ taskId: string;
37
+ } | undefined;
38
+ } | undefined;
39
+ } | undefined;
40
+ }, {
41
+ [x: string]: unknown;
42
+ _meta?: {
43
+ [x: string]: unknown;
44
+ progressToken?: string | number | undefined;
45
+ "io.modelcontextprotocol/related-task"?: {
46
+ taskId: string;
47
+ } | undefined;
48
+ } | undefined;
49
+ }>;
50
+ constructor(runtime: AgentRuntime, upstream?: UpstreamFactory, devops?: DevOpsExtension | undefined, options?: {
51
+ bootstrapTools?: boolean;
52
+ });
53
+ connect(transport: Transport): Promise<void>;
54
+ close(): Promise<void>;
55
+ /** Native HTTP discovery must not race the external connector's background attachment. */
56
+ prepare(view?: string): Promise<void>;
57
+ }
58
+ //# sourceMappingURL=gateway.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../../../src/agent/gateway.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAEnE,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AA4B/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAe,MAAM,oBAAoB,CAAC;AA0DrF,MAAM,MAAM,eAAe,GAAG,CAAC,UAAU,EAAE,eAAe,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE9F,oFAAoF;AACpF,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,eAAe,EAC3B,IAAI,CAAC,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,OAAO,KAAK,GACvB,OAAO,CAAC,MAAM,CAAC,CAqBjB;AAMD,gGAAgG;AAChG,qBAAa,iBAAiB;;IAgB1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAlB1B,QAAQ,CAAC,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAKb;gBAUiB,OAAO,EAAE,YAAY,EACrB,QAAQ,GAAE,eAAsC,EAChD,MAAM,CAAC,EAAE,eAAe,YAAA,EACxB,OAAO,GAAE;QAAE,cAAc,CAAC,EAAE,OAAO,CAAA;KAAO;IAwGvD,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAqD5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B,0FAA0F;IACpF,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAuH5C"}
@@ -0,0 +1,381 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import { CallToolRequestSchema, CallToolResultSchema, ErrorCode, GetPromptRequestSchema, GetPromptResultSchema, InitializeRequestSchema, ListPromptsRequestSchema, ListPromptsResultSchema, ListResourceTemplatesRequestSchema, ListResourceTemplatesResultSchema, ListResourcesRequestSchema, ListResourcesResultSchema, ListToolsRequestSchema, ListToolsResultSchema, McpError, PromptListChangedNotificationSchema, ReadResourceRequestSchema, ReadResourceResultSchema, ResourceListChangedNotificationSchema, ToolListChangedNotificationSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { z } from "zod";
6
+ import { SERVER_VERSION } from "../index.js";
7
+ import { WORKFLOW_VIEW_META_KEY, buildUpstreamHeaders, readWorkflowView } from "../proxy.js";
8
+ import { WORKFLOW_KNOWLEDGE, WORKFLOW_KNOWLEDGE_URI } from "./workflow-knowledge.js";
9
+ const PREFIX = "honua_agent_";
10
+ const DEVOPS_PREFIX = "honua_agent_devops_";
11
+ const knowledgeResource = {
12
+ uri: WORKFLOW_KNOWLEDGE_URI,
13
+ name: "Honua conversational GIS workflows v1",
14
+ description: "Versioned workflow semantics and evidence requirements; canonical tools remain authoritative.",
15
+ mimeType: "application/json",
16
+ };
17
+ const installInput = z
18
+ .object({
19
+ confirm: z.literal(true),
20
+ profile: z.enum(["quickstart", "gp-dev"]).default("quickstart"),
21
+ httpPort: z.number().int().min(1).max(65535).default(8080),
22
+ })
23
+ .strict();
24
+ const emptyInput = z.object({}).strict();
25
+ const emptySchema = { type: "object", properties: {}, additionalProperties: false };
26
+ const localTools = [
27
+ {
28
+ name: `${PREFIX}status`,
29
+ description: "Read this connector's verified target and handoff state. No credentials are returned.",
30
+ inputSchema: emptySchema,
31
+ annotations: { readOnlyHint: true },
32
+ },
33
+ {
34
+ name: `${PREFIX}inspect_local`,
35
+ description: "Inspect Docker prerequisites on this host without creating an installation.",
36
+ inputSchema: emptySchema,
37
+ annotations: { readOnlyHint: true },
38
+ },
39
+ {
40
+ name: `${PREFIX}connect`,
41
+ description: "Verify and attach the host-configured existing local installation. Replaces no active target and executes no GIS mutation.",
42
+ inputSchema: emptySchema,
43
+ annotations: { readOnlyHint: true },
44
+ },
45
+ {
46
+ name: `${PREFIX}install_local`,
47
+ description: "Install Honua into the host-configured directory, then verify authentication and attach its MCP catalog. Requires explicit installation authorization; returns no secrets.",
48
+ inputSchema: {
49
+ type: "object",
50
+ properties: {
51
+ confirm: { const: true, type: "boolean" },
52
+ profile: { type: "string", enum: ["quickstart", "gp-dev"], default: "quickstart" },
53
+ httpPort: { type: "integer", minimum: 1, maximum: 65535, default: 8080 },
54
+ },
55
+ required: ["confirm"],
56
+ additionalProperties: false,
57
+ },
58
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false },
59
+ },
60
+ ];
61
+ /** A gateway owns initialization; it does not impersonate the downstream client. */
62
+ export async function connectAgentUpstream(connection, view, fetchImpl) {
63
+ const headers = buildUpstreamHeaders(connection.transport);
64
+ const transport = new StreamableHTTPClientTransport(new URL(connection.transport.remoteUrl), {
65
+ requestInit: { headers, redirect: "manual" },
66
+ ...(fetchImpl ? { fetch: fetchImpl } : {}),
67
+ });
68
+ const send = transport.send.bind(transport);
69
+ transport.send = (message, options) => {
70
+ if ("method" in message && message.method === "initialize" && view) {
71
+ return send({ ...message, params: { ...message.params, _meta: { [WORKFLOW_VIEW_META_KEY]: view } } }, options);
72
+ }
73
+ return send(message, options);
74
+ };
75
+ const client = new Client({ name: "honua-agent-gateway", version: SERVER_VERSION }, { capabilities: {} });
76
+ try {
77
+ await client.connect(transport);
78
+ return client;
79
+ }
80
+ catch {
81
+ await client.close().catch(() => { });
82
+ throw new Error("Upstream connection failed");
83
+ }
84
+ }
85
+ function result(value) {
86
+ return { content: [{ type: "text", text: JSON.stringify(value) }], structuredContent: value };
87
+ }
88
+ /** One configured installation per process. Target changes require an explicit host restart. */
89
+ export class HonuaAgentGateway {
90
+ runtime;
91
+ upstream;
92
+ devops;
93
+ options;
94
+ server = new Server({ name: "honua-agent", version: SERVER_VERSION }, {
95
+ capabilities: { tools: { listChanged: true }, resources: { listChanged: true }, prompts: { listChanged: true } },
96
+ });
97
+ #client;
98
+ #target;
99
+ #view;
100
+ #transition = false;
101
+ #active = 0;
102
+ #initialized = false;
103
+ #closed = false;
104
+ constructor(runtime, upstream = connectAgentUpstream, devops, options = {}) {
105
+ this.runtime = runtime;
106
+ this.upstream = upstream;
107
+ this.devops = devops;
108
+ this.options = options;
109
+ this.server.oninitialized = () => {
110
+ this.#initialized = true;
111
+ void this.#attach().catch(() => { });
112
+ };
113
+ this.server.onclose = () => {
114
+ this.#closed = true;
115
+ void this.#client?.close().catch(() => { });
116
+ void this.devops?.close().catch(() => { });
117
+ };
118
+ // Never log transport errors: HTTP error bodies can contain credentials.
119
+ this.server.onerror = () => { };
120
+ this.server.setRequestHandler(ListToolsRequestSchema, async (request, extra) => {
121
+ const bootstrap = this.options.bootstrapTools === false ? [] : localTools;
122
+ if (!this.#client)
123
+ return { tools: bootstrap };
124
+ const page = await this.#withClient((client) => client.request(request, ListToolsResultSchema, { signal: extra.signal }));
125
+ if (page.tools.some((tool) => tool.name.startsWith(PREFIX)))
126
+ throw new McpError(ErrorCode.InvalidRequest, "Reserved connector tool namespace collision");
127
+ // A missing/private integration must not make the public GIS catalog unavailable.
128
+ const privateTools = !request.params?.cursor && this.devops ? await this.devops.listTools().catch(() => []) : [];
129
+ return { ...page, tools: request.params?.cursor ? page.tools : [...bootstrap, ...privateTools, ...page.tools] };
130
+ });
131
+ this.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
132
+ const token = request.params._meta?.progressToken;
133
+ if (request.params.name.startsWith(DEVOPS_PREFIX)) {
134
+ if (!this.devops)
135
+ throw new McpError(ErrorCode.MethodNotFound, "Private DevOps integration is not configured");
136
+ return this.#withClient(() => this.devops.callTool(request.params.name, request.params.arguments ?? {}, {
137
+ signal: extra.signal,
138
+ ...(token === undefined
139
+ ? {}
140
+ : {
141
+ onprogress: (progress) => {
142
+ void extra
143
+ .sendNotification({
144
+ method: "notifications/progress",
145
+ params: { ...progress, progressToken: token },
146
+ })
147
+ .catch(() => { });
148
+ },
149
+ }),
150
+ }));
151
+ }
152
+ if (request.params.name.startsWith(PREFIX) && this.options.bootstrapTools === false)
153
+ throw new McpError(ErrorCode.MethodNotFound, "Host installation tools are unavailable in native chat");
154
+ if (request.params.name.startsWith(PREFIX))
155
+ return this.#local(request.params.name, request.params.arguments ?? {});
156
+ return this.#withClient((client) => client.request(request, CallToolResultSchema, {
157
+ signal: extra.signal,
158
+ ...(token === undefined
159
+ ? {}
160
+ : {
161
+ onprogress: (progress) => {
162
+ void extra
163
+ .sendNotification({
164
+ method: "notifications/progress",
165
+ params: { ...progress, progressToken: token },
166
+ })
167
+ .catch(() => { });
168
+ },
169
+ }),
170
+ }));
171
+ });
172
+ this.server.setRequestHandler(ListResourcesRequestSchema, async (request, extra) => {
173
+ if (!this.#client)
174
+ return { resources: [knowledgeResource] };
175
+ const page = await this.#withClient((client) => client.request(request, ListResourcesResultSchema, { signal: extra.signal }));
176
+ if (page.resources.some((resource) => resource.uri === WORKFLOW_KNOWLEDGE_URI))
177
+ throw new McpError(ErrorCode.InvalidRequest, "Reserved workflow knowledge URI collision");
178
+ return { ...page, resources: request.params?.cursor ? page.resources : [knowledgeResource, ...page.resources] };
179
+ });
180
+ this.server.setRequestHandler(ListResourceTemplatesRequestSchema, (request, extra) => !this.#client
181
+ ? { resourceTemplates: [] }
182
+ : this.#withClient((client) => client.request(request, ListResourceTemplatesResultSchema, { signal: extra.signal })));
183
+ this.server.setRequestHandler(ReadResourceRequestSchema, (request, extra) => request.params.uri === WORKFLOW_KNOWLEDGE_URI
184
+ ? {
185
+ contents: [
186
+ { uri: WORKFLOW_KNOWLEDGE_URI, mimeType: "application/json", text: JSON.stringify(WORKFLOW_KNOWLEDGE) },
187
+ ],
188
+ }
189
+ : this.#withClient((client) => client.request(request, ReadResourceResultSchema, { signal: extra.signal })));
190
+ this.server.setRequestHandler(ListPromptsRequestSchema, (request, extra) => !this.#client
191
+ ? { prompts: [] }
192
+ : this.#withClient((client) => client.request(request, ListPromptsResultSchema, { signal: extra.signal })));
193
+ this.server.setRequestHandler(GetPromptRequestSchema, (request, extra) => this.#withClient((client) => client.request(request, GetPromptResultSchema, { signal: extra.signal })));
194
+ }
195
+ async connect(transport) {
196
+ // Capture only the upstream-owned view selector while letting Server negotiate
197
+ // its own identity and capabilities with the downstream client.
198
+ let receiver;
199
+ const wrapper = {
200
+ start: () => transport.start(),
201
+ close: () => transport.close(),
202
+ send: (message, options) => transport.send(message, options),
203
+ get sessionId() {
204
+ return transport.sessionId;
205
+ },
206
+ setProtocolVersion: (version) => transport.setProtocolVersion?.(version),
207
+ get onclose() {
208
+ return transport.onclose;
209
+ },
210
+ set onclose(value) {
211
+ transport.onclose = value;
212
+ },
213
+ get onerror() {
214
+ return transport.onerror;
215
+ },
216
+ set onerror(value) {
217
+ transport.onerror = value;
218
+ },
219
+ get onmessage() {
220
+ return receiver;
221
+ },
222
+ set onmessage(value) {
223
+ receiver = value;
224
+ },
225
+ };
226
+ transport.onmessage = (message, extra) => {
227
+ if ("method" in message && message.method === "initialize") {
228
+ try {
229
+ this.#view = readWorkflowView(InitializeRequestSchema.parse(message));
230
+ }
231
+ catch {
232
+ if ("id" in message)
233
+ void transport
234
+ .send({
235
+ jsonrpc: "2.0",
236
+ id: message.id,
237
+ error: { code: ErrorCode.InvalidParams, message: "Invalid workflow view" },
238
+ })
239
+ .catch(() => { })
240
+ .finally(() => transport.close());
241
+ return;
242
+ }
243
+ }
244
+ receiver?.(message, extra);
245
+ };
246
+ await this.server.connect(wrapper);
247
+ }
248
+ async close() {
249
+ this.#closed = true;
250
+ await this.#client?.close();
251
+ await this.devops?.close();
252
+ await this.server.close();
253
+ }
254
+ /** Native HTTP discovery must not race the external connector's background attachment. */
255
+ async prepare(view) {
256
+ this.#view = view;
257
+ await this.#attach();
258
+ }
259
+ async #withClient(call) {
260
+ const client = this.#client;
261
+ if (!client || this.#transition)
262
+ throw new McpError(ErrorCode.InvalidRequest, "Honua is not attached; inspect connector status");
263
+ this.#active++;
264
+ try {
265
+ return await call(client);
266
+ }
267
+ catch (error) {
268
+ // Preserve structured tool errors (returned results), but never echo raw
269
+ // HTTP/protocol exception messages into a credentialed model session.
270
+ if (error instanceof McpError && error.code === ErrorCode.MethodNotFound)
271
+ throw new McpError(error.code, "Operation unavailable on the attached deployment");
272
+ throw new McpError(ErrorCode.InternalError, "Upstream request failed; outcome may be unknown. Inspect the existing operation before retrying a mutation.");
273
+ }
274
+ finally {
275
+ this.#active--;
276
+ }
277
+ }
278
+ async #notify() {
279
+ if (!this.#initialized || this.#closed)
280
+ return;
281
+ await Promise.all([
282
+ this.server.notification({ method: "notifications/tools/list_changed" }),
283
+ this.server.notification({ method: "notifications/resources/list_changed" }),
284
+ this.server.notification({ method: "notifications/prompts/list_changed" }),
285
+ ]).catch(() => { });
286
+ }
287
+ async #attach(install) {
288
+ if (this.#transition || this.#active)
289
+ throw new Error("Connector is busy");
290
+ if (install && this.#target)
291
+ throw new Error("An established target cannot be reinstalled in this session");
292
+ if (this.#client) {
293
+ if (install)
294
+ throw new Error("Already attached; installation cannot run in an active session");
295
+ return;
296
+ }
297
+ this.#transition = true;
298
+ let candidate;
299
+ try {
300
+ if (install)
301
+ await this.runtime.install(install);
302
+ const connection = await this.runtime.resolve();
303
+ if (this.#target && this.#target.id !== connection.target.id)
304
+ throw new Error("Target identity changed; reconnect explicitly");
305
+ candidate = await this.upstream(connection, this.#view);
306
+ const cursors = new Set();
307
+ let cursor;
308
+ do {
309
+ const page = await candidate.listTools(cursor ? { cursor } : undefined);
310
+ if (page.tools.some((tool) => tool.name.startsWith(PREFIX)))
311
+ throw new Error("Reserved namespace collision");
312
+ cursor = page.nextCursor;
313
+ if (cursor && (cursors.has(cursor) || cursors.size >= 100))
314
+ throw new Error("Invalid catalog pagination");
315
+ if (cursor)
316
+ cursors.add(cursor);
317
+ } while (cursor);
318
+ if (this.#closed)
319
+ throw new Error("Connector closed during attachment");
320
+ const attached = candidate;
321
+ attached.onerror = () => {
322
+ if (this.#client === attached) {
323
+ this.#client = undefined;
324
+ void attached.close().catch(() => { });
325
+ void this.#notify();
326
+ }
327
+ };
328
+ attached.onclose = () => {
329
+ if (this.#client === attached) {
330
+ this.#client = undefined;
331
+ void this.#notify();
332
+ }
333
+ };
334
+ attached.setNotificationHandler(ToolListChangedNotificationSchema, () => this.#notify());
335
+ attached.setNotificationHandler(ResourceListChangedNotificationSchema, () => this.#notify());
336
+ attached.setNotificationHandler(PromptListChangedNotificationSchema, () => this.#notify());
337
+ this.#client = attached;
338
+ this.#target = connection.target;
339
+ candidate = undefined;
340
+ }
341
+ finally {
342
+ await candidate?.close().catch(() => { });
343
+ this.#transition = false;
344
+ await this.#notify();
345
+ }
346
+ }
347
+ async #local(name, args) {
348
+ try {
349
+ if (name !== `${PREFIX}install_local`)
350
+ emptyInput.parse(args);
351
+ switch (name) {
352
+ case `${PREFIX}status`:
353
+ return result({
354
+ state: this.#transition ? "connecting" : this.#client ? "attached" : "unattached",
355
+ target: this.#target ?? null,
356
+ activeRequests: this.#active,
357
+ });
358
+ case `${PREFIX}inspect_local`:
359
+ return result(await this.runtime.inspect());
360
+ case `${PREFIX}connect`:
361
+ await this.#attach();
362
+ return result({ state: "attached", target: this.#target });
363
+ case `${PREFIX}install_local`:
364
+ await this.#attach(installInput.parse(args));
365
+ return result({ state: "attached", target: this.#target });
366
+ default:
367
+ throw new McpError(ErrorCode.MethodNotFound, "Unknown connector tool");
368
+ }
369
+ }
370
+ catch {
371
+ return {
372
+ ...result({
373
+ state: "unavailable",
374
+ code: "connector-action-refused",
375
+ next: "Inspect connector status and local prerequisites; do not retry uncertain mutations.",
376
+ }),
377
+ isError: true,
378
+ };
379
+ }
380
+ }
381
+ }
@@ -0,0 +1,26 @@
1
+ import { type LocalInstallOptions } from "@honua/sdk-js/local-install";
2
+ import type { ProxyOptions } from "../proxy.js";
3
+ /** Preview artifacts come from host configuration, never from model tool arguments. */
4
+ export declare function nativeWorkspaceFromEnv(env: NodeJS.ProcessEnv): LocalInstallOptions["nativeWorkspace"];
5
+ export interface AgentTarget {
6
+ readonly id: string;
7
+ readonly endpoint: string;
8
+ readonly credentialId: string;
9
+ }
10
+ export interface AgentConnection {
11
+ readonly target: AgentTarget;
12
+ /** Transport-only configuration. Never serialize this into an MCP result. */
13
+ readonly transport: ProxyOptions;
14
+ }
15
+ export interface AgentRuntime {
16
+ inspect(): Promise<Record<string, unknown>>;
17
+ install(input: {
18
+ confirm: true;
19
+ profile: "quickstart" | "gp-dev";
20
+ httpPort: number;
21
+ }): Promise<void>;
22
+ resolve(): Promise<AgentConnection>;
23
+ }
24
+ /** The host fixes the installation root; no tool accepts credential paths or URLs. */
25
+ export declare function createLocalAgentRuntime(directory: string, nativeWorkspace?: LocalInstallOptions["nativeWorkspace"]): AgentRuntime;
26
+ //# sourceMappingURL=local-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-runtime.d.ts","sourceRoot":"","sources":["../../../src/agent/local-runtime.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,KAAK,mBAAmB,EAKzB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD,uFAAuF;AACvF,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,mBAAmB,CAAC,iBAAiB,CAAC,CAiErG;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC5C,OAAO,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,YAAY,GAAG,QAAQ,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrG,OAAO,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;CACrC;AAED,sFAAsF;AACtF,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EACjB,eAAe,CAAC,EAAE,mBAAmB,CAAC,iBAAiB,CAAC,GACvD,YAAY,CA4Ed"}
@@ -0,0 +1,152 @@
1
+ import { execFile } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { arch, platform } from "node:os";
4
+ import path from "node:path";
5
+ import { promisify } from "node:util";
6
+ import { inspectLocalAgentAccess, inspectLocalWorkspaceAgentAccess, installHonuaLocal, readVerifiedLocalEnv, } from "@honua/sdk-js/local-install";
7
+ const execute = promisify(execFile);
8
+ /** Preview artifacts come from host configuration, never from model tool arguments. */
9
+ export function nativeWorkspaceFromEnv(env) {
10
+ const prefix = "HONUA_AGENT_PREVIEW_";
11
+ const modelNames = ["MODEL_KIND", "MODEL_ENDPOINT", "MODEL_NAME", "MODEL_REGION", "MODEL_KEY_ENV"];
12
+ const gatewayNames = ["GATEWAY_HTTP_URL", "GATEWAY_HTTP_KEY_ENV"];
13
+ const processNames = ["GP_WORKSPACE_URL", "GP_WORKSPACE_KEY_ENV"];
14
+ const names = [
15
+ "SERVER_IMAGE",
16
+ "CONSOLE_IMAGE",
17
+ "RELEASE_ID",
18
+ "GATEWAY_COMMAND",
19
+ "GATEWAY_ARGS",
20
+ "CONSOLE_PORT",
21
+ ...modelNames,
22
+ ...gatewayNames,
23
+ ...processNames,
24
+ ];
25
+ if (!names.some((name) => env[`${prefix}${name}`] !== undefined))
26
+ return undefined;
27
+ const required = (name) => {
28
+ const value = env[`${prefix}${name}`];
29
+ if (!value?.trim())
30
+ throw new Error(`Incomplete native workspace host configuration: ${name}`);
31
+ return value;
32
+ };
33
+ let gatewayArgs = [];
34
+ if (env.HONUA_AGENT_PREVIEW_GATEWAY_ARGS !== undefined) {
35
+ const parsed = JSON.parse(env.HONUA_AGENT_PREVIEW_GATEWAY_ARGS);
36
+ if (!Array.isArray(parsed) || !parsed.every((arg) => typeof arg === "string"))
37
+ throw new Error("Preview gateway arguments must be a JSON string array");
38
+ gatewayArgs = parsed;
39
+ }
40
+ const consolePort = env.HONUA_AGENT_PREVIEW_CONSOLE_PORT === undefined ? undefined : Number(env.HONUA_AGENT_PREVIEW_CONSOLE_PORT);
41
+ if (consolePort !== undefined && (!Number.isInteger(consolePort) || consolePort < 1 || consolePort > 65535))
42
+ throw new Error("Invalid preview Console port");
43
+ let modelProvider;
44
+ if (modelNames.some((name) => env[`${prefix}${name}`] !== undefined)) {
45
+ const kind = required("MODEL_KIND");
46
+ if (kind !== "openai" && kind !== "anthropic" && kind !== "bedrock")
47
+ throw new Error("Invalid preview model provider kind");
48
+ modelProvider = {
49
+ kind,
50
+ model: required("MODEL_NAME"),
51
+ ...(env.HONUA_AGENT_PREVIEW_MODEL_ENDPOINT === undefined ? {} : { endpoint: required("MODEL_ENDPOINT") }),
52
+ ...(env.HONUA_AGENT_PREVIEW_MODEL_REGION === undefined ? {} : { region: required("MODEL_REGION") }),
53
+ ...(env.HONUA_AGENT_PREVIEW_MODEL_KEY_ENV === undefined
54
+ ? {}
55
+ : { apiKeyEnvironmentVariable: required("MODEL_KEY_ENV") }),
56
+ };
57
+ }
58
+ const gatewayHttp = gatewayNames.some((name) => env[`${prefix}${name}`] !== undefined)
59
+ ? { url: required("GATEWAY_HTTP_URL"), keyEnvironmentVariable: required("GATEWAY_HTTP_KEY_ENV") }
60
+ : undefined;
61
+ const localProcesses = processNames.some((name) => env[`${prefix}${name}`] !== undefined)
62
+ ? { url: required("GP_WORKSPACE_URL"), keyEnvironmentVariable: required("GP_WORKSPACE_KEY_ENV") }
63
+ : undefined;
64
+ return {
65
+ serverImage: required("SERVER_IMAGE"),
66
+ consoleImage: required("CONSOLE_IMAGE"),
67
+ releaseId: required("RELEASE_ID"),
68
+ gatewayCommand: required("GATEWAY_COMMAND"),
69
+ gatewayArgs,
70
+ ...(consolePort === undefined ? {} : { consolePort }),
71
+ ...(modelProvider === undefined ? {} : { modelProvider }),
72
+ ...(gatewayHttp === undefined ? {} : { gatewayHttp }),
73
+ ...(localProcesses === undefined ? {} : { localProcesses }),
74
+ };
75
+ }
76
+ /** The host fixes the installation root; no tool accepts credential paths or URLs. */
77
+ export function createLocalAgentRuntime(directory, nativeWorkspace) {
78
+ const root = path.resolve(directory);
79
+ async function docker(args) {
80
+ const result = await execute("docker", args, { timeout: 15_000, maxBuffer: 256 * 1024 });
81
+ return result.stdout.trim();
82
+ }
83
+ async function inspect() {
84
+ const facts = {
85
+ directory: root,
86
+ platform: platform(),
87
+ architecture: arch(),
88
+ ready: false,
89
+ };
90
+ try {
91
+ const context = JSON.parse(await docker(["context", "inspect"]));
92
+ const endpoint = process.env.DOCKER_CONTEXT
93
+ ? context[0]?.Endpoints.docker.Host
94
+ : process.env.DOCKER_HOST || context[0]?.Endpoints.docker.Host;
95
+ // This first local lane deliberately excludes remote engines and Windows pipes.
96
+ facts.localDaemon = typeof endpoint === "string" && endpoint.startsWith("unix://");
97
+ if (!facts.localDaemon)
98
+ return { ...facts, reason: "local-unix-docker-context-required" };
99
+ facts.engineVersion = await docker(["version", "--format", "{{.Server.Version}}"]);
100
+ facts.composeVersion = await docker(["compose", "version", "--short"]);
101
+ const version = String(facts.composeVersion)
102
+ .replace(/^v/, "")
103
+ .match(/^(\d+)\.(\d+)\.(\d+)/);
104
+ const compatible = version &&
105
+ (Number(version[1]) > 2 ||
106
+ (Number(version[1]) === 2 &&
107
+ (Number(version[2]) > 23 || (Number(version[2]) === 23 && Number(version[3]) >= 1))));
108
+ return { ...facts, ready: Boolean(compatible), ...(compatible ? {} : { reason: "compose-2.23.1-required" }) };
109
+ }
110
+ catch {
111
+ return { ...facts, reason: "docker-or-compose-unavailable" };
112
+ }
113
+ }
114
+ return {
115
+ inspect,
116
+ async install(input) {
117
+ if (!(await inspect()).ready)
118
+ throw new Error("Local Docker prerequisites are not ready");
119
+ await installHonuaLocal({ directory: root, profile: input.profile, httpPort: input.httpPort, nativeWorkspace });
120
+ },
121
+ async resolve() {
122
+ const env = await readVerifiedLocalEnv(root);
123
+ const workspaceCredentialPresent = env.HONUA_AGENT_API_KEY !== undefined || env.HONUA_AGENT_KEY_ID !== undefined;
124
+ if (workspaceCredentialPresent && (!env.HONUA_AGENT_API_KEY || !env.HONUA_AGENT_KEY_ID)) {
125
+ throw new Error("Native workspace credential identity is incomplete");
126
+ }
127
+ const apiKey = workspaceCredentialPresent ? env.HONUA_AGENT_API_KEY : env.HONUA_ADMIN_KEY;
128
+ const port = Number(env.HONUA_HTTP_PORT);
129
+ if (!Number.isInteger(port) || port < 1 || port > 65535 || !apiKey) {
130
+ throw new Error("No verified local installation");
131
+ }
132
+ const baseUrl = `http://127.0.0.1:${port}`;
133
+ const response = await fetch(`${baseUrl}/healthz/ready`, {
134
+ signal: AbortSignal.timeout(10_000),
135
+ redirect: "error",
136
+ });
137
+ if (!response.ok || (await response.text()).trim().toLowerCase() !== "ready") {
138
+ throw new Error("Installed server is not ready");
139
+ }
140
+ const fetchCredential = (input, init) => fetch(input, { ...init, signal: AbortSignal.timeout(10_000), redirect: "error" });
141
+ const credential = workspaceCredentialPresent
142
+ ? await inspectLocalWorkspaceAgentAccess(baseUrl, apiKey, env.HONUA_AGENT_KEY_ID, fetchCredential)
143
+ : await inspectLocalAgentAccess(baseUrl, apiKey, fetchCredential);
144
+ const endpoint = `${baseUrl}/mcp`;
145
+ const id = createHash("sha256").update(`${endpoint}\n${credential.id}`).digest("hex");
146
+ return {
147
+ target: { id, endpoint, credentialId: credential.id },
148
+ transport: { remoteUrl: endpoint, apiKey },
149
+ };
150
+ },
151
+ };
152
+ }
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ /** Host-only native bridge. The browser continues to use Console's authenticated BFF. */
3
+ export declare function runNativeAgentGateway(env?: NodeJS.ProcessEnv): Promise<import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>>;
4
+ //# sourceMappingURL=native-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-cli.d.ts","sourceRoot":"","sources":["../../../src/agent/native-cli.ts"],"names":[],"mappings":";AAOA,yFAAyF;AACzF,wBAAsB,qBAAqB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,+GA6B/E"}