npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@foundry-ai-dev/agent-sdk

v0.3.0

Published

Builder SDK for Foundry agents — mount a handler, receive dispatched events, call back into Foundry with high-level helpers.

Downloads

1,595

Readme

@foundry-ai-dev/agent-sdk

Builder SDK for Foundry agents. Mount a handler in any Express app, receive dispatched events from Foundry, and call back into Foundry through typed resource clients.

Slack is the first resource client shipped in v1. GitHub, Gmail, Calendar, Drive, and Meet are on the roadmap and will follow the same shape — same constructor, same auth, same env vars, same 401 retry behavior. The Slack examples below are the reference; every future client mirrors them one-for-one.

Install

npm install @foundry-ai-dev/agent-sdk express

Quick start

Here's the smallest agent that does something — a Slack echo handler. Slack is the only resource client wired up today, but the shape (task.eventType switch + ctx.<integration>.<method> call) is what every future integration will look like.

import express from 'express'
import { mountAgent } from '@foundry-ai-dev/agent-sdk'

const app = express()

mountAgent(app, async (task, ctx) => {
  // Slack is the first resource client. Future integrations land under the
  // same `ctx.<integration>` namespace — ctx.github, ctx.gmail, ctx.calendar,
  // ctx.drive, ctx.meet — with their own event types and typed methods.
  if (task.eventType === 'slack.message') {
    const text = (task.event as { text?: string }).text ?? ''
    await ctx.slack.postMessage({
      channel: (task.event as { channel: string }).channel,
      text: `echo: ${text}`,
    })
  }
})

// Foundry forwards traffic to port 8080 inside the machine.
// Bind to 0.0.0.0 so Foundry's proxy can reach you (not 127.0.0.1).
app.listen(8080, '0.0.0.0', () => {
  console.log('agent listening on 0.0.0.0:8080')
})

You must listen on port 8080. Foundry machines forward traffic from 80/443 to internal port 8080. If your server binds to a different port, the agent runs but nothing reaches it. (V2 will let you configure this.)

Foundry resource clients (the pattern)

A Foundry resource client is a thin, typed POST-with-Bearer-JWT wrapper around ${apiUrl}/api/agents/<tool>/<action>. Every client — Slack today, GitHub/Gmail/Calendar/Drive/Meet tomorrow — has the same shape:

  • One uniform deps bag: { apiUrl, ctxToken?, auth?, fetch? }.
  • One token rule: use the cached ctxToken (passed in from a /run dispatch) first; if absent, mint a fresh JWT via auth.getToken().
  • One retry rule: on a 401, if auth is present, call auth.invalidate() + getToken() and retry the call exactly once. A second 401 (or any other non-ok status) throws a structured error like ctx.<tool>.<method> failed (401): <detail>.
  • One transport: POST ${apiUrl}/api/agents/<tool>/<action> with Authorization: Bearer <jwt> and a JSON body.

The Slack client at packages/agent-sdk/src/clients/slack.ts is the reference implementation. GitHub, Gmail, Calendar, Drive, and Meet clients will copy its file structure exactly — only the input types, method names, and the <tool>/<action> path segment change.

Provisioned env vars

When Foundry provisions a machine for your agent, it bakes the following env vars in. You don't set these by hand in production — they're guaranteed to be present.

| Var | What it is | Used by | |---|---|---| | FOUNDRY_API_URL | Where the SDK calls back (e.g. https://api.foundry.dev) | mountAgent, createAuthClient, all resource clients | | FOUNDRY_AGENT_SIGNING_KEY | Shared HMAC secret used to verify inbound /run requests | mountAgent | | FOUNDRY_MACHINE_RUNTIME_ID | This machine's public credential id (mch_<hex>) | createAuthClient | | FOUNDRY_MACHINE_RUNTIME_SECRET | This machine's plaintext secret — never stored on Foundry's side, only hashed | createAuthClient |

mountAgent reads all four automatically. Your code just calls mountAgent(app, handler) and binds your server to port 8080.

Local development

For tests or running outside a provisioned machine, set the vars by hand:

export FOUNDRY_API_URL=http://localhost:4000
export FOUNDRY_AGENT_SIGNING_KEY=<grab from your dev agent's row in the `agents` table>
export FOUNDRY_MACHINE_RUNTIME_ID=mch_localdev
export FOUNDRY_MACHINE_RUNTIME_SECRET=secret_localdev

Deployment topology

Foundry dispatcher
       │
       ▼  POST https://<your-agent>.foundry.dev/run
       │  (HMAC-signed, fire-and-forget)
       │
  Foundry proxy (80/443)
       │
       ▼  forwards to internal_port 8080
       │
  Your agent (app.listen(8080, '0.0.0.0'))
       │
       │  resource-client callbacks (e.g. Slack), token mints, etc.
       ▼
  POST https://<foundry-api>/api/agents/slack/post-message      ← Slack today
                                /api/agents/<tool>/<action>     ← every future client, same shape
                                /api/runtime/auth/machine-access
  • Foundry POSTs /run for every dispatch. Your handler returns 200; Foundry won't retry.
  • No /health probe — don't bother adding one.
  • Callbacks the SDK makes for you (resource clients + auth) target the URL from FOUNDRY_API_URL.

What mountAgent does

  • Registers POST /run on the Express app (override with options.path).
  • Verifies the inbound HMAC signature against FOUNDRY_AGENT_SIGNING_KEY.
  • Builds a ctx with one typed helper per installed integration plus a logger. Today that's ctx.slack.postMessage and ctx.logger; as more integrations ship, they land under the same namespace (ctx.github, ctx.gmail, …) with identical construction and retry behavior. Every helper closes over the per-dispatch JWT.
  • Calls your handler with (task, ctx).
  • Returns 200 { ok: true } even on handler errors so Foundry's dispatcher doesn't retry blindly. Errors are logged via ctx.logger.

mountAgent is intentionally narrow — it only handles the webhook. Auth and resource clients are constructed by your code, not by mountAgent.

Calling Foundry from anywhere in your code

The webhook handler is one way to use the SDK, not the only way. A cron job, queue consumer, or any other route can call Foundry resource clients directly. The pattern is the same for every client — Slack today, every future integration the same way:

import { createAuthClient, createSlackClient } from '@foundry-ai-dev/agent-sdk'

const auth = createAuthClient()                        // reads FOUNDRY_* env vars
const slack = createSlackClient({
  apiUrl: process.env.FOUNDRY_API_URL!,
  auth,
})

await slack.postMessage({ channel, text: 'hi' })

Once a future integration ships, the only thing that changes is the factory name and the method:

// Illustrative — not yet shipped. Same deps bag, same auth, same 401 retry.
// const github = createGithubClient({ apiUrl: process.env.FOUNDRY_API_URL!, auth })
// await github.commentOnIssue({ repo, issueNumber, body: 'hi' })

createAuthClient() is a singleton when called with no args — every file that calls it gets the same instance and shares its cache. Call it once at module top in any file; the SDK handles the rest.

Long-running webhook handlers: opt in to 401 retries

The default ctx.<integration> helpers use the per-dispatch 5-minute ctxToken. If a handler runs longer than 5 minutes, the token expires. Pass an auth client to mountAgent so every ctx client (ctx.slack today, ctx.<integration> for any future client) can re-mint on 401:

import { mountAgent, createAuthClient } from '@foundry-ai-dev/agent-sdk'

mountAgent(app, handler, { auth: createAuthClient() })

Listening on the right port

Foundry forwards traffic to internal port 8080 on every machine. Your server must:

  • Listen on port 8080
  • Bind to 0.0.0.0 (not 127.0.0.1 — Foundry's proxy lives in another network namespace)
app.listen(8080, '0.0.0.0')

If you bind to a different port, the agent runs but nothing reaches it. (V2 will let you configure the port — Foundry will read it from your manifest and route accordingly.)

Trust model

Every dispatch is HMAC-signed with FOUNDRY_AGENT_SIGNING_KEY. Every callback uses a short-lived (5-minute) JWT issued by Foundry — the trust layer re-checks permission_grants on every call, so a revoked permission takes effect on the next attempt. If the JWT expires mid-handler, the SDK transparently re-mints one via the auth client.

You never touch raw OAuth tokens. Foundry holds them — Slack today, every future integration the same way — and the SDK lets you act through Foundry.

What's shipped today

| Integration | Resource client | Status | |---|---|---| | Slack | createSlackClient, ctx.slack.postMessage | Shipped in v1 | | GitHub | createGithubClient | Planned — same shape | | Gmail | createGmailClient | Planned — same shape | | Google Calendar | createCalendarClient | Planned — same shape | | Google Drive | createDriveClient | Planned — same shape | | Google Meet | createMeetClient | Planned — same shape |

No install or import for the planned clients yet — they will appear in this README as they ship. The pattern (deps bag, token rule, 401 retry, transport) is fixed and won't change between integrations.

License

MIT