@agentmesh-p2p/sdk 0.1.1 → 0.2.0

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
@@ -24,6 +24,66 @@ Requires Node.js 22.18 or later. In a browser, bundle it with the page
24
24
  [In a browser](https://google.github.io/agentmesh/docs/guides/native-sdks/#in-a-browser)
25
25
  section has the details and an example page.
26
26
 
27
+ ## MCP client over stdio
28
+
29
+ The package's `agentmesh` executable joins as its own mesh member and exposes
30
+ five MCP tools: `get_mesh_info`, `discover_remote_services`, `find_remote_tools`,
31
+ `describe_remote_tool` and `call_remote_tool`. A stdio-capable MCP client starts
32
+ and stops the process; no node daemon is needed for this caller. Providers still
33
+ publish their services through `agentmesh-node`.
34
+
35
+ To try the executable from a source checkout:
36
+
37
+ ```bash
38
+ cd sdk/js
39
+ npm ci
40
+ npm run build
41
+ ```
42
+
43
+ Use absolute paths in your MCP client's configuration:
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "agentmesh": {
49
+ "command": "node",
50
+ "args": ["/absolute/path/to/agentmesh/sdk/js/dist/cli.js"],
51
+ "env": {
52
+ "AGENTMESH_CONTROL_PLANE_URL": "https://your-control-plane.example",
53
+ "AGENTMESH_BOOTSTRAP_TOKEN_PATH": "/absolute/path/to/join-token",
54
+ "AGENTMESH_STATE_DIR": "/absolute/path/to/this-client-identity"
55
+ }
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ Get the control plane URL and token file from your mesh operator or
62
+ [`agentmesh-one`](https://google.github.io/agentmesh/docs/guides/native-sdks/).
63
+ Alternatively, set `AGENTMESH_JWT_PATH` to a workload/OIDC token file; do not set
64
+ both token paths. Credentials are read from files, never command-line values.
65
+
66
+ The first run enrolls and saves its identity; later runs resume from the state
67
+ directory without the bootstrap token. JWT enrollment rereads the token file
68
+ when refreshing. Use a distinct state directory for each client identity and
69
+ mesh; do not run multiple processes against the same directory. The default is
70
+ `~/.config/agentmesh/mcp`. Plain HTTP is allowed on loopback only unless
71
+ `AGENTMESH_INSECURE_CONTROL_PLANE=true` explicitly permits it elsewhere.
72
+
73
+ Stdout contains only MCP messages; diagnostics go to stderr. Closing stdin or
74
+ sending SIGINT/SIGTERM shuts down the mesh session. Tool calls use the member's
75
+ existing authenticated streams and policy, including provider label checks.
76
+ Remote tool results retain text, non-text content, structured output and errors.
77
+ Discovery follows tool pagination, rejects looping catalogs and bounds each
78
+ search to 64 providers, 256 services per provider, 256 tools per service and
79
+ 1024 results. Requests have a 10-second deadline; narrow a large search by peer
80
+ or service. `intent` is accepted but ignored, matching the node's current tool.
81
+
82
+ This member publishes no services, so it has no `list_local_services` tool.
83
+ Inference and A2A discovery return metadata, but this executable provides no
84
+ HTTP proxy, `/v1` endpoint, local socket or `local_proxy_url`. Use an SDK client
85
+ or an `agentmesh-node` HTTP proxy to invoke those protocols.
86
+
27
87
  ## Use
28
88
 
29
89
  Both programs below are in
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ // Copyright 2026 Google LLC
3
+ //
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // you may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ //
8
+ // http://www.apache.org/licenses/LICENSE-2.0
9
+ //
10
+ // Unless required by applicable law or agreed to in writing, software
11
+ // distributed under the License is distributed on an "AS IS" BASIS,
12
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ // See the License for the specific language governing permissions and
14
+ // limitations under the License.
15
+ import { readFile } from "node:fs/promises";
16
+ import { homedir } from "node:os";
17
+ import { isAbsolute, join } from "node:path";
18
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
19
+ import { AgentMesh } from "./mesh.js";
20
+ import { createMeshMCPServer } from "./mcp-server.js";
21
+ function options() {
22
+ const env = process.env;
23
+ const controlPlaneUrl = env.AGENTMESH_CONTROL_PLANE_URL?.trim();
24
+ if (!controlPlaneUrl)
25
+ throw new Error("AGENTMESH_CONTROL_PLANE_URL is required");
26
+ if (env.AGENTMESH_BOOTSTRAP_TOKEN_PATH && env.AGENTMESH_JWT_PATH) {
27
+ throw new Error("set only one of AGENTMESH_BOOTSTRAP_TOKEN_PATH and AGENTMESH_JWT_PATH");
28
+ }
29
+ if (env.AGENTMESH_INSECURE_CONTROL_PLANE && !["true", "false"].includes(env.AGENTMESH_INSECURE_CONTROL_PLANE)) {
30
+ throw new Error("AGENTMESH_INSECURE_CONTROL_PLANE must be true or false");
31
+ }
32
+ // A client launches this from any working directory; a relative path would
33
+ // silently read credentials or enrol an identity somewhere unexpected.
34
+ for (const name of ["AGENTMESH_BOOTSTRAP_TOKEN_PATH", "AGENTMESH_JWT_PATH", "AGENTMESH_STATE_DIR"]) {
35
+ if (env[name] && !isAbsolute(env[name]))
36
+ throw new Error(`${name} must be an absolute path`);
37
+ }
38
+ return {
39
+ controlPlaneUrl,
40
+ bootstrapTokenPath: env.AGENTMESH_BOOTSTRAP_TOKEN_PATH || undefined,
41
+ jwtPath: env.AGENTMESH_JWT_PATH || undefined,
42
+ stateDir: env.AGENTMESH_STATE_DIR || join(homedir(), ".config", "agentmesh", "mcp"),
43
+ allowInsecure: env.AGENTMESH_INSECURE_CONTROL_PLANE === "true",
44
+ };
45
+ }
46
+ async function main() {
47
+ if (process.argv.length > 2) {
48
+ if (process.argv.length !== 3 || !["--help", "-h"].includes(process.argv[2])) {
49
+ throw new Error("agentmesh accepts no arguments other than --help; configure it through AGENTMESH_* environment variables");
50
+ }
51
+ console.error("agentmesh: MCP over stdio\n" +
52
+ "AGENTMESH_CONTROL_PLANE_URL Mesh control plane URL (required)\n" +
53
+ "AGENTMESH_BOOTSTRAP_TOKEN_PATH Bootstrap token file for first enrollment\n" +
54
+ "AGENTMESH_JWT_PATH OIDC/workload token file, read again on refresh\n" +
55
+ "AGENTMESH_STATE_DIR Identity directory (default: ~/.config/agentmesh/mcp)\n" +
56
+ "AGENTMESH_INSECURE_CONTROL_PLANE true permits plaintext HTTP beyond loopback (default: false)");
57
+ return;
58
+ }
59
+ const config = options();
60
+ const { version } = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
61
+ const abort = new AbortController();
62
+ let stop;
63
+ const stopped = new Promise((resolve) => { stop = () => { abort.abort(); resolve(); }; });
64
+ process.once("SIGINT", stop);
65
+ process.once("SIGTERM", stop);
66
+ process.stdin.once("end", stop);
67
+ let session;
68
+ const ready = AgentMesh.enroll({
69
+ ...config,
70
+ signal: abort.signal,
71
+ // EnrollOptions.signal bounds pending approval, not every HTTP request.
72
+ // The executable owns this client: shutdown must also cancel enrollment,
73
+ // credential resume and key fetches while the control plane is silent.
74
+ fetch: (input, init) => fetch(input, {
75
+ ...init,
76
+ signal: AbortSignal.any([abort.signal, ...(init?.signal ? [init.signal] : [])]),
77
+ }),
78
+ }).then(async (mesh) => {
79
+ session = await mesh.join({ signal: abort.signal });
80
+ return session;
81
+ });
82
+ // Handle startup failure immediately, including before a client calls a tool.
83
+ void ready.catch((err) => {
84
+ if (!abort.signal.aborted) {
85
+ console.error(`agentmesh: ${err instanceof Error ? err.message : String(err)}`);
86
+ process.exitCode = 1;
87
+ }
88
+ stop();
89
+ });
90
+ const server = createMeshMCPServer(ready, version);
91
+ server.server.onclose = stop;
92
+ try {
93
+ await server.connect(new StdioServerTransport());
94
+ await stopped;
95
+ }
96
+ finally {
97
+ stop();
98
+ try {
99
+ await server.close();
100
+ }
101
+ finally {
102
+ await ready.catch(() => undefined);
103
+ try {
104
+ await session?.close();
105
+ }
106
+ finally {
107
+ process.removeListener("SIGINT", stop);
108
+ process.removeListener("SIGTERM", stop);
109
+ process.stdin.removeListener("end", stop);
110
+ }
111
+ }
112
+ }
113
+ }
114
+ void main().catch((err) => {
115
+ console.error(`agentmesh: ${err instanceof Error ? err.message : String(err)}`);
116
+ process.exitCode = 1;
117
+ });
@@ -344,6 +344,37 @@ export type BootstrapEnrollResponse = Message<"agentmesh.v1.BootstrapEnrollRespo
344
344
  * Use `create(BootstrapEnrollResponseSchema)` to create a new message.
345
345
  */
346
346
  export declare const BootstrapEnrollResponseSchema: GenMessage<BootstrapEnrollResponse>;
347
+ /**
348
+ * ServiceIngress configures external router HTTP ingress for an inbound service
349
+ * (a2a, mcp, or inference). Absent means the service is reachable only inside
350
+ * the mesh.
351
+ *
352
+ * @generated from message agentmesh.v1.ServiceIngress
353
+ */
354
+ export type ServiceIngress = Message<"agentmesh.v1.ServiceIngress"> & {
355
+ /**
356
+ * Defaults to INGRESS_AUTH_MODE_EDGE when UNSPECIFIED.
357
+ *
358
+ * @generated from field: agentmesh.v1.IngressAuthMode auth_mode = 1;
359
+ */
360
+ authMode: IngressAuthMode;
361
+ /**
362
+ * Optional caller narrowing in PolicyBinding.members syntax
363
+ * (e.g. "email:alice@example.com", "email:*@example.com", "group:eng").
364
+ * Intersected (AND) with mesh Datalog policy: when empty, any caller
365
+ * authorized by mesh policy for this service may invoke it; when non-empty,
366
+ * the verified caller must also match at least one entry here. Only allowed
367
+ * in INGRESS_AUTH_MODE_EDGE.
368
+ *
369
+ * @generated from field: repeated string allow = 2;
370
+ */
371
+ allow: string[];
372
+ };
373
+ /**
374
+ * Describes the message agentmesh.v1.ServiceIngress.
375
+ * Use `create(ServiceIngressSchema)` to create a new message.
376
+ */
377
+ export declare const ServiceIngressSchema: GenMessage<ServiceIngress>;
347
378
  /**
348
379
  * @generated from message agentmesh.v1.ServiceInfo
349
380
  */
@@ -360,6 +391,12 @@ export type ServiceInfo = Message<"agentmesh.v1.ServiceInfo"> & {
360
391
  * @generated from field: string description = 3;
361
392
  */
362
393
  description: string;
394
+ /**
395
+ * Optional external ingress exposure for this service.
396
+ *
397
+ * @generated from field: agentmesh.v1.ServiceIngress ingress = 4;
398
+ */
399
+ ingress?: ServiceIngress | undefined;
363
400
  };
364
401
  /**
365
402
  * Describes the message agentmesh.v1.ServiceInfo.
@@ -621,6 +658,14 @@ export type DirectoryProvide = Message<"agentmesh.v1.DirectoryProvide"> & {
621
658
  * @generated from field: string service = 4;
622
659
  */
623
660
  service: string;
661
+ /**
662
+ * Optional external ingress metadata for a named service record. When set,
663
+ * the router verifies that the provider's credential grants exposing this
664
+ * service with the requested auth_mode before storing or replicating it.
665
+ *
666
+ * @generated from field: agentmesh.v1.ServiceIngress ingress = 5;
667
+ */
668
+ ingress?: ServiceIngress | undefined;
624
669
  };
625
670
  /**
626
671
  * Describes the message agentmesh.v1.DirectoryProvide.
@@ -727,6 +772,12 @@ export type PeerRecord = Message<"agentmesh.v1.PeerRecord"> & {
727
772
  * @generated from field: repeated string addrs = 2;
728
773
  */
729
774
  addrs: string[];
775
+ /**
776
+ * Present on service provider records that opted into external ingress.
777
+ *
778
+ * @generated from field: agentmesh.v1.ServiceIngress ingress = 3;
779
+ */
780
+ ingress?: ServiceIngress | undefined;
730
781
  };
731
782
  /**
732
783
  * Describes the message agentmesh.v1.PeerRecord.
@@ -753,8 +804,8 @@ export type RouterInfo = Message<"agentmesh.v1.RouterInfo"> & {
753
804
  * The router's labels, the same labels as a node's: declared at
754
805
  * enrollment (agentmesh-router --label), signed by the control plane only
755
806
  * if the router's role allows them (PolicyRole.allowed_labels), and
756
- * carried as label() facts in the router's credential, where a member
757
- * verifies them at the handshake. A member selects routers by them
807
+ * carried as label() facts in the router's credential. A member selects
808
+ * routers by the labels listed here, before it connects
758
809
  * (--router-selector, every pair must match) or prefers routers by them
759
810
  * (--router-prefer). They place a router; they grant it nothing.
760
811
  *
@@ -775,6 +826,14 @@ export type RouterInfo = Message<"agentmesh.v1.RouterInfo"> & {
775
826
  * @generated from field: int32 connection_limit = 5;
776
827
  */
777
828
  connectionLimit: number;
829
+ /**
830
+ * Base HTTPS/HTTP URL where this router serves external ingress, without a
831
+ * trailing slash (e.g. "https://bananas.sam-mesh.dev/ingress"). Empty when
832
+ * external ingress is disabled on this router.
833
+ *
834
+ * @generated from field: string ingress_url = 6;
835
+ */
836
+ ingressUrl: string;
778
837
  };
779
838
  /**
780
839
  * Describes the message agentmesh.v1.RouterInfo.
@@ -840,6 +899,12 @@ export type RouterLeaseRequest = Message<"agentmesh.v1.RouterLeaseRequest"> & {
840
899
  * @generated from field: int32 connection_limit = 10;
841
900
  */
842
901
  connectionLimit: number;
902
+ /**
903
+ * Optional base URL where this router serves external HTTP ingress.
904
+ *
905
+ * @generated from field: string ingress_url = 11;
906
+ */
907
+ ingressUrl: string;
843
908
  };
844
909
  /**
845
910
  * Describes the message agentmesh.v1.RouterLeaseRequest.
@@ -914,12 +979,46 @@ export type PolicyRole = Message<"agentmesh.v1.PolicyRole"> & {
914
979
  * @generated from field: repeated string allowed_publish = 7;
915
980
  */
916
981
  allowedPublish: string[];
982
+ /**
983
+ * Inbound services a member with this role may expose on router HTTP
984
+ * ingress, together with the permitted IngressAuthModes. Empty denies all
985
+ * external ingress exposure for this role.
986
+ *
987
+ * @generated from field: repeated agentmesh.v1.IngressGrant allowed_ingress = 8;
988
+ */
989
+ allowedIngress: IngressGrant[];
917
990
  };
918
991
  /**
919
992
  * Describes the message agentmesh.v1.PolicyRole.
920
993
  * Use `create(PolicyRoleSchema)` to create a new message.
921
994
  */
922
995
  export declare const PolicyRoleSchema: GenMessage<PolicyRole>;
996
+ /**
997
+ * IngressGrant permits a role to expose matching inbound services on router
998
+ * HTTP ingress under the listed authentication modes.
999
+ *
1000
+ * @generated from message agentmesh.v1.IngressGrant
1001
+ */
1002
+ export type IngressGrant = Message<"agentmesh.v1.IngressGrant"> & {
1003
+ /**
1004
+ * Inbound service patterns ("a2a://*", "mcp://calc", "*"). Egress
1005
+ * destinations ("egress://...") cannot be exposed as ingress.
1006
+ *
1007
+ * @generated from field: repeated string services = 1;
1008
+ */
1009
+ services: string[];
1010
+ /**
1011
+ * Permitted auth modes. Empty grants ONLY INGRESS_AUTH_MODE_EDGE.
1012
+ *
1013
+ * @generated from field: repeated agentmesh.v1.IngressAuthMode allowed_auth_modes = 2;
1014
+ */
1015
+ allowedAuthModes: IngressAuthMode[];
1016
+ };
1017
+ /**
1018
+ * Describes the message agentmesh.v1.IngressGrant.
1019
+ * Use `create(IngressGrantSchema)` to create a new message.
1020
+ */
1021
+ export declare const IngressGrantSchema: GenMessage<IngressGrant>;
923
1022
  /**
924
1023
  * HTTPGrant narrows one allowed_services entry to HTTP methods and paths.
925
1024
  * The control plane compiles it into granted_method and granted_path_* facts
@@ -2037,6 +2136,10 @@ export type RouterLease = Message<"agentmesh.v1.RouterLease"> & {
2037
2136
  * @generated from field: int32 dht_size = 6;
2038
2137
  */
2039
2138
  dhtSize: number;
2139
+ /**
2140
+ * @generated from field: string ingress_url = 7;
2141
+ */
2142
+ ingressUrl: string;
2040
2143
  };
2041
2144
  /**
2042
2145
  * Describes the message agentmesh.v1.RouterLease.
@@ -2704,6 +2807,34 @@ export declare enum ServiceType {
2704
2807
  * Describes the enum agentmesh.v1.ServiceType.
2705
2808
  */
2706
2809
  export declare const ServiceTypeSchema: GenEnum<ServiceType>;
2810
+ /**
2811
+ * @generated from enum agentmesh.v1.IngressAuthMode
2812
+ */
2813
+ export declare enum IngressAuthMode {
2814
+ /**
2815
+ * Defaults to INGRESS_AUTH_MODE_EDGE.
2816
+ *
2817
+ * @generated from enum value: INGRESS_AUTH_MODE_UNSPECIFIED = 0;
2818
+ */
2819
+ UNSPECIFIED = 0,
2820
+ /**
2821
+ * Router and provider node verify a delegated Mesh Biscuit + mesh policy.
2822
+ *
2823
+ * @generated from enum value: INGRESS_AUTH_MODE_EDGE = 1;
2824
+ */
2825
+ EDGE = 1,
2826
+ /**
2827
+ * Backend authenticates its own requests; requires an explicit PASSTHROUGH
2828
+ * grant on the serving node's PolicyRole.allowed_ingress.
2829
+ *
2830
+ * @generated from enum value: INGRESS_AUTH_MODE_PASSTHROUGH = 2;
2831
+ */
2832
+ PASSTHROUGH = 2
2833
+ }
2834
+ /**
2835
+ * Describes the enum agentmesh.v1.IngressAuthMode.
2836
+ */
2837
+ export declare const IngressAuthModeSchema: GenEnum<IngressAuthMode>;
2707
2838
  /**
2708
2839
  * @generated from enum agentmesh.v1.EgressMode
2709
2840
  */