e io sarò tua guida — Inferno, Canto I
Dante's guide through Hell. In the poem he leads the pilgrim down through the nine circles without losing the way; here he leads you through SharePoint's maze of sites, folders, documents, and metadata without losing the file. Same job description, fewer damned souls, marginally better error messages.
Virgil is a read and write agent over Microsoft SharePoint. Ask it to find a file, read one, or drop a new one into a document library — in plain language, and strictly as the person who asked. It does not see more than you can see. A good guide only takes you where you're allowed to go.
Built on the eve agent framework, deployed as a Next.js + eve hybrid on Vercel.
- Finds sites and files across SharePoint that the signed-in user is allowed to reach. No keyword archaeology, no "which of the fourteen 'Final_v3' folders."
- Reads the contents of text-based documents so it can answer, quote, or summarize — with a citation back to the file, because unsourced claims are how you end up in one of the lower circles.
- Writes new files (or overwrites existing ones) into a document library. Writes change your SharePoint, so every write waits for your explicit approval first. The guide points; you take the step.
Everything runs as the signed-in user through Microsoft Graph. Virgil borrows the user's identity for each request; it has no standing access of its own and cannot lead anyone somewhere they aren't authorized to be.
You sign in once with Microsoft (Auth.js + Microsoft Entra ID, in the Next app). That single sign-in does double duty: it establishes your browser session and yields the Microsoft Graph access token that the SharePoint tools use on your behalf. No second consent, no separate service credential.
- Auth.js signs you in and stores your access + refresh tokens server-side,
rotating the access token as it expires (
auth.ts). - The chat attaches your current token as a bearer to every eve request
(
app/_components/agent-chat.tsx→GET /api/graph-token). - The eve channel validates that token with a single Graph
/mecall — which both proves it and identifies you — then hands it to the tools for this request (agent/channels/eve.ts+agent/lib/token-store.ts). graphFetch(agent/lib/graph.ts) calls Graph as you. A401surfaces as a clear "sign in again" message.
Every action is scoped to what your account can do — Virgil has no standing access of its own.
Delegated Graph scopes requested at sign-in. Delegated means the app acts as the signed-in user — effective access is the intersection of the scope and the user's own rights, never tenant-wide (that would be the application permission of the same name).
| Scope | Why | Admin consent |
|---|---|---|
openid profile email |
Sign-in identity | No |
offline_access |
Refresh the token without re-prompting | No |
User.Read |
Identify the signed-in user (/me) |
No |
Sites.Read.All |
Discover & search the user's SharePoint sites | Yes |
Files.ReadWrite.All |
Read and upload files the user can access | Yes |
No site- or list-write scope is requested — the only write is a file upload,
covered by Files.ReadWrite.All. The two *.All scopes are high-impact
(Microsoft, July 2025) and need a one-time tenant-admin consent; until it's
granted, sign-in works and the user's own OneDrive responds, but cross-site
SharePoint calls return 403.
The model never talks to Graph directly. It calls four typed tools, and only the ones it actually invokes ever run:
| Tool | Reads / Writes | What it does |
|---|---|---|
list_sites |
read | Find sites the user can access; returns the site id other tools need. |
search_sharepoint |
read | Keyword/phrase search across files and list items; returns driveId+itemId. |
get_file_content |
read | Read a text-based file's contents; returns metadata + URL for binary formats. |
upload_file |
write | Create or overwrite a file. Approval-gated — confirms before every run. |
A Next.js + eve hybrid wired together via withEve() in next.config.ts.
One Vercel deploy serves the web chat and the agent from a single origin:
app/— Next.js App Router pages (the Web Chat UI)./eve/v1/*— eve channel routes, mounted bywithEve().
agent/ eve agent
agent.ts runtime config (model: anthropic/claude-sonnet-5)
instructions.md always-on system prompt (Virgil's identity + rules)
channels/eve.ts route auth: validate the Microsoft bearer via /me → user principal
lib/token-store.ts per-request handoff of the user's Graph token to the tools
lib/graph.ts graphFetch/graphJson (reads the token from the store)
tools/
list_sites.ts discover accessible sites
search_sharepoint.ts search files & list items
get_file_content.ts read a file as text
upload_file.ts create/overwrite a file (approval-gated)
auth.ts Auth.js (Microsoft Entra ID) + refresh-token rotation
proxy.ts route guard → redirect to /login when signed out
app/ Next.js App Router (Web Chat)
page.tsx signed-in guard → chat
login/page.tsx "Sign in with Microsoft"
api/auth/[...nextauth]/ Auth.js endpoints
api/graph-token/ returns the session's fresh Graph token to the browser
_components/ chat UI (useEveAgent + bearer header + sign-out)
next.config.ts withEve() — mounts the eve runtime into Next
vercel.json web + eve services, one origin
npm install
# The agent's terminal REPL (eve HMR dev server):
npm run dev:eve
# ...or the full web chat (Next.js with eve proxied in):
npm run dev| Command | What it does |
|---|---|
npm run dev |
Next.js dev server (Web Chat, with eve proxied in) |
npm run dev:eve |
eve dev server + terminal REPL |
npm run build |
Next.js production build |
npm run build:eve |
eve build |
npm run start |
Serve the Next.js production build |
npm run typecheck |
tsc type check |
- Node.js 24.x
- Entra admin center → App registrations → New registration. Name it
Virgil; account type Accounts in this organizational directory only; redirect URI Web →http://localhost:3000/api/auth/callback/microsoft-entra-id. - Copy the Application (client) ID and Directory (tenant) ID.
- Certificates & secrets → New client secret → copy the Value.
- API permissions → Add → Microsoft Graph → Delegated → add
openid,profile,email,offline_access,User.Read,Sites.Read.All,Files.ReadWrite.All, then Grant admin consent (a tenant admin does this once; without it, only the user's own OneDrive responds). These are delegated, so access is bounded by each signed-in user's own rights — not tenant-wide. - After deploying, add the production redirect URI:
https://<deployment-domain>/api/auth/callback/microsoft-entra-id.
Copy .env.example to .env.local and fill in:
AUTH_SECRET # openssl rand -base64 33
AUTH_MICROSOFT_ENTRA_ID_ID # client ID
AUTH_MICROSOFT_ENTRA_ID_SECRET # client secret value
AUTH_MICROSOFT_ENTRA_ID_ISSUER # https://login.microsoftonline.com/<tenant-id>/v2.0
The model authenticates through the Vercel AI Gateway: on Vercel a linked
project uses OIDC automatically; locally, run eve link or set
AI_GATEWAY_API_KEY (https://vercel.com/dashboard/ai/api-keys).
Deployed on Vercel. Set the four AUTH_* env vars (Production and Preview
scope — the same AUTH_SECRET is shared by the web and eve services), add the
production redirect URI to the Entra app, then vercel deploy. Preview URLs sit
behind Vercel Deployment Protection SSO — test on production or disable
protection for the preview.
Wired end to end and building clean: npm run typecheck and npm run build both
pass, the eve service registers all four tools with zero discovery errors, and
the Microsoft sign-in → bearer → Graph path is in place. Honest caveats: the
*.All scopes need the one-time admin consent to reach SharePoint sites; the
token store is in-process memory (a shared store like Vercel KV is the scale-up);
and there's no evals/ suite, per-tool tests, or channel rate limit yet.
Contributions welcome — issues, ideas, and PRs. See CONTRIBUTING.md for setup, the checks to run before opening a PR, and how the pieces fit together. Be kind; the underworld is unpleasant enough.
MIT © 2026 cr0ss. Open source — use it, fork it, ferry your own documents.