@vaiftech/mcp 1.0.2 → 1.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 +61 -0
- package/dist/index.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,6 +98,13 @@ You can provide both for full access to all tools, or just one depending on your
|
|
|
98
98
|
| `update_row` | Update an existing row by ID |
|
|
99
99
|
| `delete_row` | Delete a row by ID |
|
|
100
100
|
|
|
101
|
+
### Auth
|
|
102
|
+
|
|
103
|
+
| Tool | Description |
|
|
104
|
+
|------|-------------|
|
|
105
|
+
| `list_api_keys` | List all API keys for the project |
|
|
106
|
+
| `create_api_key` | Create a new project-scoped API key |
|
|
107
|
+
|
|
101
108
|
### Storage
|
|
102
109
|
|
|
103
110
|
| Tool | Description |
|
|
@@ -105,6 +112,9 @@ You can provide both for full access to all tools, or just one depending on your
|
|
|
105
112
|
| `list_buckets` | List all storage buckets |
|
|
106
113
|
| `list_files` | List files in a bucket with optional path prefix |
|
|
107
114
|
| `get_signed_url` | Generate a temporary signed download URL |
|
|
115
|
+
| `create_bucket` | Create a new storage bucket (public or private) |
|
|
116
|
+
| `list_storage_policies` | List RLS-style access policies on a bucket |
|
|
117
|
+
| `create_storage_policy` | Create an access policy for a storage bucket |
|
|
108
118
|
|
|
109
119
|
### Functions
|
|
110
120
|
|
|
@@ -112,6 +122,15 @@ You can provide both for full access to all tools, or just one depending on your
|
|
|
112
122
|
|------|-------------|
|
|
113
123
|
| `list_functions` | List all serverless functions |
|
|
114
124
|
| `invoke_function` | Invoke a function with an optional JSON payload |
|
|
125
|
+
| `create_function` | Create a new serverless function definition |
|
|
126
|
+
| `deploy_function` | Deploy source code to a function |
|
|
127
|
+
|
|
128
|
+
### Realtime
|
|
129
|
+
|
|
130
|
+
| Tool | Description |
|
|
131
|
+
|------|-------------|
|
|
132
|
+
| `realtime_status` | Check which tables have realtime enabled |
|
|
133
|
+
| `enable_realtime` | Enable realtime subscriptions on specific tables |
|
|
115
134
|
|
|
116
135
|
### Schema
|
|
117
136
|
|
|
@@ -161,6 +180,48 @@ Using list_tables
|
|
|
161
180
|
Using invoke_function with functionId="fn_abc", payload={"userId": "123"}
|
|
162
181
|
```
|
|
163
182
|
|
|
183
|
+
### Create a storage bucket
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
> Create a private bucket called "avatars"
|
|
187
|
+
|
|
188
|
+
Using create_bucket with name="avatars", public=false
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Enable realtime on a table
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
> Enable realtime subscriptions on the messages table
|
|
195
|
+
|
|
196
|
+
Using enable_realtime with tables=["messages"]
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Deploy a function
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
> Create and deploy a hello_world function
|
|
203
|
+
|
|
204
|
+
Using create_function with name="hello_world", runtime="nodejs20"
|
|
205
|
+
Using deploy_function with functionId="fn_xyz", source="export default async..."
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
> **Function naming**: Names must be alphanumeric and underscores only (`^[a-zA-Z0-9_]+$`). Use `send_email` not `send-email`.
|
|
209
|
+
|
|
210
|
+
## Auth Modes
|
|
211
|
+
|
|
212
|
+
VAIF uses two authentication modes. The MCP server handles both automatically:
|
|
213
|
+
|
|
214
|
+
| Auth Mode | Header | Used For |
|
|
215
|
+
|-----------|--------|----------|
|
|
216
|
+
| **API Key** | `x-api-key: vk_xxx` | Data-plane: CRUD on tables (`/generated/*`), storage uploads/downloads, function invocation |
|
|
217
|
+
| **JWT Token** | `Authorization: Bearer <jwt>` | Control-plane: schema introspection, project management, function CRUD, bucket creation |
|
|
218
|
+
|
|
219
|
+
> **Note**: API keys do NOT work for control-plane endpoints. The MCP server uses both auth modes automatically when both are configured (which `vaif claude-setup` does by default).
|
|
220
|
+
|
|
221
|
+
## Limitations
|
|
222
|
+
|
|
223
|
+
- **Sub-agents**: MCP tools are only available to the main Claude Code session, not to spawned sub-agents via the Task tool. The main session should handle all VAIF backend operations directly.
|
|
224
|
+
|
|
164
225
|
## Development
|
|
165
226
|
|
|
166
227
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {StdioServerTransport}from'@modelcontextprotocol/sdk/server/stdio.js';import {McpServer}from'@modelcontextprotocol/sdk/server/mcp.js';import {z}from'zod';import {readFileSync}from'fs';import {join}from'path';import {homedir}from'os';var P=Object.defineProperty;var C=(r,t,e)=>t in r?P(r,t,{enumerable:true,configurable:true,writable:true,value:e}):r[t]=e;var u=(r,t,e)=>C(r,typeof t!="symbol"?t+"":t,e);var f=class{constructor(t){u(this,"apiUrl");u(this,"projectId");u(this,"apiKey");u(this,"authToken");this.apiUrl=t.apiUrl.replace(/\/+$/,""),this.projectId=t.projectId,this.apiKey=t.apiKey,this.authToken=t.authToken;}async dataPlane(t,e,o){if(!this.apiKey)throw new Error("API key is required for data plane requests. Set --api-key or VAIF_API_KEY.");let n=`${this.apiUrl}${e}`,i={"Content-Type":"application/json","x-vaif-key":this.apiKey},s=await fetch(n,{method:t,headers:i,body:o?JSON.stringify(o):void 0});if(!s.ok){let l=await s.text().catch(()=>"");throw new Error(`Data plane ${t} ${e} failed (${s.status}): ${l}`)}let p=await s.text();if(p)return JSON.parse(p)}async controlPlane(t,e,o){if(!this.authToken)throw new Error("Auth token is required for control plane requests. Set --auth-token or VAIF_AUTH_TOKEN.");let n=`${this.apiUrl}${e}`,i={"Content-Type":"application/json",Authorization:`Bearer ${this.authToken}`},s=await fetch(n,{method:t,headers:i,body:o?JSON.stringify(o):void 0});if(!s.ok){let l=await s.text().catch(()=>"");throw new Error(`Control plane ${t} ${e} failed (${s.status}): ${l}`)}let p=await s.text();if(p)return JSON.parse(p)}};function y(r,t){r.tool("list_tables","List all tables in the VAIF project database. Returns table names and basic metadata.",{},async()=>{try{let o=((await t.controlPlane("GET",`/schema-engine/introspect/${t.projectId}`)).tables||[]).map(n=>n.name);return {content:[{type:"text",text:JSON.stringify(o,null,2)}]}}catch(e){return {content:[{type:"text",text:`Error listing tables: ${e instanceof Error?e.message:String(e)}`}]}}}),r.tool("describe_table","Describe a specific table's columns, types, constraints, and relationships.",{table:z.string().describe("The name of the table to describe")},async({table:e})=>{try{let n=((await t.controlPlane("GET",`/schema-engine/introspect/${t.projectId}`)).tables||[]).find(i=>i.name===e);return n?{content:[{type:"text",text:JSON.stringify(n,null,2)}]}:{content:[{type:"text",text:`Table "${e}" not found. Use list_tables to see available tables.`}]}}catch(o){return {content:[{type:"text",text:`Error describing table: ${o instanceof Error?o.message:String(o)}`}]}}}),r.tool("query_rows","Query rows from a table with optional filtering, sorting, and pagination. Filter operators: eq, neq, gt, lt, gte, lte, in, like, ilike, is.",{table:z.string().describe("The table to query"),filter:z.record(z.string()).optional().describe('Filter conditions as key-value pairs. Use "field" for equality or "field.op" for operators (e.g. "age.gt": "18", "name.like": "%john%")'),limit:z.number().optional().describe("Maximum number of rows to return (default: 50)"),offset:z.number().optional().describe("Number of rows to skip for pagination"),order_by:z.string().optional().describe("Column name to order results by"),order:z.enum(["asc","desc"]).optional().describe("Sort direction (asc or desc)")},async({table:e,filter:o,limit:n,offset:i,order_by:s,order:p})=>{try{let l=new URLSearchParams;if(o)for(let[v,E]of Object.entries(o))l.append(`filter[${v}]`,E);n!==void 0&&l.append("limit",String(n)),i!==void 0&&l.append("offset",String(i)),s&&l.append("order_by",s),p&&l.append("order",p);let m=l.toString(),w=`/generated/${e}${m?`?${m}`:""}`,k=await t.dataPlane("GET",w);return {content:[{type:"text",text:JSON.stringify(k,null,2)}]}}catch(l){return {content:[{type:"text",text:`Error querying rows: ${l instanceof Error?l.message:String(l)}`}]}}}),r.tool("insert_row","Insert a new row into a table. Returns the created row with generated fields (id, timestamps, etc).",{table:z.string().describe("The table to insert into"),data:z.record(z.any()).describe("Column values for the new row")},async({table:e,data:o})=>{try{let n=await t.dataPlane("POST",`/generated/${e}`,o);return {content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(n){return {content:[{type:"text",text:`Error inserting row: ${n instanceof Error?n.message:String(n)}`}]}}}),r.tool("update_row","Update an existing row by ID. Returns the updated row.",{table:z.string().describe("The table containing the row"),id:z.string().describe("The ID of the row to update"),data:z.record(z.any()).describe("Column values to update")},async({table:e,id:o,data:n})=>{try{let i=await t.dataPlane("PATCH",`/generated/${e}/${o}`,n);return {content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(i){return {content:[{type:"text",text:`Error updating row: ${i instanceof Error?i.message:String(i)}`}]}}}),r.tool("delete_row","Delete a row by ID. Returns confirmation of the deletion.",{table:z.string().describe("The table containing the row"),id:z.string().describe("The ID of the row to delete")},async({table:e,id:o})=>{try{let n=await t.dataPlane("DELETE",`/generated/${e}/${o}`);return {content:[{type:"text",text:JSON.stringify(n??{success:!0,deleted:o},null,2)}]}}catch(n){return {content:[{type:"text",text:`Error deleting row: ${n instanceof Error?n.message:String(n)}`}]}}});}function h(r,t){r.tool("list_buckets","List all storage buckets in the VAIF project.",{},async()=>{try{let e=await t.controlPlane("GET",`/storage/buckets?projectId=${t.projectId}`);return {content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return {content:[{type:"text",text:`Error listing buckets: ${e instanceof Error?e.message:String(e)}`}]}}}),r.tool("list_files","List files in a storage bucket, optionally filtered by path prefix.",{bucket:z.string().describe("The bucket name"),path:z.string().optional().describe("Path prefix to filter files (e.g. 'images/avatars/')")},async({bucket:e,path:o})=>{try{let n=new URLSearchParams;o&&n.append("prefix",o);let i=n.toString(),s=`/storage/files/${t.projectId}/${e}${i?`?${i}`:""}`,p=await t.controlPlane("GET",s);return {content:[{type:"text",text:JSON.stringify(p,null,2)}]}}catch(n){return {content:[{type:"text",text:`Error listing files: ${n instanceof Error?n.message:String(n)}`}]}}}),r.tool("get_signed_url","Generate a signed download URL for a file in storage. The URL is temporary and expires after the specified duration.",{bucket:z.string().describe("The bucket name"),path:z.string().describe("Path to the file within the bucket"),expiresIn:z.number().optional().describe("URL expiration time in seconds (default: 3600)")},async({bucket:e,path:o,expiresIn:n})=>{try{let i=await t.controlPlane("POST","/storage/download",{bucket:e,path:o,expiresIn:n});return {content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(i){return {content:[{type:"text",text:`Error getting signed URL: ${i instanceof Error?i.message:String(i)}`}]}}});}function b(r,t){r.tool("list_functions","List all serverless functions deployed in the VAIF project.",{},async()=>{try{let e=await t.controlPlane("GET",`/functions/project/${t.projectId}`);return {content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return {content:[{type:"text",text:`Error listing functions: ${e instanceof Error?e.message:String(e)}`}]}}}),r.tool("invoke_function","Invoke a serverless function by ID with an optional JSON payload. Returns the function's response.",{functionId:z.string().describe("The ID of the function to invoke"),payload:z.record(z.any()).optional().describe("JSON payload to pass to the function")},async({functionId:e,payload:o})=>{try{let n=await t.controlPlane("POST",`/functions/${e}/invoke`,o??{});return {content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(n){return {content:[{type:"text",text:`Error invoking function: ${n instanceof Error?n.message:String(n)}`}]}}});}function x(r,t){r.tool("get_schema","Get the full database schema for the VAIF project, including all tables, columns, types, constraints, and relationships.",{},async()=>{try{let e=await t.controlPlane("GET",`/schema-engine/introspect/${t.projectId}`);return {content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return {content:[{type:"text",text:`Error getting schema: ${e instanceof Error?e.message:String(e)}`}]}}}),r.tool("create_tables",`Create or update database tables in the VAIF project. Accepts a full schema definition with tables, columns, indexes, and foreign keys. This is a declarative operation \u2014 provide the desired end-state and VAIF will diff and apply changes.
|
|
2
|
+
import {StdioServerTransport}from'@modelcontextprotocol/sdk/server/stdio.js';import {McpServer}from'@modelcontextprotocol/sdk/server/mcp.js';import {z}from'zod';import {readFileSync}from'fs';import {join}from'path';import {homedir}from'os';var $=Object.defineProperty;var O=(r,t,e)=>t in r?$(r,t,{enumerable:true,configurable:true,writable:true,value:e}):r[t]=e;var f=(r,t,e)=>O(r,typeof t!="symbol"?t+"":t,e);var g=class{constructor(t){f(this,"apiUrl");f(this,"projectId");f(this,"apiKey");f(this,"authToken");this.apiUrl=t.apiUrl.replace(/\/+$/,""),this.projectId=t.projectId,this.apiKey=t.apiKey,this.authToken=t.authToken;}async dataPlane(t,e,o){if(!this.apiKey)throw new Error("API key is required for data plane requests. Set --api-key or VAIF_API_KEY.");let n=`${this.apiUrl}${e}`,i={"Content-Type":"application/json","x-vaif-key":this.apiKey},s=await fetch(n,{method:t,headers:i,body:o?JSON.stringify(o):void 0});if(!s.ok){let l=await s.text().catch(()=>"");throw new Error(`Data plane ${t} ${e} failed (${s.status}): ${l}`)}let p=await s.text();if(p)return JSON.parse(p)}async controlPlane(t,e,o){if(!this.authToken)throw new Error("Auth token is required for control plane requests. Set --auth-token or VAIF_AUTH_TOKEN.");let n=`${this.apiUrl}${e}`,i={"Content-Type":"application/json",Authorization:`Bearer ${this.authToken}`},s=await fetch(n,{method:t,headers:i,body:o?JSON.stringify(o):void 0});if(!s.ok){let l=await s.text().catch(()=>"");throw new Error(`Control plane ${t} ${e} failed (${s.status}): ${l}`)}let p=await s.text();if(p)return JSON.parse(p)}};function y(r,t){r.tool("list_tables","List all tables in the VAIF project database. Returns table names and basic metadata.",{},async()=>{try{let o=((await t.controlPlane("GET",`/schema-engine/introspect/${t.projectId}`)).tables||[]).map(n=>n.name);return {content:[{type:"text",text:JSON.stringify(o,null,2)}]}}catch(e){return {content:[{type:"text",text:`Error listing tables: ${e instanceof Error?e.message:String(e)}`}]}}}),r.tool("describe_table","Describe a specific table's columns, types, constraints, and relationships.",{table:z.string().describe("The name of the table to describe")},async({table:e})=>{try{let n=((await t.controlPlane("GET",`/schema-engine/introspect/${t.projectId}`)).tables||[]).find(i=>i.name===e);return n?{content:[{type:"text",text:JSON.stringify(n,null,2)}]}:{content:[{type:"text",text:`Table "${e}" not found. Use list_tables to see available tables.`}]}}catch(o){return {content:[{type:"text",text:`Error describing table: ${o instanceof Error?o.message:String(o)}`}]}}}),r.tool("query_rows","Query rows from a table with optional filtering, sorting, and pagination. Filter operators: eq, neq, gt, lt, gte, lte, in, like, ilike, is.",{table:z.string().describe("The table to query"),filter:z.record(z.string()).optional().describe('Filter conditions as key-value pairs. Use "field" for equality or "field.op" for operators (e.g. "age.gt": "18", "name.like": "%john%")'),limit:z.number().optional().describe("Maximum number of rows to return (default: 50)"),offset:z.number().optional().describe("Number of rows to skip for pagination"),order_by:z.string().optional().describe("Column name to order results by"),order:z.enum(["asc","desc"]).optional().describe("Sort direction (asc or desc)")},async({table:e,filter:o,limit:n,offset:i,order_by:s,order:p})=>{try{let l=new URLSearchParams;if(o)for(let[_,C]of Object.entries(o))l.append(`filter[${_}]`,C);n!==void 0&&l.append("limit",String(n)),i!==void 0&&l.append("offset",String(i)),s&&l.append("order_by",s),p&&l.append("order",p);let m=l.toString(),P=`/generated/${e}${m?`?${m}`:""}`,v=await t.dataPlane("GET",P);return {content:[{type:"text",text:JSON.stringify(v,null,2)}]}}catch(l){return {content:[{type:"text",text:`Error querying rows: ${l instanceof Error?l.message:String(l)}`}]}}}),r.tool("insert_row","Insert a new row into a table. Returns the created row with generated fields (id, timestamps, etc).",{table:z.string().describe("The table to insert into"),data:z.record(z.any()).describe("Column values for the new row")},async({table:e,data:o})=>{try{let n=await t.dataPlane("POST",`/generated/${e}`,o);return {content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(n){return {content:[{type:"text",text:`Error inserting row: ${n instanceof Error?n.message:String(n)}`}]}}}),r.tool("update_row","Update an existing row by ID. Returns the updated row.",{table:z.string().describe("The table containing the row"),id:z.string().describe("The ID of the row to update"),data:z.record(z.any()).describe("Column values to update")},async({table:e,id:o,data:n})=>{try{let i=await t.dataPlane("PATCH",`/generated/${e}/${o}`,n);return {content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(i){return {content:[{type:"text",text:`Error updating row: ${i instanceof Error?i.message:String(i)}`}]}}}),r.tool("delete_row","Delete a row by ID. Returns confirmation of the deletion.",{table:z.string().describe("The table containing the row"),id:z.string().describe("The ID of the row to delete")},async({table:e,id:o})=>{try{let n=await t.dataPlane("DELETE",`/generated/${e}/${o}`);return {content:[{type:"text",text:JSON.stringify(n??{success:!0,deleted:o},null,2)}]}}catch(n){return {content:[{type:"text",text:`Error deleting row: ${n instanceof Error?n.message:String(n)}`}]}}});}function h(r,t){r.tool("list_buckets","List all storage buckets in the VAIF project.",{},async()=>{try{let e=await t.controlPlane("GET",`/storage/buckets?projectId=${t.projectId}`);return {content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return {content:[{type:"text",text:`Error listing buckets: ${e instanceof Error?e.message:String(e)}`}]}}}),r.tool("list_files","List files in a storage bucket, optionally filtered by path prefix.",{bucket:z.string().describe("The bucket name"),path:z.string().optional().describe("Path prefix to filter files (e.g. 'images/avatars/')")},async({bucket:e,path:o})=>{try{let n=new URLSearchParams;o&&n.append("prefix",o);let i=n.toString(),s=`/storage/files/${t.projectId}/${e}${i?`?${i}`:""}`,p=await t.controlPlane("GET",s);return {content:[{type:"text",text:JSON.stringify(p,null,2)}]}}catch(n){return {content:[{type:"text",text:`Error listing files: ${n instanceof Error?n.message:String(n)}`}]}}}),r.tool("get_signed_url","Generate a signed download URL for a file in storage. The URL is temporary and expires after the specified duration.",{bucket:z.string().describe("The bucket name"),path:z.string().describe("Path to the file within the bucket"),expiresIn:z.number().optional().describe("URL expiration time in seconds (default: 3600)")},async({bucket:e,path:o,expiresIn:n})=>{try{let i=await t.controlPlane("POST","/storage/download",{bucket:e,path:o,expiresIn:n});return {content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(i){return {content:[{type:"text",text:`Error getting signed URL: ${i instanceof Error?i.message:String(i)}`}]}}}),r.tool("create_bucket","Create a new storage bucket in the VAIF project. Buckets organize files and control public/private access.",{name:z.string().describe("Bucket name (lowercase, no spaces, e.g. 'avatars', 'documents')"),public:z.boolean().optional().describe("Whether files are publicly accessible without auth (default: false)")},async({name:e,public:o})=>{try{let n=await t.controlPlane("POST","/storage/buckets",{name:e,projectId:t.projectId,public:o??!1});return {content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(n){return {content:[{type:"text",text:`Error creating bucket: ${n instanceof Error?n.message:String(n)}`}]}}}),r.tool("list_storage_policies","List access policies for a storage bucket. Policies control who can upload, download, or delete files.",{bucketId:z.string().describe("The bucket ID to list policies for")},async({bucketId:e})=>{try{let o=await t.controlPlane("GET",`/storage/buckets/${e}/policies`);return {content:[{type:"text",text:JSON.stringify(o,null,2)}]}}catch(o){return {content:[{type:"text",text:`Error listing storage policies: ${o instanceof Error?o.message:String(o)}`}]}}}),r.tool("create_storage_policy","Create an access policy for a storage bucket. Policies define who can perform operations (upload, download, delete) on files in the bucket.",{bucketId:z.string().describe("The bucket ID to create the policy for"),name:z.string().describe("Policy name (e.g. 'allow-authenticated-uploads')"),operation:z.enum(["SELECT","INSERT","UPDATE","DELETE"]).describe("The storage operation this policy applies to (SELECT=download, INSERT=upload, UPDATE=replace, DELETE=remove)"),definition:z.string().describe("SQL-like policy definition (e.g. 'auth.uid() IS NOT NULL' or 'true' for public access)")},async({bucketId:e,name:o,operation:n,definition:i})=>{try{let s=await t.controlPlane("POST",`/storage/buckets/${e}/policies`,{name:o,operation:n,definition:i});return {content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(s){return {content:[{type:"text",text:`Error creating storage policy: ${s instanceof Error?s.message:String(s)}`}]}}});}function b(r,t){r.tool("list_functions","List all serverless functions deployed in the VAIF project.",{},async()=>{try{let e=await t.controlPlane("GET",`/functions/project/${t.projectId}`);return {content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return {content:[{type:"text",text:`Error listing functions: ${e instanceof Error?e.message:String(e)}`}]}}}),r.tool("invoke_function","Invoke a serverless function by ID with an optional JSON payload. Returns the function's response.",{functionId:z.string().describe("The ID of the function to invoke"),payload:z.record(z.any()).optional().describe("JSON payload to pass to the function")},async({functionId:e,payload:o})=>{try{let n=await t.controlPlane("POST",`/functions/${e}/invoke`,o??{});return {content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(n){return {content:[{type:"text",text:`Error invoking function: ${n instanceof Error?n.message:String(n)}`}]}}}),r.tool("create_function","Create a new serverless function in the VAIF project. After creation, use deploy_function to upload the source code.",{name:z.string().describe("Function name (alphanumeric and underscores only, e.g. 'send_email', 'process_webhook')"),runtime:z.string().optional().describe("Function runtime (default: 'nodejs20'). Options: nodejs20, nodejs18, deno, python311"),timeoutMs:z.number().optional().describe("Execution timeout in milliseconds (default: 10000, range: 1000-30000)")},async({name:e,runtime:o,timeoutMs:n})=>{try{let i=await t.controlPlane("POST","/functions",{name:e,projectId:t.projectId,runtime:o??"nodejs20",timeoutMs:n??1e4});return {content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(i){return {content:[{type:"text",text:`Error creating function: ${i instanceof Error?i.message:String(i)}`}]}}}),r.tool("deploy_function","Deploy source code to an existing serverless function. Uploads the code and triggers a build/deploy cycle. The function must already exist (use create_function first).",{functionId:z.string().describe("The ID of the function to deploy to"),source:z.string().describe("The function source code as a string (TypeScript or JavaScript)")},async({functionId:e,source:o})=>{try{let n=await t.controlPlane("PUT",`/functions/${e}/source`,{sourceCode:o});return {content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(n){return {content:[{type:"text",text:`Error deploying function: ${n instanceof Error?n.message:String(n)}`}]}}});}function x(r,t){r.tool("get_schema","Get the full database schema for the VAIF project, including all tables, columns, types, constraints, and relationships.",{},async()=>{try{let e=await t.controlPlane("GET",`/schema-engine/introspect/${t.projectId}`);return {content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return {content:[{type:"text",text:`Error getting schema: ${e instanceof Error?e.message:String(e)}`}]}}}),r.tool("create_tables",`Create or update database tables in the VAIF project. Accepts a full schema definition with tables, columns, indexes, and foreign keys. This is a declarative operation \u2014 provide the desired end-state and VAIF will diff and apply changes.
|
|
3
3
|
|
|
4
4
|
Valid column types: uuid, text, varchar, string, int, integer, bigint, boolean, bool, jsonb, json, timestamptz, timestamp, date, numeric, decimal, float, double, text[], integer[]
|
|
5
5
|
|
|
@@ -16,7 +16,7 @@ Example:
|
|
|
16
16
|
],
|
|
17
17
|
"indexes": [{ "name": "idx_posts_author", "columns": ["author_id"] }]
|
|
18
18
|
}]
|
|
19
|
-
}`,{tables:z.array(z.object({name:z.string().describe("Table name"),columns:z.array(z.object({name:z.string().describe("Column name"),type:z.enum(["uuid","text","varchar","string","int","integer","bigint","boolean","bool","jsonb","json","timestamptz","timestamp","date","numeric","decimal","float","double","text[]","integer[]"]).describe("Column type"),primaryKey:z.boolean().optional().describe("Is primary key"),unique:z.boolean().optional().describe("Has unique constraint"),nullable:z.boolean().optional().describe("Allows NULL (default: true)"),default:z.union([z.string(),z.number(),z.boolean()]).optional().describe("Default value (e.g. 'gen_random_uuid()', 'now()', true)"),references:z.object({table:z.string().describe("Referenced table"),column:z.string().optional().describe("Referenced column (default: id)"),onDelete:z.enum(["CASCADE","RESTRICT","SET NULL","SET DEFAULT","NO ACTION"]).optional(),onUpdate:z.enum(["CASCADE","RESTRICT","SET NULL","SET DEFAULT","NO ACTION"]).optional()}).optional().describe("Foreign key reference")})).describe("Table columns"),indexes:z.array(z.object({name:z.string().describe("Index name"),columns:z.array(z.string()).describe("Indexed columns"),unique:z.boolean().optional().describe("Unique index")})).optional().describe("Table indexes")})).describe("Tables to create or update"),migration_name:z.string().optional().describe("Optional migration name for tracking"),allow_destructive:z.boolean().optional().describe("Allow destructive changes like dropping columns (default: false)")},async({tables:e,migration_name:o,allow_destructive:n})=>{try{let i={schemaVersion:"1.0",tables:e},s=await t.controlPlane("POST","/schema-engine/apply",{projectId:t.projectId,definition:i,migrationName:o,allowDestructive:n??!1});return {content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(i){return {content:[{type:"text",text:`Error creating tables: ${i instanceof Error?i.message:String(i)}`}]}}});}function T(r,t){r.resource("schema","vaif://schema",{description:"The full database schema for the connected VAIF project, including tables, columns, types, and relationships.",mimeType:"application/json"},async()=>{let e=await t.controlPlane("GET",`/schema-engine/introspect/${t.projectId}`);return {contents:[{uri:"vaif://schema",mimeType:"application/json",text:JSON.stringify(e,null,2)}]}});}function
|
|
19
|
+
}`,{tables:z.array(z.object({name:z.string().describe("Table name"),columns:z.array(z.object({name:z.string().describe("Column name"),type:z.enum(["uuid","text","varchar","string","int","integer","bigint","boolean","bool","jsonb","json","timestamptz","timestamp","date","numeric","decimal","float","double","text[]","integer[]"]).describe("Column type"),primaryKey:z.boolean().optional().describe("Is primary key"),unique:z.boolean().optional().describe("Has unique constraint"),nullable:z.boolean().optional().describe("Allows NULL (default: true)"),default:z.union([z.string(),z.number(),z.boolean()]).optional().describe("Default value (e.g. 'gen_random_uuid()', 'now()', true)"),references:z.object({table:z.string().describe("Referenced table"),column:z.string().optional().describe("Referenced column (default: id)"),onDelete:z.enum(["CASCADE","RESTRICT","SET NULL","SET DEFAULT","NO ACTION"]).optional(),onUpdate:z.enum(["CASCADE","RESTRICT","SET NULL","SET DEFAULT","NO ACTION"]).optional()}).optional().describe("Foreign key reference")})).describe("Table columns"),indexes:z.array(z.object({name:z.string().describe("Index name"),columns:z.array(z.string()).describe("Indexed columns"),unique:z.boolean().optional().describe("Unique index")})).optional().describe("Table indexes")})).describe("Tables to create or update"),migration_name:z.string().optional().describe("Optional migration name for tracking"),allow_destructive:z.boolean().optional().describe("Allow destructive changes like dropping columns (default: false)")},async({tables:e,migration_name:o,allow_destructive:n})=>{try{let i={schemaVersion:"1.0",tables:e},s=await t.controlPlane("POST","/schema-engine/apply",{projectId:t.projectId,definition:i,migrationName:o,allowDestructive:n??!1});return {content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(i){return {content:[{type:"text",text:`Error creating tables: ${i instanceof Error?i.message:String(i)}`}]}}});}function T(r,t){r.tool("list_api_keys","List all API keys for the VAIF project. Returns key metadata (name, prefix, created date) but not the full secret.",{},async()=>{try{let e=await t.controlPlane("GET",`/v1/projects/${t.projectId}/api-keys`);return {content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return {content:[{type:"text",text:`Error listing API keys: ${e instanceof Error?e.message:String(e)}`}]}}}),r.tool("create_api_key","Create a new API key for the VAIF project. The full key is only shown once in the response \u2014 store it securely.",{name:z.string().describe("A descriptive name for the API key (e.g. 'mobile-app', 'ci-pipeline')")},async({name:e})=>{try{let o=await t.controlPlane("POST",`/v1/projects/${t.projectId}/api-keys`,{name:e});return {content:[{type:"text",text:JSON.stringify(o,null,2)}]}}catch(o){return {content:[{type:"text",text:`Error creating API key: ${o instanceof Error?o.message:String(o)}`}]}}});}function I(r,t){r.tool("realtime_status","Check the realtime subscription status for the VAIF project. Shows which tables have realtime enabled and the connection state.",{},async()=>{try{let e=await t.controlPlane("GET",`/realtime/status/project/${t.projectId}`);return {content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return {content:[{type:"text",text:`Error getting realtime status: ${e instanceof Error?e.message:String(e)}`}]}}}),r.tool("enable_realtime","Enable realtime subscriptions for one or more tables. Clients can then subscribe to INSERT, UPDATE, and DELETE events on these tables via WebSocket.",{tables:z.array(z.string()).describe("Table names to enable realtime on (e.g. ['messages', 'notifications'])")},async({tables:e})=>{try{let o=await t.controlPlane("POST","/realtime/install",{projectId:t.projectId,tables:e});return {content:[{type:"text",text:JSON.stringify(o,null,2)}]}}catch(o){return {content:[{type:"text",text:`Error enabling realtime: ${o instanceof Error?o.message:String(o)}`}]}}});}function E(r,t){r.resource("schema","vaif://schema",{description:"The full database schema for the connected VAIF project, including tables, columns, types, and relationships.",mimeType:"application/json"},async()=>{let e=await t.controlPlane("GET",`/schema-engine/introspect/${t.projectId}`);return {contents:[{uri:"vaif://schema",mimeType:"application/json",text:JSON.stringify(e,null,2)}]}});}function k(r,t){r.resource("project-info","vaif://project-info",{description:"Project metadata for the connected VAIF project, including name, region, settings, and status.",mimeType:"application/json"},async()=>{let e=await t.controlPlane("GET",`/projects/${t.projectId}`);return {contents:[{uri:"vaif://project-info",mimeType:"application/json",text:JSON.stringify(e,null,2)}]}});}function A(r){let t=new McpServer({name:"vaif-studio",version:"1.2.0"}),e=new g(r);return y(t,e),h(t,e),b(t,e),x(t,e),T(t,e),I(t,e),E(t,e),k(t,e),t}function D(){let r=process.argv.slice(2),t={};for(let e=0;e<r.length;e++){let o=r[e],n=r[e+1];switch(o){case "--api-key":t.apiKey=n,e++;break;case "--project-id":t.projectId=n,e++;break;case "--api-url":t.apiUrl=n,e++;break;case "--auth-token":t.authToken=n,e++;break;case "--help":case "-h":console.error(`
|
|
20
20
|
vaif-mcp \u2014 MCP server for VAIF Studio
|
|
21
21
|
|
|
22
22
|
Usage:
|
|
@@ -38,4 +38,4 @@ Environment variables:
|
|
|
38
38
|
Config files (checked in order):
|
|
39
39
|
./vaif.config.json Local project config
|
|
40
40
|
~/.vaif/auth.json User auth config
|
|
41
|
-
`),process.exit(0);}}return t}function j(r){try{let t=readFileSync(r,"utf-8");return JSON.parse(t)}catch{return null}}function
|
|
41
|
+
`),process.exit(0);}}return t}function j(r){try{let t=readFileSync(r,"utf-8");return JSON.parse(t)}catch{return null}}function J(){let r=D(),t={apiKey:process.env.VAIF_API_KEY,projectId:process.env.VAIF_PROJECT_ID,apiUrl:process.env.VAIF_API_URL,authToken:process.env.VAIF_AUTH_TOKEN},e=j(join(process.cwd(),"vaif.config.json")),o=e?.projectId,n=e?.api?.apiKey,i=j(join(homedir(),".vaif","auth.json")),s=i?.token,p=i?.projectId;return {apiUrl:r.apiUrl||t.apiUrl||"https://api.vaif.studio",projectId:r.projectId||t.projectId||o||p,apiKey:r.apiKey||t.apiKey||n,authToken:r.authToken||t.authToken||s}}async function L(){let r=J();r.projectId||(console.error("Error: Project ID is required. Set --project-id, VAIF_PROJECT_ID, or configure vaif.config.json."),process.exit(1)),!r.apiKey&&!r.authToken&&(console.error("Error: Authentication required. Set --api-key / VAIF_API_KEY or --auth-token / VAIF_AUTH_TOKEN."),process.exit(1)),console.error("VAIF MCP Server v1.0.0"),console.error(` Project: ${r.projectId}`),console.error(` API URL: ${r.apiUrl}`),console.error(` Auth: ${r.apiKey?"API Key":""}${r.apiKey&&r.authToken?" + ":""}${r.authToken?"Auth Token":""}`);let t=A({apiUrl:r.apiUrl,projectId:r.projectId,apiKey:r.apiKey,authToken:r.authToken}),e=new StdioServerTransport;await t.connect(e),console.error("VAIF MCP Server running on stdio");}L().catch(r=>{console.error("Fatal error:",r),process.exit(1);});
|