agent-well-known-express 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Agent Discovery Protocol Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,234 @@
1
+ # agent-well-known-express
2
+
3
+ Express middleware that auto-generates [Agent Discovery Protocol](https://github.com/user/agent-discovery-protocol/tree/main/spec) endpoints so any AI agent can discover and use your API at runtime.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install agent-well-known-express
9
+ ```
10
+
11
+ > **Peer dependency:** Express 4.x or 5.x must be installed in your project.
12
+
13
+ ## Quick Start
14
+
15
+ ```js
16
+ const express = require('express');
17
+ const { agentManifest } = require('agent-well-known-express');
18
+
19
+ const app = express();
20
+
21
+ app.use(agentManifest({
22
+ name: "Acme Email",
23
+ description: "Send and receive emails programmatically. Supports HTML content, attachments, and inbox management.",
24
+ base_url: "https://api.acme-email.com",
25
+ auth: {
26
+ type: "api_key",
27
+ header: "Authorization",
28
+ prefix: "Bearer",
29
+ setup_url: "https://acme-email.com/api-keys"
30
+ },
31
+ pricing: {
32
+ type: "freemium",
33
+ plans: [
34
+ { name: "Free", price: "$0/mo", limits: "100 emails/day" },
35
+ { name: "Pro", price: "$9/mo", limits: "Unlimited" }
36
+ ],
37
+ plans_url: "https://acme-email.com/pricing"
38
+ },
39
+ capabilities: [
40
+ {
41
+ name: "send_email",
42
+ description: "Send an email to one or more recipients with subject, body (plain text or HTML), and optional attachments.",
43
+ handler: {
44
+ endpoint: "/v1/emails/send",
45
+ method: "POST"
46
+ },
47
+ parameters: [
48
+ { name: "to", type: "string", required: true, description: "Recipient email address.", example: "alice@example.com" },
49
+ { name: "subject", type: "string", required: true, description: "Email subject line.", example: "Hello" },
50
+ { name: "body", type: "string", required: true, description: "Email body content.", example: "Hi there!" }
51
+ ],
52
+ response_example: {
53
+ status: 200,
54
+ body: {
55
+ success: true,
56
+ data: { message_id: "msg_abc123", status: "sent" }
57
+ }
58
+ },
59
+ auth_scopes: ["email.send"],
60
+ rate_limits: { requests_per_minute: 60 }
61
+ },
62
+ {
63
+ name: "list_inbox",
64
+ description: "List recent emails in the authenticated user's inbox with optional filtering.",
65
+ handler: {
66
+ endpoint: "/v1/emails/inbox",
67
+ method: "GET"
68
+ },
69
+ parameters: [
70
+ { name: "limit", type: "number", required: false, description: "Max emails to return.", example: 20 },
71
+ { name: "sender", type: "string", required: false, description: "Filter by sender address.", example: "bob@example.com" }
72
+ ],
73
+ auth_scopes: ["email.read"],
74
+ rate_limits: { requests_per_minute: 120 }
75
+ }
76
+ ]
77
+ }));
78
+
79
+ app.listen(3000, () => console.log('Listening on :3000'));
80
+ ```
81
+
82
+ This registers two endpoints automatically:
83
+
84
+ | Endpoint | Description |
85
+ |----------|-------------|
86
+ | `GET /.well-known/agent` | The service manifest (JSON) |
87
+ | `GET /.well-known/agent/capabilities/:name` | Detail for a single capability |
88
+
89
+ Both endpoints include `Access-Control-Allow-Origin: *` and `Cache-Control: public, max-age=3600` headers.
90
+
91
+ ## Configuration Reference
92
+
93
+ ### Top-level fields
94
+
95
+ | Field | Type | Required | Description |
96
+ |-------|------|----------|-------------|
97
+ | `name` | `string` | Yes | Human-readable service name. |
98
+ | `description` | `string` | Yes | 2-3 sentences describing the service. Written for an LLM to understand what this service does and when to use it. |
99
+ | `base_url` | `string` | Yes | Base URL for all API calls. |
100
+ | `auth` | `object` | Yes | Authentication configuration (see below). |
101
+ | `pricing` | `object` | No | Pricing information (see below). |
102
+ | `capabilities` | `array` | Yes | List of capability objects (see below). |
103
+
104
+ ### Auth object
105
+
106
+ **OAuth 2.0:**
107
+
108
+ ```js
109
+ {
110
+ type: "oauth2",
111
+ authorization_url: "https://auth.example.com/authorize",
112
+ token_url: "https://auth.example.com/token",
113
+ scopes: ["read", "write"]
114
+ }
115
+ ```
116
+
117
+ **API Key:**
118
+
119
+ ```js
120
+ {
121
+ type: "api_key",
122
+ header: "Authorization", // optional, defaults to "Authorization"
123
+ prefix: "Bearer", // optional
124
+ setup_url: "https://example.com/api-keys" // optional
125
+ }
126
+ ```
127
+
128
+ **None (public API):**
129
+
130
+ ```js
131
+ { type: "none" }
132
+ ```
133
+
134
+ ### Pricing object (optional)
135
+
136
+ ```js
137
+ {
138
+ type: "free", // "free" | "freemium" | "paid"
139
+ plans: [
140
+ { name: "Free", price: "$0/mo", limits: "1000 requests/day" }
141
+ ],
142
+ plans_url: "https://example.com/pricing"
143
+ }
144
+ ```
145
+
146
+ ### Capability object
147
+
148
+ | Field | Type | Required | Description |
149
+ |-------|------|----------|-------------|
150
+ | `name` | `string` | Yes | Machine-readable identifier (snake_case). |
151
+ | `description` | `string` | Yes | 1-2 sentence description for LLM understanding. |
152
+ | `handler` | `object` | Yes | `{ endpoint: string, method: string }` — the real API route. |
153
+ | `parameters` | `array` | No | Parameter definitions (see below). |
154
+ | `request_example` | `object` | No | Complete request example. Auto-generated if omitted. |
155
+ | `response_example` | `object` | No | Complete response example. |
156
+ | `auth_scopes` | `string[]` | No | OAuth scopes needed for this capability. |
157
+ | `rate_limits` | `object` | No | Rate limiting info (e.g. `{ requests_per_minute: 60 }`). |
158
+
159
+ ### Parameter object
160
+
161
+ | Field | Type | Required | Description |
162
+ |-------|------|----------|-------------|
163
+ | `name` | `string` | Yes | Parameter name. |
164
+ | `type` | `string` | Yes | Type: `string`, `number`, `boolean`, `object`, `string[]`, `object[]`. |
165
+ | `required` | `boolean` | Yes | Whether this parameter is required. |
166
+ | `description` | `string` | Yes | What this parameter does. |
167
+ | `example` | `any` | No | Example value (used in auto-generated request_example). |
168
+
169
+ ## What Gets Generated
170
+
171
+ ### Manifest (`GET /.well-known/agent`)
172
+
173
+ Returns a spec-v1.0 manifest with `detail_url` auto-populated for each capability:
174
+
175
+ ```json
176
+ {
177
+ "spec_version": "1.0",
178
+ "name": "Acme Email",
179
+ "description": "Send and receive emails programmatically...",
180
+ "base_url": "https://api.acme-email.com",
181
+ "auth": { "type": "api_key", "header": "Authorization", "prefix": "Bearer", "setup_url": "..." },
182
+ "pricing": { "type": "freemium", "plans": [...], "plans_url": "..." },
183
+ "capabilities": [
184
+ {
185
+ "name": "send_email",
186
+ "description": "Send an email to one or more recipients...",
187
+ "detail_url": "/.well-known/agent/capabilities/send_email"
188
+ }
189
+ ]
190
+ }
191
+ ```
192
+
193
+ ### Capability Detail (`GET /.well-known/agent/capabilities/:name`)
194
+
195
+ Returns everything an agent needs to call the API:
196
+
197
+ ```json
198
+ {
199
+ "name": "send_email",
200
+ "description": "Send an email to one or more recipients...",
201
+ "endpoint": "/v1/emails/send",
202
+ "method": "POST",
203
+ "parameters": [
204
+ { "name": "to", "type": "string", "description": "Recipient email address.", "required": true, "example": "alice@example.com" }
205
+ ],
206
+ "request_example": {
207
+ "method": "POST",
208
+ "url": "https://api.acme-email.com/v1/emails/send",
209
+ "headers": { "Authorization": "Bearer {api_key}", "Content-Type": "application/json" },
210
+ "body": { "to": "alice@example.com", "subject": "Hello", "body": "Hi there!" }
211
+ },
212
+ "response_example": { "status": 200, "body": { "success": true, "data": { "message_id": "msg_abc123" } } },
213
+ "auth_scopes": ["email.send"],
214
+ "rate_limits": { "requests_per_minute": 60 }
215
+ }
216
+ ```
217
+
218
+ If you omit `request_example` from a capability, the middleware generates one automatically from `base_url`, `handler`, `auth`, and the required parameters' example values.
219
+
220
+ ## Startup Validation
221
+
222
+ The middleware validates your configuration on startup and logs warnings via `console.warn` for any issues (missing fields, invalid types, etc.). It does **not** throw or crash — your server will still start even with a misconfigured manifest.
223
+
224
+ ## Spec
225
+
226
+ This middleware implements the [Agent Discovery Protocol Specification v1.0](https://github.com/user/agent-discovery-protocol/tree/main/spec).
227
+
228
+ ## Registry
229
+
230
+ Register your service in the [Agent Discovery Registry](https://registry.agentdiscovery.dev) so AI agents can find it.
231
+
232
+ ## License
233
+
234
+ MIT
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "agent-well-known-express",
3
+ "version": "1.0.1",
4
+ "description": "Express middleware to serve the Agent Discovery Protocol (/.well-known/agent) manifest and capability detail endpoints.",
5
+ "main": "src/index.js",
6
+ "types": "src/index.d.ts",
7
+ "files": [
8
+ "src/",
9
+ "LICENSE",
10
+ "README.md"
11
+ ],
12
+ "keywords": [
13
+ "agent-discovery",
14
+ "well-known",
15
+ "express",
16
+ "middleware",
17
+ "ai-agent",
18
+ "mcp",
19
+ "service-discovery",
20
+ "agent-discovery-protocol"
21
+ ],
22
+ "author": "",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/user/agent-discovery-protocol",
27
+ "directory": "sdks/express"
28
+ },
29
+ "homepage": "https://github.com/user/agent-discovery-protocol/tree/main/sdks/express",
30
+ "bugs": {
31
+ "url": "https://github.com/user/agent-discovery-protocol/issues"
32
+ },
33
+ "peerDependencies": {
34
+ "express": "^4.0.0 || ^5.0.0"
35
+ },
36
+ "engines": {
37
+ "node": ">=14.0.0"
38
+ }
39
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,140 @@
1
+ import { Router } from 'express';
2
+
3
+ /**
4
+ * Auth configuration for the service manifest.
5
+ */
6
+ export interface AuthOAuth2 {
7
+ type: 'oauth2';
8
+ authorization_url: string;
9
+ token_url: string;
10
+ scopes?: string[];
11
+ }
12
+
13
+ export interface AuthApiKey {
14
+ type: 'api_key';
15
+ header?: string;
16
+ prefix?: string;
17
+ setup_url?: string;
18
+ }
19
+
20
+ export interface AuthNone {
21
+ type: 'none';
22
+ }
23
+
24
+ export type AuthConfig = AuthOAuth2 | AuthApiKey | AuthNone;
25
+
26
+ /**
27
+ * A pricing plan entry.
28
+ */
29
+ export interface PricingPlan {
30
+ name: string;
31
+ price: string;
32
+ limits?: string;
33
+ }
34
+
35
+ /**
36
+ * Pricing configuration for the service manifest.
37
+ */
38
+ export interface PricingConfig {
39
+ type: 'free' | 'freemium' | 'paid';
40
+ plans?: PricingPlan[];
41
+ plans_url?: string;
42
+ }
43
+
44
+ /**
45
+ * A parameter definition for a capability.
46
+ */
47
+ export interface ParameterConfig {
48
+ name: string;
49
+ type: string;
50
+ required: boolean;
51
+ description: string;
52
+ example?: any;
53
+ }
54
+
55
+ /**
56
+ * Handler configuration specifying the actual API endpoint.
57
+ */
58
+ export interface HandlerConfig {
59
+ endpoint: string;
60
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
61
+ }
62
+
63
+ /**
64
+ * Rate limit configuration for a capability.
65
+ */
66
+ export interface RateLimitConfig {
67
+ requests_per_minute?: number;
68
+ daily_limit?: number;
69
+ [key: string]: any;
70
+ }
71
+
72
+ /**
73
+ * Example request object following the spec.
74
+ */
75
+ export interface RequestExample {
76
+ method: string;
77
+ url: string;
78
+ headers?: Record<string, string>;
79
+ body?: any;
80
+ }
81
+
82
+ /**
83
+ * Example response object following the spec.
84
+ */
85
+ export interface ResponseExample {
86
+ status: number;
87
+ body?: any;
88
+ }
89
+
90
+ /**
91
+ * A capability definition provided by the user.
92
+ */
93
+ export interface CapabilityConfig {
94
+ /** Machine-readable identifier (snake_case). */
95
+ name: string;
96
+ /** 1-2 sentence description for LLM understanding. */
97
+ description: string;
98
+ /** The real API endpoint and HTTP method for this capability. */
99
+ handler: HandlerConfig;
100
+ /** Parameter definitions. */
101
+ parameters?: ParameterConfig[];
102
+ /** A complete request example. Auto-generated from parameters if omitted. */
103
+ request_example?: RequestExample;
104
+ /** A complete response example. */
105
+ response_example?: ResponseExample;
106
+ /** OAuth scopes needed for this capability. */
107
+ auth_scopes?: string[];
108
+ /** Rate limiting information. */
109
+ rate_limits?: RateLimitConfig;
110
+ }
111
+
112
+ /**
113
+ * Full service configuration passed to `agentManifest()`.
114
+ */
115
+ export interface AgentManifestConfig {
116
+ /** Human-readable service name. */
117
+ name: string;
118
+ /** 2-3 sentence description of the service, written for an LLM. */
119
+ description: string;
120
+ /** Base URL for all API calls. */
121
+ base_url: string;
122
+ /** Authentication configuration. */
123
+ auth: AuthConfig;
124
+ /** Pricing information (optional). */
125
+ pricing?: PricingConfig;
126
+ /** List of capabilities the service exposes. */
127
+ capabilities: CapabilityConfig[];
128
+ }
129
+
130
+ /**
131
+ * Create an Express Router that serves Agent Discovery Protocol endpoints.
132
+ *
133
+ * Registers:
134
+ * GET /.well-known/agent — service manifest (JSON)
135
+ * GET /.well-known/agent/capabilities/:name — capability detail (JSON)
136
+ *
137
+ * @param config Service configuration following the Agent Discovery Protocol spec v1.0.
138
+ * @returns An Express Router to mount with `app.use()`.
139
+ */
140
+ export function agentManifest(config: AgentManifestConfig): Router;
package/src/index.js ADDED
@@ -0,0 +1,340 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * agent-well-known-express
5
+ *
6
+ * Express middleware that auto-generates Agent Discovery Protocol endpoints:
7
+ * GET /.well-known/agent — the service manifest
8
+ * GET /.well-known/agent/capabilities/:name — capability detail
9
+ *
10
+ * Spec: https://github.com/user/agent-discovery-protocol/tree/main/spec
11
+ */
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Validation
15
+ // ---------------------------------------------------------------------------
16
+
17
+ const VALID_AUTH_TYPES = ['oauth2', 'api_key', 'none'];
18
+ const VALID_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
19
+
20
+ /**
21
+ * Validate the user-supplied config and return an array of warning strings.
22
+ * Does NOT throw — issues are advisory so the server can still start.
23
+ */
24
+ function validateConfig(config) {
25
+ const warnings = [];
26
+
27
+ if (!config) {
28
+ warnings.push('Config is missing or undefined.');
29
+ return warnings;
30
+ }
31
+
32
+ // Top-level required fields
33
+ if (!config.name || typeof config.name !== 'string') {
34
+ warnings.push('Missing or invalid "name" (expected non-empty string).');
35
+ }
36
+ if (!config.description || typeof config.description !== 'string') {
37
+ warnings.push('Missing or invalid "description" (expected non-empty string).');
38
+ }
39
+ if (!config.base_url || typeof config.base_url !== 'string') {
40
+ warnings.push('Missing or invalid "base_url" (expected non-empty string).');
41
+ }
42
+
43
+ // Auth
44
+ if (!config.auth || typeof config.auth !== 'object') {
45
+ warnings.push('Missing or invalid "auth" object.');
46
+ } else if (!config.auth.type || !VALID_AUTH_TYPES.includes(config.auth.type)) {
47
+ warnings.push(
48
+ 'Invalid "auth.type". Expected one of: ' + VALID_AUTH_TYPES.join(', ') + '.'
49
+ );
50
+ }
51
+
52
+ // Capabilities
53
+ if (!Array.isArray(config.capabilities) || config.capabilities.length === 0) {
54
+ warnings.push('Missing or empty "capabilities" array.');
55
+ } else {
56
+ config.capabilities.forEach(function (cap, idx) {
57
+ var prefix = 'capabilities[' + idx + ']';
58
+
59
+ if (!cap.name || typeof cap.name !== 'string') {
60
+ warnings.push(prefix + ': Missing or invalid "name".');
61
+ }
62
+ if (!cap.description || typeof cap.description !== 'string') {
63
+ warnings.push(prefix + ': Missing or invalid "description".');
64
+ }
65
+ if (!cap.handler || typeof cap.handler !== 'object') {
66
+ warnings.push(prefix + ': Missing or invalid "handler" object.');
67
+ } else {
68
+ if (!cap.handler.endpoint || typeof cap.handler.endpoint !== 'string') {
69
+ warnings.push(prefix + ': Missing "handler.endpoint".');
70
+ }
71
+ if (!cap.handler.method || typeof cap.handler.method !== 'string') {
72
+ warnings.push(prefix + ': Missing "handler.method".');
73
+ } else if (!VALID_METHODS.includes(cap.handler.method.toUpperCase())) {
74
+ warnings.push(
75
+ prefix +
76
+ ': Invalid "handler.method" (' +
77
+ cap.handler.method +
78
+ '). Expected one of: ' +
79
+ VALID_METHODS.join(', ') +
80
+ '.'
81
+ );
82
+ }
83
+ }
84
+
85
+ // Parameters
86
+ if (cap.parameters) {
87
+ if (!Array.isArray(cap.parameters)) {
88
+ warnings.push(prefix + ': "parameters" must be an array.');
89
+ } else {
90
+ cap.parameters.forEach(function (param, pIdx) {
91
+ var pPrefix = prefix + '.parameters[' + pIdx + ']';
92
+ if (!param.name || typeof param.name !== 'string') {
93
+ warnings.push(pPrefix + ': Missing or invalid "name".');
94
+ }
95
+ if (!param.type || typeof param.type !== 'string') {
96
+ warnings.push(pPrefix + ': Missing or invalid "type".');
97
+ }
98
+ if (typeof param.required !== 'boolean') {
99
+ warnings.push(pPrefix + ': Missing or invalid "required" (expected boolean).');
100
+ }
101
+ if (!param.description || typeof param.description !== 'string') {
102
+ warnings.push(pPrefix + ': Missing or invalid "description".');
103
+ }
104
+ });
105
+ }
106
+ }
107
+ });
108
+ }
109
+
110
+ return warnings;
111
+ }
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // Helpers
115
+ // ---------------------------------------------------------------------------
116
+
117
+ /**
118
+ * Build the auth header representation used in auto-generated request examples.
119
+ */
120
+ function buildAuthHeader(auth) {
121
+ if (!auth) return {};
122
+ switch (auth.type) {
123
+ case 'oauth2':
124
+ return { Authorization: 'Bearer {access_token}' };
125
+ case 'api_key': {
126
+ var header = auth.header || 'Authorization';
127
+ var prefix = auth.prefix ? auth.prefix + ' ' : '';
128
+ var obj = {};
129
+ obj[header] = prefix + '{api_key}';
130
+ return obj;
131
+ }
132
+ default:
133
+ return {};
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Auto-generate a request_example from the capability definition when the user
139
+ * has not provided one explicitly.
140
+ */
141
+ function generateRequestExample(cap, config) {
142
+ var handler = cap.handler || {};
143
+ var method = (handler.method || 'GET').toUpperCase();
144
+ var baseUrl = (config.base_url || '').replace(/\/+$/, '');
145
+ var url = baseUrl + (handler.endpoint || '');
146
+
147
+ var headers = Object.assign(
148
+ {},
149
+ buildAuthHeader(config.auth),
150
+ { 'Content-Type': 'application/json' }
151
+ );
152
+
153
+ var example = {
154
+ method: method,
155
+ url: url,
156
+ headers: headers,
157
+ };
158
+
159
+ // Build body from parameters using their example values
160
+ if (['POST', 'PUT', 'PATCH'].includes(method) && Array.isArray(cap.parameters)) {
161
+ var body = {};
162
+ cap.parameters.forEach(function (param) {
163
+ if (param.required && param.example !== undefined) {
164
+ body[param.name] = param.example;
165
+ }
166
+ });
167
+ if (Object.keys(body).length > 0) {
168
+ example.body = body;
169
+ }
170
+ }
171
+
172
+ return example;
173
+ }
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // Manifest builder
177
+ // ---------------------------------------------------------------------------
178
+
179
+ /**
180
+ * Build the spec-v1.0 manifest JSON from the user config.
181
+ */
182
+ function buildManifest(config) {
183
+ var manifest = {
184
+ spec_version: '1.0',
185
+ name: config.name,
186
+ description: config.description,
187
+ base_url: config.base_url,
188
+ auth: config.auth,
189
+ };
190
+
191
+ if (config.pricing) {
192
+ manifest.pricing = config.pricing;
193
+ }
194
+
195
+ manifest.capabilities = (config.capabilities || []).map(function (cap) {
196
+ var name = cap.name || '';
197
+ return {
198
+ name: name,
199
+ description: cap.description || '',
200
+ detail_url: '/.well-known/agent/capabilities/' + name,
201
+ };
202
+ });
203
+
204
+ return manifest;
205
+ }
206
+
207
+ /**
208
+ * Build the capability detail JSON for a single capability.
209
+ */
210
+ function buildCapabilityDetail(cap, config) {
211
+ var handler = cap.handler || {};
212
+ var detail = {
213
+ name: cap.name || '',
214
+ description: cap.description || '',
215
+ endpoint: handler.endpoint || '',
216
+ method: (handler.method || 'GET').toUpperCase(),
217
+ parameters: (cap.parameters || []).map(function (p) {
218
+ var param = {
219
+ name: p.name,
220
+ type: p.type,
221
+ description: p.description,
222
+ required: p.required,
223
+ };
224
+ if (p.example !== undefined) {
225
+ param.example = p.example;
226
+ }
227
+ return param;
228
+ }),
229
+ request_example: cap.request_example || generateRequestExample(cap, config),
230
+ };
231
+
232
+ if (cap.response_example) {
233
+ detail.response_example = cap.response_example;
234
+ }
235
+
236
+ if (cap.auth_scopes) {
237
+ detail.auth_scopes = cap.auth_scopes;
238
+ }
239
+
240
+ if (cap.rate_limits) {
241
+ detail.rate_limits = cap.rate_limits;
242
+ }
243
+
244
+ return detail;
245
+ }
246
+
247
+ // ---------------------------------------------------------------------------
248
+ // Middleware
249
+ // ---------------------------------------------------------------------------
250
+
251
+ /**
252
+ * Create and return an Express Router that serves the Agent Discovery Protocol
253
+ * endpoints for the given service configuration.
254
+ *
255
+ * @param {object} config — service configuration (see README for full reference)
256
+ * @returns {import('express').Router}
257
+ */
258
+ function agentManifest(config) {
259
+ // We require express at call-time so it's resolved from the host app's
260
+ // node_modules (peer dependency).
261
+ var express;
262
+ try {
263
+ express = require('express');
264
+ } catch (_err) {
265
+ throw new Error(
266
+ 'agent-well-known-express: "express" is a peer dependency and must be installed in your project.'
267
+ );
268
+ }
269
+
270
+ // --- Validate --------------------------------------------------------
271
+ var warnings = validateConfig(config);
272
+ if (warnings.length > 0) {
273
+ warnings.forEach(function (w) {
274
+ console.warn('[agent-well-known-express] ' + w);
275
+ });
276
+ }
277
+
278
+ // --- Pre-compute manifest & detail map --------------------------------
279
+ var manifest = buildManifest(config);
280
+ var manifestJSON = JSON.stringify(manifest);
281
+
282
+ var capabilityDetails = {};
283
+ (config.capabilities || []).forEach(function (cap) {
284
+ capabilityDetails[cap.name] = buildCapabilityDetail(cap, config);
285
+ });
286
+
287
+ // --- Router -----------------------------------------------------------
288
+ var router = express.Router();
289
+
290
+ // CORS preflight for /.well-known/agent paths
291
+ router.options('/.well-known/agent', function (_req, res) {
292
+ res.set('Access-Control-Allow-Origin', '*');
293
+ res.set('Access-Control-Allow-Methods', 'GET, OPTIONS');
294
+ res.set('Access-Control-Allow-Headers', 'Content-Type');
295
+ res.status(204).end();
296
+ });
297
+
298
+ router.options('/.well-known/agent/capabilities/:name', function (_req, res) {
299
+ res.set('Access-Control-Allow-Origin', '*');
300
+ res.set('Access-Control-Allow-Methods', 'GET, OPTIONS');
301
+ res.set('Access-Control-Allow-Headers', 'Content-Type');
302
+ res.status(204).end();
303
+ });
304
+
305
+ // GET /.well-known/agent — the manifest
306
+ router.get('/.well-known/agent', function (_req, res) {
307
+ res.set('Content-Type', 'application/json');
308
+ res.set('Access-Control-Allow-Origin', '*');
309
+ res.set('Cache-Control', 'public, max-age=3600');
310
+ res.status(200).send(manifestJSON);
311
+ });
312
+
313
+ // GET /.well-known/agent/capabilities/:name — capability detail
314
+ router.get('/.well-known/agent/capabilities/:name', function (req, res) {
315
+ var name = req.params.name;
316
+ var detail = capabilityDetails[name];
317
+
318
+ if (!detail) {
319
+ res.set('Content-Type', 'application/json');
320
+ res.set('Access-Control-Allow-Origin', '*');
321
+ res.status(404).json({
322
+ error: 'Capability not found: ' + name,
323
+ });
324
+ return;
325
+ }
326
+
327
+ res.set('Content-Type', 'application/json');
328
+ res.set('Access-Control-Allow-Origin', '*');
329
+ res.set('Cache-Control', 'public, max-age=3600');
330
+ res.status(200).json(detail);
331
+ });
332
+
333
+ return router;
334
+ }
335
+
336
+ // ---------------------------------------------------------------------------
337
+ // Exports
338
+ // ---------------------------------------------------------------------------
339
+
340
+ module.exports = { agentManifest };