Skip to content

Connect an agent (MCP)

The Model Context Protocol is a standard way for an AI assistant to call tools in another system. Point a compatible client — Claude Desktop, an IDE assistant, your own agent — at a server, and the assistant can use what that server exposes.

Sloose runs one. An agent can read your CRM’s shape, build importable templates with it, and draft changes to an import you have open.

The endpoint is POST /mcp on your API origin, over Streamable HTTP:

https://api.sloose.com/mcp

Point an MCP client at it and it signs you in itself. Sloose is an OAuth 2.1 authorisation server for MCP clients: the first request comes back 401 with a challenge naming /.well-known/oauth-protected-resource/mcp, the client discovers the authorisation and token endpoints from there, and sends you to app.sloose.com to sign in (with Zoho, a sign-in link or a passkey), choose which organisation it may use, and allow it. The token it gets is for you, in that organisation, at your role — an agent can do exactly what you could in the app, and nothing once your access is revoked. Clients register themselves by Client ID Metadata Document (the client id is the HTTPS URL of a document describing the client), which is how Claude and other current clients work; there is no registration form and no dynamic registration. If you are building a client: the document is fetched from the HTTPS URL you send as client_id, must come back as application/json from that URL itself (redirects are not followed), must carry the same URL in its own client_id field, and any client_uri in it must share that URL’s origin.

You can still paste a bearer instead: a session token or an organisation API token both work as Authorization: Bearer ….

It is stateless: each request builds a server scoped to your organisation and tears it down again. There is no session to keep alive.

MCP is a plan feature: Business and Enterprise, and a trial. On a plan without it, every request is refused with FEATURE_MCP before it reaches the server, whatever was consented to, so an upgrade works at once and a move down stops it at once. See Plans and limits.

Tool Returns
list_modules The organisation’s configured modules, with the default enabled set named separately, and their vocabulary: each has singular, the identifier a formula writes after the organisation’s context name (<expressionContext>.<singular>, by default rec.Contact), and singularLabel, the CRM’s own words for one record, which is what to say to a person.
get_fields A module’s importable fields, with the admin annotations. A module that takes notes has four more, Sloose_Note_Title, Sloose_Note_Content, Sloose_Note_Date and Sloose_Note_Author, marked addedAs: "note": not fields of the record but a note added to it after it is saved, never required of the record. A file or image field, and a record’s attachments, are not listed: they are mapped on Map fields, not proposed.
propose_matching How an existing record is recognised, per module: the field, where its value comes from, and the confidence. Priced on its own.
propose_mapping A validated mapping proposal for a set of columns. Tell it the key (keys) and it maps that column to that field without arguing.
test_expression Compiles and runs an expression against sample rows; one that reads records is compiled only.
validate_template Checks a template configuration without saving it. ok is false for what would stop it being stored, and for a required field a record it makes would leave empty, asked as Validate asks it. It is not every check Validate makes: read issues whenever issueCount is not zero, since a picklist value that is not one of its options is there, and Validate refuses that too.
list_templates Templates visible to the caller.
get_template One template, migrated to the current schema.
save_template Creates or updates a template. Pass updatedAt from get_template, so a change somebody else made is not written over. A template a configuration repository manages (managedBy: "sdk") is refused: save a copy without an id instead.
list_imports The newest 50 imports that write through a connection, with each one’s version and whether a run holds it. It says when there are more, and takes a name to look for.
get_import One import as it stands now: its version, its files and their columns, what each file writes, how its rows find their records, every field mapping and the value maps.
apply_import_mapping Sets or clears one field’s mapping on one file of an import. A lookup is mapped by searchField and searchExpression, and only onto a lookup field. The match key is set and cleared only by apply_import_match; a mapping that is a key can be cleared or skipped through this tool only after that tool has cleared the key.
apply_import_match Sets how one file’s rows meet a module’s existing records: new or existing, what to do with a record found, what to do when several are found, and the field a record is found by.
apply_import_value_map Sets or removes one entry of a value map, for a picklist or a lookup: what a value in the file is written as. Value maps belong to the whole import, not to one file.
check_import_recipe Checks an import’s recipe only: that each file writes a module its connection offers, maps fields the connection has (a plain mapping needs one an import can write; a match key may be any field), and reads columns its own file has. It checks no row: validate_import does, as the Validate step in the browser does, so an import this finds nothing in can still stop on a row.
validate_import Checks an import’s rows with the same checks the Validate step makes: values against picklists and Must be rules, required fields, dates, formulas (run in a sandbox, with the helper libraries and the versions the import keeps), links between files and the lookups a subform’s lines make in the CRM. It returns the errors and warnings in words with every value of your file left as “…” (row numbers, counts and the names of your columns and fields are returned; no value, and no text a formula threw; see data handling), the first 50 of each, with their full counts. It changes nothing, but when the import has subform lines that look records up it reads your CRM and spends your daily API allowance, and the answer says how many calls it made. It is limited per grant and per organisation. An import whose files are over about 40 MB stored, or a million cells, is refused before it is read, so check that one on the Validate step; the answer’s unchecked names what the page checks for this import and this does not. A clear result is not a promise: read the Validate step before a run.

Note what is not there: nothing runs an import. An agent can build the recipe; a person runs it.

The import tools change an import that a person may have open in the browser. Each change is one thing (one field’s mapping, one match rule, one value map entry), made on the import as it stands at that moment and written under its version, so what the person changed elsewhere is kept. The person sees the change arrive in place.

Every change names what the agent read of the key it changes (expect: the field’s mapping as get_import showed it, or null for none). If somebody has changed that key since, nothing is written and the answer is changed_elsewhere with the value that stands. The tool descriptions tell the agent to ask the person and not to read again and apply over it, but the server cannot stop an agent that does: a change made on a fresh read goes through. A change to a different key does not conflict.

Every answer says whether the change landed. applied: true gives the import’s new version. applied: false says why, and in that case nothing was written: do not tell the person it was. A change that finds the import moved under it is made again on the new copy, a few times, and is then given up with changed_elsewhere; read the import again with get_import and decide whether the change still stands. A change is refused while a run holds the import, for an operator on an import they did not start, for a connection other than the import’s own, and for an import with no recipe yet (it has not been set up on Target); asking for what the import already says is answered unchanged, with nothing written. An agent may make 60 changes a minute under one consent; past that the answer says nothing was written and how many seconds to wait.

An organisation may hold several connections — production and a sandbox, or a client’s CRM each — so every tool that reads a CRM’s modules and fields, or checks something against them, takes connection: the connection’s id, on every call. Nothing picks one for a call that names none, even in an organisation holding a single connection. Such a call is refused with the list of connections it may name, which is also how an agent finds them.

save_template is the exception: a template may name no connection. Saved with one, it is checked against that connection’s fields and records it, so an import started from it has that connection chosen already. A new template saved with none has only its shape checked here, and its fields are checked against whichever connection it is used with, when an import is started from it. An update saved with none is checked against the connection the template already records, when it records one. list_templates and get_template are the organisation’s, and take none.

A module the connection has but no longer offers — taken out of its chosen modules, or gone from the CRM — does not stop a template being saved. save_template stores it and validate_template passes it, each with a warning in warnings: an import started from the template keeps that module’s setup aside until the module is offered again. A module the connection has never had is refused. A lookup that searches its module by a field that module no longer has saves too, with a warning, since it would find nothing.

Given the file’s columns, validate_template reads them as the wizard’s Validate step does. A column mapping must name a column the file has, by its exact header. A formula, or a search, must read only columns the file has: one that reads a missing column is a problem, unless the formula falls back where it is missing (row.Phone || row.Mobile), which is a warning. A key’s own match rule, and the organisation’s default rule when the template keys nothing, are read the same way, since a rule over a missing column matches no row. Nothing is said of a mapping the import never runs: one switched off, one onto a field the import cannot write, or one in a module matched on its key alone (a search beside that key is run: it fills a link on the record). A required field a record would be made without is a problem, so ok is false: the import requires it (the CRM’s rule, or the organisation’s Required for imports), Validate refuses an import of the template without it, and the problem says which fields and names updates as the way to only find. That includes a module matched on its key alone under mixed (or no intent), which makes a record holding only that key when a row finds none. Under updates a row that finds none makes nothing, so nothing is asked — unless the module has something of its own to write and its If several match is create, which then makes a record where several match. Pass validate_template the answer the run will take, as multiMatch: the flow’s own for the module, else the template’s run.multiMatchAction. A module matched on its key alone makes none there.

Exactly the ones you are, because they are enforced on the server rather than in a client.

  • Organisation scoping. The bearer is issued for one organisation; the agent cannot see another.
  • Roles. Saving a template needs the same role a person would need.
  • Sharing. Making a template organisation-visible is a plan feature, checked here too.
  • Validation. A configuration that would not validate in the wizard does not validate here.
  • Migration. An old template is migrated forward on read, not refused.

That symmetry is deliberate: a gate the app honours and the API does not is not a gate. There is no path through MCP that a person could not take through the wizard.

The tools compose into an obvious loop, and it is worth doing in this order:

  1. list_modules, then get_fields on the one you are importing into — so the agent is working from your real schema and annotations, not a guess.
  2. propose_matching for how an existing record is recognised, then propose_mapping for the columns in hand, passing the key it settled on. Two calls, because they are two questions and each is priced on its own.
  3. test_expression on anything computed, against sample rows, before committing to it.
  4. validate_template — which tells you what the wizard would say, without saving.
  5. save_template, then open it in the wizard, look at it, and run it. Or, for an import a person already has open, list_imports and get_import, then apply_import_mapping, apply_import_match and apply_import_value_map one change at a time, check_import_recipe to check the recipe, and validate_import to check the rows.

Step 4 before step 5 is the one people skip and should not. It costs nothing and it is the difference between a template that saves and a template that works.

Good at: mapping columns from a schema it can read, writing an expression and testing it, building a first draft of a template from a file description.

Not good at: deciding what counts as the same record. That is a judgement about your data and your business, and it is the decision with the worst failure mode — see Which rows & existing. Review the match rule yourself, every time: the key is the mapping carrying key: true, and what it produces for a row is what the destination is searched on.

Only propose_matching and propose_mapping reach a model, and they are metered like any other AI call; the other tools cost nothing. See AI credits and Your data and the model — the same sample policy applies here.

If you want to script against Sloose rather than converse with it, the REST API and the typed client are the better fit: see the TypeScript client and the API reference.

Report a problem with this page

sloose.com