Connect Seam to Claude
An MCP server so Claude can list devices, create access codes, and query sensors across your Seam workspace.
What one Seam connector unlocks
What you'll be able to ask Claude
Prerequisites
- Seam account. Free sandbox available at console.seam.co. The sandbox gives you virtual devices (locks, noise sensors, thermostats) you can use to test the connector end-to-end before connecting real ones.
- Claude Desktop installed, or the Claude Code CLI. Download Claude Desktop.
- Node.js 18 or later. Check with
node --version.
Create a free Seam workspace and API key
- Sign up at console.seam.co. It's free and takes about a minute.
- When prompted, create a sandbox workspace. This comes pre-loaded with virtual devices (locks, noise sensors, thermostats) you can use to test without real hardware.
- In the Seam Console, open the workspace and find the API Keys page. Generate a new key and copy it. It starts with
seam_. - Important: Seam API keys are scoped to a single workspace. They're server-side only. Never paste one into a browser or client-side code.
You can flip between sandbox and production workspaces freely. Build and test against sandbox, then swap the API key in your Claude config when you're ready for real devices.
Set up the Node.js project
Unlike OwnerRez, Seam has a clean official Node SDK we'll use instead of raw fetch calls. Three dependencies: the MCP SDK, zod, and seam.
mkdir seam-mcp
cd seam-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod seam
Then add "type": "module" to package.json:
{
"name": "seam-mcp",
"version": "1.0.0",
"type": "module",
"main": "index.js",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"zod": "^3.22.0",
"seam": "^1.0.0"
}
}
Create the MCP server file
Save the following as index.js. The server exposes five tools: list-devices, list-locks, list-access-codes, create-access-code, and list-noise-sensors.
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { Seam } from "seam";
import { z } from "zod";
const SEAM_API_KEY = process.env.SEAM_API_KEY;
if (!SEAM_API_KEY) {
console.error("Missing SEAM_API_KEY environment variable");
process.exit(1);
}
const seam = new Seam({ apiKey: SEAM_API_KEY });
const server = new McpServer({
name: "seam",
version: "1.0.0",
});
// Tool 1: List all devices (locks, sensors, thermostats)
server.tool(
"list-devices",
"List every device in the Seam workspace: locks, noise sensors, and thermostats. Returns device_id, display_name, device_type, manufacturer, and online status. Use this as the entry point for any question about devices.",
{},
async () => {
const devices = await seam.devices.list();
return {
content: [{ type: "text", text: JSON.stringify(devices, null, 2) }],
};
}
);
// Tool 2: List locks with online/battery status
server.tool(
"list-locks",
"List only smart locks in the Seam workspace. Returns lock state (locked/unlocked), battery level, and online status for each.",
{},
async () => {
const locks = await seam.locks.list();
return {
content: [{ type: "text", text: JSON.stringify(locks, null, 2) }],
};
}
);
// Tool 3: List access codes on a specific lock
server.tool(
"list-access-codes",
"List all access codes for a specific lock. Returns code, name, starts_at, ends_at, and status (set/unset) for each code.",
{
device_id: z
.string()
.describe("The Seam device_id of the lock to query. Get this from list-locks."),
},
async ({ device_id }) => {
const codes = await seam.accessCodes.list({ device_id });
return {
content: [{ type: "text", text: JSON.stringify(codes, null, 2) }],
};
}
);
// Tool 4: Create a time-bounded access code
server.tool(
"create-access-code",
"Create a new time-bounded access code on a lock. Use this for guest check-ins, cleaner access, and maintenance visits. The code will be active from starts_at to ends_at.",
{
device_id: z
.string()
.describe("The Seam device_id of the lock. Get this from list-locks."),
name: z
.string()
.describe("Human-readable name for the code (e.g. 'Smith stay — Mar 14-17')"),
code: z
.string()
.optional()
.describe(
"Optional 4-8 digit PIN code. If omitted, Seam will generate one."
),
starts_at: z
.string()
.describe("ISO 8601 UTC timestamp when the code becomes active (e.g. 2026-04-14T15:00:00Z)"),
ends_at: z
.string()
.describe("ISO 8601 UTC timestamp when the code expires (e.g. 2026-04-17T11:00:00Z)"),
},
async ({ device_id, name, code, starts_at, ends_at }) => {
const result = await seam.accessCodes.create({
device_id,
name,
code,
starts_at,
ends_at,
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
);
// Tool 5: List noise sensors
server.tool(
"list-noise-sensors",
"List noise sensors (Minut, NoiseAware) in the Seam workspace. Returns current readings and threshold status for each sensor.",
{},
async () => {
const sensors = await seam.noiseSensors.list();
return {
content: [{ type: "text", text: JSON.stringify(sensors, null, 2) }],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
The seam SDK handles authentication, request signing, response parsing, and error handling. That's why this server is shorter than the OwnerRez one despite exposing more functionality.
Register the server with Claude Desktop
Open your Claude Desktop config file:
~/Library/Application Support/Claude/claude_desktop_config.json
%APPDATA%\Claude\claude_desktop_config.json
Add the Seam entry (merge it into your existing mcpServers block if you already have one from the OwnerRez guide):
{
"mcpServers": {
"seam": {
"command": "node",
"args": ["/absolute/path/to/seam-mcp/index.js"],
"env": {
"SEAM_API_KEY": "seam_your_api_key_here"
}
}
}
}
Replace the path and API key with your real values. Then fully quit and reopen Claude Desktop (not just close the window).
Try it
"Create an access code 1234 named 'Test Stay' on the first lock, active right now through tomorrow at noon."
Troubleshooting
Seam returns "Unauthorized" when Claude calls a tool
Your SEAM_API_KEY is wrong, expired, or from a different workspace than the devices you're querying. Double-check the key in claude_desktop_config.json. You can regenerate it in the Seam Console.
If you're mixing sandbox and production workspaces, remember each workspace has its own API key — you can't use a sandbox key against production devices.
"No devices" returned when you expect some
If you're on a fresh sandbox, you may need to add virtual devices via the Seam Console first. If you're on production, the API key might be scoped to a workspace with no connected devices. Check the workspace dropdown in the console.
Cannot find module "seam"
Make sure you ran npm install seam inside the project directory. The package name is seam, lowercase, no scope.
create-access-code fails with an invalid date error
Seam requires ISO 8601 timestamps for starts_at and ends_at. Example: 2026-04-14T15:00:00Z. Claude usually formats these correctly from natural language, but if you're typing timestamps manually, include the T separator and timezone.
I want to expose thermostat or webhook tools too
The Seam SDK exposes the full surface: seam.thermostats.list(), seam.thermostats.set(), seam.webhooks.create(), and more. Add new tools by copying the existing server.tool(...) pattern and calling into the SDK. See docs.seam.co for the full reference.
Other guides
Connect OwnerRez to Claude
The biggest US indie and pro PMS Cole didn't cover. Bookings, properties, guests, and payments.
Connect PriceLabs to Claude
Read-only queries against your synced listings and current dynamic prices.
Connect Safely to Claude
Query guest verifications, damage claims, and coverage. 300 req/min.