@frontmcp/skills 1.7.2 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/catalog/create-tool/examples/08-tool-with-provider-injection.md +2 -2
  2. package/catalog/create-tool/examples/22-tool-with-ui-html-template.md +7 -7
  3. package/catalog/create-tool/examples/24-tool-with-ui-csp-and-bridge.md +3 -3
  4. package/catalog/create-tool/references/decorator-options.md +1 -0
  5. package/catalog/create-tool/references/elicitation.md +9 -0
  6. package/catalog/create-tool/references/error-handling.md +10 -6
  7. package/catalog/create-tool/references/execution-context.md +7 -3
  8. package/catalog/create-tool/references/input-schema.md +2 -0
  9. package/catalog/create-tool/references/output-schema.md +6 -1
  10. package/catalog/create-tool/references/throttling.md +7 -8
  11. package/catalog/create-tool/references/ui-widgets.md +14 -1
  12. package/catalog/frontmcp-authorities/SKILL.md +1 -0
  13. package/catalog/frontmcp-authorities/references/custom-evaluators.md +13 -2
  14. package/catalog/frontmcp-authorities/references/rbac-abac-rebac.md +2 -0
  15. package/catalog/frontmcp-config/examples/configure-skills-http/inject-instructions.md +4 -4
  16. package/catalog/frontmcp-config/examples/configure-throttle/server-level-rate-limit.md +1 -2
  17. package/catalog/frontmcp-config/examples/configure-throttle-guard-config/full-guard-config.md +2 -3
  18. package/catalog/frontmcp-config/references/configure-auth-modes.md +36 -13
  19. package/catalog/frontmcp-config/references/configure-auth.md +1 -0
  20. package/catalog/frontmcp-config/references/configure-http.md +10 -2
  21. package/catalog/frontmcp-config/references/configure-skills-http.md +2 -2
  22. package/catalog/frontmcp-config/references/configure-throttle-guard-config.md +5 -5
  23. package/catalog/frontmcp-config/references/configure-throttle.md +97 -20
  24. package/catalog/frontmcp-deployment/SKILL.md +10 -6
  25. package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/worker-custom-domain.md +4 -7
  26. package/catalog/frontmcp-deployment/references/deploy-to-cloudflare-skills-only.md +4 -0
  27. package/catalog/frontmcp-deployment/references/deploy-to-cloudflare.md +82 -39
  28. package/catalog/frontmcp-development/examples/create-plugin-hooks/caching-with-around.md +7 -6
  29. package/catalog/frontmcp-development/examples/official-plugins/production-multi-plugin-setup.md +2 -2
  30. package/catalog/frontmcp-development/references/create-job.md +2 -2
  31. package/catalog/frontmcp-development/references/create-plugin-hooks.md +21 -17
  32. package/catalog/frontmcp-development/references/create-prompt.md +18 -16
  33. package/catalog/frontmcp-development/references/create-provider.md +3 -3
  34. package/catalog/frontmcp-development/references/create-resource.md +10 -8
  35. package/catalog/frontmcp-development/references/create-skill.md +7 -0
  36. package/catalog/frontmcp-development/references/decorators-guide.md +9 -8
  37. package/catalog/frontmcp-development/references/official-plugins.md +167 -6
  38. package/catalog/frontmcp-development/references/openapi-adapter.md +16 -0
  39. package/catalog/frontmcp-observability/references/metrics-endpoint.md +1 -1
  40. package/catalog/frontmcp-observability/references/structured-logging.md +35 -0
  41. package/catalog/frontmcp-setup/SKILL.md +8 -7
  42. package/catalog/frontmcp-testing/SKILL.md +12 -8
  43. package/catalog/frontmcp-testing/examples/test-e2e-handler/tool-call-and-error-e2e.md +3 -1
  44. package/catalog/frontmcp-testing/references/setup-testing.md +42 -9
  45. package/catalog/frontmcp-testing/references/test-e2e-handler.md +35 -0
  46. package/catalog/skills-manifest.json +4 -3
  47. package/package.json +1 -1
@@ -99,6 +99,41 @@ logging: {
99
99
 
100
100
  Redaction is recursive (handles nested objects) and case-insensitive.
101
101
 
102
+ ## CodeCall Audit Events
103
+
104
+ If `CodeCallPlugin` is installed, it emits a structured audit event at every meaningful point in a
105
+ script execution. The plugin registers the bridge itself, so the events reach your normal log
106
+ output at `info` under the `codecall:audit` prefix with no wiring.
107
+
108
+ Event families, all carrying `executionId` for correlation:
109
+
110
+ - `codecall:execution:start` / `:success` / `:failure` / `:timeout`
111
+ - `codecall:tool:call:start` / `:success` / `:failure`
112
+ - `codecall:security:self-reference` / `:access-denied` / `:ast-blocked`
113
+ - `codecall:search:performed` / `codecall:describe:performed` / `codecall:invoke:performed`
114
+
115
+ No event carries script source, tool arguments, tool results, or query text — a script is reduced
116
+ to `scriptHash` + `scriptLength`, a query to `queryLength`. Do not add those fields when building
117
+ on this; the omission is what makes the events safe to emit at `info`.
118
+
119
+ To route events elsewhere, subscribe rather than parsing logs:
120
+
121
+ ```typescript
122
+ import { AUDIT_EVENT_TYPES, AuditLoggerService, type AuditEvent } from '@frontmcp/plugin-codecall';
123
+
124
+ const audit = scope.providers.get(AuditLoggerService);
125
+ const unsubscribe = audit.subscribe((event: AuditEvent) => myShipper.send(event));
126
+ ```
127
+
128
+ Fan-out is synchronous and on the execution hot path — hand off to a queue, never do network I/O
129
+ inside the listener. Note the file transport writes only the message, so structured fields are
130
+ dropped there; use the structured transport or subscribe directly.
131
+
132
+ **Over stdio:** stdout carries the MCP JSON-RPC frames, so nothing else may be written there.
133
+ `runStdio()` redirects the stdout-bound `console` methods to stderr, and the NDJSON `stdout` sink
134
+ defaults to stderr when `FRONTMCP_STDIO` is set. Never configure a sink with an explicit
135
+ `stream: process.stdout` on a stdio server — an explicit stream overrides the guard.
136
+
102
137
  ## Examples
103
138
 
104
139
  | Example | Level | Description |
@@ -118,13 +118,14 @@ Entry point for project setup and scaffolding. This skill helps you find the rig
118
118
 
119
119
  ## Troubleshooting
120
120
 
121
- | Problem | Cause | Solution |
122
- | ------------------------ | -------------------------------- | --------------------------------------------------------------------- |
123
- | `frontmcp create` fails | Missing Node.js 24+ or npm/yarn | Install Node.js 24+ and ensure npm/yarn is available |
124
- | Server fails to start | `main.ts` missing default export | Add `export default MyServerClass` to `main.ts` |
125
- | Redis connection refused | Redis not running or wrong URL | Start Redis (`docker compose up redis`) or fix `REDIS_URL` env var |
126
- | Nx generator not found | `@frontmcp/nx` not installed | Run `npm install -D @frontmcp/nx` |
127
- | Skills not loading | Skills placed in wrong directory | Catalog skills go in top-level `skills/`, app skills in `src/skills/` |
121
+ | Problem | Cause | Solution |
122
+ | ------------------------------------------------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
123
+ | `frontmcp create` fails | Missing Node.js 24+ or npm/yarn | Install Node.js 24+ and ensure npm/yarn is available |
124
+ | Server fails to start | `main.ts` missing default export | Add `export default MyServerClass` to `main.ts` |
125
+ | Redis connection refused | Redis not running or wrong URL | Start Redis (`docker compose up redis`) or fix `REDIS_URL` env var |
126
+ | Nx generator not found | `@frontmcp/nx` not installed | Run `npm install -D @frontmcp/nx` |
127
+ | Skills not loading | Skills placed in wrong directory | Catalog skills go in top-level `skills/`, app skills in `src/skills/` |
128
+ | Build or start fails under Yarn 4 with peer-dependency or `TS2688` errors | Yarn defaults to Plug'n'Play, which enforces peer dependencies strictly | `frontmcp create --pm yarn` scaffolds `.yarnrc.yml` with `nodeLinker: node-modules`; add that file and reinstall for projects scaffolded by an older CLI |
128
129
 
129
130
  ## Examples
130
131
 
@@ -99,6 +99,7 @@ This is a router skill. Follow this order to pick a testing approach, then move
99
99
  | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
100
100
  | File naming | Always `.spec.ts` (not `.test.ts`); E2E uses `.e2e.spec.ts` |
101
101
  | File organization | Split E2E tests by app/feature: `e2e/calc.e2e.spec.ts`, `e2e/ecommerce.e2e.spec.ts`. Never put all tests in a single `server.e2e.spec.ts` |
102
+ | Environment | `frontmcp test` loads `.env` / `.env.local` into the Jest child, same precedence as `dev` (real environment wins over the files, config `env.shared` + `env.test` underneath). `--no-env` skips it |
102
103
  | Test runner | Standalone projects: use `frontmcp test` (auto-generates Jest/SWC config; discovers `src/**/*.spec.ts(x)`, `__tests__/**/*.spec.ts(x)`, and `e2e/**/*.e2e.spec.ts(x)`; transforms both `.ts` and `.tsx` with the automatic JSX runtime; transpiles ESM-only deps such as `jose` under npm, yarn AND pnpm's `node_modules/.pnpm/` store — add your own via `test.esmPackages` in `frontmcp.config.ts`; delegates to a user-provided `jest.config.{ts,js,mjs,cjs,json}` if present, which drops the injected ESM transforms). Nx monorepos: use `nx test <lib>` (resolves the project's `jest.config.ts`). Never invoke `jest --config ...` directly |
103
104
  | Coverage threshold | 95%+ across statements, branches, functions, lines |
104
105
  | Test descriptions | Plain English, no prefixes like "PT-001"; describe behavior not implementation |
@@ -145,14 +146,17 @@ This is a router skill. Follow this order to pick a testing approach, then move
145
146
 
146
147
  ## Troubleshooting
147
148
 
148
- | Problem | Cause | Solution |
149
- | ---------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
150
- | Jest not finding test files | Wrong file extension (`.test.ts` instead of `.spec.ts`) | Rename to `.spec.ts`; check `testMatch` in jest.config |
151
- | `SyntaxError: Unexpected token 'export'` | An ESM-only dependency is being ignored instead of transpiled | Add it to `test.esmPackages` in `frontmcp.config.ts`. With a hand-written `jest.config.ts`, use the pnpm-safe `transformIgnorePatterns` in [`setup-testing`](./references/setup-testing.md#jest-configuration) AND make sure `transform` matches `.js` (`^.+\.[tj]sx?$` + `allowJs`) — un-ignoring a file does nothing if no transform matches it |
152
- | Coverage below 95% | Untested error paths or conditional branches | Run `frontmcp test --coverage` and inspect uncovered lines in the report |
153
- | E2E test timeout | Server startup too slow or port conflict | Increase Jest timeout; use random port allocation |
154
- | DI resolution fails in tests | Provider not registered in test scope | Register mock providers before creating the test context |
155
- | Istanbul shows 0% on async methods | TypeScript source-map mismatch with Istanbul | Known issue with some TS compilation settings; verify coverage with actual test output |
149
+ | Problem | Cause | Solution |
150
+ | --------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
151
+ | Jest not finding test files | Wrong file extension (`.test.ts` instead of `.spec.ts`) | Rename to `.spec.ts`; check `testMatch` in jest.config |
152
+ | `SyntaxError: Unexpected token 'export'` | An ESM-only dependency is being ignored instead of transpiled | Add it to `test.esmPackages` in `frontmcp.config.ts`. With a hand-written `jest.config.ts`, use the pnpm-safe `transformIgnorePatterns` in [`setup-testing`](./references/setup-testing.md#jest-configuration) AND make sure `transform` matches `.js` (`^.+\.[tj]sx?$` + `allowJs`) — un-ignoring a file does nothing if no transform matches it |
153
+ | Coverage below 95% | Untested error paths or conditional branches | Run `frontmcp test --coverage` and inspect uncovered lines in the report |
154
+ | E2E test timeout | Server startup too slow or port conflict | Increase Jest timeout; use random port allocation |
155
+ | DI resolution fails in tests | Provider not registered in test scope | Register mock providers before creating the test context |
156
+ | Istanbul shows 0% on async methods | TypeScript source-map mismatch with Istanbul | Known issue with some TS compilation settings; verify coverage with actual test output |
157
+ | Specs see no `.env` values | An older CLI did not load `.env` for `frontmcp test`, only for `dev` | Upgrade the CLI — `frontmcp test` now loads `.env` / `.env.local` with the same precedence as `dev` (real environment wins, so CI secrets still override). Pass `--no-env` for a hermetic run |
158
+ | `Invalid first argument, true` at collection time | `test.skip(condition, reason)` reached Jest's `skip(name, fn)` | Upgrade the CLI — the Playwright signature is supported: `test.skip(!hasCredentials, 'credentials not set')` skips every test registered after it in the enclosing block |
159
+ | Every spec fails with `HTTP 404` after setting `http.entryPath` | The test client always connected to the server root | Upgrade the CLI — the client now follows the `entryPaths` a 404 reports, and `test.use({ entryPath: '/mcp' })` sets it explicitly |
156
160
 
157
161
  ## Examples
158
162
 
@@ -8,6 +8,7 @@ features:
8
8
  - 'Calling tools via `client.tools.call(name, args)` and asserting success with `toBeSuccessful()`'
9
9
  - 'Asserting text content with the `toHaveTextContent()` matcher'
10
10
  - 'Asserting error results with `toBeError()` for invalid input and unknown tools'
11
+ - 'Matching the tool error code: invalid input is an `isError` result with `_meta.code: "INVALID_INPUT"`, so `toBeError(''INVALID_INPUT'')` matches it (a numeric code matches JSON-RPC errors only)'
11
12
  - 'Testing edge cases like zero values'
12
13
  ---
13
14
 
@@ -53,7 +54,7 @@ describe('Tool Call E2E', () => {
53
54
 
54
55
  it('returns an error for invalid input', async () => {
55
56
  const result = await client.tools.call('add_numbers', { a: 'bad' });
56
- expect(result).toBeError();
57
+ expect(result).toBeError('INVALID_INPUT');
57
58
  });
58
59
 
59
60
  it('returns an error for a nonexistent tool', async () => {
@@ -74,6 +75,7 @@ describe('Tool Call E2E', () => {
74
75
  - Calling tools via `client.tools.call(name, args)` and asserting success with `toBeSuccessful()`
75
76
  - Asserting text content with the `toHaveTextContent()` matcher
76
77
  - Asserting error results with `toBeError()` for invalid input and unknown tools
78
+ - Matching the tool error code: invalid input is an `isError` result with `_meta.code: "INVALID_INPUT"`, so `toBeError('INVALID_INPUT')` matches it (a numeric code matches JSON-RPC errors only)
77
79
  - Testing edge cases like zero values
78
80
 
79
81
  ## Related
@@ -380,6 +380,39 @@ test('unauthenticated call is rejected', async ({ mcp }) => {
380
380
  });
381
381
  ```
382
382
 
383
+ ### Servers that serve MCP somewhere other than `/`
384
+
385
+ A server configured with `http: { entryPath: '/mcp' }` serves MCP at `/mcp`, and the test client follows it:
386
+
387
+ ```typescript
388
+ test.use({
389
+ server: './src/main.ts',
390
+ entryPath: '/mcp',
391
+ });
392
+ ```
393
+
394
+ You usually do not need to set this: when the client's first request 404s and the server reports where it does serve MCP (`{"error":"Not Found","entryPaths":["/mcp"]}`), the client reconnects there on its own, and a failure names the reported paths instead of a bare `HTTP 404`. `entryPath` applies to the MCP endpoint only — OAuth and discovery endpoints stay at the server root.
395
+
396
+ `baseUrl` may also be supplied alongside `server` to override the booted server's own URL (for a proxy, or a different host than it binds).
397
+
398
+ ### Gating a block on credentials
399
+
400
+ `test.skip(condition, reason)` is the Playwright signature and skips every test registered after it in the enclosing block:
401
+
402
+ ```typescript
403
+ const hasCredentials = Boolean(process.env.TWILIO_ACCOUNT_SID);
404
+
405
+ test.describe('against the live API', () => {
406
+ test.skip(!hasCredentials, 'credentials not set');
407
+
408
+ test('looks up a carrier', async ({ mcp }) => {
409
+ // ...
410
+ });
411
+ });
412
+ ```
413
+
414
+ A nested `test.describe` inherits an outer skip. The `(name, fn)` form still skips a single named test. `frontmcp test` loads `.env` / `.env.local`, so a credential in `.env` reaches the condition above.
415
+
383
416
  ## Custom MCP Matchers
384
417
 
385
418
  `@frontmcp/testing` provides Jest matchers tailored for MCP responses. Import `expect` from `@frontmcp/testing` instead of from Jest:
@@ -388,15 +421,15 @@ test('unauthenticated call is rejected', async ({ mcp }) => {
388
421
  import { expect } from '@frontmcp/testing';
389
422
  ```
390
423
 
391
- | Matcher | Asserts |
392
- | ------------------------- | ----------------------------------------------------- |
393
- | `toContainTool(name)` | Tools list includes a tool with the given name |
394
- | `toContainResource(uri)` | Resources list includes a resource with the given URI |
395
- | `toContainPrompt(name)` | Prompts list includes a prompt with the given name |
396
- | `toBeSuccessful()` | Tool call result is not an error |
397
- | `toBeError()` | Tool call result is an MCP error |
398
- | `toHaveTextContent(text)` | Result contains text content matching the string |
399
- | `toHaveMimeType(mime)` | Resource content has the expected MIME type |
424
+ | Matcher | Asserts |
425
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------------ |
426
+ | `toContainTool(name)` | Tools list includes a tool with the given name |
427
+ | `toContainResource(uri)` | Resources list includes a resource with the given URI |
428
+ | `toContainPrompt(name)` | Prompts list includes a prompt with the given name |
429
+ | `toBeSuccessful()` | Tool call result is not an error |
430
+ | `toBeError(code?)` | Result is an error; a string code matches `_meta.code` (`'INVALID_INPUT'`), a number matches a JSON-RPC error code |
431
+ | `toHaveTextContent(text)` | Result contains text content matching the string |
432
+ | `toHaveMimeType(mime)` | Resource content has the expected MIME type |
400
433
 
401
434
  ## Running Tests with Nx
402
435
 
@@ -67,6 +67,41 @@ describe('Server E2E', () => {
67
67
  });
68
68
  ```
69
69
 
70
+ ## Error Codes
71
+
72
+ Errors raised while a tool runs, including invalid input, come back as a tool result with `isError: true` and a string `_meta.code`, not as a JSON-RPC error. Match them with the string form, and keep the numeric form for JSON-RPC errors:
73
+
74
+ ```typescript
75
+ expect(await client.tools.call('add_numbers', { a: 5 })).toBeError('INVALID_INPUT');
76
+ expect(await client.tools.call('nonexistent_tool', {})).toBeError('TOOL_NOT_FOUND');
77
+ expect(await client.prompts.get('nonexistent_prompt')).toBeError(-32602);
78
+ ```
79
+
80
+ A tool without an `outputSchema` that returns a plain number sends `{ value: 8 }`, so assert `expect(result.json()).toEqual({ value: 8 })`.
81
+
82
+ ## Notifications and Progress
83
+
84
+ After `initialize`, `McpTestClient` opens the session's notification stream (a `GET` on the MCP endpoint) and records what the server sends there and on each request's own response:
85
+
86
+ ```typescript
87
+ it('records progress and log messages', async () => {
88
+ const progress = client.notifications.collectProgress(); // tools.call now sends a _meta.progressToken
89
+ const notifications = client.notifications.collect();
90
+ await client.raw.request({ jsonrpc: '2.0', id: 1, method: 'logging/setLevel', params: { level: 'info' } });
91
+
92
+ await client.tools.call('import_files', {});
93
+
94
+ await progress.waitForComplete(5000);
95
+ expect(progress.all.length).toBeGreaterThan(0);
96
+ await notifications.waitFor('notifications/message', 5000);
97
+ });
98
+ ```
99
+
100
+ - Pass `{ progressToken }` as the third argument of `tools.call()` to choose the token yourself.
101
+ - The server sends `notifications/message` (from `this.notify()`) only after `logging/setLevel`.
102
+ - Session-stream notifications can arrive after the call's result, so wait with `waitFor()` / `waitForComplete()` before asserting.
103
+ - `McpTestClient` speaks `2025-06-18` by default. `2026-07-28` has no `initialize` handshake, so `withProtocolVersion('2026-07-28')` makes `build()` throw.
104
+
70
105
  ## Examples
71
106
 
72
107
  | Example | Level | Description |
@@ -365,7 +365,7 @@
365
365
  "features": [
366
366
  "Restricting the widget's outbound `fetch` via `ui.csp.connectDomains` (emitted on the resource per #455)",
367
367
  "Opting the widget into cross-tool calls with `widgetAccessible: true` and using `window.FrontMcpBridge.callTool(name, args)` instead of host-specific APIs",
368
- "Embedding initial data into the widget's inline `<script>` safely via `ctx.helpers.jsonEmbed(...)` (escapes `</script>`)",
368
+ "Embedding initial data into the widget's inline `<script>` safely via `ctx.helpers.jsonEmbed(...)` (escapes `<`, `>` and `&`)",
369
369
  "Surfacing in-flight status via `invocationStatus.invoking` / `invoked` so the host UI shows feedback"
370
370
  ]
371
371
  },
@@ -1069,7 +1069,7 @@
1069
1069
  "Top-level `instructions` on `@FrontMcp` exposes a global system prompt to MCP clients",
1070
1070
  "`skillsConfig.injectInstructions: 'append'` adds the skill catalog summary after the user prompt",
1071
1071
  "Dynamic skills are picked up because the composer runs on every initialize request",
1072
- "Catalog summary is bounded at 16 KB with a truncation footer pointing at skill://catalog"
1072
+ "Catalog summary is bounded at 16 KB with a truncation footer pointing at skill://index.json"
1073
1073
  ]
1074
1074
  },
1075
1075
  {
@@ -1276,7 +1276,7 @@
1276
1276
  "tags": ["deployment", "json-rpc", "cloudflare", "worker", "custom", "domain"],
1277
1277
  "features": [
1278
1278
  "Using `frontmcp create --target cloudflare` to scaffold a project with `wrangler.toml` and deploy scripts",
1279
- "Adding a custom domain with `wrangler domains add` for production-ready URLs",
1279
+ "Adding a custom domain with `wrangler deploy --domain` for production-ready URLs",
1280
1280
  "End-to-end verification of both the health check and MCP JSON-RPC endpoint"
1281
1281
  ]
1282
1282
  },
@@ -3449,6 +3449,7 @@
3449
3449
  "Calling tools via `client.tools.call(name, args)` and asserting success with `toBeSuccessful()`",
3450
3450
  "Asserting text content with the `toHaveTextContent()` matcher",
3451
3451
  "Asserting error results with `toBeError()` for invalid input and unknown tools",
3452
+ "Matching the tool error code: invalid input is an `isError` result with `_meta.code: \"INVALID_INPUT\"`, so `toBeError('INVALID_INPUT')` matches it (a numeric code matches JSON-RPC errors only)",
3452
3453
  "Testing edge cases like zero values"
3453
3454
  ]
3454
3455
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/skills",
3
- "version": "1.7.2",
3
+ "version": "1.8.1",
4
4
  "description": "Curated skills catalog for FrontMCP projects",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",