@apono-io/mcp-aws-managed 0.2.0
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/README.md +30 -0
- package/dist/failures.d.ts +14 -0
- package/dist/failures.d.ts.map +1 -0
- package/dist/failures.js +36 -0
- package/dist/failures.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/run.d.ts +24 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +94 -0
- package/dist/run.js.map +1 -0
- package/dist/signing.d.ts +18 -0
- package/dist/signing.d.ts.map +1 -0
- package/dist/signing.js +61 -0
- package/dist/signing.js.map +1 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# @apono-io/mcp-aws-managed
|
|
2
|
+
|
|
3
|
+
Apono MCP server for AWS. Assumes the granted role via STS, then proxies stdio MCP
|
|
4
|
+
traffic to the [managed AWS MCP Server](https://docs.aws.amazon.com/agent-toolkit/latest/userguide/getting-started-aws-mcp-server.html),
|
|
5
|
+
signing every request with SigV4.
|
|
6
|
+
|
|
7
|
+
Unlike `@apono-io/mcp-aws` it spawns no vendor process, so `uvx`, `uv` and Python
|
|
8
|
+
are not required on the host.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
Launched by the Apono CLI via `npx`. Environment variables:
|
|
13
|
+
|
|
14
|
+
| Variable | Description |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `AWS_ACCESS_KEY_ID` | Brokered IAM credentials — access key |
|
|
17
|
+
| `AWS_SECRET_ACCESS_KEY` | Brokered IAM credentials — secret key |
|
|
18
|
+
| `AWS_SESSION_TOKEN` | Brokered IAM credentials — session token |
|
|
19
|
+
| `AWS_ROLE_ARN` | ARN of the role to assume via STS |
|
|
20
|
+
| `AWS_REGION` | Region for the STS call (default `us-east-1`) |
|
|
21
|
+
| `AWS_MCP_ENDPOINT` | Override the endpoint (default `https://aws-mcp.us-east-1.api.aws/mcp`; `https://aws-mcp.eu-central-1.api.aws/mcp` also exists) |
|
|
22
|
+
|
|
23
|
+
The SigV4 signing service and region are derived from the endpoint hostname, so
|
|
24
|
+
overriding `AWS_MCP_ENDPOINT` is enough to switch region.
|
|
25
|
+
|
|
26
|
+
## Limitations
|
|
27
|
+
|
|
28
|
+
The role is assumed once at startup, as in `@apono-io/mcp-aws`. Sessions outliving
|
|
29
|
+
the assumed credentials will see `ExpiredToken` surface as tool errors rather than
|
|
30
|
+
as a startup failure.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const FailureCategory: {
|
|
2
|
+
readonly MissingEnv: "missing_env";
|
|
3
|
+
readonly CredentialExpired: "credential_expired";
|
|
4
|
+
readonly CredentialInvalid: "credential_invalid";
|
|
5
|
+
readonly AssumeRoleDenied: "assume_role_denied";
|
|
6
|
+
};
|
|
7
|
+
export type FailureCategory = (typeof FailureCategory)[keyof typeof FailureCategory];
|
|
8
|
+
export declare class StartupFailure extends Error {
|
|
9
|
+
readonly category: FailureCategory;
|
|
10
|
+
constructor(category: FailureCategory, message: string);
|
|
11
|
+
}
|
|
12
|
+
export declare function stsFailureCategory(error: unknown): FailureCategory | undefined;
|
|
13
|
+
export declare function reportFailure(error: unknown): void;
|
|
14
|
+
//# sourceMappingURL=failures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failures.d.ts","sourceRoot":"","sources":["../src/failures.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe;;;;;CAKlB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,CAAC;AAYrF,qBAAa,cAAe,SAAQ,KAAK;IAErC,QAAQ,CAAC,QAAQ,EAAE,eAAe;gBAAzB,QAAQ,EAAE,eAAe,EAClC,OAAO,EAAE,MAAM;CAKlB;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAG9E;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAKlD"}
|
package/dist/failures.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const FailureCategory = {
|
|
2
|
+
MissingEnv: 'missing_env',
|
|
3
|
+
CredentialExpired: 'credential_expired',
|
|
4
|
+
CredentialInvalid: 'credential_invalid',
|
|
5
|
+
AssumeRoleDenied: 'assume_role_denied',
|
|
6
|
+
};
|
|
7
|
+
const MARKER_PREFIX = 'APONO_MCP_ERROR:';
|
|
8
|
+
const STS_ERROR_CATEGORIES = [
|
|
9
|
+
['ExpiredToken', FailureCategory.CredentialExpired],
|
|
10
|
+
['InvalidClientTokenId', FailureCategory.CredentialInvalid],
|
|
11
|
+
['UnrecognizedClient', FailureCategory.CredentialInvalid],
|
|
12
|
+
['SignatureDoesNotMatch', FailureCategory.CredentialInvalid],
|
|
13
|
+
['AccessDenied', FailureCategory.AssumeRoleDenied],
|
|
14
|
+
];
|
|
15
|
+
export class StartupFailure extends Error {
|
|
16
|
+
category;
|
|
17
|
+
constructor(category, message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.category = category;
|
|
20
|
+
this.name = 'StartupFailure';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function stsFailureCategory(error) {
|
|
24
|
+
const name = error instanceof Error ? error.name : '';
|
|
25
|
+
return STS_ERROR_CATEGORIES.find(([code]) => name.includes(code))?.[1];
|
|
26
|
+
}
|
|
27
|
+
export function reportFailure(error) {
|
|
28
|
+
process.stderr.write(`Error: ${message(error)}\n`);
|
|
29
|
+
if (error instanceof StartupFailure) {
|
|
30
|
+
process.stderr.write(`${MARKER_PREFIX} ${error.category}\n`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function message(error) {
|
|
34
|
+
return error instanceof StartupFailure ? error.message : String(error);
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=failures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failures.js","sourceRoot":"","sources":["../src/failures.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,UAAU,EAAE,aAAa;IACzB,iBAAiB,EAAE,oBAAoB;IACvC,iBAAiB,EAAE,oBAAoB;IACvC,gBAAgB,EAAE,oBAAoB;CAC9B,CAAC;AAIX,MAAM,aAAa,GAAG,kBAAkB,CAAC;AAEzC,MAAM,oBAAoB,GAAsD;IAC9E,CAAC,cAAc,EAAE,eAAe,CAAC,iBAAiB,CAAC;IACnD,CAAC,sBAAsB,EAAE,eAAe,CAAC,iBAAiB,CAAC;IAC3D,CAAC,oBAAoB,EAAE,eAAe,CAAC,iBAAiB,CAAC;IACzD,CAAC,uBAAuB,EAAE,eAAe,CAAC,iBAAiB,CAAC;IAC5D,CAAC,cAAc,EAAE,eAAe,CAAC,gBAAgB,CAAC;CACnD,CAAC;AAEF,MAAM,OAAO,cAAe,SAAQ,KAAK;IAE5B;IADX,YACW,QAAyB,EAClC,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAHN,aAAQ,GAAR,QAAQ,CAAiB;QAIlC,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,MAAM,IAAI,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnD,IAAI,KAAK,YAAY,cAAc,EAAE,CAAC;QACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,aAAa,IAAI,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC;IAC/D,CAAC;AACH,CAAC;AAED,SAAS,OAAO,CAAC,KAAc;IAC7B,OAAO,KAAK,YAAY,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzE,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IACtC,aAAa,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/run.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { STSClient } from '@aws-sdk/client-sts';
|
|
2
|
+
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
3
|
+
export declare const DEFAULT_ENDPOINT = "https://aws-mcp.us-east-1.api.aws/mcp";
|
|
4
|
+
export interface AwsCredentials {
|
|
5
|
+
accessKeyId: string;
|
|
6
|
+
secretAccessKey: string;
|
|
7
|
+
sessionToken: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function validateEnv(env: NodeJS.ProcessEnv): {
|
|
10
|
+
credentials: AwsCredentials;
|
|
11
|
+
roleArn: string;
|
|
12
|
+
region: string;
|
|
13
|
+
endpoint: URL;
|
|
14
|
+
};
|
|
15
|
+
export declare function assumeRole(sts: STSClient, roleArn: string): Promise<AwsCredentials>;
|
|
16
|
+
export declare function assumeRoleOrFail(sts: STSClient, roleArn: string): Promise<AwsCredentials>;
|
|
17
|
+
/**
|
|
18
|
+
* Pumps JSON-RPC messages between the two transports without interpreting them,
|
|
19
|
+
* so every tool, notification and future protocol method passes through
|
|
20
|
+
* untouched. The only thing this process adds is the SigV4 signature.
|
|
21
|
+
*/
|
|
22
|
+
export declare function relay(local: Transport, remote: Transport): void;
|
|
23
|
+
export declare function run(env: NodeJS.ProcessEnv): Promise<void>;
|
|
24
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAW/E,eAAO,MAAM,gBAAgB,0CAA0C,CAAC;AAExE,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG;IACnD,WAAW,EAAE,cAAc,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC;CACf,CAoBA;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAgBzF;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAU/F;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI,CAS/D;AAMD,wBAAsB,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB/D"}
|
package/dist/run.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts';
|
|
2
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { FailureCategory, StartupFailure, stsFailureCategory } from './failures.js';
|
|
5
|
+
import { createSignedFetch } from './signing.js';
|
|
6
|
+
const REQUIRED_ENV = [
|
|
7
|
+
'AWS_ACCESS_KEY_ID',
|
|
8
|
+
'AWS_SECRET_ACCESS_KEY',
|
|
9
|
+
'AWS_SESSION_TOKEN',
|
|
10
|
+
'AWS_ROLE_ARN',
|
|
11
|
+
];
|
|
12
|
+
export const DEFAULT_ENDPOINT = 'https://aws-mcp.us-east-1.api.aws/mcp';
|
|
13
|
+
export function validateEnv(env) {
|
|
14
|
+
for (const key of REQUIRED_ENV) {
|
|
15
|
+
if (!env[key]) {
|
|
16
|
+
throw new StartupFailure(FailureCategory.MissingEnv, `Missing required environment variable: ${key}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
credentials: {
|
|
21
|
+
accessKeyId: env.AWS_ACCESS_KEY_ID,
|
|
22
|
+
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
|
|
23
|
+
sessionToken: env.AWS_SESSION_TOKEN,
|
|
24
|
+
},
|
|
25
|
+
roleArn: env.AWS_ROLE_ARN,
|
|
26
|
+
region: env.AWS_REGION ?? env.AWS_DEFAULT_REGION ?? 'us-east-1',
|
|
27
|
+
endpoint: new URL(env.AWS_MCP_ENDPOINT ?? DEFAULT_ENDPOINT),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export async function assumeRole(sts, roleArn) {
|
|
31
|
+
const response = await sts.send(new AssumeRoleCommand({
|
|
32
|
+
RoleArn: roleArn,
|
|
33
|
+
RoleSessionName: `apono-mcp-aws-${Date.now()}`,
|
|
34
|
+
}));
|
|
35
|
+
const credentials = response.Credentials;
|
|
36
|
+
if (!credentials?.AccessKeyId || !credentials?.SecretAccessKey || !credentials?.SessionToken) {
|
|
37
|
+
throw new Error('STS response missing credentials fields');
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
accessKeyId: credentials.AccessKeyId,
|
|
41
|
+
secretAccessKey: credentials.SecretAccessKey,
|
|
42
|
+
sessionToken: credentials.SessionToken,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export async function assumeRoleOrFail(sts, roleArn) {
|
|
46
|
+
try {
|
|
47
|
+
return await assumeRole(sts, roleArn);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const category = stsFailureCategory(error);
|
|
51
|
+
if (!category) {
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
throw new StartupFailure(category, `AssumeRole failed: ${String(error)}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Pumps JSON-RPC messages between the two transports without interpreting them,
|
|
59
|
+
* so every tool, notification and future protocol method passes through
|
|
60
|
+
* untouched. The only thing this process adds is the SigV4 signature.
|
|
61
|
+
*/
|
|
62
|
+
export function relay(local, remote) {
|
|
63
|
+
local.onmessage = (message) => {
|
|
64
|
+
remote.send(message).catch((err) => local.onerror?.(asError(err)));
|
|
65
|
+
};
|
|
66
|
+
remote.onmessage = (message) => {
|
|
67
|
+
local.send(message).catch((err) => remote.onerror?.(asError(err)));
|
|
68
|
+
};
|
|
69
|
+
local.onclose = () => void remote.close();
|
|
70
|
+
remote.onclose = () => void local.close();
|
|
71
|
+
}
|
|
72
|
+
function asError(err) {
|
|
73
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
74
|
+
}
|
|
75
|
+
export async function run(env) {
|
|
76
|
+
const { credentials, roleArn, region, endpoint } = validateEnv(env);
|
|
77
|
+
const assumed = await assumeRoleOrFail(new STSClient({ credentials, region }), roleArn);
|
|
78
|
+
const remote = new StreamableHTTPClientTransport(endpoint, {
|
|
79
|
+
fetch: createSignedFetch(endpoint, assumed),
|
|
80
|
+
});
|
|
81
|
+
const local = new StdioServerTransport();
|
|
82
|
+
relay(local, remote);
|
|
83
|
+
remote.onerror = (err) => process.stderr.write(`Upstream error: ${err.message}\n`);
|
|
84
|
+
await remote.start();
|
|
85
|
+
await local.start();
|
|
86
|
+
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
87
|
+
process.on(signal, () => {
|
|
88
|
+
void remote.close();
|
|
89
|
+
void local.close();
|
|
90
|
+
process.exit(0);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=run.js.map
|
package/dist/run.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AwsCredentialIdentity } from '@smithy/types';
|
|
2
|
+
export type FetchLike = (url: string | URL, init?: RequestInit) => Promise<Response>;
|
|
3
|
+
/**
|
|
4
|
+
* The managed AWS MCP endpoints are named `<service>.<region>.api.aws`, and the
|
|
5
|
+
* signature's credential scope must match both. AWS's own `mcp-proxy-for-aws`
|
|
6
|
+
* derives them from the hostname the same way, so
|
|
7
|
+
* `aws-mcp.us-east-1.api.aws` signs as service `aws-mcp` in `us-east-1`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function signingTarget(endpoint: URL): {
|
|
10
|
+
service: string;
|
|
11
|
+
region: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* A `fetch` that SigV4-signs every request before sending it. Handed to the MCP
|
|
15
|
+
* streamable-HTTP transport so both the POSTs and the GET event stream are signed.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createSignedFetch(endpoint: URL, credentials: AwsCredentialIdentity, fetchImpl?: FetchLike): FetchLike;
|
|
18
|
+
//# sourceMappingURL=signing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing.d.ts","sourceRoot":"","sources":["../src/signing.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAE3D,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAErF;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,GAAG,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAShF;AAoBD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,GAAG,EACb,WAAW,EAAE,qBAAqB,EAClC,SAAS,GAAE,SAAiB,GAC3B,SAAS,CA0BX"}
|
package/dist/signing.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Sha256 } from '@aws-crypto/sha256-js';
|
|
2
|
+
import { SignatureV4 } from '@smithy/signature-v4';
|
|
3
|
+
/**
|
|
4
|
+
* The managed AWS MCP endpoints are named `<service>.<region>.api.aws`, and the
|
|
5
|
+
* signature's credential scope must match both. AWS's own `mcp-proxy-for-aws`
|
|
6
|
+
* derives them from the hostname the same way, so
|
|
7
|
+
* `aws-mcp.us-east-1.api.aws` signs as service `aws-mcp` in `us-east-1`.
|
|
8
|
+
*/
|
|
9
|
+
export function signingTarget(endpoint) {
|
|
10
|
+
const [service, region, ...rest] = endpoint.hostname.split('.');
|
|
11
|
+
if (rest.join('.') !== 'api.aws' || !service || !region) {
|
|
12
|
+
throw new Error(`Cannot derive SigV4 service and region from endpoint host "${endpoint.hostname}"; ` +
|
|
13
|
+
'expected <service>.<region>.api.aws');
|
|
14
|
+
}
|
|
15
|
+
return { service, region };
|
|
16
|
+
}
|
|
17
|
+
function headerRecord(headers) {
|
|
18
|
+
const record = {};
|
|
19
|
+
new Headers(headers).forEach((value, key) => {
|
|
20
|
+
record[key] = value;
|
|
21
|
+
});
|
|
22
|
+
delete record.connection;
|
|
23
|
+
return record;
|
|
24
|
+
}
|
|
25
|
+
function queryRecord(url) {
|
|
26
|
+
const query = {};
|
|
27
|
+
for (const key of new Set(url.searchParams.keys())) {
|
|
28
|
+
const values = url.searchParams.getAll(key);
|
|
29
|
+
query[key] = values.length > 1 ? values : values[0];
|
|
30
|
+
}
|
|
31
|
+
return query;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A `fetch` that SigV4-signs every request before sending it. Handed to the MCP
|
|
35
|
+
* streamable-HTTP transport so both the POSTs and the GET event stream are signed.
|
|
36
|
+
*/
|
|
37
|
+
export function createSignedFetch(endpoint, credentials, fetchImpl = fetch) {
|
|
38
|
+
const { service, region } = signingTarget(endpoint);
|
|
39
|
+
const signer = new SignatureV4({ service, region, sha256: Sha256, credentials });
|
|
40
|
+
return async (input, init = {}) => {
|
|
41
|
+
const url = new URL(input.toString());
|
|
42
|
+
const body = typeof init.body === 'string' ? init.body : undefined;
|
|
43
|
+
const headers = headerRecord(init.headers);
|
|
44
|
+
headers.host = url.host;
|
|
45
|
+
if (body !== undefined) {
|
|
46
|
+
headers['content-length'] = String(Buffer.byteLength(body));
|
|
47
|
+
}
|
|
48
|
+
const signed = await signer.sign({
|
|
49
|
+
method: init.method ?? 'GET',
|
|
50
|
+
protocol: url.protocol,
|
|
51
|
+
hostname: url.hostname,
|
|
52
|
+
port: url.port ? Number(url.port) : undefined,
|
|
53
|
+
path: url.pathname,
|
|
54
|
+
query: queryRecord(url),
|
|
55
|
+
headers,
|
|
56
|
+
body,
|
|
57
|
+
});
|
|
58
|
+
return fetchImpl(url, { ...init, headers: signed.headers });
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=signing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing.js","sourceRoot":"","sources":["../src/signing.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@apono-io/mcp-aws-managed",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Apono MCP server for AWS — signs requests to the managed AWS MCP Server with STS AssumeRole credentials",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"apono-mcp-aws-managed": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.build.json",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@aws-crypto/sha256-js": "^5.2.0",
|
|
22
|
+
"@aws-sdk/client-sts": "^3.0.0",
|
|
23
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
24
|
+
"@smithy/signature-v4": "^5.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^22.0.0",
|
|
28
|
+
"typescript": "^5.7.0",
|
|
29
|
+
"vitest": "^3.0.0"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"author": "Apono",
|
|
35
|
+
"license": "ISC"
|
|
36
|
+
}
|