Agent skill
gtmos-source-mapping
Use when the GTM engineer wants data from ANY app connected through Composio (Close, Typeform, Stripe, Intercom, Airtable, a niche CRM...) to land in GTM OS -- as accounts, contacts, deals, signals, activities or a custom object.
Filed under Prospecting and list building.
From ay-automate/gtmos-skills · 107 skill entries · 0 · pushed 2026-11-02
What it does when it runs
Use when the GTM engineer wants data from ANY app connected through Composio (Close, Typeform, Stripe, Intercom, Airtable, a niche CRM...) to land in GTM OS -- as accounts, contacts, deals, signals, activities or a custom object. Covers the definition.source_mapping spec, the preview -> suggest -> define -> publish -> sign -> sync path over MCP/CLI and the console wizard, transforms, pagination, match keys, schedules, write-back, and errors.
Automated analysis of the skill and the 0 files bundled beside it. A skill’s own description is written to be selected by an agent, so it describes the job and not the dependencies.
- Keys and connectors you must supply
- None found.
- Hosts it reaches
- docs.composio.dev
- Tool permissions it declares
- No
allowed-toolsin the frontmatter. It only issues instructions, so there is nothing to bound. - Actions present in the files
- None. Instructions only.
Ask about gtmos-source-mapping
Opens your assistant with this page's recorded context. Check the linked sources before relying on it.
Install it
View source on GitHub ↗git clone --depth 1 --filter=blob:none --sparse https://github.com/ay-automate/gtmos-skills.git /tmp/gtmos-skills git -C /tmp/gtmos-skills sparse-checkout set "skills/gtmos-source-mapping" mkdir -p ~/.claude/skills/gtmos-source-mapping cp -R "/tmp/gtmos-skills/skills/gtmos-source-mapping/." ~/.claude/skills/gtmos-source-mapping/
Picked up without a restart. A project skill of the same name is shadowed by your personal one. For one repository only, swap ~/.claude/skills for .claude/skills. Claude Code docs ↗
Or take the whole library
This repo ships a .claude-plugin manifest, so Claude Code can install all 107 skills at once. Plugin skills are invoked as /<plugin>:<skill>, so they never collide with your own.
/plugin marketplace add ay-automate/gtmos-skills /plugin
The folder is the same in every client that implements the format — 46 of them — so if yours is not above, only the destination changes.
The skill
Source on GitHub ↗Reproduced in full from ay-automate/gtmos-skills/blob/86780d2f545cdc0a1de971917aa04b41bb0b3ccd/skills/gtmos-source-mapping/SKILL.md, which is licensed MIT (repository). 1,476 words, 8 headings.
Map any app into the GTM OS ontology (definition.source_mapping)
What it is. A source mapping says: run this app's LIST tool (through the org's own Composio
connected account), page through it, and write each item into an ontology object -- a canon
gtm.account / gtm.contact / gtm.deal / gtm.signal, gtm.activity, or a custom object
(custom.<key>). It is a governed definition (definition.source_mapping): validated against
the org's live ontology, published through an approval, signed by a human; only a signed
version ever syncs. The five built-in CRMs (HubSpot, Salesforce, Pipedrive, Attio, Zoho) keep their
code-defined path (CRM of record + crm.sync); everything else uses a source mapping. Both share
one page walker and one canon writer.
Prerequisite: the app is connected and ACTIVE (Integrations > Connections > Composio, composio.status).
Data you must BUY (enrichment, emails, intent) is looot's job, not a source mapping.
The path (MCP tools; every step is also in the console)
- Pick the list tool --
gtm_query source.list_tools { toolkit: "close", search: "leads" }(read tools first; with a Composio Connect credential the catalog is not available: use the slug from docs.composio.dev/toolkits/<toolkit>). A list tool must be a READ (list/get/search). - Preview --
gtm_query source.preview { toolkit: "close", listTool: "CLOSE_LIST_LEADS", args: { _limit: 25 } }runs the tool ONCE, live, no writes. ReaditemsPath(where the items are),fields(every path with type / example / presence),suggestedIdFieldandpaginationHints(next cursor, has_more, page_count...). - Pick the target --
gtm_query source.targetslists canon targets with their fields (plus the org's custom fieldscustom.<slug>) and the org's custom objects. Missing one? Create the custom field (field.create, Settings > Fields) or the custom object (ontology.propose_changesetwith acreate_object, the console Model page) FIRST -- the mapping is validated against them. - Suggest --
gtm_query source.suggest_mapping { sample: <preview items>, target: "gtm.contact" }: a deterministic heuristic (names, synonyms, value types; no model needed). It proposesfields,idField,match,modeand listsunmappedRequired. Review it -- it is a draft. - Define the spec (below) in the org repo's
sources.yamlandgtm plan/gtm apply --env dev, or over MCP:gtm_act config.propose_changeset->gtm_query config.validate_changeset->gtm_act config.apply. Definitions never go straight into prod.config.applyis an approval a DIFFERENT member releases; validation issues (unknown field, missing custom object, bad path, unknown transform) come back before anything is applied. Say why: pass ajustification(the MCP argument ofgtm_act, why this mapping and what it brings in, at least 10 characters) on everygtm_actof this path that proposes. Optional in GTM OS 0.2.6; required for agent proposals at tier T2 and above from 0.2.7. Never write a reason you cannot back with a preview or a count. - Sign --
gtm definition sign definition.source_mapping/<key> --env dev(human only; an agent can never sign). Optionallygtm promoteit to prod (again an approval). - Sync --
gtm_act source.sync { mappingKey }enqueues the job (one running per mapping);gtm_query source.sync_status { mappingKey }shows the signed version used, the schedule and each run's counts (created / updated / unchanged / skipped / invalid / unresolved),truncatedand the first errors.gtm_query source.mappingslists them all.
Console: Integrations > Connections > Composio > a connected app > Map to ontology (the same six steps), then the mappings list: Propose for signing, Sign, Promote, Sync now, last run.
From an MCP server (backend "mcp")
Any of the org's own MCP servers (Integrations > Connections > MCP servers, mcp_servers.*) is a
source: source: { backend: mcp, toolkit: <server name>, listTool: <tool name> }. The list tool
must be declared read (mcp_servers.set_tool_effects); source.list_tools {toolkit, backend: "mcp"} lists the server's stored catalog (read tools first) and source.preview {..., backend: "mcp"} runs one page live. A write-back updateTool must be declared write and runs only
through the approval gate (source.update_record).
The spec
kind: definition.source_mapping
key: close-leads-to-contacts
spec:
label: Close leads -> contacts
source:
backend: composio # or "mcp": toolkit = your MCP server name, tools = its tool names
toolkit: close
listTool: CLOSE_LIST_LEADS
args: { query: "status:active" } # static, every page
pagination: { kind: offset, offsetParam: _skip, pageSizeParam: _limit, pageSize: 100, hasMorePath: has_more }
itemsPath: data
maxRecords: 10000 # cap per run -> truncated
# count: { tool: <a READ tool of this toolkit that returns the total>, args: {...}, path: <path to the number> }
# optional (0.2.5): the source's row count for sync.completeness -- only a tool you verified in the toolkit docs
target: { objectType: gtm.contact }
idField: id
fields:
- { from: "contacts[0].name", to: displayName }
- { from: "contacts[0].emails[0].email", to: primaryEmail, transform: [trim, lower] }
- { from: url, to: accountDomain, transform: domain_from_url } # employment link by domain
- { from: custom.Tier, to: custom.tier } # an existing custom field
match: [external_id, email]
mode: mirror # mirror = create + update; append = create only
schedule: { kind: interval, everyHours: 6 } # or { kind: manual }
writeBack: # optional: source.update_record (approval-gated)
updateTool: CLOSE_UPDATE_LEAD
idArg: lead_id
fieldMap: { displayName: display_name }
getTool: CLOSE_GET_LEAD # snapshot -> undoable
getIdArg: lead_id
- Paths:
a.b,a[0].b,a["key.with.dots"];frommay list several paths (an array, thenjoin). - Transforms (chain left to right):
trim,lower,upper,number,boolean,date(ISO / epoch s or ms),domain_from_url(also from an email),first,{ split: "," },{ join: " " },{ const: value }(nofromneeded),{ map: { "Closed Won": won }, default: open }. - Pagination:
none;cursor(cursorParam + nextCursorPath [+ pageSizeParam/pageSize]);offset(offsetParam + pageSizeParam + pageSize [+ hasMorePath]);page(pageParam [+ pageSize, firstPage 0|1, hasMorePath, totalPagesPath]). Argument names may be dotted (paging.after). - Targets and fields: gtm.account (name, domain, website, industry, employeeCount, countryCode),
gtm.contact (displayName, givenName, familyName, primaryEmail, phone, linkedinUrl, countryCode,
accountExternalId, accountDomain), gtm.deal (name, valueAmount, valueCurrency, status open/won/lost,
accountExternalId, accountDomain), gtm.signal (kind, summary, occurredAt, accountDomain, accountName,
contactEmail; append only), plus
custom.<slug>custom fields; a custom object or gtm.activity takes its own field keys (+subject.contactEmail/subject.accountDomainto link a subject). - Match: always the app's record id first (
custom.source = { mappingKey, toolkit, tool, recordId }), then account: domain, name; contact: email; custom object: any mapped field.
Examples
Typeform responses -> gtm.signal (append only; the funnel resolves the contact, never invents one):
source: { backend: composio, toolkit: typeform, listTool: TYPEFORM_LIST_RESPONSES, args: { form_id: "abc123" },
pagination: { kind: page, pageParam: page, pageSizeParam: page_size, pageSize: 100, totalPagesPath: page_count }, itemsPath: items }
target: { objectType: gtm.signal }
idField: token
fields:
- { to: kind, transform: { const: form_submitted } }
- { from: "answers[0].text", to: summary }
- { from: submitted_at, to: occurredAt, transform: date }
- { from: hidden.email, to: contactEmail, transform: lower }
mode: append
schedule: { kind: interval, everyHours: 1 }
Stripe customers -> a custom customer object (create custom.customer with email / name /
balance / created_at first):
source: { backend: composio, toolkit: stripe, listTool: STRIPE_LIST_CUSTOMERS,
pagination: { kind: cursor, cursorParam: starting_after, nextCursorPath: next_cursor, pageSizeParam: limit, pageSize: 100 }, itemsPath: data }
target: { objectType: custom.customer }
idField: id
fields:
- { from: email, to: email, transform: lower }
- { from: name, to: name }
- { from: balance, to: balance, transform: number }
- { from: created, to: created_at, transform: date }
match: [external_id, email]
mode: mirror
schedule: { kind: manual }
(Check the exact cursor field of the tool's response in source.preview's paginationHints -- never guess it.)
Governance you must respect
- Only a signed version syncs; editing the mapping makes a new draft that must be signed again.
- Canon writes respect the org's binding (mirror) mode:
federate/in_zonematerialise nothing;soft_merge/sheetnever overwrite a human-set value. Every write carries provenance (source:<toolkit>:<tool>) and an observation of the raw item. - Write-back is
gtm_act source.update_record { mappingKey, recordId, fields }with TARGET field names fromwriteBack.fieldMap: a proposal a different human approves;source.undo_update_record { approvalId }restores the snapshot when the mapping has agetTool.
Errors and recovery
NOT_CONFIGURED"Connect <App>": the toolkit has no ACTIVE account -- connect it on the Composio card.NOT_CONFIGURED"no signed version": sign it (gtm definition sign), then sync.- validation: "not a custom field" / "custom object does not exist" -> create it first; "not a known transform" / path errors -> fix the spec; "is a write tool" -> pick a list/get tool.
- A run with
invalidrows: the target refused them (e.g. a required field missing) -- readsource.sync_statuserrors, add aconstor a better path.unresolvedsignals: the contact/company is not in GTM OS yet (sync contacts first). truncated: raisesource.maxRecords(max 50,000) or narrow theargsof the spec'ssourceblock.
References
- The kind and its fields:
docs/gtmos/reference/definition-kinds.md(definition.source_mapping); the ops:docs/gtmos/reference/operations.md(areasource). - Composio tools per app: https://docs.composio.dev/toolkits
Other skills for the same job
Different authors, same problem. Matched on the words in the skill name, across every library in the catalogue except this one.
- citation-source-gap-finder by irinabuht12-oss · 6,620
- talent-mapping by explorium-ai · 348
- customer-journey-mapping by thatrebeccarae · 189
- trigger-mapping by kenny589 · 71
- apollo-filter-mapping by impecablemee · 70
- persona-mapping by Frontal-so · 9
- source-companies by Frontal-so · 9
- gtm-stakeholder-mapping by rvanshur · 2
Need help setting it up?
This page tells you what gtmos-source-mapping does and what it needs. Cheetah builds the agent setup it runs inside: data, CRM, sequencing and the guardrails.
Book a call →The directory stays free. There is nothing gated behind this.