vecura-mcp-server
by NYB-AI
README.md
# vecura-mcp-server
> Working on this repo with an AI agent? Read [AGENTS.md](./AGENTS.md) first —
> it covers the architecture decisions and the upstream contract gotchas.
MCP server exposing Vecura's bio/drug-discovery models to LLM agents.
Thin by design: it authenticates the caller and forwards to existing services. The
schema and submission logic live in `vecura-app`, so a job submitted through MCP and
one submitted through the tool page take the same code path.
## Tools
| Tool | Status | Backing call |
|---|---|---|
| `FindTools` | ✅ | `GET {BACKEND}/api/v1/life-science-models/` |
| `GetToolDetail` | ✅ | `GET {APP}/api/agent-tools/model-schema/{id}` |
| `SubmitTool` | ✅ | `POST {APP}/api/agent-tools/tool-runs` |
| `GetJobStatus` | ✅ | `GET {APP}/api/workflow/{orgId}/tool-runs/{id}` |
## Auth
Clients send `Authorization: Bearer <JWT>` — the same token the frontend gets from
`GET {AUTH_SERVICE_URL}/v1/token`. The token is verified against the auth service's
JWKS (via Hono's built-in `jwk` middleware, with the key set cached here) and
forwarded downstream, so every job is created as the real user with their
org membership, role and credits. There is no service account.
`orgId` comes from the token's `session.activeOrganizationId`, which pins one
organization per token: switching orgs requires a new token.
Unlike `vecura-workflow` (which leaves `JWT_ISSUER`/`JWT_AUDIENCE` unset), this
server always verifies `iss` and `aud` — it is the internet-facing entry point.
## Configuration
| Env | Default | Purpose |
|---|---|---|
| `AUTH_SERVICE_URL` | `http://localhost:8080` | Better Auth service |
| `AUTH_JWKS_URL` | `${AUTH_SERVICE_URL}/v1/jwks` | JWKS endpoint |
| `AUTH_JWT_ISSUER` | origin of `AUTH_SERVICE_URL` | Expected `iss` |
| `AUTH_JWT_AUDIENCE` | origin of `AUTH_SERVICE_URL` | Expected `aud` |
| `VECURA_BACKEND_URL` | `http://localhost:8011` | Model catalog |
| `VECURA_APP_URL` | `http://localhost:3000` | Next.js app: schema + submit routes, and the `/api/workflow/*` proxy |
| `PORT` | `3100` | Listen port (3000 is vecura-app's dev server) |
The `iss`/`aud` defaults match Better Auth, which signs both as the auth service's
base-URL origin.
## Running
```sh
pnpm install
cp .env.example .env # optional — defaults in src/config.ts work for a local stack
pnpm dev # tsx watch
pnpm build && pnpm start
```
Smoke test (expects 401 without a token):
```sh
curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:3100/mcp \
-H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```
## Note on the MCP server lifecycle
`src/index.ts` builds a **new** `McpServer` per request. A module-level instance —
as shown in the hono-mcp docs — fails from the second request onward with
`Already connected to a transport`, because `Protocol.connect` refuses to rebind
(`@modelcontextprotocol/sdk` `shared/protocol.js:215-218`). Guarding that with
`isConnected()` instead would make every caller share one transport, which is not a
safe boundary when each request carries a different user's identity.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues