Armature analytics wrapper SDK for Python MCP servers.
Project description
armature-mcp-analytics
Armature analytics for Python MCP servers. Drop in the FastMCP wrapper, keep writing normal tools, and get Armature events for who called each tool, what the agent was trying to do, and where calls failed.
The Python SDK is FastMCP-first and supports both common import paths:
from fastmcp import FastMCPfrom mcp.server.fastmcp import FastMCP
It also exposes lower-level recorder and dispatcher primitives for custom MCP servers.
Getting Started
Cloud: sign in at app.armature.tech, create a server, and copy the ingest API key.
Install the SDK in your MCP server environment:
pip install "armature-mcp-analytics[fastmcp]"
Use the mcp extra instead if your server imports FastMCP from the official MCP
Python SDK:
pip install "armature-mcp-analytics[mcp]"
Install both extras when a repo supports either import path:
pip install "armature-mcp-analytics[fastmcp,mcp]"
Wrap your FastMCP server before registering tools:
import os
from fastmcp import FastMCP
from armature_mcp_analytics import instrument_fastmcp
mcp = FastMCP("Customer MCP")
analytics = instrument_fastmcp(
mcp,
{
"armature": {
# endpoint_url / api_key default to env vars
"api_key": os.getenv("ANALYTICS_INGEST_API_KEY"),
"delivery": "await",
}
},
)
@mcp.tool
def lookup_customer(customer_id: str) -> dict:
"""Look up a customer by id."""
return {"customer_id": customer_id, "status": "active"}
if __name__ == "__main__":
mcp.run()
The same wrapper works with the SDK-integrated FastMCP:
from mcp.server.fastmcp import FastMCP
from armature_mcp_analytics import instrument_fastmcp
mcp = FastMCP("Customer MCP")
instrument_fastmcp(mcp, {"armature": {"delivery": "await"}})
That's it. Tools registered after instrument_fastmcp(...) are decorated and
their calls are recorded.
Want an agent to wire this into a repo? Point it at
SKILL.md. The playbook tells it how to detect the FastMCP import path, where to place the wrapper, and how to verify that telemetry is really emitted.
How It Works
Three things happen on every instrumented tool call:
- The agent sees a
telemetryblock added to the tool input schema withintent,context, andfrustration_level. The block is optional. - Your handler sees its original args. The SDK strips
telemetrybefore invoking your function. - An authenticated batch is POSTed to Armature with timing, status,
input/output previews, and whatever telemetry the agent supplied. The first
call for a session id also emits
session_init.
Telemetry is observability, not auth. Keep your existing MCP auth/authorization checks in place.
Integration Shapes
FastMCP decorator
Use instrument_fastmcp(mcp, config) for servers that use @mcp.tool or
@mcp.tool(...). Instrument the server before the tool decorators run:
from fastmcp import FastMCP
from armature_mcp_analytics import instrument_fastmcp
mcp = FastMCP("Orders MCP")
instrument_fastmcp(mcp, {"armature": {"delivery": "await"}})
@mcp.tool(name="lookup_order")
async def lookup_order(order_id: str) -> dict:
return {"order_id": order_id}
instrument_fastmcp is idempotent. Calling it twice on the same server returns
the existing instrumentation instead of double-wrapping tools.
Lower-level recorder / dispatcher
For custom JSON-RPC dispatchers or servers that do not use FastMCP decorators,
use create_analytics_recorder():
from armature_mcp_analytics import create_analytics_recorder
analytics = create_analytics_recorder({"armature": {"delivery": "await"}})
async def lookup_customer(args, context):
return {"customer_id": args["customer_id"]}
analytics.tool(
{
"name": "lookup_customer",
"description": "Look up a customer by id.",
"inputSchema": {
"type": "object",
"properties": {"customer_id": {"type": "string"}},
"required": ["customer_id"],
},
},
lookup_customer,
)
# In tools/list:
tools = analytics.tool_definitions()
# In tools/call:
result = await analytics.dispatch(
"lookup_customer",
{"customer_id": "cus_123", "telemetry": {"intent": "find customer"}},
{"sessionId": "session_123"},
)
Pass the MCP session id, request id, headers, auth info, and client info in the dispatch context when your server has them. That improves session grouping, actor attribution, and client attribution in Armature.
Configuration
config = {
"armature": {
"endpoint_url": "https://app.armature.tech/api/mcp-analytics/ingest",
"api_key": "...",
"actor_id": "stable-user-or-tenant-seed",
"enabled": True,
"delivery": "await", # "background" or "await"
"timeout_ms": 500,
"emit": None, # optional test/custom emitter
"on_error": None, # optional delivery error hook
}
}
CamelCase aliases are also accepted for JS parity:
endpointUrl, apiKey, actorId, timeoutMs, and onError.
Delivery mode. "background" schedules delivery on the running event loop
and returns the tool result immediately. Use it for long-lived processes and
call await analytics.recorder.flush() at shutdown. "await" waits for the
batch delivery attempt before returning and is the safer default for serverless
or short-lived request handlers.
Actor id. By default the SDK derives an actor seed from MCP authInfo
(token, clientId, apiKey, or principalId), then the Authorization
header, then "anonymous". Pass armature.actor_id as a string or function to
control the seed:
def actor_id(input):
return input.get("authInfo", {}).get("principalId", "anonymous")
instrument_fastmcp(mcp, {"armature": {"actor_id": actor_id}})
Missing API key. If no API key is configured, delivery silently no-ops. This is intentional for local development.
Auth. Each batch is POSTed with Authorization: Bearer <api_key>. Server
identity is resolved from the API key.
Environment Variables
| Variable | Purpose |
|---|---|
ANALYTICS_INGEST_API_KEY |
Armature ingest API key. Missing keys no-op for local development. |
ANALYTICS_INGEST_URL |
Optional ingest endpoint override. Defaults to https://app.armature.tech/api/mcp-analytics/ingest. |
Verification
Do both checks when installing the SDK into a server:
- Schema decoration: start the MCP server or call its tool-listing helper
and confirm at least one tool has
telemetryin its input schema. - Batch emission: configure
armature.emitto capture a batch, invoke a tool with{"telemetry": {"intent": "test"}}, and assert atool_callevent is captured with that intent.
Example local capture:
import asyncio
from fastmcp import FastMCP
from armature_mcp_analytics import instrument_fastmcp
batches = []
mcp = FastMCP("Analytics smoke test")
instrumentation = instrument_fastmcp(
mcp,
{
"armature": {
"delivery": "await",
"actor_id": "smoke-test",
"emit": batches.append,
}
},
)
@mcp.tool
def ping(message: str) -> dict:
return {"message": message}
async def main():
await ping(message="hello", telemetry={"intent": "verify analytics"})
await instrumentation.recorder.flush()
assert batches[0]["events"][0]["kind"] == "tool_call"
assert batches[0]["events"][0]["metadata"]["intent"] == "verify analytics"
asyncio.run(main())
A passing import or type check is not enough; verify both schema decoration and batch emission.
Python vs. JavaScript SDK
The Python SDK covers the most common Python MCP framework path today:
FastMCP, including both the standalone fastmcp package and the official MCP
SDK import path. It also includes recorder/dispatcher primitives for custom
servers.
The JavaScript SDK currently has additional adapters for JS-specific shapes,
including Mastra and stateless HTTP helpers. Those do not apply directly to
Python. If your Python server has a custom stateless HTTP transport, pass stable
sessionId, clientInfo, headers, and auth info into the recorder/dispatcher
context yourself so Armature can group sessions correctly.
More
- Official MCP SDK support: install with
pip install "armature-mcp-analytics[mcp]"and usefrom mcp.server.fastmcp import FastMCP. - Custom integrations: use
create_analytics_recorder,decorate_input_schema_with_telemetry, andextract_telemetry_argumentsfor non-FastMCP servers. - Support:
hey@armature.techor open an issue.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file armature_mcp_analytics-0.1.2.tar.gz.
File metadata
- Download URL: armature_mcp_analytics-0.1.2.tar.gz
- Upload date:
- Size: 17.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3500ccc580eb4d7bf495b63dc383ce437223e6ff2b8cd3737e92e49e95069cfe
|
|
| MD5 |
87efe493d29c3fe9a4b831844f8fdce6
|
|
| BLAKE2b-256 |
ea3bdcadda19c368786fe7af8c16675fb518355c21d52b420166273cc7f5c7a5
|
Provenance
The following attestation bundles were made for armature_mcp_analytics-0.1.2.tar.gz:
Publisher:
publish.yml on armature-tech/mcp-analytics-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
armature_mcp_analytics-0.1.2.tar.gz -
Subject digest:
3500ccc580eb4d7bf495b63dc383ce437223e6ff2b8cd3737e92e49e95069cfe - Sigstore transparency entry: 2060154802
- Sigstore integration time:
-
Permalink:
armature-tech/mcp-analytics-python@d489ea6ca1f2e5649b63830638f0ffa5651f49a9 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/armature-tech
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d489ea6ca1f2e5649b63830638f0ffa5651f49a9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file armature_mcp_analytics-0.1.2-py3-none-any.whl.
File metadata
- Download URL: armature_mcp_analytics-0.1.2-py3-none-any.whl
- Upload date:
- Size: 18.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d6d4aefa57fa636bd6255831bd65f1437c7e04d6f82b59ec53d5bd527c0068f9
|
|
| MD5 |
e3f073a5f91d906e41b7200f488ff82b
|
|
| BLAKE2b-256 |
e2648a6accf6a6c149ba12cd098f686eaae55a98d1b82394a2a1b6e98e82fdfa
|
Provenance
The following attestation bundles were made for armature_mcp_analytics-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on armature-tech/mcp-analytics-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
armature_mcp_analytics-0.1.2-py3-none-any.whl -
Subject digest:
d6d4aefa57fa636bd6255831bd65f1437c7e04d6f82b59ec53d5bd527c0068f9 - Sigstore transparency entry: 2060155067
- Sigstore integration time:
-
Permalink:
armature-tech/mcp-analytics-python@d489ea6ca1f2e5649b63830638f0ffa5651f49a9 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/armature-tech
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d489ea6ca1f2e5649b63830638f0ffa5651f49a9 -
Trigger Event:
push
-
Statement type: