Push inventory collector
Generate one collector script for one self-managed scanner configuration. Do not assume a cloud vendor, a programming language, or a credential shape until the customer provides them. The script is the pull-scanner connection form on their machine, in the language they name. It reads their APIs, builds one payload per asset, uploads each payload, and then commits the scan. The scanner reconciles and publishes. The script does not publish by itself.
There is no universal provider credential set. There is no schedule on this scanner. Do not add a schedule, a last-run time, or a "run discovery" step.
When to Use This Skill
Use this skill when the user asks to:
- Generate a collector script for a self-managed scanner
- How to Push, download the collector skill, or push inventory
- Write a script that calls POST /push then POST /commit
Trigger keywords: push inventory, push script, How to Push, self-managed scanner, collector skill, POST /push, POST /commit.
Do NOT use this skill when: creating a pull scanner that discovers from a connected cloud platform — use skill setup-service-scanner. Running a hosted scan and reviewing staging assets — use skill run-service-scan-and-view-results.
Prerequisites
- Organization id and scanner configuration id copied from the self-managed scanner page
- A Connected App with client-credentials grant and on that organization
- The provider API specification they can paste or attach (OpenAPI, Postman, or a redacted request and response)
- Authentication documentation for those APIs, without secret values
- A language they can run on their machine
Reference files
references/scanner-contract.md
— ZIP layout, POST /push multipart, POST /commit body. Read it before writing the upload and commit functions.
references/generated-script-standard.md
— runtime inputs, stderr stages, JSON stdout, mapping fingerprint. Read it when writing the script.
references/provider-auth-resolution.md
— one question per turn, managed-only auth, new-provider contract. Read it when choosing operations and credential fields.
Workflow
Rules that always apply
- Customer-facing words — use the table below in every question, in , in the FAILED block, and in Ways to run the script. A wrong verb here is a generation failure.
- One question per response — never combine asset type, inventory, auth, or language.
- Never ask for a provider name — infer the provider from a usable spec. On an unreadable or incomplete spec, ask only the missing list, fetch, handshake, export, or auth piece.
- Never reorder — skip a step only when this chat already answered it with usable data. Ask a step again when the previous answer was unreadable, the wrong kind of file, or missing a required piece.
- Do not create a scanner during generation — do not log in, do not call Anypoint CLI, do not mint a token to discover an organization. Organization id and configuration id are values copied from the scanner page into the inputs file or the parameters.
Use these words. Do not hard-code a vendor, a non-production host, or a loopback address into a question or an example host. The example host is
https://anypoint.mulesoft.com
.
| Say | Do not say |
|---|
| script | adapter, collector, product command |
| input file, parameters, or prompts | three CLIs, CLI mode |
| self-managed scanner | self-serve, self-service, build your own |
| commit the scan | seal, seal the zip, complete and publish, publish to Exchange |
The upload call is
. The finish call is
. The question is exactly: "Commit this scan? [y/N]". A generated script that calls
,
, or
is a generation failure. The JSON body still has a boolean field named
. That field is not the customer sentence.
Examples of an inventory (OpenAPI, a Postman collection, a redacted request and response) show the shape. They are not a menu and not a default. Language examples are not a fixed list. Do not default to one language.
A script that creates a configuration, calls
, or calls
in order to invent an organization id is a generation failure. That auto-fill is later work. It is not this script.
The only customer questions in this chat are the quoted sentences in Steps 1–5, the missing-piece follow-ups, "Does this look fine?", and the generated script's "Commit this scan? [y/N]". Do not ask which provider.
Step 1: Ask asset type
When the asset type is not already explicit, the whole message is:
"What asset type does this scanner configuration accept? Reply with one of: agent, mcp, or rest-api.
agent is one agent card. mcp is one MCP server card. rest-api is one API description. One script commits one of these, to one configuration."
Then stop.
Explicit means they wrote
,
, or
, or a harmless alias (
→
,
/
/
→
,
/
→
). A provider name, a file name, or an operation name is not an asset type. Do not infer the asset type from a single-type contract. Inferring the type so generation can jump ahead is a generation failure.
Step 2: Collect inventory
Once the type is known, the whole next message is:
"Please provide the API specification or documentation for the provider APIs that list and fetch these assets. OpenAPI or Swagger, a Postman or Insomnia collection, RAML, an API Blueprint, a GraphQL schema, an SDK reference, or a redacted request and response are examples, not required formats."
Then stop. Do not ask which provider. Infer the provider from a usable spec. Do not invent calls. Do not fetch a spec they did not provide. The catalog later in this file is a shape reminder for a matching paste, not a substitute.
Read what they attached. Then stop if it is not yet usable. Ask only the missing piece. One question per response.
- Unreadable, empty, unrelated, or not a spec or redacted request and response. Whole message: "This attachment is not a usable specification for listing and fetching these assets. Please attach or paste a valid API spec or a redacted request and response that includes those APIs."
- No list API for the type they named. Whole message: "This specification does not include an API that lists {agent|mcp|rest-api} assets. Please attach or paste that list API."
- Agent list with no fetch or card API. Whole message: "This specification lists assets but does not include the fetch or card API needed to build the agent zip. Please attach or paste that call."
- MCP list with no / (or the equivalent handshake). Whole message: "This specification does not include initialize and tools/list (or the equivalent handshake) needed to build an MCP card. Please attach or paste those calls."
- REST list with no export or OpenAPI download. Whole message: "This specification does not include the export or OpenAPI download needed to build a REST zip. Please attach or paste that call."
A usable inventory has the list call plus the fetch, card, handshake, or export that type needs. Only then ask authentication. Vertex MCP, Gemini MCP, Gemini agents, Azure AI Foundry MCP, and Microsoft 365 Copilot with no customer list docs are enum-only: ask for a customer list call, or stop. If they ask which file to use, they only have this skill and the files they attach; do not name a path from this package.
Step 3: Collect authentication
Once the inventory is usable, the whole next message is:
"Please provide the authentication documentation needed to call those APIs. Include the token or signing request, the response, which field is a secret, and how a call is proven to be the right account. Do not paste secret values."
Then stop. A long-lived secret, a session token, and a private key are never typed into this chat. They belong in the inputs file or in the environment when the script runs.
If that document is missing a piece, ask only that piece. Whole message, fill one gap: "The authentication documentation is missing {the token or signing request|the response|which field is a secret|how a call is proven to be the right account}. Please add that piece. Do not paste secret values." Do not proceed to language until those four are present.
Use the authentication document they pasted. Do not open a catalog file from this skill. When they describe Salesforce connected-org (C2C) login or a MuleSoft-managed authorization-code flow, that is not customer-executable: ask for a request and response they can run from their machine. Until they provide one, do not write files. A pasted SigV4 access-key flow still uses the request and response they pasted. Do not copy pull-scanner-only fields such as Hub Role ARN, Spoke Role Name, External ID, Account IDs, or Target Regions into the script.
Step 4: Ask script language
Once auth is known, the whole next message is:
"Which script language should I use? Project conventions are optional. Any language you can run is acceptable. Named languages are examples, not a fixed list."
Then stop. Do not default to a language. Writing a file before they answer is a failure. If they name a language and omit conventions, write a plain script in that language.
Default script stderr the customer sees: the numbered step banner only when that step runs; after a successful list
listed N, matching M, skipped K
; then one line per
matching asset (
, external id, display name);
No matching {ASSET_TYPE} assets found.
when M is 0; FAILED for real failures. Do not print skip ids,
, or
unless
.
Step 5: Show the mapping
[GATE] WAIT for an explicit yes to "Does this look fine?" before writing files.
When type, inventory, auth, and language are known, do not write files in that turn. Show the mapping in this order, then stop. Do not treat dumping the script as having shown the mapping.
- What was collected, in plain English: the list and detail calls, the auth field names (not values), the language, and the one asset type.
- One row per inventory call. Columns: Call, Request, Response field, Zip path.
- One row per field the zip will contain. Columns: Source, Sent as, Required. Source is the operation and response path, or . If a required Source cannot be pointed at, leave the row blank and ask only for that API or field. Do not guess. If any required Source is blank, stop here. Do not show items 4–8 and do not ask "Does this look fine?" until every required Source is filled.
- One Representative generated JSON object for a single asset, using values from their inventory when they gave an example, otherwise empty strings. For , every required A2A v0.3 field has a row (, , , , , , , , ). A v1 card uses or and has no top-level . The artifact is or . For ,
specification/mcp-metadata.json
with required; if capabilities are included, include all four booleans , , , and ; do not add . For , with and ; is the provider id, and one stage is one asset.
- How the zip is uploaded after they run the script:
POST {ANYPOINT_BASE}/agent-scanner/api/v1/organizations/{org}/scanner-configurations/{config}/push
is the zip as multipart part , filename . Not JSON. Finish is a separate . The customer question is "Commit this scan? [y/N]". Do not name , , or .
- Script architecture: the function names to write (provider login, list, shape zip, token, upload, optional commit, print status) and the numbered stderr steps below.
- Flags, environment variables, prompts, and which of those are secret. Same names as The script. three columns: Prompt label, Parameter flag, Input file key.
- Ask exactly: "Does this look fine?"
Do not show Prerequisites or Ways to run the script in this turn. Those come after the files exist. Showing them before they say yes is a generation failure.
If they say no, ask which row is wrong, fix the table, and ask again. Still do not write files. If they say yes, write the two files. After the files exist, show Prerequisites, then Ways to run the script in this order: interactive, parameters, input file, and the full
as a fenced block named
so they can download it. Do not ask "Does this look fine?" after writing.
That yes is the only approval. The script is not written until they say yes, so a run does not ask them to approve the mapping again and does not refuse live because a fingerprint is pending. Record the approved rows as
: canonical JSON, UTF-8, keys sorted,
{"assetType":"...","rows":[{"source":"...","sentAs":"...","required":true}]}
, rows in the order shown, SHA-256 hex. That value is a record of the chat yes. It is not a second question.
Classify with the check described in their inventory. Do not classify by display name. A list that returns more than one kind of asset still yields only the type they named. The other kinds are skipped. A shared AgentCore
is not permission to zip both agents and MCP. Agent script: skip
protocolConfiguration.serverProtocol=MCP
, then
or the documented synthesized card. MCP script: SigV4 JSON-RPC
, then
; null
= skip. Name is never classification. REST script lists API Gateway (or their REST inventory), not AgentCore runtimes. A skip is a count, not a default log line: increment
and continue. Do not print
,
, or
on stderr unless
is set.
may print
. FAILED still prints (list/page errors, HTTP 401/403, missing required zip fields, upload refusals).
AgentCore MCP handshake URL is not the Exchange card field
(
). The invoke URL is
https://bedrock-agentcore.{region}.amazonaws.com/runtimes/{urlencoded agentRuntimeArn}/invocations?qualifier={endpointName}
.
comes from
/ the list item. Encode it with
urllib.parse.quote(arn, safe="")
(every
,
, and extra character).
is
list-agent-runtime-endpoints
, usually
. No endpoint name means skip that runtime.
is
application/json, text/event-stream
. Parse JSON or SSE
lines.
params are
,
,
. HTTP 401/403 on initialize is FAILED. HTTP 400/404/405/415/422 or a missing
object is skip. A generated script that POSTs
/runtimes/{agentRuntimeId}/invocations/mcp
, uses the unencoded runtime id in the path, or omits
is a generation failure. A default run that prints
for every non-matching runtime is a generation failure.
Step 6: Write the two files
Two files, in the language they named. No markdown guide. The chat already showed the mapping. Prerequisites and Ways to run come after these files exist.
- The script. One run lists the assets, checks each matching object, uploads each matching one, and then either commits the scan or stops. Zero matching assets stop after list/shape: no MuleSoft login. The asset type is a source constant (). The script does not accept , , or an environment variable that switches type.
- An inputs file, , written to disk beside the script and shown again as a fenced block named so the customer can download it. The block contains the whole file. A chat that only prints the command is a generation failure. Every secret value is empty. Non-secret placeholders are obvious fakes, never a real id from this chat. Mark each line or in a comment.
Do not write a pipeline, a container, a schedule, or a second library.
always contains these MuleSoft lines, then the provider keys from the auth they described:
# (required) Anypoint host. Example: https://anypoint.mulesoft.com
ANYPOINT_BASE=
# (required) Organization id, copied from the scanner page
ANYPOINT_ORG_ID=
# (required) Scanner configuration id, copied from the scanner page
SCANNER_CONFIGURATION_ID=
# (required) Connected App client id
ANYPOINT_CLIENT_ID=
# (required) Connected App client secret. Leave empty in chat. Fill this on the machine that runs the script.
ANYPOINT_CLIENT_SECRET=
# (optional) Commit the scan without a prompt. yes or no. Default no.
PUSH_COMMIT=
Append one line per provider credential the script will actually ask for, with
or
. Do not copy a credential set from a different provider. A session token, a private key, or any other long secret is
only when their auth document says the call fails without it.
When that document offers more than one credential source, ask which one before the mapping. The script uses only that source. It does not fall back to another. An attached role or a named profile is allowed when they select it. Do not demand an access key for that choice. An unattended run names the source in the inputs file. It does not guess from leftover environment variables. For AWS that line is
with one of
,
, or
.
The rest of this file is the script contract. Read
The script,
One input mode,
ZIP and push, and
references/scanner-contract.md
before writing. Read
references/generated-script-standard.md
for stderr, JSON stdout, and the mapping fingerprint. Read
references/provider-auth-resolution.md
for managed-only auth.
Step 7: Verify generation and show how to run
After the two files exist, show Prerequisites, then
Ways to run the script in this order: interactive, parameters, input file, and the full
as a fenced block named
so they can download it. Do not ask "Does this look fine?" after writing.
Run the language checker in
Same job, any language on the generated file. Search for forbidden patterns (
or
"Content-Type": "application/json"
on
,
,
,
,
). If the checker cannot run here, search the file for those patterns. Do not declare completion until the file parses in the named language and those searches are clean.
Connected App
The script logs in to Anypoint with one Connected App: client id and client secret, grant
. It never asks for an Anypoint username or password.
An Organization Administrator creates the app once, in Access Management, before they run the script. The script cannot create it.
- Access Management → Connected Apps → Create App.
- Type: App acts on its own behalf (client credentials).
- Add scope and apply it to this organization. A token without that scope is rejected.
- Save. Copy the client id. Store the client secret as . Do not paste it into this chat.
The self-managed types are offered only when
is on and the entitlement matches:
for agents,
for MCP and REST. If upload returns that the type is absent, stop and say so. Do not invent an id.
Token request, in every language:
POST {ANYPOINT_BASE}/accounts/api/v2/oauth2/token
with
Content-Type: application/x-www-form-urlencoded
and body
grant_type=client_credentials&client_id=...&client_secret=...
. Never JSON.
What the script does
Inside that one run the script prints a numbered step banner on stderr only when that step runs, then stops. Do not split them across programs. Do not print a later banner after the run has stopped.
── Step 1: provider login
── Step 2: list assets
── Step 3: shape zips
── Step 4: MuleSoft login
── Step 5: publish
── Step 6: commit
stops after step 3. There is no MuleSoft login and no secret prompt. That skip is success.
After step 3, if zero assets matched
:
- Print
No matching {ASSET_TYPE} assets found.
on stderr (, , or ).
- Print the JSON object on stdout ( true, that type's count , for classified-away and handshake skips, empty publish ids).
- Exit 0.
- Do not print Step 4, 5, or 6. Do not prompt for the Connected App secret. Do not call or .
- uses this same stop.
- Live exception only: continues to and the empty-commit path in step 5.
- Read inputs. See One input mode.
- Call their list and detail APIs with their auth. Follow pages until the list ends or a page cap is hit. If the list call itself fails, exit 2 and do not commit. After a successful list, print one summary line:
listed N, matching M, skipped K
. Do not print one line per skipped id unless .
- For each matching asset, build the object from the mapping they accepted. If a required field is missing, print FAILED and do not upload that object. An invalid object is the script's fault. Print each matching asset on its own line: type, external id, display name. Print the zip path
{id}/specification/config.json
plus the artifact. A zip that uses (no ) is a generation failure.
- Upload each valid ZIP with
POST {ANYPOINT_BASE}/agent-scanner/api/v1/organizations/{org}/scanner-configurations/{config}/push
. The body is the zip bytes, not JSON. is with exactly one part named , filename , part Content-Type: application/zip
. HTTP 202 JSON is {assetId, action, workspaceRunId}
. Keep that . Every later upload in this run must return the same id. The server checks the zip again. A refusal is FAILED, with the response body. The same provider id replaces the staged object. It does not create a second row, and Exchange is not called. An id uploaded earlier and absent from this list stays staged until DELETE .../push/{externalId}
. Omission is not deletion. HTTP 415 means this POST was not a zip part named (JSON body, application/x-www-form-urlencoded
, Content-Type: multipart/form-data
with no boundary, or a boundary that starts with ). A generated script that posts JSON to , uses , sets Content-Type: application/json
on , or names the part anything other than is a generation failure.
- Close the workspace and declare completeness separately. comes only from a 202 (or when and nothing was uploaded). Inventing or posting without that id is a generation failure. Zero accepted uploads: do not call .
- The list stopped at a page cap, or any upload failed, and at least one upload was accepted: with
{"workspaceRunId":"<id from 202>","complete":false}
. Do not ask. is upsert only. It must not be .
- The list finished and every upload succeeded: ask on a TTY, "Commit this scan? [y/N]". The default is no. or is the yes when there is no TTY. Yes sends with . No uploads and does not commit.
- An empty successful list does not commit unless they also set .
- Print one JSON object on stdout: counts, , , , and whether the scan was committed. Exit 0 when nothing failed. Exit 2 when a list, an upload, or a requested commit failed.
means this list is the full inventory, so the scanner may retire assets that were not in the list.
means upsert only.
The upload is one ZIP per asset because that is the API. The customer confirms the mapping and the sample JSON. Do not spend the chat on archive layout. The script still produces a valid ZIP: one directory whose name is the provider's stable id,
specification/config.json
with
and
, and the artifact next to it (
or
,
, or
).
Live stderr after MuleSoft values resolve (non-secrets in full; secret presence only):
Anypoint host: https://anypoint.mulesoft.com
Anypoint Organization ID: …
Scanner Configuration ID: …
Connected App Client ID: …
Connected App Client Secret: set
Ask only the missing ones.
must not prompt for those Anypoint rows.
One input mode
One run uses one value source. Control flags do not pick a source and do not block prompts. Control flags are
,
,
, and
. Provider fields come from the authentication document they pasted, minus pull-scanner-only hub/spoke fields. AWS, Azure, GCP, Databricks, Snowflake, Kong, and a provider that is not in the catalog use that same rule. Do not copy an AWS field onto another provider.
Inside the chosen source, every field uses this fill order. A variable already in the shell fills the value. It does not switch the source, and a blank prompt must not erase it.
- Command flag already set → use it. Do not prompt.
- Else the environment variable.
- Else the / key, when that source is the input file.
- Else a TTY prompt in this same process. Hide a secret. An empty answer keeps step 2.
- Else, not a TTY → naming the missing input.
Three ways to supply values. They are OR.
- Input file. or is set. Every required value, including secrets, is in that file. A missing required key is FAILED. and may be added. They do not switch the run to parameters mode.
- Parameters. A value flag is on the command line, and no input file is set. Value flags are , , , , then only that provider's flags. AWS adds and / . Azure and GCP do not get . Secrets are environment variables, never flags. A missing required value is FAILED. No prompt.
- Prompts. No input file and no value flag, and stdin is a TTY. alone is this mode. Ask only for a label that is still empty after the environment. A secret already in the environment is not asked again.
does not call the scanner. It does not require the Anypoint host, organization id, configuration id, client id, or client secret. It still needs the provider credentials that list the assets.
No file, no value flag, and no TTY → FAILED naming the missing input.
Pull-form provider login
Step 1 looks like the Exchange connection form for
that provider, not a cloud-CLI tutorial. Derive prompts from the authentication document they pasted. If a current pull form matches that provider, use that credential class's
for the fields the script actually asks. Do not copy an AWS, Azure, or Kong field set onto a different provider. Do not send any of these values to MuleSoft. The push configuration is
authScheme=customer-upload
with no
.
| Pull scanner form (UI label) | Script prompt | Secret? |
|---|
| AWS Access Key ID | | no |
| AWS Secret Access Key | | yes |
| AWS Session Token | hidden, only when the access key is after stripping quotes and the token is not already in the environment or the input file | yes |
| AWS Region | | no |
| Tenant ID / Client ID / Client Secret (Azure) | same labels | secret last |
| Subscription ID (Azure APIM) | same label | no |
| Personal Access Token (Kong) | same label | yes |
| GCP Project ID / Service Account Email / Private Key | same labels | private key yes |
| Workspace URL + client id/secret (Databricks) | same labels | secret yes |
Do not prompt Hub Role ARN, Spoke Role Name, External ID, Account IDs, or Target Regions. Those names exist on the pull scanner form. This script does not assume a role. A
row labeled Hub Role ARN, Spoke Role Name, External ID, Account IDs, or Target Regions is a generation failure.
Already logged in
The laptop CLI/SDK session is the default. Access keys, client secrets, and private keys are the other branch.
- AWS. / already work. must have the row
AWS Profile | --profile | AWS_PROFILE
. uses that profile and does not ask for Access Key, Secret, or Session Token. / wins over leftover / in the environment. Do not tell them to . On a TTY, AWS Access Key ID first. Blank, and / already set: do not ask Secret. Blank, and no profile yet: prompt AWS Profile (empty = default chain already on this machine), then AWS Region only. Non-TTY: , AWS_AUTH_MODE=default-chain
, or access keys. Blank Access Key with none of those is FAILED; do not silently call STS on leftover environment credentials or a default chain they did not choose. An AWS that lists Access Key and Region but never / is a generation failure.
- Azure. is already logged in. Use that session (DefaultAzureCredential / ). Do not add . Do not prompt Tenant ID, Client ID, or Client Secret unless they pasted those as the auth document they will actually use.
- GCP. is already logged in (ADC). Use that session. Do not add . Do not prompt Service Account Email or Private Key unless they pasted the service-account JSON as the auth they will actually use.
- Kong, Databricks, Snowflake, Claude, LangSmith. No analog of . Use that provider's token fields. Do not invent .
is AWS only. A
row
on Azure, GCP, Kong, or Databricks is a generation failure.
AWS login
AWS asks for one login, then the region. Nothing else. Order on a TTY:
AWS Access Key ID, then either the already-logged-in profile path above, or
AWS Secret Access Key (hidden) when the key is non-empty, then
AWS Session Token (hidden) only when the unquoted key starts with
and the token is not already set, then
AWS Region. Skipping Access Key ID and jumping to Secret is a generation failure.
- Access keys: and from the flag/env/file/prompt. Strip wrapping quotes, including , before an check. A check on the raw prompt string is a generation failure. Pass the unquoted key, secret, and token into boto3 together. Do not leave a leftover in the process environment. If the key is and the token is still empty, names before any AWS call.
- Region: / / . One region. Do not ask for a second region list.
provider rows for AWS are only
, the access-key pair (secret has no flag), and
/
.
Required unquote (or the same behavior in the language they named):
python
def _unquote_aws(value):
text = (value or "").strip()
marks = "\"'" + "\u201c\u201d\u2018\u2019"
while len(text) >= 2 and text[0] in marks and text[-1] in marks:
text = text[1:-1].strip()
return text
ASIA check is
_unquote_aws(ack).upper().startswith("ASIA")
. After writing, search the script for
applied to the raw prompt string. Any such match is a generation failure.
AWS inventory paths such as
are boto3 operation names. They are not URLs. Call
boto3.client("bedrock-agentcore-control").list_agent_runtimes
and
boto3.client("bedrock-agentcore").get_agent_card
. A handwritten SigV4 request, a POST to
https://bedrock-agentcore-control.../ListAgentRuntimes
, or
Content-Type: application/x-amz-json-1.1
without the AWS SDK is a generation failure. The 403 body
Unable to determine service/operation name to be authorized
means that mistake.
--region us-east-1 --profile scanner
must reach STS as region
and profile
. Do not prompt AWS Secret Access Key when Access Key ID is blank and
is set. Nested Bash
${CLI_REGION:-${AWS_REGION:-x}}}}
is a generation failure (
). Sequential assignments only. After writing a
, search for
and
.
The script
Flags, same spelling in every language:
,
,
,
,
,
,
,
,
. There is no
and no
.
is AWS only.
/
is only when that provider uses a region. Other provider flags come from their auth document.
exits 0 and shows three columns for every input, in this order of ways to supply it: Prompt label, Parameter flag, Input file key. Connected App fields first, then the already-logged-in row for that cloud if it has one (
/
/
), then the fields from their auth document. The client secret has no flag. Help explains
and
. A
that lists
but never mentions
Connected App Client Secret or
is a generation failure. Copy this into argparse
/ usage:
Inputs and where to obtain them:
--anypoint-base / ANYPOINT_BASE: Anypoint host. Scanner path is that host plus /agent-scanner.
Example US production: https://anypoint.mulesoft.com
--org / ANYPOINT_ORG_ID: organization id from the Exchange scanner page
--config / SCANNER_CONFIGURATION_ID: configuration id from that same page
--client-id / ANYPOINT_CLIENT_ID: Connected App Client ID
Connected App Client Secret: ANYPOINT_CLIENT_SECRET, matching --input-file key, or hidden
prompt labeled "Connected App Client Secret". Never a process argument. No --client-secret.
AWS scripts also:
--profile / AWS_PROFILE: AWS Profile. Already logged in via ~/.aws/config. Wins over leftover
AWS_ACCESS_KEY_ID. Not an Azure or GCP flag.
AWS Access Key ID: AWS_ACCESS_KEY_ID or prompt. Blank uses --profile / the default chain.
AWS Secret Access Key: AWS_SECRET_ACCESS_KEY or hidden prompt. No flag. Skip when Access Key
is blank and --profile or the default chain is in use.
--region / --aws-region / AWS_REGION: AWS Region
Azure scripts: az already logged in (DefaultAzureCredential). Do not add --profile.
GCP scripts: gcloud already logged in (ADC). Do not add --profile.
Python argparse dests use underscores (
).
is a generation failure. After writing, search the script for
. Any match is a failure.
builds the objects on disk and does not call the scanner. It does not commit. It does not prove the server will accept the object. The server check happens on upload. Say that in the help text and in
Ways to run the script.
A generated script that posts to
,
, a laptop port, or any URL other than
{ANYPOINT_BASE}/agent-scanner
is a generation failure. There is no
and no
.
Ways to run the script
After the two files exist, the chat shows this section in this order, then it stops: interactive, then parameters, then the input file. The command is whatever runs the file they asked for. Do not show a Python, Bash, Java, or Go command unless that is the language they named. The blocks below use
as the shape.
uses the same three columns: prompt label, parameter flag, input file key.
Prerequisites
- The language runtime they named is installed, including any library their auth document requires.
- A Connected App client id and secret, as in Connected App.
- The organization id and the configuration id, copied from the self-managed scanner page. The script does not look these up and does not create them.
- The provider session: already logged in ( / on AWS, on Azure, on GCP) or the credentials from their auth document, stored as environment variables or in the inputs file. Not in the chat. Not on the command line.
Prompts
The script asks once for each required value that is still empty, then uploads. At the end it asks "Commit this scan? [y/N]". Pressing Enter means no.
Parameters
export ANYPOINT_CLIENT_SECRET="..."
export PROVIDER_SECRET="..."
<run> <script> \
--anypoint-base https://anypoint.mulesoft.com \
--org YOUR_ORG_ID \
--config YOUR_CONFIGURATION_ID \
--client-id YOUR_CLIENT_ID \
--commit
answers yes. Without it, the script uploads and does not commit. Secret names match their auth document.
above is a placeholder for that name. For AWS already logged in on this machine, add
--profile NAME --region us-east-1
instead of access-key exports.
Input file
The file
is a downloadable file, written beside the script, and its full text is in the chat as a block named
. They fill the empty secrets on the machine that runs the script, then:
<run> <script> --input-file inputs.env
in that file commits the scan. Empty or
does not.
Dry-run and help
<run> <script> --help
<run> <script> --dry-run
<run> <script> --input-file inputs.env --dry-run
lists the inputs.
writes the objects locally and does not contact the scanner.
Failures
Every failure prints this on stderr and exits 2:
FAILED step=<name> http=<code>
where: <call>
request: <method and path, no secrets>
response fields: <names only>
why: <one sentence>
body: <response snippet with secrets removed>
next: <what to fix>
Do not print only
or a traceback.
| http | why | next |
|---|
| 0 / connection refused | Cannot reach that Anypoint host. | Confirm / is reachable. Token and push both use {that host}/agent-scanner
. |
| 401 / 403 | Connected App rejected. Wrong secret, or missing on this organization. | Reset the secret on the same app as the client id. Scope it to this org. |
| 404 | Org id, configuration id, or path is wrong on this Anypoint host. | Stay on {ANYPOINT_BASE}/agent-scanner
. Copy both ids from the Exchange scanner page. |
| 400 / 422 | Zip refused. must match this configuration. Path must be . Or without a real . | Unzip and check specification/config.json
. Do not invent the workspace id. |
| 415 | did not receive a zip. The endpoint consumes part . | POST the zip bytes. Do not JSON. Boundary must not start with . With , use and do not also set . |
| 503 | Scanner or a proxy in front of it is down. | Retry when the operator says the scanner is healthy. |
JSON stdout must include
,
, asset counts by type,
,
, and
with
,
,
,
,
,
.
Read configuration id from
,
, then
. One id. Zip
is the source constant
. A customer script that can emit more than one zip type is a generation failure.
Provider-agnostic UX, asset-type-specific pull
The generated script is the laptop half of pull
. Provider SDK calls and form fields change. Customer UX does not.
- Provider-agnostic UX. Same flags, same “prompt only what is missing”, same numbered stderr, same FAILED block, same JSON stdout — AWS, Azure, GCP, Databricks, Snowflake, Kong, or a new provider. Only the pull-form labels and that bundle’s inventory calls change.
- Language-agnostic I/O. Python, Bash, Node, Java, Go, PowerShell: same flag names, env names, step labels, JSON keys.
- Asset-type-specific discovery. One source constant, . Classify with the probe in that bundle’s inventory, the way pull Java does. Never by name. Never “every READY row from a shared list”.
Zipping
as
, or zipping
agent_scanner_mcp_fixture
as
, is a generation failure even when both runtimes appeared in
.
Same job, any language
Whatever they named (Python, Bash, Node, Java, Go, PowerShell, or another runtime) the generated file must parse in that language and do the same job: pull-form provider login, list one zip type, MuleSoft token,
, optional commit, FAILED block, JSON stdout. Do not emit Python because Bash is hard.
Run these on the generated file before claiming it works. If the checker cannot run here, search the file for the forbidden patterns.
| They asked | Must pass |
|---|
| Python | python3 -m py_compile FILE
. Search for . Any match fails. |
| Node / | . |
| Bash | , then search for and . Any match fails. |
| Java | project compile. |
| Go | . |
| Other | that project's syntax check. |
Do
not nest
${CLI_REGION:-${AWS_REGION:-${AWS_DEFAULT_REGION:-x}}}}
. Sequential assignments only:
bash
region=""
[ -n "${CLI_REGION:-}" ] && region="$CLI_REGION"
[ -z "$region" ] && region="${AWS_REGION:-}"
[ -z "$region" ] && region="${AWS_DEFAULT_REGION:-}"
region="$(unquote_aws "$region")"
Python: stdlib plus the cloud SDK already on the laptop; AWS uses
. Node: Node 18+
unless their project already has an SDK. Bash:
,
,
; token call is form-urlencoded, never JSON content-type. Same env names (
,
,
,
).
Catalog of calls
Shape reminder only. The customer paste is their own OpenAPI, Postman collection, or redacted request and response. Do not embed a block from here because they named that provider. Use a block only when they pasted that same inventory. A provider that is not in this catalog is onboarded the same way: their inventory, their auth, their language, the same
, the same
.
AWS agents and MCP (AgentCore)
Already logged in on this machine, or access keys. Service
bedrock-agentcore-control
. Call boto3
(CLI name
). Do not POST to the path
.
- List: , 100, token . Response array . Stop after 50 pages. A stopped list is partial.
- Keep runtimes whose is .
- External id is the runtime id. Display name is the runtime name.
- This script publishes one asset type. Shared list is not shared publish.
- Agent (): before any A2A zip. Skip (count only unless ). Then . If that returns a validation error that the card is only for A2A agents, build a card from the runtime id. is
https://bedrock-agentcore.{region}.amazonaws.com/runtimes/{id}/invocations
. is . is required. is . is . Both mode arrays are . is . If GetAgentCard returns v1 interfaces and no , write and keep .
- MCP (): same list, then for , then
list-agent-runtime-endpoints
for . SigV4 JSON-RPC then to https://bedrock-agentcore.{region}.amazonaws.com/runtimes/{urlencoded ARN}/invocations?qualifier={endpointName}
. Service is . Null result = not MCP; skip (count only; may print ). Name is never classification. Do not POST /runtimes/{id}/invocations/mcp
. Exchange is the zip field only.
AWS REST (API Gateway v1)
- List:
aws apigateway get-rest-apis
, 500, token query .
- Stages: for that . No stage means skip that API (count only unless ).
- Export: with and .
- One asset per stage. External id is .
Azure AI Foundry agents
already logged in. List URL they must confirm:
https://{resource}.services.ai.azure.com/api/projects/{project}/agents?api-version=v1
. Detail:
that host
/agents/{id}?api-version=v1
. Stop after 100 pages or 50 agents.
Azure API Management REST
already logged in. List APIs with the API Management resource in the subscription, resource group, and service name they provide. Export each API as OpenAPI. Caps: 1000 instances, 10000 APIs.
Azure API Management MCP
GET https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.ApiManagement/service?api-version=2025-09-01-preview
, then
GET .../service/{name}/apis?api-version=2025-09-01-preview
with
$filter=properties/type eq 'mcp'
. One MCP card per API.
Google Vertex agents
already logged in (ADC, scope
). Same list API as pull
VertexAgentScannerService
. Prompt
GCP Project ID and
Location. Do not prompt Service Account Email or Private Key unless they pasted a service-account key as the auth they will actually use. Pull form is
ServiceAccountCredentials
from those fields; both mint a Bearer token for the same host.
GET https://{location}-aiplatform.googleapis.com/v1/projects/{project}/locations/{location}/reasoningEngines?pageSize=100
. Follow
. Cap 10000. Connection test
.
Zip directory (push external id) is
projectId + location + engineId
with no separators (
, example
test-projectus-central1123456789
). A Vertex zip named only the last path segment of
is a generation failure.
Card: look at
for
and
. If that JSON is A2A v0.3 or v1, use it. Always set
to
https://{location}-aiplatform.googleapis.com/v1/{engine.name}:query
. If there is no card, write
with
/
from
and
(empty string if missing),
,
,
,
,
,
.
Google Apigee REST
already logged in (ADC). Prompt
GCP Project ID (that value is the Apigee org in every path). Same calls as pull
. Do not prompt Service Account Email or Private Key unless they pasted a service-account key.
GET https://apigee.googleapis.com/v1/organizations/{project}/apis
, array
,
, stop after 1000 pages. Then
GET .../apis/{api}/deployments
. Skip when
is missing or empty. Each deployment needs
and
. Download
GET .../apis/{api}/revisions/{rev}?format=bundle
. Take the first valid
or
under
. One zip per proxy. If no bundle has OAS, synthesize
,
the proxy name,
,
,
[{ "url": "https://{project}-{firstEnvironment}.apigee.net" }]
. External id
.
Google Cloud Run MCP (customer-supplied)
Not
. That enum has no pull class. Do not list reasoning engines.
ADC. Prompt
GCP Project ID and
Location. List
GET https://run.googleapis.com/v2/projects/{project}/locations/{location}/services
with an
access token. For each service
, JSON-RPC
(
) then
to
with an
identity token (
gcloud auth print-identity-token --audiences={uri}
). Null
result = skip (count only unless
). HTTP 401/403 on initialize is FAILED. Do not POST
https://run.googleapis.com/mcp-handshake
.
Zip
:
,
. If
is present, four booleans
,
,
,
. Omit
. External id is the service id (last segment of
).
Databricks agents
They give a workspace URL. List serving endpoints. Then
GET {workspace}/api/2.0/serving-endpoints/{name}/openapi
. Cap 10000. Reject a workspace URL that is not public https.
Databricks MCP
Same workspace login. Try, in order,
/api/2.0/mcp/functions/system/ai
,
,
,
/api/2.1/unity-catalog/connections
,
/api/2.0/unity-catalog/connections
,
,
,
GET /api/2.0/genie/spaces
. A 404 on the first page means that source is absent.
Snowflake agents
They give an account URL. Header
and
X-Snowflake-Authorization-Token-Type: PROGRAMMATIC_ACCESS_TOKEN
. List databases, schemas, agents, then read
.
Snowflake MCP
POST {account}/api/v2/statements
with SQL
. Then JSON-RPC
, then
. A failed handshake is a skip (count only unless
).
Microsoft Copilot Studio agents
GET https://api.powerplatform.com/environmentmanagement/environments?api-version=2024-10-01
. Then Dataverse
with the published, non-msdyn filter. Dataverse host must look like
https://{name}.crm.dynamics.com
.
Kong REST
Bearer token in the environment, not in the file. Control planes, then services, routes, plugins. OpenAPI body is
GET {host}/v3/apis/{id}/specifications/{specId}
. Do not invent an OpenAPI document from the route list.
GoDaddy agents
GET {baseUrl}/v1/agents?protocol=A2A&limit=20&offset={offset}
. Cap 1000.
must be public https.
Claude agents
GET https://api.anthropic.com/v1/agents?limit=100&page={next_page}
. Headers
from the environment,
anthropic-version: 2023-06-01
,
anthropic-beta: managed-agents-2026-04-01
. Card
is
https://api.anthropic.com/v1/agents/{id}/messages
.
LangSmith agents
GET {apiHost}/v2/deployments?status=READY&limit=100&offset=
. Then
POST {custom_url}/assistants/search
and
GET {custom_url}/.well-known/agent-card.json
.
Salesforce Agentforce
GET {instance}/services/data/v64.0/query
. SOQL
SELECT Id, DeveloperName, MasterLabel FROM BotDefinition WHERE IsDeleted = false
. Follow
for at most 50 pages. External id is
.
Bedrock agents (not AgentCore)
, then
, versions, aliases. A missing permission omits that field and the script continues. Do not mix this inventory with AgentCore
.
ServiceNow agents
Not a shipped pull scanner. Use it only if they confirm this list. Client credentials token, then
alm_ai_system_digital_asset
. Do not call
. Ask them what to put in the card
.
No calls exist
Do not write a script for Azure AI Foundry MCP, Microsoft 365 Copilot, Vertex MCP, Gemini agents, or Gemini MCP from those enum names. There is no pull-scanner call to copy. Ask them for the list call, or stop. A customer-supplied Cloud Run MCP inventory is a list-plus-handshake they paste; that is not the Vertex MCP enum.
ZIP and push
One asset is one zip. The zip has one top-level directory. That directory name is the external id, at most 255 characters, the provider id, not the display name.
specification/config.json
:
json
{ "name": "<display name>", "type": "agent", "description": "<optional, max 500 chars>" }
is the baked
and must match that configuration.
Artifact JSON must pass the Exchange agent-fabric schemas (tag
:
,
,
,
). Do not invent a second subset.
A2A v0.3 (
) required keys:
,
,
,
,
,
,
(at least one),
(at least one),
. Synthesized AgentCore fallbacks use
. Skill objects, when present, need
,
,
, and
.
A2A v1 (
):
or
with a callable
; never a top-level
.
MCP (
):
required; if emitting
, emit all four booleans
,
,
,
; omit
.
REST artifact is the exported OpenAPI document. It must contain
or
.
Push, to that one configuration id.
is the zip.
http
POST {ANYPOINT_BASE}/agent-scanner/api/v1/organizations/{ANYPOINT_ORG_ID}/scanner-configurations/{configId}/push
------pushcollector7b1c
<zip bytes>
------pushcollector7b1c--
Required Python (or the same bytes in the language they named). Copy the boundary; do not start it with
.
python
def _multipart_zip(filename, payload):
boundary = "----pushcollector7b1c"
head = (
f"--{boundary}\r\n"
f'Content-Disposition: form-data; name="file"; filename="{filename}"\r\n'
"Content-Type: application/zip\r\n\r\n"
).encode()
tail = f"\r\n--{boundary}--\r\n".encode()
return head + payload + tail, f"multipart/form-data; boundary={boundary}"
equivalent:
files={"file": (f"{external_id}.zip", zip_bytes, "application/zip")}
and do not set
yourself (that drops the boundary → 415).
:
-F "file=@{externalId}.zip;type=application/zip"
. After writing, search the script for
or
"Content-Type": "application/json"
on the
URL. Any match is a generation failure. Search for
or a boundary that starts with
. Any match is a generation failure.
HTTP 202 body has
. Read it. A generated script that calls
is a generation failure.
- Finish with
Content-Type: application/json
{"workspaceRunId":"<id from 202>","complete":true|false}
. The customer question is "Commit this scan? [y/N]". A generated script that calls or is a generation failure. A generated script that POSTs when every failed, or with a homemade id, is a generation failure.
The zip
must contain
{externalId}/specification/config.json
and
{externalId}/specification/<artifact>
.
at the top of the zip is invalid.
Best Practices
- One question per turn. ✅ the quoted sentences in Workflow. ❌ combining asset type, inventory, auth, or language in one reply.
- Infer the provider; never ask its name. ✅ read the attached spec. ❌ "which provider?". On an invalid spec, ask for the missing list, fetch, handshake, export, or auth piece.
- Mapping gate. ✅ show the mapping, then wait for "Does this look fine?". ❌ writing files in the mapping turn. ❌ treating a script dump as the mapping.
- Quiet skips. ✅
listed N, matching M, skipped K
plus matching asset lines. ❌ skip ids, , or unless .
- Omission is not deletion. ✅
DELETE .../push/{externalId}
only when they ask to unstage. ❌ DELETE because a second run's list is shorter.
- Baked asset type. ✅ constant. ❌ or an environment variable that switches type.
Troubleshooting
HTTP 415 on POST /push: the body was not a ZIP as multipart part
. JSON,
,
Content-Type: application/json
, a missing boundary, or a boundary that starts with
all produce 415. Use the
helper (or
without setting Content-Type).
No inventory file in this skill: this package does not ship provider OpenAPI files. If they ask which file to open, ask them to paste or attach their own specification.
Invalid or incomplete spec: do not invent calls and do not ask which provider. Ask for the missing list, fetch, handshake, export, or auth piece using the questions in Workflow.
Salesforce C2C or managed authorization-code login: that pull form is not customer-executable. Ask for a request and response they can run from their machine before writing files.
Zero matching assets: print
No matching {ASSET_TYPE} assets found.
, print JSON, exit 0. Do not call /push or /commit.
HTTP 401/403 on /push or /commit: Connected App missing
, wrong organization, or the configuration is not a self-managed scanner they can write.
Related Skills
- skill setup-service-scanner: Create a pull scanner configuration that discovers assets from a connected cloud platform.
- skill run-service-scan-and-view-results: Run a hosted scan and review discovered staging assets.