OpenControl lets you control your infrastructure with AI.
- Self-hosted: Runs in your infrastructure with access to internal resources and functions from your codebase; deploys to AWS Lambda, Cloudflare Workers, or containers.
- Unified gateway: Generates a single HTTP endpoint that you can chat with or register with your AI client and it exposes all your tools.
- Universal: Works with any model that supports tool calling. Like the models from Anthropic, OpenAI, or Google.
- Secure: Supports authentication through any OAuth provider.
-
Install dependencies
npm install opencontrol hono @ai-sdk/anthropic
Here are we are going to use Anthropic's Claude.
-
Create the server
touch src/opencontrol.ts
import { create } from "opencontrol" import { handle } from "hono/aws-lambda" const app = create({ // model: ..., // tools: [ ] }) export const handler = handle(app)
-
Pick the model
+ import { Resource } from "sst" + import { createAnthropic } from "@ai-sdk/anthropic" const app = create({ + model: createAnthropic({ + apiKey: Resource.AnthropicKey.value, + })("claude-3-7-sonnet-20250219") })
-
Define your tools
+ import { tool } from "opencontrol/tool" + import { Inventory } from "@acme/core/inventory/index" + const inventory = tool({ + name: "inventory_record", + description: "Record new inventory event to track in or out amounts", + args: Inventory.record.schema, + async run(input) { + return Inventory.record(input) + } + }) const app = create({ tools: [ + inventory ] })
-
Infrastructure
We are using SST here, but you can since OpenControl is just a Hono app, you can deploy it however you want.
touch sst.config.ts
const anthropicKey = new sst.Secret("AnthropicKey") const server = new sst.aws.OpenControl("MyServer", { server: { handler: "src/opencontrol.handler", link: [anthropicKey], }, })
We are defining a secret for the Anthropic API key and linking it to our OpenControl server.
-
Link any resources
const bucket = new sst.aws.Bucket("MyBucket") const server = new sst.aws.OpenControl("MyServer", { server: { handler: "src/opencontrol.handler", link: [bucket], }, })
If your tools need to access to your resources, you can link them as well.
-
Grant permissions
If you are using the AWS tool, you'll need to give your OpenControl server permissions to access your AWS account.
const server = new sst.aws.OpenControl("MyServer", { server: { handler: "src/opencontrol.handler", policies: $dev ? ["arn:aws:iam::aws:policy/AdministratorAccess"] : ["arn:aws:iam::aws:policy/ReadOnlyAccess"], }, })
Here we are giving it admin access in dev but read-only access in prod.
-
Deploy
Currently, OpenControl uses basic auth but we'll be adding support for OAuth soon.
return { OpenControlUrl: server.url, OpenControlPassword: server.password, }
You can print out the URL of your server and it's password and deploy.
sst deploy
Now head over to the URL, login with the password, and you can use AI to talk to your infrastructure.
Check out how Terminal uses OpenControl.
Here are some examples of the tools you can use with OpenControl.
-
AWS
import { z } from "zod" import AWS from "aws-sdk" import { tool } from "opencontrol/tool" const aws = tool({ name: "aws", description: "Make a call to the AWS SDK for JavaScript v2", args: z.object({ client: z.string().describe("Class name of the client to use"), command: z.string().describe("Command to call on the client"), args: z .record(z.string(), z.any()) .optional() .describe("Arguments to pass to the command"), }), async run(input) { // @ts-ignore const client = new AWS[input.client]() return await client[input.command](input.args).promise() }, })
-
Stripe
import { z } from "zod" import { tool } from "opencontrol/tool" const stripe = tool({ name: "stripe", description: "make a call to the stripe api", args: z.object({ method: z.string().describe("HTTP method to use"), path: z.string().describe("Path to call"), query: z.record(z.string()).optional().describe("Query params"), contentType: z.string().optional().describe("HTTP content type to use"), body: z.string().optional().describe("HTTP body to use if it is not GET"), }), async run(input) { const url = new URL("https://api.stripe.com" + input.path) if (input.query) url.search = new URLSearchParams(input.query).toString() const response = await fetch(url.toString(), { method: input.method, headers: { Authorization: `Bearer ${Resource.StripeSecret.value}`, "Content-Type": input.contentType, }, body: input.body ? input.body : undefined, }) if (!response.ok) throw new Error(await response.text()) return response.text() }, })
-
SQL Database
import { z } from "zod" import { tool } from "opencontrol/tool" import { db } from "@acme/core/drizzle/index" const databaseRead = tool({ name: "database_query_readonly", description: "Readonly database query for MySQL, use this if there are no direct tools", args: z.object({ query: z.string() }), async run(input) { return db.transaction(async (tx) => tx.execute(input.query), { accessMode: "read only", isolationLevel: "read committed", }) }, }) const databaseWrite = tool({ name: "database_query_write", description: "DANGEROUS operation that writes to the database. You MUST triple check with the user before using this tool - show them the query you are about to run.", args: z.object({ query: z.string() }), async run(input) { return db.transaction(async (tx) => tx.execute(input.query), { isolationLevel: "read committed", }) }, })
OpenControl is created by the maintainers of SST.