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

# Make a first governed call

> Register and call a synthetic local tool without models or external services.

# Make a first governed call

> **Public documentation preview — incomplete.** This synthetic example is not proof of the full installed-consumer matrix, release readiness or live-provider compatibility. The package remains private and unpublished.

After [installing a verified candidate](/start/install), save this as `first-call.mjs` in your fresh ESM consumer. It uses only the core package and a synthetic in-process handler. No model, MCP server, credential, or external network request is required.

```js theme={null}
import { createRouter } from '@surf-lab/mcprouter';

const router = createRouter({
  policy: {
    eligibility: tool => tool.name === 'synthetic_balance',
    authorize: (call, context) =>
      call.descriptor.name === 'synthetic_balance' &&
      context.principal === 'demo-host'
        ? { decision: 'allow' }
        : { decision: 'deny' },
    revision: () => 1,
  },
});

try {
  const [tool] = await router.registerTools([{
    localKey: 'balance',
    descriptor: {
      name: 'synthetic_balance',
      inputSchema: { type: 'object', properties: {}, additionalProperties: false },
    },
    handler: () => ({ content: [], structuredContent: { balance: 100 } }),
  }]);

  const result = await router.call({
    toolId: tool.toolId,
    arguments: {},
    context: { principal: 'demo-host' },
    expectedSnapshot: {
      schemaHash: tool.schemaHash,
      bindingGeneration: tool.bindingGeneration,
    },
  });
  console.log({ status: result.status, sideEffectState: result.sideEffectState });
} finally {
  await router.close();
}
```

Run `node first-call.mjs` after installation. This exact snippet was exercised locally against a previously installed development candidate: its synthetic result reported `status: 'executed'` and `sideEffectState: 'completed'`. That focused check does **not** establish a freshly packed candidate, an offline-isolated runtime matrix or a live-provider result. Removing policy means default-deny with zero handler dispatch, but that negative case is not tested by this site's focused snippet check. The `demo-host` identity is a local fixture: production callers must authenticate identity and authorize the actual resource, tenant and arguments. Keep `close()` in `finally` and check the returned status rather than assuming every call succeeds.

Next: [choose a connected integration path](/start/choose-integration).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.