@frontmcp/skills 1.1.2 → 1.2.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.
- package/catalog/TEMPLATE.md +16 -11
- package/catalog/frontmcp-authorities/SKILL.md +116 -11
- package/catalog/frontmcp-authorities/references/authority-profiles.md +39 -36
- package/catalog/frontmcp-authorities/references/claims-mapping.md +7 -0
- package/catalog/frontmcp-authorities/references/custom-evaluators.md +63 -14
- package/catalog/frontmcp-channels/SKILL.md +36 -0
- package/catalog/frontmcp-channels/examples/channel-sources/file-watcher.md +8 -2
- package/catalog/frontmcp-channels/examples/channel-sources/replay-buffer.md +111 -30
- package/catalog/frontmcp-channels/examples/channel-two-way/whatsapp-bridge.md +45 -3
- package/catalog/frontmcp-channels/references/channel-sources.md +11 -3
- package/catalog/frontmcp-channels/references/channel-two-way.md +60 -89
- package/catalog/frontmcp-config/SKILL.md +111 -8
- package/catalog/frontmcp-config/examples/configure-auth-modes/local-self-signed-tokens.md +4 -4
- package/catalog/frontmcp-config/examples/configure-auth-modes/remote-enterprise-oauth.md +7 -1
- package/catalog/frontmcp-config/examples/configure-deployment-targets/distributed-ha-config.md +1 -1
- package/catalog/frontmcp-config/examples/configure-deployment-targets/json-schema-ide-support.md +1 -1
- package/catalog/frontmcp-config/examples/configure-deployment-targets/multi-target-with-security.md +12 -9
- package/catalog/frontmcp-config/examples/configure-http/cors-restricted-origins.md +2 -2
- package/catalog/frontmcp-config/examples/configure-http/entry-path-reverse-proxy.md +1 -1
- package/catalog/frontmcp-config/examples/configure-security-headers/csp-report-only.md +1 -1
- package/catalog/frontmcp-config/examples/configure-security-headers/full-production-headers.md +1 -1
- package/catalog/frontmcp-config/examples/configure-skills-http/audit-log-basic.md +76 -0
- package/catalog/frontmcp-config/examples/configure-skills-http/audit-log-redis.md +116 -0
- package/catalog/frontmcp-config/examples/configure-skills-http/inject-instructions.md +59 -0
- package/catalog/frontmcp-config/references/configure-auth-modes.md +5 -5
- package/catalog/frontmcp-config/references/configure-deployment-targets.md +27 -24
- package/catalog/frontmcp-config/references/configure-http.md +14 -10
- package/catalog/frontmcp-config/references/configure-security-headers.md +2 -2
- package/catalog/frontmcp-config/references/configure-session.md +25 -25
- package/catalog/frontmcp-config/references/configure-skills-http.md +157 -0
- package/catalog/frontmcp-config/references/configure-throttle.md +1 -1
- package/catalog/frontmcp-config/references/configure-transport.md +2 -2
- package/catalog/frontmcp-deployment/SKILL.md +112 -9
- package/catalog/frontmcp-deployment/examples/build-for-browser/browser-build-with-custom-entry.md +23 -11
- package/catalog/frontmcp-deployment/examples/build-for-browser/browser-crypto-and-storage.md +44 -17
- package/catalog/frontmcp-deployment/examples/build-for-browser/react-provider-setup.md +53 -21
- package/catalog/frontmcp-deployment/examples/build-for-cli/cli-binary-build.md +1 -1
- package/catalog/frontmcp-deployment/examples/build-for-cli/unix-socket-daemon.md +1 -1
- package/catalog/frontmcp-deployment/examples/build-for-mcpb/mcpb-bundle-build.md +1 -1
- package/catalog/frontmcp-deployment/examples/build-for-sdk/connect-openai.md +1 -1
- package/catalog/frontmcp-deployment/examples/build-for-sdk/multi-platform-connect.md +1 -1
- package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/basic-worker-deploy.md +7 -8
- package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/worker-custom-domain.md +8 -6
- package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/worker-with-kv-storage.md +5 -4
- package/catalog/frontmcp-deployment/examples/deploy-to-lambda/cdk-deployment.md +8 -5
- package/catalog/frontmcp-deployment/examples/deploy-to-lambda/lambda-handler-with-cors.md +20 -18
- package/catalog/frontmcp-deployment/examples/deploy-to-lambda/sam-template-basic.md +8 -5
- package/catalog/frontmcp-deployment/examples/deploy-to-node/docker-compose-with-redis.md +3 -3
- package/catalog/frontmcp-deployment/examples/deploy-to-node/pm2-with-nginx.md +1 -1
- package/catalog/frontmcp-deployment/examples/deploy-to-node/resource-limits.md +2 -2
- package/catalog/frontmcp-deployment/examples/deploy-to-node-dockerfile/basic-multistage-dockerfile.md +2 -2
- package/catalog/frontmcp-deployment/examples/deploy-to-node-dockerfile/secure-nonroot-dockerfile.md +1 -1
- package/catalog/frontmcp-deployment/examples/deploy-to-vercel/vercel-mcp-endpoint-test.md +23 -21
- package/catalog/frontmcp-deployment/examples/deploy-to-vercel/vercel-with-kv.md +25 -22
- package/catalog/frontmcp-deployment/examples/deploy-to-vercel/vercel-with-skills-cache.md +23 -30
- package/catalog/frontmcp-deployment/examples/deploy-to-vercel-config/minimal-vercel-config.md +52 -28
- package/catalog/frontmcp-deployment/examples/deploy-to-vercel-config/vercel-config-with-security-headers.md +32 -55
- package/catalog/frontmcp-deployment/examples/mcp-client-integration/http-remote.md +9 -0
- package/catalog/frontmcp-deployment/references/build-for-browser.md +40 -17
- package/catalog/frontmcp-deployment/references/build-for-cli.md +8 -8
- package/catalog/frontmcp-deployment/references/deploy-to-cloudflare.md +43 -24
- package/catalog/frontmcp-deployment/references/deploy-to-lambda.md +36 -25
- package/catalog/frontmcp-deployment/references/deploy-to-node-dockerfile.md +56 -14
- package/catalog/frontmcp-deployment/references/deploy-to-node.md +9 -6
- package/catalog/frontmcp-deployment/references/deploy-to-vercel-config.md +57 -58
- package/catalog/frontmcp-deployment/references/deploy-to-vercel.md +49 -59
- package/catalog/frontmcp-deployment/references/mcp-client-integration.md +2 -0
- package/catalog/frontmcp-development/SKILL.md +186 -11
- package/catalog/frontmcp-development/examples/create-agent/custom-multi-pass-agent.md +1 -1
- package/catalog/frontmcp-development/examples/create-agent/nested-agents-with-swarm.md +30 -27
- package/catalog/frontmcp-development/examples/create-job/job-with-permissions.md +13 -8
- package/catalog/frontmcp-development/examples/create-provider/basic-database-provider.md +33 -23
- package/catalog/frontmcp-development/examples/create-provider/config-and-api-providers.md +19 -10
- package/catalog/frontmcp-development/examples/create-tool/tool-with-rate-limiting-and-progress.md +3 -3
- package/catalog/frontmcp-development/examples/create-workflow/webhook-triggered-workflow.md +6 -4
- package/catalog/frontmcp-development/examples/decorators-guide/agent-skill-job-workflow.md +1 -1
- package/catalog/frontmcp-development/examples/decorators-guide/basic-server-with-app-and-tools.md +13 -8
- package/catalog/frontmcp-development/examples/decorators-guide/multi-app-with-plugins-and-providers.md +50 -23
- package/catalog/frontmcp-development/references/create-agent.md +47 -30
- package/catalog/frontmcp-development/references/create-job.md +69 -54
- package/catalog/frontmcp-development/references/create-plugin-hooks.md +45 -28
- package/catalog/frontmcp-development/references/create-plugin.md +10 -8
- package/catalog/frontmcp-development/references/create-prompt.md +3 -3
- package/catalog/frontmcp-development/references/create-provider.md +91 -51
- package/catalog/frontmcp-development/references/create-resource.md +3 -3
- package/catalog/frontmcp-development/references/create-skill.md +2 -2
- package/catalog/frontmcp-development/references/create-tool.md +7 -7
- package/catalog/frontmcp-development/references/create-workflow.md +8 -10
- package/catalog/frontmcp-development/references/decorators-guide.md +92 -56
- package/catalog/frontmcp-development/references/official-plugins.md +4 -3
- package/catalog/frontmcp-development/references/openapi-adapter.md +1 -1
- package/catalog/frontmcp-extensibility/SKILL.md +70 -10
- package/catalog/frontmcp-extensibility/examples/skill-audit-log/custom-store.md +197 -0
- package/catalog/frontmcp-extensibility/examples/skill-audit-log/verify-chain.md +68 -0
- package/catalog/frontmcp-extensibility/examples/vectoriadb/product-catalog-search.md +3 -5
- package/catalog/frontmcp-extensibility/examples/vectoriadb/semantic-search-with-persistence.md +4 -11
- package/catalog/frontmcp-extensibility/examples/vectoriadb/tfidf-keyword-search.md +41 -30
- package/catalog/frontmcp-extensibility/references/skill-audit-log.md +233 -0
- package/catalog/frontmcp-extensibility/references/vectoriadb.md +73 -63
- package/catalog/frontmcp-guides/SKILL.md +84 -27
- package/catalog/frontmcp-guides/examples/example-knowledge-base/agent-and-plugin.md +72 -62
- package/catalog/frontmcp-guides/examples/example-knowledge-base/vector-search-and-resources.md +32 -43
- package/catalog/frontmcp-guides/examples/example-task-manager/auth-and-crud-tools.md +24 -17
- package/catalog/frontmcp-guides/examples/example-task-manager/authenticated-e2e-tests.md +23 -21
- package/catalog/frontmcp-guides/examples/example-task-manager/redis-provider-with-di.md +47 -39
- package/catalog/frontmcp-guides/examples/example-weather-api/server-and-app-setup.md +16 -6
- package/catalog/frontmcp-guides/examples/example-weather-api/unit-and-e2e-tests.md +9 -8
- package/catalog/frontmcp-guides/references/example-knowledge-base.md +192 -265
- package/catalog/frontmcp-guides/references/example-task-manager.md +60 -54
- package/catalog/frontmcp-guides/references/example-weather-api.md +22 -24
- package/catalog/frontmcp-observability/SKILL.md +66 -2
- package/catalog/frontmcp-observability/examples/telemetry-api/skill-counters.md +100 -0
- package/catalog/frontmcp-observability/examples/tracing-setup/production-tracing.md +7 -2
- package/catalog/frontmcp-observability/examples/vendor-integrations/coralogix-setup.md +6 -2
- package/catalog/frontmcp-observability/references/telemetry-api.md +72 -8
- package/catalog/frontmcp-observability/references/testing-observability.md +33 -49
- package/catalog/frontmcp-observability/references/tracing-setup.md +12 -5
- package/catalog/frontmcp-observability/references/vendor-integrations.md +46 -1
- package/catalog/frontmcp-production-readiness/SKILL.md +134 -3
- package/catalog/frontmcp-production-readiness/examples/common-checklist/caching-and-performance.md +57 -36
- package/catalog/frontmcp-production-readiness/examples/common-checklist/observability-setup.md +1 -1
- package/catalog/frontmcp-production-readiness/examples/common-checklist/security-hardening.md +102 -6
- package/catalog/frontmcp-production-readiness/examples/production-cli-daemon/daemon-socket-config.md +2 -1
- package/catalog/frontmcp-production-readiness/examples/production-cli-daemon/graceful-shutdown-cleanup.md +66 -58
- package/catalog/frontmcp-production-readiness/examples/production-cli-daemon/security-and-permissions.md +5 -3
- package/catalog/frontmcp-production-readiness/examples/production-cloudflare/durable-objects-state.md +2 -1
- package/catalog/frontmcp-production-readiness/examples/production-cloudflare/wrangler-config.md +55 -76
- package/catalog/frontmcp-production-readiness/examples/production-lambda/cold-start-connection-reuse.md +43 -40
- package/catalog/frontmcp-production-readiness/examples/production-lambda/sam-template.md +63 -94
- package/catalog/frontmcp-production-readiness/examples/production-lambda/scaling-and-monitoring.md +28 -18
- package/catalog/frontmcp-production-readiness/examples/production-node-sdk/multi-instance-cleanup.md +29 -14
- package/catalog/frontmcp-production-readiness/examples/production-node-server/graceful-shutdown.md +58 -42
- package/catalog/frontmcp-production-readiness/examples/production-node-server/redis-session-scaling.md +5 -2
- package/catalog/frontmcp-production-readiness/examples/production-vercel/cold-start-optimization.md +41 -24
- package/catalog/frontmcp-production-readiness/examples/production-vercel/vercel-edge-config.md +56 -65
- package/catalog/frontmcp-production-readiness/references/common-checklist.md +17 -5
- package/catalog/frontmcp-production-readiness/references/production-cli-daemon.md +5 -5
- package/catalog/frontmcp-production-readiness/references/production-cloudflare.md +5 -5
- package/catalog/frontmcp-production-readiness/references/production-lambda.md +5 -5
- package/catalog/frontmcp-production-readiness/references/production-node-sdk.md +5 -5
- package/catalog/frontmcp-production-readiness/references/production-node-server.md +1 -1
- package/catalog/frontmcp-production-readiness/references/production-vercel.md +5 -5
- package/catalog/frontmcp-setup/SKILL.md +88 -0
- package/catalog/frontmcp-setup/examples/project-structure-nx/nx-workspace-with-apps.md +10 -4
- package/catalog/frontmcp-setup/examples/project-structure-standalone/dev-workflow-commands.md +21 -8
- package/catalog/frontmcp-setup/examples/readme-guide/node-server-readme.md +3 -3
- package/catalog/frontmcp-setup/references/multi-app-composition.md +4 -3
- package/catalog/frontmcp-setup/references/project-structure-nx.md +15 -6
- package/catalog/frontmcp-setup/references/project-structure-standalone.md +18 -15
- package/catalog/frontmcp-setup/references/readme-guide.md +1 -1
- package/catalog/frontmcp-setup/references/setup-project.md +19 -5
- package/catalog/frontmcp-setup/references/setup-redis.md +27 -39
- package/catalog/frontmcp-setup/references/setup-sqlite.md +25 -18
- package/catalog/frontmcp-testing/SKILL.md +102 -15
- package/catalog/frontmcp-testing/examples/setup-testing/unit-test-tool-resource-prompt.md +3 -3
- package/catalog/frontmcp-testing/examples/test-auth/oauth-flow-test.md +50 -39
- package/catalog/frontmcp-testing/examples/test-auth/role-based-access-test.md +52 -29
- package/catalog/frontmcp-testing/examples/test-auth/token-factory-test.md +37 -20
- package/catalog/frontmcp-testing/examples/test-direct-client/basic-create-test.md +25 -15
- package/catalog/frontmcp-testing/examples/test-direct-client/openai-claude-format-test.md +27 -21
- package/catalog/frontmcp-testing/examples/test-e2e-handler/basic-e2e-test.md +29 -20
- package/catalog/frontmcp-testing/examples/test-e2e-handler/manual-client-with-transport.md +5 -3
- package/catalog/frontmcp-testing/examples/test-e2e-handler/tool-call-and-error-e2e.md +35 -26
- package/catalog/frontmcp-testing/examples/test-tool-unit/basic-tool-test.md +8 -3
- package/catalog/frontmcp-testing/examples/test-tool-unit/schema-validation-test.md +4 -1
- package/catalog/frontmcp-testing/examples/test-tool-unit/tool-error-handling-test.md +6 -3
- package/catalog/frontmcp-testing/references/setup-testing.md +35 -39
- package/catalog/frontmcp-testing/references/test-auth.md +86 -43
- package/catalog/frontmcp-testing/references/test-browser-build.md +1 -1
- package/catalog/frontmcp-testing/references/test-direct-client.md +29 -19
- package/catalog/frontmcp-testing/references/test-e2e-handler.md +31 -19
- package/catalog/frontmcp-testing/references/test-tool-unit.md +6 -2
- package/catalog/skills-manifest.json +428 -339
- package/package.json +1 -1
- package/src/manifest.d.ts +13 -0
- package/src/manifest.js.map +1 -1
|
@@ -56,97 +56,79 @@ export class IngestionApp {}
|
|
|
56
56
|
|
|
57
57
|
### Provider: Vector Store
|
|
58
58
|
|
|
59
|
+
This example uses the in-tree `vectoriadb` package (already a runtime dep in the SDK). The provider class is its own DI token — tools inject it via `this.get(VectorStoreProvider)`.
|
|
60
|
+
|
|
59
61
|
```typescript
|
|
60
62
|
// src/ingestion/providers/vector-store.provider.ts
|
|
61
|
-
import type
|
|
62
|
-
|
|
63
|
+
import { FileStorageAdapter, VectoriaDB, type DocumentMetadata } from 'vectoriadb';
|
|
64
|
+
|
|
65
|
+
import { Provider, ProviderScope } from '@frontmcp/sdk';
|
|
63
66
|
|
|
64
|
-
export interface
|
|
65
|
-
id: string;
|
|
67
|
+
export interface DocChunk extends DocumentMetadata {
|
|
66
68
|
documentId: string;
|
|
69
|
+
title: string;
|
|
70
|
+
chunkIndex: number;
|
|
67
71
|
content: string;
|
|
68
|
-
|
|
69
|
-
metadata: Record<string, string>;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface VectorStore {
|
|
73
|
-
upsert(chunks: DocumentChunk[]): Promise<void>;
|
|
74
|
-
search(embedding: number[], topK: number): Promise<DocumentChunk[]>;
|
|
75
|
-
getByDocumentId(documentId: string): Promise<DocumentChunk[]>;
|
|
76
|
-
deleteByDocumentId(documentId: string): Promise<void>;
|
|
72
|
+
tags: string;
|
|
77
73
|
}
|
|
78
74
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
75
|
+
@Provider({ name: 'vector-store', scope: ProviderScope.GLOBAL })
|
|
76
|
+
export class VectorStoreProvider {
|
|
77
|
+
private readonly db: VectoriaDB<DocChunk>;
|
|
78
|
+
private readonly ready: Promise<void>;
|
|
79
|
+
|
|
80
|
+
constructor() {
|
|
81
|
+
this.db = new VectoriaDB<DocChunk>({
|
|
82
|
+
modelName: 'Xenova/all-MiniLM-L6-v2',
|
|
83
|
+
cacheDir: './.cache/transformers',
|
|
84
|
+
useHNSW: true,
|
|
85
|
+
defaultSimilarityThreshold: 0.3,
|
|
86
|
+
defaultTopK: 10,
|
|
87
|
+
storageAdapter: new FileStorageAdapter({ cacheDir: './.cache/kb-vectors' }),
|
|
88
|
+
});
|
|
89
|
+
this.ready = this.db.initialize();
|
|
93
90
|
}
|
|
94
91
|
|
|
95
|
-
async upsert(chunks:
|
|
96
|
-
await this.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
values: c.embedding,
|
|
100
|
-
metadata: { ...c.metadata, documentId: c.documentId, content: c.content },
|
|
101
|
-
})),
|
|
102
|
-
);
|
|
92
|
+
async upsert(chunks: Array<{ id: string; text: string; metadata: DocChunk }>): Promise<void> {
|
|
93
|
+
await this.ready;
|
|
94
|
+
await this.db.addMany(chunks);
|
|
95
|
+
await this.db.saveToStorage();
|
|
103
96
|
}
|
|
104
97
|
|
|
105
|
-
async search(
|
|
106
|
-
|
|
107
|
-
return
|
|
108
|
-
id: m.id as string,
|
|
109
|
-
documentId: (m.metadata as Record<string, string>).documentId,
|
|
110
|
-
content: (m.metadata as Record<string, string>).content,
|
|
111
|
-
embedding: m.values as number[],
|
|
112
|
-
metadata: m.metadata as Record<string, string>,
|
|
113
|
-
}));
|
|
98
|
+
async search(query: string, topK: number) {
|
|
99
|
+
await this.ready;
|
|
100
|
+
return this.db.search(query, { topK });
|
|
114
101
|
}
|
|
115
102
|
|
|
116
|
-
async getByDocumentId(documentId: string)
|
|
117
|
-
|
|
118
|
-
|
|
103
|
+
async getByDocumentId(documentId: string) {
|
|
104
|
+
await this.ready;
|
|
105
|
+
// VectoriaDB supports metadata filters via the `filter` predicate
|
|
106
|
+
return this.db.search(documentId, {
|
|
119
107
|
topK: 100,
|
|
120
|
-
|
|
108
|
+
filter: (meta) => meta.documentId === documentId,
|
|
121
109
|
});
|
|
122
|
-
return results.matches.map((m: Record<string, unknown>) => ({
|
|
123
|
-
id: m.id as string,
|
|
124
|
-
documentId,
|
|
125
|
-
content: (m.metadata as Record<string, string>).content,
|
|
126
|
-
embedding: m.values as number[],
|
|
127
|
-
metadata: m.metadata as Record<string, string>,
|
|
128
|
-
}));
|
|
129
110
|
}
|
|
130
111
|
|
|
131
112
|
async deleteByDocumentId(documentId: string): Promise<void> {
|
|
132
|
-
await this.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
throw new Error('Implement with your vector DB provider (e.g., Pinecone, Weaviate, Qdrant)');
|
|
113
|
+
await this.ready;
|
|
114
|
+
const matches = await this.getByDocumentId(documentId);
|
|
115
|
+
for (const match of matches) {
|
|
116
|
+
this.db.remove(match.id);
|
|
117
|
+
}
|
|
118
|
+
await this.db.saveToStorage();
|
|
139
119
|
}
|
|
140
120
|
}
|
|
141
121
|
```
|
|
142
122
|
|
|
143
123
|
### Tool: Ingest Document
|
|
144
124
|
|
|
125
|
+
VectoriaDB owns embedding generation, so the tool only chunks the text and hands raw strings to the provider.
|
|
126
|
+
|
|
145
127
|
```typescript
|
|
146
128
|
// src/ingestion/tools/ingest-document.tool.ts
|
|
147
129
|
import { Tool, ToolContext, z } from '@frontmcp/sdk';
|
|
148
130
|
|
|
149
|
-
import {
|
|
131
|
+
import { VectorStoreProvider } from '../providers/vector-store.provider';
|
|
150
132
|
|
|
151
133
|
@Tool({
|
|
152
134
|
name: 'ingest_document',
|
|
@@ -165,35 +147,36 @@ import { VECTOR_STORE, type DocumentChunk } from '../providers/vector-store.prov
|
|
|
165
147
|
})
|
|
166
148
|
export class IngestDocumentTool extends ToolContext {
|
|
167
149
|
async execute(input: { documentId: string; title: string; content: string; tags: string[] }) {
|
|
168
|
-
const store = this.get(
|
|
150
|
+
const store = this.get(VectorStoreProvider);
|
|
169
151
|
|
|
170
152
|
this.mark('chunking');
|
|
171
153
|
const textChunks = this.chunkText(input.content, 512);
|
|
172
154
|
|
|
173
155
|
this.mark('embedding');
|
|
174
|
-
await this.
|
|
156
|
+
await this.progress(0, textChunks.length, 'Generating embeddings');
|
|
175
157
|
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
158
|
+
const docs = textChunks.map((text, i) => ({
|
|
159
|
+
id: `${input.documentId}-chunk-${i}`,
|
|
160
|
+
text,
|
|
161
|
+
metadata: {
|
|
180
162
|
id: `${input.documentId}-chunk-${i}`,
|
|
181
163
|
documentId: input.documentId,
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
164
|
+
title: input.title,
|
|
165
|
+
chunkIndex: i,
|
|
166
|
+
content: text,
|
|
167
|
+
tags: input.tags.join(','),
|
|
168
|
+
},
|
|
169
|
+
}));
|
|
188
170
|
|
|
189
171
|
this.mark('storing');
|
|
190
|
-
await store.upsert(
|
|
172
|
+
await store.upsert(docs);
|
|
173
|
+
await this.progress(textChunks.length, textChunks.length, 'Stored');
|
|
191
174
|
|
|
192
|
-
await this.notify(`Ingested "${input.title}" with ${
|
|
175
|
+
await this.notify(`Ingested "${input.title}" with ${docs.length} chunks`, 'info');
|
|
193
176
|
|
|
194
177
|
return {
|
|
195
178
|
documentId: input.documentId,
|
|
196
|
-
chunksCreated:
|
|
179
|
+
chunksCreated: docs.length,
|
|
197
180
|
title: input.title,
|
|
198
181
|
};
|
|
199
182
|
}
|
|
@@ -214,19 +197,6 @@ export class IngestDocumentTool extends ToolContext {
|
|
|
214
197
|
if (current.trim()) chunks.push(current.trim());
|
|
215
198
|
return chunks;
|
|
216
199
|
}
|
|
217
|
-
|
|
218
|
-
private async generateEmbedding(text: string): Promise<number[]> {
|
|
219
|
-
const response = await this.fetch('https://api.openai.com/v1/embeddings', {
|
|
220
|
-
method: 'POST',
|
|
221
|
-
headers: {
|
|
222
|
-
'Content-Type': 'application/json',
|
|
223
|
-
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
|
|
224
|
-
},
|
|
225
|
-
body: JSON.stringify({ input: text, model: 'text-embedding-3-small' }),
|
|
226
|
-
});
|
|
227
|
-
const data = await response.json();
|
|
228
|
-
return data.data[0].embedding;
|
|
229
|
-
}
|
|
230
200
|
}
|
|
231
201
|
```
|
|
232
202
|
|
|
@@ -260,7 +230,7 @@ export class SearchApp {}
|
|
|
260
230
|
// src/search/tools/search-docs.tool.ts
|
|
261
231
|
import { Tool, ToolContext, z } from '@frontmcp/sdk';
|
|
262
232
|
|
|
263
|
-
import {
|
|
233
|
+
import { VectorStoreProvider } from '../../ingestion/providers/vector-store.provider';
|
|
264
234
|
|
|
265
235
|
@Tool({
|
|
266
236
|
name: 'search_docs',
|
|
@@ -283,36 +253,21 @@ import { VECTOR_STORE } from '../../ingestion/providers/vector-store.provider';
|
|
|
283
253
|
})
|
|
284
254
|
export class SearchDocsTool extends ToolContext {
|
|
285
255
|
async execute(input: { query: string; topK: number }) {
|
|
286
|
-
const store = this.get(
|
|
287
|
-
|
|
288
|
-
this.mark('embedding-query');
|
|
289
|
-
const queryEmbedding = await this.generateQueryEmbedding(input.query);
|
|
256
|
+
const store = this.get(VectorStoreProvider);
|
|
290
257
|
|
|
258
|
+
// VectoriaDB handles embedding generation internally — pass the raw query.
|
|
291
259
|
this.mark('searching');
|
|
292
|
-
const
|
|
260
|
+
const matches = await store.search(input.query, input.topK);
|
|
293
261
|
|
|
294
|
-
const results =
|
|
295
|
-
documentId:
|
|
296
|
-
content:
|
|
297
|
-
score:
|
|
298
|
-
title:
|
|
262
|
+
const results = matches.map((m) => ({
|
|
263
|
+
documentId: m.metadata.documentId,
|
|
264
|
+
content: m.metadata.content,
|
|
265
|
+
score: m.score,
|
|
266
|
+
title: m.metadata.title ?? 'Untitled',
|
|
299
267
|
}));
|
|
300
268
|
|
|
301
269
|
return { results, total: results.length };
|
|
302
270
|
}
|
|
303
|
-
|
|
304
|
-
private async generateQueryEmbedding(query: string): Promise<number[]> {
|
|
305
|
-
const response = await this.fetch('https://api.openai.com/v1/embeddings', {
|
|
306
|
-
method: 'POST',
|
|
307
|
-
headers: {
|
|
308
|
-
'Content-Type': 'application/json',
|
|
309
|
-
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
|
|
310
|
-
},
|
|
311
|
-
body: JSON.stringify({ input: query, model: 'text-embedding-3-small' }),
|
|
312
|
-
});
|
|
313
|
-
const data = await response.json();
|
|
314
|
-
return data.data[0].embedding;
|
|
315
|
-
}
|
|
316
271
|
}
|
|
317
272
|
```
|
|
318
273
|
|
|
@@ -320,10 +275,9 @@ export class SearchDocsTool extends ToolContext {
|
|
|
320
275
|
|
|
321
276
|
```typescript
|
|
322
277
|
// src/search/resources/doc.resource.ts
|
|
323
|
-
import
|
|
324
|
-
import { ResourceContext, ResourceTemplate } from '@frontmcp/sdk';
|
|
278
|
+
import { ReadResourceResult, ResourceContext, ResourceNotFoundError, ResourceTemplate } from '@frontmcp/sdk';
|
|
325
279
|
|
|
326
|
-
import {
|
|
280
|
+
import { VectorStoreProvider } from '../../ingestion/providers/vector-store.provider';
|
|
327
281
|
|
|
328
282
|
@ResourceTemplate({
|
|
329
283
|
name: 'document',
|
|
@@ -333,20 +287,20 @@ import { VECTOR_STORE } from '../../ingestion/providers/vector-store.provider';
|
|
|
333
287
|
})
|
|
334
288
|
export class DocResource extends ResourceContext<{ documentId: string }> {
|
|
335
289
|
async execute(uri: string, params: { documentId: string }): Promise<ReadResourceResult> {
|
|
336
|
-
const store = this.get(
|
|
337
|
-
const
|
|
290
|
+
const store = this.get(VectorStoreProvider);
|
|
291
|
+
const matches = await store.getByDocumentId(params.documentId);
|
|
338
292
|
|
|
339
|
-
if (
|
|
340
|
-
|
|
293
|
+
if (matches.length === 0) {
|
|
294
|
+
// Use a typed MCP error so the protocol response carries the correct JSON-RPC code.
|
|
295
|
+
throw new ResourceNotFoundError(uri);
|
|
341
296
|
}
|
|
342
297
|
|
|
343
298
|
const document = {
|
|
344
299
|
documentId: params.documentId,
|
|
345
|
-
title:
|
|
346
|
-
chunks:
|
|
347
|
-
chunkIndex:
|
|
348
|
-
|
|
349
|
-
})),
|
|
300
|
+
title: matches[0].metadata.title ?? 'Untitled',
|
|
301
|
+
chunks: matches
|
|
302
|
+
.map((m) => ({ chunkIndex: m.metadata.chunkIndex, content: m.metadata.content }))
|
|
303
|
+
.sort((a, b) => a.chunkIndex - b.chunkIndex),
|
|
350
304
|
};
|
|
351
305
|
|
|
352
306
|
return {
|
|
@@ -384,6 +338,8 @@ export class ResearchApp {}
|
|
|
384
338
|
|
|
385
339
|
### Agent: Researcher
|
|
386
340
|
|
|
341
|
+
The framework drives the LLM tool-use loop via the `agents:call-agent` flow — the agent class doesn't override `execute()`. Configure iteration limits via `@Agent({ execution: { maxIterations } })`.
|
|
342
|
+
|
|
387
343
|
```typescript
|
|
388
344
|
// src/research/agents/researcher.agent.ts
|
|
389
345
|
import { Agent, AgentContext, z } from '@frontmcp/sdk';
|
|
@@ -412,47 +368,43 @@ import { SearchDocsTool } from '../../search/tools/search-docs.tool';
|
|
|
412
368
|
},
|
|
413
369
|
llm: {
|
|
414
370
|
provider: 'anthropic', // Any supported provider — 'anthropic', 'openai', etc.
|
|
415
|
-
model: 'claude-sonnet-4-
|
|
371
|
+
model: 'claude-sonnet-4-6', // Any supported model for the chosen provider
|
|
416
372
|
apiKey: { env: 'ANTHROPIC_API_KEY' },
|
|
417
373
|
maxTokens: 4096,
|
|
418
374
|
},
|
|
375
|
+
// Cap the inner tool-use loop. The framework — not your code — drives iteration.
|
|
376
|
+
execution: { maxIterations: 5 },
|
|
419
377
|
tools: [SearchDocsTool, IngestDocumentTool],
|
|
420
378
|
systemInstructions: `You are a research assistant with access to a knowledge base.
|
|
421
379
|
Your job is to:
|
|
422
380
|
1. Search the knowledge base for relevant documents using the search_docs tool.
|
|
423
381
|
2. Analyze the results and identify key themes.
|
|
424
|
-
3.
|
|
382
|
+
3. When depth is "deep", perform multiple searches with refined queries; for "shallow", a single search is enough.
|
|
425
383
|
4. Synthesize findings into a structured summary with source attribution.
|
|
426
|
-
Always cite which documents support your findings.`,
|
|
384
|
+
Always cite which documents support your findings, and return JSON matching the output schema.`,
|
|
427
385
|
})
|
|
428
|
-
export class ResearcherAgent extends AgentContext {
|
|
429
|
-
async execute(input: { topic: string; depth: 'shallow' | 'deep' }) {
|
|
430
|
-
const maxIterations = input.depth === 'deep' ? 5 : 2;
|
|
431
|
-
const prompt = [
|
|
432
|
-
`Research the following topic: "${input.topic}"`,
|
|
433
|
-
`Depth: ${input.depth} (max ${maxIterations} search iterations)`,
|
|
434
|
-
'Search the knowledge base, analyze results, and produce a structured summary.',
|
|
435
|
-
'Return your findings as JSON matching the output schema.',
|
|
436
|
-
].join('\n');
|
|
437
|
-
|
|
438
|
-
return this.run(prompt, { maxIterations });
|
|
439
|
-
}
|
|
440
|
-
}
|
|
386
|
+
export class ResearcherAgent extends AgentContext {}
|
|
441
387
|
```
|
|
442
388
|
|
|
443
389
|
---
|
|
444
390
|
|
|
445
391
|
## Plugin: Audit Log
|
|
446
392
|
|
|
393
|
+
Real plugins extend `DynamicPlugin<Options>` and attach to flows via the `FlowHooksOf` decorators (e.g. `@ToolHook.Will('execute')`, `@ToolHook.Did('execute')`). There is no `PluginHookContext`/`onToolExecute*` lifecycle.
|
|
394
|
+
|
|
447
395
|
```typescript
|
|
448
396
|
// src/plugins/audit-log.plugin.ts
|
|
449
|
-
import { Plugin,
|
|
397
|
+
import { DynamicPlugin, FlowCtxOf, Plugin, ToolHook } from '@frontmcp/sdk';
|
|
398
|
+
|
|
399
|
+
export interface AuditLogPluginOptions {
|
|
400
|
+
endpoint?: string;
|
|
401
|
+
}
|
|
450
402
|
|
|
451
403
|
@Plugin({
|
|
452
|
-
name: '
|
|
404
|
+
name: 'audit-log',
|
|
453
405
|
description: 'Logs all tool invocations for audit compliance',
|
|
454
406
|
})
|
|
455
|
-
export class AuditLogPlugin {
|
|
407
|
+
export class AuditLogPlugin extends DynamicPlugin<AuditLogPluginOptions> {
|
|
456
408
|
private readonly logs: Array<{
|
|
457
409
|
timestamp: string;
|
|
458
410
|
tool: string;
|
|
@@ -461,51 +413,64 @@ export class AuditLogPlugin {
|
|
|
461
413
|
success: boolean;
|
|
462
414
|
}> = [];
|
|
463
415
|
|
|
464
|
-
|
|
465
|
-
|
|
416
|
+
constructor(protected options: AuditLogPluginOptions = {}) {
|
|
417
|
+
super();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// `Will('execute')` runs immediately before the tool's execute() — record start time on the flow state.
|
|
421
|
+
@ToolHook.Will('execute', { priority: 100 })
|
|
422
|
+
async onWillExecute(flowCtx: FlowCtxOf<'tools:call-tool'>): Promise<void> {
|
|
423
|
+
flowCtx.state.set('audit:startTime', Date.now());
|
|
466
424
|
}
|
|
467
425
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
426
|
+
// `Did('execute')` runs after a successful execute() — compute duration and log success.
|
|
427
|
+
@ToolHook.Did('execute', { priority: 100 })
|
|
428
|
+
async onDidExecute(flowCtx: FlowCtxOf<'tools:call-tool'>): Promise<void> {
|
|
429
|
+
const startTime = flowCtx.state.get('audit:startTime') as number | undefined;
|
|
430
|
+
const duration = startTime ? Date.now() - startTime : 0;
|
|
431
|
+
const ctx = flowCtx.state.required.toolContext;
|
|
471
432
|
|
|
472
433
|
const entry = {
|
|
473
434
|
timestamp: new Date().toISOString(),
|
|
474
|
-
tool: ctx.
|
|
475
|
-
userId: ctx.
|
|
435
|
+
tool: ctx.metadata.name,
|
|
436
|
+
userId: (ctx.authInfo as any)?.user?.sub as string | undefined,
|
|
476
437
|
duration,
|
|
477
438
|
success: true,
|
|
478
439
|
};
|
|
479
440
|
this.logs.push(entry);
|
|
480
441
|
|
|
481
|
-
|
|
482
|
-
|
|
442
|
+
if (this.options.endpoint) {
|
|
443
|
+
// Audit logging should never block tool execution.
|
|
483
444
|
await ctx
|
|
484
|
-
.fetch(
|
|
445
|
+
.fetch(this.options.endpoint, {
|
|
485
446
|
method: 'POST',
|
|
486
447
|
headers: { 'Content-Type': 'application/json' },
|
|
487
448
|
body: JSON.stringify(entry),
|
|
488
449
|
})
|
|
489
|
-
.catch(() =>
|
|
490
|
-
// Audit logging should not block tool execution
|
|
491
|
-
});
|
|
450
|
+
.catch(() => undefined);
|
|
492
451
|
}
|
|
493
452
|
}
|
|
494
453
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
454
|
+
// `Around('execute')` wraps the call so we can capture errors as well.
|
|
455
|
+
@ToolHook.Around('execute', { priority: 100 })
|
|
456
|
+
async aroundExecute(flowCtx: FlowCtxOf<'tools:call-tool'>, next: () => Promise<unknown>): Promise<unknown> {
|
|
457
|
+
try {
|
|
458
|
+
return await next();
|
|
459
|
+
} catch (err) {
|
|
460
|
+
const startTime = flowCtx.state.get('audit:startTime') as number | undefined;
|
|
461
|
+
const ctx = flowCtx.state.required.toolContext;
|
|
462
|
+
this.logs.push({
|
|
463
|
+
timestamp: new Date().toISOString(),
|
|
464
|
+
tool: ctx.metadata.name,
|
|
465
|
+
userId: (ctx.authInfo as any)?.user?.sub as string | undefined,
|
|
466
|
+
duration: startTime ? Date.now() - startTime : 0,
|
|
467
|
+
success: false,
|
|
468
|
+
});
|
|
469
|
+
throw err;
|
|
470
|
+
}
|
|
506
471
|
}
|
|
507
472
|
|
|
508
|
-
getLogs(): typeof this.logs {
|
|
473
|
+
getLogs(): ReadonlyArray<(typeof this.logs)[number]> {
|
|
509
474
|
return [...this.logs];
|
|
510
475
|
}
|
|
511
476
|
}
|
|
@@ -515,75 +480,30 @@ export class AuditLogPlugin {
|
|
|
515
480
|
|
|
516
481
|
## Test: Researcher Agent
|
|
517
482
|
|
|
518
|
-
|
|
519
|
-
// test/researcher.agent.spec.ts
|
|
520
|
-
import { AgentContext } from '@frontmcp/sdk';
|
|
483
|
+
The agent class has no `execute()` to unit-test — the framework drives the LLM tool-use loop via the `agents:call-agent` flow. Cover the agent end-to-end: register it on a `TestServer`, call it via `client.tools.call('research_topic', ...)` (agents are exposed as tools), and assert on the structured output.
|
|
521
484
|
|
|
522
|
-
|
|
485
|
+
```typescript
|
|
486
|
+
// test/researcher.agent.e2e.spec.ts
|
|
487
|
+
import { McpTestClient, TestServer, TestTokenFactory } from '@frontmcp/testing';
|
|
523
488
|
|
|
524
|
-
describe('ResearcherAgent', () => {
|
|
525
|
-
let
|
|
489
|
+
describe('ResearcherAgent E2E', () => {
|
|
490
|
+
let client: McpTestClient;
|
|
491
|
+
let server: TestServer;
|
|
526
492
|
|
|
527
|
-
|
|
528
|
-
|
|
493
|
+
beforeAll(async () => {
|
|
494
|
+
server = await TestServer.start({ command: 'npx tsx src/main.ts' });
|
|
495
|
+
const token = await new TestTokenFactory().createTestToken({ sub: 'researcher-1', scopes: ['kb'] });
|
|
496
|
+
client = await McpTestClient.create({ baseUrl: server.info.baseUrl }).withToken(token).buildAndConnect();
|
|
529
497
|
});
|
|
530
498
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
summary: 'Key patterns include generics and type guards.',
|
|
535
|
-
sources: [{ documentId: 'doc-1', title: 'TS Handbook', relevance: 'high' }],
|
|
536
|
-
confidence: 'medium',
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
const ctx = {
|
|
540
|
-
run: runFn,
|
|
541
|
-
get: jest.fn(),
|
|
542
|
-
tryGet: jest.fn(),
|
|
543
|
-
fail: jest.fn((err: Error) => {
|
|
544
|
-
throw err;
|
|
545
|
-
}),
|
|
546
|
-
mark: jest.fn(),
|
|
547
|
-
notify: jest.fn(),
|
|
548
|
-
respondProgress: jest.fn(),
|
|
549
|
-
} as unknown as AgentContext;
|
|
550
|
-
Object.assign(agent, ctx);
|
|
551
|
-
|
|
552
|
-
const result = await agent.execute({
|
|
553
|
-
topic: 'TypeScript patterns',
|
|
554
|
-
depth: 'shallow',
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
expect(runFn).toHaveBeenCalledWith(expect.stringContaining('TypeScript patterns'), { maxIterations: 2 });
|
|
558
|
-
expect(result).toHaveProperty('summary');
|
|
559
|
-
expect(result).toHaveProperty('sources');
|
|
560
|
-
expect(result.confidence).toBe('medium');
|
|
499
|
+
afterAll(async () => {
|
|
500
|
+
await client.disconnect();
|
|
501
|
+
await server.stop();
|
|
561
502
|
});
|
|
562
503
|
|
|
563
|
-
it('
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
summary: 'Consensus, replication, and partition tolerance.',
|
|
567
|
-
sources: [],
|
|
568
|
-
confidence: 'low',
|
|
569
|
-
});
|
|
570
|
-
|
|
571
|
-
const ctx = {
|
|
572
|
-
run: runFn,
|
|
573
|
-
get: jest.fn(),
|
|
574
|
-
tryGet: jest.fn(),
|
|
575
|
-
fail: jest.fn((err: Error) => {
|
|
576
|
-
throw err;
|
|
577
|
-
}),
|
|
578
|
-
mark: jest.fn(),
|
|
579
|
-
notify: jest.fn(),
|
|
580
|
-
respondProgress: jest.fn(),
|
|
581
|
-
} as unknown as AgentContext;
|
|
582
|
-
Object.assign(agent, ctx);
|
|
583
|
-
|
|
584
|
-
await agent.execute({ topic: 'Distributed systems', depth: 'deep' });
|
|
585
|
-
|
|
586
|
-
expect(runFn).toHaveBeenCalledWith(expect.stringContaining('Distributed systems'), { maxIterations: 5 });
|
|
504
|
+
it('exposes the research_topic agent in the tool list', async () => {
|
|
505
|
+
const tools = await client.tools.list();
|
|
506
|
+
expect(tools.map((t) => t.name)).toContain('research_topic');
|
|
587
507
|
});
|
|
588
508
|
});
|
|
589
509
|
```
|
|
@@ -592,30 +512,40 @@ describe('ResearcherAgent', () => {
|
|
|
592
512
|
|
|
593
513
|
## Test: Audit Log Plugin
|
|
594
514
|
|
|
515
|
+
Test the hook methods directly with a minimal `FlowCtx` shape. The `state` object is a `Map`-like store and `state.required.toolContext` is the live `ToolContext` exposed to hooks.
|
|
516
|
+
|
|
595
517
|
```typescript
|
|
596
518
|
// test/audit-log.plugin.spec.ts
|
|
597
|
-
import type {
|
|
519
|
+
import type { FlowCtxOf } from '@frontmcp/sdk';
|
|
598
520
|
|
|
599
521
|
import { AuditLogPlugin } from '../src/plugins/audit-log.plugin';
|
|
600
522
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
523
|
+
type ToolFlowCtx = FlowCtxOf<'tools:call-tool'>;
|
|
524
|
+
|
|
525
|
+
function makeFlowCtx(toolName: string, userSub: string | undefined): ToolFlowCtx {
|
|
526
|
+
const map = new Map<string, unknown>();
|
|
527
|
+
const toolContext = {
|
|
528
|
+
metadata: { name: toolName },
|
|
529
|
+
authInfo: userSub ? { user: { sub: userSub } } : {},
|
|
530
|
+
fetch: jest.fn().mockResolvedValue(new Response(null, { status: 204 })),
|
|
531
|
+
};
|
|
532
|
+
return {
|
|
533
|
+
state: {
|
|
534
|
+
set: (k: string, v: unknown) => map.set(k, v),
|
|
535
|
+
get: (k: string) => map.get(k),
|
|
536
|
+
required: { toolContext },
|
|
537
|
+
},
|
|
538
|
+
rawInput: {},
|
|
539
|
+
} as unknown as ToolFlowCtx;
|
|
540
|
+
}
|
|
607
541
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
session: { userId: 'user-1' },
|
|
613
|
-
state: { set: (k: string, v: unknown) => state.set(k, v), get: (k: string) => state.get(k) },
|
|
614
|
-
fetch: jest.fn(),
|
|
615
|
-
} as unknown as PluginHookContext;
|
|
542
|
+
describe('AuditLogPlugin', () => {
|
|
543
|
+
it('records a successful tool execution', async () => {
|
|
544
|
+
const plugin = new AuditLogPlugin();
|
|
545
|
+
const flowCtx = makeFlowCtx('search_docs', 'user-1');
|
|
616
546
|
|
|
617
|
-
await plugin.
|
|
618
|
-
await plugin.
|
|
547
|
+
await plugin.onWillExecute(flowCtx);
|
|
548
|
+
await plugin.onDidExecute(flowCtx);
|
|
619
549
|
|
|
620
550
|
const logs = plugin.getLogs();
|
|
621
551
|
expect(logs).toHaveLength(1);
|
|
@@ -625,17 +555,14 @@ describe('AuditLogPlugin', () => {
|
|
|
625
555
|
expect(logs[0].duration).toBeGreaterThanOrEqual(0);
|
|
626
556
|
});
|
|
627
557
|
|
|
628
|
-
it('
|
|
629
|
-
const
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
fetch: jest.fn(),
|
|
635
|
-
} as unknown as PluginHookContext;
|
|
558
|
+
it('records a failed tool execution via aroundExecute', async () => {
|
|
559
|
+
const plugin = new AuditLogPlugin();
|
|
560
|
+
const flowCtx = makeFlowCtx('ingest_document', undefined);
|
|
561
|
+
|
|
562
|
+
await plugin.onWillExecute(flowCtx);
|
|
563
|
+
const failing = () => Promise.reject(new Error('boom'));
|
|
636
564
|
|
|
637
|
-
await plugin.
|
|
638
|
-
await plugin.onToolExecuteError(ctx);
|
|
565
|
+
await expect(plugin.aroundExecute(flowCtx, failing)).rejects.toThrow('boom');
|
|
639
566
|
|
|
640
567
|
const logs = plugin.getLogs();
|
|
641
568
|
expect(logs).toHaveLength(1);
|
|
@@ -647,10 +574,10 @@ describe('AuditLogPlugin', () => {
|
|
|
647
574
|
|
|
648
575
|
## Examples
|
|
649
576
|
|
|
650
|
-
| Example | Level | Description
|
|
651
|
-
| -------------------------------------------------------------------------------------------------- | ------------ |
|
|
652
|
-
| [`agent-and-plugin`](../examples/example-knowledge-base/agent-and-plugin.md) | Advanced | Shows an autonomous research agent with inner tools and
|
|
653
|
-
| [`multi-app-composition`](../examples/example-knowledge-base/multi-app-composition.md) | Basic | Shows how to compose multiple apps (Ingestion, Search, Research) into a single server with shared providers, plugins, and agent registration.
|
|
654
|
-
| [`vector-search-and-resources`](../examples/example-knowledge-base/vector-search-and-resources.md) | Intermediate | Shows a semantic search tool with embedding generation and a resource template for retrieving documents by ID using URI parameters.
|
|
577
|
+
| Example | Level | Description |
|
|
578
|
+
| -------------------------------------------------------------------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
579
|
+
| [`agent-and-plugin`](../examples/example-knowledge-base/agent-and-plugin.md) | Advanced | Shows an autonomous research agent with inner tools and a real `ToolHook`-based plugin that hooks into the `tools:call-tool` flow for audit logging. |
|
|
580
|
+
| [`multi-app-composition`](../examples/example-knowledge-base/multi-app-composition.md) | Basic | Shows how to compose multiple apps (Ingestion, Search, Research) into a single server with shared providers, plugins, and agent registration. |
|
|
581
|
+
| [`vector-search-and-resources`](../examples/example-knowledge-base/vector-search-and-resources.md) | Intermediate | Shows a semantic search tool with embedding generation and a resource template for retrieving documents by ID using URI parameters. |
|
|
655
582
|
|
|
656
583
|
> See all examples in [`examples/example-knowledge-base/`](../examples/example-knowledge-base/)
|