@gripforgeai/mcp 0.1.2 → 0.1.9

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,48 @@
1
+ /** Same authoring tools in the hosted MCP and the npm stdio client. */
2
+ // mcp-handler requires v4 field schemas for raw shapes. zod 3.25 ships v4
3
+ // alongside v3, so existing stdio tools can retain their current v3 schemas.
4
+ import { z } from 'zod/v4';
5
+ // The hosted SDK uses a newer Zod v4 serializer than the npm SDK. Inject its
6
+ // matching factory so schemas remain compatible with each host's serializer.
7
+ export function registerVfxProjectTools(register, options, schema = z) {
8
+ const workspace = { workspace_id: schema.string().max(100).optional().describe('Explicit workspace id. The key must have access to this workspace; never infer another user’s workspace.') };
9
+ const id = schema.string().regex(/^lib_[A-Za-z0-9_-]+$/);
10
+ const project = schema.record(schema.string(), schema.unknown()).describe('Complete editable project JSON. Read gripforge_vfx_project_schema for the format and a working example.');
11
+ const times = schema.array(schema.number().min(0).max(12)).min(1).max(4).optional().describe('Render timestamps in seconds. Defaults to anticipation, middle and decay samples.');
12
+ async function api(path, args, method, signal) {
13
+ const key = options.getApiKey();
14
+ if (!key)
15
+ return { isError: true, content: [{ type: 'text', text: 'GripForge API key required.' }] };
16
+ const { workspace_id, ...body } = args;
17
+ try {
18
+ const res = await fetch(`${options.apiUrl}/api/v1/vfx/projects${path}`, {
19
+ method, headers: { 'x-api-key': key, 'x-gripforge-client': 'mcp', 'content-type': 'application/json', ...(typeof workspace_id === 'string' ? { 'x-workspace-id': workspace_id } : {}) },
20
+ ...(method === 'GET' ? {} : { body: JSON.stringify(body) }), signal: AbortSignal.any([...(signal ? [signal] : []), AbortSignal.timeout(285_000)]),
21
+ });
22
+ const raw = await res.json();
23
+ if (!res.ok)
24
+ return { isError: true, content: [{ type: 'text', text: JSON.stringify({ status: res.status, ...raw }) }] };
25
+ const { preview_image, ...data } = raw;
26
+ if (typeof data.studio_url === 'string')
27
+ data.studio_url = new URL(data.studio_url, options.apiUrl).href;
28
+ const content = [{ type: 'text', text: JSON.stringify(data) }];
29
+ const image = preview_image;
30
+ if (image?.mimeType === 'image/png' && typeof image.data === 'string')
31
+ content.push({ type: 'image', mimeType: 'image/png', data: image.data });
32
+ return { content, structuredContent: data };
33
+ }
34
+ catch (error) {
35
+ return { isError: true, content: [{ type: 'text', text: error instanceof Error ? error.message : 'VFX API failed' }] };
36
+ }
37
+ }
38
+ function tool(name, title, description, inputSchema, readOnly, callback, external = false) {
39
+ register(name, { title, description: `ADMIN ONLY. ${description}`, inputSchema: { ...inputSchema, ...workspace }, annotations: { readOnlyHint: readOnly, destructiveHint: false, idempotentHint: readOnly && !external, openWorldHint: external } }, callback);
40
+ }
41
+ tool('gripforge_vfx_project_schema', 'VFX authoring language and example', 'Start here to create original VFX beyond presets. Returns the editable source format, shader helpers, geometry budgets and a working example. Workflow: write custom source -> render -> inspect the returned image -> revise -> save. Blender or another modeling tool may supply custom mesh vertices/UVs/indices; no Blender instance is required for procedural effects.', {}, true, (args, extra) => api('', args, 'GET', extra?.signal));
42
+ tool('gripforge_vfx_project_read', 'Read VFX project files and revisions', 'Read a project in the authenticated workspace. Returns project.json, per-layer .vert/.frag source, current revision, recent revision history and a Studio link. Read an older revision to restore it through write with the CURRENT expected_revision. Does not grant access to the server repository or arbitrary user files.', { id, revision: schema.number().int().positive().optional() }, true, (args, extra) => api(`/${encodeURIComponent(String(args.id))}${args.revision ? `?revision=${args.revision}` : ''}`, args, 'GET', extra?.signal));
43
+ tool('gripforge_vfx_project_write', 'Create or edit a VFX project', 'Create with project, or update id with project OR edits:[{path,content}]. Updates require expected_revision. Returns HTTP 202 job_id and studio_url immediately. Poll gripforge_generation_read; the worker compiles, renders and saves a work revision, then reviews it visually. Completed result includes source_url, preview_url and review. Use generation_read with include_preview to see the actual image. A source conflict fails the job and preserves current work; reread before writing. Validated versions remain unchanged. Editable files are project.json and layers/{id}.vert/.frag.', { idempotency_key: schema.string().min(8).max(160).optional(), id: id.optional(), expected_revision: schema.number().int().positive().optional(), project: project.optional(), edits: schema.array(schema.object({ path: schema.string().max(100), content: schema.string().max(400_000) })).min(1).max(24).optional(), times, note: schema.string().max(1000).optional() }, false, (args, extra) => api(args.id ? `/${encodeURIComponent(String(args.id))}` : '', args, args.id ? 'PATCH' : 'POST', extra?.signal));
44
+ tool('gripforge_vfx_project_render', 'Render actual VFX frames for inspection', 'Queue a durable render of an owned id OR an unsaved project at 1–4 timestamps. Returns job_id immediately. Poll gripforge_generation_read with include_preview for the actual rendered image, coverage, clipping and shader diagnostics. Uses the shared GripForge SceneViewport. Saves preview evidence only, no project revision. Requires workspace write access because the job and preview consume storage. Render timings are not browser FPS.', { idempotency_key: schema.string().min(8).max(160).optional(), id: id.optional(), project: project.optional(), times }, false, (args, extra) => api('/render', args, 'POST', extra?.signal));
45
+ tool('gripforge_vfx_project_review', 'Review an existing work revision', 'Queue a fresh GripForge capture and independent visual review without writing another source revision. Poll gripforge_generation_read; inspect its preview, then explicitly promote if approved.', { id, expected_revision: schema.number().int().positive(), idempotency_key: schema.string().min(8).max(160).optional() }, false, (args, extra) => api(`/${args.id}/review`, args, 'POST', extra?.signal));
46
+ tool('gripforge_vfx_project_promote', 'Set the visually approved VFX version', 'Explicitly promote work after a trusted positive review matching this source, dependencies and renderer. expectedCurrent is the existing validated revision, or null.', { id, revision: schema.number().int().positive(), expectedCurrent: schema.number().int().positive().nullable() }, false, (args, extra) => api(`/${args.id}/promote`, args, 'POST', extra?.signal));
47
+ tool('gripforge_vfx_project_generate', 'Create, render and refine a custom VFX', 'Text/image -> durable generation job -> original geometry/shaders -> actual GripForge rendering -> visual critique/correction -> workspace work revision. Returns immediately with job_id, status_url and studio_url. Poll gripforge_generation_read, then open its source_url to inspect the saved source and review. Closing the request does not cancel the job. Completed steps survive worker restart. Pass id + expected_revision to refine; image_id must belong to this workspace. A visually rejected candidate never replaces the validated version.', { idempotency_key: schema.string().min(8).max(160).optional(), prompt: schema.string().max(2000).optional(), visual_style: schema.enum(['realistic', 'stylized', 'anime', 'lowpoly']).optional(), image_mode: schema.enum(['spatial', 'animated-artwork']).optional().describe('Default spatial: build a 3D effect. animated-artwork explicitly opts into a flat animated illustration.'), image_id: id.optional(), image_data: schema.string().max(12 * 1024 * 1024).optional(), id: id.optional(), expected_revision: schema.number().int().positive().optional() }, false, (args, extra) => api('/generate', args, 'POST', extra?.signal), true);
48
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gripforgeai/mcp",
3
- "version": "0.1.2",
4
- "description": "GripForge MCP server \u2014 attach weapons & props to rigged characters from Claude Code, Cursor or any MCP client. Calls the GripForge API.",
3
+ "version": "0.1.9",
4
+ "description": "The tools your AI needs to make your games. GripForge MCP for Cursor, Windsurf, VS Code or any MCP client.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
@@ -23,20 +23,19 @@
23
23
  "gamedev",
24
24
  "rigging",
25
25
  "weapon",
26
- "bone-attach",
27
- "claude"
26
+ "bone-attach"
28
27
  ],
29
28
  "repository": {
30
29
  "type": "git",
31
30
  "url": "https://github.com/gripforgeai/mcp"
32
31
  },
33
32
  "dependencies": {
34
- "@modelcontextprotocol/sdk": "^1.12.1",
35
- "zod": "^3.24.0"
33
+ "@modelcontextprotocol/sdk": "^1.30.0",
34
+ "zod": "^3.25.76"
36
35
  },
37
36
  "devDependencies": {
38
37
  "@types/node": "^20",
39
38
  "typescript": "^5"
40
39
  },
41
40
  "mcpName": "io.github.gripforgeai/mcp"
42
- }
41
+ }