Skip to main content
Glama
graysonlevino

Addepar MCP Server

Addepar MCP Server

只读 MCP 服务器,向 Claude 暴露 Addepar 的投资组合和所有权数据。

为在注册投资顾问处的财务报告而构建。治理原则按优先级排列:可靠性、准确性、安全性,最后是便利性。


此服务器保证什么

保证任何数字在现实世界中是正确的。这不是任何工具都能诚实承诺的,因为 Addepar 本身就带有陈旧标记:按“截至今日”运行的查询通常会返回一个标记为几周前的值,因为私募基金按季度估值。

它保证的是对来源的完全诚实:

  • 它从不凭空捏造数字。

  • 它从不静默丢弃数据。

  • 它始终说明自己所不知道的。

这里刻意没有置信度评分。标注“94% 置信”的数字是虚假精度,而在合规场景中,虚假精度比毫无用处更糟。相反,每个响应都带有一个结构化的 caveats 数组,当结果干净时该数组为空,因此“为空”本身是一种肯定性陈述,而非未做检查。

null 规则

null 值和 0.0 值是不同的事实,绝不会被合并。

在实时数据中得到确认,同一家庭中的两个持仓:

Position

Value

Meaning

Leslie A Dahl, WRD Capital

0.0

Addepar 计算出了一个值,且该值为零

W Robert Dahl, Goldman Sachs -400P

null

未计算出任何值,原因未说明

将该 null 强制转换为零并求和,会产生一个自信地错误且看起来完全合理的总额。因此,null 被排除在求和之外,被计数,并在 NULL_VALUES_EXCLUDED 中被点名。

警示代码

Code

Raised when

STALE_VALUATION

某个持仓的估值日期早于请求日期超过 35 天

NULL_VALUES_EXCLUDED

一个或多个持仓未返回任何计算值

AMBIGUOUS_MATCH

查找发现了多个看似合理的候选

PATTERN_MATCH_USED

使用了名称匹配,而名称匹配本质上不具有穷尽性

DEPTH_CAP_REACHED

遍历提前停止,提示可能存在循环嵌套

MIXED_VALUATION_DATES

某个总额合并了在不同日期估值的数值

RESULT_TRUNCATED

实际行数多于返回的行数;总额仍覆盖全部

UNVERIFIED_CITATION

该对象类型不存在已确认的 UI 链接模式


工具

Tool

Question it answers

resolve_entity

将名称转换为特定的 Addepar ID 和对象类型

get_ownership_rollup

总敞口、定向持仓或实益所有权

get_group_exposure

一组关联基金的整体敞口

get_entity_attributes

某位客户的持仓是如何分类的

list_views

存在哪些已保存的报告

get_view_data

运行公司自己的某个已保存报告

get_commitments

已承诺、已缴付和未缴付的资本

全部七个工具都声明了 read_only_hint=True,因此受信任的客户端可以跳过确认提示。真正的保证是结构性的:见下文。

保持工具数量克制。工具定义会在每次请求时加载到模型的上下文中,因此无论是否使用,每个工具都会消耗 token,而过大的工具面会显著降低工具选择的质量。与其添加一个近乎重复的工具,不如给现有工具增加一个参数。


架构

src/addepar_mcp/
  config.py       Settings from environment. No secrets in code.
  errors.py       Three failure classes. Extends the SDK ToolError.
  models.py       The response contract. Caveats, provenance, disclosure.
  client.py       Read-only HTTP client. Cannot construct a mutating request.
  tree.py         Traversal and null-safe arithmetic. No network dependency.
  citations.py    UI links, only for confirmed URL patterns.
  audit.py        Structured JSON Lines compliance record.
  auth.py         Per-request identity extraction. Entra ready.
  runtime.py      Shared runtime container.
  server.py       Entrypoint, transports, identity middleware.
  tools/          One module per tool, each exposing register(mcp).

添加一个工具意味着新增一个模块,并在 tools/__init__.py 中增加一行。

只读,在代码层面强制

客户端只暴露 getquery,其中 query 是一个 POST,仅限于固定的只读查询端点白名单。不存在任何可以发出 PATCH、PUT 或 DELETE 的代码路径,也无法向任意路径发起 POST。尝试此类操作会抛出 ReadOnlyViolationError

这是有意为之,而非装饰。v1 没有任何写入用例,而且一个改变了客户所有权结构的 bug 是无法完全恢复的。

故障时关闭

在操作中途遇到超时或命中速率限制时,工具会返回错误且不返回任何数据。它们绝不会返回部分树或更小的总额,因为被截断的所有权总额乍一看与正确的总额无法区分。


设置

python -m venv .venv
.venv/bin/pip install -e ".[dev]"
cp .env.example .env      # then fill in credentials
.venv/bin/python -m pytest tests/ -q

通过 stdio 在本地运行:

TRANSPORT=stdio .venv/bin/python -m addepar_mcp.server

通过 HTTP 运行:

TRANSPORT=http HOST=0.0.0.0 PORT=8080 .venv/bin/python -m addepar_mcp.server

健康检查位于 GET /healthz。MCP 端点位于 /mcp


部署与身份验证

通过 HTTPS 进行远程部署,这样只需运维一个实例,而不是每台工作站一个。

身份,及其重要性

两个相互独立的身份层,混为一谈会在日后造成混乱:

  • 调用者身份:谁调用了该工具。按请求提取,写入每一条审计记录。

  • 上游身份:Addepar 自己的日志所显示的内容,即无论谁发起请求,都是同一个服务凭据。

这意味着服务器端的审计日志是“谁查看了客户数据”这一问题的权威答案。Addepar 的日志无法在用户层面佐证它。

两种受支持的模式:

Mode

Per-user attribution

共享组织凭据 (static_headers)

否。 管理员输入一个凭据;每个用户的请求都携带它,所有用户无法区分。

按用户 OAuth

是。 每个用户单独授权,因此请求能识别出他们。

如果合规性需要回答“谁请求了什么”,OAuth 就不是可选项。它是唯一能产生该答案的配置。

对于本次部署,最自然的授权服务器是公司的 Entra ID 租户,因为他们已经在运行 Microsoft 365。这会将访问与实际公司账户绑定,并使其可以通过公司自己的 SSO 进行治理,而不是依赖外部方持有的凭据。

一旦配置好 OAuth,就设置 REQUIRE_AUTH=true。在此之前,服务器会将调用记录为无归属,这虽然诚实,但无法满足按用户审计的要求。

值得提前了解的部署陷阱

  • 托管 Claude 界面的重定向 URI 是 https://claude.ai/api/mcp/auth_callback

  • Anthropic 的出站流量来自 160.79.104.0/21。此服务器以及授权服务器的发现端点都必须能从该网段访问。即使 MCP 服务器本身可访问,身份提供者前面的防火墙也会中断该流程。

  • 使用 Entra ID 时,MCP 服务器 URL 还必须在应用注册中注册为应用程序 ID URI,否则令牌请求会以 AADSTS9010010 失败。

  • Claude 为发现端点和令牌端点预留约 10 秒,为刷新预留 30 秒。缓慢的端点表现为间歇性连接失败,而不是干净利落的错误。

签名验证尚未实现

auth.py 会解码 JWT 声明以供审计之用,但验证签名。在受信任的网络上这是可以接受的,但一旦服务器可被不受信任的调用者访问,就不可接受了。在暴露服务器之前,请用真正的 JWKS 验证(获取租户密钥、验证签名、检查签发者、受众和过期时间)取代它。来自未验证令牌的身份是一种声明,而非事实。这里有意保持明显的未完成状态,而不是用桩代码伪装成已完成。


审计日志

结构化 JSON Lines,每次工具调用写一条对象记录,写入 AUDIT_LOG_PATH。每条记录包含时间戳、工具、调用者身份、参数、结果、耗时、Addepar 请求 ID、涉及的实体、行数、警示代码以及任何错误。

日志必须保存在服务器端。对话记录不是持久性记录:用户可将其删除,而且在某些界面上根本无法归档。如果一次数据访问的唯一痕迹只存在于聊天窗口中,那么从合规角度看,它就不存在。

请在合规性讨论中提出这一点: 这些记录包含实体名称、美元金额和访问模式。因此,日志与底层客户数据处于同一合规边界内,并附带相同的保留和访问问题。尽早决定它是写入自己的存储,还是输出到现有的归档管线,因为同一客户数据的两份存储会使合规面翻倍。


引用

规则:仅当对象类型和 ID 命名空间都已确认时才发出链接;否则,发出可复现的查询。一个自信地错误的引用比没有引用更糟,因为它看起来具有权威性,却会把用户引向错误的地方。

Object

Pattern

Status

实体详情

/app/tools/details/entity/{entity_id}

已确认

实体上的视图

/app/tools/portfolio/entity/{portfolio_id}/view/{view_id}

已确认

组上的视图

/app/tools/portfolio/group/{group_id}/view/{view_id}

推断得出,未发出

持仓详情

未知,可能不存在

未发出

计算得出的聚合值(如总敞口)并非 Addepar 对象,也没有原生 URL。它们的引用方式是深层链接到以同一投资组合为根的已保存视图,使用户进入公司构建且已经信任的报告。

这些模式无法以编程方式验证。Addepar Web 应用是单页应用,会对每个路径(包括故意乱写的路由)都返回 HTTP 200,因此 curl 无法区分有效路由和无效路由。任何新模式都必须由人工从实时 UI 中复制真实 URL 来确认。


已验证的行为

已于 2026-08-26 针对实时租户验证。这些是 tests/test_regression_fixtures.py 中的回归测试固件。如果重构改变了其中任何一项,那么该重构就是错误的,除非有证据证明并非如此。

Assertion

Value

Dahl 家庭总额

486,034,402.38

Loon Point Holdings II LLC,定向,4 处出现求和

21,427,660.34

Pacific Lake 家族,扫描 4,121 个中匹配 6 个

15,229,060.19

Charlotte 在共享 LLC 中的份额

5,356,915.09

真实最大所有权深度

5

公司内的家庭数

9

两项交叉校验使这些数据值得信赖,而不仅仅是记录在案:

  1. 无论通过按 ownership(嵌套的法律层级)分组还是按 security(扁平持仓)分组,家庭总额都完全相同。两种完全不同的查询形态,结果精确到分也一样。

  2. 一个共享 LLC 自身的总额等于四个兄弟信托对其份额之和,从相反的遍历方向得出,且无重复计数。


Addepar API 说明

来之不易的行为记录,以免日后痛苦地重新发现。

  • 每个端点都需要 Addepar-Firm 请求头,包括 /v1/users/me

  • /v1/entities 上的 filter[name] 会被静默忽略。 它不是真正的过滤器:它返回的是任意实体,而不是名称匹配;这比报错更糟,因为它看起来像是成功了。filter[entity_types] 是真实且有效的。

  • 未加过滤器的 GET /v1/entities 返回 400(“cache is not responsible for firm 2142”)。添加任意过滤器参数即可避免。这是 Addepar 侧的 bug,我们选择绕开而不是上报。

  • 名称搜索在 POST /v1/groups/query 上有效,通过 display_names。这是 API 中唯一有效的名称搜索。

  • ownership 分组只遍历法律实体。 它会在持仓账户风格的叶节点处停止,不会下探到其中的证券。对于实际投资条目,请使用 security 分组。

  • 离散过滤器仅支持精确匹配。 没有前缀或子串匹配,因此部分名称会返回零行,而不是模糊匹配结果。

  • 错误信息干净且具体,例如 "Invalid grouping attribute: nonsense_grouping"。它们会被原样转发。

  • 延迟是变化的。 household 汇总通常约 3 秒完成。有一次运行耗时 47.8 秒,而 Addepar 的上限是 60 秒。客户端超时设置为 55 秒,因此会出现清晰的错误,而不是连接在响应中途被切断。

  • 速率限制是全公司范围的,每 15 分钟 50 个请求,每 24 小时 1,000 个请求,与公司内所有其他集成共享。因此,即使活动与本服务器无关,也可能触发限制。

同名冲突

单次会话中就发现了三例,这说明这是数据的固有形态,而非运气不好。

名称

对象

Dahl 2012 Dynasty Trust

PERSON_NODE 实体 31643590 和 TRUST 实体 31643598

Pacific Lake Partners Long-Term Hold Fund One, L.P.

出现两次

Dahl Family

GROUP 3192711 和 HOUSEHOLD 实体 31647552


SDK 版本说明

本说明针对 MCP Python SDK 2.x。如果要移植本组织中的旧代码:

  • FastMCP 现在是 MCPServer,从 mcp.server.mcpserver 导入。

  • ToolAnnotations 字段从 camelCase 改为了 snake_case(readOnlyHint 变成了 read_only_hint)。

  • stateless_httpjson_response 从构造函数移到了 streamable_http_app() 上。

  • 自定义异常必须继承 SDK 的 ToolError。其他任何异常都会被当作崩溃处理,其消息会保留在服务端,因此模型只会收到一个通用失败信息。这会静默破坏任何本应携带信息返回的错误,例如歧义候选列表。


已知缺口

  • get_commitmentsget_entity_attributes 遵循与其他工具相同的已验证模式,但尚未在真实数据上验证过。在早期探测中,Commitment 列返回了 0.0,可能需要该公司保存视图所使用的 period 参数。

  • JWT 签名验证尚未实现。见上文。

  • 组根视图的 URL 模式是推断出来的,刻意不输出。

  • 凭据已在多个工作会话中以明文形式暴露。代码从环境变量读取凭据,因此轮换只是一个配置变更,但轮换本身仍需要在生产使用之前完成。

-
license - not tested
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • Read-only public financial evidence from LiquiLens, Undertow, Seiche and Palimpsest.

  • Read-only access to your VortexIQ store data: audits, KPIs, alerts, Brand DNA, reports, Ask VIQ.

  • Read-only NuMetric.work accounting & ERP data: statements, KPIs, reports, invoices, documents.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/graysonlevino/oakridge-addepar'

If you have feedback or need assistance with the MCP directory API, please join our Discord server