Build Composio

KINDAreplaces $29/mosaves $348/yrback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A local MCP server exposing hand-written tools for the three or four services you actually use, where the hard parts are a transport that breaks on one stray print, credentials that must never reach the model, provider limits the agent cannot see, and write tools an impatient agent will call twice.

estimated effort weekendthe files for this build are in the project pack

ProtocolThe MCP SDK over stdio, with every log line on stderrLanguageTypeScript on Node 22 with a zod schema per toolHTTPOne shared fetch helper with timeouts, Retry-After and a per-provider budgetCredentialsPersonal tokens from the environment or the platform keychain, never from tool argumentsTestsA mocked-HTTP suite covering 401, 429 and repeated writes

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the server and the test suite.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. installfree

    Why The stdout discipline and the tool descriptions are only testable under a client that actually speaks the protocol.

    Get it Claude Desktop, Cursor, or your own agent loop built on the SDK. open ↗

    Verify The client lists the smoke tool from phase one

  3. API keyfree

    Why Every tool is a wrapper around one of these, and a token with more permission than the tools use is exposure for nothing.

    Get it A GitHub fine-grained token with an expiry and only the repositories and permissions the tools need, a Slack user token for your own workspace, a Notion integration token shared with only the pages you want read.

    Verify Each token lists exactly the permissions the tools call

  4. have readyfree

    Why Idempotency and dry run are tested by posting, and the first posts of a new write tool should land somewhere nobody reads.

    Get it A private Slack channel, a scratch repository and a scratch Notion page.

    Verify A test write appears in each and nowhere else

  5. decidefree

    Why The verdict on this category is that under five hand-written integrations beat a hosted layer and above it they do not; deciding the list first keeps the build honest.

    Get it Count the integrations you would call this month and write the list down.

    Verify The list exists and has fewer than five entries

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- providers(id, enabled, base_url, token_ref, scopes_declared, rate_policy_json) · what each token may do, written down
- tools(name, provider_id, kind, input_schema, output_fields, description) · kind is read or write
- calls(id, tool, at, duration_ms, status, provider_status, retry_after_s, bytes_returned) · shapes and statuses, never bodies
- writes(idempotency_key, tool, at, provider_ref, result_json) · a write tool never repeats a key
- budgets(provider_id, window_start, used, limit, unit) · from the provider's published limits, checked before the call
- The calls log records what was asked and how the provider answered but never the content returned, because a log of everything the agent read is a second copy of your mailbox.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
PROVIDERSrequiredgithub,slack,notionEnabled providers. A missing token for any of these stops startup.
GITHUB_TOKENsecretoptionalgithub_pat_...redactedFine-grained, least privilege, with an expiry. The doctor command reports when it lapses.
SLACK_USER_TOKENsecretoptionalxoxp-...redactedYour own workspace. History calls are budgeted at the published non-Marketplace rate.
NOTION_TOKENsecretoptionalntn_...redactedAn integration shared with only the pages the tools should see.
LIMITS_PATHrequired./config/limits.jsonPer-provider budgets with the date each was checked.
MAX_RESULT_BYTESrequired16000Every read tool trims to this. The model's context is the scarce resource.
WRITES_DB_PATHrequired./data/writes.sqliteIdempotency keys and results. Must survive a restart.
LOG_LEVELrequiredinfoLogs go to stderr only. There is no level that writes to stdout.

The build, in order

  1. A server that speaks only protocol on stdout

    The server runs under a real client and nothing but MCP messages ever reaches stdout.

    1. One stray print breaks the protocol; make it impossible rather than unlikely.

      Files src/index.tssrc/log.tstests/stdout-clean.test.ts

    2. Naming the variable, before any client connects.

      Files src/config.ts

    3. The exact JSON block for two clients belongs in the README from day one.

      Files README.md

    done when · tick each as it passes
    watch out
    • A dependency that prints a deprecation notice to stdout, or one console.log left in a handler, corrupts the message stream and the client reports a parse error with no hint of the cause. Redirect all non-transport stdout writes to stderr at startup, and scan the stream in a test for anything that is not a newline-delimited JSON message.
  2. Read tools that return what the model needs

    Each read tool returns trimmed, bounded, paginated results with a description the model can act on.

    1. The model's context is the scarce resource.

      Files src/providers/github.tssrc/providers/slack.tssrc/providers/notion.ts

    2. A bounded page beats a truncated dump.

      Files src/paging.tstests/output-size.test.ts

    3. Test with a canary token that must never appear.

      Files src/http.tstests/no-token-leak.test.ts

    done when · tick each as it passes
    watch out
    • Returning the provider's raw response hands the model forty kilobytes of metadata to find one subject line, and after three calls the conversation is out of context. Trim to the fields a person would read, cap the count, and return a cursor.
  3. Limits the agent cannot see, made visible

    Provider rate limits are known before the call and returned as a wait, never discovered as a ban.

    1. They change; the config says when you last looked.

      Files config/limits.jsonsrc/budgets.ts

    2. To the second. A guess is a longer wait.

      Files src/http.tstests/retry-after.test.ts

    3. The agent can plan around a number; it cannot see a silent sleep.

      Files src/budgets.ts

    done when · tick each as it passes
    watch out
    • For Slack apps outside its Marketplace, message history calls are limited to one request per minute with at most fifteen results, so an agent paging through a channel hits the limit on its second call and, if the server retries for it, retries into a longer wait. Put the limit in the description, return the wait, and never retry a 429 more than once.
  4. Write tools that cannot run twice

    Sending, posting and creating happen once per intent, however many times the agent asks.

    1. The model cannot omit what the schema demands.

      Files src/writes.tssrc/providers/slack.ts

    2. Tested with a provider mock that counts calls.

      Files src/writes.tstests/idempotent-writes.test.ts

    3. What would be sent, verbatim, with no side effect.

      Files src/writes.ts

    done when · tick each as it passes
    watch out
    • An agent whose tool call timed out will call again with the same arguments, and without a key you have posted twice to a channel or opened two issues before anyone notices. Require the key in the schema, store it before calling, and return the stored result on a repeat without touching the provider.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more dev tools.