@lifesi/genesis-mcp 0.1.1 → 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.
@@ -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
+ }>;
@@ -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
+ }
@@ -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
+ }
@@ -11,6 +11,8 @@ export async function closeHivesNodeTransport() {
11
11
  await current?.destroy();
12
12
  }
13
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;
14
16
  if (id === 'hives_cancel_deployment' || id === 'hives_delete_deployment')
15
17
  return 330_000;
16
18
  if (['hives_create_deployment', 'hives_upload_deployment', 'hives_invoke_deployment', 'hives_invoke_function', 'hives_invoke_function_async'].includes(id))
@@ -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,7 +5,7 @@ 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.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
  });
package/dist/tools.js CHANGED
@@ -1,4 +1,7 @@
1
+ import { callHivesResourceTool, hivesResourceInputSchema, isHivesResourceTool } from './hives-resources.js';
1
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';
2
5
  import { Genesis, GenesisError, MAX_WASM_BYTES, hivesResponseBytes, operationTable } from '@lifesi/genesis';
3
6
  const uploadSchema = { type: 'object', properties: {
4
7
  project_id: { type: 'string', description: 'Project owned by the caller’s verified org' },
@@ -6,7 +9,7 @@ const uploadSchema = { type: 'object', properties: {
6
9
  filename: { type: 'string', maxLength: 255 },
7
10
  git_sha: { type: 'string', minLength: 7, maxLength: 40 }, git_branch: { type: 'string', maxLength: 255 }, git_message: { type: 'string', maxLength: 1000 },
8
11
  }, required: ['project_id', 'wasm_base64'], additionalProperties: false };
9
- const inputSchema = (id, op) => id === 'hives_upload_deployment' ? uploadSchema : op.inputSchema;
12
+ const inputSchema = (id, op) => id === 'hives_upload_deployment' ? uploadSchema : fleetsInputSchema(id, beaconsInputSchema(id, hivesResourceInputSchema(id, op.inputSchema)));
10
13
  const all = Object.entries(operationTable);
11
14
  /** Operations callable by agents (everything except gateway-internal ones like credential verify). */
12
15
  export const callable = all.filter(([, op]) => !op.internal);
@@ -14,13 +17,13 @@ function describe(id, op) {
14
17
  const status = op.upstream.status === 'live' ? '' : ` [service status: ${op.upstream.status}]`;
15
18
  return `${op.summary} (${op.method} ${op.path})${status}`;
16
19
  }
17
- const annotate = (op) => ({
18
- readOnlyHint: op.method === 'GET', destructiveHint: op.method === 'DELETE',
19
- 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,
20
23
  });
21
24
  /** Curated tools: one per operation tagged x-mcp in the merged spec. */
22
25
  export const specTools = callable.filter(([, op]) => op.mcp).map(([id, op]) => ({
23
- name: id, title: op.summary, description: describe(id, op), inputSchema: inputSchema(id, op), annotations: annotate(op),
26
+ name: id, title: op.summary, description: describe(id, op), inputSchema: inputSchema(id, op), annotations: annotate(op, id),
24
27
  }));
25
28
  export const metaTools = [
26
29
  {
@@ -65,7 +68,16 @@ async function invoke(genesis, id, args, signal) {
65
68
  return text(`Unknown operation "${id}". Use genesis_search_tools to find one.`, true);
66
69
  try {
67
70
  let r;
68
- if (op.service === 'hives' && op.rawResponse) {
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) {
69
81
  const response = await genesis.raw(id, args, { signal });
70
82
  const bytes = await hivesResponseBytes(response);
71
83
  const payload = { status: response.status, headers: Object.fromEntries(response.headers), body_base64: Buffer.from(bytes).toString('base64') };
@@ -113,5 +125,5 @@ export async function callTool(opts, name, args = {}, signal) {
113
125
  return text(`Unknown operation "${id}". Use genesis_search_tools to find one.`, true);
114
126
  const hives = op.service === 'hives';
115
127
  const genesis = new Genesis(hives ? { ...opts, fetch: createHivesNodeTransport(opts, signal) } : opts);
116
- return invoke(genesis, id, input, hives ? signal : undefined);
128
+ return invoke(genesis, id, input, hives || isFleetsLifecycleTool(id) ? signal : undefined);
117
129
  }
package/package.json CHANGED
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "name": "@lifesi/genesis-mcp",
3
- "version": "0.1.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": {
@@ -13,7 +18,7 @@
13
18
  "dependencies": {
14
19
  "@modelcontextprotocol/sdk": "^1.32.1",
15
20
  "undici": "6.29.0",
16
- "@lifesi/genesis": "0.1.1"
21
+ "@lifesi/genesis": "0.1.2"
17
22
  },
18
23
  "scripts": {
19
24
  "build": "tsc -p tsconfig.json"