Connect Seam to Claude
One MCP connector, eight lock brands, plus noise sensors and thermostats. Ask Claude to create a guest access code, check which units are currently unlocked, or surface noise events from last weekend. All through Seam.
What one Seam connector unlocks
Cole Rubin at Conduit covered 8 PMSes. Smart locks, noise sensors, and thermostats were missing entirely. This guide fills that gap with Seam, which is unusually useful here because one API covers dozens of device brands — so a single MCP server gives Claude visibility into almost every device in your portfolio.
What you'll be able to ask Claude
Natural-language queries against your entire device fleet:
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 your first query
If you're on the sandbox workspace, your first test can hit virtual devices:
Then try a create operation: "Create an access code 1234 named 'Test Stay' on the first lock, active right now through tomorrow at noon." Claude will call create-access-code and return the new code object.
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.
What's next
Guide 2 of the series. If you haven't connected your PMS yet, do OwnerRez next. Then layer on Hospitable if you're on that platform instead.