> ## Documentation Index
> Fetch the complete documentation index at: https://docs.grantex.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# How to Add Permissions to LangChain Agent Tools

> Add scoped permissions to configured LangChain tools with verified Grantex JWTs, manifest enforcement, and optional grant-aware audit callbacks.

LangChain agent permissions are application-enforced rules that decide whether a configured tool may run for a particular principal and grant. LangChain supplies tool and callback abstractions; your application still has to install the authorization check at each protected execution boundary.

This guide covers the two permission paths implemented by Grantex:

1. `createGrantexTool()` for an exact required-scope check on one configured function.
2. `Grantex.wrapTool()` for manifest-based `read`, `write`, `delete`, and `admin` permission levels.

<Note>
  **Implementation snapshot, updated July 12, 2026:** these examples target
  `@grantex/langchain@0.1.7`, `@grantex/sdk@0.3.13`, and the repository's
  resolved `@langchain/core@1.2.2`. The adapter declares
  `@langchain/core >=0.3.0`, but that range is not a promise of compatibility
  with every future LangChain package or agent runtime.
</Note>

## Does LangChain have permissions for agent tools?

A LangChain agent can choose among the tools its host application registers. LangChain also offers callbacks, tracing, and extension points that applications can use to build controls.

LangChain does not, by itself, understand a Grantex grant token, principal, scope, or revocation state. In a typical unwrapped tools array:

```typescript theme={null}
import { DynamicTool } from '@langchain/core/tools';

const searchContactsTool = new DynamicTool({
  name: 'search_contacts',
  description: 'Search CRM contacts',
  func: async (input) => JSON.stringify(await searchContacts(input)),
});

const deleteContactTool = new DynamicTool({
  name: 'delete_contact',
  description: 'Delete a CRM contact by ID',
  func: async (input) => JSON.stringify(await deleteContact(input)),
});

const tools = [searchContactsTool, deleteContactTool];
```

both tools are available to whichever agent or executor receives that array. Your application can filter tools, add middleware, or implement its own checks. The trusted execution boundary, not the model, must verify authority before the protected function runs.

Separate three controls:

* **Availability:** which tools the application exposes.
* **Authorization:** which exposed calls a verified grant permits.
* **Audit:** which configured execution events are recorded.

Grantex supplies authorization wrappers and an optional audit callback. Neither is a LangChain default, and neither covers a tool where it is not installed.

## Install the tested package snapshot

Add the integration to an existing LangChain application with exact versions:

```bash theme={null}
npm install --save-exact @grantex/langchain@0.1.7 @grantex/sdk@0.3.13 @langchain/core@1.2.2
```

Node.js 18 or later is required. Pin your model-provider and higher-level `langchain` packages separately for the agent runtime you use.

## Add an exact scope check with `createGrantexTool`

`createGrantexTool()` returns a LangChain `DynamicTool`. On invocation it verifies the configured grant token and requires one exact scope string before calling `func`.

```typescript theme={null}
import { createGrantexTool } from '@grantex/langchain';

export function buildCrmTools(grantToken: string) {
  const searchTool = createGrantexTool({
    name: 'search_contacts',
    description: 'Search CRM contacts',
    grantToken,
    requiredScope: 'contacts:read',
    audience: 'https://crm.example.com',
    func: async (input) => JSON.stringify(await searchContacts(input)),
  });

  const deleteTool = createGrantexTool({
    name: 'delete_contact',
    description: 'Delete a CRM contact by ID',
    grantToken,
    requiredScope: 'contacts:delete',
    audience: 'https://crm.example.com',
    func: async (input) => JSON.stringify(await deleteContact(input)),
  });

  return [searchTool, deleteTool];
}
```

A verified token with `contacts:read` but not `contacts:delete` causes the delete wrapper to throw before `deleteContact()` runs.

The wrapper delegates to `verifyGrantToken()` in `@grantex/sdk`. It checks:

* RS256 signature against the configured JWKS.
* Expected issuer and JWT expiry.
* Optional `audience` and clock tolerance.
* Exact membership of `requiredScope` in the verified `scp` claim.

The SDK reuses a bounded, process-level set of JOSE remote-JWKS resolvers. Initial lookup, an unknown key ID, or key rotation can still require network access. There is no latency or network-free guarantee.

<Warning>
  Local signature and claim verification does not query current grant revocation
  state. Add online or synchronized state where immediate revocation matters,
  and use bounded token lifetimes as an additional exposure limit.
</Warning>

### Configured-tool boundary

`createGrantexTool()` protects only its configured `func`. It does not discover or rewrite other tools. Wrap each protected function and pass the protected instances, not unwrapped equivalents, to the agent.

The `grantToken` option is a string captured by the tool. Build request-, principal-, or session-specific tools when different users need different grants. Do not share one token-bearing tool instance across unrelated principals.

This path uses exact scope strings. It does **not** apply the manifest hierarchy below.

## Add manifest-based permissions with `wrapTool`

For connector-style tools, `@grantex/sdk` maps tool names to permission levels. `loadManifest()` stores a mapping, and `wrapTool()` applies it when the returned object's `invoke()` runs.

```typescript theme={null}
import { Grantex, Permission, ToolManifest } from '@grantex/sdk';

const gx = new Grantex({
  apiKey: process.env.GRANTEX_API_KEY,
  issuer: 'https://grantex.dev',
  jwksUri: 'https://api.grantex.dev/.well-known/jwks.json',
});

gx.loadManifest(new ToolManifest({
  connector: 'crm',
  tools: {
    search_contacts: Permission.READ,
    create_contact: Permission.WRITE,
    delete_contact: Permission.DELETE,
    export_all: Permission.ADMIN,
  },
}));

let currentGrantToken = initialGrantToken;

const protectedSearch = gx.wrapTool(searchContactsTool, {
  connector: 'crm',
  tool: 'search_contacts',
  grantToken: () => currentGrantToken,
});

const protectedDelete = gx.wrapTool(deleteContactTool, {
  connector: 'crm',
  tool: 'delete_contact',
  grantToken: () => currentGrantToken,
});

const protectedTools = [protectedSearch, protectedDelete];
```

Loading a manifest alone does not protect anything. Enforcement occurs only through `gx.enforce()` or the object returned by `gx.wrapTool()`. Keep original unwrapped tools out of the executable tool list.

The token getter is synchronous. If the application refreshes a token, update the state read by the getter before the next invocation.

### Permission hierarchy for manifest enforcement

| Granted level | Covers                    |
| ------------- | ------------------------- |
| `read`        | `read`                    |
| `write`       | `read`, `write`           |
| `delete`      | `read`, `write`, `delete` |
| `admin`       | all four levels           |

For connector `crm`, `tool:crm:write` can cover `search_contacts` and `create_contact`, but not `delete_contact` or `export_all`.

Unknown connectors and tools are denied in the default strict mode. This hierarchy applies to `gx.enforce()` and `gx.wrapTool()`, not to `createGrantexTool()`.

## Use the pre-built manifests that exist

`@grantex/sdk@0.3.13` exports 53 manifests. Salesforce, HubSpot, Jira, and Stripe are included:

```typescript theme={null}
import {
  jiraManifest,
  salesforceManifest,
  stripeManifest,
} from '@grantex/sdk/manifests';

gx.loadManifests([salesforceManifest, jiraManifest, stripeManifest]);
```

Inspect the version-pinned CLI catalog:

```bash theme={null}
npx --yes @grantex/cli@0.2.5 manifest list
npx --yes @grantex/cli@0.2.5 manifest show salesforce
```

A manifest is a permission mapping, not a connector implementation. It does not create a Salesforce, Jira, or Stripe tool, and it does not prove a third-party tool name matches the mapping.

## Define a custom manifest

```typescript theme={null}
import { Permission, ToolManifest } from '@grantex/sdk';

const billingManifest = new ToolManifest({
  connector: 'internal-billing',
  tools: {
    get_invoice: Permission.READ,
    create_invoice: Permission.WRITE,
    void_invoice: Permission.DELETE,
    run_period_close: Permission.ADMIN,
  },
});

gx.loadManifest(billingManifest);
```

The CLI can generate a draft from supported source patterns:

```bash theme={null}
npx --yes @grantex/cli@0.2.5 manifest generate agent_tools.py --connector internal-billing --out billing-manifest.json
```

Generation uses name-based inference. Review every discovered tool and permission; it is not a security-policy generator.

## Add the optional Grantex audit callback

`GrantexAuditHandler` writes through `client.audit.log()` only where the callback is attached.

```typescript theme={null}
import { Grantex } from '@grantex/sdk';
import { GrantexAuditHandler } from '@grantex/langchain';

const client = new Grantex({
  apiKey: process.env.GRANTEX_API_KEY,
});

const auditHandler = new GrantexAuditHandler({
  client,
  agentId: 'ag_crm_assistant',
  agentDid: 'did:grantex:ag_crm_assistant',
  principalId: 'user_alice',
  grantToken,
});

const result = await executor.invoke(
  { input: 'Find contacts in Bengaluru' },
  { callbacks: [auditHandler] },
);
```

The current `0.1.7` behavior is specific:

* `handleToolStart()` writes `tool:<toolName>` with `status: "success"` when invocation starts. That is not completion proof.
* `handleToolError()` writes generic `tool:error` with `status: "failure"`.
* The constructor decodes `grnt`, falling back to `jti`, for attribution. That decode is not authorization.
* Tool input and error text enter audit metadata. Apply an appropriate sensitive-data logging policy.
* Calls outside the callback-enabled executor or chain are not recorded by this handler.

The implemented audit client can list stored entries:

```typescript theme={null}
const page = await client.audit.list({
  agentId: 'ag_crm_assistant',
  pageSize: 50,
});
```

Audit and authorization are independent. Keep the scope wrapper at the protected boundary even when the callback is attached.

## Put the controls into a LangChain agent safely

For the agent-construction API used by your pinned LangChain application:

1. Obtain a grant for the correct principal, agent, audience, and scopes.
2. Build `createGrantexTool()` or `wrapTool()` protected instances.
3. Pass only protected instances for actions requiring Grantex authorization.
4. Attach `GrantexAuditHandler` only if its current event and metadata behavior fits your policy.
5. Test permitted, missing-scope, malformed, expired, and bypass cases.
6. Add current revocation-state handling if expiry alone is insufficient.

Do not call the entire agent protected because one tool is wrapped. Coverage is the set of execution boundaries where verification is installed and tested.

## Which LangChain permission approach should you use?

| Requirement                              | Use                                         |
| ---------------------------------------- | ------------------------------------------- |
| One exact scope per configured function  | `createGrantexTool()`                       |
| Connector permission hierarchy           | `gx.wrapTool()` plus `ToolManifest`         |
| Decision without wrapping                | `gx.enforce()`                              |
| Grant-aware tool-start and error records | Optional `GrantexAuditHandler`              |
| Immediate revocation awareness           | Online or synchronized state                |
| Framework-wide coverage                  | Inventory and wrap every protected boundary |

## Validation checklist

* The agent receives protected tool objects, not originals.
* Each protected function has the intended exact scope or manifest entry.
* Token issuer and audience match the receiving service.
* Missing, expired, malformed, and wrongly scoped tokens fail before the underlying function.
* Manifest names match the actual LangChain tool names.
* Audit callbacks do not capture prohibited data.
* Revocation freshness and key rotation meet the threat model.
* The pinned LangChain and Grantex versions pass application integration tests.

## Next steps

* [LangChain integration reference](/integrations/langchain)
* [Scope enforcement guide](/guides/scope-enforcement)
* [Custom manifests guide](/guides/custom-manifests)
* [Token verification and revocation boundaries](/guides/token-verification)
* [Current package versions and limitations](/release-status)
* [Grantex source repository](https://github.com/mishrasanjeev/grantex)
