@lifesi/genesis-mcp 0.1.0 → 0.1.2
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/beacons.d.ts +14 -0
- package/dist/beacons.js +17 -0
- package/dist/fleets.d.ts +27 -0
- package/dist/fleets.js +20 -0
- package/dist/hives-node-transport.d.ts +6 -0
- package/dist/hives-node-transport.js +147 -0
- package/dist/hives-resources.d.ts +6 -0
- package/dist/hives-resources.js +72 -0
- package/dist/server.js +2 -2
- package/dist/tools.d.ts +34 -2
- package/dist/tools.js +66 -14
- package/package.json +8 -2
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Genesis } from '@lifesi/genesis';
|
|
2
|
+
export declare const BEACONS_PROVISION_OPERATION = "beacons_provisionBridgeFleet";
|
|
3
|
+
export declare function beaconsInputSchema(id: string, fallback: Record<string, unknown>): Record<string, unknown>;
|
|
4
|
+
/** The request key is a header, never authority or a JSON field sent to the producer. */
|
|
5
|
+
export declare function callBeaconsProvision(genesis: Genesis, args: Record<string, unknown>): Promise<{
|
|
6
|
+
data: {
|
|
7
|
+
fleet_id: string;
|
|
8
|
+
organization_id: string;
|
|
9
|
+
workspace_id: string;
|
|
10
|
+
project_id: string;
|
|
11
|
+
environment_id: string;
|
|
12
|
+
metadata_only: true;
|
|
13
|
+
};
|
|
14
|
+
}>;
|
package/dist/beacons.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const BEACONS_PROVISION_OPERATION = 'beacons_provisionBridgeFleet';
|
|
2
|
+
export function beaconsInputSchema(id, fallback) {
|
|
3
|
+
if (id !== BEACONS_PROVISION_OPERATION)
|
|
4
|
+
return fallback;
|
|
5
|
+
return {
|
|
6
|
+
type: 'object', properties: {
|
|
7
|
+
request_id: { type: 'string', format: 'uuid', description: 'Nonzero UUID. Reuse this ID and the identical name after an interrupted response.' },
|
|
8
|
+
body: { type: 'object', properties: { name: { type: 'string', maxLength: 256 } }, additionalProperties: false },
|
|
9
|
+
}, required: ['request_id'], additionalProperties: false,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/** The request key is a header, never authority or a JSON field sent to the producer. */
|
|
13
|
+
export function callBeaconsProvision(genesis, args) {
|
|
14
|
+
if (Object.keys(args).some(key => !['request_id', 'body'].includes(key)))
|
|
15
|
+
throw new Error('Unsupported Beacons provisioning argument.');
|
|
16
|
+
return genesis.beacons.fleets.provision(args.request_id, args.body === undefined ? {} : args.body);
|
|
17
|
+
}
|
package/dist/fleets.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Genesis } from '@lifesi/genesis';
|
|
2
|
+
export declare const isFleetsLifecycleTool: (id: string) => id is "fleets_startInstance" | "fleets_stopInstance";
|
|
3
|
+
export declare function fleetsInputSchema(id: string, schema: Record<string, unknown>): Record<string, unknown>;
|
|
4
|
+
export declare function callFleetsLifecycleTool(genesis: Genesis, id: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<{
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
organization_id: string;
|
|
8
|
+
workspace_id: string;
|
|
9
|
+
project_id: string;
|
|
10
|
+
admission_held: boolean | null;
|
|
11
|
+
physical_state: "created" | "running" | "held" | "stopped" | "recovery_required" | "released";
|
|
12
|
+
status_evidence: "live_controller" | "confirmed_release_receipt";
|
|
13
|
+
release_acknowledged_at_ms: number | null;
|
|
14
|
+
harness_ready: boolean;
|
|
15
|
+
observed_at_ms: number | null;
|
|
16
|
+
lease_expires: {
|
|
17
|
+
wall_ms: number;
|
|
18
|
+
logical: number;
|
|
19
|
+
} | null;
|
|
20
|
+
} | {
|
|
21
|
+
id: string;
|
|
22
|
+
state: "released";
|
|
23
|
+
reaped: true;
|
|
24
|
+
data_retained: true;
|
|
25
|
+
status_evidence: "confirmed_release_receipt";
|
|
26
|
+
release_acknowledged_at_ms: number;
|
|
27
|
+
}>;
|
package/dist/fleets.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { GenesisError } from '@lifesi/genesis';
|
|
2
|
+
export const isFleetsLifecycleTool = (id) => id === 'fleets_startInstance' || id === 'fleets_stopInstance';
|
|
3
|
+
export function fleetsInputSchema(id, schema) {
|
|
4
|
+
if (!isFleetsLifecycleTool(id))
|
|
5
|
+
return schema;
|
|
6
|
+
return { ...schema, properties: { ...schema.properties, idempotency_key: {
|
|
7
|
+
type: 'string', pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
|
|
8
|
+
description: 'Optional canonical UUID4 for this explicit operation; retain it after an uncertain reply. Carries no authority.',
|
|
9
|
+
} }, additionalProperties: false };
|
|
10
|
+
}
|
|
11
|
+
export async function callFleetsLifecycleTool(genesis, id, args, signal) {
|
|
12
|
+
if (!args || typeof args !== 'object' || Array.isArray(args)
|
|
13
|
+
|| Object.keys(args).some(key => !['instance_id', 'idempotency_key'].includes(key))
|
|
14
|
+
|| typeof args.instance_id !== 'string'
|
|
15
|
+
|| (args.idempotency_key !== undefined && typeof args.idempotency_key !== 'string')) {
|
|
16
|
+
throw new GenesisError({ status: 0, type: 'invalid_request', operationId: id, message: 'Provide an instance ID and optional canonical UUID4 idempotency_key only.' });
|
|
17
|
+
}
|
|
18
|
+
const options = { idempotencyKey: args.idempotency_key, signal };
|
|
19
|
+
return id === 'fleets_startInstance' ? genesis.fleets.instances.start(args.instance_id, options) : genesis.fleets.instances.stop(args.instance_id, options);
|
|
20
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type GenesisOptions } from '@lifesi/genesis';
|
|
2
|
+
export declare function closeHivesNodeTransport(): Promise<void>;
|
|
3
|
+
export declare function hivesOperationBudget(id: string): number;
|
|
4
|
+
/** Hives leaf transport. Caller fetch overrides stay authoritative; only native
|
|
5
|
+
* global fetch gets our Agent. Every operation retains an overall body deadline. */
|
|
6
|
+
export declare function createHivesNodeTransport(opts: GenesisOptions, callerSignal?: AbortSignal): typeof fetch;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Agent } from 'undici';
|
|
2
|
+
import { Genesis, operationTable } from '@lifesi/genesis';
|
|
3
|
+
// Node's native fetch accepts a compatible per-request dispatcher. Never change
|
|
4
|
+
// the global dispatcher or the portable SDK: other products retain their transport.
|
|
5
|
+
const agentOptions = { headersTimeout: 340_000, bodyTimeout: 340_000, connect: { timeout: 5_000 }, connections: 4, pipelining: 1 };
|
|
6
|
+
let agent;
|
|
7
|
+
const nativeAgent = () => agent ??= new Agent(agentOptions);
|
|
8
|
+
export async function closeHivesNodeTransport() {
|
|
9
|
+
const current = agent;
|
|
10
|
+
agent = undefined;
|
|
11
|
+
await current?.destroy();
|
|
12
|
+
}
|
|
13
|
+
export function hivesOperationBudget(id) {
|
|
14
|
+
if (['hives_list_buckets', 'hives_create_bucket', 'hives_get_bucket', 'hives_delete_bucket', 'hives_list_objects', 'hives_upload_object', 'hives_upload_object_by_key', 'hives_delete_object', 'hives_delete_object_by_key', 'hives_download_object_by_key', 'hives_get_cors_config', 'hives_set_cors_config', 'hives_generate_presigned_url', 'hives_reconcile'].includes(id))
|
|
15
|
+
return 100_000;
|
|
16
|
+
if (id === 'hives_cancel_deployment' || id === 'hives_delete_deployment')
|
|
17
|
+
return 330_000;
|
|
18
|
+
if (['hives_create_deployment', 'hives_upload_deployment', 'hives_invoke_deployment', 'hives_invoke_function', 'hives_invoke_function_async'].includes(id))
|
|
19
|
+
return 40_000;
|
|
20
|
+
if (['hives_create_function', 'hives_update_function', 'hives_delete_function'].includes(id))
|
|
21
|
+
return 100_000;
|
|
22
|
+
return 20_000;
|
|
23
|
+
}
|
|
24
|
+
const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
25
|
+
const templatePattern = (value) => new RegExp('^' + value.split(/(\{[^}]+\})/).map((part) => part.startsWith('{') ? '[^/]+' : escape(part)).join('') + '$');
|
|
26
|
+
/** Hives leaf transport. Caller fetch overrides stay authoritative; only native
|
|
27
|
+
* global fetch gets our Agent. Every operation retains an overall body deadline. */
|
|
28
|
+
export function createHivesNodeTransport(opts, callerSignal) {
|
|
29
|
+
const supplied = opts.fetch ?? globalThis.fetch;
|
|
30
|
+
const underlying = supplied.bind(globalThis);
|
|
31
|
+
const useNative = supplied === globalThis.fetch;
|
|
32
|
+
const resolver = new Genesis(opts);
|
|
33
|
+
const routes = Object.entries(operationTable)
|
|
34
|
+
.filter(([, op]) => op.service === 'hives' && !op.internal)
|
|
35
|
+
.flatMap(([id, op]) => {
|
|
36
|
+
const direct = resolver.routing === 'direct';
|
|
37
|
+
let base;
|
|
38
|
+
try {
|
|
39
|
+
base = new URL(direct ? (opts.upstreams?.hives ?? op.upstream.base) : resolver.baseUrl);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
throw new Error('Invalid Hives service address.');
|
|
43
|
+
}
|
|
44
|
+
const path = direct ? op.upstream.path : op.path;
|
|
45
|
+
const prefix = base.pathname.replace(/\/+$/, '');
|
|
46
|
+
const paths = [path];
|
|
47
|
+
if (!direct && path.startsWith('/v1/hives/'))
|
|
48
|
+
paths.push(path.replace('/v1/hives/', '/hives/v1/'));
|
|
49
|
+
return paths.map((p) => ({ id, origin: base.origin, method: direct ? op.upstream.method : op.method, pattern: templatePattern(prefix + p) }));
|
|
50
|
+
});
|
|
51
|
+
return (async (input, init = {}) => {
|
|
52
|
+
let url;
|
|
53
|
+
try {
|
|
54
|
+
url = new URL(input instanceof Request ? input.url : String(input));
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
throw new Error('Invalid Hives request address.');
|
|
58
|
+
}
|
|
59
|
+
const method = (init.method ?? (input instanceof Request ? input.method : 'GET')).toUpperCase();
|
|
60
|
+
const route = routes.find((entry) => entry.origin === url.origin && entry.method === method && entry.pattern.test(url.pathname));
|
|
61
|
+
if (!route)
|
|
62
|
+
return underlying(input, init);
|
|
63
|
+
const controller = new AbortController();
|
|
64
|
+
let message = 'Hives transport failed. Refresh the resource and retry.';
|
|
65
|
+
const safeError = () => new Error(message);
|
|
66
|
+
const signals = [...new Set([callerSignal, init.signal, input instanceof Request ? input.signal : undefined].filter((signal) => !!signal))];
|
|
67
|
+
const canceled = () => {
|
|
68
|
+
message = 'Hives request canceled before completion. Refresh the resource before retrying.';
|
|
69
|
+
controller.abort();
|
|
70
|
+
};
|
|
71
|
+
for (const signal of signals)
|
|
72
|
+
signal.addEventListener('abort', canceled, { once: true });
|
|
73
|
+
const timer = setTimeout(() => {
|
|
74
|
+
message = 'Hives request deadline exceeded. Refresh the resource before retrying.';
|
|
75
|
+
controller.abort();
|
|
76
|
+
}, hivesOperationBudget(route.id));
|
|
77
|
+
timer.unref();
|
|
78
|
+
let abortReject = () => { };
|
|
79
|
+
let reader;
|
|
80
|
+
let bodyController;
|
|
81
|
+
const aborted = () => {
|
|
82
|
+
abortReject(safeError());
|
|
83
|
+
bodyController?.error(safeError());
|
|
84
|
+
void reader?.cancel().catch(() => { });
|
|
85
|
+
cleanup();
|
|
86
|
+
};
|
|
87
|
+
controller.signal.addEventListener('abort', aborted, { once: true });
|
|
88
|
+
const abortPromise = new Promise((_, reject) => { abortReject = reject; });
|
|
89
|
+
void abortPromise.catch(() => { });
|
|
90
|
+
const cleanup = () => {
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
for (const signal of signals)
|
|
93
|
+
signal.removeEventListener('abort', canceled);
|
|
94
|
+
controller.signal.removeEventListener('abort', aborted);
|
|
95
|
+
};
|
|
96
|
+
if (signals.some((signal) => signal.aborted))
|
|
97
|
+
canceled();
|
|
98
|
+
try {
|
|
99
|
+
if (controller.signal.aborted)
|
|
100
|
+
throw safeError();
|
|
101
|
+
const requestInit = {
|
|
102
|
+
...init, signal: controller.signal, cache: 'no-store',
|
|
103
|
+
redirect: init.redirect === 'manual' ? 'manual' : 'error',
|
|
104
|
+
...(useNative ? { dispatcher: nativeAgent() } : {}),
|
|
105
|
+
};
|
|
106
|
+
const pending = underlying(input, requestInit);
|
|
107
|
+
// A caller transport may ignore AbortSignal. Reject on our budget anyway,
|
|
108
|
+
// and discard any late response rather than presenting an acknowledgement.
|
|
109
|
+
void pending.then((response) => { if (controller.signal.aborted)
|
|
110
|
+
void response.body?.cancel().catch(() => { }); }, () => { });
|
|
111
|
+
const response = await Promise.race([pending, abortPromise]);
|
|
112
|
+
if (controller.signal.aborted)
|
|
113
|
+
throw safeError();
|
|
114
|
+
if (!response.body) {
|
|
115
|
+
cleanup();
|
|
116
|
+
return response;
|
|
117
|
+
}
|
|
118
|
+
reader = response.body.getReader();
|
|
119
|
+
const body = new ReadableStream({
|
|
120
|
+
start(value) { bodyController = value; },
|
|
121
|
+
async pull(value) {
|
|
122
|
+
try {
|
|
123
|
+
const next = await reader.read();
|
|
124
|
+
if (controller.signal.aborted)
|
|
125
|
+
return;
|
|
126
|
+
if (next.done) {
|
|
127
|
+
cleanup();
|
|
128
|
+
value.close();
|
|
129
|
+
}
|
|
130
|
+
else
|
|
131
|
+
value.enqueue(next.value);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
cleanup();
|
|
135
|
+
value.error(safeError());
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
async cancel() { cleanup(); await reader.cancel().catch(() => { }); },
|
|
139
|
+
});
|
|
140
|
+
return new Response(body, { status: response.status, statusText: response.statusText, headers: response.headers });
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
cleanup();
|
|
144
|
+
throw safeError();
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Genesis } from '@lifesi/genesis';
|
|
2
|
+
/** Binary data and plain keys are explicitly encoded for the actual native object API. */
|
|
3
|
+
export declare function hivesResourceInputSchema(id: string, fallback: Record<string, unknown>): Record<string, unknown>;
|
|
4
|
+
export declare const isHivesResourceTool: (id: string) => boolean;
|
|
5
|
+
/** Called only by the reviewed Hives-only shared-dispatch proposal. No replay or external capability fetch. */
|
|
6
|
+
export declare function callHivesResourceTool(g: Genesis, id: string, a: Record<string, unknown>, signal?: AbortSignal): Promise<unknown>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const OBJECT_BYTES = 10 * 1024 * 1024;
|
|
2
|
+
const keyProperties = { project_id: { type: 'string' }, bucket_id: { type: 'string' }, key: { type: 'string', minLength: 1, maxLength: 1024 } };
|
|
3
|
+
/** Binary data and plain keys are explicitly encoded for the actual native object API. */
|
|
4
|
+
export function hivesResourceInputSchema(id, fallback) {
|
|
5
|
+
if (!['hives_upload_object', 'hives_upload_object_by_key', 'hives_delete_object_by_key', 'hives_download_object_by_key'].includes(id))
|
|
6
|
+
return fallback;
|
|
7
|
+
const upload = id.startsWith('hives_upload_object');
|
|
8
|
+
return { type: 'object', properties: { ...keyProperties, ...(upload ? { body_base64: { type: 'string', maxLength: Math.ceil(OBJECT_BYTES / 3) * 4 }, content_type: { type: 'string', maxLength: 512 } } : {}) }, required: ['project_id', 'bucket_id', 'key', ...(upload ? ['body_base64'] : [])], additionalProperties: false };
|
|
9
|
+
}
|
|
10
|
+
export const isHivesResourceTool = (id) => /^(?:hives_.*_native_queue|hives_(?:list_buckets|create_bucket|get_bucket|delete_bucket|list_objects|upload_object(?:_by_key)?|delete_object(?:_by_key)?|download_object_by_key|get_cors_config|set_cors_config|generate_presigned_url|reconcile|list_runtimes|get_runtime|select_runtime|list_project_runtimes))$/.test(id);
|
|
11
|
+
function text(value) { if (typeof value !== 'string')
|
|
12
|
+
throw new Error('Invalid Hives resource input.'); return value; }
|
|
13
|
+
function decode(value) { const s = text(value); if (s.length > Math.ceil(OBJECT_BYTES / 3) * 4 || s.length % 4 || !/^[A-Za-z0-9+/]*={0,2}$/.test(s))
|
|
14
|
+
throw new Error('Invalid bounded object body.'); const bytes = Buffer.from(s, 'base64'); if (bytes.length > OBJECT_BYTES || bytes.toString('base64') !== s)
|
|
15
|
+
throw new Error('Invalid bounded object body.'); return Uint8Array.from(bytes); }
|
|
16
|
+
/** Called only by the reviewed Hives-only shared-dispatch proposal. No replay or external capability fetch. */
|
|
17
|
+
export async function callHivesResourceTool(g, id, a, signal) {
|
|
18
|
+
const options = { signal };
|
|
19
|
+
const project = () => text(a.project_id);
|
|
20
|
+
const bucket = () => text(a.bucket_id);
|
|
21
|
+
const queue = () => text(a.queue_id);
|
|
22
|
+
const body = () => a.body;
|
|
23
|
+
const cells = g.hives.cells;
|
|
24
|
+
const buzz = g.hives.queues;
|
|
25
|
+
switch (id) {
|
|
26
|
+
case 'hives_list_buckets': return cells.list(project(), { page: a.page, per_page: a.per_page }, options);
|
|
27
|
+
case 'hives_get_bucket': return cells.get(project(), bucket(), options);
|
|
28
|
+
case 'hives_create_bucket': return cells.create(project(), body(), options);
|
|
29
|
+
case 'hives_delete_bucket': return cells.remove(project(), bucket(), options);
|
|
30
|
+
case 'hives_reconcile': return cells.reconcile(project(), bucket(), options);
|
|
31
|
+
case 'hives_list_objects': return cells.objects.list(project(), bucket(), { prefix: a.prefix, delimiter: a.delimiter, continuation_token: a.continuation_token, max_keys: a.max_keys }, options);
|
|
32
|
+
case 'hives_get_cors_config': return cells.cors.get(project(), bucket(), options);
|
|
33
|
+
case 'hives_set_cors_config': return cells.cors.set(project(), bucket(), body()?.rules, options);
|
|
34
|
+
case 'hives_generate_presigned_url': return cells.presign(project(), bucket(), body(), options);
|
|
35
|
+
case 'hives_upload_object_by_key':
|
|
36
|
+
case 'hives_upload_object': return cells.objects[id === 'hives_upload_object' ? 'putFlat' : 'put'](project(), bucket(), text(a.key), decode(a.body_base64), { ...options, content_type: a.content_type });
|
|
37
|
+
case 'hives_delete_object_by_key':
|
|
38
|
+
await cells.objects.remove(project(), bucket(), text(a.key), options);
|
|
39
|
+
return { acknowledged: true };
|
|
40
|
+
case 'hives_delete_object':
|
|
41
|
+
await cells.objects.removeFlat(project(), bucket(), text(a.key), options);
|
|
42
|
+
return { acknowledged: true };
|
|
43
|
+
case 'hives_download_object_by_key': {
|
|
44
|
+
const response = await cells.objects.download(project(), bucket(), text(a.key), options);
|
|
45
|
+
return { status: response.status, content_type: response.headers.get('content-type'), body_base64: Buffer.from(await response.arrayBuffer()).toString('base64') };
|
|
46
|
+
}
|
|
47
|
+
case 'hives_list_runtimes': return g.hives.runtimes.list(options);
|
|
48
|
+
case 'hives_list_project_runtimes': return g.hives.runtimes.project(project(), options);
|
|
49
|
+
case 'hives_get_runtime': return g.hives.runtimes.get(text(a.name), options);
|
|
50
|
+
case 'hives_select_runtime': return g.hives.runtimes.select({ wasi: text(a.wasi), runner: text(a.runner), artifact: text(a.artifact) }, options);
|
|
51
|
+
case 'hives_list_native_queue': return buzz.list(project(), options);
|
|
52
|
+
case 'hives_inspect_native_queue': return buzz.get(project(), queue(), options);
|
|
53
|
+
case 'hives_create_native_queue': return buzz.create(project(), body(), options);
|
|
54
|
+
case 'hives_configure_native_queue': return buzz.configure(project(), queue(), body(), options);
|
|
55
|
+
case 'hives_delete_native_queue': return buzz.remove(project(), queue(), options);
|
|
56
|
+
case 'hives_stats_native_queue': return buzz.stats(project(), queue(), options);
|
|
57
|
+
case 'hives_consumers_native_queue': return buzz.consumers(project(), queue(), options);
|
|
58
|
+
case 'hives_pause_native_queue': return buzz.pause(project(), queue(), options);
|
|
59
|
+
case 'hives_resume_native_queue': return buzz.resume(project(), queue(), options);
|
|
60
|
+
case 'hives_purge_native_queue': return buzz.purge(project(), queue(), options);
|
|
61
|
+
case 'hives_send_native_queue': return buzz.send(project(), queue(), body(), options);
|
|
62
|
+
case 'hives_receive_native_queue': return buzz.receive(project(), queue(), body(), options);
|
|
63
|
+
case 'hives_ack_native_queue': return buzz.ack(project(), queue(), text(a.message_id), text(body()?.receiptHandle), options);
|
|
64
|
+
case 'hives_nack_native_queue': return buzz.nack(project(), queue(), text(a.message_id), text(body()?.receiptHandle), options);
|
|
65
|
+
case 'hives_dead_letters_native_queue': return buzz.deadLetters(project(), queue(), { limit: a.limit, offset: a.offset }, options);
|
|
66
|
+
case 'hives_dlq_stats_native_queue': return buzz.deadLetterStats(project(), queue(), options);
|
|
67
|
+
case 'hives_replay_native_queue': return buzz.replay(project(), queue(), text(body()?.message_id), options);
|
|
68
|
+
case 'hives_delete_dlq_native_queue': return buzz.deleteDeadLetter(project(), queue(), text(a.message_id), options);
|
|
69
|
+
case 'hives_purge_dlq_native_queue': return buzz.purgeDeadLetters(project(), queue(), options);
|
|
70
|
+
default: throw new Error('Unknown Hives resource operation.');
|
|
71
|
+
}
|
|
72
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -5,12 +5,12 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
|
|
|
5
5
|
import http from 'node:http';
|
|
6
6
|
import { callTool, tools } from './tools.js';
|
|
7
7
|
export function createServer(opts = {}) {
|
|
8
|
-
const server = new Server({ name: 'genesis', version: '0.1.
|
|
8
|
+
const server = new Server({ name: 'genesis', version: '0.1.2' }, {
|
|
9
9
|
capabilities: { tools: {} },
|
|
10
10
|
instructions: 'Genesis: one API for Foundry (inference), Hives (compute), Relays (DNS) and LifeID keys. Use the named tools for common tasks; use genesis_search_tools + genesis_call for anything else.',
|
|
11
11
|
});
|
|
12
12
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: tools() }));
|
|
13
|
-
server.setRequestHandler(CallToolRequestSchema, async (req) => callTool(opts, req.params.name, (req.params.arguments ?? {})));
|
|
13
|
+
server.setRequestHandler(CallToolRequestSchema, async (req, extra) => callTool(opts, req.params.name, (req.params.arguments ?? {}), extra.signal));
|
|
14
14
|
return server;
|
|
15
15
|
}
|
|
16
16
|
/** stdio: credential from GENESIS_API_KEY / GENESIS_TOKEN (or `credentials` provider). */
|
package/dist/tools.d.ts
CHANGED
|
@@ -24,7 +24,39 @@ export declare function searchTools(query: string, service?: string, limit?: num
|
|
|
24
24
|
path: string;
|
|
25
25
|
summary: string;
|
|
26
26
|
status: string;
|
|
27
|
-
inputSchema: Record<string, unknown
|
|
27
|
+
inputSchema: Record<string, unknown> | {
|
|
28
|
+
type: string;
|
|
29
|
+
properties: {
|
|
30
|
+
project_id: {
|
|
31
|
+
type: string;
|
|
32
|
+
description: string;
|
|
33
|
+
};
|
|
34
|
+
wasm_base64: {
|
|
35
|
+
type: string;
|
|
36
|
+
maxLength: number;
|
|
37
|
+
description: string;
|
|
38
|
+
};
|
|
39
|
+
filename: {
|
|
40
|
+
type: string;
|
|
41
|
+
maxLength: number;
|
|
42
|
+
};
|
|
43
|
+
git_sha: {
|
|
44
|
+
type: string;
|
|
45
|
+
minLength: number;
|
|
46
|
+
maxLength: number;
|
|
47
|
+
};
|
|
48
|
+
git_branch: {
|
|
49
|
+
type: string;
|
|
50
|
+
maxLength: number;
|
|
51
|
+
};
|
|
52
|
+
git_message: {
|
|
53
|
+
type: string;
|
|
54
|
+
maxLength: number;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
required: string[];
|
|
58
|
+
additionalProperties: boolean;
|
|
59
|
+
};
|
|
28
60
|
}[];
|
|
29
61
|
type Result = {
|
|
30
62
|
content: {
|
|
@@ -34,5 +66,5 @@ type Result = {
|
|
|
34
66
|
isError?: boolean;
|
|
35
67
|
structuredContent?: Record<string, unknown>;
|
|
36
68
|
};
|
|
37
|
-
export declare function callTool(opts: GenesisOptions, name: string, args?: Record<string, unknown
|
|
69
|
+
export declare function callTool(opts: GenesisOptions, name: string, args?: Record<string, unknown>, signal?: AbortSignal): Promise<Result>;
|
|
38
70
|
export {};
|
package/dist/tools.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { callHivesResourceTool, hivesResourceInputSchema, isHivesResourceTool } from './hives-resources.js';
|
|
2
|
+
import { createHivesNodeTransport } from './hives-node-transport.js';
|
|
3
|
+
import { BEACONS_PROVISION_OPERATION, beaconsInputSchema, callBeaconsProvision } from './beacons.js';
|
|
4
|
+
import { fleetsInputSchema, isFleetsLifecycleTool, callFleetsLifecycleTool } from './fleets.js';
|
|
5
|
+
import { Genesis, GenesisError, MAX_WASM_BYTES, hivesResponseBytes, operationTable } from '@lifesi/genesis';
|
|
6
|
+
const uploadSchema = { type: 'object', properties: {
|
|
7
|
+
project_id: { type: 'string', description: 'Project owned by the caller’s verified org' },
|
|
8
|
+
wasm_base64: { type: 'string', maxLength: Math.ceil(MAX_WASM_BYTES / 3) * 4, description: 'Explicit standard-base64 content of a real WASM binary (decoded maximum 10 MiB)' },
|
|
9
|
+
filename: { type: 'string', maxLength: 255 },
|
|
10
|
+
git_sha: { type: 'string', minLength: 7, maxLength: 40 }, git_branch: { type: 'string', maxLength: 255 }, git_message: { type: 'string', maxLength: 1000 },
|
|
11
|
+
}, required: ['project_id', 'wasm_base64'], additionalProperties: false };
|
|
12
|
+
const inputSchema = (id, op) => id === 'hives_upload_deployment' ? uploadSchema : fleetsInputSchema(id, beaconsInputSchema(id, hivesResourceInputSchema(id, op.inputSchema)));
|
|
2
13
|
const all = Object.entries(operationTable);
|
|
3
14
|
/** Operations callable by agents (everything except gateway-internal ones like credential verify). */
|
|
4
15
|
export const callable = all.filter(([, op]) => !op.internal);
|
|
@@ -6,13 +17,13 @@ function describe(id, op) {
|
|
|
6
17
|
const status = op.upstream.status === 'live' ? '' : ` [service status: ${op.upstream.status}]`;
|
|
7
18
|
return `${op.summary} (${op.method} ${op.path})${status}`;
|
|
8
19
|
}
|
|
9
|
-
const annotate = (op) => ({
|
|
10
|
-
readOnlyHint: op.method === 'GET', destructiveHint: op.method === 'DELETE',
|
|
11
|
-
idempotentHint: ['GET', 'PUT', 'DELETE'].includes(op.method), openWorldHint: true,
|
|
20
|
+
const annotate = (op, id) => ({
|
|
21
|
+
readOnlyHint: op.method === 'GET', destructiveHint: op.method === 'DELETE' || isHivesResourceTool(id) && /^hives_purge/.test(id),
|
|
22
|
+
idempotentHint: isHivesResourceTool(id) && op.method !== 'GET' ? false : ['GET', 'PUT', 'DELETE'].includes(op.method) || id === BEACONS_PROVISION_OPERATION || op.path === '/v1/iam/runner/self-execution-grant', openWorldHint: true,
|
|
12
23
|
});
|
|
13
24
|
/** Curated tools: one per operation tagged x-mcp in the merged spec. */
|
|
14
25
|
export const specTools = callable.filter(([, op]) => op.mcp).map(([id, op]) => ({
|
|
15
|
-
name: id, title: op.summary, description: describe(id, op), inputSchema:
|
|
26
|
+
name: id, title: op.summary, description: describe(id, op), inputSchema: inputSchema(id, op), annotations: annotate(op, id),
|
|
16
27
|
}));
|
|
17
28
|
export const metaTools = [
|
|
18
29
|
{
|
|
@@ -48,15 +59,52 @@ export function searchTools(query, service, limit = 10) {
|
|
|
48
59
|
.filter((x) => x.score > 0)
|
|
49
60
|
.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id))
|
|
50
61
|
.slice(0, limit)
|
|
51
|
-
.map(({ id, op }) => ({ operation: id, service: op.service, method: op.method, path: op.path, summary: op.summary, status: op.upstream.status, inputSchema:
|
|
62
|
+
.map(({ id, op }) => ({ operation: id, service: op.service, method: op.method, path: op.path, summary: op.summary, status: op.upstream.status, inputSchema: inputSchema(id, op) }));
|
|
52
63
|
}
|
|
53
64
|
const text = (v, isError = false) => ({ content: [{ type: 'text', text: typeof v === 'string' ? v : JSON.stringify(v, null, 2) }], ...(isError ? { isError } : {}) });
|
|
54
|
-
async function invoke(genesis, id, args) {
|
|
65
|
+
async function invoke(genesis, id, args, signal) {
|
|
55
66
|
const op = operationTable[id];
|
|
56
67
|
if (!op || op.internal)
|
|
57
68
|
return text(`Unknown operation "${id}". Use genesis_search_tools to find one.`, true);
|
|
58
69
|
try {
|
|
59
|
-
|
|
70
|
+
let r;
|
|
71
|
+
if (isFleetsLifecycleTool(id)) {
|
|
72
|
+
r = await callFleetsLifecycleTool(genesis, id, args, signal);
|
|
73
|
+
}
|
|
74
|
+
else if (id === BEACONS_PROVISION_OPERATION) {
|
|
75
|
+
r = await callBeaconsProvision(genesis, args);
|
|
76
|
+
}
|
|
77
|
+
else if (op.service === 'hives' && isHivesResourceTool(id)) {
|
|
78
|
+
r = await callHivesResourceTool(genesis, id, args, signal);
|
|
79
|
+
}
|
|
80
|
+
else if (op.service === 'hives' && op.rawResponse) {
|
|
81
|
+
const response = await genesis.raw(id, args, { signal });
|
|
82
|
+
const bytes = await hivesResponseBytes(response);
|
|
83
|
+
const payload = { status: response.status, headers: Object.fromEntries(response.headers), body_base64: Buffer.from(bytes).toString('base64') };
|
|
84
|
+
return { ...text(payload), structuredContent: payload };
|
|
85
|
+
}
|
|
86
|
+
else if (id === 'hives_upload_deployment') {
|
|
87
|
+
const encoded = args.wasm_base64;
|
|
88
|
+
if (typeof args.project_id !== 'string' || !args.project_id || typeof encoded !== 'string'
|
|
89
|
+
|| encoded.length > Math.ceil(MAX_WASM_BYTES / 3) * 4 || encoded.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(encoded)) {
|
|
90
|
+
return text({ error: { type: 'invalid_request', message: 'Provide a project ID and standard base64 of a real WASM file, up to 10 MiB decoded.' } }, true);
|
|
91
|
+
}
|
|
92
|
+
const bytes = Buffer.from(encoded, 'base64');
|
|
93
|
+
if (bytes.toString('base64') !== encoded || bytes.length > MAX_WASM_BYTES)
|
|
94
|
+
return text({ error: { type: 'invalid_request', message: 'WASM base64 is invalid or exceeds 10 MiB.' } }, true);
|
|
95
|
+
const optional = (key) => {
|
|
96
|
+
if (args[key] !== undefined && typeof args[key] !== 'string')
|
|
97
|
+
throw new Error('WASM upload metadata must contain strings.');
|
|
98
|
+
return args[key];
|
|
99
|
+
};
|
|
100
|
+
r = await genesis.hives.deployments.upload(args.project_id, new Blob([Uint8Array.from(bytes)], { type: 'application/wasm' }), {
|
|
101
|
+
filename: optional('filename'), git_sha: optional('git_sha'), git_branch: optional('git_branch'), git_message: optional('git_message'),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
else
|
|
105
|
+
r = op.service === 'hives'
|
|
106
|
+
? await genesis.request(id, args, { signal })
|
|
107
|
+
: await genesis.request(id, args);
|
|
60
108
|
return text(r === undefined ? { ok: true } : r);
|
|
61
109
|
}
|
|
62
110
|
catch (e) {
|
|
@@ -65,13 +113,17 @@ async function invoke(genesis, id, args) {
|
|
|
65
113
|
return text({ error: { type: 'network', message: e.message, hint: `The ${op.service} service may be unreachable (status: ${op.upstream.status}).` } }, true);
|
|
66
114
|
}
|
|
67
115
|
}
|
|
68
|
-
export async function callTool(opts, name, args = {}) {
|
|
116
|
+
export async function callTool(opts, name, args = {}, signal) {
|
|
69
117
|
if (name === 'genesis_search_tools')
|
|
70
118
|
return text(searchTools(String(args.query ?? ''), args.service, Number(args.limit ?? 10)));
|
|
71
|
-
|
|
72
|
-
if (name === 'genesis_call')
|
|
73
|
-
return invoke(genesis, String(args.operation ?? ''), args.arguments ?? {});
|
|
74
|
-
if (!specTools.some((t) => t.name === name))
|
|
119
|
+
if (name !== 'genesis_call' && !specTools.some((t) => t.name === name))
|
|
75
120
|
return text(`Unknown tool "${name}".`, true);
|
|
76
|
-
|
|
121
|
+
const id = name === 'genesis_call' ? String(args.operation ?? '') : name;
|
|
122
|
+
const input = name === 'genesis_call' ? args.arguments ?? {} : args;
|
|
123
|
+
const op = operationTable[id];
|
|
124
|
+
if (!op || op.internal)
|
|
125
|
+
return text(`Unknown operation "${id}". Use genesis_search_tools to find one.`, true);
|
|
126
|
+
const hives = op.service === 'hives';
|
|
127
|
+
const genesis = new Genesis(hives ? { ...opts, fetch: createHivesNodeTransport(opts, signal) } : opts);
|
|
128
|
+
return invoke(genesis, id, input, hives || isFleetsLifecycleTool(id) ? signal : undefined);
|
|
77
129
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lifesi/genesis-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/l1felabs/genesis-api.git",
|
|
7
|
+
"directory": "packages/mcp"
|
|
8
|
+
},
|
|
4
9
|
"description": "Genesis MCP server (stdio + streamable HTTP): tools generated from the Genesis OpenAPI",
|
|
5
10
|
"type": "module",
|
|
6
11
|
"bin": {
|
|
@@ -12,7 +17,8 @@
|
|
|
12
17
|
],
|
|
13
18
|
"dependencies": {
|
|
14
19
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
15
|
-
"
|
|
20
|
+
"undici": "6.29.0",
|
|
21
|
+
"@lifesi/genesis": "0.1.2"
|
|
16
22
|
},
|
|
17
23
|
"scripts": {
|
|
18
24
|
"build": "tsc -p tsconfig.json"
|