做过企业自动化的人应该都有体会:业务系统越老,越难接进新平台。我在帮一个客户把 n8n 和他们的核心业务系统打通时,就遇到了 FileMaker。这个数据库在上面跑着十几年的客户资料、报价记录、财务对账,但 n8n 的节点列表里根本没有 FileMaker 官方节点。我一开始也以为要自己从零写一个符合 n8n 规范的自定义节点,结果研究了一天后发现,根本不用那么复杂——FileMaker 自带一套完整的 Data API,n8n 的 HTTP Request 节点就能把它吃进来,再配合 AI Agent 节点,就能让大模型直接查询和操作 FileMaker 里的数据。这篇文章就把整个接入思路、代码封装、以及我在实际项目里踩过的坑完整记录下来,给同样需要把 n8n 智能体和 FileMaker 这类垂直数据库打通的朋友一个可参考的路线。
1. 为什么我在 n8n 里接 FileMaker:没有官方节点反而是一条更稳的路
很多人第一反应是:n8n 没有 FileMaker 节点,是不是就不能用了?其实这正是我一开始的误区。n8n 的节点生态的确很丰富,但没有任何一个平台的官方集成能覆盖所有私有化软件。 真正务实的做法是看目标系统有没有暴露 API,而不是看 n8n 有没有现成节点。
FileMaker 在这一点上做得相当好。从 FileMaker 16 开始,FileMaker Server 就内置了 Data API,只需要在服务器管理后台启用,就能通过 HTTP 调用数据库的增删改查,甚至执行 FileMaker 脚本。这意味着:
- 不需要在 FileMaker 服务器上安装额外插件;
- 不需要开放的 ODBC/JDBC 端口;
- 只需要一个能访问 FileMaker Server 的 HTTP 地址和一组账号密码。
对比一下其他方案:ODBC/JDBC 在公网环境下暴露 2399 端口本身就是安全隐患,而且 n8n 对 JDBC 的支持很弱,几乎只能靠 Code 节点硬写;而 Data API 走的是标准的 443 HTTPS 端口,认证方式也很简单,非常适合 n8n 这种以 HTTP 为中心的自动化平台。
我在项目中遇到的实际场景是这样的:客户销售团队用 FileMaker 管理客户和订单,管理层希望在钉钉群里直接问"上海地区的未回款订单有多少",或者"张伟名下有多少个活跃客户"。这些数据都在 FileMaker 里,没有现成的报表接口,更不可能让 AI 直接连数据库。于是我决定在 n8n 里构建一个智能体工作流:AI Agent 收到问题后,把意图解析成"查询客户"或"查询订单"的动作,再通过这些动作去调用 FileMaker Data API,最后把结果用自然语言返回给用户。
这个方案里,FileMaker 并不需要以"官方节点"的形式存在,它只需要成为一个大模型可以调用的工具。 而 n8n 的 Workflow Tool 和 Code Tool 恰恰就是干这个的。所以整篇文章的落点,不是教你怎么写一个标准化的 n8n 自定义节点,而是教你怎么通过 API 封装 + 工具描述,让 FileMaker 在你的 n8n 智能体里变得像本地节点一样好用。
如果你只是想跑通一个最简单的查询,看完第 3 节就够了;如果目标是让 AI Agent 自己决定什么时候查 FileMaker、怎么查,那你需要重点看第 4 节。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. FileMaker Data API 的 15 分钟 Token 和三类核心调用
要把 FileMaker 接进 n8n,先得吃透它 Data API 的调用方式。我最初上手时以为它和普通的 REST API 一样,用 API Key 或者 OAuth 认证,结果发现完全不是——它采用的是"先获取临时会话 Token,再带 Token 访问资源"的模式。
2.1 会话认证:Token 的获取与过期机制
获取 Token 的端点是:
code复制POST /fmi/data/vLatest/databases/{数据库名}/sessions
请求体是 JSON:
json复制{
"userName": "n8n_service",
"password": "your_password"
}
请求成功后,响应体大致长这样:
json复制{
"response": {
"token": "a1b2c3d4e5f6..."
},
"messages": [
{
"code": "0",
"message": "OK"
}
]
}
拿到这个 token 之后,后续所有请求都在 HTTP 头里带上:
code复制Authorization: Bearer a1b2c3d4e5f6...
这里有个特别容易踩坑的细节:Token 默认有效期只有 15 分钟。 如果你在 15 分钟内没有再次调用该 Token,它就失效了。但 FileMaker Server 允许在配置里调整这个时间,最长可以设到 60 分钟。即便如此,对于一个 AI Agent 来说,多轮对话可能持续超过 15 分钟,那你不能简单地"获取一次 Token 然后复用到底",而需要在每次请求前都检查 Token 是否过期,或者干脆每次请求都重新获取。
我一开始偷懒,把这个 Token 存在 n8n 的静态变量里,结果智能体运行了不到十几分钟就开始报 401。后来我改用了一种更稳妥的方式:写一个统一的请求函数,每次执行时都先尝试用现有 Token 请求,如果收到 401 或 FileMaker 返回 952 错误码,就重新登录获取新 Token,再重试一次请求。这个思路在后文封装工具时会具体展开。
2.2 读取布局数据:查询的骨架
FileMaker 的数据是存在"布局"(Layout)里的,Data API 不能直接查底层表,只能查询布局。这在刚开始可能让人不习惯,但好处是:你可以为 API 调用专门设计一个布局,只暴露需要的字段,天然做了数据隔离。
读取记录的端点:
code复制GET /fmi/data/vLatest/databases/{数据库名}/layouts/{布局名}/records?_limit=50&_offset=0
返回格式:
json复制{
"response": {
"data": [
{
"fieldData": {
"客户名称": "上海华诚贸易",
"联系人": "张伟",
"回款状态": "未回款"
},
"recordId": "123",
"modId": "7"
}
],
"dataInfo": {
"foundCount": 18,
"returnedCount": 50,
"totalRecordCount": 18,
"offset": 1
}
}
}
注意:FileMaker Data API 返回的 JSON 字段名,和你 FileMaker 布局里的字段名完全一致,甚至大小写、空格都会被保留。 我在做字段映射时曾经因为"客户名称"和"客户名称 "(后面带了个空格)对不上,排查了半天。
除了简单读取,Data API 还支持通过 _find 接口做条件查询:
code复制POST /fmi/data/vLatest/databases/{数据库名}/layouts/{布局名}/_find
请求体:
json复制{
"query": [
{
"客户名称": "==上海华诚贸易"
}
],
"limit": 10,
"offset": 1,
"sort": [
{
"fieldName": "记录创建时间",
"sortOrder": "descend"
}
]
}
这里 == 表示精确匹配,不加运算符则默认是包含匹配。这个 _find 接口是我在智能体场景里用得最多的,因为大模型提问时通常会带上筛选条件,比如"查询所有回款状态为未回款且金额大于一万的客户",你就需要把这些条件翻译成 FileMaker 的 find 请求。
2.3 执行 FileMaker 脚本:Data API 的隐藏王牌
Data API 最大的亮点之一,是可以在请求 URL 或请求体里指定要执行的 FileMaker 脚本:
code复制GET /fmi/data/vLatest/databases/{数据库名}/layouts/{布局名}/records?_limit=10&script.name=查询客户余额&script.param=10000
也可以把脚本名放在 POST 的 JSON 请求体里:
json复制{
"query": [
{
"客户状态": "==活跃"
}
],
"script": {
"name": "记录日志",
"param": "AI 助手查询了客户数据"
}
}
这意味着你可以在 FileMaker 端做一部分复杂的业务逻辑,而不是把所有逻辑都堆在 n8n 里。比如"计算客户当前总欠款"这种需要遍历多个关联表的逻辑,在 FileMaker 脚本里可能几行就解决了,通过脚本参数传入客户 ID,脚本执行完把结果写到某个全局字段,或者直接通过脚本返回值带出来。这是我后来特别喜欢的一种模式:让 FileMaker 做它擅长的事,让 n8n 做流程编排,让 AI 做意图理解。
不过要提醒的是,Data API 里脚本返回结果的解析方式和普通字段不一样,脚本返回的值会出现在响应体顶层的 scriptResult 字段里。如果你期待它像字段数据一样出现在 data 数组里,那大概率会踩坑。
3. 用 HTTP 请求先把 FileMaker 数据拉进 n8n 工作流
在对接 AI Agent 之前,我建议先把最基础的链路跑通:n8n 发出 HTTP 请求,成功从 FileMaker 拿到数据,并且在 n8n 的编辑器里能看到返回的 JSON。这一步走通之后,后面所有封装都是水到渠成的事。
3.1 第一步:用两个 HTTP Request 节点完成登录和查询
最简单的方式,是在 n8n 工作流里放两个 HTTP Request 节点:
第一个节点负责获取 Token:
- Method:
POST - URL:
https://你的FileMaker域名/fmi/data/vLatest/databases/CRM/sessions - Body:
{"userName":"n8n_service","password":"****"} - Options 里勾选
Send Body为 JSON - Response Format 选 JSON
运行后,你会得到一个 JSON 响应,里面包含 response.token。记住这个路径。
第二个节点负责查询数据:
- Method:
GET - URL:
https://你的FileMaker域名/fmi/data/vLatest/databases/CRM/layouts/客户列表/records?_limit=10 - Header 里新增一个
Authorization,值为Bearer {{$json.response.token}}(这里引用第一个节点的输出) - Query Parameters 可以根据需要填写
这两个节点串联起来,你就能看到 FileMaker 返回的数据了。但这种方式有两个问题:
- 如果 Token 过期了,第二个节点会直接报错;
- 后续其他节点想复用这个 Token,得反复引用第一个节点的输出,链路会很乱。
所以我更推荐用 Code 节点来做封装。
3.2 更稳的做法:用 Code 节点封装一个 FileMaker 请求函数
n8n 的 Code 节点支持 JavaScript,并且提供了 this.helpers.httpRequest 方法。你可以在 Code 节点里写一个通用的请求函数,把登录、请求、重试逻辑全部包进去。我实际项目里用的封装大致长这样:
javascript复制// n8n Code 节点示例:封装 FileMaker Data API 请求
const baseUrl = 'https://fm.example.com/fmi/data/vLatest/databases/CRM';
const credentials = {
userName: 'n8n_service',
password: 'your_password'
};
let token = '';
async function getToken() {
const res = await this.helpers.httpRequest({
method: 'POST',
url: `${baseUrl}/sessions`,
body: credentials,
json: true
});
return res.response.token;
}
async function fmRequest(method, path, body) {
// 首次请求尝试带现有 token
try {
return await this.helpers.httpRequest({
method,
url: `${baseUrl}${path}`,
headers: {
Authorization: `Bearer ${token}`
},
body: body || undefined,
json: body !== undefined
});
} catch (error) {
// 如果是 401 或 token 失效,重新登录再试一次
if (error.httpCode === 401) {
token = await getToken();
return await this.helpers.httpRequest({
method,
url: `${baseUrl}${path}`,
headers: {
Authorization: `Bearer ${token}`
},
body: body || undefined,
json: body !== undefined
});
}
throw error;
}
}
// 示例:查询最近 10 条客户记录
const records = await fmRequest('GET', '/layouts/客户列表/records?_limit=10');
return records.response.data.map(item => item.fieldData);
这个函数的优势在于:
- Token 自动续期:遇到 401 就重新登录;
- 一次封装,到处调用:后续在工作流里加任何 FileMaker 节点,只要复制这个函数改请求路径就行;
- 输出干净:直接把
fieldData数组返回给后续节点,而不是把整个 JSON 都丢给智能体。
3.3 分页遍历:让查询不再局限于 50 条
很多人第一次跑通查询后,发现 FileMaker 默认每页只返回 50 条记录。如果客户表有几千条记录,你不可能只在智能体里返回前 50 条。
FileMaker Data API 返回的 dataInfo 里有几个关键字段:
| 字段 | 含义 |
|---|---|
foundCount |
当前查询命中的记录总数 |
returnedCount |
本页实际返回的记录数 |
offset |
当前页的起始位置,从 1 开始 |
你要做的就是循环请求,每次把 offset 加上 returnedCount,直到返回的条数少于请求的 _limit,或者累计到达 foundCount。在 n8n 里,我一般直接在 Code 节点里写循环,一来避免 Workflow 里挂着十几个节点,二来循环的终止条件更好控制。
javascript复制async function fetchAll(initialPath, limit = 100, maxRecords = 5000) {
const allRecords = [];
let offset = 1;
let fetched = 0;
while (offset <= maxRecords) {
const path = `${initialPath}&_limit=${limit}&_offset=${offset}`;
const res = await fmRequest('GET', path);
const items = res.response.data || [];
allRecords.push(...items.map(i => i.fieldData));
fetched += items.length;
if (items.length < limit || fetched >= res.response.dataInfo.foundCount) {
break;
}
offset += items.length;
}
return allRecords;
}
return await fetchAll('/layouts/客户列表/records?');
这里有个细节:不要把 maxRecords 设得太大,否则一个大模型提示词里塞几千条字段数据,既浪费 Token,又容易让模型回答跑偏。 我的经验是,智能体查询场景里,默认只查前 20~50 条就够了。真正需要全量导出的场景,应该走定时同步任务,而不是让 AI 在线查。
4. 把 FileMaker 能力包装成 AI 工具:Agent 多轮对话也能稳定调用
基础链路打通后,重头戏来了:怎么让 n8n 的 AI Agent 在对话中自主决定调用 FileMaker,而不是靠人手动触发。
4.1 n8n 智能体的基本架构
n8n 的 AI Agent 工作流通常包含三个部分:
- Chat Trigger:接收用户消息;
- Agent 节点:配置 LLM(模型)、Memory(记忆)、Tools(工具);
- 各种 Tool 节点:Agent 在执行过程中按需调用。
在 n8n 中,工具可以是 Workflow Tool、Code Tool,也可以是你自己开发的自定义 Tool。对 FileMaker 来说,最实用的方案是 Code Tool——把上一节封装的代码直接变成一个工具,让 Agent 调用。
4.2 用 Code Tool 暴露查询能力
添加一个 Code Tool 节点,名称可以叫 FileMaker 客户查询。它的输入参数,就是大模型决定调用它时传递的参数。你需要在节点描述或配置里明确告诉大模型:
- 这个工具是干什么的;
- 需要哪些参数;
- 参数的含义和示例值。
比如我配置的 Code Tool 描述:
根据客户名称或地域查询 FileMaker CRM 中的客户资料。当用户提到客户名字、公司名、城市时应该调用此工具。
参数:customerName(可选,客户名称,支持模糊匹配)、city(可选,城市名称,精确匹配)
返回客户的联系人、回款状态、客户等级等信息。
然后在这个 Code Tool 里写:
javascript复制// Code Tool 实现
const customerName = $input.first().json.query.customerName || '';
const city = $input.first().json.query.city || '';
// 构建 FileMaker 查询条件
const findBody = {
query: []
};
if (customerName) {
findBody.query.push({ '客户名称': `*${customerName}*` });
}
if (city) {
findBody.query.push({ '城市': `==${city}` });
}
if (findBody.query.length === 0) {
findBody.query.push({}); // 查全部
}
// 调用封装的 FileMaker 请求函数
const records = await fmRequest('POST', '/layouts/客户列表/_find', findBody);
return records.map(item => {
const f = item.fieldData;
return {
客户名称: f['客户名称'],
联系人: f['联系人'],
城市: f['城市'],
回款状态: f['回款状态'],
客户等级: f['客户等级']
};
});
这里要特别提醒:
- Code Tool 的输入格式通常是
{ "query": { ... } },大模型生成的参数会放在query对象里; - 返回值一定是数组,因为 n8n 会把 Tool 的输出当作一组 items 返回;
- 不要把原始
fieldData全部返回,只返回大模型回答问题真正需要的字段,否则上下文会被无关字段撑爆。
4.3 工具描述怎么写,大模型才不会乱来
我在试验中发现,工具的调用效果,一半靠代码,一半靠描述。描述写不好,大模型要么不调用,要么传错参数。几个关键的编写技巧:
| 编写要点 | 说明 |
|---|---|
| 说清楚什么时候用 | 在描述里标注"当用户提到客户名字、公司名、城市时应该调用此工具" |
| 给参数范围 | 列出可选参数,标注哪些是必填、哪些可选 |
| 给示例值 | 写"例如 customerName='上海华诚'"比空泛的描述有效得多 |
| 说明输出字段含义 | 让大模型知道返回的是什么,它才能组织好回答 |
我一开始在描述里只写了"查询 FileMaker 客户资料",结果大模型经常不传任何参数就直接调用,返回了前 50 条客户数据,回答质量很差。后来我加了一句"必须根据用户问题提取至少一个筛选条件,如果用户没有给明确条件,请先询问",情况立刻好转。
4.4 多轮对话中的 Token 与状态维护
AI Agent 是多轮对话的,这意味着 FileMaker 的 Token 管理不能依赖"每次工作流开始时获取一次"。我的做法是利用 n8n 的 Workflow Static Data 或者用 Code 节点 + 全局变量缓存 Token。
最简单的实现:在 Code Tool 里先尝试用一个缓存的 Token 请求,如果 401 再重新登录,并将新 Token 写回缓存。n8n 中可以通过 $getWorkflowStaticData('global') 来读写静态数据:
javascript复制const staticData = $getWorkflowStaticData('global');
let token = staticData.fmToken || '';
async function requestWithRetry(path, method, body) {
// 尝试用现有 token 请求
try {
return await doRequest(path, method, body, token);
} catch (e) {
if (e.httpCode === 401) {
token = await getToken();
staticData.fmToken = token;
return await doRequest(path, method, body, token);
}
throw e;
}
}
不过要注意:16 分钟无请求后 FileMaker 会回收会话,即便 Token 还在,也会失效。 所以缓存的 Token 只能减少重复登录,不能完全避免重新登录。好在 FileMaker Data API 登录成本很低,重试机制才是确保稳定的核心。
4.5 一个完整的智能体场景演示
我把整套流程串起来后,实际效果是这样的:
用户在钉钉群里问:"帮我查一下上海的未回款客户有哪些?"
工作流的处理过程:
- Chat Trigger 接收消息;
- Agent 节点(GPT-4o)通过工具描述判断需要调用"FileMaker 客户查询"工具;
- Agent 生成参数
{ "city": "上海", "回款状态": "未回款" }; - Code Tool 收到参数,构造 FileMaker
_find请求; - FileMaker Data API 返回符合条件的记录;
- Code Tool 把字段精简后返回给 Agent;
- Agent 根据返回结果生成自然语言回答:"上海共有 5 个未回款客户,其中金额最大的是上海华诚贸易,联系人张伟……"
整个过程只需要几个节点,FileMaker 那边也没有任何额外开发,只是把布局和账号准备好了。
5. 踩坑记录:认证超时、日期格式、错误码与反向 Webhook
最后这部分,分享一些我在实际项目中遇到的高频问题。这些问题在文档里不一定查得到,但几乎每次上线都会遇到。
5.1 错误码 952:Token 失效的典型信号
FileMaker Data API 的错误码和常规 HTTP 状态码不是一回事。比如 Token 失效时,HTTP 状态码可能是 401,但响应体里的 messages[0].code 是 952,message 是 The session has expired.。
在代码里,我建议同时判断 HTTP 状态码和业务错误码:
javascript复制if (error.httpCode === 401 || (error.response && error.response.messages && error.response.messages[0].code === '952')) {
// 重新登录
}
如果只判断 HTTP 状态码,有时候会遇到 FileMaker 网关层返回的其他 401 情况,重试逻辑就会误触发。
5.2 日期和时间字段的格式坑
FileMaker 的日期字段、时间字段、时间戳字段在 Data API 返回的格式都不一样:
- 日期字段:
"04/21/2025" - 时间字段:
"14:30:00" - 时间戳字段:
1577836800(Unix 秒级)
如果直接把日期字符串扔给大模型,它能看懂,但如果你需要在 n8n 里做日期计算(比如判断"是否超过回款期限"),就要先把 MM/DD/YYYY 转换成标准格式。我写了一个小的转换函数:
javascript复制function parseFMDate(dateStr) {
// FileMaker 返回 MM/DD/YYYY
const [month, day, year] = dateStr.split('/');
return new Date(`${year}-${month}-${day}`);
}
另外,FileMaker 对空日期返回空字符串,而不是 null,逻辑判断时要特别处理。
5.3 字段名称的精确匹配问题
前面提过,Data API 返回的 JSON 字段名跟布局字段名一模一样,包括大小写和空格。FileMaker 字段名允许包含空格,而且不区分大小写(但 JSON 键名区分大小写)。为了避免出错,我建议在 FileMaker 端建一个"API 专用布局",把字段重命名为全英文、无空格的名称,比如 CustomerName、City、PaymentStatus,这样在 n8n 和 LLM 工具描述里引用时都不容易出错。
如果没有条件改布局,那在代码里引用字段时一定要从返回结果里实际确认键名,不要凭记忆写。我就是凭记忆写了 联系人,结果实际键名是 联系人 (带空格),花了一个小时排查。
5.4 CORS、HTTPS 与代理问题
如果 n8n 和 FileMaker Server 不在同一个内网,通信链路会涉及到 CORS 和 HTTPS 证书。n8n 的 Code 节点请求默认走 Node.js 的 HTTP 客户端,不受浏览器 CORS 限制,所以 CORS 通常不是问题。我更想提醒的是:
- FileMaker Data API 必须是 HTTPS 访问,如果你的 FileMaker Server 还没配证书,那么 n8n 请求一定会失败;
- 如果 n8n 服务器和 FileMaker 之间隔着防火墙,记得放行 443 端口;
- 不要在 n8n 工作流里硬编码 FileMaker 用户名密码,尽量用 n8n 的 Credentials 功能存起来,然后在 Code 节点里通过
$credentials引用。
5.5 反向集成:让 FileMaker 脚本主动触发 n8n 工作流
除了让 n8n 主动查询 FileMaker,很多业务场景还需要实现反向:用户在 FileMaker 客户端点了某个按钮后,触发 n8n 工作流跑一段自动化。
这个其实很简单,因为 n8n 自带 Webhook 节点。只需要在 n8n 里创建一个带 Webhook 入口的工作流,拿到 Webhook URL,然后在 FileMaker 脚本里用 Insert from URL 脚本步骤,发送一个 POST 请求到这个 URL 即可。
FileMaker 的脚本步骤大概是这样:
code复制Insert from URL [ Select ; With dialog: Off ; $url ;
cURL options: "" ;
cURL options SSL/TLS: "Verify Certificate" ]
不过这个反向链路有个坑:FileMaker 的 Insert from URL 默认发送的请求头可能不带 Content-Type: application/json,n8n 的 Webhook 节点如果不做处理,解析不到 JSON 请求体。解决方法是,在插入 URL 时明确指定 Header,或者在 FileMaker 里先拼好 JSON 字符串,再把内容放到请求体变量里。
我在实际项目里,用这个反向链路做了"在 FileMaker 中点击按钮 -> 触发 n8n -> 调用外部 API -> 将结果写回 FileMaker"的流程。这个模式下,n8n 更像一个中间调度平台,FileMaker 仍然是业务系统的前台,但所有的外部连接能力都通过 n8n 得到了增强。
后续可以怎么扩展
如果你照着上文跑通了 FileMaker 查询,下一步可以尝试的方向是:把 FileMaker 的创建、修改、删除操作也封装成独立的 Code Tool,让 AI Agent 不仅能查数据,还能在得到授权后写入数据。不过写操作一定记得在 FileMaker 端做好权限控制,给 n8n 专用的账号分配最小必要权限,并且在 FileMaker 脚本里加操作日志,这样才能在出现误操作时快速回溯。我自己在线上环境中,写操作默认都是走"先查询确认 -> 再提交脚本"的两步式设计,目前运行了几个月没有出过安全问题。n8n 和 FileMaker 的组合,表面上看是两个不同生态的拼凑,但只要接得稳,它们配合起来能应付不少复杂业务。
